Compare commits
1576
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2e93b6466 | ||
|
|
0176ca6d78 | ||
|
|
5eda352fd4 | ||
|
|
5afe2ac2dc | ||
|
|
773246ccf7 | ||
|
|
734699ef61 | ||
|
|
ce0b29d20c | ||
|
|
ed7da973ff | ||
|
|
71142e7053 | ||
|
|
2927b0be95 | ||
|
|
e63e9ad701 | ||
|
|
d7a4be0267 | ||
|
|
d728101ace | ||
|
|
d31e0a18cb | ||
|
|
b268f563f6 | ||
|
|
62c1943b63 | ||
|
|
c5c9a7cd7b | ||
|
|
bcf346f13b | ||
|
|
2813ee66cb | ||
|
|
d31241d2c8 | ||
|
|
8c3d5d9d09 | ||
|
|
e7f54bfde8 | ||
|
|
d6a35ea98b | ||
|
|
b7a8e5ca52 | ||
|
|
3464b06c4c | ||
|
|
4edcdf297d | ||
|
|
d0b14aa29e | ||
|
|
89d72011e0 | ||
|
|
3793317199 | ||
|
|
e85f1e429d | ||
|
|
145c80df8d | ||
|
|
fb41178b75 | ||
|
|
ab07d6cc32 | ||
|
|
3e33d65ff9 | ||
|
|
e0fd3fea70 | ||
|
|
f610e46b01 | ||
|
|
e21232684f | ||
|
|
619f9fd372 | ||
|
|
42a61d742a | ||
|
|
65d62e1688 | ||
|
|
903f406ecb | ||
|
|
1bf1aea864 | ||
|
|
b5ee676e1d | ||
|
|
bb433ff031 | ||
|
|
2e3a8e1ccf | ||
|
|
870d08d9cb | ||
|
|
7271f004d4 | ||
|
|
71714d51d3 | ||
|
|
2a8403091c | ||
|
|
a615999e85 | ||
|
|
bbad000130 | ||
|
|
b3529d148e | ||
|
|
8549372c4c | ||
|
|
c27adcfdba | ||
|
|
45651fd866 | ||
|
|
28bc3aff6d | ||
|
|
7992e7f1ba | ||
|
|
3a10719283 | ||
|
|
52bcf848e5 | ||
|
|
5a99573420 | ||
|
|
c9f2293acc | ||
|
|
a9f4b4f480 | ||
|
|
25c57a1bbf | ||
|
|
293e955e3f | ||
|
|
9d5d1be482 | ||
|
|
785629d8d8 | ||
|
|
899fcf9b16 | ||
|
|
448c998191 | ||
|
|
0f916a843b | ||
|
|
4b94854902 | ||
|
|
c99c444ee9 | ||
|
|
24c7b7b9d8 | ||
|
|
903d127138 | ||
|
|
8476a08273 | ||
|
|
04166140e1 | ||
|
|
3c26757b41 | ||
|
|
ed92ac6e3f | ||
|
|
36549632ce | ||
|
|
562ad565fa | ||
|
|
3dcd2fa9b4 | ||
|
|
5c60b0c2af | ||
|
|
5113d251a8 | ||
|
|
ee7edb4f80 | ||
|
|
a9fed55df6 | ||
|
|
a38a062eee | ||
|
|
e2a8bbc6e1 | ||
|
|
8a1afc1fe1 | ||
|
|
845c5ee866 | ||
|
|
7cec68c4a6 | ||
|
|
ec4d691dad | ||
|
|
afb777d3f7 | ||
|
|
532cfd5c07 | ||
|
|
cccb82874c | ||
|
|
4abf97ab13 | ||
|
|
5ecfd27e8f | ||
|
|
e11ab1f9c6 | ||
|
|
4db7ac61ac | ||
|
|
6df3848fcd | ||
|
|
c9c09169e4 | ||
|
|
3edaf8a499 | ||
|
|
80ea4b55c0 | ||
|
|
cfcc1c6a1b | ||
|
|
b98d6e44fa | ||
|
|
9815039618 | ||
|
|
384f5acb62 | ||
|
|
7d377ee02b | ||
|
|
7172a7f1c5 | ||
|
|
4873b60b58 | ||
|
|
e5b505aba0 | ||
|
|
2a7b21b9ba | ||
|
|
bca20e6921 | ||
|
|
ab72240867 | ||
|
|
d279ef32ca | ||
|
|
dca8e74e47 | ||
|
|
530454b772 | ||
|
|
1a952817be | ||
|
|
6625f8ac5f | ||
|
|
441122013e | ||
|
|
5a54cdbbf1 | ||
|
|
eb6f6cc734 | ||
|
|
78ef24110a | ||
|
|
7772fec117 | ||
|
|
35be26d2a9 | ||
|
|
24a830de3c | ||
|
|
7781af869b | ||
|
|
584c7c0fef | ||
|
|
71da12569d | ||
|
|
fba89949a3 | ||
|
|
79f002fc2a | ||
|
|
7bb1d84f70 | ||
|
|
9e048184f9 | ||
|
|
6467c6dfc2 | ||
|
|
8778073702 | ||
|
|
c109ec4c0f | ||
|
|
18e4b97a5f | ||
|
|
3d2dd97aa7 | ||
|
|
54f59dcb98 | ||
|
|
e63a194563 | ||
|
|
c98eeb230d | ||
|
|
c7efc0685d | ||
|
|
cb3f1150db | ||
|
|
a420c2896e | ||
|
|
565a8e540a | ||
|
|
04c43a0f58 | ||
|
|
c5a4a6ff8c | ||
|
|
67a74919b4 | ||
|
|
771153c588 | ||
|
|
9356e95351 | ||
|
|
30f313be54 | ||
|
|
22940f4a86 | ||
|
|
a76eae469e | ||
|
|
3c3caaa5e9 | ||
|
|
753a5bbaf4 | ||
|
|
7fefd9f03c | ||
|
|
1a9753b738 | ||
|
|
5a21eb91dc | ||
|
|
0cd9c988c4 | ||
|
|
d0baa451b5 | ||
|
|
c8d9d73807 | ||
|
|
7861e423d5 | ||
|
|
8f8a09e01b | ||
|
|
b61fcf4040 | ||
|
|
7993ee5913 | ||
|
|
3b976c7acb | ||
|
|
2530c697b5 | ||
|
|
d8214c6eaa | ||
|
|
514d689e7c | ||
|
|
91e422a825 | ||
|
|
a0af039ba1 | ||
|
|
34c2a66db8 | ||
|
|
3c356d1227 | ||
|
|
616481d859 | ||
|
|
3afd153301 | ||
|
|
a659eb9650 | ||
|
|
27614dc1de | ||
|
|
0e742d93bf | ||
|
|
22d681c1e2 | ||
|
|
c5b46c5339 | ||
|
|
0bf0d5d4fb | ||
|
|
84c158e8b8 | ||
|
|
ac855491e1 | ||
|
|
00b6696f38 | ||
|
|
84dd663dac | ||
|
|
b9a57e0f4c | ||
|
|
996a7d36a7 | ||
|
|
9d4816708f | ||
|
|
cd4088d282 | ||
|
|
df41637c07 | ||
|
|
dafa292860 | ||
|
|
da5ebb3622 | ||
|
|
d9a35acaeb | ||
|
|
2fd9adccce | ||
|
|
41ad29f9c9 | ||
|
|
65e1bb9ff5 | ||
|
|
9ce28c1d12 | ||
|
|
f1bb9d76e3 | ||
|
|
9690ca4c70 | ||
|
|
98d2bcc93e | ||
|
|
22ff21d8bd | ||
|
|
e11c4abc89 | ||
|
|
07d23cf65f | ||
|
|
b3a91f7100 | ||
|
|
1efd43a2c9 | ||
|
|
f6950e8a95 | ||
|
|
4e42df172b | ||
|
|
9ee0335bca | ||
|
|
2259d75568 | ||
|
|
9882429fb5 | ||
|
|
a30db65b29 | ||
|
|
c0f1a024dc | ||
|
|
88b4de5d65 | ||
|
|
bb7b256a9b | ||
|
|
b3d50abbad | ||
|
|
65efa8ca2d | ||
|
|
243142386f | ||
|
|
4224f365e0 | ||
|
|
250c5145f6 | ||
|
|
f73cc2503f | ||
|
|
2eee3a2d6a | ||
|
|
9485052d98 | ||
|
|
ee03fb0bdf | ||
|
|
8f3745bf87 | ||
|
|
6751b1a8d6 | ||
|
|
d6d95ff728 | ||
|
|
d67fa31aee | ||
|
|
936416229a | ||
|
|
8b2c86662b | ||
|
|
074e246081 | ||
|
|
82a37d19fa | ||
|
|
c910aa42d0 | ||
|
|
aa58a23bf8 | ||
|
|
66c0961dc8 | ||
|
|
c8b2706126 | ||
|
|
e442ffab3a | ||
|
|
cb3fcb62b9 | ||
|
|
3d9bcaefb1 | ||
|
|
3d6eedc86d | ||
|
|
cde48c99e6 | ||
|
|
61df9288f9 | ||
|
|
8a42507a08 | ||
|
|
e32a4a56ec | ||
|
|
9d2d1a4c06 | ||
|
|
e27310f1e3 | ||
|
|
346a8b1a9d | ||
|
|
879626d9bd | ||
|
|
5b3748924e | ||
|
|
211da2488f | ||
|
|
248063da08 | ||
|
|
6c2dccad75 | ||
|
|
c6fa7ece2e | ||
|
|
81eb448ee0 | ||
|
|
208aa68b10 | ||
|
|
0a6a7e0869 | ||
|
|
49be543fef | ||
|
|
ae4a4a5262 | ||
|
|
a9200e9659 | ||
|
|
df722a3cf8 | ||
|
|
083e89fd95 | ||
|
|
2730b04222 | ||
|
|
adb51dcf33 | ||
|
|
d4e0d19512 | ||
|
|
39b838e8fd | ||
|
|
554135d9c6 | ||
|
|
4fb315d636 | ||
|
|
688fce6569 | ||
|
|
38928c989d | ||
|
|
c940226baf | ||
|
|
4f368bbc5d | ||
|
|
9813840059 | ||
|
|
395174881d | ||
|
|
ca82d0a325 | ||
|
|
deba4d6cd5 | ||
|
|
1812c0ce71 | ||
|
|
61a8703bd1 | ||
|
|
b9da7c8a76 | ||
|
|
b33a106fb9 | ||
|
|
8a4dc5b0f6 | ||
|
|
7535fb59ff | ||
|
|
0202f4b94c | ||
|
|
cda60174f7 | ||
|
|
e579fdb9e6 | ||
|
|
13d5100df5 | ||
|
|
d84718d23e | ||
|
|
0390d98adc | ||
|
|
e844facbb9 | ||
|
|
fb980e9751 | ||
|
|
04da57c694 | ||
|
|
68d16ce43c | ||
|
|
213dba8dd6 | ||
|
|
bd5782ca77 | ||
|
|
0b5f4562aa | ||
|
|
b99ee7f44e | ||
|
|
3151c92d25 | ||
|
|
5d5f63b19a | ||
|
|
82095c6197 | ||
|
|
33261492c5 | ||
|
|
3387580469 | ||
|
|
b8d3d784e4 | ||
|
|
a8d9540421 | ||
|
|
be3dd8b0dd | ||
|
|
ee179c70f2 | ||
|
|
03c4d84bba | ||
|
|
36b3a6c458 | ||
|
|
5a9870800d | ||
|
|
218c157acc | ||
|
|
9547a10a19 | ||
|
|
6f1fa8953f | ||
|
|
37a171e44d | ||
|
|
5418817c5f | ||
|
|
2d19e2799a | ||
|
|
52777b1081 | ||
|
|
14e41b1b5b | ||
|
|
3243ea36ef | ||
|
|
6308849b25 | ||
|
|
8f1339b65b | ||
|
|
47e2999afb | ||
|
|
74ed9d7d83 | ||
|
|
83c984cdd9 | ||
|
|
17b9f1e582 | ||
|
|
237618d14f | ||
|
|
8d5da63ac4 | ||
|
|
0b56eac4f1 | ||
|
|
022efa3751 | ||
|
|
96659c7d44 | ||
|
|
07fa737c4c | ||
|
|
4637c23381 | ||
|
|
a9c5653066 | ||
|
|
a6643f8ce6 | ||
|
|
807eb76cfc | ||
|
|
ba3718b5d8 | ||
|
|
d622d2bc2e | ||
|
|
cbc79f8a0c | ||
|
|
097e28c9d7 | ||
|
|
17d89dbd74 | ||
|
|
299257c4bc | ||
|
|
9eea2470fb | ||
|
|
55a6d9d687 | ||
|
|
6c52c5a744 | ||
|
|
10cbb27509 | ||
|
|
8943e98be1 | ||
|
|
76ad0eb3d6 | ||
|
|
192a94d178 | ||
|
|
f046bcaec3 | ||
|
|
c40e9d7d53 | ||
|
|
4d310f0a59 | ||
|
|
5ee9ae1e60 | ||
|
|
ba2e955e0b | ||
|
|
2a7ca2e6a2 | ||
|
|
ebef5f5fee | ||
|
|
cb77fa2a64 | ||
|
|
b3ae9702f7 | ||
|
|
b6b0130a7d | ||
|
|
b56952d51e | ||
|
|
17d0f979ab | ||
|
|
b4635a0ab9 | ||
|
|
d5d2dd7704 | ||
|
|
a89e382279 | ||
|
|
10ddf030ed | ||
|
|
4d5381bd21 | ||
|
|
c40faf56bf | ||
|
|
ec4e51233f | ||
|
|
5a6e215859 | ||
|
|
5bdda2e8c2 | ||
|
|
4b8f0f965c | ||
|
|
4960fde8f7 | ||
|
|
9da2ec6b06 | ||
|
|
83fbf5a752 | ||
|
|
f36abbb92b | ||
|
|
a9b737bac5 | ||
|
|
20d2e9821f | ||
|
|
266f9413d3 | ||
|
|
1658c2e905 | ||
|
|
e16b5524ff | ||
|
|
34b6b3172f | ||
|
|
e9057a22fc | ||
|
|
62aeae3a20 | ||
|
|
62a46d0fd8 | ||
|
|
503ae89818 | ||
|
|
d16bc5f120 | ||
|
|
694536d87f | ||
|
|
08d10be70d | ||
|
|
50c8126f76 | ||
|
|
c46c9c7f7d | ||
|
|
e969572ffb | ||
|
|
ed3d2204db | ||
|
|
43e3d8bfc6 | ||
|
|
dec20a5272 | ||
|
|
087fb382a8 | ||
|
|
6b0f6eb184 | ||
|
|
28be760bba | ||
|
|
522c8fb680 | ||
|
|
045b46c8c1 | ||
|
|
0298168e00 | ||
|
|
1f11cd7e17 | ||
|
|
50f736491f | ||
|
|
5d6e226c1d | ||
|
|
7fe17c7614 | ||
|
|
1cb43238b5 | ||
|
|
2633fdfcc4 | ||
|
|
5e4039478f | ||
|
|
9e49d08492 | ||
|
|
62536f12ed | ||
|
|
eed37c2c66 | ||
|
|
cd286f70f7 | ||
|
|
1921245719 | ||
|
|
3945d6e7dc | ||
|
|
dbb10c67ba | ||
|
|
2179765026 | ||
|
|
d631f76f8b | ||
|
|
e6a69f4bd1 | ||
|
|
9f3cf00bb3 | ||
|
|
3f2b0e2afc | ||
|
|
a5519ebed0 | ||
|
|
ea64263b41 | ||
|
|
126c302e7a | ||
|
|
63f2cea173 | ||
|
|
7fe65a2204 | ||
|
|
76d6f0f574 | ||
|
|
0e46595f81 | ||
|
|
f28573731f | ||
|
|
62fab6e78a | ||
|
|
4d88245a29 | ||
|
|
d4f6993ac9 | ||
|
|
6130ddb3c3 | ||
|
|
3494b8c8b1 | ||
|
|
83c87d632c | ||
|
|
e11b878116 | ||
|
|
8437ff3990 | ||
|
|
0ade9f36d3 | ||
|
|
a9bc069a35 | ||
|
|
b0fc01efc6 | ||
|
|
5bd6a16cab | ||
|
|
3a0da34c48 | ||
|
|
e108764509 | ||
|
|
69db5c6c63 | ||
|
|
ff1dc02cbc | ||
|
|
5ed2615017 | ||
|
|
ed911223ab | ||
|
|
3a6122bbaf | ||
|
|
458529a1c6 | ||
|
|
2549aed0ee | ||
|
|
ae6af1b8db | ||
|
|
d919a375b3 | ||
|
|
d86c4900fa | ||
|
|
eb0e4ff100 | ||
|
|
45a5b970b8 | ||
|
|
5f5ab19246 | ||
|
|
bf202afb1e | ||
|
|
0c03739e50 | ||
|
|
92e22bd8c6 | ||
|
|
abb81511d4 | ||
|
|
7dba8776df | ||
|
|
bfe32a2b12 | ||
|
|
c73276d3f6 | ||
|
|
d2cbbb901b | ||
|
|
435ba67550 | ||
|
|
da1c04b81a | ||
|
|
e8f9c10965 | ||
|
|
c0ce57ffc8 | ||
|
|
408bab6939 | ||
|
|
5e82420a74 | ||
|
|
e1e621e78c | ||
|
|
12066050d0 | ||
|
|
9492109c62 | ||
|
|
79c347765a | ||
|
|
6efbc46fc7 | ||
|
|
64d5d2c1f4 | ||
|
|
1254bc94a5 | ||
|
|
1bd9214f41 | ||
|
|
410fd620be | ||
|
|
d3e630f774 | ||
|
|
46f21c4413 | ||
|
|
222b798483 | ||
|
|
b6046999b6 | ||
|
|
a8f17e0207 | ||
|
|
eb6e81b8f2 | ||
|
|
092513ca05 | ||
|
|
3875f479ac | ||
|
|
d7c36f2257 | ||
|
|
66991d2b2c | ||
|
|
50625a842a | ||
|
|
3977803d81 | ||
|
|
3275edff48 | ||
|
|
b3ddd57d49 | ||
|
|
d5c955dfc7 | ||
|
|
9c8a4aea90 | ||
|
|
8bb825ba13 | ||
|
|
436da67f4b | ||
|
|
50ae10289a | ||
|
|
07e8da79f8 | ||
|
|
045a0de8e2 | ||
|
|
3bbc0dc753 | ||
|
|
164eb6cdc2 | ||
|
|
cd2cabc7ab | ||
|
|
0263bdd53e | ||
|
|
f78a7baa80 | ||
|
|
f2a3cad69a | ||
|
|
3186c675da | ||
|
|
562b6b9e90 | ||
|
|
820f3ffd42 | ||
|
|
f9ce4b9de3 | ||
|
|
8d6a01b370 | ||
|
|
5d40b1e213 | ||
|
|
d1f2c9a3be | ||
|
|
84a7deae24 | ||
|
|
6b001b5325 | ||
|
|
81ef6f228d | ||
|
|
1478804fa2 | ||
|
|
e5b8919eeb | ||
|
|
df292beac6 | ||
|
|
f685533110 | ||
|
|
23e386d1c2 | ||
|
|
ded4e461b0 | ||
|
|
495f740eda | ||
|
|
286fb24a63 | ||
|
|
25a3a04fb0 | ||
|
|
7a11d3fc98 | ||
|
|
e2f77cd4de | ||
|
|
01fe87da2e | ||
|
|
433178adf5 | ||
|
|
afcc76b48a | ||
|
|
8998a4c907 | ||
|
|
bbbda7bddf | ||
|
|
be3fa4f3c3 | ||
|
|
9d94573ccd | ||
|
|
030fc05665 | ||
|
|
98c6d29ec9 | ||
|
|
da0af62e91 | ||
|
|
2caa346dc5 | ||
|
|
7b64e0503e | ||
|
|
a276d820b2 | ||
|
|
0226f7ba02 | ||
|
|
b4c925075c | ||
|
|
972784c043 | ||
|
|
5d6938f680 | ||
|
|
2824879238 | ||
|
|
586dfe6911 | ||
|
|
50803a9ecd | ||
|
|
318ec94985 | ||
|
|
eee63d18fc | ||
|
|
960f3f05f6 | ||
|
|
a8e7f69ab1 | ||
|
|
1dac9b9f6b | ||
|
|
53416cd923 | ||
|
|
3146c7010e | ||
|
|
cdbbd8a445 | ||
|
|
2094c7eb2c | ||
|
|
2d1868a050 | ||
|
|
e52e9c94e8 | ||
|
|
7cd555ab9e | ||
|
|
441bf91f04 | ||
|
|
f3d011eec1 | ||
|
|
804aabd786 | ||
|
|
917c3930d4 | ||
|
|
a84b44268a | ||
|
|
a9d78d7787 | ||
|
|
bc078d1232 | ||
|
|
49cf80684e | ||
|
|
470e6967e6 | ||
|
|
883df0594b | ||
|
|
7ca7296ba5 | ||
|
|
83cac0bd20 | ||
|
|
388d55d0d7 | ||
|
|
ad34bed33e | ||
|
|
378c4d7511 | ||
|
|
fc9431c2eb | ||
|
|
1e73eaf182 | ||
|
|
1687f31a3b | ||
|
|
834cd084aa | ||
|
|
e4a68e6300 | ||
|
|
d818bf1a01 | ||
|
|
567d74923a | ||
|
|
254e66d02d | ||
|
|
58ccf6762a | ||
|
|
6a98903627 | ||
|
|
62cd627ce7 | ||
|
|
09416daff2 | ||
|
|
5780fe8143 | ||
|
|
34fa3471f8 | ||
|
|
16f681595d | ||
|
|
8c66e96bea | ||
|
|
a1ef070e65 | ||
|
|
4ea41b2f20 | ||
|
|
b06da59aba | ||
|
|
9a9a185b1a | ||
|
|
10d8554b86 | ||
|
|
3e7beab7fa | ||
|
|
6449d50ffd | ||
|
|
87d3221730 | ||
|
|
faac8776f4 | ||
|
|
13db670118 | ||
|
|
e9fc71683f | ||
|
|
e78225c349 | ||
|
|
691345d5ba | ||
|
|
29ce1c3a2b | ||
|
|
aaa37dfbbd | ||
|
|
e113e2f6b8 | ||
|
|
59fd426420 | ||
|
|
0253f21ea5 | ||
|
|
f2d892281b | ||
|
|
5c4b424bf6 | ||
|
|
0050697619 | ||
|
|
c598da4d84 | ||
|
|
9e6658b0b2 | ||
|
|
1a9f44f7be | ||
|
|
9d1c61329a | ||
|
|
3aa3daa4f9 | ||
|
|
2051564688 | ||
|
|
de1f529882 | ||
|
|
486298a566 | ||
|
|
b3b303d294 | ||
|
|
060ad1383a | ||
|
|
efb9babb4b | ||
|
|
8bc2448fe1 | ||
|
|
36165e08cd | ||
|
|
2fd64bb024 | ||
|
|
53d0847239 | ||
|
|
c138066e21 | ||
|
|
15a60352c0 | ||
|
|
229285b76d | ||
|
|
acbe023579 | ||
|
|
ddfdbbac21 | ||
|
|
cde475b36b | ||
|
|
eff24a90c9 | ||
|
|
d77bb13e97 | ||
|
|
c7517abd08 | ||
|
|
a80012b48e | ||
|
|
32f2e8a812 | ||
|
|
a2cf90ce21 | ||
|
|
be42f35015 | ||
|
|
6315b7ed2c | ||
|
|
47a634e28b | ||
|
|
929931f617 | ||
|
|
f35025b90e | ||
|
|
a50d7b432d | ||
|
|
4d6ce38166 | ||
|
|
92111d1caa | ||
|
|
cb397d0be9 | ||
|
|
2ac4c5a6fd | ||
|
|
99642f1c19 | ||
|
|
804736733e | ||
|
|
d71a0fe1a5 | ||
|
|
ddebd05257 | ||
|
|
23ff0d7468 | ||
|
|
c6820d1a5b | ||
|
|
2925b3ae95 | ||
|
|
cbe9acb6bc | ||
|
|
6d730133bf | ||
|
|
e2c5aaf70a | ||
|
|
dfc4f9c7af | ||
|
|
f6b2dd2ddd | ||
|
|
0795b9ed8b | ||
|
|
fbaf4c67c6 | ||
|
|
60963fb52e | ||
|
|
8d1fc7309c | ||
|
|
2ffd260f96 | ||
|
|
7e34d94616 | ||
|
|
3a8f506d58 | ||
|
|
2e47a88733 | ||
|
|
31a055838e | ||
|
|
543e7cbf49 | ||
|
|
2fea4ebd8d | ||
|
|
5669edde75 | ||
|
|
59805f59ab | ||
|
|
e1bcc61827 | ||
|
|
ae487529f3 | ||
|
|
8b23f693e9 | ||
|
|
038cb56f85 | ||
|
|
f444d7b666 | ||
|
|
44b47c7bae | ||
|
|
fe838dd11f | ||
|
|
82de56d5d6 | ||
|
|
72bdf7ea7c | ||
|
|
375e9a1c27 | ||
|
|
b3c54ca36b | ||
|
|
953177d098 | ||
|
|
d8373eebdb | ||
|
|
fa18860071 | ||
|
|
b3852fe5b9 | ||
|
|
6f2f889b0a | ||
|
|
5bb622f98c | ||
|
|
febcdc62d3 | ||
|
|
0b7b4144a2 | ||
|
|
361127db51 | ||
|
|
fca7da3969 | ||
|
|
b427c0b2b3 | ||
|
|
a4234d46c7 | ||
|
|
db8e758cb7 | ||
|
|
4d9ce81db6 | ||
|
|
f3d376e191 | ||
|
|
8de4497f77 | ||
|
|
620e2c26b2 | ||
|
|
74242a4687 | ||
|
|
62e9da75e7 | ||
|
|
5e9e8cd16d | ||
|
|
ab3cfaf685 | ||
|
|
978a92f44c | ||
|
|
77724a9672 | ||
|
|
64f732022d | ||
|
|
a1206f1a5c | ||
|
|
9eabbcd8d0 | ||
|
|
2cbf4a7f93 | ||
|
|
8cb738bb54 | ||
|
|
9f764f7429 | ||
|
|
f05152105f | ||
|
|
fe051ba2ba | ||
|
|
ac365421b0 | ||
|
|
37b6325815 | ||
|
|
e72f9755c3 | ||
|
|
0ac9d155ab | ||
|
|
19c23d1208 | ||
|
|
fb634ef652 | ||
|
|
73b4a8a4f2 | ||
|
|
e1ac652396 | ||
|
|
6cc56bac93 | ||
|
|
55ce401f7f | ||
|
|
c0a84fc9dd | ||
|
|
632035024c | ||
|
|
ed105d016e | ||
|
|
a0f082f47b | ||
|
|
055b86c09f | ||
|
|
4669a5d34f | ||
|
|
e3723b394a | ||
|
|
1a5e1b25a2 | ||
|
|
e145f2fa36 | ||
|
|
7c2097edab | ||
|
|
601b21b1a6 | ||
|
|
efb0feba5e | ||
|
|
ede9c210d8 | ||
|
|
1040b412a7 | ||
|
|
869433e6b2 | ||
|
|
89c7ff156e | ||
|
|
e3032a9bc3 | ||
|
|
1e0ff7bfd7 | ||
|
|
609078e377 | ||
|
|
cd9413ee05 | ||
|
|
3b6bcd7dca | ||
|
|
55c68fc638 | ||
|
|
2633136009 | ||
|
|
68e600ace0 | ||
|
|
824743a429 | ||
|
|
b19da9c52d | ||
|
|
7704a85e7f | ||
|
|
329ed9bb08 | ||
|
|
fa9f2bc4a3 | ||
|
|
79ed5c1ac1 | ||
|
|
27777e9f10 | ||
|
|
f23546c953 | ||
|
|
14094ed9ed | ||
|
|
9a07550a9e | ||
|
|
734a8c8e9d | ||
|
|
e7ff445c60 | ||
|
|
ffab35113c | ||
|
|
2dcfa5d065 | ||
|
|
276efb2a89 | ||
|
|
32d213064f | ||
|
|
462858ccf9 | ||
|
|
50f1c72a18 | ||
|
|
5ffd15d845 | ||
|
|
fc8f6d479d | ||
|
|
117712f38f | ||
|
|
f5bbdcbe4f | ||
|
|
9a4861b4ee | ||
|
|
878fa3c8d6 | ||
|
|
1410e7c1f1 | ||
|
|
6a44d736b4 | ||
|
|
30104e6c13 | ||
|
|
05d3e0c5a1 | ||
|
|
acd00737c6 | ||
|
|
08f6a05904 | ||
|
|
75c3a75a80 | ||
|
|
4a0c3808de | ||
|
|
76ab7d5c96 | ||
|
|
452c8ec89f | ||
|
|
b59fd09847 | ||
|
|
63a3a7646f | ||
|
|
bd1f57a2d3 | ||
|
|
0f35a386c1 | ||
|
|
48ee94f882 | ||
|
|
da4a5336a2 | ||
|
|
d500bc8758 | ||
|
|
1a30e20713 | ||
|
|
1e590b77aa | ||
|
|
0a6c637b3f | ||
|
|
587a8c8e6f | ||
|
|
8077d7b42b | ||
|
|
d09a712b71 | ||
|
|
74d698d40f | ||
|
|
4679a8bf0a | ||
|
|
e7ad9c5de4 | ||
|
|
c9608d744f | ||
|
|
6315e88f65 | ||
|
|
36ddc88274 | ||
|
|
49ea494d52 | ||
|
|
46489c2818 | ||
|
|
d5d4c565df | ||
|
|
71e9516cb7 | ||
|
|
a4f3c433b4 | ||
|
|
b21ec054ff | ||
|
|
a689812865 | ||
|
|
37b9221f4d | ||
|
|
5ff59cadc4 | ||
|
|
862df58673 | ||
|
|
874cab7496 | ||
|
|
960b38329f | ||
|
|
02eb087db0 | ||
|
|
fa54b5b0dc | ||
|
|
cb978fdbcb | ||
|
|
ac8bbd9fc2 | ||
|
|
1ac680fb5f | ||
|
|
3f3600e029 | ||
|
|
6bf51e6351 | ||
|
|
0d6cb0cc83 | ||
|
|
b2b0e8f4d6 | ||
|
|
587d1e4435 | ||
|
|
14fa2c5ff1 | ||
|
|
01a2ae2ba8 | ||
|
|
d44b8a46e2 | ||
|
|
392aa0ebd4 | ||
|
|
6adb752638 | ||
|
|
d9169bba1e | ||
|
|
0c0af47317 | ||
|
|
5368cb6a69 | ||
|
|
a951af97cd | ||
|
|
e6705a3f9c | ||
|
|
c64e2f53f1 | ||
|
|
ff79794377 | ||
|
|
33f3ffd099 | ||
|
|
5487dbaad9 | ||
|
|
9f81916bc7 | ||
|
|
982d4ab3ab | ||
|
|
5fca0f3f62 | ||
|
|
7fa63e2b95 | ||
|
|
d2c69950ce | ||
|
|
556ef3eb63 | ||
|
|
48103268c5 | ||
|
|
18c1c43509 | ||
|
|
a25fd359ca | ||
|
|
50ff20755c | ||
|
|
b0fc3ecdd4 | ||
|
|
3217accdcd | ||
|
|
895b9c79c7 | ||
|
|
ba97109526 | ||
|
|
4780184e3d | ||
|
|
8b4ff6aec2 | ||
|
|
9c598da9cb | ||
|
|
8c81cda4bb | ||
|
|
8ee2d5203a | ||
|
|
6ed173f0ad | ||
|
|
4ec9fdae13 | ||
|
|
c493b069d2 | ||
|
|
55a4c8c33c | ||
|
|
0bb547e62c | ||
|
|
08794b3341 | ||
|
|
38a9b4344f | ||
|
|
ff6a3d4b29 | ||
|
|
b762375cfc | ||
|
|
49e424dc8c | ||
|
|
ba7e646304 | ||
|
|
a93b17cc72 | ||
|
|
89acd994b2 | ||
|
|
974c7589b9 | ||
|
|
e1d1163c15 | ||
|
|
2a1b5d19a3 | ||
|
|
da18545f2f | ||
|
|
ffa7bfff66 | ||
|
|
eb354eb95a | ||
|
|
910dbd4230 | ||
|
|
67b63b6c4e | ||
|
|
463cbf01cf | ||
|
|
67f9c37eee | ||
|
|
c545cacd84 | ||
|
|
739db6e683 | ||
|
|
ae5b1c7dff | ||
|
|
7e863227d7 | ||
|
|
3c7978b116 | ||
|
|
51de0dda19 | ||
|
|
1190d2f46f | ||
|
|
fc2f35d593 | ||
|
|
9a403df7bd | ||
|
|
52e6774737 | ||
|
|
1865ac151c | ||
|
|
52b92ff310 | ||
|
|
27d2a9eb47 | ||
|
|
79252fad3a | ||
|
|
f7d3e12c85 | ||
|
|
35b2bdbc78 | ||
|
|
e62d609811 | ||
|
|
291627a3e3 | ||
|
|
c0ed5f679e | ||
|
|
2733b9fa28 | ||
|
|
9215ea61f8 | ||
|
|
a2250d38f5 | ||
|
|
654d8a6688 | ||
|
|
a5632eb5f2 | ||
|
|
54cc4924c5 | ||
|
|
089b401607 | ||
|
|
d63b6a7793 | ||
|
|
e486e6c0c7 | ||
|
|
fe199ce232 | ||
|
|
64d9f90220 | ||
|
|
be30fbc543 | ||
|
|
f2de28ac7d | ||
|
|
26354abedf | ||
|
|
434c6431b4 | ||
|
|
c3ba48f4d8 | ||
|
|
0c0055cb9d | ||
|
|
e2d7e61adf | ||
|
|
c9e6a45f28 | ||
|
|
f0ea46b3ff | ||
|
|
07b561b31a | ||
|
|
0f7d7a82ca | ||
|
|
21597cf4f4 | ||
|
|
c49cbf8408 | ||
|
|
30e6965a6d | ||
|
|
48e0cd3828 | ||
|
|
28fb48a4e9 | ||
|
|
8871622efc | ||
|
|
dc38839721 | ||
|
|
48a11cfadd | ||
|
|
c145d7897c | ||
|
|
ad3c78a4fc | ||
|
|
38e96ca31a | ||
|
|
ae15096a06 | ||
|
|
a5619dace1 | ||
|
|
4290af9e08 | ||
|
|
ce7d8cef9f | ||
|
|
d4a497eaab | ||
|
|
1bf18b5a8a | ||
|
|
c5579bd0e9 | ||
|
|
2ccef4f100 | ||
|
|
f44460e76b | ||
|
|
b84c1bed0d | ||
|
|
2b8425307d | ||
|
|
073c513015 | ||
|
|
712e7c378a | ||
|
|
f1f2de712a | ||
|
|
63b7bc4bde | ||
|
|
406a7844ab | ||
|
|
2f4b2192f3 | ||
|
|
a0745542ad | ||
|
|
9f98ea4461 | ||
|
|
5ebe1af489 | ||
|
|
508ad6524a | ||
|
|
470ea9355e | ||
|
|
0a6e640250 | ||
|
|
8f4a6df4b2 | ||
|
|
cdac016c21 | ||
|
|
aab84abc02 | ||
|
|
527823bc50 | ||
|
|
dcf2bd2870 | ||
|
|
bd6b3eaafb | ||
|
|
5fe5278e60 | ||
|
|
32bb7d9e99 | ||
|
|
f0c7d3c5b1 | ||
|
|
3ede1f95a7 | ||
|
|
7d99ce2cad | ||
|
|
635b6468df | ||
|
|
ae86786dc2 | ||
|
|
35643274da | ||
|
|
8712f83649 | ||
|
|
3f4d4c968c | ||
|
|
13cd650de2 | ||
|
|
59f713795a | ||
|
|
18017c6497 | ||
|
|
5c0b2574b3 | ||
|
|
96f1383301 | ||
|
|
2fdd5e58c2 | ||
|
|
a9bb38099f | ||
|
|
83c04bde69 | ||
|
|
c9e74cbf8c | ||
|
|
c262581688 | ||
|
|
d0d97cd5f8 | ||
|
|
f83e411035 | ||
|
|
fea6530e4c | ||
|
|
1d9251b881 | ||
|
|
cced6d98e2 | ||
|
|
893c95aeb5 | ||
|
|
e2066c4d68 | ||
|
|
33e68d3dc3 | ||
|
|
6cc825e8d6 | ||
|
|
6d66d249ac | ||
|
|
d6aaf8d481 | ||
|
|
500995c0ad | ||
|
|
c07c474998 | ||
|
|
277de3148a | ||
|
|
a00d9c23dc | ||
|
|
40fd970a62 | ||
|
|
e8fdf030d5 | ||
|
|
b34097f2db | ||
|
|
8504aa35f2 | ||
|
|
eaa8e36a67 | ||
|
|
84a71166b8 | ||
|
|
05818be3f9 | ||
|
|
e0371b1f6f | ||
|
|
dfdd2daf84 | ||
|
|
1670043207 | ||
|
|
afd68edefb | ||
|
|
be83ef6796 | ||
|
|
12d8785954 | ||
|
|
ccd0f31bab | ||
|
|
f4f53c7e6c | ||
|
|
1b5e9f95d0 | ||
|
|
9eaafb739e | ||
|
|
d5f4ac3924 | ||
|
|
035dc70f77 | ||
|
|
b892aebfe8 | ||
|
|
156ad8a318 | ||
|
|
74433f1ca8 | ||
|
|
7042fdacbc | ||
|
|
5ec1b18115 | ||
|
|
abfa6d4644 | ||
|
|
730c1e6bc4 | ||
|
|
e1366c9a6f | ||
|
|
9d9b3a110d | ||
|
|
92893feef2 | ||
|
|
775c64f21c | ||
|
|
dbd131cc88 | ||
|
|
7e295782c3 | ||
|
|
f2b1fa1399 | ||
|
|
4ca86478ee | ||
|
|
eb53372646 | ||
|
|
111d6ae5cf | ||
|
|
cc2b556e65 | ||
|
|
69acda60ec | ||
|
|
efe07e28c1 | ||
|
|
fe2a4862ad | ||
|
|
7503cd841a | ||
|
|
8b099099d9 | ||
|
|
b6c7afbb26 | ||
|
|
0997df9069 | ||
|
|
91f9205adb | ||
|
|
e62bcae84b | ||
|
|
8ff6083faf | ||
|
|
648fecab61 | ||
|
|
db1ea288ef | ||
|
|
8a3922ad30 | ||
|
|
d4b9dcfb15 | ||
|
|
4b678ffee8 | ||
|
|
3ae81a0835 | ||
|
|
4fe7f6bc86 | ||
|
|
ffa4d6a829 | ||
|
|
0d51bb3f73 | ||
|
|
8bac0d5de1 | ||
|
|
e2166f086d | ||
|
|
8f4a9974b1 | ||
|
|
e93f8920c1 | ||
|
|
0021a7fb65 | ||
|
|
eec1e84e72 | ||
|
|
fddf50db2a | ||
|
|
8cf8e32bb4 | ||
|
|
f4bbf91a08 | ||
|
|
0b558a470e | ||
|
|
816adb335a | ||
|
|
a67e67d42e | ||
|
|
9f5b4b2fd3 | ||
|
|
a32a87c346 | ||
|
|
7be76e40f4 | ||
|
|
0a7aae6aaf | ||
|
|
6a893d200c | ||
|
|
e651c58384 | ||
|
|
0636feb5aa | ||
|
|
276bcfd0bb | ||
|
|
3d09ba536c | ||
|
|
a454664a1d | ||
|
|
b9ca7b686b | ||
|
|
763969f965 | ||
|
|
df617bee5e | ||
|
|
159de7fd68 | ||
|
|
cc369a2b40 | ||
|
|
d8d4d924ad | ||
|
|
1330d64b22 | ||
|
|
e404b252b1 | ||
|
|
af6dd20503 | ||
|
|
7e61c2e40d | ||
|
|
265af52853 | ||
|
|
bea98d0749 | ||
|
|
ff9186f1dc | ||
|
|
871fce1661 | ||
|
|
5657dec7af | ||
|
|
0f23104156 | ||
|
|
e23a588bad | ||
|
|
c44e921e0b | ||
|
|
8d09f734cd | ||
|
|
ef281ab8c6 | ||
|
|
aa082ca119 | ||
|
|
fc3f23976b | ||
|
|
2fe41b81c5 | ||
|
|
0f4a3567b1 | ||
|
|
9f90e76a0c | ||
|
|
3220eb4c1b | ||
|
|
f01bed49d5 | ||
|
|
5777d37af1 | ||
|
|
833ffa3aab | ||
|
|
d0487f5869 | ||
|
|
a2308e8518 | ||
|
|
07662e65ba | ||
|
|
bc834aa083 | ||
|
|
46f5d5470a | ||
|
|
69d754cb77 | ||
|
|
e8b403e0b9 | ||
|
|
83dcaf932f | ||
|
|
15a6f4b1b3 | ||
|
|
d19fa4a8f1 | ||
|
|
869adbbd1c | ||
|
|
9e3bbcb5e1 | ||
|
|
e72606ffb6 | ||
|
|
425daf97b4 | ||
|
|
709d0b30fe | ||
|
|
4cede79c69 | ||
|
|
32782cc69f | ||
|
|
c5ea85a212 | ||
|
|
e62f0787f8 | ||
|
|
88c18714c9 | ||
|
|
7c61da51cf | ||
|
|
c93b9a2125 | ||
|
|
6b75f9c486 | ||
|
|
d9b4647e11 | ||
|
|
078d815ad5 | ||
|
|
7c4a229fbc | ||
|
|
e8faa6254e | ||
|
|
3c5dc9d3a7 | ||
|
|
0f6480a303 | ||
|
|
073720049b | ||
|
|
efde1c17f1 | ||
|
|
964b509df0 | ||
|
|
39fc5b4fff | ||
|
|
e37e273375 | ||
|
|
4cd37cce64 | ||
|
|
f0e54bbe9f | ||
|
|
754cbccf56 | ||
|
|
ffd1b627e2 | ||
|
|
efd070e7b2 | ||
|
|
83f4ba6069 | ||
|
|
7a704400ab | ||
|
|
2c549825ea | ||
|
|
87dc452498 | ||
|
|
1261bad465 | ||
|
|
0d3ef08d6f | ||
|
|
bc22983319 | ||
|
|
014e1b0e76 | ||
|
|
77039ebf7e | ||
|
|
f386f9db4b | ||
|
|
1822632ce6 | ||
|
|
b4bcfd8fa5 | ||
|
|
2e2fb13389 | ||
|
|
6704e26553 | ||
|
|
3d67023c9c | ||
|
|
229225cab5 | ||
|
|
b253509c8b | ||
|
|
c22eb26949 | ||
|
|
827ad77989 | ||
|
|
ed49bcd543 | ||
|
|
fe23ebfec1 | ||
|
|
143a8da8d6 | ||
|
|
085afb9549 | ||
|
|
07428a1def | ||
|
|
29ed1a27b9 | ||
|
|
7ac0d4826c | ||
|
|
583096e059 | ||
|
|
477577696f | ||
|
|
a97f8c9bdf | ||
|
|
d2e23d3886 | ||
|
|
ea986d3260 | ||
|
|
7c6375761d | ||
|
|
02e4e5a6a0 | ||
|
|
fec7951493 | ||
|
|
6980b00031 | ||
|
|
c6c2716726 | ||
|
|
9089d1ec95 | ||
|
|
e4e631725a | ||
|
|
bcb2886e5b | ||
|
|
6c0a78e1c7 | ||
|
|
d554c13cc8 | ||
|
|
dc6b07abe3 | ||
|
|
8c0f7bb032 | ||
|
|
ac0d8e922b | ||
|
|
d9fa202bbb | ||
|
|
a65bd14fa1 | ||
|
|
e0dd25a5e0 | ||
|
|
312fbc4f8d | ||
|
|
b6be4d9a4d | ||
|
|
7a9abb3f6f | ||
|
|
b5dbb9c705 | ||
|
|
7e014ba67b | ||
|
|
3bd50b83d4 | ||
|
|
7fa12b8806 | ||
|
|
aa01b789be | ||
|
|
09baf89e70 | ||
|
|
3483751d9d | ||
|
|
e0c027edf4 | ||
|
|
97e33e1df0 | ||
|
|
1f4130e15e | ||
|
|
8a001e66b1 | ||
|
|
ce2e896fcb | ||
|
|
ef4d5b3cd6 | ||
|
|
c1a9cb6de6 | ||
|
|
bf50e0da6f | ||
|
|
362b112a77 | ||
|
|
6b424ca736 | ||
|
|
4560115ec8 | ||
|
|
c89bab7e42 | ||
|
|
bbac29011e | ||
|
|
c60c6b67ab | ||
|
|
8767a6eb4e | ||
|
|
539b772d6b | ||
|
|
bc6c8131b5 | ||
|
|
632145f3a5 | ||
|
|
feb6252fae | ||
|
|
5f3fb1b548 | ||
|
|
c92736d124 | ||
|
|
3f07514a44 | ||
|
|
9b3288dc18 | ||
|
|
1d6be38cc8 | ||
|
|
4a42f07ad2 | ||
|
|
072cca75ce | ||
|
|
f4f78af7dd | ||
|
|
6341547de6 | ||
|
|
8282937409 | ||
|
|
8361bb299b | ||
|
|
2109acacd5 | ||
|
|
7a8e77b24a | ||
|
|
751fe08d98 | ||
|
|
33195758f0 | ||
|
|
30bde95314 | ||
|
|
aaf13dc3c2 | ||
|
|
e0e8ca9f67 | ||
|
|
8ac0b91f78 | ||
|
|
05466aa85a | ||
|
|
5be88bfe57 | ||
|
|
17295fdcde | ||
|
|
7821217b60 | ||
|
|
68485f8bbf | ||
|
|
f0cd188d73 | ||
|
|
5a94a87d67 | ||
|
|
fab3a48efe | ||
|
|
1e75a20a49 | ||
|
|
2205d11bf5 | ||
|
|
583c737c15 | ||
|
|
8649f7f9e9 | ||
|
|
0c48a4e68d | ||
|
|
fda51675fc | ||
|
|
4c586edefa | ||
|
|
11a5cf4a06 | ||
|
|
b3bbfccef4 | ||
|
|
b8d201e18a | ||
|
|
15829d25a9 | ||
|
|
44a6abe0b7 | ||
|
|
c6393f1f23 | ||
|
|
83ed0e8820 | ||
|
|
049b5490ba | ||
|
|
35d339e137 | ||
|
|
a218bee02d | ||
|
|
59c0d35a32 | ||
|
|
794867863f | ||
|
|
2c5e6f8a4e | ||
|
|
23a732a107 | ||
|
|
370727c2fb | ||
|
|
ea70a64733 | ||
|
|
7d596c7216 | ||
|
|
b26e3736c1 | ||
|
|
a86ee04ce6 | ||
|
|
7750a08f65 | ||
|
|
a22d921c17 | ||
|
|
010d9b89d0 | ||
|
|
c82cd6704b | ||
|
|
55011709b0 | ||
|
|
bcc68b091b | ||
|
|
e6fcf60b8e | ||
|
|
68f38ef5a4 | ||
|
|
9ee9311122 | ||
|
|
6f2a153a44 | ||
|
|
3502d10db3 | ||
|
|
b38865e056 | ||
|
|
b659709f2f | ||
|
|
add9a1e9ad | ||
|
|
447c8387a2 | ||
|
|
f7f519dcc4 | ||
|
|
39ba239618 | ||
|
|
e9b59a540e | ||
|
|
c92563577c | ||
|
|
d1f0dc6376 | ||
|
|
9753e7301e | ||
|
|
884a7ac5c1 | ||
|
|
e589831baa | ||
|
|
1a44d9343c | ||
|
|
631fd3b08d | ||
|
|
3bd81ca3be | ||
|
|
b94d2687f8 | ||
|
|
86751fa022 | ||
|
|
71e1acd1b0 | ||
|
|
c00379c85c | ||
|
|
5d628bf703 | ||
|
|
66835ab703 | ||
|
|
4aeccc7b5b | ||
|
|
bc38aa4173 | ||
|
|
7f283d3a61 | ||
|
|
504c307197 | ||
|
|
f9604d9ebb | ||
|
|
34c4d37ff0 | ||
|
|
d171f5b873 | ||
|
|
5a583f4918 | ||
|
|
ad0880f33b | ||
|
|
9c544f4701 | ||
|
|
6b249254da | ||
|
|
55296dc01b | ||
|
|
3334c52558 | ||
|
|
257743a26d | ||
|
|
2f83bbaa50 | ||
|
|
e63632f872 | ||
|
|
6033af8dd1 | ||
|
|
656d9522e0 | ||
|
|
107a83d634 | ||
|
|
43d8adc717 | ||
|
|
02c31c9246 | ||
|
|
0810b54837 | ||
|
|
9e9f7c0b44 | ||
|
|
686220b3ae | ||
|
|
e54741d1f9 | ||
|
|
39c59ba0cd | ||
|
|
49c7baaa44 | ||
|
|
9585a65341 | ||
|
|
81ceb534f3 | ||
|
|
383e49921c | ||
|
|
fb484d9530 | ||
|
|
479d7175f6 | ||
|
|
f28afb3069 | ||
|
|
ec48677623 | ||
|
|
71e3d4dc6d | ||
|
|
6eaa5b44cf | ||
|
|
69cdead4fc | ||
|
|
fb3b6fd699 | ||
|
|
4aba37bd3b | ||
|
|
6557cd5523 | ||
|
|
ac8ae6b0ee | ||
|
|
ec8d7d9c1e | ||
|
|
8959c489f2 | ||
|
|
b4d939bd38 | ||
|
|
f26b0e7671 | ||
|
|
b6441120c5 | ||
|
|
e5acd7847f | ||
|
|
f30492c37f | ||
|
|
c03be40bb5 | ||
|
|
5dd62e7a08 | ||
|
|
439d09333d | ||
|
|
4352fab581 | ||
|
|
4c8dc87a28 | ||
|
|
c42d235336 | ||
|
|
d54fa25512 | ||
|
|
7d118628a4 | ||
|
|
3a2d6530a3 | ||
|
|
0ad45c90fa | ||
|
|
1c88fb4cda | ||
|
|
2b33da2a33 | ||
|
|
daf0420aa4 | ||
|
|
8c2a400be2 | ||
|
|
582d23c884 | ||
|
|
6ef9c61220 | ||
|
|
5fe009246f | ||
|
|
b101b5646f | ||
|
|
279e529dab | ||
|
|
f93e26d6a5 | ||
|
|
a642059809 | ||
|
|
3fbd7f61b6 | ||
|
|
bb5b6307df | ||
|
|
6cb8956275 | ||
|
|
89b1d058fb | ||
|
|
b4ef4c2dc8 | ||
|
|
1dbd1651ad | ||
|
|
efef8c4895 | ||
|
|
cdd6e1d724 | ||
|
|
ed945f347e | ||
|
|
1c9ba78db0 | ||
|
|
dbb755833a | ||
|
|
8f8f6bb5e1 | ||
|
|
3a8564acb1 | ||
|
|
0cb66802c6 | ||
|
|
f6632ff53b | ||
|
|
0420946391 | ||
|
|
e4915514bd | ||
|
|
8bdf5c3608 | ||
|
|
1f84dd7159 | ||
|
|
c00aaa29a4 | ||
|
|
0c8b93e28a | ||
|
|
5e6d4245db | ||
|
|
2b48347ec3 | ||
|
|
765adc1690 | ||
|
|
3cad66317f | ||
|
|
f3d9d2381b | ||
|
|
036d402f34 | ||
|
|
891a05dfdb | ||
|
|
3f3fa5aa31 | ||
|
|
22693772a6 | ||
|
|
715747a054 | ||
|
|
cc71c8b504 | ||
|
|
ee7ad8fcb7 | ||
|
|
b7edddf553 | ||
|
|
ab0d910edc | ||
|
|
1250493b2f | ||
|
|
d729869093 | ||
|
|
f99bf07949 | ||
|
|
a9611d8e7f | ||
|
|
b284ec81a5 | ||
|
|
5e91f0a07d | ||
|
|
88d253f383 | ||
|
|
c2112cbe40 | ||
|
|
d81fc37201 | ||
|
|
3cff405f9e | ||
|
|
62ffca0dfd | ||
|
|
5b24a10e54 | ||
|
|
40eb20a970 | ||
|
|
7d055b7a28 | ||
|
|
145f9dff8f | ||
|
|
5250470d17 | ||
|
|
a2c9029e3f | ||
|
|
d477a7178f | ||
|
|
842c43d557 | ||
|
|
1bbdd25a22 | ||
|
|
1f39d25523 | ||
|
|
764280843c | ||
|
|
caa3fa7f9f | ||
|
|
b24c133f60 | ||
|
|
ee878fc161 | ||
|
|
3b005f03fe | ||
|
|
796a8bc2b3 | ||
|
|
f790e06bc3 | ||
|
|
6c93c24107 | ||
|
|
ac65b549ac | ||
|
|
f3bb32f284 | ||
|
|
3deda6cab6 | ||
|
|
866ccbe818 | ||
|
|
3442a5c475 | ||
|
|
704feb689c | ||
|
|
dcc9b447cb | ||
|
|
56b797b0bb | ||
|
|
1e628422a3 | ||
|
|
26563d7f30 | ||
|
|
6cc874356c | ||
|
|
4aae7a0129 | ||
|
|
6701f53be9 | ||
|
|
4a7932a15e | ||
|
|
95e62bec1f | ||
|
|
e10aca5304 | ||
|
|
2aec524970 | ||
|
|
9eaf3d8b71 | ||
|
|
9dcc6a79fa | ||
|
|
e39db348e5 | ||
|
|
1def28243d | ||
|
|
c675cda187 | ||
|
|
f535d0e8dd | ||
|
|
2ebc6b71c9 | ||
|
|
0ca065b7c2 | ||
|
|
4944af825e | ||
|
|
7c44272788 | ||
|
|
3dc0bd320a | ||
|
|
fe5c2bcdc2 | ||
|
|
3f0a28fe18 | ||
|
|
b0885e18a1 | ||
|
|
ab4f43d818 | ||
|
|
00b572dfdf | ||
|
|
0f5b7340dd | ||
|
|
d224010908 | ||
|
|
f412cb04d7 | ||
|
|
dc7240348e | ||
|
|
9efcd841b9 | ||
|
|
641fd77b4e | ||
|
|
e89b790394 | ||
|
|
90eed8c8ef | ||
|
|
4a83f39eb5 | ||
|
|
4369f2be15 | ||
|
|
43242c8672 | ||
|
|
ab475d3ed7 | ||
|
|
c0d68d79cc | ||
|
|
15f134a85a | ||
|
|
3fab712c1e | ||
|
|
518f5407fa | ||
|
|
7f10a5a590 | ||
|
|
058d0730dd | ||
|
|
4e92b11f09 | ||
|
|
af77e4c2e4 | ||
|
|
e3460fa423 | ||
|
|
771b5010e7 | ||
|
|
ab39638013 | ||
|
|
0b95b4b080 | ||
|
|
b8fb0338e1 | ||
|
|
3f917ab018 | ||
|
|
dd51aedc2f | ||
|
|
6616fca614 | ||
|
|
2e3f36a958 | ||
|
|
dd1f8458b4 | ||
|
|
9d9b5da489 | ||
|
|
cecc9022fa | ||
|
|
ce11b31f3b | ||
|
|
d2c84c9d4c | ||
|
|
88b883191b | ||
|
|
8129295567 | ||
|
|
d2c7d51d00 | ||
|
|
b05ee61a96 | ||
|
|
148eaf9c80 | ||
|
|
03c71fa649 | ||
|
|
3414173a17 | ||
|
|
a4b9a51304 | ||
|
|
27d6979ea5 | ||
|
|
97c9723c8f | ||
|
|
212fa3171c | ||
|
|
e295cb5837 | ||
|
|
9d9d80c124 | ||
|
|
f1abfa5fde | ||
|
|
ef9c8bf522 | ||
|
|
893683b559 | ||
|
|
2b95216443 | ||
|
|
e661113bbd | ||
|
|
cda85a59ac | ||
|
|
566a276495 | ||
|
|
31fc221295 | ||
|
|
e70c75b60c | ||
|
|
0a705f29e4 | ||
|
|
f76ec3d91c | ||
|
|
22ec52bf2c | ||
|
|
30031f7d7b | ||
|
|
2959f467ce | ||
|
|
190a1de6f7 | ||
|
|
59cd4c1aff | ||
|
|
bf3b49d23b | ||
|
|
10f5bdffe2 | ||
|
|
b23edef723 | ||
|
|
06dd4c14e4 | ||
|
|
ef55b435e5 | ||
|
|
af3dd14fe0 | ||
|
|
4de27cdee1 | ||
|
|
f32d372a9a | ||
|
|
4346c1d6ad | ||
|
|
7f0109069b | ||
|
|
b0b00d426c | ||
|
|
38a1faf55d | ||
|
|
c08d333386 | ||
|
|
7bcb155f6a | ||
|
|
904b727ac0 | ||
|
|
7910ae1578 | ||
|
|
96fa9f7471 | ||
|
|
b4c27dac05 | ||
|
|
b7e70987ff | ||
|
|
c22e0f8ec8 | ||
|
|
0f9ec68b45 | ||
|
|
2e8e30099e | ||
|
|
2258424a33 | ||
|
|
0794fd91bd | ||
|
|
55b724ed82 | ||
|
|
df2b544093 | ||
|
|
2309f23a21 | ||
|
|
14b3afe238 | ||
|
|
c733edf13c | ||
|
|
47039fc7d4 | ||
|
|
c25ce452cb | ||
|
|
b135fb8a59 | ||
|
|
7ca1ca6a5c | ||
|
|
31eba923af | ||
|
|
d48bfb5671 | ||
|
|
fa638dd3d6 | ||
|
|
d2c52297ae | ||
|
|
1c49c133ea | ||
|
|
52221a939c | ||
|
|
2fd91c95fc | ||
|
|
35be8465ea | ||
|
|
8d5e233acf | ||
|
|
662c5c5248 | ||
|
|
bf8d2862f8 | ||
|
|
40e93383ae | ||
|
|
93791cc2a2 | ||
|
|
e19725fcbe | ||
|
|
9d12e06dbc | ||
|
|
4994130a96 | ||
|
|
03b7305ca1 | ||
|
|
54ec59e5ee | ||
|
|
ce8db94ed2 | ||
|
|
bf34dfb00a |
+1
-1
@@ -1 +1 @@
|
||||
* @actions/virtual-environments-owners
|
||||
* @actions/runner-images-team
|
||||
|
||||
@@ -34,14 +34,17 @@ body:
|
||||
- label: GitHub Actions
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Virtual environments affected
|
||||
label: Runner images affected
|
||||
options:
|
||||
- label: Ubuntu 18.04
|
||||
- label: Ubuntu 20.04
|
||||
- label: Ubuntu 22.04
|
||||
- label: macOS 10.15
|
||||
- label: Ubuntu 24.04
|
||||
- label: macOS 11
|
||||
- label: macOS 12
|
||||
- label: macOS 13
|
||||
- label: macOS 13 Arm64
|
||||
- label: macOS 14
|
||||
- label: macOS 14 Arm64
|
||||
- label: Windows Server 2019
|
||||
- label: Windows Server 2022
|
||||
- type: textarea
|
||||
|
||||
@@ -13,17 +13,21 @@ body:
|
||||
label: Platforms affected
|
||||
options:
|
||||
- label: Azure DevOps
|
||||
- label: GitHub Actions
|
||||
- label: GitHub Actions - Standard Runners
|
||||
- label: GitHub Actions - Larger Runners
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Virtual environments affected
|
||||
label: Runner images affected
|
||||
options:
|
||||
- label: Ubuntu 18.04
|
||||
- label: Ubuntu 20.04
|
||||
- label: Ubuntu 22.04
|
||||
- label: macOS 10.15
|
||||
- label: Ubuntu 24.04
|
||||
- label: macOS 11
|
||||
- label: macOS 12
|
||||
- label: macOS 13
|
||||
- label: macOS 13 Arm64
|
||||
- label: macOS 14
|
||||
- label: macOS 14 Arm64
|
||||
- label: Windows Server 2019
|
||||
- label: Windows Server 2022
|
||||
- type: textarea
|
||||
@@ -31,8 +35,8 @@ body:
|
||||
label: Image version and build link
|
||||
description: |
|
||||
Image version where you are experiencing the issue. Where to find image version in build logs:
|
||||
1. For GitHub Actions, under "Set up job" -> "Virtual Environment" -> "Version".
|
||||
2. For Azure DevOps, under "Initialize job" -> "Virtual Environment" -> "Version".
|
||||
1. For GitHub Actions, under "Set up job" -> "Runner Image" -> "Version".
|
||||
2. For Azure DevOps, under "Initialize job" -> "Runner Image" -> "Version".
|
||||
|
||||
If you have a public example, please, provide a link to the failed build.
|
||||
validations:
|
||||
|
||||
@@ -2,5 +2,5 @@ blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Get help in GitHub Discussions
|
||||
url: https://github.com/actions/virtual-environments/discussions
|
||||
about: Have a question? Feel free to ask in the virtual-environments GitHub Discussions!
|
||||
url: https://github.com/actions/runner-images/discussions
|
||||
about: Have a question? Feel free to ask in the runner-images GitHub Discussions!
|
||||
|
||||
@@ -55,14 +55,17 @@ body:
|
||||
- label: GitHub Actions
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Virtual environments where you need the tool
|
||||
label: Runner images where you need the tool
|
||||
options:
|
||||
- label: Ubuntu 18.04
|
||||
- label: Ubuntu 20.04
|
||||
- label: Ubuntu 22.04
|
||||
- label: macOS 10.15
|
||||
- label: Ubuntu 24.04
|
||||
- label: macOS 11
|
||||
- label: macOS 12
|
||||
- label: macOS 13
|
||||
- label: macOS 13 Arm64
|
||||
- label: macOS 14
|
||||
- label: macOS 14 Arm64
|
||||
- label: Windows Server 2019
|
||||
- label: Windows Server 2022
|
||||
- type: textarea
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Description
|
||||
New tool, Bug fixing, or Improvement?
|
||||
Please include a summary of the change and which issue is fixed. Also include relevant motivation and context.
|
||||
New tool, Bug fixing, or Improvement?
|
||||
Please include a summary of the change and which issue is fixed. Also include relevant motivation and context.
|
||||
**For new tools, please provide total size and installation time.**
|
||||
|
||||
<!-- Currently, we can't accept external contributions to macOS source. Please find more details in [CONTRIBUTING.md](CONTRIBUTING.md#macOS) guide -->
|
||||
|
||||
@@ -25,8 +25,10 @@ jobs:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
@@ -38,11 +40,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -53,7 +55,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -67,4 +69,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
@@ -10,15 +10,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create release for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
uses: actions/create-release@v1.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
tag_name: ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
release_name: ${{ github.event.client_payload.ReleaseTitle }}
|
||||
tag: ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
name: ${{ github.event.client_payload.ReleaseTitle }}
|
||||
body: ${{ github.event.client_payload.ReleaseBody }}
|
||||
prerelease: ${{ github.event.client_payload.Prerelease }}
|
||||
commitish: ${{ github.event.client_payload.Commitish }}
|
||||
commit: ${{ github.event.client_payload.Commitish }}
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -22,11 +22,11 @@ jobs:
|
||||
|
||||
- name: Create pull request for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
id: create-pr
|
||||
uses: actions/github-script@v2
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
let response = await github.pulls.create({
|
||||
let response = await github.rest.pulls.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: "${{ github.event.client_payload.PullRequestTitle }}",
|
||||
@@ -37,13 +37,13 @@ jobs:
|
||||
return response.data.number
|
||||
|
||||
- name: Request reviewers
|
||||
uses: actions/github-script@v2
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.PRAPPROVAL_SECRET}}
|
||||
script: |
|
||||
github.pulls.requestReviewers({
|
||||
github.rest.pulls.requestReviewers({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: ${{ steps.create-pr.outputs.result }},
|
||||
team_reviewers: ['virtual-environments-akvelon']
|
||||
})
|
||||
team_reviewers: ['runner-images-team']
|
||||
})
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
name: Create SBOM for the release
|
||||
# Inherited variables:
|
||||
# github.event.client_payload.agentSpec - Current YAML Label
|
||||
# github.event.client_payload.ReleaseID - Current release ID
|
||||
# github.event.client_payload.imageVersion - AzDO image version "major.minor"
|
||||
# github.event.client_payload.ReleaseBranchName - Necessary to identify workflow run
|
||||
#
|
||||
# Current SYFT tool issues:
|
||||
# macOS (major): prompt privilegies that blocking process indefinetely (https://github.com/anchore/syft/issues/1367)
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [generate-sbom]
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
jobs:
|
||||
#Checking current release for SBOM
|
||||
sbom-check:
|
||||
outputs:
|
||||
check_status: ${{ steps.check.outputs.status }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check release for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
id: check
|
||||
shell: pwsh
|
||||
run: |
|
||||
$apiUrl = "https://api.github.com/repos/actions/runner-images/releases/${{ github.event.client_payload.ReleaseID }}"
|
||||
$response = Invoke-RestMethod -Uri $apiUrl -Method Get -SkipHttpErrorCheck
|
||||
if ($response.message -ilike "Not Found") {
|
||||
echo "status=release_not_found" >> $env:GITHUB_OUTPUT
|
||||
Write-Error "Release ${{ github.event.client_payload.ReleaseID }} wasn't found"
|
||||
exit 1
|
||||
}
|
||||
foreach ($asset in $response.assets) {
|
||||
if ($asset.name -like '*sbom*') {
|
||||
echo "status=sbom_exists" >> $env:GITHUB_OUTPUT
|
||||
return "Release ${{ github.event.client_payload.ReleaseID }} already contains a SBOM"
|
||||
}
|
||||
}
|
||||
Write-Host "Release has been found, SBOM is not attached, starting generation."
|
||||
echo "status=okay" >> $env:GITHUB_OUTPUT
|
||||
#Generating SBOM
|
||||
building-sbom:
|
||||
needs: sbom-check
|
||||
if: ${{ needs.sbom-check.outputs.check_status == 'okay' }}
|
||||
runs-on: ${{ github.event.client_payload.agentSpec }}
|
||||
steps:
|
||||
- name: Available image version check for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
run: |
|
||||
$imageVersionComponents = $env:ImageVersion.Split('.')
|
||||
$imageMajorVersion = $imageVersionComponents[0]
|
||||
$imageMinorVersion = $imageVersionComponents[1]
|
||||
if ("$imageMajorVersion.$imageMinorVersion" -ne '${{ github.event.client_payload.imageVersion }}') {
|
||||
throw "Current runner $imageMajorVersion.$imageMinorVersion image version doesn't match ${{ github.event.client_payload.imageVersion }}."
|
||||
}
|
||||
- name: Install SYFT tool on Windows
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b D:/syft
|
||||
- name: Install SYFT tool on Ubuntu or macOS
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v0.100.0
|
||||
#Running section.
|
||||
- name: Run SYFT on Windows
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: D:/syft/syft dir:C:/ -vv -o spdx-json=sbom.json
|
||||
- name: Run SYFT on Ubuntu
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: syft dir:/ -vv -o spdx-json=sbom.json
|
||||
- name: Run SYFT on macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: sudo syft dir:/ -vv -o spdx-json=sbom.json --exclude ./Users --exclude ./System/Volumes --exclude ./private
|
||||
shell: bash
|
||||
#Preparing artifact (raw SBOM.json is too big)
|
||||
- name: Compress SBOM file
|
||||
run: Compress-Archive sbom.json sbom.json.zip
|
||||
#Upload artifact action
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sbom-${{ github.event.client_payload.agentSpec }}-${{ github.event.client_payload.imageVersion }}
|
||||
path: sbom.json.zip
|
||||
if-no-files-found: warn
|
||||
#Upload release asset action
|
||||
#Might be changed to softprops/action-gh-release after additional check
|
||||
- name: Upload release asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: "https://uploads.github.com/repos/actions/runner-images/releases/${{ github.event.client_payload.ReleaseID }}/assets{?name,label}"
|
||||
asset_path: ./sbom.json.zip
|
||||
asset_name: sbom.${{ github.event.client_payload.agentSpec }}.json.zip
|
||||
asset_content_type: application/zip
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -24,11 +24,11 @@ jobs:
|
||||
sleep 30
|
||||
|
||||
- name: Approve pull request by GitHub-Actions bot
|
||||
uses: actions/github-script@v2
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.PRAPPROVAL_SECRET}}
|
||||
script: |
|
||||
github.pulls.createReview({
|
||||
github.rest.pulls.createReview({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: ${{ github.event.client_payload.PullRequestNumber }},
|
||||
@@ -36,11 +36,11 @@ jobs:
|
||||
});
|
||||
|
||||
- name: Merge pull request for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
uses: actions/github-script@v2
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.pulls.merge({
|
||||
github.rest.pulls.merge({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: ${{ github.event.client_payload.PullRequestNumber }},
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# CI Validation
|
||||
|
||||
name: PowerShell Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'helpers/software-report-base/**'
|
||||
|
||||
jobs:
|
||||
powershell-tests:
|
||||
name: PowerShell tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Software Report module tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
Invoke-Pester -Output Detailed "helpers/software-report-base/tests"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
name: Trigger Build workflow
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_type:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
jobs:
|
||||
trigger-workflow:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Build workflow
|
||||
env:
|
||||
CI_PR_TOKEN: ${{ secrets.CI_PR_TOKEN }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
CI_PR: ${{ secrets.CI_REPO }}
|
||||
run: |
|
||||
$headers = @{
|
||||
Authorization="Bearer $env:CI_PR_TOKEN"
|
||||
}
|
||||
|
||||
# Private repository for builds
|
||||
$apiRepoUrl = "https://api.github.com/repos/$env:CI_PR"
|
||||
|
||||
$eventType = "trigger-${{ inputs.image_type }}-build"
|
||||
$body = @{
|
||||
event_type = $eventType;
|
||||
client_payload = @{
|
||||
pr_title = "$env:PR_TITLE"
|
||||
custom_repo = "${{ github.event.pull_request.head.repo.full_name }}"
|
||||
custom_repo_commit_hash = "${{ github.event.pull_request.head.sha }}"
|
||||
}
|
||||
}
|
||||
|
||||
$bodyString = $body | ConvertTo-Json
|
||||
|
||||
try {
|
||||
Invoke-WebRequest -Uri "$apiRepoUrl/dispatches" -Method Post -Headers $headers -Body $bodyString | Out-Null
|
||||
} catch {
|
||||
throw "$($_.exception[0].message)"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Trigger Ubuntu20.04 CI
|
||||
run-name: Ubuntu20.04 - ${{ github.event.pull_request.title }}
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: labeled
|
||||
paths:
|
||||
- 'images/ubuntu/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
jobs:
|
||||
Ubuntu_2004:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'CI ubuntu-all') || contains(github.event.pull_request.labels.*.name, 'CI ubuntu-2004')
|
||||
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
|
||||
with:
|
||||
image_type: 'ubuntu2004'
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Trigger Ubuntu22.04 CI
|
||||
run-name: Ubuntu22.04 - ${{ github.event.pull_request.title }}
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: labeled
|
||||
paths:
|
||||
- 'images/ubuntu/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
jobs:
|
||||
Ubuntu_2204:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'CI ubuntu-all') || contains(github.event.pull_request.labels.*.name, 'CI ubuntu-2204')
|
||||
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
|
||||
with:
|
||||
image_type: 'ubuntu2204'
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Trigger Ubuntu24.04 CI
|
||||
run-name: Ubuntu24.04 - ${{ github.event.pull_request.title }}
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: labeled
|
||||
paths:
|
||||
- 'images/ubuntu/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
jobs:
|
||||
Ubuntu_2404:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'CI ubuntu-all') || contains(github.event.pull_request.labels.*.name, 'CI ubuntu-2404')
|
||||
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
|
||||
with:
|
||||
image_type: 'ubuntu2404'
|
||||
secrets: inherit
|
||||
@@ -10,19 +10,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Update release for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
uses: actions/github-script@v2
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const response = await github.repos.getReleaseByTag({
|
||||
const response = await github.rest.repos.getReleaseByTag({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
tag: "${{ github.event.client_payload.ReleaseBranchName }}"
|
||||
});
|
||||
github.repos.updateRelease({
|
||||
github.rest.repos.updateRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: response.data.id,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Trigger Windows19 CI
|
||||
run-name: Windows2019 - ${{ github.event.pull_request.title }}
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: labeled
|
||||
paths:
|
||||
- 'images/windows/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
jobs:
|
||||
Windows_2019:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'CI windows-all') || contains(github.event.pull_request.labels.*.name, 'CI windows-2019')
|
||||
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
|
||||
with:
|
||||
image_type: 'windows2019'
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Trigger Windows22 CI
|
||||
run-name: Windows2022 - ${{ github.event.pull_request.title }}
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: labeled
|
||||
paths:
|
||||
- 'images/windows/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
jobs:
|
||||
Windows_2022:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'CI windows-all') || contains(github.event.pull_request.labels.*.name, 'CI windows-2022')
|
||||
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
|
||||
with:
|
||||
image_type: 'windows2022'
|
||||
secrets: inherit
|
||||
@@ -267,6 +267,12 @@ paket-files/
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# VSCode settings
|
||||
.vscode/**
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"hashicorp.hcl",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"ms-vscode.powershell",
|
||||
"timonwong.shellcheck"
|
||||
]
|
||||
}
|
||||
Vendored
+23
-1
@@ -1,3 +1,25 @@
|
||||
{
|
||||
"files.trimTrailingWhitespace": false
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"powershell.codeFormatting.addWhitespaceAroundPipe": true,
|
||||
"powershell.codeFormatting.alignPropertyValuePairs": true,
|
||||
"powershell.codeFormatting.autoCorrectAliases": true,
|
||||
"powershell.codeFormatting.newLineAfterCloseBrace": false,
|
||||
"powershell.codeFormatting.newLineAfterOpenBrace": true,
|
||||
"powershell.codeFormatting.openBraceOnSameLine": true,
|
||||
"powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline",
|
||||
"powershell.codeFormatting.preset": "OTBS",
|
||||
"powershell.codeFormatting.trimWhitespaceAroundPipe": true,
|
||||
"powershell.codeFormatting.whitespaceAfterSeparator": true,
|
||||
"powershell.codeFormatting.whitespaceAroundOperator": true,
|
||||
"powershell.codeFormatting.whitespaceBeforeOpenBrace": true,
|
||||
"powershell.codeFormatting.whitespaceBeforeOpenParen": true,
|
||||
"powershell.codeFormatting.whitespaceBetweenParameters": true,
|
||||
"powershell.codeFormatting.whitespaceInsideBrace": true,
|
||||
"shellcheck.exclude": [
|
||||
"SC1090","SC2096"
|
||||
],
|
||||
"shellcheck.customArgs": [
|
||||
"-x"
|
||||
]
|
||||
}
|
||||
|
||||
+37
-32
@@ -1,59 +1,64 @@
|
||||
## Contributing
|
||||
# Contributing
|
||||
|
||||
[fork]: https://github.com/actions/virtual-environments/fork
|
||||
[pr]: https://github.com//actions/virtual-environments/compare
|
||||
[fork]: https://github.com/actions/runner-images/fork
|
||||
[pr]: https://github.com//actions/runner-images/compare
|
||||
[code-of-conduct]: CODE_OF_CONDUCT.md
|
||||
|
||||
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
|
||||
|
||||
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [MIT](LICENSE.md).
|
||||
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [MIT](LICENSE.md) license.
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
|
||||
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project, you agree to abide by its terms.
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
1. [Fork][fork] and clone the repository
|
||||
1. Create a new branch: `git checkout -b my-branch-name`
|
||||
1. Make your changes, ensure that they include steps to install, validate post-install and update software report (please see [How to add new tool](CONTRIBUTING.md#how-to-add-new-tool) for details).
|
||||
1. Test your changes by [creating VHD and deploying a VM](docs/create-image-and-azure-resources.md).
|
||||
1. Push to your fork and [submit a pull request][pr]
|
||||
1. [Fork][fork] and clone the repository.
|
||||
1. Create a new branch: `git checkout -b my-branch-name`.
|
||||
1. Make your changes, ensuring that they include steps to install, validate post-install, and update the software report (please see [How to add a new tool](CONTRIBUTING.md#how-to-add-a-new-tool) for details).
|
||||
1. Test your changes by [creating an image and deploying a VM](docs/create-image-and-azure-resources.md).
|
||||
1. Push to your fork and [submit a pull request][pr].
|
||||
|
||||
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
|
||||
|
||||
- Follow the style guide for [Powershell](https://github.com/PoshCode/PowerShellPracticeAndStyle) when writing Windows scripts. There is currently no set style for the Shell scripts that run Linux installs :soon:.
|
||||
- Include complete details of why this is needed in the PR description.
|
||||
- Include complete details of why this is needed in the PR description.
|
||||
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
- Write [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
- For new tools:
|
||||
- Make sure that the tool satisfies [Software Guidelines](README.md#software-guidelines).
|
||||
- Create an issue and get an approval from us to add this tool to the image before creating the pull request.
|
||||
- Make sure that the tool satisfies the [Software Guidelines](README.md#software-guidelines).
|
||||
- Create an issue and get approval from us to add this tool to the image before creating the pull request.
|
||||
|
||||
## How to add a new tool
|
||||
|
||||
## How to add new tool
|
||||
### General rules
|
||||
- For every new tool add validation scripts and update software report script to make sure that it is included to documentation
|
||||
- If the tool is available in other platforms (macOS, Windows, Linux), make sure you include it in as many as possible.
|
||||
- If installing a few versions of the tool, consider putting the list of versions in the corresponding `toolset.json` file. It will help other customers to configure their builds flexibly. See [toolset-windows-2019.json](images/win/toolsets/toolset-2019.json) as example.
|
||||
- Use consistent naming across all files
|
||||
- Validation scripts should be simple and shouldn't change image content
|
||||
|
||||
- For every new tool, add validation scripts and update the software report script to ensure that it is included in the documentation.
|
||||
- If the tool is available on multiple platforms (macOS, Windows, Linux), make sure you include it on as many as possible.
|
||||
- If installing multiple versions of the tool, consider putting the list of versions in the corresponding `toolset.json` file. This will help other customers configure their builds flexibly. See [toolset-windows-2019.json](images/windows/toolsets/toolset-2019.json) as an example.
|
||||
- Use consistent naming across all files.
|
||||
- Validation scripts should be simple and shouldn't change the image content.
|
||||
|
||||
### Windows
|
||||
- Add a script that will install the tool and put the script in the `scripts/Installers` folder.
|
||||
There are a bunch of helper functions that could simplify your code: `Choco-Install`, `Install-Binary`, `Install-VsixExtension`, `Start-DownloadWithRetry`, `Test-IsWin19`, `Test-IsWin22` (find the full list of helpers in [ImageHelpers.psm1](images/win/scripts/ImageHelpers/ImageHelpers.psm1)).
|
||||
- Add a script that will validate the tool installation and put the script in the `scripts/Tests` folder.
|
||||
We use [Pester v5](https://github.com/pester/pester) for validation scripts. If the tests for the tool are complex enough, create a separate `*.Tests.ps1`. Otherwise, use `Tools.Tests.ps1` for simple tests.
|
||||
Add `Invoke-PesterTests -TestFile <testFileName> [-TestName <describeName>]` at the end of the installation script to make sure that your tests will be run.
|
||||
- Add changes to the software report generator `images/win/scripts/SoftwareReport/SoftwareReport.Generator.ps1`. The software report generator is used to generate an image's README file, e.g. [Windows2019-Readme.md](images/win/Windows2019-Readme.md) and uses [MarkdownPS](https://github.com/Sarafian/MarkdownPS).
|
||||
|
||||
- Add a script that will install the tool and put the script in the `scripts/build` folder.
|
||||
There are a bunch of helper functions that could simplify your code: `Install-ChocoPackage`, `Install-Binary`, `Install-VSIXFromFile`, `Install-VSIXFromUrl`, `Invoke-DownloadWithRetry`, `Test-IsWin19`, `Test-IsWin22` (find the full list of helpers in [ImageHelpers.psm1](images/windows/scripts/helpers/ImageHelpers.psm1)).
|
||||
- Add a script that will validate the tool installation and put the script in the `scripts/tests` folder.
|
||||
We use [Pester v5](https://github.com/pester/pester) for validation scripts. If the tests for the tool are complex enough, create a separate `*.Tests.ps1`. Otherwise, use `Tools.Tests.ps1` for simple tests.
|
||||
Add `Invoke-PesterTests -TestFile <testFileName> [-TestName <describeName>]` at the end of the installation script to ensure that your tests will be run.
|
||||
- Add changes to the software report generator `images/windows/scripts/docs-gen/Generate-SoftwareReport.ps1`. The software report generator is used to generate an image's README file, e.g. [Windows2019-Readme.md](images/windows/Windows2019-Readme.md) and uses [MarkdownPS](https://github.com/Sarafian/MarkdownPS).
|
||||
|
||||
### Ubuntu
|
||||
- Add script that will install and validate the tool and put the script in the `scripts/installers` folder.
|
||||
Use existing scripts such as [github-cli.sh](images/linux/scripts/installers/github-cli.sh) as a starting point.
|
||||
- Use [helpers](images/linux/scripts/helpers/install.sh) to simplify installation process.
|
||||
- Validation part should `exit 1` if any issue with installation.
|
||||
- Add changes to the software report generator `images/linux/scripts/SoftwareReport/SoftwareReport.Generator.ps1`. The software report generator is used to generate an image's README file, e.g. [Ubuntu1804-Readme.md](images/linux/Ubuntu1804-README.md) and it uses [MarkdownPS](https://github.com/Sarafian/MarkdownPS).
|
||||
|
||||
- Add a script that will install and validate the tool and put the script in the `scripts/build` folder.
|
||||
Use existing scripts such as [github-cli.sh](images/ubuntu/scripts/build/github-cli.sh) as a starting point.
|
||||
- Use [helpers](images/ubuntu/scripts/helpers/install.sh) to simplify the installation process.
|
||||
- The validation part should `exit 1` if there is any issue with the installation.
|
||||
- Add changes to the software report generator `images/ubuntu/scripts/docs-gen/Generate-SoftwareReport.ps1`. The software report generator is used to generate an image's README file, e.g. [Ubuntu2004-Readme.md](images/ubuntu/Ubuntu2004-README.md) and it uses [MarkdownPS](https://github.com/Sarafian/MarkdownPS).
|
||||
|
||||
### macOS
|
||||
macOS source lives in this repository and available for everyone. However, macOS image-generation CI doesn't support external contributions yet so we are not able to accept pull-requests for now.
|
||||
We are in the process of preparing macOS CI to accept contributions. Until then, we appreciate your patience and ask you continue to make tool requests by filing issues.
|
||||
|
||||
The macOS source lives in this repository and is available for everyone. However, the macOS image-generation CI doesn't support external contributions yet, so we are not able to accept pull requests for now.
|
||||
We are in the process of preparing the macOS CI to accept contributions. Until then, we appreciate your patience and ask that you continue to make tool requests by filing issues.
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 GitHub
|
||||
Copyright (c) 2024 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,62 +1,214 @@
|
||||
# GitHub Actions Virtual Environments
|
||||
Repository contains the source used to create the [virtual environments](https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners) for GitHub Actions hosted runners, as well as the VM images of [Microsoft-hosted agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops#use-a-microsoft-hosted-agent) used for Azure Pipelines.
|
||||
To build a VM machine from this repo's source, see the [instructions](docs/create-image-and-azure-resources.md).
|
||||
|
||||
How to get in touch with us:
|
||||
- To file a bug report, or request tools to be added/updated, please [open an issue using the appropriate template](https://github.com/actions/virtual-environments/issues/new/choose)
|
||||
- If you want to share your thoughts about image configuration, installed software, or bring some idea, please, create a new topic in a [discussions section](https://github.com/actions/virtual-environments/discussions) for a corresponding category. Before making a new discussion please make sure no similar topics were created earlier.
|
||||
|
||||
For general questions about using the virtual environments or writing your Actions workflow, please open requests in the [GitHub Actions Community Forum](https://github.community/c/github-actions/41).
|
||||
|
||||
## Available Environments
|
||||
| Environment | YAML Label | Included Software | Latest Release & Rollout Progress |
|
||||
| --------------------|---------------------|--------------------|---------------------|
|
||||
| Ubuntu 22.04 <sup>beta</sup> | `ubuntu-22.04` | [ubuntu-22.04] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=ubuntu22&redirect=1)
|
||||
| Ubuntu 20.04 | `ubuntu-latest` or `ubuntu-20.04` | [ubuntu-20.04] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=ubuntu20&redirect=1)
|
||||
| Ubuntu 18.04 | `ubuntu-18.04` | [ubuntu-18.04] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=ubuntu18&redirect=1)
|
||||
| macOS 12 | `macos-12`| [macOS-12] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=macos-12&redirect=1)
|
||||
| macOS 11 | `macos-latest` or `macos-11`| [macOS-11] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=macos-11&redirect=1)
|
||||
| macOS 10.15 <sup>deprecated</sup> | `macos-10.15` | [macOS-10.15] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=macos-10.15&redirect=1)
|
||||
| Windows Server 2022 | `windows-latest` or `windows-2022` | [windows-2022] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=windows-2022&redirect=1) |
|
||||
| Windows Server 2019 | `windows-2019` | [windows-2019] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=windows-2019&redirect=1)
|
||||
|
||||
<b>Note:</b> Beta and Preview images are provided "as-is", "with all faults" and "as available" and are excluded from the service level agreement and warranty. Beta and Preview images may not be covered by customer support.
|
||||
|
||||
***What images are available for GitHub Actions and Azure DevOps?***
|
||||
The availability of images for GitHub Actions and Azure DevOps is different. See documentation for more details:
|
||||
- [GitHub Actions](https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners#supported-runners-and-hardware-resources)
|
||||
- [Azure DevOps](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#software)
|
||||
|
||||
***What image version is used in my build?*** Usually, image deployment takes 3-4 days, and documentation in the `main` branch is only updated when deployment is finished. To find out which image version and what software versions are used in a specific build, see `Set up job` (GitHub Actions) or `Initialize job` (Azure DevOps) step log.
|
||||
|
||||
***Looking for other Linux distributions?*** We do not plan to offer other Linux distributions. We recommend using Docker if you'd like to build using other distributions with the hosted virtual environments. Alternatively, you can leverage [self-hosted runners] and fully customize your environment to your needs.
|
||||
|
||||
***How to contribute to macOS source?*** macOS source lives in this repository and available for everyone. However, MacOS image-generation CI doesn't support external contributions yet so we are not able to accept pull-requests for now.
|
||||
We are in the process of preparing MacOS CI to accept contributions. Until then, we appreciate your patience and ask you continue to make tool requests by filing issues.
|
||||
|
||||
## Updates to virtual environments
|
||||
*Cadence*
|
||||
|
||||
We typically deploy weekly updates to the software on the virtual environments.
|
||||
For some tools, we always install the latest at the time of the deployment; for others,
|
||||
we pin the tool to specific version(s).
|
||||
|
||||
*Following Along / Change Notifications*
|
||||
|
||||
* **High Impact Changes** (ex. breaking changes, new or deprecated environments) will be posted to the GitHub Changelog on our [blog](https://github.blog/changelog/) and on [twitter](https://twitter.com/GHchangelog).
|
||||
* **Low Impact Changes** will be pinned in this repository and marked with the [Announcement](https://github.com/actions/virtual-environments/labels/Announcement) label.
|
||||
* **Regular Weekly Rhythm** can be followed by watching [Releases](https://github.com/actions/virtual-environments/releases). Pre-release is created when deployment is started. As soon as deployment is finished, it is converted to release.
|
||||
You can also track upcoming changes using the [awaiting-deployment](https://github.com/actions/virtual-environments/labels/awaiting-deployment) label.
|
||||
|
||||
[ubuntu-22.04]: https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2204-Readme.md
|
||||
[ubuntu-20.04]: https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md
|
||||
[ubuntu-18.04]: https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu1804-Readme.md
|
||||
[windows-2022]: https://github.com/actions/virtual-environments/blob/main/images/win/Windows2022-Readme.md
|
||||
[windows-2019]: https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
|
||||
[macOS-11]: https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11-Readme.md
|
||||
[macOS-12]: https://github.com/actions/virtual-environments/blob/main/images/macos/macos-12-Readme.md
|
||||
[macOS-10.15]: https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md
|
||||
[self-hosted runners]: https://help.github.com/en/actions/hosting-your-own-runners
|
||||
|
||||
## Software and image guidelines
|
||||
To learn more about tools and images support policy, see the [guidelines](./docs/software-and-images-guidelines.md).
|
||||
|
||||
# GitHub Actions Runner Images
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
- [About](#about)
|
||||
- [Available Images](#available-images)
|
||||
- [Announcements](#announcements)
|
||||
- [Image Definitions](#image-definitions)
|
||||
- [Image Releases](#image-releases)
|
||||
- [Software and Image Support](#software-and-image-support)
|
||||
- [How to Interact with the Repo](#how-to-interact-with-the-repo)
|
||||
- [FAQs](#faqs)
|
||||
|
||||
## About
|
||||
|
||||
This repository contains the source code used to create the VM images for [GitHub-hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners) used for Actions, as well as for [Microsoft-hosted agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops#use-a-microsoft-hosted-agent) used for Azure Pipelines.
|
||||
To build a VM machine from this repo's source, see the [instructions](docs/create-image-and-azure-resources.md).
|
||||
|
||||
## Available Images
|
||||
|
||||
| Image | YAML Label | Included Software | Rollout Status of Latest Image Release |
|
||||
| --------------------|---------------------|--------------------|--------------------|
|
||||
| Ubuntu 24.04 <sup>beta</sup> | `ubuntu-24.04` | [ubuntu-24.04] |  |
|
||||
| Ubuntu 22.04 | `ubuntu-latest` or `ubuntu-22.04` | [ubuntu-22.04] |  |
|
||||
| Ubuntu 20.04 | `ubuntu-20.04` | [ubuntu-20.04] |  |
|
||||
| macOS 14 | `macos-latest-large` or `macos-14-large`| [macOS-14] |  |
|
||||
| macOS 14 Arm64 |`macos-latest`, `macos-14`, `macos-latest-xlarge` or `macos-14-xlarge`| [macOS-14-arm64] |  |
|
||||
| macOS 13 | `macos-13` or `macos-13-large` | [macOS-13] |  |
|
||||
| macOS 13 Arm64 | `macos-13-xlarge` | [macOS-13-arm64] |  |
|
||||
| macOS 12 | `macos-12` or `macos-12-large`| [macOS-12] |  |
|
||||
| macOS 11 <sup>deprecated</sup> | `macos-11`| [macOS-11] |  |
|
||||
| Windows Server 2022 | `windows-latest` or `windows-2022` | [windows-2022] |  |
|
||||
| Windows Server 2019 | `windows-2019` | [windows-2019] |  |
|
||||
|
||||
### Label scheme
|
||||
|
||||
- In general the `-latest` label is used for the latest OS image version that is GA
|
||||
- Before moving the`-latest` label to a new OS version we will announce the change and give sufficient lead time for users to update their workflows
|
||||
|
||||
[ubuntu-24.04]: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md
|
||||
[ubuntu-22.04]: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md
|
||||
[ubuntu-20.04]: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2004-Readme.md
|
||||
[windows-2022]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md
|
||||
[windows-2019]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2019-Readme.md
|
||||
[macOS-11]: https://github.com/actions/runner-images/blob/main/images/macos/macos-11-Readme.md
|
||||
[macOS-12]: https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md
|
||||
[macOS-13]: https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md
|
||||
[macOS-13-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-13-arm64-Readme.md
|
||||
[macOS-14]: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
||||
[macOS-14-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md
|
||||
[self-hosted runners]: https://help.github.com/en/actions/hosting-your-own-runners
|
||||
|
||||
## Announcements
|
||||
|
||||
See notable upcoming changes by viewing issues with the [Announcement](https://github.com/actions/runner-images/labels/Announcement) label.
|
||||
|
||||
## Image Definitions
|
||||
|
||||
### Beta
|
||||
|
||||
The purpose of a Beta is to collect feedback on an image before it is released to GA. The goal of a Beta is to identify and fix any potential issues that exist on that
|
||||
image. Images are updated on a weekly cadence. Any workflows that run on a beta image do not fall under the customer [SLA](https://github.com/customer-terms/github-online-services-sla) in place for Actions.
|
||||
Customers choosing to use Beta images are encouraged to provide feedback in the runner-images repo by creating an issue. A Beta may take on different availability, i.e. public vs private.
|
||||
|
||||
### GA
|
||||
|
||||
A GA (General Availability) image has been through a Beta period and is deemed ready for general use. Images are updated on a weekly cadence. In order to be moved to
|
||||
GA the image must meet the following criteria:
|
||||
|
||||
1. Has been through a Beta period (public or private)
|
||||
2. Most major software we install on the image has a compatible
|
||||
version for the underlying OS and
|
||||
3. All major bugs reported during the Beta period have been addressed.
|
||||
|
||||
This image type falls under the customer [SLA](https://github.com/customer-terms/github-online-services-sla) for actions. GA images are eventually deprecated according to our guidelines as we only support the
|
||||
latest 2 versions of an OS.
|
||||
|
||||
#### Latest Migration Process
|
||||
|
||||
GitHub Actions and Azure DevOps use the `-latest` YAML label (ex: `ubuntu-latest`, `windows-latest`, and `macos-latest`). These labels point towards the newest stable OS version available.
|
||||
|
||||
|
||||
The `-latest` migration process is gradual and happens over 1-2 months in order to allow customers to adapt their workflows to the newest OS version. During this process, any workflow using the `-latest` label, may see changes in the OS version in their workflows or pipelines. To avoid unwanted migration, users can specify a specific OS version in the yaml file (ex: macos-12, windows-2022, ubuntu-22.04).
|
||||
|
||||
## Image Releases
|
||||
|
||||
*How to best follow along with changes*
|
||||
|
||||
1. Find the latest releases for this repository [here.](https://github.com/actions/runner-images/releases)
|
||||
2. Subscribe to the releases coming out of this repository, instructions [here.](https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#configuring-your-watch-settings-for-an-individual-repository)
|
||||
3. Upcoming changes: A pre-release is created when the deployment of an image has started. As soon as the deployment is finished, the pre-release is converted to a release. If you have subscribed to releases, you will get notified of pre-releases as well.
|
||||
|
||||
- You can also track upcoming changes using the [awaiting-deployment](https://github.com/actions/runner-images/labels/awaiting-deployment) label.
|
||||
4. For high impact changes, we will post these in advance to the GitHub Changelog on our [blog](https://github.blog/changelog/) and on [twitter](https://twitter.com/GHchangelog).
|
||||
- Ex: breaking changes, GA or deprecation of images
|
||||
|
||||
*Cadence*
|
||||
|
||||
- We typically deploy weekly updates to the software on the runner images.
|
||||
|
||||
## Software and Image Support
|
||||
|
||||
### Support Policy
|
||||
|
||||
- Tools and versions will typically be removed 6 months after they are deprecated or have reached end-of-life
|
||||
- We support (at maximum) 2 GA images and 1 beta image at a time. We begin the deprecation process of the oldest image label once the newest OS image label has been released to GA.
|
||||
- The images generally contain the latest versions of packages installed except for Ubuntu LTS where we mostly rely on the Canonical-provided repositories.
|
||||
|
||||
- Popular tools can have several versions installed side-by-side with the following strategy:
|
||||
|
||||
| Tool name | Installation strategy |
|
||||
|-----------|-----------------------|
|
||||
| Docker images | not more than 3 latest LTS OS\tool versions. New images or new versions of current images are added using the standard tool request process |
|
||||
| Java | all LTS versions |
|
||||
| Node.js | 3 latest LTS versions |
|
||||
| Go | 3 latest minor versions |
|
||||
| Python <br/> Ruby | 5 most popular `major.minor` versions |
|
||||
| PyPy | 3 most popular `major.minor` versions |
|
||||
| .NET Core | 2 latest LTS versions and 1 latest version. For each feature version only latest patch is installed |
|
||||
| GCC <br/> GNU Fortran <br/> Clang <br/> GNU C++ | 3 latest major versions |
|
||||
| Android NDK | 1 latest non-LTS, 2 latest LTS versions |
|
||||
| Xcode | - all OS compatible versions side-by-side <br/> - for beta, GM versions - latest beta only <br/> - old patch versions are deprecated in 3 months |
|
||||
|
||||
### Package managers usage
|
||||
|
||||
We use third-party package managers to install software during the image generation process. The table below lists the package managers and the software installed.
|
||||
> **Note**: third-party repositories are re-evaluated every year to identify if they are still useful and secure.
|
||||
|
||||
| Operating system | Package manager | Third-party repos and packages |
|
||||
| :--- | :---: | ---: |
|
||||
| Ubuntu | [APT](https://wiki.debian.org/Apt) | [containers](https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable) (Ubuntu 20 only) <br/> [docker](https://download.docker.com/linux/ubuntu) (Ubuntu20 0nly) <br/> [Eclipse-Temurin (Adoptium)](https://packages.adoptium.net/artifactory/deb/) <br/> [Erlang](https://packages.erlang-solutions.com/ubuntu) <br/> [Firefox](http://ppa.launchpad.net/mozillateam/ppa/ubuntu) <br/> [git-lfs](https://packagecloud.io/install/repositories/github/git-lfs) <br/> [git](https://launchpad.net/~git-core/+archive/ubuntu/ppa) <br/> [Google Cloud CLI](https://packages.cloud.google.com/apt) <br/> [Heroku](https://cli-assets.heroku.com/channels/stable/apt) <br/> [HHvm](https://dl.hhvm.com/ubuntu) <br/> [MongoDB](https://repo.mongodb.org/apt/ubuntu) <br/> [Mono](https://download.mono-project.com/repo/ubuntu) <br/> [MS Edge](https://packages.microsoft.com/repos/edge) <br/> [PostgreSQL](https://apt.postgresql.org/pub/repos/apt/) <br/> [R](https://cloud.r-project.org/bin/linux/ubuntu) |
|
||||
| | [pipx](https://pypa.github.io/pipx) | ansible-core <br/>yamllint |
|
||||
| Windows | [Chocolatey](https://chocolatey.org) | No third-party repos installed |
|
||||
| macOS | [Homebrew](https://brew.sh) | [aws-cli v2](https://github.com/aws/homebrew-tap) </br> [azure/bicep](https://github.com/Azure/homebrew-bicep) </br> [mongodb/brew](https://github.com/mongodb/homebrew-brew) |
|
||||
| | [pipx](https://pypa.github.io/pipx/) | yamllint |
|
||||
|
||||
### Image Deprecation Policy
|
||||
|
||||
- Images begin the deprecation process of the oldest image label once a new GA OS version has been released.
|
||||
- Deprecation process begins with an announcement that sets a date for deprecation
|
||||
- As it gets closer to the date, GitHub begins doing scheduled brownouts of the image
|
||||
- During this time there will be an Announcement pinned in the repo to remind users of the deprecation.
|
||||
- Finally GitHub will deprecate the image and it will no longer be available
|
||||
|
||||
### Preinstallation Policy
|
||||
|
||||
In general, these are the guidelines we follow when deciding what to pre-install on our images:
|
||||
|
||||
- Popularity: widely-used tools and ecosystems will be given priority.
|
||||
- Latest Technology: recent versions of tools will be given priority.
|
||||
- Deprecation: end-of-life tools and versions will not be added.
|
||||
- Licensing: MIT, Apache, or GNU licenses are allowed.
|
||||
- Time & Space on the Image: we will evaluate how much time is saved and how much space is used by having the tool pre-installed.
|
||||
- Support: If a tool requires the support of more than one version, we will consider the cost of this maintenance.
|
||||
|
||||
### Default Version Update Policy
|
||||
|
||||
- In general, once a new version is installed on the image, we announce the default version update 2 weeks prior to deploying it.
|
||||
- For potentially dangerous updates, we may extend the timeline up to 1 month between the announcement and deployment.
|
||||
|
||||
## How to Interact with the Repo
|
||||
|
||||
- **Issues**: To file a bug report, or request tools to be added/updated, please [open an issue using the appropriate template](https://github.com/actions/runner-images/issues/new/choose)
|
||||
- **Discussions**: If you want to share your thoughts about image configuration, installed software, or bring a new idea, please create a new topic in a [discussion](https://github.com/actions/runner-images/discussions) for a corresponding category. Before making a new discussion please make sure no similar topics were created earlier.
|
||||
- For general questions about using the runner images or writing your Actions workflow, please open requests in the [GitHub Actions Community Forum](https://github.community/c/github-actions/41).
|
||||
|
||||
## FAQs
|
||||
|
||||
<details>
|
||||
<summary><b><i>What images are available for GitHub Actions and Azure DevOps?</b></i></summary>
|
||||
|
||||
The availability of images for GitHub Actions and Azure DevOps is the same. However, deprecation policies may differ. See documentation for more details:
|
||||
- [GitHub Actions](https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners#supported-runners-and-hardware-resources)
|
||||
- [Azure DevOps](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#software)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b><i>What image version is used in my build?</b></i></summary>
|
||||
|
||||
Usually, image deployment takes 2-3 days, and documentation in the `main` branch is only updated when deployment is finished. To find out which image version and what software versions are used in a specific build, see `Set up job` (GitHub Actions) or `Initialize job` (Azure DevOps) step log.
|
||||
<img width="1440" alt="actions-runner-image" src="https://github.com/actions/runner-images/assets/88318005/922a8bf5-3e4d-4265-9527-b3b51e6bf9c8">
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b><i>Looking for other Linux distributions?</b></i></summary>
|
||||
|
||||
We do not plan to offer other Linux distributions. We recommend using Docker if you'd like to build using other distributions with the hosted runner images. Alternatively, you can leverage [self-hosted runners] and fully customize your VM image to your needs.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b><i>How do I contribute to the macOS source?</b></i></summary>
|
||||
|
||||
macOS source lives in this repository and is available for everyone. However, macOS image-generation CI doesn't support external contributions yet so we are not able to accept pull-requests for now.
|
||||
|
||||
We are in the process of preparing macOS CI to accept contributions. Until then, we appreciate your patience and ask you to continue to make tool requests by filing issues.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b><i>How does GitHub determine what tools are installed on the images?</b></i></summary>
|
||||
|
||||
For some tools, we always install the latest at the time of the deployment; for others, we pin the tool to specific version(s). For more details please see the [Preinstallation Policy](#preinstallation-policy)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b><i>How do I request that a new tool be pre-installed on the image?</b></i></summary>
|
||||
Please create an issue and get an approval from us to add this tool to the image before creating the pull request.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b><i>What branch should I use to build custom image?</b></i></summary>
|
||||
We strongly encourage customers to build their own images using the main branch.
|
||||
This repository contains multiple branches and releases that serve as document milestones to reflect what software is installed in the images at certain point of time. Current builds are not idempotent and if one tries to build a runner image using the specific tag it is not guaranteed that the build will succeed.
|
||||
</details>
|
||||
|
||||
@@ -1,189 +1,311 @@
|
||||
# Virtual-Environments
|
||||
The virtual-environments project uses [Packer](https://www.packer.io/) to generate disk images for the following platforms: Windows 2019/2022, Ubuntu 18.04/20.04/22.04.
|
||||
Each image is configured through a JSON template that Packer understands and which specifies where to build the image (Azure in this case), and what scripts to run to install software and prepare the disk.
|
||||
The Packer process initializes a connection to Azure subscription via Azure CLI, and automatically creates the temporary Azure resources required to build the source VM(temporary resource group, network interfaces, and VM from the "clean" image specified in the template).
|
||||
If the VM deployment succeeds, the build agent connects to the VM and starts to execute installation steps from the JSON template.
|
||||
If any step in the JSON template fails, image generation will be aborted and the temporary VM will be terminated. Packer will also attempt to cleanup all the temporary resources it created (unless otherwise told).
|
||||
After successful image generation, a snapshot of the temporary VM will be converted to VHD image and then uploaded to the specified Azure Storage Account.
|
||||
# GitHub Actions Runner Images
|
||||
|
||||
## Prerequisites and Image-generation
|
||||
### Build Agent requirements
|
||||
- `OS` - Windows/Linux
|
||||
- `packer` - Can be downloaded from https://www.packer.io/downloads
|
||||
- `PowerShell 5.0 or higher` or `PSCore` for linux distributes.
|
||||
- `Azure CLI ` - https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
|
||||
- `Azure Az Powershell module` - https://docs.microsoft.com/en-us/powershell/azure/install-az-ps
|
||||
- `Git for Windows` - https://gitforwindows.org/
|
||||
The runner-images project uses [Packer](https://www.packer.io/) to generate disk images for Windows 2019/2022 and Ubuntu 20.04/22.04.
|
||||
|
||||
> To connect to a temporary VM packer uses WinRM or SSH connections on public IP interfaces.
|
||||
If you use a build agent located in an Azure subscription, please make sure that HTTPS/SSH ports are allowed for incoming/outgoing connections.
|
||||
In case of firewall restrictions, prohibiting connections from public addresses, private virtual network resources can be deployed and passed as arguments to the packer. This approach allows virtual machines to use private connections inside VLAN.
|
||||
Each image is configured by a HCL2 Packer template that specifies where to build the image (Azure, in this case),
|
||||
and what steps to run to install software and prepare the disk.
|
||||
|
||||
### Service principal
|
||||
Packer uses Service Principal to authorize in Azure infrastructure. To setup image-generation CI or use packer manually — SP with full read-write permissions for selected Azure subscription needed.
|
||||
Detailed instruction can be found in [Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)
|
||||
The Packer process initializes a connection to the Azure subscription using Azure CLI and creates temporary resources
|
||||
required for the build process: a resource group, network interfaces and a virtual machine from the "clean" image specified in the template.
|
||||
|
||||
### Prepare environment and image deployment
|
||||
#### How to prepare Windows build agent
|
||||
Local machine or [Azure VM](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/quick-create-cli) can be used as a build agent.
|
||||
If the VM deployment succeeds, Packer connects to it using SSH or WinRM and begins executing installation steps from the template one-by-one.
|
||||
If any step fails, image generation is aborted, and the temporary VM is terminated.
|
||||
Packer also attempts to clean up all the temporary resources it created (unless otherwise configured).
|
||||
|
||||
Download & install `packer` from https://www.packer.io/downloads, or install it via [Chocolatey](https://chocolatey.org/):
|
||||
```
|
||||
choco install packer
|
||||
After successful completion of all installation steps, Packer creates a managed image from the temporary VM's disk and deletes the VM.
|
||||
|
||||
- [Build Agent Preparation](#build-agent-preparation)
|
||||
- [Manual image generation](#manual-image-generation)
|
||||
- [Manual Image Generation Customization](#manual-image-generation-customization)
|
||||
- [Network Security](#network-security)
|
||||
- [Azure Subscription Authentication](#azure-subscription-authentication)
|
||||
- [Generated Machine Deployment](#generated-machine-deployment)
|
||||
- [Automated image generation](#automated-image-generation)
|
||||
- [Required variables](#required-variables)
|
||||
- [Optional variables](#optional-variables)
|
||||
- [Builder variables](#builder-variables)
|
||||
- [Toolset](#toolset)
|
||||
- [Post-generation scripts](#post-generation-scripts)
|
||||
- [Running scripts](#running-scripts)
|
||||
- [Script Details: Ubuntu](#script-details-ubuntu)
|
||||
- [Script Details: Windows](#script-details-windows)
|
||||
|
||||
## Build Agent Preparation
|
||||
|
||||
The build agent is a machine where the Packer process will be started.
|
||||
You can use any physical or virtual machine running Windows or Linux OS.
|
||||
Of course, you may also use an [Azure VM](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/quick-create-cli).
|
||||
In any case, you will need these software installed:
|
||||
|
||||
- Packer 1.8.2 or higher.
|
||||
|
||||
Download and install it manually from [here](https://www.packer.io/downloads) or use [Chocolatey](https://chocolatey.org/):
|
||||
|
||||
```powershell
|
||||
choco install packer
|
||||
```
|
||||
|
||||
- Git.
|
||||
|
||||
For Linux - install the latest version from your distro's package repo.
|
||||
|
||||
For Windows - download and install it from [here](https://gitforwindows.org/) of use [Chocolatey](https://chocolatey.org/):
|
||||
|
||||
```powershell
|
||||
choco install git -params '"/GitAndUnixToolsOnPath"'
|
||||
```
|
||||
|
||||
- Powershell 5.0 or higher.
|
||||
|
||||
In Windows you already have it.
|
||||
|
||||
For Linux follow instructions [here](https://learn.microsoft.com/en-us/windows-server/administration/linux-package-repository-for-microsoft-software)
|
||||
to add Microsoft's Linux Software Repository and then install the `powershell` package.
|
||||
|
||||
- Azure CLI.
|
||||
|
||||
Follow the instructions [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli).
|
||||
Or if you use Windows, you may run this command in Powershell instead:
|
||||
|
||||
```powershell
|
||||
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi
|
||||
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; rm .\AzureCLI.msi
|
||||
```
|
||||
|
||||
## Manual image generation
|
||||
|
||||
This repository includes a script that assists in generating images in Azure.
|
||||
All you need is an Azure subscription and a build agent configured as described above.
|
||||
We suggest starting with building the UbuntuMinimal image because it includes only basic software and builds in less than 30 minutes.
|
||||
|
||||
All the commands below should be executed in PowerShell.
|
||||
|
||||
First, clone the runner-images repository and set the current directory to it:
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/actions/runner-images.git
|
||||
Set-Location runner-images
|
||||
```
|
||||
|
||||
Download & install `git` from https://github.com/git-for-windows/git/releases, or install it via [Chocolatey](https://chocolatey.org/):
|
||||
```
|
||||
choco install git -params '"/GitAndUnixToolsOnPath"'
|
||||
```
|
||||
|
||||
Install the Azure Az PowerShell module - https://docs.microsoft.com/en-us/powershell/azure/install-az-ps.
|
||||
```
|
||||
Install-Module -Name Az -Repository PSGallery -Force
|
||||
```
|
||||
|
||||
Install Azure CLI - https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows?view=azure-cli-latest&tabs=azure-cli.
|
||||
```
|
||||
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; rm .\AzureCLI.msi
|
||||
```
|
||||
|
||||
Download Virtual-Environments repository.
|
||||
```
|
||||
Set-Location c:\
|
||||
git clone https://github.com/actions/virtual-environments.git
|
||||
```
|
||||
|
||||
Import [GenerateResourcesAndImage](../helpers/GenerateResourcesAndImage.ps1) script from `/helpers` folder, and run `GenerateResourcesAndImage` function via Powershell.
|
||||
|
||||
```
|
||||
Set-Location C:\virtual-environments
|
||||
Then, import the [GenerateResourcesAndImage](../helpers/GenerateResourcesAndImage.ps1) script from the `helpers` subdirectory:
|
||||
|
||||
```powershell
|
||||
Import-Module .\helpers\GenerateResourcesAndImage.ps1
|
||||
|
||||
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "myTestResourceGroup" -ImageGenerationRepositoryRoot "$pwd" -ImageType Ubuntu1804 -AzureLocation "East US"
|
||||
```
|
||||
Where:
|
||||
- `SubscriptionId` - The Azure subscription Id where resources will be created.
|
||||
- `ResourceGroupName` - The Azure resource group name where the Azure resources will be created.
|
||||
- `ImageGenerationRepositoryRoot` - The root path of the image generation repository source.
|
||||
- `ImageType` - The type of the image being generated. Valid options are: "Windows2019", "Windows2022", "Ubuntu1804", "Ubuntu2004", "Ubuntu2204".
|
||||
- `AzureLocation` - The location of the resources being created in Azure. For example "East US".
|
||||
|
||||
The function automatically creates all required Azure resources and kicks off packer image generation for the selected image type.
|
||||
|
||||
For optional authentication via service principal make sure to provide the following params — `AzureClientId`, `AzureClientSecret`, `AzureTenantId`, so the whole command will be:
|
||||
|
||||
```
|
||||
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "myTestResourceGroup" -ImageGenerationRepositoryRoot "$pwd" -ImageType Ubuntu1804 -AzureLocation "East US" -AzureClientId {AADApplicationID} -AzureClientSecret {AADApplicationSecret} -AzureTenantId {AADTenantID}
|
||||
```
|
||||
|
||||
As extra options, you can add more params for permit to add Azure Tags on resources and enable https for Storage Account. It could be helpful on some tenants with hardenning policy. Params are — `EnableHttpsTrafficOnly` (Boolean) and `tags` (HashTable), so the whole command will be:
|
||||
Finally, run the `GenerateResourcesAndImage` function, setting the mandatory arguments: image type and where to build and store the resulting managed image:
|
||||
|
||||
```
|
||||
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "myTestResourceGroup" -ImageGenerationRepositoryRoot "$pwd" -ImageType Ubuntu1804 -AzureLocation "East US" -EnableHttpsTrafficOnly $true -tags @{dept="devops";env="prod"}
|
||||
- `SubscriptionId` - your Azure Subscription ID;
|
||||
- `ResourceGroupName` - the name of the resource group that will be created within your subscription (e.g., "imagegen-test");
|
||||
- `AzureLocation` - the location where resources will be created (e.g., "East US");
|
||||
- `ImageType` - the type of image to build (we suggest choosing "UbuntuMinimal" here; other valid options are "Windows2019", "Windows2022", "Ubuntu2004", "Ubuntu2204").
|
||||
|
||||
This function automatically creates all required Azure resources and initiates the Packer image generation for the selected image type.
|
||||
|
||||
When the image is ready, you may proceed to [deployment](#generated-machine-deployment).
|
||||
|
||||
## Manual Image Generation Customization
|
||||
|
||||
The `GenerateResourcesAndImage` function accepts a number of arguments that may assist you in generating an image in your specific environment.
|
||||
|
||||
For example, you may want all the resources involved in the image generation process to be tagged.
|
||||
In this case, pass a HashTable of tags as a value for the `Tags` parameter.
|
||||
|
||||
If you don't want the function to authenticate interactively, you should create a Service Principal and invoke the function with the parameters `AzureClientId`, `AzureClientSecret` and `AzureTenantId`.
|
||||
You can find more details in the [corresponding section below](#azure-subscription-authentication).
|
||||
|
||||
Use `get-help GenerateResourcesAndImage -Detailed` for the complete list of available parameters.
|
||||
|
||||
### Network Security
|
||||
|
||||
To connect to a temporary virtual machine, Packer uses WinRM or SSH.
|
||||
|
||||
If your build agent is located outside of the Azure subscription where the temporary VM is created, a public network interface and public IP address are used.
|
||||
Make sure that firewalls are configured properly and that WinRM (TCP port 5986) and SSH (TCP port 22) connections are allowed both outgoing for the build agent and incoming for the temporary VM.
|
||||
Also, if you don't want the temporary VM to be accessible from everywhere, set the `RestrictToAgentIpAddress` parameter value to `$true`
|
||||
to set up firewall rules allowing access only from your build agent's public IP address.
|
||||
|
||||
If your build agent and temporary VM are in the same subscription, you can configure Packer to connect using a private virtual network.
|
||||
To achieve this, set proper values for the environment variables `VNET_RESOURCE_GROUP`, `VNET_NAME` and `VNET_SUBNET`.
|
||||
|
||||
### Azure Subscription Authentication
|
||||
|
||||
Packer uses a Service Principal to authenticate in Azure infrastructure.
|
||||
For more information about Service Principals, refer to the
|
||||
[Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal).
|
||||
|
||||
The `GenerateResourcesAndImage` function is able to create a Service Principal to be used by Packer.
|
||||
It uses the Connect-AzAccount cmdlet that invokes an interactive authentication process by default.
|
||||
If you don't want to use interactive authentication, you should create a Service Principal with full read-write permissions for the selected Azure subscription on your own
|
||||
and provide proper values for the parameters `AzureClientId`, `AzureClientSecret` and `AzureTenantId`.
|
||||
|
||||
Here is an example of how to create a Service Principal using the Az PowerShell module:
|
||||
|
||||
```powershell
|
||||
$credentials = [Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential]@{
|
||||
StartDateTime = Get-Date
|
||||
EndDateTime = (Get-Date).AddDays(7)
|
||||
}
|
||||
|
||||
$sp = New-AzADServicePrincipal -DisplayName "imagegen-app"
|
||||
$appCred = New-AzADAppCredential -ApplicationId $sp.AppId -PasswordCredentials $credentials
|
||||
|
||||
Start-Sleep -Seconds 30
|
||||
New-AzRoleAssignment -RoleDefinitionName "Contributor" -PrincipalId $sp.Id
|
||||
Start-Sleep -Seconds 30
|
||||
|
||||
@{
|
||||
ClientId = $sp.AppId
|
||||
ClientSecret = $appCred.SecretText
|
||||
TenantId = (Get-AzSubscription -SubscriptionId $SubscriptionId).TenantId
|
||||
}
|
||||
```
|
||||
|
||||
*Please, check synopsis of `GenerateResourcesAndImage` for details about non-mandatory parameters.*
|
||||
## Generated Machine Deployment
|
||||
|
||||
#### Generated VM Deployment
|
||||
After the successful image generation, Virtual Machine can be created from the generated VHD using [CreateAzureVMFromPackerTemplate](../helpers/CreateAzureVMFromPackerTemplate.ps1) script.
|
||||
|
||||
```
|
||||
Set-Location C:\virtual-environments
|
||||
After successful image generation, a Virtual Machine can be created from the generated image using the [CreateAzureVMFromPackerTemplate](../helpers/CreateAzureVMFromPackerTemplate.ps1) script.
|
||||
|
||||
```powershell
|
||||
Import-Module .\helpers\CreateAzureVMFromPackerTemplate.ps1
|
||||
|
||||
CreateAzureVMFromPackerTemplate -SubscriptionId {YourSubscriptionId} -ResourceGroupName {ResourceGroupName} -TemplateFile "C:\BuildVmImages\temporaryTemplate.json" -VirtualMachineName "testvm1" -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus"
|
||||
CreateAzureVMFromPackerTemplate -SubscriptionId {YourSubscriptionId} -ResourceGroupName {ResourceGroupName} -ManagedImageName "Runner-Image-Ubuntu2204" -VirtualMachineName "testvm1" -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus"
|
||||
```
|
||||
|
||||
Where:
|
||||
- `SubscriptionId` - The Azure subscription Id where resources will be created.
|
||||
- `ResourceGroupName` - The Azure resource group name where the Azure virtual machine will be created.
|
||||
- `TemplateFilePath` - The path to the json ARM-template generated by packer during image generation locally.*
|
||||
- `VirtualMachineName` - The name of the virtual machine to be generated.
|
||||
- `AdminUserName` - The administrator username for the virtual machine to be created.
|
||||
- `AdminPassword` - The administrator password for the virtual machine to be created.
|
||||
- `AzureLocation` - The location where the Azure virtual machine will be provisioned. Example: "eastus"
|
||||
|
||||
\* *ARM-template can be obtained from the Packer output. For now, it seems like there is an [Az CLI bug](https://github.com/Azure/azure-cli/issues/5899) with specifying the template through a URI, so download the template from URI, that will be printed at the bottom of image-generation log, and use the local path of the template file.*
|
||||
- `SubscriptionId` - the Azure subscription ID where resources will be created;
|
||||
- `ResourceGroupName` - the Azure resource group name where the Azure virtual machine will be created;
|
||||
- `ManagedImageName` - the name of the managed image to be used for the virtual machine creation;
|
||||
- `VirtualMachineName` - the name of the virtual machine to be generated;
|
||||
- `AdminUserName` - the administrator username for the virtual machine to be created;
|
||||
- `AdminPassword` - the administrator password for the virtual machine to be created;
|
||||
- `AzureLocation` - the location where the Azure virtual machine will be provisioned (e.g., "eastus").
|
||||
|
||||
The function creates an Azure VM from a template and generates network resources in Azure to make the VM accessible.
|
||||
This function creates an Azure VM and generates network resources in Azure to make the VM accessible.
|
||||
|
||||
## Additional
|
||||
### User variables
|
||||
The Packer template includes `variables` section containing user variables used in image generation. Each variable is defined as a key/value strings. User variables can be passed to packer via predefined environment variables, or as direct arguments, in case if packer started manually.
|
||||
## Automated image generation
|
||||
|
||||
- `build_resource_group_name` - Specify an existing resource group to run the build in it. By default, a temporary resource group will be created and destroyed as part of the build. If you do not have permission to do so, use build_resource_group_name to specify an existing resource group to run the build in it.
|
||||
- `client_id` - The application ID of the AAD Service Principal. Requires `client_secret`.
|
||||
- `object_id` - The object ID for the AAD SP. Will be derived from the oAuth token if empty.
|
||||
- `client_secret` - The password or secret for your service principal.
|
||||
- `client_cert_path` - The location of a PEM file containing a certificate and private key for service principal.
|
||||
- `subscription_id` - The subscription to use.
|
||||
- `tenant_id` - The Active Directory tenant identifier with which your `client_id` and `subscription_id` are associated. If not specified, `tenant_id` will be looked up using `subscription_id`.
|
||||
- `resource_group` - Resource group under which the final artifact will be stored.
|
||||
- `storage_account` - Storage account under which the final artifact will be stored.
|
||||
- `location` - Azure datacenter in which your VM will be built.
|
||||
- `temp_resource_group_name` - Name assigned to the temporary resource group created during the build. If this value is not set, a random value will be assigned. This resource group is deleted at the end of the build.
|
||||
- `private_virtual_network_with_public_ip` - This value allows you to set a `virtual_network_name` and obtain a public IP. If this value is not set and `virtual_network_name` is defined Packer is only allowed to be executed from a host on the same subnet / virtual network.
|
||||
- `virtual_network_name` - Use a pre-existing virtual network for the VM. This option enables private communication with the VM, no public IP address is used or provisioned (unless you set `private_virtual_network_with_public_ip`).
|
||||
- `virtual_network_resource_group_name` - If `virtual_network_name` is set, this value may also be set. If `virtual_network_name` is set, and this value is not set the builder attempts to determine the resource group containing the virtual network. If the resource group cannot be found, or it cannot be disambiguated, this value should be set.
|
||||
- `virtual_network_subnet_name` - If `virtual_network_name` is set, this value may also be set. If `virtual_network_name` is set, and this value is not set the builder attempts to determine the subnet to use with the virtual network. If the subnet cannot be found, or it cannot be disambiguated, this value should be set.
|
||||
- `capture_name_prefix` - VHD prefix. The final artifacts will be named PREFIX-osDisk.UUID and PREFIX-vmTemplate.UUID.
|
||||
If you want to generate images automatically (e.g., as a part of a CI/CD pipeline),
|
||||
you can use Packer directly. To do this, you will need:
|
||||
|
||||
- a build agent configured as described in the
|
||||
[Build agent preparation](#build-agent-preparation) section;
|
||||
- an Azure subscription and Service Principal configured as described in the
|
||||
[Azure subscription authentication](#azure-subscription-authentication) section;
|
||||
- a resource group created in your Azure subscription where the managed image will be stored;
|
||||
- a string to be used as a password for the user used to install software (Windows only).
|
||||
|
||||
Then, you can invoke Packer in your CI/CD pipeline using the following command:
|
||||
|
||||
```powershell
|
||||
packer build -var "subscription_id=$SubscriptionId" `
|
||||
-var "client_id=$ClientId" `
|
||||
-var "client_secret=$ClientSecret" `
|
||||
-var "install_password=$InstallPassword" `
|
||||
-var "location=$Location" `
|
||||
-var "managed_image_name=$ImageName" `
|
||||
-var "managed_image_resource_group_name=$ImageResourceGroupName" `
|
||||
-var "tenant_id=$TenantId" `
|
||||
$TemplatePath
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `SubscriptionId` - your Azure Subscription ID;
|
||||
- `ClientId` and `ClientSecret` - Service Principal credentials;
|
||||
- `TenantId` - Azure Tenant ID;
|
||||
- `InstallPassword` - password for the user used to install software (Windows only);
|
||||
- `Location` - location where resources will be created (e.g., "East US");
|
||||
- `ImageName` and `ImageResourceGroupName` - name of the resource group where the managed image will be stored;
|
||||
- `TemplatePath` - path to the Packer template file (e.g., "images/windows/templates/windows-2022.pkr.hcl").
|
||||
|
||||
### Required variables
|
||||
|
||||
The following variables are required to be passed to the Packer process:
|
||||
|
||||
| Template var | Env var | Description
|
||||
| ------------ | ------- | -----------
|
||||
| `subscription_id` | `ARM_SUBSCRIPTION_ID` | The subscription under which the build will be performed.
|
||||
| `client_id` | `ARM_CLIENT_ID` | The Active Directory service principal associated with your builder.
|
||||
| `client_secret` | `ARM_CLIENT_SECRET` | The password or secret for your service principal; may be omitted if `client_cert_path` is set.
|
||||
| `client_cert_path` | `ARM_CLIENT_CERT_PATH` | The location of a PEM file containing a certificate and private key for the service principal; may be omitted if `client_secret` is set.
|
||||
| `location` | `ARM_RESOURCE_LOCATION` | The Azure datacenter in which your VM will be built.
|
||||
| `managed_image_resource_group_name` | `ARM_RESOURCE_GROUP` | The resource group under which the final artifact will be stored.
|
||||
|
||||
### Optional variables
|
||||
|
||||
The following variables are optional:
|
||||
|
||||
- `managed_image_name` - the name of the managed image to create. If not specified, "Runner-Image-{{ImageType}}" will be used;
|
||||
- `build_resource_group_name` - specify an existing resource group to run the build in; by default, a temporary resource group will be created and destroyed as part of the build; if you do not have permission to do so, use `build_resource_group_name` to specify an existing resource group to run the build in;
|
||||
- `object_id` - the object ID for the AAD SP; will be derived from the oAuth token if empty;
|
||||
- `tenant_id` - the Active Directory tenant identifier with which your `client_id` and `subscription_id` are associated; if not specified, `tenant_id` will be looked up using `subscription_id`;
|
||||
- `temp_resource_group_name` - the name assigned to the temporary resource group created during the build; if this value is not set, a random value will be assigned; this resource group is deleted at the end of the build;
|
||||
- `private_virtual_network_with_public_ip` - this value allows you to set a `virtual_network_name` and obtain a public IP; if this value is not set and `virtual_network_name` is defined, Packer is only allowed to be executed from a host on the same subnet / virtual network;
|
||||
- `virtual_network_name` - use a pre-existing virtual network for the VM; this option enables private communication with the VM, no public IP address is used or provisioned (unless you set `private_virtual_network_with_public_ip`);
|
||||
- `virtual_network_resource_group_name` - if `virtual_network_name` is set, this value may also be set; if `virtual_network_name` is set, and this value is not set, the builder attempts to determine the resource group containing the virtual network; if the resource group cannot be found, or it cannot be disambiguated, this value should be set;
|
||||
- `virtual_network_subnet_name` - if `virtual_network_name` is set, this value may also be set; if `virtual_network_name` is set, and this value is not set, the builder attempts to determine the subnet to use with the virtual network; if the subnet cannot be found, or it cannot be disambiguated, this value should be set.
|
||||
|
||||
## Builder variables
|
||||
|
||||
### Builder variables
|
||||
The `builders` section contains variables for the `azure-arm` builder used in the project. Most of the builder variables are inherited from the `user variables` section, however, the variables can be overwritten to adjust image-generation performance.
|
||||
|
||||
- `vm_size` - Size of the VM used for building. This can be changed when you deploy a VM from your VHD.
|
||||
- `image_os` - Type of OS that will be deployed as a temporary VM.
|
||||
- `image_version` - Specify version of an OS to boot from.
|
||||
- `vm_size` - the size of the VM used for building; this can be changed when you deploy a VM from your image;
|
||||
- `image_os` - the type of OS that will be deployed as a temporary VM;
|
||||
- `image_version` - specify the version of an OS to boot from.
|
||||
|
||||
**Detailed Azure builders documentation can be found in [packer documentation](https://www.packer.io/docs/builders/azure).**
|
||||
**Detailed Azure builders documentation can be found in the [packer documentation](https://www.packer.io/docs/builders/azure).**
|
||||
|
||||
### Toolset
|
||||
Configuration for some installed software is located in `toolset.json` files. These files define the list of Ruby, Python, Go versions, the list of PowerShell modules and VS components that will be installed to image. They can be changed if these tools are not required to reduce image generation time or image size.
|
||||
## Toolset
|
||||
|
||||
The configuration for some installed software is located in `toolset.json` files. These files define the list of Ruby, Python, Go versions, the list of PowerShell modules and VS components that will be installed on the image. They can be changed if these tools are not required, to reduce image generation time or image size.
|
||||
|
||||
Generated tool versions and details can be found in related projects:
|
||||
|
||||
- [Python](https://github.com/actions/python-versions/)
|
||||
- [Go](https://github.com/actions/go-versions)
|
||||
- [Node](https://github.com/actions/node-versions)
|
||||
|
||||
### Post-generation scripts
|
||||
## Post-generation scripts
|
||||
|
||||
> :warning: These scripts are intended to run on a VM deployed in Azure
|
||||
> :warning: These scripts are intended to be run on a VM deployed in Azure
|
||||
|
||||
The user, created during the image generation, does not exist in the result VHD hence some configuration files related to the user's home directory need to be changed as well as the file permissions for some directories. Scripts for that are located in the `post-generation` folder in the repository:
|
||||
- Windows: https://github.com/actions/virtual-environments/tree/main/images/win/post-generation
|
||||
- Linux: https://github.com/actions/virtual-environments/tree/main/images/linux/post-generation
|
||||
The user, created during the image generation, does not exist in the resulting image. Hence, some configuration files related to the user's home directory need to be changed, as well as the file permissions for some directories. Scripts for that are located in the `post-gen` folder in the repository:
|
||||
|
||||
- Windows: <https://github.com/actions/runner-images/tree/main/images/windows/assets/post-gen>
|
||||
- Linux: <https://github.com/actions/runner-images/tree/main/images/ubuntu/assets/post-gen>
|
||||
|
||||
**Note:** The default user for Linux should have `sudo privileges`.
|
||||
|
||||
The scripts are copied to the VHD during the image generation process to the following paths:
|
||||
The scripts are copied to the image during the generation process to the following paths:
|
||||
|
||||
- Windows: `C:\post-generation`
|
||||
- Linux: `/opt/post-generation`
|
||||
- Linux: `/opt/post-generation`
|
||||
|
||||
#### Running scripts
|
||||
### Running scripts
|
||||
|
||||
##### Ubuntu
|
||||
- Ubuntu
|
||||
|
||||
sudo su -c "find /opt/post-generation -mindepth 1 -maxdepth 1 -type f -name '*.sh' -exec bash {} \;"
|
||||
```bash
|
||||
sudo su -c "find /opt/post-generation -mindepth 1 -maxdepth 1 -type f -name '*.sh' -exec bash {} \;"
|
||||
```
|
||||
|
||||
##### Windows
|
||||
- Windows
|
||||
|
||||
Get-ChildItem C:\post-generation -Filter *.ps1 | ForEach-Object { & $_.FullName }
|
||||
```powershell
|
||||
Get-ChildItem C:\post-generation -Filter *.ps1 | ForEach-Object { & $_.FullName }
|
||||
```
|
||||
|
||||
#### Script details
|
||||
### Script Details: Ubuntu
|
||||
|
||||
##### Ubuntu
|
||||
- **cleanup-logs.sh** - removes all build process logs from the machine;
|
||||
- **environment-variables.sh** - replaces `$HOME` with the default user's home directory for environment variables related to the default user home directory;
|
||||
- **homebrew-permissions.sh** - resets the Homebrew repository directory by running `git reset --hard` to make the working tree clean after changing permissions in /home and changes the repository directory owner to the current user;
|
||||
- **rust-permissions.sh** - fixes permissions for the Rust folder; a detailed issue explanation is provided in [runner-images/issues/572](https://github.com/actions/runner-images/issues/572).
|
||||
|
||||
- **cleanup-logs.sh** - removes all build process logs from the machine
|
||||
- **environment-variables.sh** - replaces `$HOME` with the default user's home directory for environmental variables related to the default user home directory
|
||||
- **homebrew-permissions.sh** - Resets homebrew repository directory by running `git reset --hard` to make the working tree clean after chmoding /home and changes the repository directory owner to the current user
|
||||
- **rust-permissions.sh** - fixes permissions for the Rust folder. Detailed issue explanation is provided in [virtual-environments/issues/572](https://github.com/actions/virtual-environments/issues/572).
|
||||
### Script Details: Windows
|
||||
|
||||
##### Windows
|
||||
|
||||
- **Choco.ps1** - contains dummy command to cleanup orphaned packages to avoid initial delay for future choco commands
|
||||
- **Dotnet.ps1** - adds `$env:USERPROFILE\.dotnet\tools` directory to the PATH
|
||||
- **InternetExplorerConfiguration** - turns off the Internet Explorer Enhanced Security feature
|
||||
- **Msys2FirstLaunch.ps1** - initializes bash user profile in MSYS2
|
||||
- **RustJunction.ps1** - creates Rust junction points to cargo and rustup folders
|
||||
- **VSConfiguration.ps1** - performs initial Visual Studio configuration
|
||||
- **GenerateIISExpressCertificate.ps1** - generates and imports a certificate to run applications with IIS Express through HTTPS;
|
||||
- **InternetExplorerConfiguration.ps1** - turns off the Internet Explorer Enhanced Security feature;
|
||||
- **Msys2FirstLaunch.ps1** - initializes the bash user profile in MSYS2;
|
||||
- **VSConfiguration.ps1** - performs initial Visual Studio configuration.
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Debugging Failed Packer Builds
|
||||
|
||||
## Step 1: Run packer build `-on-error=ask`
|
||||
When you run the `packer build` command, give it the `-on-error=ask` flag.
|
||||
By default, `packer build` will delete the resource group as soon as the build fails.
|
||||
`-on-error=ask` will pause it and wait for your input so you have time to remote in to the VM and diagnose the failure.
|
||||
|
||||
When the build fails, you will see this:
|
||||
|
||||

|
||||
|
||||
## Step 2: Find the resource group name in the build log
|
||||
At the beginning of the build log (written to console), find the resource group name for the VM:
|
||||
|
||||

|
||||
|
||||
Log into the Azure Portal. Find that resource group under `Resource groups`. You should see the resources for the Packer build:
|
||||
|
||||

|
||||
|
||||
## Step 3: Connect to the VM
|
||||
Select the VM in the resource group. Click `Connect:`
|
||||
|
||||
This will download an RDP file. Open that and enter the credentials found in the JSON file you pass to `packer build`:
|
||||
|
||||

|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 139 KiB |
@@ -1,38 +0,0 @@
|
||||
# Software and image guidelines
|
||||
|
||||
## Software preinstallation policy
|
||||
In general, these are the guidelines we consider when deciding what to pre-install:
|
||||
|
||||
- Tools and ecosystems that are broadly popular and widely-used will be given priority.
|
||||
- Recent versions of tools will be given priority over older versions.
|
||||
- Tools and versions that are deprecated or have reached end-of-life will not be added.
|
||||
- If a tool can be installed during the build, we will evaluate how much time is saved
|
||||
and how much space is used by having the tool pre-installed.
|
||||
- MIT, Apache, and GNU licenses are ok, anything else we'll have to check with lawyers.
|
||||
- If a tool takes much space we will evaluate space usage and provide a decision if this tool can be pre-installed.
|
||||
- If a tool requires the support of more than one version, we will consider the cost of this maintenance, how often new versions bring dangerous updates.
|
||||
|
||||
**Note:** For new tools, please, create an issue and get an approval from us to add this tool to the image before creating the pull request.
|
||||
|
||||
## Software and images support policy
|
||||
These are the guidelines we follow in software and images supporting routine:
|
||||
- Tools and versions will typically be removed 6 months after they are deprecated or have reached end-of-life.
|
||||
- We support at least 2 latest OS versions. We begin the deprecation process of the oldest image label once a new OS version has been released.
|
||||
- The images generally contain the latest versions of packages installed except for Ubuntu LTS where we rely on the Canonical-provided repositories mostly.
|
||||
- Popular tools can have several versions installed side-by-side with the following strategy:
|
||||
|
||||
| Tool name | Installation strategy |
|
||||
|-----------|-----------------------|
|
||||
| Docker images | not more than 3 latest LTS OS\tool versions. New images or new versions of current images are added using the standard tool request process |
|
||||
| Java | all LTS versions |
|
||||
| Node.js | 3 latest LTS versions |
|
||||
| Go | 3 latest minor versions |
|
||||
| Python <br/> Ruby | 5 most popular `major.minor` versions |
|
||||
| PyPy | 3 most popular `major.minor` versions |
|
||||
| .NET Core | 2 latest LTS versions and 1 latest version. For each feature version only latest patch is installed |
|
||||
| GCC <br/> GNU Fortran <br/> Clang <br/> GNU C++ | 3 latest major versions |
|
||||
| Android NDK | 1 latest non-LTS, 2 latest LTS versions |
|
||||
| Xcode | - all OS compatible versions side-by-side <br/> - for beta, GM versions - latest beta only <br/> - old patch versions are deprecated in 3 months |
|
||||
|
||||
## Software default versions update policy for tools with multiple versions installed
|
||||
In general, once a new version is installed on the image, we announce the default version update 2 weeks prior to deploying it to give time to adapt to upcoming changes. For potentially dangerous updates, we can extend the timeline up to 1 month between the announcement and deployment.
|
||||
@@ -12,8 +12,8 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
.PARAMETER ResourceGroupName
|
||||
The Azure resource group name where the Azure virtual machine will be created.
|
||||
|
||||
.PARAMETER TemplatFilePath
|
||||
The path for the json template generated by packer during image generation locally.
|
||||
.PARAMETER ManagedImageName
|
||||
The name of the managed image to be used to create the virtual machine.
|
||||
|
||||
.PARAMETER VirtualMachineName
|
||||
The name of the virtual machine to be generated.
|
||||
@@ -28,7 +28,7 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
The location where the Azure virtual machine will be provisioned. Example: "eastus"
|
||||
|
||||
.EXAMPLE
|
||||
CreateAzureVMFromPackerTemplate -SubscriptionId {YourSubscriptionId} -ResourceGroupName {ResourceGroupName} -TemplateFile "C:\BuildVmImages\temporaryTemplate.json" -VirtualMachineName "testvm1" -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus"
|
||||
CreateAzureVMFromPackerTemplate -SubscriptionId {SubscriptionId} -ResourceGroupName {ResourceGroupName} -VirtualMachineName "testvm1" -ManagedImageName {ManagedImageName} -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus"
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory = $True)]
|
||||
@@ -36,7 +36,7 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $ResourceGroupName,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $TemplateFilePath,
|
||||
[string] $ManagedImageName,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $VirtualMachineName,
|
||||
[Parameter(Mandatory = $True)]
|
||||
@@ -52,7 +52,7 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
$vnetName = $env:UserName + "vnet-" + $guid
|
||||
$subnetName = $env:UserName + "subnet-" + $guid
|
||||
$nicName = $env:UserName + "nic-" + $guid
|
||||
$publicIpName = $env:UserName + "pip-" + $guid
|
||||
$publicIpName = $env:UserName + "pip-" + $guid
|
||||
|
||||
Write-Host "Creating a virtual network and subnet"
|
||||
($vnet = az network vnet create -g $ResourceGroupName -l $AzureLocation -n $vnetName --address-prefixes 10.0.0.0/16 --subnet-name $subnetName --subnet-prefixes 10.0.1.0/24 --subscription $subscriptionId -o json)
|
||||
@@ -70,7 +70,16 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
az network nic ip-config update -g $ResourceGroupName -n ipconfig1 --nic-name $nicName --public-ip-address $publicIpId --subscription $subscriptionId
|
||||
|
||||
Write-Host "`nCreating the VM"
|
||||
az deployment group create -g $ResourceGroupName -n $VirtualMachineName --subscription $subscriptionId --template-file $templateFilePath --parameters vmSize=$vmSize vmName=$VirtualMachineName adminUserName=$AdminUsername adminPassword=$AdminPassword networkInterfaceId=$networkId
|
||||
az vm create `
|
||||
--resource-group $ResourceGroupName `
|
||||
--name $VirtualMachineName `
|
||||
--image $ManagedImageName `
|
||||
--size $vmSize `
|
||||
--admin-username $AdminUsername `
|
||||
--admin-password $AdminPassword `
|
||||
--nics $networkId `
|
||||
--subscription $subscriptionId `
|
||||
--location $AzureLocation
|
||||
|
||||
Write-Host "`nCreated in ${ResourceGroupName}:`n vnet ${vnetName}`n subnet ${subnetName}`n nic ${nicName}`n publicip ${publicIpName}`n vm ${VirtualMachineName}"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
enum ImageType {
|
||||
Windows2019 = 1
|
||||
Windows2022 = 2
|
||||
Ubuntu1804 = 3
|
||||
Ubuntu2004 = 4
|
||||
Ubuntu2204 = 5
|
||||
Windows2019 = 1
|
||||
Windows2022 = 2
|
||||
Ubuntu2004 = 3
|
||||
Ubuntu2204 = 4
|
||||
Ubuntu2404 = 5
|
||||
UbuntuMinimal = 6
|
||||
}
|
||||
|
||||
Function Get-PackerTemplatePath {
|
||||
@@ -17,20 +18,24 @@ Function Get-PackerTemplatePath {
|
||||
)
|
||||
|
||||
switch ($ImageType) {
|
||||
# Note: Double Join-Path is required to support PowerShell 5.1
|
||||
([ImageType]::Windows2019) {
|
||||
$relativeTemplatePath = Join-Path "win" "windows2019.json"
|
||||
$relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "windows-2019.pkr.hcl"
|
||||
}
|
||||
([ImageType]::Windows2022) {
|
||||
$relativeTemplatePath = Join-Path "win" "windows2022.json"
|
||||
}
|
||||
([ImageType]::Ubuntu1804) {
|
||||
$relativeTemplatePath = Join-Path "linux" "ubuntu1804.json"
|
||||
$relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "windows-2022.pkr.hcl"
|
||||
}
|
||||
([ImageType]::Ubuntu2004) {
|
||||
$relativeTemplatePath = Join-Path "linux" "ubuntu2004.json"
|
||||
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "ubuntu-20.04.pkr.hcl"
|
||||
}
|
||||
([ImageType]::Ubuntu2204) {
|
||||
$relativeTemplatePath = Join-Path "linux" "ubuntu2204.pkr.hcl"
|
||||
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "ubuntu-22.04.pkr.hcl"
|
||||
}
|
||||
([ImageType]::Ubuntu2404) {
|
||||
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "ubuntu-24.04.pkr.hcl"
|
||||
}
|
||||
([ImageType]::UbuntuMinimal) {
|
||||
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "ubuntu-minimal.pkr.hcl"
|
||||
}
|
||||
default { throw "Unknown type of image" }
|
||||
}
|
||||
@@ -38,56 +43,80 @@ Function Get-PackerTemplatePath {
|
||||
$imageTemplatePath = [IO.Path]::Combine($RepositoryRoot, "images", $relativeTemplatePath)
|
||||
|
||||
if (-not (Test-Path $imageTemplatePath)) {
|
||||
throw "Template for image '$ImageType' doesn't exist on path '$imageTemplatePath'"
|
||||
throw "Template for image '$ImageType' doesn't exist on path '$imageTemplatePath'."
|
||||
}
|
||||
|
||||
return $imageTemplatePath;
|
||||
}
|
||||
|
||||
Function Get-LatestCommit {
|
||||
Function Show-LatestCommit {
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
process {
|
||||
Write-Host "Latest commit:"
|
||||
git --no-pager log --pretty=format:"Date: %cd; Commit: %H - %s; Author: %an <%ae>" -1
|
||||
$latestCommit = (git --no-pager log --pretty=format:"Date: %cd; Commit: %H - %s; Author: %an <%ae>" -1)
|
||||
Write-Host "Latest commit: $latestCommit."
|
||||
}
|
||||
}
|
||||
|
||||
function Start-Sleep($seconds) {
|
||||
$doneDT = (Get-Date).AddSeconds($seconds)
|
||||
while ($doneDT -gt (Get-Date)) {
|
||||
$secondsLeft = $doneDT.Subtract((Get-Date)).TotalSeconds
|
||||
$percent = ($seconds - $secondsLeft) / $seconds * 100
|
||||
Write-Progress -Activity "Sleeping" -Status "Sleeping..." -SecondsRemaining $secondsLeft -PercentComplete $percent
|
||||
[System.Threading.Thread]::Sleep(500)
|
||||
}
|
||||
Write-Progress -Activity "Sleeping" -Status "Sleeping..." -SecondsRemaining 0 -Completed
|
||||
}
|
||||
|
||||
Function GenerateResourcesAndImage {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A helper function to help generate an image.
|
||||
.DESCRIPTION
|
||||
Creates Azure resources and kicks off a packer image generation for the selected image type.
|
||||
This function will generate the Azure resources and image for the specified image type.
|
||||
.PARAMETER SubscriptionId
|
||||
The Azure subscription Id where resources will be created.
|
||||
The Azure subscription id where the Azure resources will be created.
|
||||
.PARAMETER ResourceGroupName
|
||||
The Azure resource group name where the Azure resources will be created.
|
||||
.PARAMETER ImageGenerationRepositoryRoot
|
||||
The root path of the image generation repository source.
|
||||
The name of the resource group to create the Azure resources in.
|
||||
.PARAMETER ImageType
|
||||
The type of the image being generated. Valid options are: {"Windows2019", "Windows2022", "Ubuntu1804", "Ubuntu2004", "Ubuntu2204"}.
|
||||
The type of image to generate. Valid values are: Windows2019, Windows2022, Ubuntu2004, Ubuntu2204, UbuntuMinimal.
|
||||
.PARAMETER ManagedImageName
|
||||
The name of the managed image to create. The default is "Runner-Image-{{ImageType}}".
|
||||
.PARAMETER AzureLocation
|
||||
The location of the resources being created in Azure. For example "East US".
|
||||
.PARAMETER Force
|
||||
Delete the resource group if it exists without user confirmation.
|
||||
The Azure location where the Azure resources will be created. For example: "East US"
|
||||
.PARAMETER ImageGenerationRepositoryRoot
|
||||
The root directory of the image generation repository. This is used to locate the packer template.
|
||||
.PARAMETER SecondsToWaitForServicePrincipalSetup
|
||||
The number of seconds to wait for the service principal to be setup. The default is 120 seconds.
|
||||
.PARAMETER AzureClientId
|
||||
Client id needs to be provided for optional authentication via service principal. Example: "11111111-1111-1111-1111-111111111111"
|
||||
The Azure client id to use to authenticate with Azure. If not specified, the current user's credentials will be used.
|
||||
.PARAMETER AzureClientSecret
|
||||
Client secret needs to be provided for optional authentication via service principal. Example: "11111111-1111-1111-1111-111111111111"
|
||||
The Azure client secret to use to authenticate with Azure. If not specified, the current user's credentials will be used.
|
||||
.PARAMETER AzureTenantId
|
||||
Tenant needs to be provided for optional authentication via service principal. Example: "11111111-1111-1111-1111-111111111111"
|
||||
The Azure tenant id to use to authenticate with Azure. If not specified, the current user's credentials will be used.
|
||||
.PARAMETER RestrictToAgentIpAddress
|
||||
If set, access to the VM used by packer to generate the image is restricted to the public IP address this script is run from.
|
||||
This parameter cannot be used in combination with the virtual_network_name packer parameter.
|
||||
|
||||
.PARAMETER AllowBlobPublicAccess
|
||||
The Azure storage account will be created with this option.
|
||||
.PARAMETER Force
|
||||
Delete the resource group if it exists without user confirmation.
|
||||
This parameter is deprecated and will be removed in a future release.
|
||||
.PARAMETER ReuseResourceGroup
|
||||
Reuse the resource group if it exists without user confirmation.
|
||||
This parameter is deprecated and will be removed in a future release.
|
||||
.PARAMETER OnError
|
||||
Specify how packer handles an error during image creation.
|
||||
Options:
|
||||
abort - abort immediately
|
||||
ask - ask user for input
|
||||
cleanup - attempt to cleanup and then abort
|
||||
run-cleanup-provisioner - run the cleanup provisioner and then abort
|
||||
The default is 'ask'.
|
||||
.PARAMETER Tags
|
||||
Tags to be applied to the Azure resources created.
|
||||
.EXAMPLE
|
||||
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "shsamytest1" -ImageGenerationRepositoryRoot "C:\virtual-environments" -ImageType Ubuntu1804 -AzureLocation "East US"
|
||||
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "shsamytest1" -ImageGenerationRepositoryRoot "C:\runner-images" -ImageType Ubuntu2004 -AzureLocation "East US"
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory = $True)]
|
||||
@@ -96,12 +125,14 @@ Function GenerateResourcesAndImage {
|
||||
[string] $ResourceGroupName,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[ImageType] $ImageType,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $ManagedImageName = "Runner-Image-$($ImageType)",
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $AzureLocation,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $ImageGenerationRepositoryRoot = $pwd,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[int] $SecondsToWaitForServicePrincipalSetup = 30,
|
||||
[int] $SecondsToWaitForServicePrincipalSetup = 120,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $AzureClientId,
|
||||
[Parameter(Mandatory = $False)]
|
||||
@@ -109,207 +140,253 @@ Function GenerateResourcesAndImage {
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $AzureTenantId,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[Switch] $RestrictToAgentIpAddress,
|
||||
[switch] $RestrictToAgentIpAddress,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[Switch] $Force,
|
||||
[switch] $Force,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[bool] $AllowBlobPublicAccess = $False,
|
||||
[switch] $ReuseResourceGroup,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[bool] $EnableHttpsTrafficOnly = $False,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[ValidateSet("abort","ask","cleanup","run-cleanup-provisioner")]
|
||||
[ValidateSet("abort", "ask", "cleanup", "run-cleanup-provisioner")]
|
||||
[string] $OnError = "ask",
|
||||
[Parameter(Mandatory = $False)]
|
||||
[hashtable] $Tags
|
||||
[hashtable] $Tags = @{}
|
||||
)
|
||||
|
||||
if ($Force -or $ReuseResourceGroup) {
|
||||
Write-Warning "The `ReuseResourceGroup` and `Force` parameters are deprecated and will be removed in a future release. The resource group will be reused when it already exists and an error will be thrown when it doesn't. If you want to delete the resource group, please delete it manually."
|
||||
}
|
||||
|
||||
if ($Force -and $ReuseResourceGroup) {
|
||||
throw "Force and ReuseResourceGroup cannot be used together."
|
||||
}
|
||||
|
||||
Show-LatestCommit -ErrorAction SilentlyContinue
|
||||
|
||||
# Validate packer is installed
|
||||
$PackerBinary = Get-Command "packer"
|
||||
if (-not ($PackerBinary)) {
|
||||
throw "'packer' binary is not found on PATH."
|
||||
}
|
||||
|
||||
# Get template path
|
||||
$TemplatePath = Get-PackerTemplatePath -RepositoryRoot $ImageGenerationRepositoryRoot -ImageType $ImageType
|
||||
Write-Debug "Template path: $TemplatePath."
|
||||
|
||||
# Prepare list of allowed inbound IP addresses
|
||||
if ($RestrictToAgentIpAddress) {
|
||||
$AgentIp = (Invoke-RestMethod http://ipinfo.io/json).ip
|
||||
if (-not $AgentIp) {
|
||||
throw "Unable to determine agent IP address."
|
||||
}
|
||||
|
||||
Write-Host "Access to packer generated VM will be restricted to agent IP Address: $AgentIp."
|
||||
if ($PSVersionTable.PSVersion.Major -eq 5) {
|
||||
Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in allowed inbound IP addresses."
|
||||
$AllowedInboundIpAddresses = '[\"{0}\"]' -f $AgentIp
|
||||
}
|
||||
elseif ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) {
|
||||
Write-Verbose "PowerShell 7.0-7.2 detected. Replacing double quotes with escaped double quotes in allowed inbound IP addresses."
|
||||
$AllowedInboundIpAddresses = '[\"{0}\"]' -f $AgentIp
|
||||
}
|
||||
else {
|
||||
$AllowedInboundIpAddresses = '["{0}"]' -f $AgentIp
|
||||
}
|
||||
}
|
||||
else {
|
||||
$AllowedInboundIpAddresses = "[]"
|
||||
}
|
||||
Write-Debug "Allowed inbound IP addresses: $AllowedInboundIpAddresses."
|
||||
|
||||
# Prepare tags
|
||||
$TagsList = $Tags.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }
|
||||
Write-Debug "Tags list: $TagsList."
|
||||
$TagsJson = $Tags | ConvertTo-Json -Compress
|
||||
if ($PSVersionTable.PSVersion.Major -eq 5) {
|
||||
Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in tags JSON."
|
||||
$TagsJson = $TagsJson -replace '"', '\"'
|
||||
}
|
||||
elseif ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) {
|
||||
Write-Verbose "PowerShell 7.0-7.2 detected. Replacing double quotes with escaped double quotes in tags JSON."
|
||||
$TagsJson = $TagsJson -replace '"', '\"'
|
||||
}
|
||||
Write-Debug "Tags JSON: $TagsJson."
|
||||
|
||||
$InstallPassword = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
|
||||
Write-Host "Downloading packer plugins..."
|
||||
& $PackerBinary init $TemplatePath
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Packer plugins download failed."
|
||||
}
|
||||
|
||||
Write-Host "Validating packer template..."
|
||||
& $PackerBinary validate `
|
||||
"-var=client_id=fake" `
|
||||
"-var=client_secret=fake" `
|
||||
"-var=subscription_id=$($SubscriptionId)" `
|
||||
"-var=tenant_id=fake" `
|
||||
"-var=location=$($AzureLocation)" `
|
||||
"-var=managed_image_name=$($ManagedImageName)" `
|
||||
"-var=managed_image_resource_group_name=$($ResourceGroupName)" `
|
||||
"-var=install_password=$($InstallPassword)" `
|
||||
"-var=allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" `
|
||||
"-var=azure_tags=$($TagsJson)" `
|
||||
$TemplatePath
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Packer template validation failed."
|
||||
}
|
||||
|
||||
try {
|
||||
$builderScriptPath = Get-PackerTemplatePath -RepositoryRoot $ImageGenerationRepositoryRoot -ImageType $ImageType
|
||||
$ServicePrincipalClientSecret = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
$InstallPassword = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
|
||||
if ([string]::IsNullOrEmpty($AzureClientId))
|
||||
{
|
||||
Connect-AzAccount
|
||||
} else {
|
||||
$AzSecureSecret = ConvertTo-SecureString $AzureClientSecret -AsPlainText -Force
|
||||
$AzureAppCred = New-Object System.Management.Automation.PSCredential($AzureClientId, $AzSecureSecret)
|
||||
Connect-AzAccount -ServicePrincipal -Credential $AzureAppCred -Tenant $AzureTenantId
|
||||
}
|
||||
Set-AzContext -SubscriptionId $SubscriptionId
|
||||
|
||||
$alreadyExists = $true;
|
||||
try {
|
||||
Get-AzResourceGroup -Name $ResourceGroupName
|
||||
Write-Verbose "Resource group was found, will delete and recreate it."
|
||||
}
|
||||
catch {
|
||||
Write-Verbose "Resource group was not found, will create it."
|
||||
$alreadyExists = $false;
|
||||
}
|
||||
|
||||
if ($alreadyExists) {
|
||||
if($Force -eq $true) {
|
||||
# Cleanup the resource group if it already exitsted before
|
||||
Remove-AzResourceGroup -Name $ResourceGroupName -Force
|
||||
New-AzResourceGroup -Name $ResourceGroupName -Location $AzureLocation -Tag $tags
|
||||
|
||||
} else {
|
||||
$title = "Delete Resource Group"
|
||||
$message = "The resource group you specified already exists. Do you want to clean it up?"
|
||||
|
||||
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
|
||||
"Delete the resource group including all resources."
|
||||
|
||||
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
|
||||
"Keep the resource group and continue."
|
||||
|
||||
$stop = New-Object System.Management.Automation.Host.ChoiceDescription "&Stop", `
|
||||
"Stop the current action."
|
||||
|
||||
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no, $stop)
|
||||
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
|
||||
|
||||
switch ($result)
|
||||
{
|
||||
0 { Remove-AzResourceGroup -Name $ResourceGroupName -Force; New-AzResourceGroup -Name $ResourceGroupName -Location $AzureLocation -Tag $tags }
|
||||
1 { <# Do nothing #> }
|
||||
2 { exit }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
New-AzResourceGroup -Name $ResourceGroupName -Location $AzureLocation -Tag $tags
|
||||
}
|
||||
|
||||
# This script should follow the recommended naming conventions for azure resources
|
||||
$storageAccountName = if($ResourceGroupName.EndsWith("-rg")) {
|
||||
$ResourceGroupName.Substring(0, $ResourceGroupName.Length -3)
|
||||
} else { $ResourceGroupName }
|
||||
|
||||
# Resource group names may contain special characters, that are not allowed in the storage account name
|
||||
$storageAccountName = $storageAccountName.Replace("-", "").Replace("_", "").Replace("(", "").Replace(")", "").ToLower()
|
||||
$storageAccountName += "001"
|
||||
|
||||
|
||||
# Storage Account Name can only be 24 characters long
|
||||
if ($storageAccountName.Length -gt 24){
|
||||
$storageAccountName = $storageAccountName.Substring(0, 24)
|
||||
}
|
||||
|
||||
if ($tags) {
|
||||
New-AzStorageAccount -ResourceGroupName $ResourceGroupName -AccountName $storageAccountName -Location $AzureLocation -SkuName "Standard_LRS" -AllowBlobPublicAccess $AllowBlobPublicAccess -EnableHttpsTrafficOnly $EnableHttpsTrafficOnly -Tag $tags
|
||||
} else {
|
||||
New-AzStorageAccount -ResourceGroupName $ResourceGroupName -AccountName $storageAccountName -Location $AzureLocation -SkuName "Standard_LRS" -AllowBlobPublicAccess $AllowBlobPublicAccess -EnableHttpsTrafficOnly $EnableHttpsTrafficOnly
|
||||
}
|
||||
|
||||
# Login to Azure subscription
|
||||
if ([string]::IsNullOrEmpty($AzureClientId)) {
|
||||
# Interactive authentication: A service principal is created during runtime.
|
||||
$spDisplayName = [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
$startDate = Get-Date
|
||||
$endDate = $startDate.AddYears(1)
|
||||
Write-Verbose "No AzureClientId was provided, will use interactive login."
|
||||
az login --output none
|
||||
}
|
||||
else {
|
||||
Write-Verbose "AzureClientId was provided, will use service principal login."
|
||||
az login --service-principal --username $AzureClientId --password=$AzureClientSecret --tenant $AzureTenantId --output none
|
||||
}
|
||||
az account set --subscription $SubscriptionId
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Failed to login to Azure subscription '$SubscriptionId'."
|
||||
}
|
||||
|
||||
if ('Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential' -as [type]) {
|
||||
$credentials = [Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential]@{
|
||||
StartDate = $startDate
|
||||
EndDate = $endDate
|
||||
Password = $ServicePrincipalClientSecret
|
||||
}
|
||||
$sp = New-AzADServicePrincipal -DisplayName $spDisplayName -PasswordCredential $credentials
|
||||
$spClientId = $sp.ApplicationId
|
||||
$azRoleParam = @{
|
||||
RoleDefinitionName = "Contributor"
|
||||
ServicePrincipalName = $spClientId
|
||||
# Check resource group
|
||||
$ResourceGroupExists = [System.Convert]::ToBoolean((az group exists --name $ResourceGroupName));
|
||||
if ($ResourceGroupExists) {
|
||||
Write-Verbose "Resource group '$ResourceGroupName' already exists."
|
||||
}
|
||||
|
||||
# Remove resource group if it exists and we are not reusing it
|
||||
if ($ResourceGroupExists -and -not $ReuseResourceGroup) {
|
||||
if ($Force) {
|
||||
# Delete and recreate the resource group
|
||||
Write-Host "Deleting resource group '$ResourceGroupName'..."
|
||||
az group delete --name $ResourceGroupName --yes --output none
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Failed to delete resource group '$ResourceGroupName'."
|
||||
}
|
||||
Write-Host "Resource group '$ResourceGroupName' was deleted."
|
||||
$ResourceGroupExists = $false
|
||||
}
|
||||
|
||||
if ('Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential' -as [type]) {
|
||||
$credentials = [Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential]@{
|
||||
StartDateTime = $startDate
|
||||
EndDateTime = $endDate
|
||||
else {
|
||||
# are we running in a non-interactive session?
|
||||
# https://stackoverflow.com/questions/9738535/powershell-test-for-noninteractive-mode
|
||||
if ([System.Console]::IsOutputRedirected -or ![Environment]::UserInteractive -or !!([Environment]::GetCommandLineArgs() | Where-Object { $_ -ilike '-noni*' })) {
|
||||
throw "Non-interactive mode, resource group '$ResourceGroupName' already exists, either specify -Force to delete it, or -ReuseResourceGroup to reuse."
|
||||
}
|
||||
$sp = New-AzADServicePrincipal -DisplayName $spDisplayName
|
||||
$appCred = New-AzADAppCredential -ApplicationId $sp.AppId -PasswordCredentials $credentials
|
||||
$spClientId = $sp.AppId
|
||||
$azRoleParam = @{
|
||||
RoleDefinitionName = "Contributor"
|
||||
PrincipalId = $sp.Id
|
||||
else {
|
||||
# Resource group already exists, ask the user what to do
|
||||
$title = "Resource group '$ResourceGroupName' already exists"
|
||||
$message = "Do you want to delete the resource group and all resources in it?"
|
||||
|
||||
$options = @(
|
||||
[System.Management.Automation.Host.ChoiceDescription]::new("&Yes", "Delete the resource group and all resources in it."),
|
||||
[System.Management.Automation.Host.ChoiceDescription]::new("&No", "Keep the resource group and continue."),
|
||||
[System.Management.Automation.Host.ChoiceDescription]::new("&Abort", "Abort execution.")
|
||||
)
|
||||
$result = $Host.UI.PromptForChoice($title, $message, $options, 0)
|
||||
}
|
||||
$ServicePrincipalClientSecret = $appCred.SecretText
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $SecondsToWaitForServicePrincipalSetup
|
||||
New-AzRoleAssignment @azRoleParam
|
||||
Start-Sleep -Seconds $SecondsToWaitForServicePrincipalSetup
|
||||
$sub = Get-AzSubscription -SubscriptionId $SubscriptionId
|
||||
$tenantId = $sub.TenantId
|
||||
|
||||
# Remove ADPrincipal after the script completed
|
||||
$isCleanupADPrincipal = $true
|
||||
} else {
|
||||
# Parametrized Authentication via given service principal: The service principal with the data provided via the command line
|
||||
# is used for all authentication purposes.
|
||||
$spClientId = $AzureClientId
|
||||
$credentials = $AzureAppCred
|
||||
$ServicePrincipalClientSecret = $AzureClientSecret
|
||||
$tenantId = $AzureTenantId
|
||||
}
|
||||
|
||||
Get-LatestCommit -ErrorAction SilentlyContinue
|
||||
|
||||
$packerBinary = Get-Command "packer"
|
||||
if (-not ($packerBinary)) {
|
||||
throw "'packer' binary is not found on PATH"
|
||||
}
|
||||
|
||||
if ($RestrictToAgentIpAddress) {
|
||||
$AgentIp = (Invoke-RestMethod http://ipinfo.io/json).ip
|
||||
Write-Host "Restricting access to packer generated VM to agent IP Address: $AgentIp"
|
||||
}
|
||||
|
||||
if ($builderScriptPath.Contains("pkr.hcl")) {
|
||||
if ($AgentIp) {
|
||||
$AgentIp = '[ \"{0}\" ]' -f $AgentIp
|
||||
} else {
|
||||
$AgentIp = "[]"
|
||||
switch ($result) {
|
||||
0 {
|
||||
# Delete and recreate the resource group
|
||||
Write-Host "Deleting resource group '$ResourceGroupName'..."
|
||||
az group delete --name $ResourceGroupName --yes
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Failed to delete resource group '$ResourceGroupName'."
|
||||
}
|
||||
Write-Host "Resource group '$ResourceGroupName' was deleted."
|
||||
$ResourceGroupExists = $false
|
||||
}
|
||||
1 {
|
||||
# Keep the resource group and continue
|
||||
}
|
||||
2 {
|
||||
# Stop the current action
|
||||
Write-Error "User stopped the action."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($Tags) {
|
||||
$builderScriptPath_temp = $builderScriptPath.Replace(".json", "-temp.json")
|
||||
$packer_script = Get-Content -Path $builderScriptPath | ConvertFrom-Json
|
||||
$packer_script.builders | Add-Member -Name "azure_tags" -Value $Tags -MemberType NoteProperty
|
||||
$packer_script | ConvertTo-Json -Depth 3 | Out-File $builderScriptPath_temp
|
||||
$builderScriptPath = $builderScriptPath_temp
|
||||
# Create resource group
|
||||
if (-not $ResourceGroupExists) {
|
||||
Write-Host "Creating resource group '$ResourceGroupName' in location '$AzureLocation'..."
|
||||
if ($TagsList) {
|
||||
az group create --name $ResourceGroupName --location $AzureLocation --tags $TagsList --query id
|
||||
}
|
||||
else {
|
||||
az group create --name $ResourceGroupName --location $AzureLocation --query id
|
||||
}
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Failed to create resource group '$ResourceGroupName'."
|
||||
}
|
||||
}
|
||||
|
||||
& $packerBinary build -on-error="$($OnError)" `
|
||||
-var "client_id=$($spClientId)" `
|
||||
-var "client_secret=$($ServicePrincipalClientSecret)" `
|
||||
# Create service principal
|
||||
if ([string]::IsNullOrEmpty($AzureClientId)) {
|
||||
Write-Host "Creating service principal for packer..."
|
||||
$ADCleanupRequired = $true
|
||||
|
||||
$ServicePrincipalName = "packer-" + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
$ServicePrincipal = az ad sp create-for-rbac --name $ServicePrincipalName --role Contributor --scopes /subscriptions/$SubscriptionId --only-show-errors | ConvertFrom-Json
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Failed to create service principal '$ServicePrincipalName'."
|
||||
}
|
||||
|
||||
$ServicePrincipalAppId = $ServicePrincipal.appId
|
||||
$ServicePrincipalPassword = $ServicePrincipal.password
|
||||
$TenantId = $ServicePrincipal.tenant
|
||||
|
||||
Write-Verbose "Waiting for service principal to propagate..."
|
||||
Start-Sleep $SecondsToWaitForServicePrincipalSetup
|
||||
Write-Host "Service principal created with id '$ServicePrincipalAppId'. It will be deleted after the build."
|
||||
}
|
||||
else {
|
||||
$ServicePrincipalAppId = $AzureClientId
|
||||
$ServicePrincipalPassword = $AzureClientSecret
|
||||
$TenantId = $AzureTenantId
|
||||
}
|
||||
Write-Debug "Service principal app id: $ServicePrincipalAppId."
|
||||
Write-Debug "Tenant id: $TenantId."
|
||||
|
||||
& $PackerBinary build -on-error="$($OnError)" `
|
||||
-var "client_id=$($ServicePrincipalAppId)" `
|
||||
-var "client_secret=$($ServicePrincipalPassword)" `
|
||||
-var "subscription_id=$($SubscriptionId)" `
|
||||
-var "tenant_id=$($tenantId)" `
|
||||
-var "tenant_id=$($TenantId)" `
|
||||
-var "location=$($AzureLocation)" `
|
||||
-var "resource_group=$($ResourceGroupName)" `
|
||||
-var "storage_account=$($storageAccountName)" `
|
||||
-var "managed_image_name=$($ManagedImageName)" `
|
||||
-var "managed_image_resource_group_name=$($ResourceGroupName)" `
|
||||
-var "install_password=$($InstallPassword)" `
|
||||
-var "allowed_inbound_ip_addresses=$($AgentIp)" `
|
||||
$builderScriptPath
|
||||
}
|
||||
catch {
|
||||
-var "allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" `
|
||||
-var "azure_tags=$($TagsJson)" `
|
||||
$TemplatePath
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Failed to build image."
|
||||
}
|
||||
} catch {
|
||||
Write-Error $_
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
Write-Verbose "`nCleaning up..."
|
||||
|
||||
# Remove ADServicePrincipal and ADApplication
|
||||
if ($isCleanupADPrincipal) {
|
||||
Write-Host "`nRemoving ${spDisplayName}/${spClientId}:"
|
||||
if (Get-AzADServicePrincipal -DisplayName $spDisplayName) {
|
||||
Write-Host " [+] ADServicePrincipal"
|
||||
Remove-AzADServicePrincipal -DisplayName $spDisplayName -Confirm:$false
|
||||
if ($ADCleanupRequired) {
|
||||
Write-Host "Removing ADServicePrincipal..."
|
||||
if (az ad sp show --id $ServicePrincipalAppId --query id) {
|
||||
az ad sp delete --id $ServicePrincipalAppId
|
||||
}
|
||||
|
||||
if (Get-AzADApplication -DisplayName $spDisplayName) {
|
||||
Write-Host " [+] ADApplication"
|
||||
Remove-AzADApplication -DisplayName $spDisplayName -Confirm:$false
|
||||
Write-Host "Removing ADApplication..."
|
||||
if (az ad app show --id $ServicePrincipalAppId --query id) {
|
||||
az ad app delete --id $ServicePrincipalAppId
|
||||
}
|
||||
}
|
||||
Write-Verbose "Cleanup completed."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using module ./SoftwareReport.psm1
|
||||
using module ./SoftwareReport.DifferenceCalculator.psm1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Calculates the difference between two software reports and saves it to a file.
|
||||
.PARAMETER PreviousJsonReportPath
|
||||
Path to the previous software report.
|
||||
.PARAMETER CurrentJsonReportPath
|
||||
Path to the current software report.
|
||||
.PARAMETER OutputFile
|
||||
Path to the file where the difference will be saved.
|
||||
.PARAMETER ReleaseBranchName
|
||||
Name of the release branch to build image docs URL.
|
||||
.PARAMETER ReadmePath
|
||||
Path to the README file in repository to build image docs URL.
|
||||
#>
|
||||
|
||||
Param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $PreviousJsonReportPath,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $CurrentJsonReportPath,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $OutputFile,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string] $ReleaseBranchName,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string] $ReadmePath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$global:ErrorView = "NormalView"
|
||||
|
||||
function Read-SoftwareReport {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $JsonReportPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path $JsonReportPath)) {
|
||||
throw "File '$JsonReportPath' does not exist"
|
||||
}
|
||||
|
||||
$jsonReport = Get-Content -Path $JsonReportPath -Raw
|
||||
$report = [SoftwareReport]::FromJson($jsonReport)
|
||||
return $report
|
||||
}
|
||||
|
||||
$previousReport = Read-SoftwareReport -JsonReportPath $PreviousJsonReportPath
|
||||
$currentReport = Read-SoftwareReport -JsonReportPath $CurrentJsonReportPath
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($previousReport, $currentReport)
|
||||
$comparer.CompareReports()
|
||||
$diff = $comparer.GetMarkdownReport()
|
||||
|
||||
if ($ReleaseBranchName -and $ReadmePath) {
|
||||
# https://github.com/actions/runner-images/blob/releases/macOS-12/20221215/images/macos/macos-12-Readme.md
|
||||
$ImageDocsUrl = "https://github.com/actions/runner-images/blob/${ReleaseBranchName}/${ReadmePath}"
|
||||
$diff += "`n`n`nFor comprehensive list of software installed on this image please click [here]($ImageDocsUrl)."
|
||||
}
|
||||
|
||||
$parentDirectory = Split-Path $OutputFile -Parent
|
||||
if (-not (Test-Path $parentDirectory)) { New-Item -Path $parentDirectory -ItemType Directory | Out-Null }
|
||||
|
||||
$diff | Out-File -Path $OutputFile -Encoding utf8NoBOM
|
||||
@@ -0,0 +1,56 @@
|
||||
############################
|
||||
### Abstract base nodes ####
|
||||
############################
|
||||
|
||||
# Abstract base class for all nodes
|
||||
class BaseNode {
|
||||
[Boolean] ShouldBeIncludedToDiff() {
|
||||
return $false
|
||||
}
|
||||
|
||||
[String] ToMarkdown() {
|
||||
return $this.ToMarkdown(1)
|
||||
}
|
||||
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
throw "Abstract method 'ToMarkdown(level)' is not implemented for '$($this.GetType().Name)'"
|
||||
}
|
||||
|
||||
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
|
||||
throw "Abstract method 'IsSimilarTo' is not implemented for '$($this.GetType().Name)'"
|
||||
}
|
||||
|
||||
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
|
||||
throw "Abstract method 'IsIdenticalTo' is not implemented for '$($this.GetType().Name)'"
|
||||
}
|
||||
}
|
||||
|
||||
# Abstract base class for all nodes that describe a tool and should be rendered inside diff table
|
||||
class BaseToolNode: BaseNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $ToolName
|
||||
|
||||
BaseToolNode([String] $ToolName) {
|
||||
$this.ToolName = $ToolName
|
||||
}
|
||||
|
||||
[Boolean] ShouldBeIncludedToDiff() {
|
||||
return $true
|
||||
}
|
||||
|
||||
[String] GetValue() {
|
||||
throw "Abstract method 'GetValue' is not implemented for '$($this.GetType().Name)'"
|
||||
}
|
||||
|
||||
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
|
||||
if ($this.GetType() -ne $OtherNode.GetType()) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $this.ToolName -eq $OtherNode.ToolName
|
||||
}
|
||||
|
||||
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
|
||||
return $this.IsSimilarTo($OtherNode) -and ($this.GetValue() -eq $OtherNode.GetValue())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using module ./SoftwareReport.psm1
|
||||
using module ./SoftwareReport.BaseNodes.psm1
|
||||
using module ./SoftwareReport.Nodes.psm1
|
||||
using module ./SoftwareReport.DifferenceRender.psm1
|
||||
|
||||
class SoftwareReportDifferenceCalculator {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
hidden [SoftwareReport] $PreviousReport
|
||||
[ValidateNotNullOrEmpty()]
|
||||
hidden [SoftwareReport] $CurrentReport
|
||||
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $AddedItems
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $ChangedItems
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $DeletedItems
|
||||
|
||||
SoftwareReportDifferenceCalculator([SoftwareReport] $PreviousReport, [SoftwareReport] $CurrentReport) {
|
||||
$this.PreviousReport = $PreviousReport
|
||||
$this.CurrentReport = $CurrentReport
|
||||
}
|
||||
|
||||
[void] CompareReports() {
|
||||
$this.AddedItems = @()
|
||||
$this.ChangedItems = @()
|
||||
$this.DeletedItems = @()
|
||||
|
||||
$this.CompareInternal($this.PreviousReport.Root, $this.CurrentReport.Root, @())
|
||||
}
|
||||
|
||||
[String] GetMarkdownReport() {
|
||||
$reporter = [SoftwareReportDifferenceRender]::new()
|
||||
$report = $reporter.GenerateMarkdownReport($this.CurrentReport, $this.PreviousReport, $this.AddedItems, $this.ChangedItems, $this.DeletedItems)
|
||||
return $report
|
||||
}
|
||||
|
||||
hidden [void] CompareInternal([HeaderNode] $previousReportPointer, [HeaderNode] $currentReportPointer, [String[]] $Headers) {
|
||||
$currentReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object {
|
||||
$currentReportNode = $_
|
||||
$sameNodeInPreviousReport = $previousReportPointer ? $previousReportPointer.FindSimilarChildNode($currentReportNode) : $null
|
||||
|
||||
if ($currentReportNode -is [HeaderNode]) {
|
||||
# Compare HeaderNode recursively
|
||||
$this.CompareInternal($sameNodeInPreviousReport, $currentReportNode, $Headers + $currentReportNode.Title)
|
||||
} else {
|
||||
if ($sameNodeInPreviousReport -and ($currentReportNode.IsIdenticalTo($sameNodeInPreviousReport))) {
|
||||
# Nodes are identical, nothing changed, just ignore it
|
||||
} elseif ($sameNodeInPreviousReport) {
|
||||
# Nodes are equal but not identical, something was changed
|
||||
if ($currentReportNode -is [TableNode]) {
|
||||
$this.CompareSimilarTableNodes($sameNodeInPreviousReport, $currentReportNode, $Headers)
|
||||
} elseif ($currentReportNode -is [ToolVersionsListNode]) {
|
||||
$this.CompareSimilarToolVersionsListNodes($sameNodeInPreviousReport, $currentReportNode, $Headers)
|
||||
} else {
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($sameNodeInPreviousReport, $currentReportNode, $Headers))
|
||||
}
|
||||
} else {
|
||||
# Node was not found in previous report, new node was added
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($null, $currentReportNode, $Headers))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Detecting nodes that were removed
|
||||
$previousReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object {
|
||||
$previousReportNode = $_
|
||||
$sameNodeInCurrentReport = $currentReportPointer ? $currentReportPointer.FindSimilarChildNode($previousReportNode) : $null
|
||||
|
||||
if (-not $sameNodeInCurrentReport) {
|
||||
if ($previousReportNode -is [HeaderNode]) {
|
||||
# Compare removed HeaderNode recursively
|
||||
$this.CompareInternal($previousReportNode, $null, $Headers + $previousReportNode.Title)
|
||||
} else {
|
||||
# Node was not found in current report, node was removed
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new($previousReportNode, $null, $Headers))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hidden [void] CompareSimilarTableNodes([TableNode] $PreviousReportNode, [TableNode] $CurrentReportNode, [String[]] $Headers) {
|
||||
$addedRows = $CurrentReportNode.Rows | Where-Object { $_ -notin $PreviousReportNode.Rows }
|
||||
$deletedRows = $PreviousReportNode.Rows | Where-Object { $_ -notin $CurrentReportNode.Rows }
|
||||
|
||||
if (($addedRows.Count -eq 0) -and ($deletedRows.Count -eq 0)) {
|
||||
# Unexpected state: TableNodes are identical
|
||||
return
|
||||
}
|
||||
|
||||
if ($PreviousReportNode.Headers -ne $CurrentReportNode.Headers) {
|
||||
# If headers are changed and rows are changed at the same time, we should track it as removing table and adding new one
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $null, $Headers))
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($null, $CurrentReportNode, $Headers))
|
||||
} elseif (($addedRows.Count -gt 0) -and ($deletedRows.Count -eq 0)) {
|
||||
# If new rows were added and no rows were deleted, then it is AddedItem
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
} elseif (($deletedRows.Count -gt 0) -and ($addedRows.Count -eq 0)) {
|
||||
# If no rows were added and some rows were deleted, then it is DeletedItem
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
} else {
|
||||
# If some rows were added and some rows were removed, then it is UpdatedItem
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
}
|
||||
}
|
||||
|
||||
hidden [void] CompareSimilarToolVersionsListNodes([ToolVersionsListNode] $PreviousReportNode, [ToolVersionsListNode] $CurrentReportNode, [String[]] $Headers) {
|
||||
$previousReportMajorVersions = $PreviousReportNode.Versions | ForEach-Object { $PreviousReportNode.ExtractMajorVersion($_) }
|
||||
$currentReportMajorVersion = $CurrentReportNode.Versions | ForEach-Object { $CurrentReportNode.ExtractMajorVersion($_) }
|
||||
|
||||
$addedVersions = $CurrentReportNode.Versions | Where-Object { $CurrentReportNode.ExtractMajorVersion($_) -notin $previousReportMajorVersions }
|
||||
$deletedVersions = $PreviousReportNode.Versions | Where-Object { $PreviousReportNode.ExtractMajorVersion($_) -notin $currentReportMajorVersion }
|
||||
$changedPreviousVersions = $PreviousReportNode.Versions | Where-Object { ($PreviousReportNode.ExtractMajorVersion($_) -in $currentReportMajorVersion) -and ($_ -notin $CurrentReportNode.Versions) }
|
||||
$changedCurrentVersions = $CurrentReportNode.Versions | Where-Object { ($CurrentReportNode.ExtractMajorVersion($_) -in $previousReportMajorVersions) -and ($_ -notin $PreviousReportNode.Versions) }
|
||||
|
||||
if ($addedVersions.Count -gt 0) {
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($null, [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $addedVersions, $CurrentReportNode.MajorVersionRegex, "List"), $Headers))
|
||||
}
|
||||
|
||||
if ($deletedVersions.Count -gt 0) {
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new([ToolVersionsListNode]::new($PreviousReportNode.ToolName, $deletedVersions, $PreviousReportNode.MajorVersionRegex, "List"), $null, $Headers))
|
||||
}
|
||||
|
||||
$previousChangedNode = ($changedPreviousVersions.Count -gt 0) ? [ToolVersionsListNode]::new($PreviousReportNode.ToolName, $changedPreviousVersions, $PreviousReportNode.MajorVersionRegex, "List") : $null
|
||||
$currentChangedNode = ($changedCurrentVersions.Count -gt 0) ? [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $changedCurrentVersions, $CurrentReportNode.MajorVersionRegex, "List") : $null
|
||||
if ($previousChangedNode -and $currentChangedNode) {
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($previousChangedNode, $currentChangedNode, $Headers))
|
||||
}
|
||||
}
|
||||
|
||||
hidden [Boolean] FilterExcludedNodes([BaseNode] $Node) {
|
||||
# We shouldn't show "Image Version" diff because it is already shown in report header
|
||||
if (($Node -is [ToolVersionNode]) -and ($Node.ToolName -eq "Image Version:")) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
using module ./SoftwareReport.psm1
|
||||
using module ./SoftwareReport.BaseNodes.psm1
|
||||
using module ./SoftwareReport.Nodes.psm1
|
||||
|
||||
class SoftwareReportDifferenceRender {
|
||||
[String] GenerateMarkdownReport([SoftwareReport] $CurrentReport, [SoftwareReport] $PreviousReport, [ReportDifferenceItem[]] $AddedItems, [ReportDifferenceItem[]] $ChangedItems, [ReportDifferenceItem[]] $DeletedItems) {
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
|
||||
$rootNode = $CurrentReport.Root
|
||||
$imageVersion = $CurrentReport.GetImageVersion()
|
||||
$previousImageVersion = $PreviousReport.GetImageVersion()
|
||||
|
||||
#############################
|
||||
### Render report header ####
|
||||
#############################
|
||||
|
||||
$sb.AppendLine("# :desktop_computer: Actions Runner Image: $($rootNode.Title)")
|
||||
|
||||
# ToolVersionNodes on root level contains main image description so just copy-paste them to final report
|
||||
$rootNode.Children | Where-Object { $_ -is [ToolVersionNode] } | ForEach-Object {
|
||||
$sb.AppendLine($_.ToMarkdown())
|
||||
}
|
||||
$sb.AppendLine()
|
||||
|
||||
$sb.AppendLine("## :mega: What's changed?").AppendLine()
|
||||
|
||||
###########################
|
||||
### Render added items ####
|
||||
###########################
|
||||
|
||||
[ReportDifferenceItem[]] $addedItemsBaseTools = $AddedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
[ReportDifferenceItem[]] $addedItemsTables = $AddedItems | Where-Object { $_.IsTableNode() }
|
||||
if ($addedItemsBaseTools.Count + $addedItemsTables.Count -gt 0) {
|
||||
$sb.AppendLine("### Added :heavy_plus_sign:").AppendLine()
|
||||
}
|
||||
if ($addedItemsBaseTools.Count -gt 0) {
|
||||
$tableItems = $addedItemsBaseTools | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName);
|
||||
"Current ($imageVersion)" = $_.CurrentReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
if ($addedItemsTables.Count -gt 0) {
|
||||
$addedItemsTables | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
}
|
||||
|
||||
#############################
|
||||
### Render deleted items ####
|
||||
#############################
|
||||
|
||||
[ReportDifferenceItem[]] $deletedItemsBaseTools = $DeletedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
[ReportDifferenceItem[]] $deletedItemsTables = $DeletedItems | Where-Object { $_.IsTableNode() }
|
||||
if ($deletedItemsBaseTools.Count + $deletedItemsTables.Count -gt 0) {
|
||||
$sb.AppendLine("### Deleted :heavy_minus_sign:").AppendLine()
|
||||
}
|
||||
if ($deletedItemsBaseTools.Count -gt 0) {
|
||||
$tableItems = $deletedItemsBaseTools | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.PreviousReportNode.ToolName);
|
||||
"Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
if ($deletedItemsTables.Count -gt 0) {
|
||||
$deletedItemsTables | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
}
|
||||
|
||||
#############################
|
||||
### Render updated items ####
|
||||
#############################
|
||||
|
||||
[ReportDifferenceItem[]] $changedItemsBaseTools = $ChangedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
[ReportDifferenceItem[]] $changedItemsTables = $ChangedItems | Where-Object { $_.IsTableNode() }
|
||||
if ($changedItemsBaseTools.Count + $changedItemsTables.Count -gt 0) {
|
||||
$sb.AppendLine("### Updated").AppendLine()
|
||||
}
|
||||
if ($changedItemsBaseTools.Count -gt 0) {
|
||||
$tableItems = $changedItemsBaseTools | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName);
|
||||
"Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue();
|
||||
"Current ($imageVersion)" = $_.CurrentReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
if ($changedItemsTables.Count -gt 0) {
|
||||
$changedItemsTables | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
}
|
||||
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[String] RenderHtmlTable([PSCustomObject[]] $Table, [String] $RowSpanColumnName) {
|
||||
$headers = $Table[0].PSObject.Properties.Name
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine("<table>")
|
||||
$sb.AppendLine(" <thead>")
|
||||
$headers | ForEach-Object {
|
||||
$sb.AppendLine(" <th>$_</th>")
|
||||
}
|
||||
$sb.AppendLine(" </thead>")
|
||||
$sb.AppendLine(" <tbody>")
|
||||
|
||||
$tableRowSpans = $this.CalculateHtmlTableRowSpan($Table, $RowSpanColumnName)
|
||||
for ($rowIndex = 0; $rowIndex -lt $Table.Count; $rowIndex++) {
|
||||
$row = $Table[$rowIndex]
|
||||
|
||||
$sb.AppendLine(" <tr>")
|
||||
$headers | ForEach-Object {
|
||||
if ($_ -eq $RowSpanColumnName) {
|
||||
if ($tableRowSpans[$rowIndex] -gt 0) {
|
||||
$sb.AppendLine(" <td rowspan=`"$($tableRowSpans[$rowIndex])`">$($row.$_)</td>")
|
||||
} else {
|
||||
# Skip rendering this cell at all
|
||||
}
|
||||
} else {
|
||||
$sb.AppendLine(" <td>$($row.$_)</td>")
|
||||
}
|
||||
}
|
||||
$sb.AppendLine(" </tr>")
|
||||
}
|
||||
$sb.AppendLine(" </tbody>")
|
||||
$sb.AppendLine("</table>")
|
||||
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[int[]] CalculateHtmlTableRowSpan([PSCustomObject[]] $Table, [String] $keyColumn) {
|
||||
$result = @(0) * $Table.Count
|
||||
|
||||
for ($rowIndex = $Table.Count - 1; $rowIndex -ge 0; $rowIndex--) {
|
||||
if (($rowIndex -lt ($Table.Count - 1)) -and ($Table[$rowIndex].$keyColumn -eq $Table[$rowIndex + 1].$keyColumn)) {
|
||||
# If the current row is the same as the next row
|
||||
# Then rowspan of current row should be equal to rowspan of the next row + 1
|
||||
# And rowspan of the next row should be 0 because it is already included in the rowspan of the current row
|
||||
$result[$rowIndex] = $result[$rowIndex + 1] + 1
|
||||
$result[$rowIndex + 1] = 0
|
||||
} else {
|
||||
$result[$rowIndex] = 1
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
[String] RenderTableNodesDiff([ReportDifferenceItem] $DiffItem) {
|
||||
# Use the simplest approach for now: first, print all removed lines. Then print added lines
|
||||
# It will work well for most cases like changing existing rows, adding new rows and removing rows
|
||||
# But can produce not so pretty results for cases when some rows are changed and some rows are added at the same time
|
||||
# Let's see how it works in practice and improve it later if needed
|
||||
|
||||
[String] $tableHeaders = ($DiffItem.CurrentReportNode ?? $DiffItem.PreviousReportNode).Headers
|
||||
[Collections.Generic.List[String]] $tableRows = @()
|
||||
$DiffItem.PreviousReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.CurrentReportNode.Rows } | ForEach-Object {
|
||||
$tableRows.Add($this.StrikeTableRow($_))
|
||||
}
|
||||
$DiffItem.CurrentReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.PreviousReportNode.Rows } | ForEach-Object {
|
||||
$tableRows.Add($_)
|
||||
}
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine("#### $($this.RenderCategory($DiffItem.Headers, $false))")
|
||||
$sb.AppendLine([TableNode]::new($tableHeaders, $tableRows).ToMarkdown())
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[String] RenderCategory([String[]] $Headers, [Boolean] $AddLineSeparator) {
|
||||
# Always skip the first header because it is "Installed Software"
|
||||
[String[]] $takeHeaders = $Headers | Select-Object -Skip 1
|
||||
if ($takeHeaders.Count -eq 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
$lineSeparator = $AddLineSeparator ? "<br>": ""
|
||||
return [String]::Join(" >$lineSeparator ", $takeHeaders)
|
||||
}
|
||||
|
||||
[String] RenderToolName([String] $ToolName) {
|
||||
return $ToolName.TrimEnd(":")
|
||||
}
|
||||
|
||||
[String] StrikeTableRow([String] $Row) {
|
||||
# Convert "a|b|c" to "~~a~~|~~b~~|~~c~~
|
||||
$cells = $Row.Split("|")
|
||||
$strikedCells = $cells | ForEach-Object { "~~$($_)~~"}
|
||||
return [String]::Join("|", $strikedCells)
|
||||
}
|
||||
}
|
||||
|
||||
# Temporary structure to store the single difference between two reports
|
||||
class ReportDifferenceItem {
|
||||
[BaseNode] $PreviousReportNode
|
||||
[BaseNode] $CurrentReportNode
|
||||
[String[]] $Headers
|
||||
|
||||
ReportDifferenceItem([BaseNode] $PreviousReportNode, [BaseNode] $CurrentReportNode, [String[]] $Headers) {
|
||||
$this.PreviousReportNode = $PreviousReportNode
|
||||
$this.CurrentReportNode = $CurrentReportNode
|
||||
$this.Headers = $Headers
|
||||
}
|
||||
|
||||
[Boolean] IsBaseToolNode() {
|
||||
$node = $this.CurrentReportNode ?? $this.PreviousReportNode
|
||||
return $node -is [BaseToolNode]
|
||||
}
|
||||
|
||||
[Boolean] IsTableNode() {
|
||||
$node = $this.CurrentReportNode ?? $this.PreviousReportNode
|
||||
return $node -is [TableNode]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
using module ./SoftwareReport.BaseNodes.psm1
|
||||
|
||||
#########################################
|
||||
### Nodes to describe image software ####
|
||||
#########################################
|
||||
|
||||
# NodesFactory is used to simplify parsing different types of notes
|
||||
# Every node has own logic of parsing and this method just invokes "FromJsonObject" of correct node type
|
||||
class NodesFactory {
|
||||
static [BaseNode] ParseNodeFromObject([object] $JsonObj) {
|
||||
if ($JsonObj.NodeType -eq [HeaderNode].Name) {
|
||||
return [HeaderNode]::FromJsonObject($JsonObj)
|
||||
} elseif ($JsonObj.NodeType -eq [ToolVersionNode].Name) {
|
||||
return [ToolVersionNode]::FromJsonObject($JsonObj)
|
||||
} elseif ($JsonObj.NodeType -eq [ToolVersionsListNode].Name) {
|
||||
return [ToolVersionsListNode]::FromJsonObject($JsonObj)
|
||||
} elseif ($JsonObj.NodeType -eq [TableNode].Name) {
|
||||
return [TableNode]::FromJsonObject($JsonObj)
|
||||
} elseif ($JsonObj.NodeType -eq [NoteNode].Name) {
|
||||
return [NoteNode]::FromJsonObject($JsonObj)
|
||||
}
|
||||
|
||||
throw "Unknown node type in ParseNodeFromObject '$($JsonObj.NodeType)'"
|
||||
}
|
||||
}
|
||||
|
||||
class HeaderNode: BaseNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $Title
|
||||
[Collections.Generic.List[BaseNode]] $Children
|
||||
|
||||
HeaderNode([String] $Title) {
|
||||
$this.Title = $Title
|
||||
$this.Children = @()
|
||||
}
|
||||
|
||||
[Boolean] ShouldBeIncludedToDiff() {
|
||||
return $true
|
||||
}
|
||||
|
||||
[void] AddNode([BaseNode] $node) {
|
||||
$similarNode = $this.FindSimilarChildNode($node)
|
||||
if ($similarNode) {
|
||||
throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.`nFound node: $($similarNode.ToJsonObject() | ConvertTo-Json)`nNew node: $($node.ToJsonObject() | ConvertTo-Json)"
|
||||
}
|
||||
|
||||
if (-not $this.IsNodeHasMarkdownHeader($node)) {
|
||||
# If the node doesn't print own header to markdown, we should check that there is no other nodes that print header to markdown before it.
|
||||
# It is done to avoid unexpected situation like this:
|
||||
#
|
||||
# HeaderNode A -> # A
|
||||
# HeaderNode B -> ## B
|
||||
# ToolVersionNode C -> - C
|
||||
# ToolVersionNode D -> - D
|
||||
#
|
||||
# In this example, we add 'HeaderNode B" to 'HeaderNode A' and add 'ToolVersionNode C' to 'HeaderNode B'.
|
||||
# Then we add 'ToolVersionNode D' to 'HeaderNode A'.
|
||||
# But the result markdown will look like 'ToolVersionNode D' belongs to 'HeaderNode B' instead of 'HeaderNode A'.
|
||||
$this.Children | Where-Object { $this.IsNodeHasMarkdownHeader($_) } | ForEach-Object {
|
||||
throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node"
|
||||
}
|
||||
}
|
||||
|
||||
$this.Children.Add($node)
|
||||
}
|
||||
|
||||
[void] AddNodes([BaseNode[]] $nodes) {
|
||||
$nodes | ForEach-Object {
|
||||
$this.AddNode($_)
|
||||
}
|
||||
}
|
||||
|
||||
[HeaderNode] AddHeader([String] $Title) {
|
||||
$node = [HeaderNode]::new($Title)
|
||||
$this.AddNode($node)
|
||||
return $node
|
||||
}
|
||||
|
||||
[void] AddToolVersion([String] $ToolName, [String] $Version) {
|
||||
$this.AddNode([ToolVersionNode]::new($ToolName, $Version))
|
||||
}
|
||||
|
||||
[void] AddToolVersionsList([String] $ToolName, [String[]] $Version, [String] $MajorVersionRegex) {
|
||||
$this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, "List"))
|
||||
}
|
||||
|
||||
[void] AddToolVersionsListInline([String] $ToolName, [String[]] $Version, [String] $MajorVersionRegex) {
|
||||
$this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, "Inline"))
|
||||
}
|
||||
|
||||
[void] AddTable([PSCustomObject[]] $Table) {
|
||||
$this.AddNode([TableNode]::FromObjectsArray($Table))
|
||||
}
|
||||
|
||||
[void] AddNote([String] $Content) {
|
||||
$this.AddNode([NoteNode]::new($Content))
|
||||
}
|
||||
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine()
|
||||
$sb.AppendLine("$("#" * $Level) $($this.Title)")
|
||||
$this.Children | ForEach-Object {
|
||||
$sb.AppendLine($_.ToMarkdown($Level + 1))
|
||||
}
|
||||
|
||||
return $sb.ToString().TrimEnd()
|
||||
}
|
||||
|
||||
[PSCustomObject] ToJsonObject() {
|
||||
return [PSCustomObject]@{
|
||||
NodeType = $this.GetType().Name
|
||||
Title = $this.Title
|
||||
Children = $this.Children | ForEach-Object { $_.ToJsonObject() }
|
||||
}
|
||||
}
|
||||
|
||||
static [HeaderNode] FromJsonObject([Object] $JsonObj) {
|
||||
$node = [HeaderNode]::new($JsonObj.Title)
|
||||
$JsonObj.Children | Where-Object { $_ } | ForEach-Object { $node.AddNode([NodesFactory]::ParseNodeFromObject($_)) }
|
||||
return $node
|
||||
}
|
||||
|
||||
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
|
||||
if ($OtherNode.GetType() -ne [HeaderNode]) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $this.Title -eq $OtherNode.Title
|
||||
}
|
||||
|
||||
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
|
||||
return $this.IsSimilarTo($OtherNode)
|
||||
}
|
||||
|
||||
[BaseNode] FindSimilarChildNode([BaseNode] $Find) {
|
||||
foreach ($childNode in $this.Children) {
|
||||
if ($childNode.IsSimilarTo($Find)) {
|
||||
return $childNode
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
hidden [Boolean] IsNodeHasMarkdownHeader([BaseNode] $node) {
|
||||
if ($node -is [HeaderNode]) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if (($node -is [ToolVersionsListNode]) -and ($node.ListType -eq "List")) {
|
||||
return $true
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
class ToolVersionNode: BaseToolNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $Version
|
||||
|
||||
ToolVersionNode([String] $ToolName, [String] $Version): base($ToolName) {
|
||||
|
||||
if ([String]::IsNullOrEmpty($Version)) {
|
||||
throw "ToolVersionNode '$($this.ToolName)' has empty version"
|
||||
}
|
||||
|
||||
$this.Version = $Version
|
||||
}
|
||||
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
return "- $($this.ToolName) $($this.Version)"
|
||||
}
|
||||
|
||||
[String] GetValue() {
|
||||
return $this.Version
|
||||
}
|
||||
|
||||
[PSCustomObject] ToJsonObject() {
|
||||
return [PSCustomObject]@{
|
||||
NodeType = $this.GetType().Name
|
||||
ToolName = $this.ToolName
|
||||
Version = $this.Version
|
||||
}
|
||||
}
|
||||
|
||||
static [BaseNode] FromJsonObject([Object] $JsonObj) {
|
||||
return [ToolVersionNode]::new($JsonObj.ToolName, $JsonObj.Version)
|
||||
}
|
||||
}
|
||||
|
||||
class ToolVersionsListNode: BaseToolNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String[]] $Versions
|
||||
|
||||
[Regex] $MajorVersionRegex
|
||||
|
||||
[ValidateSet("List", "Inline")]
|
||||
[String] $ListType
|
||||
|
||||
ToolVersionsListNode([String] $ToolName, [String[]] $Versions, [String] $MajorVersionRegex, [String] $ListType): base($ToolName) {
|
||||
$this.Versions = $Versions
|
||||
|
||||
if ([String]::IsNullOrEmpty($Versions)) {
|
||||
throw "ToolVersionsListNode '$($this.ToolName)' has empty versions list"
|
||||
}
|
||||
|
||||
$this.MajorVersionRegex = [Regex]::new($MajorVersionRegex)
|
||||
$this.ListType = $ListType
|
||||
$this.ValidateMajorVersionRegex()
|
||||
}
|
||||
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
if ($this.ListType -eq "Inline") {
|
||||
return "- $($this.ToolName): $($this.Versions -join ', ')"
|
||||
}
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine()
|
||||
$sb.AppendLine("$("#" * $Level) $($this.ToolName)")
|
||||
$this.Versions | ForEach-Object {
|
||||
$sb.AppendLine("- $_")
|
||||
}
|
||||
|
||||
return $sb.ToString().TrimEnd()
|
||||
}
|
||||
|
||||
[String] GetValue() {
|
||||
return $this.Versions -join ', '
|
||||
}
|
||||
|
||||
[String] ExtractMajorVersion([String] $Version) {
|
||||
$match = $this.MajorVersionRegex.Match($Version)
|
||||
if (($match.Success -ne $true) -or [String]::IsNullOrEmpty($match.Groups[0].Value)) {
|
||||
throw "Version '$Version' doesn't match regex '$($this.PrimaryVersionRegex)'"
|
||||
}
|
||||
|
||||
return $match.Groups[0].Value
|
||||
}
|
||||
|
||||
[PSCustomObject] ToJsonObject() {
|
||||
return [PSCustomObject]@{
|
||||
NodeType = $this.GetType().Name
|
||||
ToolName = $this.ToolName
|
||||
Versions = $this.Versions
|
||||
MajorVersionRegex = $this.MajorVersionRegex.ToString()
|
||||
ListType = $this.ListType
|
||||
}
|
||||
}
|
||||
|
||||
static [ToolVersionsListNode] FromJsonObject([Object] $JsonObj) {
|
||||
return [ToolVersionsListNode]::new($JsonObj.ToolName, $JsonObj.Versions, $JsonObj.MajorVersionRegex, $JsonObj.ListType)
|
||||
}
|
||||
|
||||
hidden [void] ValidateMajorVersionRegex() {
|
||||
$this.Versions | Group-Object { $this.ExtractMajorVersion($_) } | ForEach-Object {
|
||||
if ($_.Count -gt 1) {
|
||||
throw "Multiple versions from list '$($this.GetValue())' return the same result from regex '$($this.MajorVersionRegex)': $($_.Name)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TableNode: BaseNode {
|
||||
# It is easier to store the table as rendered lines because it will simplify finding differences in rows later
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $Headers
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String[]] $Rows
|
||||
|
||||
TableNode([String] $Headers, [String[]] $Rows) {
|
||||
$this.Headers = $Headers
|
||||
$this.Rows = $Rows
|
||||
|
||||
$columnsCount = $this.Headers.Split("|").Count
|
||||
$this.Rows | ForEach-Object {
|
||||
if ($_.Split("|").Count -ne $columnsCount) {
|
||||
throw "Table has different number of columns in different rows"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Boolean] ShouldBeIncludedToDiff() {
|
||||
return $true
|
||||
}
|
||||
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
$maxColumnWidths = $this.CalculateColumnsWidth()
|
||||
$columnsCount = $maxColumnWidths.Count
|
||||
|
||||
$delimiterLine = [String]::Join("|", @("-") * $columnsCount)
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
@($this.Headers) + @($delimiterLine) + $this.Rows | ForEach-Object {
|
||||
$sb.Append("|")
|
||||
$row = $_.Split("|")
|
||||
|
||||
for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) {
|
||||
$padSymbol = $row[$colIndex] -eq "-" ? "-" : " "
|
||||
$cellContent = $row[$colIndex].PadRight($maxColumnWidths[$colIndex], $padSymbol)
|
||||
$sb.Append(" $($cellContent) |")
|
||||
}
|
||||
|
||||
$sb.AppendLine()
|
||||
}
|
||||
|
||||
return $sb.ToString().TrimEnd()
|
||||
}
|
||||
|
||||
hidden [Int32[]] CalculateColumnsWidth() {
|
||||
$maxColumnWidths = $this.Headers.Split("|") | ForEach-Object { $_.Length }
|
||||
$columnsCount = $maxColumnWidths.Count
|
||||
|
||||
$this.Rows | ForEach-Object {
|
||||
$columnWidths = $_.Split("|") | ForEach-Object { $_.Length }
|
||||
for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) {
|
||||
$maxColumnWidths[$colIndex] = [Math]::Max($maxColumnWidths[$colIndex], $columnWidths[$colIndex])
|
||||
}
|
||||
}
|
||||
|
||||
return $maxColumnWidths
|
||||
}
|
||||
|
||||
[PSCustomObject] ToJsonObject() {
|
||||
return [PSCustomObject]@{
|
||||
NodeType = $this.GetType().Name
|
||||
Headers = $this.Headers
|
||||
Rows = $this.Rows
|
||||
}
|
||||
}
|
||||
|
||||
static [TableNode] FromJsonObject([Object] $JsonObj) {
|
||||
return [TableNode]::new($JsonObj.Headers, $JsonObj.Rows)
|
||||
}
|
||||
|
||||
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
|
||||
if ($OtherNode.GetType() -ne [TableNode]) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# We don't support having multiple TableNode instances on the same header level so such check is fine
|
||||
return $true
|
||||
}
|
||||
|
||||
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
|
||||
if (-not $this.IsSimilarTo($OtherNode)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# We don't compare $this.Headers intentionally
|
||||
# It is fine to ignore the tables where headers are changed but rows are not changed
|
||||
|
||||
if ($this.Rows.Count -ne $OtherNode.Rows.Count) {
|
||||
return $false
|
||||
}
|
||||
|
||||
for ($rowIndex = 0; $rowIndex -lt $this.Rows.Count; $rowIndex++) {
|
||||
if ($this.Rows[$rowIndex] -ne $OtherNode.Rows[$rowIndex]) {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
static [TableNode] FromObjectsArray([PSCustomObject[]] $Table) {
|
||||
if ($Table.Count -eq 0) {
|
||||
throw "Failed to create TableNode from empty objects array"
|
||||
}
|
||||
|
||||
[String] $tableHeaders = [TableNode]::ArrayToTableRow($Table[0].PSObject.Properties.Name)
|
||||
[Collections.Generic.List[String]] $tableRows = @()
|
||||
|
||||
$Table | ForEach-Object {
|
||||
$rowHeaders = [TableNode]::ArrayToTableRow($_.PSObject.Properties.Name)
|
||||
if (($rowHeaders -ne $tableHeaders)) {
|
||||
throw "Failed to create TableNode from objects array because objects have different properties"
|
||||
}
|
||||
|
||||
$tableRows.Add([TableNode]::ArrayToTableRow($_.PSObject.Properties.Value))
|
||||
}
|
||||
|
||||
return [TableNode]::new($tableHeaders, $tableRows)
|
||||
}
|
||||
|
||||
hidden static [String] ArrayToTableRow([String[]] $Values) {
|
||||
if ($Values.Count -eq 0) {
|
||||
throw "Failed to create TableNode because some objects are empty"
|
||||
}
|
||||
$Values | ForEach-Object {
|
||||
if ($_.Contains("|")) {
|
||||
throw "Failed to create TableNode because some cells '$_' contains forbidden symbol '|'"
|
||||
}
|
||||
}
|
||||
|
||||
return [String]::Join("|", $Values)
|
||||
}
|
||||
}
|
||||
|
||||
class NoteNode: BaseNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $Content
|
||||
|
||||
NoteNode([String] $Content) {
|
||||
$this.Content = $Content
|
||||
}
|
||||
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
return @(
|
||||
'```',
|
||||
$this.Content,
|
||||
'```'
|
||||
) -join "`n"
|
||||
}
|
||||
|
||||
[PSCustomObject] ToJsonObject() {
|
||||
return [PSCustomObject]@{
|
||||
NodeType = $this.GetType().Name
|
||||
Content = $this.Content
|
||||
}
|
||||
}
|
||||
|
||||
static [NoteNode] FromJsonObject([Object] $JsonObj) {
|
||||
return [NoteNode]::new($JsonObj.Content)
|
||||
}
|
||||
|
||||
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
|
||||
if ($OtherNode.GetType() -ne [NoteNode]) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $this.Content -eq $OtherNode.Content
|
||||
}
|
||||
|
||||
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
|
||||
return $this.IsSimilarTo($OtherNode)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using module ./SoftwareReport.BaseNodes.psm1
|
||||
using module ./SoftwareReport.Nodes.psm1
|
||||
|
||||
class SoftwareReport {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[HeaderNode] $Root
|
||||
|
||||
SoftwareReport([String] $Title) {
|
||||
$this.Root = [HeaderNode]::new($Title)
|
||||
}
|
||||
|
||||
SoftwareReport([HeaderNode] $Root) {
|
||||
$this.Root = $Root
|
||||
}
|
||||
|
||||
[String] ToJson() {
|
||||
return $this.Root.ToJsonObject() | ConvertTo-Json -Depth 10
|
||||
}
|
||||
|
||||
static [SoftwareReport] FromJson([String] $JsonString) {
|
||||
$jsonObj = $JsonString | ConvertFrom-Json
|
||||
$rootNode = [NodesFactory]::ParseNodeFromObject($jsonObj)
|
||||
return [SoftwareReport]::new($rootNode)
|
||||
}
|
||||
|
||||
[String] ToMarkdown() {
|
||||
return $this.Root.ToMarkdown().Trim()
|
||||
}
|
||||
|
||||
[String] GetImageVersion() {
|
||||
$imageVersionNode = $this.Root.Children ?? @() | Where-Object { ($_ -is [ToolVersionNode]) -and ($_.ToolName -eq "Image Version:") } | Select-Object -First 1
|
||||
return $imageVersionNode.Version ?? "Unknown version"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
using module ../SoftwareReport.psm1
|
||||
using module ../SoftwareReport.DifferenceCalculator.psm1
|
||||
|
||||
Describe "Comparer.E2E" {
|
||||
It "Some tools are updated" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
|
||||
$prevTools.AddToolVersion("ToolWillBeUpdated1", "1.0.0")
|
||||
$prevTools.AddToolVersion("ToolWillBeUpdated2", "3.0.1")
|
||||
$prevTools.AddToolVersionsList("ToolWillBeUpdated3", @("14.0.0", "15.5.1"), "^\d+")
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
|
||||
$nextTools.AddToolVersion("ToolWillBeUpdated1", "2.5.0")
|
||||
$nextTools.AddToolVersion("ToolWillBeUpdated2", "3.0.2")
|
||||
$nextTools.AddToolVersionsList("ToolWillBeUpdated3", @("14.2.0", "15.5.1"), "^\d+")
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- OS Version: macOS 11.7.1 (20G817)
|
||||
- Image Version: 20220922.1
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Updated
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Previous (20220918.1)</th>
|
||||
<th>Current (20220922.1)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="3">Tools</td>
|
||||
<td>ToolWillBeUpdated1</td>
|
||||
<td>1.0.0</td>
|
||||
<td>2.5.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWillBeUpdated2</td>
|
||||
<td>3.0.1</td>
|
||||
<td>3.0.2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWillBeUpdated3</td>
|
||||
<td>14.0.0</td>
|
||||
<td>14.2.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Some tools are updated, added and removed" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
|
||||
$prevLanguagesAndRuntimes = $prevInstalledSoftware.AddHeader("Language and Runtime")
|
||||
$prevLanguagesAndRuntimes.AddToolVersion("ToolWillBeRemoved", "5.1.16(1)-release")
|
||||
$prevLanguagesAndRuntimes.AddToolVersionsListInline("ToolWithMultipleVersions3", @("1.2.100", "1.2.200", "1.3.500", "1.4.100", "1.4.200"), "^\d+\.\d+\.\d")
|
||||
$prevLanguagesAndRuntimes.AddToolVersion("ToolWithoutChanges", "5.34.0")
|
||||
$prevLanguagesAndRuntimes.AddToolVersion("ToolWillBeUpdated", "8.1.0")
|
||||
|
||||
$prevCachedTools = $prevInstalledSoftware.AddHeader("Cached Tools")
|
||||
$prevCachedTools.AddToolVersionsList("ToolWithMultipleVersions1", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
|
||||
$prevCachedTools.AddToolVersionsList("ToolWithMultipleVersions2", @("14.8.0", "15.1.0", "16.4.2"), "^\d+")
|
||||
|
||||
$prevSQLSection = $prevInstalledSoftware.AddHeader("Databases")
|
||||
$prevSQLSection.AddToolVersion("MineSQL", "6.1.0")
|
||||
$prevSQLSection.AddNote("First Note")
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.2 (20G922)")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.0")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
|
||||
$nextLanguagesAndRuntimes = $nextInstalledSoftware.AddHeader("Language and Runtime")
|
||||
$nextLanguagesAndRuntimes.AddToolVersion("ToolWillBeAdded", "16.18.0")
|
||||
$nextLanguagesAndRuntimes.AddToolVersionsListInline("ToolWithMultipleVersions3", @("1.2.200", "1.3.515", "1.4.100", "1.4.200", "1.5.800"), "^\d+\.\d+\.\d")
|
||||
$nextLanguagesAndRuntimes.AddToolVersion("ToolWithoutChanges", "5.34.0")
|
||||
$nextLanguagesAndRuntimes.AddToolVersion("ToolWillBeUpdated", "8.3.0")
|
||||
|
||||
$nextCachedTools = $nextInstalledSoftware.AddHeader("Cached Tools")
|
||||
$nextCachedTools.AddToolVersionsList("ToolWithMultipleVersions1", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
|
||||
$nextCachedTools.AddToolVersionsList("ToolWithMultipleVersions2", @("15.1.0", "16.4.2", "17.0.1"), "^\d+")
|
||||
|
||||
$nextSQLSection = $nextInstalledSoftware.AddHeader("Databases")
|
||||
$nextSQLSection.AddToolVersion("MineSQL", "6.1.1")
|
||||
$nextSQLSection.AddNote("Second Note")
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- OS Version: macOS 11.7.2 (20G922)
|
||||
- Image Version: 20220922.0
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Added :heavy_plus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Current (20220922.0)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="2">Language and Runtime</td>
|
||||
<td>ToolWillBeAdded</td>
|
||||
<td>16.18.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWithMultipleVersions3</td>
|
||||
<td>1.5.800</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Cached Tools</td>
|
||||
<td>ToolWithMultipleVersions2</td>
|
||||
<td>17.0.1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Deleted :heavy_minus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Previous (20220918.1)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="2">Language and Runtime</td>
|
||||
<td>ToolWithMultipleVersions3</td>
|
||||
<td>1.2.100</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWillBeRemoved</td>
|
||||
<td>5.1.16(1)-release</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Cached Tools</td>
|
||||
<td>ToolWithMultipleVersions2</td>
|
||||
<td>14.8.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Updated
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Previous (20220918.1)</th>
|
||||
<th>Current (20220922.0)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1"></td>
|
||||
<td>OS Version</td>
|
||||
<td>macOS 11.7.1 (20G817)</td>
|
||||
<td>macOS 11.7.2 (20G922)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">Language and Runtime</td>
|
||||
<td>ToolWithMultipleVersions3</td>
|
||||
<td>1.3.500</td>
|
||||
<td>1.3.515</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWillBeUpdated</td>
|
||||
<td>8.1.0</td>
|
||||
<td>8.3.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Databases</td>
|
||||
<td>MineSQL</td>
|
||||
<td>6.1.0</td>
|
||||
<td>6.1.1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Header tree changes" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevInstalledSoftware.AddToolVersion("ToolWithoutChanges", "5.34.0")
|
||||
$prevInstalledSoftware.AddHeader("HeaderWillBeRemoved").AddHeader("SubheaderWillBeRemoved").AddToolVersion("ToolWillBeRemoved", "1.0.0")
|
||||
$prevInstalledSoftware.AddHeader("Header1").AddToolVersion("ToolWillBeMovedToAnotherHeader", "3.0.0")
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.0")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextInstalledSoftware.AddToolVersion("ToolWithoutChanges", "5.34.0")
|
||||
$nextInstalledSoftware.AddHeader("HeaderWillBeAdded").AddHeader("SubheaderWillBeAdded").AddToolVersion("ToolWillBeAdded", "5.0.0")
|
||||
$nextInstalledSoftware.AddHeader("Header2").AddToolVersion("ToolWillBeMovedToAnotherHeader", "3.0.0")
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- Image Version: 20220922.0
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Added :heavy_plus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Current (20220922.0)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1">HeaderWillBeAdded ><br> SubheaderWillBeAdded</td>
|
||||
<td>ToolWillBeAdded</td>
|
||||
<td>5.0.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Header2</td>
|
||||
<td>ToolWillBeMovedToAnotherHeader</td>
|
||||
<td>3.0.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Deleted :heavy_minus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Previous (20220918.1)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1">HeaderWillBeRemoved ><br> SubheaderWillBeRemoved</td>
|
||||
<td>ToolWillBeRemoved</td>
|
||||
<td>1.0.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Header1</td>
|
||||
<td>ToolWillBeMovedToAnotherHeader</td>
|
||||
<td>3.0.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Tables are added and removed" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevInstalledSoftware.AddHeader("HeaderWillExist").AddTable(@(
|
||||
[PSCustomObject]@{TableInExistingHeaderWillBeRemoved = "Q"; Value = "25"},
|
||||
[PSCustomObject]@{TableInExistingHeaderWillBeRemoved = "O"; Value = "24"}
|
||||
))
|
||||
|
||||
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
|
||||
$prevTools.AddHeader("HeaderWillBeRemoved").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "Z"; Value = "30"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "W"; Value = "29"}
|
||||
))
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextInstalledSoftware.AddHeader("HeaderWillExist")
|
||||
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
|
||||
$nextTools.AddToolVersion("ToolWillBeAdded", "3.0.1")
|
||||
$nextTools.AddTable(@(
|
||||
[PSCustomObject]@{NewTableInExistingHeader = "A"; Value = "1"},
|
||||
[PSCustomObject]@{NewTableInExistingHeader = "B"; Value = "2"}
|
||||
))
|
||||
$nextTools.AddHeader("NewHeaderWithTable").AddTable(@(
|
||||
[PSCustomObject]@{NewTableInNewHeader = "C"; Value = "3"},
|
||||
[PSCustomObject]@{NewTableInNewHeader = "D"; Value = "4"}
|
||||
))
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- Image Version: 20220922.1
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Added :heavy_plus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Current (20220922.1)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1">Tools</td>
|
||||
<td>ToolWillBeAdded</td>
|
||||
<td>3.0.1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
#### Tools
|
||||
| NewTableInExistingHeader | Value |
|
||||
| ------------------------ | ----- |
|
||||
| A | 1 |
|
||||
| B | 2 |
|
||||
|
||||
#### Tools > NewHeaderWithTable
|
||||
| NewTableInNewHeader | Value |
|
||||
| ------------------- | ----- |
|
||||
| C | 3 |
|
||||
| D | 4 |
|
||||
|
||||
### Deleted :heavy_minus_sign:
|
||||
|
||||
#### HeaderWillExist
|
||||
| TableInExistingHeaderWillBeRemoved | Value |
|
||||
| ---------------------------------- | ------ |
|
||||
| ~~Q~~ | ~~25~~ |
|
||||
| ~~O~~ | ~~24~~ |
|
||||
|
||||
#### Tools > HeaderWillBeRemoved
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~Z~~ | ~~30~~ |
|
||||
| ~~W~~ | ~~29~~ |
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Tables are changed" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
|
||||
$prevTools.AddHeader("TableWithAddedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AA"; Value = "10"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AB"; Value = "11"}
|
||||
))
|
||||
$prevTools.AddHeader("TableWithRemovedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BA"; Value = "32"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BB"; Value = "33"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BC"; Value = "34"}
|
||||
))
|
||||
$prevTools.AddHeader("TableWithUpdatedRow").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CA"; Value = "42"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CB"; Value = "43"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CC"; Value = "44"}
|
||||
))
|
||||
$prevTools.AddHeader("TableWithUpdatedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DA"; Value = "50"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DB"; Value = "51"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DC"; Value = "52"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DD"; Value = "53"}
|
||||
))
|
||||
$prevTools.AddHeader("TableWithComplexChanges").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EA"; Value = "62"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EB"; Value = "63"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EC"; Value = "64"}
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "ED"; Value = "65"}
|
||||
))
|
||||
|
||||
$prevTools.AddHeader("TableWithOnlyHeaderChanged").AddTable(@(
|
||||
[PSCustomObject]@{TableWithOnlyHeaderChanged = "FA"; Value = "72"},
|
||||
[PSCustomObject]@{TableWithOnlyHeaderChanged = "FB"; Value = "73"}
|
||||
))
|
||||
|
||||
$prevTools.AddHeader("TableWithHeaderAndRowsChanges").AddTable(@(
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GA"; Value = "82"},
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GB"; Value = "83"},
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GC"; Value = "84"}
|
||||
))
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
|
||||
$nextTools.AddHeader("TableWithAddedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AA"; Value = "10"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AB"; Value = "11"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AC"; Value = "12"}
|
||||
))
|
||||
$nextTools.AddHeader("TableWithRemovedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BB"; Value = "33"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BC"; Value = "34"}
|
||||
))
|
||||
$nextTools.AddHeader("TableWithUpdatedRow").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CA"; Value = "42"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CB"; Value = "500"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CC"; Value = "44"}
|
||||
))
|
||||
$nextTools.AddHeader("TableWithUpdatedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DA"; Value = "50"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DB"; Value = "5100"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DC"; Value = "5200"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DD"; Value = "53"}
|
||||
))
|
||||
$nextTools.AddHeader("TableWithComplexChanges").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EB"; Value = "63"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EC"; Value = "640"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "ED"; Value = "65"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EE"; Value = "66"}
|
||||
))
|
||||
|
||||
$nextTools.AddHeader("TableWithOnlyHeaderChanged").AddTable(@(
|
||||
[PSCustomObject]@{TableWithOnlyHeaderChanged2 = "FA"; Value = "72"},
|
||||
[PSCustomObject]@{TableWithOnlyHeaderChanged2 = "FB"; Value = "73"}
|
||||
))
|
||||
|
||||
$nextTools.AddHeader("TableWithHeaderAndRowsChanges").AddTable(@(
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GA"; Value = "82"},
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GE"; Value = "850"},
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GC"; Value = "840"}
|
||||
))
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- Image Version: 20220922.1
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Added :heavy_plus_sign:
|
||||
|
||||
#### Tools > TableWithAddedRows
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ----- |
|
||||
| AC | 12 |
|
||||
|
||||
#### Tools > TableWithHeaderAndRowsChanges
|
||||
| TableWithHeaderAndRowsChanges2 | Value |
|
||||
| ------------------------------ | ----- |
|
||||
| GA | 82 |
|
||||
| GE | 850 |
|
||||
| GC | 840 |
|
||||
|
||||
### Deleted :heavy_minus_sign:
|
||||
|
||||
#### Tools > TableWithRemovedRows
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~BA~~ | ~~32~~ |
|
||||
|
||||
#### Tools > TableWithHeaderAndRowsChanges
|
||||
| TableWithHeaderAndRowsChanges | Value |
|
||||
| ----------------------------- | ------ |
|
||||
| ~~GA~~ | ~~82~~ |
|
||||
| ~~GB~~ | ~~83~~ |
|
||||
| ~~GC~~ | ~~84~~ |
|
||||
|
||||
### Updated
|
||||
|
||||
#### Tools > TableWithUpdatedRow
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~CB~~ | ~~43~~ |
|
||||
| CB | 500 |
|
||||
|
||||
#### Tools > TableWithUpdatedRows
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~DB~~ | ~~51~~ |
|
||||
| ~~DC~~ | ~~52~~ |
|
||||
| DB | 5100 |
|
||||
| DC | 5200 |
|
||||
|
||||
#### Tools > TableWithComplexChanges
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~EA~~ | ~~62~~ |
|
||||
| ~~EC~~ | ~~64~~ |
|
||||
| EC | 640 |
|
||||
| EE | 66 |
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Reports are identical" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
|
||||
$prevTools.AddToolVersion("ToolA", "1.0.0")
|
||||
$prevTools.AddToolVersion("ToolB", "3.0.1")
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
|
||||
$nextTools.AddToolVersion("ToolA", "1.0.0")
|
||||
$nextTools.AddToolVersion("ToolB", "3.0.1")
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- OS Version: macOS 11.7.1 (20G817)
|
||||
- Image Version: 20220922.1
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
}
|
||||
+603
@@ -0,0 +1,603 @@
|
||||
using module ../SoftwareReport.Nodes.psm1
|
||||
using module ../SoftwareReport.DifferenceCalculator.psm1
|
||||
|
||||
BeforeDiscovery {
|
||||
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
|
||||
}
|
||||
|
||||
Describe "Comparer.UnitTests" {
|
||||
Describe "Headers Tree" {
|
||||
It "Add Node to existing header" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader")
|
||||
}
|
||||
|
||||
It "Add new header with Node" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddHeader("MySubHeader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubHeader")
|
||||
}
|
||||
|
||||
It "Remove Node from existing header" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader")
|
||||
}
|
||||
|
||||
It "Remove header with Node" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
|
||||
}
|
||||
|
||||
It "Node with minor changes" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.4")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
|
||||
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader", "MySubHeader")
|
||||
}
|
||||
|
||||
It "Node without changes" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
|
||||
It "Node is moved to different header" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader2").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader2")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
|
||||
}
|
||||
|
||||
It "Complex structure" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevSubHeader = $prevReport.AddHeader("MyHeader").AddHeader("MySubheader")
|
||||
$prevSubHeader.AddToolVersion("MyTool1", "2.1.3")
|
||||
$prevSubHeader.AddHeader("MySubSubheader").AddToolVersion("MyTool2", "2.9.1")
|
||||
$prevReport.AddHeader("MyHeader2")
|
||||
$prevReport.AddHeader("MyHeader3").AddHeader("MySubheader3").AddToolVersion("MyTool3", "14.2.1")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextSubHeader = $nextReport.AddHeader("MyHeader").AddHeader("MySubheader")
|
||||
$nextSubHeader.AddToolVersion("MyTool1", "2.1.4")
|
||||
$nextSubSubHeader = $nextSubHeader.AddHeader("MySubSubheader")
|
||||
$nextSubSubHeader.AddToolVersion("MyTool2", "2.9.1")
|
||||
$nextSubSubHeader.AddToolVersion("MyTool4", "2.7.6")
|
||||
$nextReport.AddHeader("MyHeader2")
|
||||
$nextReport.AddHeader("MyHeader3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool4"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.7.6"
|
||||
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader", "MySubSubheader")
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
|
||||
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool3"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "14.2.1"
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader3", "MySubheader3")
|
||||
}
|
||||
}
|
||||
|
||||
Describe "ToolVersionNode" {
|
||||
It "ToolVersionNode is updated" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.4")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
|
||||
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader")
|
||||
}
|
||||
}
|
||||
|
||||
Describe "ToolVersionsListNode" {
|
||||
It "Single version is not changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^.+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^.+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
|
||||
It "Single version is changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.4"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.1.3")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.1.4")
|
||||
}
|
||||
|
||||
It "Major version is added" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.1.4"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.1.4")
|
||||
}
|
||||
|
||||
It "Major version is removed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.1.4"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.1.4"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.1.3")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Major version is changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.1.4"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.2.0"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("3.1.4")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.2.0")
|
||||
}
|
||||
|
||||
It "Major version is added, removed and updated at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("1.0.0", "2.1.3", "3.1.4", "4.0.2"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.2.0", "4.0.2", "5.1.0"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("5.1.0")
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("3.1.4")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.2.0")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("1.0.0")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Minor version is added, removed and updated at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.3.8", "2.4.9", "2.5.3", "2.6.0", "2.7.4", "2.8.0"), "^\d+\.\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.5.3", "2.6.2", "2.7.5", "2.8.0", "2.9.2", "2.10.3"), "^\d+\.\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.9.2", "2.10.3")
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.6.0", "2.7.4")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.6.2", "2.7.5")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.3.8", "2.4.9")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Patch version is added, removed and updated at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.3.8", "2.4.9", "2.5.3", "2.6.0", "2.7.4"), "^\d+\.\d+\.\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.4.9", "2.5.4", "2.6.0", "2.7.5", "2.8.2"), "^\d+\.\d+\.\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.5.4", "2.7.5", "2.8.2")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.3.8", "2.5.3", "2.7.4")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Describe "TableNode" {
|
||||
It "Rows are added" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2", "D1|D2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.AddedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.AddedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2", "D1|D2")
|
||||
}
|
||||
|
||||
It "Rows are deleted" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2", "D1|D2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("C1|C2", "D1|D2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2", "D1|D2")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.DeletedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.DeletedItems[0].CurrentReportNode.Rows | Should -BeArray @("C1|C2", "D1|D2")
|
||||
}
|
||||
|
||||
It "Rows are changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4")
|
||||
}
|
||||
|
||||
It "Rows are changed and updated at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4", "C1|C2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4", "C1|C2")
|
||||
}
|
||||
|
||||
It "Rows are changed and removed at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4")
|
||||
}
|
||||
|
||||
It "Rows are not changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
|
||||
It "Rows are not changed but header is changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value2", @("A1|A2", "B1|B2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
|
||||
It "Rows are changed and header is changed at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value2", @("A1|A2", "B1|B2", "C1|C2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value2"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Describe "NoteNode" {
|
||||
It "NoteNode is ignored from report" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddNote("MyFirstNote")
|
||||
$prevReport.AddHeader("MyFirstHeader").AddNote("MyFirstSubNote")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddNote("MySecondNote")
|
||||
$nextReport.AddHeader("MySecondHeader").AddNote("MySecondSubNote")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
using module ../SoftwareReport.Nodes.psm1
|
||||
using module ../SoftwareReport.DifferenceRender.psm1
|
||||
|
||||
BeforeDiscovery {
|
||||
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
|
||||
}
|
||||
|
||||
Describe "ComparerReport.UnitTests" {
|
||||
BeforeAll {
|
||||
$script:DifferenceRender = [SoftwareReportDifferenceRender]::new()
|
||||
}
|
||||
|
||||
Context "CalculateHtmlTableRowSpan" {
|
||||
It "Without the equal cells" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ Key = "A"; Value = "1" }
|
||||
[PSCustomObject]@{ Key = "B"; Value = "2" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "3" }
|
||||
)
|
||||
|
||||
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
|
||||
$actual | Should -BeArray @(1, 1, 1)
|
||||
}
|
||||
|
||||
It "Only equal cells" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ Key = "A"; Value = "D" }
|
||||
[PSCustomObject]@{ Key = "B"; Value = "D" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "D" }
|
||||
)
|
||||
|
||||
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Value")
|
||||
$actual | Should -BeArray @(3, 0, 0)
|
||||
}
|
||||
|
||||
It "Single row" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ Key = "A"; Value = "1" }
|
||||
)
|
||||
|
||||
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
|
||||
$actual | Should -BeArray @(1)
|
||||
}
|
||||
|
||||
It "Different cells" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ Key = "A"; Value = "1" }
|
||||
[PSCustomObject]@{ Key = "B"; Value = "2" }
|
||||
[PSCustomObject]@{ Key = "B"; Value = "3" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "4" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "5" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "6" }
|
||||
[PSCustomObject]@{ Key = "D"; Value = "7" }
|
||||
[PSCustomObject]@{ Key = "E"; Value = "8" }
|
||||
[PSCustomObject]@{ Key = "E"; Value = "9" }
|
||||
[PSCustomObject]@{ Key = "F"; Value = "10" }
|
||||
)
|
||||
|
||||
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
|
||||
$actual | Should -BeArray @(1, 2, 0, 3, 0, 0, 1, 2, 0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
Context "RenderCategory" {
|
||||
It "With line separator" {
|
||||
$actual = $DifferenceRender.RenderCategory(@("Header 1", "Header 2", "Header 3"), $true)
|
||||
$actual | Should -Be "Header 2 ><br> Header 3"
|
||||
}
|
||||
|
||||
It "Without line separator" {
|
||||
$actual = $DifferenceRender.RenderCategory(@("Header 1", "Header 2", "Header 3"), $false)
|
||||
$actual | Should -Be "Header 2 > Header 3"
|
||||
}
|
||||
|
||||
It "One header" {
|
||||
$actual = $DifferenceRender.RenderCategory(@("Header 1"), $false)
|
||||
$actual | Should -Be ""
|
||||
}
|
||||
|
||||
It "Empty headers" {
|
||||
$actual = $DifferenceRender.RenderCategory(@(), $false)
|
||||
$actual | Should -Be ""
|
||||
}
|
||||
}
|
||||
|
||||
Context "RenderToolName" {
|
||||
It "Clear tool name" {
|
||||
$actual = $DifferenceRender.RenderToolName("My Tool 1")
|
||||
$actual | Should -Be "My Tool 1"
|
||||
}
|
||||
|
||||
It "Name with colon symbol" {
|
||||
$actual = $DifferenceRender.RenderToolName("My Tool 1:")
|
||||
$actual | Should -Be "My Tool 1"
|
||||
}
|
||||
}
|
||||
|
||||
Context "StrikeTableRow" {
|
||||
It "Simple row" {
|
||||
$actual = $DifferenceRender.StrikeTableRow("Test1|Test2|Test3")
|
||||
$actual | Should -Be "~~Test1~~|~~Test2~~|~~Test3~~"
|
||||
}
|
||||
|
||||
It "Row with spaces" {
|
||||
$actual = $DifferenceRender.StrikeTableRow("Test 1|Test 2|Test 3")
|
||||
$actual | Should -Be "~~Test 1~~|~~Test 2~~|~~Test 3~~"
|
||||
}
|
||||
}
|
||||
|
||||
Context "RenderHtmlTable" {
|
||||
It "Simple table" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 1"; "Version" = "1.0" },
|
||||
[PSCustomObject]@{ "Category" = "B"; "Tool name" = "My Tool 2"; "Version" = "2.0" },
|
||||
[PSCustomObject]@{ "Category" = "C"; "Tool name" = "My Tool 3"; "Version" = "3.0" }
|
||||
)
|
||||
|
||||
$renderedTable = $DifferenceRender.RenderHtmlTable($table, "Category")
|
||||
$renderedTable | Should -Be @'
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Version</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1">A</td>
|
||||
<td>My Tool 1</td>
|
||||
<td>1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">B</td>
|
||||
<td>My Tool 2</td>
|
||||
<td>2.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">C</td>
|
||||
<td>My Tool 3</td>
|
||||
<td>3.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
'@
|
||||
|
||||
}
|
||||
|
||||
It "Table with the same category" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 1"; "Version" = "1.0" },
|
||||
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 2"; "Version" = "2.0" },
|
||||
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 3"; "Version" = "3.0" },
|
||||
[PSCustomObject]@{ "Category" = "B"; "Tool name" = "My Tool 4"; "Version" = "4.0" }
|
||||
)
|
||||
|
||||
$renderedTable = $DifferenceRender.RenderHtmlTable($table, "Category")
|
||||
$renderedTable | Should -Be @'
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Version</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="3">A</td>
|
||||
<td>My Tool 1</td>
|
||||
<td>1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>My Tool 2</td>
|
||||
<td>2.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>My Tool 3</td>
|
||||
<td>3.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">B</td>
|
||||
<td>My Tool 4</td>
|
||||
<td>4.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
'@
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Context "RenderTableNodesDiff" {
|
||||
It "Add new table" {
|
||||
$previousNode = $null
|
||||
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ---- | ----- |
|
||||
| A | 1 |
|
||||
| B | 2 |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Remove existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
|
||||
$currentNode = $null
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ----- | ----- |
|
||||
| ~~A~~ | ~~1~~ |
|
||||
| ~~B~~ | ~~2~~ |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Add new rows to existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
|
||||
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ---- | ----- |
|
||||
| C | 3 |
|
||||
| D | 4 |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Remove rows from existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
|
||||
$currentNode = [TableNode]::new("Name|Value", @("C|3", "D|4"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ----- | ----- |
|
||||
| ~~A~~ | ~~1~~ |
|
||||
| ~~B~~ | ~~2~~ |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Row is changed in existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
|
||||
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|3"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ----- | ----- |
|
||||
| ~~B~~ | ~~2~~ |
|
||||
| B | 3 |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Row is changed, added and removed at the same time in existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
|
||||
$currentNode = [TableNode]::new("Name|Value", @("B|2", "C|4", "D|4", "E|5"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ----- | ----- |
|
||||
| ~~A~~ | ~~1~~ |
|
||||
| ~~C~~ | ~~3~~ |
|
||||
| C | 4 |
|
||||
| E | 5 |
|
||||
|
||||
'@
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using module ../SoftwareReport.psm1
|
||||
using module ../SoftwareReport.Nodes.psm1
|
||||
|
||||
Describe "SoftwareReport.E2E" {
|
||||
Context "Report example 1" {
|
||||
BeforeEach {
|
||||
$softwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$softwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7 (20G817)")
|
||||
$softwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$installedSoftware = $softwareReport.Root.AddHeader("Installed Software")
|
||||
|
||||
$languagesAndRuntimes = $installedSoftware.AddHeader("Language and Runtime")
|
||||
$languagesAndRuntimes.AddToolVersion("Bash", "5.1.16(1)-release")
|
||||
$languagesAndRuntimes.AddToolVersionsListInline(".NET Core SDK", @("1.2.100", "1.2.200", "3.1.414"), "^\d+\.\d+\.\d")
|
||||
$languagesAndRuntimes.AddNode([ToolVersionNode]::new("Perl", "5.34.0"))
|
||||
|
||||
$cachedTools = $installedSoftware.AddHeader("Cached Tools")
|
||||
$cachedTools.AddToolVersionsList("Ruby", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
|
||||
$cachedTools.AddToolVersionsList("Node.js", @("14.8.0", "15.1.0", "16.4.2"), "^\d+")
|
||||
|
||||
$javaSection = $installedSoftware.AddHeader("Java")
|
||||
$javaSection.AddTable(@(
|
||||
[PSCustomObject] @{ Version = "8.0.125"; Vendor = "My Vendor"; "Environment Variable" = "JAVA_HOME_8_X64" },
|
||||
[PSCustomObject] @{ Version = "11.3.103"; Vendor = "My Vendor"; "Environment Variable" = "JAVA_HOME_11_X64" }
|
||||
))
|
||||
|
||||
$sqlSection = $installedSoftware.AddHeader("MySQL")
|
||||
$sqlSection.AddToolVersion("MySQL", "6.1.0")
|
||||
$sqlSection.AddNote("MySQL service is disabled by default.`nUse the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'")
|
||||
|
||||
$expectedMarkdown = @'
|
||||
# macOS 11
|
||||
- OS Version: macOS 11.7 (20G817)
|
||||
- Image Version: 20220918.1
|
||||
|
||||
## Installed Software
|
||||
|
||||
### Language and Runtime
|
||||
- Bash 5.1.16(1)-release
|
||||
- .NET Core SDK: 1.2.100, 1.2.200, 3.1.414
|
||||
- Perl 5.34.0
|
||||
|
||||
### Cached Tools
|
||||
|
||||
#### Ruby
|
||||
- 2.7.3
|
||||
- 2.8.1
|
||||
- 3.1.2
|
||||
|
||||
#### Node.js
|
||||
- 14.8.0
|
||||
- 15.1.0
|
||||
- 16.4.2
|
||||
|
||||
### Java
|
||||
| Version | Vendor | Environment Variable |
|
||||
| -------- | --------- | -------------------- |
|
||||
| 8.0.125 | My Vendor | JAVA_HOME_8_X64 |
|
||||
| 11.3.103 | My Vendor | JAVA_HOME_11_X64 |
|
||||
|
||||
### MySQL
|
||||
- MySQL 6.1.0
|
||||
```
|
||||
MySQL service is disabled by default.
|
||||
Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'
|
||||
```
|
||||
'@
|
||||
}
|
||||
|
||||
It "ToMarkdown" {
|
||||
$softwareReport.ToMarkdown() | Should -Be $expectedMarkdown
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$json = $softwareReport.ToJson()
|
||||
$deserializedReport = [SoftwareReport]::FromJson($json)
|
||||
$deserializedReport.ToMarkdown() | Should -Be $expectedMarkdown
|
||||
}
|
||||
}
|
||||
|
||||
Context "GetImageVersion" {
|
||||
It "Image version exists" {
|
||||
$softwareReport = [SoftwareReport]::new("MyReport")
|
||||
$softwareReport.Root.AddToolVersion("Image Version:", "123.4")
|
||||
$softwareReport.GetImageVersion() | Should -Be "123.4"
|
||||
}
|
||||
|
||||
It "Empty report" {
|
||||
$softwareReport = [SoftwareReport]::new("MyReport")
|
||||
$softwareReport.GetImageVersion() | Should -Be "Unknown version"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
using module ../SoftwareReport.Nodes.psm1
|
||||
|
||||
BeforeDiscovery {
|
||||
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
|
||||
}
|
||||
|
||||
Describe "Nodes.UnitTests" {
|
||||
Context "ToolVersionNode" {
|
||||
It "ToMarkdown" {
|
||||
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
$node.ToMarkdown() | Should -Be "- MyTool 2.1.3"
|
||||
}
|
||||
|
||||
It "GetValue" {
|
||||
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
$node.GetValue() | Should -Be "2.1.3"
|
||||
}
|
||||
|
||||
It "Serialization" {
|
||||
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "ToolVersionNode"
|
||||
$json.ToolName | Should -Be "MyTool"
|
||||
$json.Version | Should -Be "2.1.3"
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = ""; Version = "2.1.3" }) } | Should -Throw '*Exception setting "ToolName": "The argument is null or empty.*'
|
||||
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = "MyTool"; Version = "" }) } | Should -Throw 'ToolVersionNode ''MyTool'' has empty version'
|
||||
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = "MyTool"; Version = "2.1.3" }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [ToolVersionNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool", "2.1.3")) | Should -BeTrue
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -BeTrue
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool2", "2.1.3")) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool", "2.1.3")) | Should -BeTrue
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -BeFalse
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool2", "2.1.3")) | Should -BeFalse
|
||||
}
|
||||
}
|
||||
|
||||
Context "ToolVersionsListNode" {
|
||||
It "ToMarkdown - List" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
|
||||
$expected = @(
|
||||
"",
|
||||
"# MyTool"
|
||||
"- 2.7.7"
|
||||
"- 3.0.5"
|
||||
"- 3.1.3"
|
||||
) -join "`n"
|
||||
$node.ToMarkdown() | Should -Be $expected
|
||||
}
|
||||
|
||||
It "ToMarkdown - Inline" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "Inline")
|
||||
$node.ToMarkdown() | Should -Be "- MyTool: 2.7.7, 3.0.5, 3.1.3"
|
||||
}
|
||||
|
||||
It "GetValue" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
|
||||
$node.GetValue() | Should -Be "2.7.7, 3.0.5, 3.1.3"
|
||||
}
|
||||
|
||||
It "Serialization - List" {
|
||||
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "ToolVersionsListNode"
|
||||
$json.ToolName | Should -Be "Ruby"
|
||||
$json.Versions | Should -BeArray @("2.7.7", "3.0.5", "3.1.3")
|
||||
$json.MajorVersionRegex | Should -Be "^.+"
|
||||
$json.ListType | Should -Be "List"
|
||||
}
|
||||
|
||||
It "Serialization - Inline" {
|
||||
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "Inline")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "ToolVersionsListNode"
|
||||
$json.ToolName | Should -Be "Ruby"
|
||||
$json.Versions | Should -BeArray @("2.7.7", "3.0.5", "3.1.3")
|
||||
$json.MajorVersionRegex | Should -Be "^.+"
|
||||
$json.ListType | Should -Be "Inline"
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = ""; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "ToolName": "The argument is null or empty.*'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "Versions": "The argument is null or empty.*'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @(); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "Versions": "The argument is null, empty,*'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", '2.2.4'); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw 'Multiple versions from list * return the same result from regex *'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = ""; ListType = "List" }) } | Should -Throw 'Version * doesn''t match regex *'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "Fake" }) } | Should -Throw '*Exception setting "ListType": "The argument * does not belong to the set*'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Not -Throw
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "Inline" }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [ToolVersionsListNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
|
||||
) | Should -BeTrue
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.5", "5.0.0"), "^.+", "List")
|
||||
) | Should -BeTrue
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
|
||||
[ToolVersionsListNode]::new("MyTool2", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
|
||||
) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
|
||||
) | Should -BeTrue
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.5", "5.0.0"), "^.+", "List")
|
||||
) | Should -BeFalse
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
|
||||
[ToolVersionsListNode]::new("MyTool2", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
|
||||
) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "ExtractMajorVersion" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^\d+\.\d+", "List")
|
||||
$node.ExtractMajorVersion("2.1.3") | Should -Be "2.1"
|
||||
$node.ExtractMajorVersion("3.1.5") | Should -Be "3.1"
|
||||
$node.ExtractMajorVersion("4.0.0") | Should -Be "4.0"
|
||||
}
|
||||
|
||||
Context "ValidateMajorVersionRegex" {
|
||||
It "Major version regex - unique versions" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^\d+", "List")
|
||||
$node.Versions | Should -BeArray @("2.1.3", "3.1.5", "4.0.0")
|
||||
}
|
||||
|
||||
It "Major version regex - non-unique versions" {
|
||||
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "3.2.0", "4.0.0"), "^\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
|
||||
}
|
||||
|
||||
It "Minor version regex - unique versions" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.4.0", "3.1.2"), "^\d+\.\d+", "List")
|
||||
$node.Versions | Should -BeArray @("2.1.3", "2.4.0", "3.1.2")
|
||||
}
|
||||
|
||||
It "Minor version regex - non-unique versions" {
|
||||
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "3.1.2"), "^\d+\.\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
|
||||
}
|
||||
|
||||
It "Patch version regex - unique versions" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "2.1.5"), "^\d+\.\d+\.\d+", "List")
|
||||
$node.Versions | Should -BeArray @("2.1.3", "2.1.4", "2.1.5")
|
||||
}
|
||||
|
||||
It "Patch version regex - non-unique versions" {
|
||||
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "2.1.4"), "^\d+\.\d+\.\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
|
||||
}
|
||||
|
||||
It ".NET Core version regex - unique versions" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.100", "2.1.205", "2.1.303"), "^\d+\.\d+\.\d", "List")
|
||||
$node.Versions | Should -BeArray @("2.1.100", "2.1.205", "2.1.303")
|
||||
}
|
||||
|
||||
It ".NET Core version regex - non-unique versions" {
|
||||
{ [ToolVersionsListNode]::new("MyTool", @("2.1.100", "2.1.205", "2.1.230", "3.1.0"), "^\d+\.\d+\.\d", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context "TableNode" {
|
||||
It "ToMarkdown (Simple table)" {
|
||||
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
|
||||
$node.ToMarkdown() | Should -Be @'
|
||||
| Name | Value |
|
||||
| ---- | ----- |
|
||||
| A | B |
|
||||
| C | D |
|
||||
'@
|
||||
}
|
||||
|
||||
It "ToMarkdown (Wide cells)" {
|
||||
$node = [TableNode]::new("Name|Value", @("Very long value here|B", "C|And very long value here too"))
|
||||
$node.ToMarkdown() | Should -Be @'
|
||||
| Name | Value |
|
||||
| -------------------- | ---------------------------- |
|
||||
| Very long value here | B |
|
||||
| C | And very long value here too |
|
||||
'@
|
||||
}
|
||||
|
||||
It "CalculateColumnsWidth" {
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).CalculateColumnsWidth() | Should -BeArray @(4, 5)
|
||||
[TableNode]::new("Name|Value", @("Very long value here|B", "C|And very long value here too")).CalculateColumnsWidth() | Should -BeArray @(20, 28)
|
||||
}
|
||||
|
||||
It "Serialization" {
|
||||
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "TableNode"
|
||||
$json.Headers | Should -Be "Name|Value"
|
||||
$json.Rows | Should -BeArray @("A|B", "C|D")
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = ""; Rows = @("A|1", "B|2") }) } | Should -Throw 'Exception setting "Headers": "The argument is null or empty. *'
|
||||
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @() }) } | Should -Throw 'Exception setting "Rows": "The argument is null, empty, *'
|
||||
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @("A|1", "B|2|T", "C|3") }) } | Should -Throw 'Table has different number of columns in different rows'
|
||||
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @("A|1", "B|2") }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [TableNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|D"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|D", "F|W"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|E"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Key", @("A|B", "C|D"))) | Should -BeTrue
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|D"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Key", @("A|B", "C|D"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|D", "F|W"))) | Should -BeFalse
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|E"))) | Should -BeFalse
|
||||
}
|
||||
|
||||
Context "FromObjectsArray" {
|
||||
It "Correct table" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A"; Value = "B"}
|
||||
[PSCustomObject]@{Name = "C"; Value = "D"}
|
||||
)
|
||||
|
||||
$tableNode = [TableNode]::FromObjectsArray($table)
|
||||
$tableNode.Headers | Should -Be "Name|Value"
|
||||
$tableNode.Rows | Should -BeArray @("A|B", "C|D")
|
||||
}
|
||||
|
||||
It "Correct table with spaces" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A B"; "My Value" = "1 2"}
|
||||
[PSCustomObject]@{Name = "C D"; "My Value" = "3 4"}
|
||||
)
|
||||
|
||||
$tableNode = [TableNode]::FromObjectsArray($table)
|
||||
$tableNode.Headers | Should -Be "Name|My Value"
|
||||
$tableNode.Rows | Should -BeArray @("A B|1 2", "C D|3 4")
|
||||
}
|
||||
|
||||
It "Throw on empty table" {
|
||||
{ [TableNode]::FromObjectsArray(@()) } | Should -Throw "Failed to create TableNode from empty objects array"
|
||||
}
|
||||
|
||||
It "Throw on table with different columns" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A"; Value = "B"}
|
||||
[PSCustomObject]@{Name = "C"; Value2 = "D"}
|
||||
)
|
||||
|
||||
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode from objects array because objects have different properties"
|
||||
}
|
||||
|
||||
It "Throw on empty row" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A"; Value = "B"},
|
||||
[PSCustomObject]@{},
|
||||
[PSCustomObject]@{Name = "C"; Value2 = "D"}
|
||||
)
|
||||
|
||||
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some objects are empty"
|
||||
}
|
||||
|
||||
It "Throw on incorrect symbols in table column names" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{"Name|War" = "A"; Value = "B"}
|
||||
[PSCustomObject]@{"Name|War" = "C"; Value = "D"}
|
||||
)
|
||||
|
||||
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some cells * contains forbidden symbol*"
|
||||
}
|
||||
|
||||
It "Throw on incorrect symbols in table rows" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A"; Value = "B|AA"}
|
||||
[PSCustomObject]@{Name = "C"; Value = "D"}
|
||||
)
|
||||
|
||||
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some cells * contains forbidden symbol*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context "NoteNode" {
|
||||
It "ToMarkdown" {
|
||||
$node = [NoteNode]::new("Hello world`nGood Bye world")
|
||||
$node.ToMarkdown() | Should -Be @'
|
||||
```
|
||||
hello world
|
||||
Good Bye world
|
||||
```
|
||||
'@
|
||||
}
|
||||
|
||||
It "Serialization" {
|
||||
$node = [NoteNode]::new("MyContent`nMyContent2")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "NoteNode"
|
||||
$json.Content | Should -Be "MyContent`nMyContent2"
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode" }) } | Should -Throw '*Exception setting "Content": "The argument is null or empty.*'
|
||||
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode"; Content = "" }) } | Should -Throw '*Exception setting "Content": "The argument is null or empty.*'
|
||||
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode"; Content = "MyTool" }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [NoteNode]::new("MyContent`nMyContent2")
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [NoteNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[NoteNode]::new("MyContent").IsSimilarTo([NoteNode]::new("MyContent")) | Should -BeTrue
|
||||
[NoteNode]::new("MyContent").IsSimilarTo([NoteNode]::new("MyContent2")) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[NoteNode]::new("MyContent").IsIdenticalTo([NoteNode]::new("MyContent")) | Should -BeTrue
|
||||
[NoteNode]::new("MyContent").IsIdenticalTo([NoteNode]::new("MyContent2")) | Should -BeFalse
|
||||
}
|
||||
}
|
||||
|
||||
Context "HeaderNode" {
|
||||
It "ToMarkdown" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$node.ToMarkdown(1) | Should -Be @'
|
||||
|
||||
# MyHeader
|
||||
- MyTool 2.1.3
|
||||
'@
|
||||
}
|
||||
|
||||
It "ToMarkdown (level 3)" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$node.ToMarkdown(3) | Should -Be @'
|
||||
|
||||
### MyHeader
|
||||
- MyTool 2.1.3
|
||||
'@
|
||||
}
|
||||
|
||||
It "ToMarkdown (multiple levels)" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddHeader("MyHeader 2").AddHeader("MyHeader 3").AddHeader("MyHeader 4").AddToolVersion("MyTool", "2.1.3")
|
||||
$node.ToMarkdown(1) | Should -Be @'
|
||||
|
||||
# MyHeader
|
||||
|
||||
## MyHeader 2
|
||||
|
||||
### MyHeader 3
|
||||
|
||||
#### MyHeader 4
|
||||
- MyTool 2.1.3
|
||||
'@
|
||||
}
|
||||
|
||||
It "Serialization" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "HeaderNode"
|
||||
$json.Title | Should -Be "MyHeader"
|
||||
$json.Children | Should -HaveCount 1
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode" }) } | Should -Throw '*Exception setting "Title": "The argument is null or empty.*'
|
||||
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode"; Title = "" }) } | Should -Throw '*Exception setting "Title": "The argument is null or empty.*'
|
||||
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode"; Title = "MyHeader" }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [HeaderNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[HeaderNode]::new("MyHeader").IsSimilarTo([HeaderNode]::new("MyHeader")) | Should -BeTrue
|
||||
[HeaderNode]::new("MyHeader").IsSimilarTo([HeaderNode]::new("MyHeader2")) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[HeaderNode]::new("MyHeader").IsIdenticalTo([HeaderNode]::new("MyHeader")) | Should -BeTrue
|
||||
[HeaderNode]::new("MyHeader").IsIdenticalTo([HeaderNode]::new("MyHeader2")) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "FindSimilarChildNode" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
|
||||
$node.FindSimilarChildNode([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -Not -BeNullOrEmpty
|
||||
$node.FindSimilarChildNode([ToolVersionNode]::New("MyTool2", "1.0.0")) | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
Context "Detect node duplicates" {
|
||||
It "Similar HeaderNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddHeader("MySubHeader1")
|
||||
$node.AddHeader("MySubHeader2")
|
||||
{ $node.AddHeader("MySubHeader1") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Similar ToolVersionNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$node.AddToolVersion("MyTool2", "2.1.3")
|
||||
{ $node.AddToolVersion("MyTool", "2.1.3") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Similar ToolVersionsListNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersionsListInline("MyTool", @("2.1.3", "3.0.0"), "^\d+")
|
||||
$node.AddToolVersionsListInline("MyTool2", @("2.1.3", "3.0.0"), "^\d+")
|
||||
{ $node.AddToolVersionsList("MyTool", @("2.1.3", "3.0.0"), "^\d+") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Similar TableNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddTable(@(
|
||||
[PSCustomObject]@{Name = "Value1"},
|
||||
[PSCustomObject]@{Name = "Value2"}
|
||||
))
|
||||
{
|
||||
$node.AddTable(@(
|
||||
[PSCustomObject]@{Name = "Value1"},
|
||||
[PSCustomObject]@{Name = "Value2"}
|
||||
))
|
||||
} | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Similar NoteNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddNote("MyContent")
|
||||
$node.AddNote("MyContent2")
|
||||
{ $node.AddNote("MyContent") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "AddNode detects duplicates" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddNode([ToolVersionNode]::new("MyTool", "2.1.3"))
|
||||
{ $node.AddNode([ToolVersionNode]::new("MyTool", "2.1.3")) } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "AddNodes detects duplicates" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddNodes(@(
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3"),
|
||||
[ToolVersionNode]::new("MyTool2", "2.1.4")
|
||||
))
|
||||
{
|
||||
$node.AddNodes(@(
|
||||
[ToolVersionNode]::new("MyTool3", "2.1.5"),
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
))
|
||||
} | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Doesn't allow adding non-header nodes after header node" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
{ $node.AddToolVersion("MyTool", "2.1.3") } | Should -Not -Throw
|
||||
{ $node.AddHeader("MySubHeader") } | Should -Not -Throw
|
||||
{ $node.AddToolVersion("MyTool2", "2.1.4") } | Should -Throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node"
|
||||
{ $node.AddHeader("MySubHeader2") } | Should -Not -Throw
|
||||
{ $node.AddToolVersionsListInline("MyTool3", @("2.1.4", "2.1.5"), "^.+") } | Should -Throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node"
|
||||
{ $node.AddToolVersionsList("MyTool4", @("2.1.4", "2.1.5"), "^.+") } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
function ShouldBeArray([Array] $ActualValue, [Array]$ExpectedValue, [Switch] $Negate, [String] $Because) {
|
||||
if ($Negate) {
|
||||
throw "Negation is not supported for Should-BeArray"
|
||||
}
|
||||
|
||||
if ($ExpectedValue.Count -eq 0) {
|
||||
throw "Expected array cannot be empty. Use Should-BeNullOrEmpty instead."
|
||||
}
|
||||
|
||||
$ExpectedValue | ForEach-Object {
|
||||
if ($_.GetType() -notin @([String], [Int32])) {
|
||||
throw "Only string or int arrays are supported in Should-BeArray"
|
||||
}
|
||||
}
|
||||
|
||||
$actualValueJson = $ActualValue | ConvertTo-Json
|
||||
$expectedValueJson = $ExpectedValue | ConvertTo-Json
|
||||
|
||||
$succeeded = ($ActualValue.Count -eq $ExpectedValue.Count) -and ($actualValueJson -eq $expectedValueJson)
|
||||
|
||||
if (-not $succeeded) {
|
||||
$failureMessage = "Expected array '$actualValueJson' to be equal to '$expectedValueJson'"
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
Succeeded = $succeeded
|
||||
FailureMessage = $failureMessage
|
||||
}
|
||||
}
|
||||
|
||||
Add-ShouldOperator -Name BeArray `
|
||||
-InternalName 'ShouldBeArray' `
|
||||
-Test ${function:ShouldBeArray} `
|
||||
-SupportsArrayInput
|
||||
@@ -4,16 +4,52 @@
|
||||
# - https://github.community/t5/GitHub-Actions/GitHub-Actions-Manual-Trigger-Approvals/td-p/31504
|
||||
# - https://github.community/t5/GitHub-Actions/Protecting-github-workflows/td-p/30290
|
||||
|
||||
parameters:
|
||||
- name: job_id
|
||||
type: string
|
||||
default: 'generate_image'
|
||||
|
||||
- name: image_type
|
||||
type: string
|
||||
|
||||
- name: image_template_name
|
||||
type: string
|
||||
|
||||
- name: image_readme_name
|
||||
type: string
|
||||
|
||||
- name: agent_pool
|
||||
type: object
|
||||
default:
|
||||
name: 'ci-agent-pool'
|
||||
|
||||
- name: variable_group_name
|
||||
type: string
|
||||
default: 'Image Generation Variables'
|
||||
|
||||
- name: create_release
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
- name: repository_ref
|
||||
type: string
|
||||
default: 'self'
|
||||
|
||||
jobs:
|
||||
- job:
|
||||
- job: ${{ parameters.job_id }}
|
||||
displayName: Image Generation (${{ parameters.image_type }})
|
||||
timeoutInMinutes: 600
|
||||
cancelTimeoutInMinutes: 30
|
||||
pool: ci-agent-pool
|
||||
pool: ${{ parameters.agent_pool }}
|
||||
variables:
|
||||
- group: Image Generation Variables
|
||||
- group: ${{ parameters.variable_group_name }}
|
||||
|
||||
steps:
|
||||
- checkout: ${{ parameters.repository_ref }}
|
||||
clean: true
|
||||
fetchDepth: 0
|
||||
fetchTags: false
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Download custom repository'
|
||||
condition: and(ne(variables['CUSTOM_REPOSITORY_URL'], ''), ne(variables['CUSTOM_REPOSITORY_BRANCH'], ''))
|
||||
@@ -23,70 +59,114 @@ jobs:
|
||||
arguments: -RepoUrl $(CUSTOM_REPOSITORY_URL) `
|
||||
-RepoBranch $(CUSTOM_REPOSITORY_BRANCH)
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Set image template variables'
|
||||
- task: AzureCLI@2
|
||||
displayName: 'Set variables'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
azureSubscription: 'spn-hosted-runners'
|
||||
scriptType: 'pscore'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
$ImageType = "${{ parameters.image_type }}"
|
||||
$TemplateDirectoryName = if ($ImageType.StartsWith("ubuntu")) { "linux" } else { "win" }
|
||||
$TemplateDirectoryName = if ($ImageType.StartsWith("ubuntu")) { "ubuntu/templates" } else { "windows/templates" }
|
||||
$TemplateDirectoryPath = Join-Path "images" $TemplateDirectoryName | Resolve-Path
|
||||
$TemplatePath = Join-Path $TemplateDirectoryPath "$ImageType.pkr.hcl"
|
||||
if ( -not (Test-Path $TemplatePath) ) {
|
||||
$TemplatePath = Join-Path $TemplateDirectoryPath "$ImageType.json"
|
||||
}
|
||||
|
||||
$TemplateFileName = "${{ parameters.image_template_name }}"
|
||||
$TemplatePath = Join-Path $TemplateDirectoryPath $TemplateFileName
|
||||
Write-Host "##vso[task.setvariable variable=TemplateDirectoryPath;]$TemplateDirectoryPath"
|
||||
Write-Host "##vso[task.setvariable variable=TemplatePath;]$TemplatePath"
|
||||
|
||||
$ManagedImageName = "${{ parameters.image_type }}-$(Build.BuildId)"
|
||||
Write-Host "##vso[task.setvariable variable=ManagedImageName;]$ManagedImageName"
|
||||
|
||||
$TempResourceGroupName = "packer-temp-$ManagedImageName"
|
||||
Write-Host "##vso[task.setvariable variable=TempResourceGroupName;]$TempResourceGroupName"
|
||||
|
||||
$clientSecret = $(az keyvault secret show --name "spnhostedrunners" --vault-name "gh-imagegeneration" --query value -o tsv)
|
||||
Write-Host "##vso[task.setvariable variable=ClientSecret;issecret=true]$clientSecret"
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Build VM'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: ./images.CI/linux-and-win/build-image.ps1
|
||||
arguments: -ResourcesNamePrefix $(Build.BuildId) `
|
||||
-ClientId $(CLIENT_ID) `
|
||||
-ClientSecret $(CLIENT_SECRET) `
|
||||
-TemplatePath $(TemplatePath) `
|
||||
-ResourceGroup $(AZURE_RESOURCE_GROUP) `
|
||||
-StorageAccount $(AZURE_STORAGE_ACCOUNT) `
|
||||
-SubscriptionId $(AZURE_SUBSCRIPTION) `
|
||||
-TenantId $(AZURE_TENANT) `
|
||||
-Location $(AZURE_LOCATION) `
|
||||
-VirtualNetworkName $(BUILD_AGENT_VNET_NAME) `
|
||||
-VirtualNetworkRG $(BUILD_AGENT_VNET_RESOURCE_GROUP) `
|
||||
-VirtualNetworkSubnet $(BUILD_AGENT_SUBNET_NAME)
|
||||
arguments: -ClientId $(CLIENT_ID) `
|
||||
-ClientSecret "$(ClientSecret)" `
|
||||
-TemplatePath $(TemplatePath) `
|
||||
-ImageName "$(ManagedImageName)" `
|
||||
-ImageResourceGroupName $(AZURE_RESOURCE_GROUP) `
|
||||
-TempResourceGroupName "$(TempResourceGroupName)" `
|
||||
-SubscriptionId $(AZURE_SUBSCRIPTION) `
|
||||
-TenantId $(AZURE_TENANT) `
|
||||
-Location $(AZURE_LOCATION) `
|
||||
-VirtualNetworkName $(BUILD_AGENT_VNET_NAME) `
|
||||
-VirtualNetworkRG $(BUILD_AGENT_VNET_RESOURCE_GROUP) `
|
||||
-VirtualNetworkSubnet $(BUILD_AGENT_SUBNET_NAME)
|
||||
|
||||
env:
|
||||
PACKER_LOG: 1
|
||||
PACKER_LOG_PATH: $(Build.ArtifactStagingDirectory)/packer-log.txt
|
||||
PACKER_LOG_PATH: "$(Agent.TempDirectory)/packer-log.txt"
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Output Readme file content'
|
||||
displayName: 'Copy image artifacts to the separate directory'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Get-Content -Path (Join-Path "$(TemplateDirectoryPath)" "${{ parameters.image_readme_name }}")
|
||||
$ImageType = "${{ parameters.image_type }}"
|
||||
$rootDirectoryName = if ($ImageType.StartsWith("ubuntu")) { "ubuntu" } else { "windows" }
|
||||
$rootDirectoryPath = Join-Path "images" $rootDirectoryName | Resolve-Path
|
||||
|
||||
$readmePath = Join-Path $rootDirectoryPath "${{ parameters.image_readme_name }}"
|
||||
$softwareReportPath = Join-Path $rootDirectoryPath "software-report.json"
|
||||
|
||||
Copy-Item -Path $readmePath -Destination "$(Build.ArtifactStagingDirectory)/"
|
||||
if (Test-Path $softwareReportPath) {
|
||||
Copy-Item -Path $softwareReportPath -Destination "$(Build.ArtifactStagingDirectory)/"
|
||||
}
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Print markdown software report'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Get-Content -Path "$(Build.ArtifactStagingDirectory)/${{ parameters.image_readme_name }}"
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Print json software report'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
$softwareReportPath = "$(Build.ArtifactStagingDirectory)/software-report.json"
|
||||
if (Test-Path $softwareReportPath) {
|
||||
Get-Content -Path $softwareReportPath
|
||||
}
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
ArtifactName: 'Built_VM_Artifacts'
|
||||
displayName: Publish Artifacts
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Print provisioners duration'
|
||||
inputs:
|
||||
targetType: 'filePath'
|
||||
filePath: ./images.CI/measure-provisioners-duration.ps1
|
||||
arguments: -PackerLogPath "$(Build.ArtifactStagingDirectory)/packer-log.txt" `
|
||||
arguments: -PackerLogPath "$(Agent.TempDirectory)/packer-log.txt" `
|
||||
-PrefixToPathTrim "$(TemplateDirectoryPath)" `
|
||||
-PrintTopNLongest 25
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Create release for VM deployment'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: ./images.CI/linux-and-win/create-release.ps1
|
||||
arguments: -BuildId $(Build.BuildId) `
|
||||
-Organization $(RELEASE_TARGET_ORGANIZATION) `
|
||||
-DefinitionId $(RELEASE_TARGET_DEFINITION_ID) `
|
||||
-Project $(RELEASE_TARGET_PROJECT) `
|
||||
-ImageName ${{ parameters.image_type }} `
|
||||
-AccessToken $(RELEASE_TARGET_TOKEN)
|
||||
- ${{ if eq(parameters.create_release, true) }}:
|
||||
- task: PowerShell@2
|
||||
displayName: 'Create release for VM deployment'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: ./images.CI/linux-and-win/create-release.ps1
|
||||
arguments: -BuildId $(Build.BuildId) `
|
||||
-Organization $(RELEASE_TARGET_ORGANIZATION) `
|
||||
-DefinitionId $(RELEASE_TARGET_DEFINITION_ID) `
|
||||
-Project $(RELEASE_TARGET_PROJECT) `
|
||||
-ImageType "${{ parameters.image_type }}" `
|
||||
-ManagedImageName "$(ManagedImageName)" `
|
||||
-AccessToken $(RELEASE_TARGET_TOKEN)
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Clean up resources'
|
||||
@@ -94,10 +174,8 @@ jobs:
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: ./images.CI/linux-and-win/cleanup.ps1
|
||||
arguments: -ResourcesNamePrefix $(Build.BuildId) `
|
||||
-Image ${{ parameters.image_type }} `
|
||||
-StorageAccount $(AZURE_STORAGE_ACCOUNT) `
|
||||
-SubscriptionId $(AZURE_SUBSCRIPTION) `
|
||||
-ClientId $(CLIENT_ID) `
|
||||
-ClientSecret $(CLIENT_SECRET) `
|
||||
-TenantId $(AZURE_TENANT)
|
||||
arguments: -TempResourceGroupName "$(TempResourceGroupName)" `
|
||||
-SubscriptionId $(AZURE_SUBSCRIPTION) `
|
||||
-ClientId $(CLIENT_ID) `
|
||||
-ClientSecret "$(ClientSecret)" `
|
||||
-TenantId $(AZURE_TENANT)
|
||||
|
||||
@@ -17,4 +17,5 @@ jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: ubuntu2004
|
||||
image_readme_name: Ubuntu2004-Readme.md
|
||||
image_readme_name: Ubuntu2004-Readme.md
|
||||
image_template_name: ubuntu-20.04.pkr.hcl
|
||||
|
||||
@@ -17,4 +17,5 @@ jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: ubuntu2204
|
||||
image_readme_name: Ubuntu2204-Readme.md
|
||||
image_readme_name: Ubuntu2204-Readme.md
|
||||
image_template_name: ubuntu-22.04.pkr.hcl
|
||||
|
||||
+3
-2
@@ -16,5 +16,6 @@ pr:
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: ubuntu1804
|
||||
image_readme_name: Ubuntu1804-Readme.md
|
||||
image_type: ubuntu2404
|
||||
image_readme_name: Ubuntu2404-Readme.md
|
||||
image_template_name: ubuntu-24.04.pkr.hcl
|
||||
@@ -17,4 +17,5 @@ jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: windows2019
|
||||
image_readme_name: Windows2019-Readme.md
|
||||
image_readme_name: Windows2019-Readme.md
|
||||
image_template_name: windows-2019.pkr.hcl
|
||||
|
||||
@@ -17,4 +17,5 @@ jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: windows2022
|
||||
image_readme_name: Windows2022-Readme.md
|
||||
image_readme_name: Windows2022-Readme.md
|
||||
image_template_name: windows-2022.pkr.hcl
|
||||
|
||||
@@ -2,10 +2,10 @@ param(
|
||||
[String] [Parameter (Mandatory=$true)] $TemplatePath,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientId,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientSecret,
|
||||
[String] [Parameter (Mandatory=$true)] $ResourcesNamePrefix,
|
||||
[String] [Parameter (Mandatory=$true)] $Location,
|
||||
[String] [Parameter (Mandatory=$true)] $ResourceGroup,
|
||||
[String] [Parameter (Mandatory=$true)] $StorageAccount,
|
||||
[String] [Parameter (Mandatory=$true)] $ImageName,
|
||||
[String] [Parameter (Mandatory=$true)] $ImageResourceGroupName,
|
||||
[String] [Parameter (Mandatory=$true)] $TempResourceGroupName,
|
||||
[String] [Parameter (Mandatory=$true)] $SubscriptionId,
|
||||
[String] [Parameter (Mandatory=$true)] $TenantId,
|
||||
[String] [Parameter (Mandatory=$false)] $VirtualNetworkName,
|
||||
@@ -19,12 +19,9 @@ if (-not (Test-Path $TemplatePath))
|
||||
exit 1
|
||||
}
|
||||
|
||||
$Image = [io.path]::GetFileName($TemplatePath).Split(".")[0]
|
||||
$TempResourceGroupName = "${ResourcesNamePrefix}_${Image}"
|
||||
$ImageTemplateName = [io.path]::GetFileName($TemplatePath).Split(".")[0]
|
||||
$InstallPassword = [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
|
||||
packer validate -syntax-only $TemplatePath
|
||||
|
||||
$SensitiveData = @(
|
||||
'OSType',
|
||||
'StorageAccountLocation',
|
||||
@@ -38,21 +35,26 @@ $SensitiveData = @(
|
||||
Write-Host "Show Packer Version"
|
||||
packer --version
|
||||
|
||||
Write-Host "Build $Image VM"
|
||||
packer build -var "capture_name_prefix=$ResourcesNamePrefix" `
|
||||
-var "client_id=$ClientId" `
|
||||
Write-Host "Download packer plugins"
|
||||
packer init $TemplatePath
|
||||
|
||||
Write-Host "Validate packer template"
|
||||
packer validate -syntax-only $TemplatePath
|
||||
|
||||
Write-Host "Build $ImageTemplateName VM"
|
||||
packer build -var "client_id=$ClientId" `
|
||||
-var "client_secret=$ClientSecret" `
|
||||
-var "install_password=$InstallPassword" `
|
||||
-var "location=$Location" `
|
||||
-var "resource_group=$ResourceGroup" `
|
||||
-var "storage_account=$StorageAccount" `
|
||||
-var "managed_image_name=$ImageName" `
|
||||
-var "managed_image_resource_group_name=$ImageResourceGroupName" `
|
||||
-var "subscription_id=$SubscriptionId" `
|
||||
-var "temp_resource_group_name=$TempResourceGroupName" `
|
||||
-var "tenant_id=$TenantId" `
|
||||
-var "virtual_network_name=$VirtualNetworkName" `
|
||||
-var "virtual_network_resource_group_name=$VirtualNetworkRG" `
|
||||
-var "virtual_network_subnet_name=$VirtualNetworkSubnet" `
|
||||
-var "run_validation_diskspace=$env:RUN_VALIDATION_FLAG" `
|
||||
-color=false `
|
||||
$TemplatePath `
|
||||
| Where-Object {
|
||||
#Filter sensitive data from Packer logs
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
param(
|
||||
[String] [Parameter (Mandatory=$true)] $Image,
|
||||
[String] [Parameter (Mandatory=$true)] $ResourcesNamePrefix,
|
||||
[String] [Parameter (Mandatory=$true)] $StorageAccount,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientId,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientSecret,
|
||||
[String] [Parameter (Mandatory=$true)] $SubscriptionId,
|
||||
[String] [Parameter (Mandatory=$true)] $TenantId
|
||||
[Parameter (Mandatory=$true)] [string] $TempResourceGroupName,
|
||||
[Parameter (Mandatory=$true)] [string] $SubscriptionId,
|
||||
[Parameter (Mandatory=$true)] [string] $ClientId,
|
||||
[Parameter (Mandatory=$true)] [string] $ClientSecret,
|
||||
[Parameter (Mandatory=$true)] [string] $TenantId
|
||||
)
|
||||
|
||||
az login --service-principal --username $ClientId --password $ClientSecret --tenant $TenantId | Out-Null
|
||||
az login --service-principal --username $ClientId --password=$ClientSecret --tenant $TenantId | Out-Null
|
||||
az account set --subscription $SubscriptionId | Out-Null
|
||||
|
||||
$TempResourceGroupName = "${ResourcesNamePrefix}_${Image}"
|
||||
|
||||
$groupExist = az group exists --name $TempResourceGroupName --subscription $SubscriptionId
|
||||
$groupExist = az group exists --name $TempResourceGroupName
|
||||
if ($groupExist -eq "true") {
|
||||
$osDiskName = az deployment group list --resource-group $TempResourceGroupName --query "[].properties.parameters.osDiskName.value" -o tsv
|
||||
Write-Host "Found a match, deleting temporary files"
|
||||
az group delete --name $TempResourceGroupName --subscription $SubscriptionId --yes | Out-Null
|
||||
az group delete --name $TempResourceGroupName --yes | Out-Null
|
||||
Write-Host "Temporary group was deleted successfully"
|
||||
Write-Host "Deleting OS disk"
|
||||
az storage remove --account-name $StorageAccount -c "images" -n "$osDiskName.vhd" --only-show-errors | Out-Null
|
||||
Write-Host "OS disk deleted"
|
||||
} else {
|
||||
Write-Host "No temporary groups found"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
param(
|
||||
[UInt32] [Parameter (Mandatory)] $BuildId,
|
||||
[String] [Parameter (Mandatory)] $Organization,
|
||||
[String] [Parameter (Mandatory)] $Project,
|
||||
[String] [Parameter (Mandatory)] $ImageName,
|
||||
[String] [Parameter (Mandatory)] $DefinitionId,
|
||||
[String] [Parameter (Mandatory)] $AccessToken
|
||||
[Parameter (Mandatory)] [UInt32] $BuildId,
|
||||
[Parameter (Mandatory)] [string] $Organization,
|
||||
[Parameter (Mandatory)] [string] $Project,
|
||||
[Parameter (Mandatory)] [string] $ImageType,
|
||||
[Parameter (Mandatory)] [string] $ManagedImageName,
|
||||
[Parameter (Mandatory)] [string] $DefinitionId,
|
||||
[Parameter (Mandatory)] [string] $AccessToken
|
||||
)
|
||||
|
||||
$Body = @{
|
||||
@@ -13,8 +14,11 @@ $Body = @{
|
||||
ImageBuildId = @{
|
||||
value = $BuildId
|
||||
}
|
||||
ImageName = @{
|
||||
value = $ImageName
|
||||
ImageType = @{
|
||||
value = $ImageType
|
||||
}
|
||||
ManagedImageName = @{
|
||||
value = $ManagedImageName
|
||||
}
|
||||
}
|
||||
isDraft = "false"
|
||||
@@ -29,4 +33,4 @@ $headers = @{
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13
|
||||
$NewRelease = Invoke-RestMethod $URL -Body $Body -Method "POST" -Headers $headers -ContentType "application/json"
|
||||
|
||||
Write-Host "Created release: $($NewRelease._links.web.href)"
|
||||
Write-Host "Created release: $($NewRelease._links.web.href)"
|
||||
|
||||
@@ -13,16 +13,17 @@ function Push-AnkaTemplateToRegistry {
|
||||
[string] $TemplateName
|
||||
)
|
||||
|
||||
# if registry uuid doesn't match than delete an image in registry
|
||||
$images = anka --machine-readable registry --registry-path $RegistryUrl list | ConvertFrom-Json | ForEach-Object body
|
||||
# if registry uuid doesn't match then delete an image in registry
|
||||
$AnkaCaCrtPath="$HOME/.config/anka/certs/anka-ca-crt.pem"
|
||||
$images = anka --machine-readable registry --cacert $AnkaCaCrtPath --registry-path $RegistryUrl list | ConvertFrom-Json | ForEach-Object body
|
||||
$images | Where-Object name -eq $TemplateName | ForEach-Object {
|
||||
$id = $_.id
|
||||
$id = $_.uuid
|
||||
Show-StringWithFormat "Deleting '$TemplateName[$id]' VM and '$TagName' tag"
|
||||
$uri = '{0}/registry/vm?id={1}' -f $RegistryUrl, $id
|
||||
Invoke-WebRequest -Uri $uri -Method Delete | Out-Null
|
||||
$curlCommand='curl -s -X DELETE -k "{0}/registry/vm?id={1}"' -f $RegistryUrl, $id
|
||||
Invoke-AnkaCommand -Command $curlCommand
|
||||
}
|
||||
|
||||
$command = "anka registry --registry-path $RegistryUrl push --force --tag $TagName $TemplateName"
|
||||
$command = "anka registry --cacert $AnkaCaCrtPath --registry-path $RegistryUrl push --force --tag $TagName $TemplateName"
|
||||
Invoke-AnkaCommand -Command $command
|
||||
}
|
||||
|
||||
@@ -66,7 +67,7 @@ function Invoke-AnkaCommand {
|
||||
[string] $Command
|
||||
)
|
||||
|
||||
$result = bash -c "$Command 2>&1" | Out-String
|
||||
$result = bash -c "$Command 2>&1"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "There is an error during command execution:`n$result"
|
||||
exit 1
|
||||
@@ -232,3 +233,19 @@ function Wait-AnkaVMSSHService {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Set-AnkaVMUuid {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $VMName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $Uuid
|
||||
)
|
||||
|
||||
$command = "anka modify $VMName set custom-variable hw.uuid $Uuid"
|
||||
Write-Host "`t[*] Setting $VMName uuid to $Uuid"
|
||||
Invoke-AnkaCommand -Command $command
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ param(
|
||||
[string] $TemplateName,
|
||||
|
||||
[bool] $DownloadLatestVersion = $true,
|
||||
[bool] $PushToRegistry = $true,
|
||||
[bool] $BetaSearch = $false,
|
||||
[bool] $InstallSoftwareUpdate = $true,
|
||||
[bool] $EnableAutoLogon = $true,
|
||||
@@ -26,7 +27,8 @@ param(
|
||||
[int] $RamSizeGb = 7,
|
||||
[int] $DiskSizeGb = 300,
|
||||
[string] $DisplayResolution = "1920x1080",
|
||||
[string] $TagName = [DateTimeOffset]::Now.ToUnixTimeSeconds()
|
||||
[string] $TagName = [DateTimeOffset]::Now.ToUnixTimeSeconds(),
|
||||
[string] $Uuid = "4203018E-580F-C1B5-9525-B745CECA79EB"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -45,6 +47,8 @@ function Invoke-EnableAutoLogon {
|
||||
|
||||
$ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName
|
||||
|
||||
Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30
|
||||
|
||||
Write-Host "`t[*] Enable AutoLogon"
|
||||
Enable-AutoLogon -HostName $ipAddress -UserName $TemplateUsername -Password $TemplatePassword
|
||||
|
||||
@@ -58,6 +62,12 @@ function Invoke-EnableAutoLogon {
|
||||
}
|
||||
|
||||
function Invoke-SoftwareUpdate {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $Password
|
||||
)
|
||||
|
||||
if (-not $InstallSoftwareUpdate) {
|
||||
Write-Host "`t[*] Skip installing software updates"
|
||||
return
|
||||
@@ -66,7 +76,7 @@ function Invoke-SoftwareUpdate {
|
||||
$ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName
|
||||
|
||||
# Unenroll Seed
|
||||
Write-Host "`t[*] Reseting the seed before requesting stable versions"
|
||||
Write-Host "`t[*] Resetting the seed before requesting stable versions"
|
||||
Remove-CurrentBetaSeed -HostName $ipAddress | Show-StringWithFormat
|
||||
|
||||
# Install Software Updates
|
||||
@@ -79,26 +89,31 @@ function Invoke-SoftwareUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
# Define the next macOS version
|
||||
$command = "sw_vers"
|
||||
$guestMacosVersion = Invoke-SSHPassCommand -HostName $ipAddress -Command $command
|
||||
switch -regex ($guestMacosVersion[1]) {
|
||||
'12.\d' { $nextOSVersion = 'macOS Ventura|macOS Sonoma' }
|
||||
'13.\d' { $nextOSVersion = 'macOS Sonoma' }
|
||||
}
|
||||
|
||||
Write-Host "`t[*] Fetching Software Updates ready to install on '$TemplateName' VM:"
|
||||
Show-StringWithFormat $newUpdates
|
||||
$listOfNewUpdates = $($($newUpdates.Split("*")).Split("Title").where({$_ -match "Label:"}).Replace("Label: ", '').where({$_ -notmatch $nextOSVersion}))
|
||||
Write-Host "`t[*] Installing Software Updates on '$TemplateName' VM:"
|
||||
Install-SoftwareUpdate -HostName $ipAddress | Show-StringWithFormat
|
||||
|
||||
# Check if Action: restart
|
||||
if ($newUpdates.Contains("Action: restart")) {
|
||||
Write-Host "`t[*] Sleep 60 seconds before the software updates have been installed"
|
||||
Start-Sleep -Seconds 60
|
||||
|
||||
Write-Host "`t[*] Waiting for loginwindow process"
|
||||
Wait-LoginWindow -HostName $ipAddress | Show-StringWithFormat
|
||||
|
||||
# Re-enable AutoLogon after installing a new security software update
|
||||
Invoke-EnableAutoLogon
|
||||
Install-SoftwareUpdate -HostName $ipAddress -listOfUpdates $listOfNewUpdates -Password $Password | Show-StringWithFormat
|
||||
Write-Host "`t[*] Sleep 60 seconds before the software updates have been installed"
|
||||
Start-Sleep -Seconds 60
|
||||
Write-Host "`t[*] Waiting for loginwindow process"
|
||||
Wait-LoginWindow -HostName $ipAddress | Show-StringWithFormat
|
||||
# Re-enable AutoLogon after installing a new security software update
|
||||
Invoke-EnableAutoLogon
|
||||
|
||||
foreach ($newupdate in $listOfNewUpdates) {
|
||||
# Check software updates have been installed
|
||||
$updates = Get-SoftwareUpdate -HostName $ipAddress
|
||||
if ($updates.Contains("Action: restart")) {
|
||||
Write-Host "`t[x] Software updates failed to install: $updates"
|
||||
if ($updates.Contains("Action: restart") -and !($updates -match $nextOSVersion)) {
|
||||
Write-Host "`t[x] Software updates failed to install: "
|
||||
Show-StringWithFormat $updates
|
||||
exit 1
|
||||
}
|
||||
@@ -107,9 +122,19 @@ function Invoke-SoftwareUpdate {
|
||||
Write-Host "`t[*] Show the install history:"
|
||||
$hUpdates = Get-SoftwareUpdateHistory -HostName $ipAddress
|
||||
Show-StringWithFormat $hUpdates
|
||||
|
||||
Write-Host "`t[*] The current macOS version:"
|
||||
$command = "sw_vers"
|
||||
Invoke-SSHPassCommand -HostName $ipAddress -Command $command | Show-StringWithFormat
|
||||
}
|
||||
|
||||
|
||||
function Invoke-UpdateSettings {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $Password
|
||||
)
|
||||
$isConfRequired = $InstallSoftwareUpdate -or $EnableAutoLogon
|
||||
if (-not $isConfRequired) {
|
||||
Write-Host "`t[*] Skip additional configuration"
|
||||
@@ -126,7 +151,7 @@ function Invoke-UpdateSettings {
|
||||
Invoke-EnableAutoLogon
|
||||
|
||||
# Install software updates
|
||||
Invoke-SoftwareUpdate
|
||||
Invoke-SoftwareUpdate -Password $Password
|
||||
|
||||
Write-Host "`t[*] Stopping '$TemplateName' VM"
|
||||
Stop-AnkaVM -VMName $TemplateName
|
||||
@@ -145,19 +170,31 @@ $env:SSHUSER = $TemplateUsername
|
||||
$env:SSHPASS = $TemplatePassword
|
||||
|
||||
Write-Host "`n[#1] Download macOS application installer:"
|
||||
$macOSInstaller = Get-MacOSInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch
|
||||
$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion
|
||||
if ([string]::IsNullOrEmpty($TemplateName)) {
|
||||
$TemplateName = "clean_macos_${shortMacOSVersion}_${DiskSizeGb}gb"
|
||||
$osArch = $(arch)
|
||||
if ($osArch -eq "arm64") {
|
||||
$macOSInstaller = Get-MacOSIPSWInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch
|
||||
$TemplateName = "clean_macos_${shortMacOSVersion}_${osArch}_${DiskSizeGb}gb"
|
||||
} else {
|
||||
$macOSInstaller = Get-MacOSInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch
|
||||
$TemplateName = "clean_macos_${shortMacOSVersion}_${DiskSizeGb}gb"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[#2] Create a VM template:"
|
||||
Write-Host "`t[*] Deleting existed template with name '$TemplateName' before creating a new one"
|
||||
Remove-AnkaVM -VMName $TemplateName
|
||||
|
||||
# Temporary disable VNC for macOS 14
|
||||
# It's probably Anka's bug fixed in 3.3.2
|
||||
if ($shortMacOSVersion -eq "14") {
|
||||
$env:ANKA_CREATE_VNC = 0
|
||||
}
|
||||
|
||||
Write-Host "`t[*] Creating Anka VM template with name '$TemplateName' and '$TemplateUsername' user"
|
||||
Write-Host "`t[*] CPU Count: $CPUCount, RamSize: ${RamSizeGb}G, DiskSizeGb: ${DiskSizeGb}G, InstallerPath: $macOSInstaller, TemplateName: $TemplateName"
|
||||
New-AnkaVMTemplate -InstallerPath $macOSInstaller `
|
||||
New-AnkaVMTemplate -InstallerPath "$macOSInstaller" `
|
||||
-TemplateName $TemplateName `
|
||||
-TemplateUsername $TemplateUsername `
|
||||
-TemplatePassword $TemplatePassword `
|
||||
@@ -166,7 +203,7 @@ New-AnkaVMTemplate -InstallerPath $macOSInstaller `
|
||||
-DiskSizeGb $DiskSizeGb | Show-StringWithFormat
|
||||
|
||||
Write-Host "`n[#3] Configure AutoLogon and/or install software updates:"
|
||||
Invoke-UpdateSettings
|
||||
Invoke-UpdateSettings -Password $TemplatePassword
|
||||
|
||||
Write-Host "`n[#4] Finalization '$TemplateName' configuration and push to the registry:"
|
||||
Write-Host "`t[*] The '$TemplateName' VM status is stopped"
|
||||
@@ -179,6 +216,11 @@ Set-AnkaVMVideoController -VMName $TemplateName -ShortMacOSVersion $ShortMacOSVe
|
||||
Write-Host "`t[*] Setting screen resolution to $DisplayResolution for $TemplateName"
|
||||
Set-AnkaVMDisplayResolution -VMName $TemplateName -DisplayResolution $DisplayResolution
|
||||
|
||||
# Push a VM template (and tag) to the Cloud
|
||||
Write-Host "`t[*] Pushing '$TemplateName' image with '$TagName' tag to the '$RegistryUrl' registry..."
|
||||
Push-AnkaTemplateToRegistry -RegistryUrl $registryUrl -TagName $TagName -TemplateName $TemplateName
|
||||
# Set static UUID
|
||||
Set-AnkaVMUuid -VMName $TemplateName -Uuid $Uuid
|
||||
|
||||
if ($PushToRegistry) {
|
||||
# Push a VM template (and tag) to the Cloud
|
||||
Write-Host "`t[*] Pushing '$TemplateName' image with '$TagName' tag to the '$RegistryUrl' registry..."
|
||||
Push-AnkaTemplateToRegistry -RegistryUrl $registryUrl -TagName $TagName -TemplateName $TemplateName
|
||||
}
|
||||
|
||||
@@ -13,13 +13,48 @@ function Enable-AutoLogon {
|
||||
[string] $Password
|
||||
)
|
||||
|
||||
$url = "https://raw.githubusercontent.com/actions/virtual-environments/main/images/macos/provision/bootstrap-provisioner/setAutoLogin.sh"
|
||||
$url = "https://raw.githubusercontent.com/actions/runner-images/main/images/macos/assets/bootstrap-provisioner/setAutoLogin.sh"
|
||||
$script = Invoke-RestMethod -Uri $url
|
||||
$base64 = [Convert]::ToBase64String($script.ToCharArray())
|
||||
$command = "echo $base64 | base64 --decode > ./setAutoLogin.sh;sudo bash ./setAutoLogin.sh '${UserName}' '${Password}';rm ./setAutoLogin.sh"
|
||||
Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||
}
|
||||
|
||||
function Invoke-SoftwareUpdateArm64 {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $HostName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $Password,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[array] $ListOfUpdates
|
||||
)
|
||||
|
||||
# Define the next macOS version
|
||||
$command = "sw_vers"
|
||||
$guestMacosVersion = Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||
switch -regex ($guestMacosVersion[1]) {
|
||||
'13.\d' { $nextOSVersion = 'Sonoma' }
|
||||
'14.\d' { $nextOSVersion = 'NotYetDefined' }
|
||||
}
|
||||
|
||||
$url = "https://raw.githubusercontent.com/actions/runner-images/main/images/macos/assets/auto-software-update-arm64.exp"
|
||||
$script = Invoke-RestMethod -Uri $url
|
||||
foreach ($update in $listOfUpdates) {
|
||||
if ($update -notmatch "$nextOSVersion") {
|
||||
$updatedScript = $script.Replace("MACOSUPDATE", $($($update.trim()).Replace(" ","\ ")))
|
||||
$base64 = [Convert]::ToBase64String($updatedScript.ToCharArray())
|
||||
$command = "echo $base64 | base64 --decode > ./auto-software-update-arm64.exp;chmod +x ./auto-software-update-arm64.exp; ./auto-software-update-arm64.exp ${Password};rm ./auto-software-update-arm64.exp"
|
||||
Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AvailableVersions {
|
||||
param (
|
||||
[bool] $IsBeta = $false
|
||||
@@ -37,6 +72,90 @@ function Get-AvailableVersions {
|
||||
$allVersions
|
||||
}
|
||||
|
||||
function Get-AvailableIPSWVersions {
|
||||
param (
|
||||
[bool] $IsBeta = $false,
|
||||
[bool] $IsLatest = $true,
|
||||
[string] $MacOSCodeNameOrVersion
|
||||
)
|
||||
|
||||
if ($IsBeta) {
|
||||
$command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --include-betas --latest --export "/Applications/export.json" }
|
||||
} elseif ($IsLatest) {
|
||||
$command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --latest --export "/Applications/export.json" }
|
||||
} else {
|
||||
$command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --export "/Applications/export.json" }
|
||||
}
|
||||
|
||||
$condition = { $LASTEXITCODE -eq 0 }
|
||||
Invoke-WithRetry -Command $command -BreakCondition $condition | Out-Null
|
||||
$softwareList = get-content -Path "/Applications/export.json"
|
||||
$availableBuilds = ($softwareList | ConvertFrom-Json).build
|
||||
if ($null -eq $availableBuilds) {
|
||||
Write-Host "Requested macOS '$MacOSCodeNameOrVersion' version not found in the list of available installers."
|
||||
$command = { mist list firmware "$($MacOSCodeNameOrVersion.split('.')[0])" }
|
||||
Invoke-WithRetry -Command $command -BreakCondition $condition
|
||||
exit 1
|
||||
}
|
||||
return $availableBuilds
|
||||
}
|
||||
|
||||
function Get-MacOSIPSWInstaller {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[version] $MacOSVersion,
|
||||
|
||||
[bool] $DownloadLatestVersion = $false,
|
||||
[bool] $BetaSearch = $false
|
||||
)
|
||||
|
||||
if ($MacOSVersion -eq [version] "12.0") {
|
||||
$MacOSName = "macOS Monterey"
|
||||
} elseif ($MacOSVersion -eq [version] "13.0") {
|
||||
$MacOSName = "macOS Ventura"
|
||||
} elseif ($MacOSVersion -eq [version] "14.0") {
|
||||
$MacOSName = "macOS Sonoma"
|
||||
} else {
|
||||
$MacOSName = $MacOSVersion.ToString()
|
||||
}
|
||||
|
||||
Write-Host "`t[*] Finding available full installers"
|
||||
if ($DownloadLatestVersion -eq $true) {
|
||||
$targetBuild = Get-AvailableIPSWVersions -IsLatest $true -MacOSCodeNameOrVersion $MacOSName
|
||||
Write-Host "`t[*] The 'DownloadLatestVersion' flag is set to true. Latest compatible macOS build of '$MacOSName' is '$targetBuild'"
|
||||
} elseif ($BetaSearch -eq $true) {
|
||||
$targetBuild = Get-AvailableIPSWVersions -IsBeta $true -MacOSCodeNameOrVersion $MacOSName
|
||||
Write-Host "`t[*] The 'BetaSearch' flag is set to true. Latest compatible beta macOS build of '$MacOSName' is '$targetBuild'"
|
||||
} else {
|
||||
$targetBuild = Get-AvailableIPSWVersions -MacOSCodeNameOrVersion $MacOSName -IsLatest $false
|
||||
Write-Host "`t[*] Available compatible macOS builds of '$MacOSName' are: $($targetBuild -join ', ')"
|
||||
if ($targetBuild.Count -gt 1) {
|
||||
Write-Error "`t[*] Please specify the exact build number of macOS you want to install"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$installerPathPattern = "/Applications/Install ${macOSName}*.ipsw"
|
||||
if (Test-Path $installerPathPattern) {
|
||||
$previousInstallerPath = Get-Item -Path $installerPathPattern
|
||||
Write-Host "`t[*] Removing '$previousInstallerPath' installation app before downloading the new one"
|
||||
sudo rm -rf "$previousInstallerPath"
|
||||
}
|
||||
|
||||
# Download macOS installer
|
||||
$installerDir = "/Applications/"
|
||||
$installerName = "Install ${macOSName}.ipsw"
|
||||
Write-Host "`t[*] Requested macOS '$targetBuild' version installer found, fetching it from mist database"
|
||||
Invoke-WithRetry { mist download firmware "$targetBuild" --output-directory $installerDir --firmware-name "$installerName" } { $LASTEXITCODE -eq 0 } | Out-Null
|
||||
if (Test-Path "$installerDir$installerName") {
|
||||
$result = "$installerDir$installerName"
|
||||
} else {
|
||||
Write-Error "`t[*] Requested macOS '$targetBuild' version installer failed to download"
|
||||
exit 1
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-MacOSInstaller {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
@@ -52,11 +171,11 @@ function Get-MacOSInstaller {
|
||||
Write-Host "`t[*] Beta Version requested. Enrolling machine to DeveloperSeed"
|
||||
sudo $seedutil enroll DeveloperSeed | Out-Null
|
||||
} else {
|
||||
Write-Host "`t[*] Reseting the seed before requesting stable versions"
|
||||
Write-Host "`t[*] Resetting the seed before requesting stable versions"
|
||||
sudo $seedutil unenroll | Out-Null
|
||||
}
|
||||
|
||||
# Validate there is no softwareupdate at the moment
|
||||
# Validate there is no software update at the moment
|
||||
Test-SoftwareUpdate
|
||||
|
||||
# Validate availability OSVersion
|
||||
@@ -71,8 +190,8 @@ function Get-MacOSInstaller {
|
||||
Show-StringWithFormat $availableVersions
|
||||
exit 1
|
||||
}
|
||||
Show-StringWithFormat $filteredVersions
|
||||
$osVersions = $filteredVersions.OSVersion | Sort-Object {[version]$_}
|
||||
Show-StringWithFormat $filteredVersions
|
||||
$osVersions = $filteredVersions.OSVersion | Sort-Object { [version]$_ }
|
||||
$MacOSVersion = $osVersions | Select-Object -Last 1
|
||||
Write-Host "`t[*] The 'DownloadLatestVersion' flag is set. Latest macOS version is '$MacOSVersion' now"
|
||||
}
|
||||
@@ -84,26 +203,19 @@ function Get-MacOSInstaller {
|
||||
exit 1
|
||||
}
|
||||
|
||||
$installerPathPattern = "/Applications/Install macOS ${macOSName}*.app"
|
||||
if (Test-Path $installerPathPattern) {
|
||||
$previousInstallerPath = Get-Item -Path $installerPathPattern
|
||||
Write-Host "`t[*] Removing '$previousInstallerPath' installation app before downloading the new one"
|
||||
sudo rm -rf "$previousInstallerPath"
|
||||
}
|
||||
|
||||
# Clear LastRecommendedMajorOSBundleIdentifier to prevent error during fetching updates
|
||||
# Install failed with error: Update not found
|
||||
Update-SoftwareBundle
|
||||
|
||||
# Download macOS installer
|
||||
Write-Host "`t[*] Requested macOS '$MacOSVersion' version installer found, fetching it from Apple Software Update"
|
||||
$result = Invoke-WithRetry { /usr/sbin/softwareupdate --fetch-full-installer --full-installer-version $MacOSVersion } {$LASTEXITCODE -eq 0} | Out-String
|
||||
if (-not $result.Contains("Install finished successfully")) {
|
||||
Write-Host "`t[x] Failed to fetch $MacOSVersion macOS `n$result"
|
||||
Invoke-WithRetry -Command { sudo /usr/local/bin/mist download installer $MacOSVersion application --force --export installer.json --output-directory /Applications } -BreakCondition { $LASTEXITCODE -eq 0 } | Out-Null
|
||||
if (-not(Test-Path installer.json -PathType leaf)) {
|
||||
Write-Host "`t[x] Failed to fetch $MacOSVersion macOS"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$installerPath = (Get-Item -Path $installerPathPattern).FullName
|
||||
$installerPath = (Get-Content installer.json | Out-String | ConvertFrom-Json).options.applicationPath
|
||||
if (-not $installerPath) {
|
||||
Write-Host "`t[x] Path not found using '$installerPathPattern'"
|
||||
exit 1
|
||||
@@ -150,11 +262,20 @@ function Install-SoftwareUpdate {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $HostName
|
||||
[string] $HostName,
|
||||
[array] $listOfUpdates,
|
||||
[string] $Password
|
||||
)
|
||||
|
||||
$command = "sudo /usr/sbin/softwareupdate --all --install --restart --verbose"
|
||||
Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||
# If an update is happening on macOS arm64 we will use the additional tool to install updates.
|
||||
$osArch = $(arch)
|
||||
if ($osArch -eq "arm64") {
|
||||
Invoke-SoftwareUpdateArm64 -HostName $HostName -Password $Password -ListOfUpdates $listOfUpdates
|
||||
} else {
|
||||
foreach ($update in $listOfUpdates) {
|
||||
$command = "sudo /usr/sbin/softwareupdate --restart --verbose --install '$($update.trim())'"
|
||||
Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-SSHPassCommand {
|
||||
@@ -185,7 +306,16 @@ function Invoke-SSHPassCommand {
|
||||
"${env:SSHUSER}@${HostName}"
|
||||
)
|
||||
$sshPassOptions = $sshArg -join " "
|
||||
bash -c "$sshPassOptions \""$Command\"" 2>&1"
|
||||
if ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) {
|
||||
$result = bash -c "$sshPassOptions \""$Command\"" 2>&1"
|
||||
} else {
|
||||
$result = bash -c "$sshPassOptions `"$Command`" 2>&1"
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "There is an error during command execution:`n$result"
|
||||
exit 1
|
||||
}
|
||||
$result
|
||||
}
|
||||
|
||||
function Invoke-WithRetry {
|
||||
@@ -195,14 +325,17 @@ function Invoke-WithRetry {
|
||||
[int] $RetryCount = 20,
|
||||
[int] $Seconds = 60
|
||||
)
|
||||
|
||||
while ($RetryCount -gt 0) {
|
||||
if ($Command) {
|
||||
$result = & $Command
|
||||
}
|
||||
try {
|
||||
if ($Command) {
|
||||
$result = & $Command
|
||||
}
|
||||
|
||||
if (& $BreakCondition) {
|
||||
return $result
|
||||
if (& $BreakCondition) {
|
||||
return $result
|
||||
}
|
||||
} catch {
|
||||
Write-Host "`t [!] Error during command execution: $_"
|
||||
}
|
||||
|
||||
$RetryCount--
|
||||
@@ -223,7 +356,10 @@ function Restart-VMSSH {
|
||||
[string] $HostName
|
||||
)
|
||||
|
||||
$command = "sudo reboot"
|
||||
#
|
||||
# https://unix.stackexchange.com/questions/58271/closing-connection-after-executing-reboot-using-ssh-command
|
||||
#
|
||||
$command = '(sleep 1 && sudo reboot &) && exit'
|
||||
Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||
}
|
||||
|
||||
@@ -232,7 +368,7 @@ function Show-StringWithFormat {
|
||||
[Parameter(ValuefromPipeline)]
|
||||
[object] $string
|
||||
)
|
||||
|
||||
|
||||
process {
|
||||
($string | Out-String).Trim().split("`n") | ForEach-Object { Write-Host "`t $_" }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
jobs:
|
||||
- job: Image_generation
|
||||
displayName: Image Generation (${{ parameters.image_label }})
|
||||
timeoutInMinutes: 720
|
||||
timeoutInMinutes: 1200
|
||||
pool:
|
||||
name: Mac-Cloud Image Generation
|
||||
demands:
|
||||
- PrimaryRackAgent -equals ${{ parameters.primary_agent }}
|
||||
variables:
|
||||
- group: Mac-Cloud Image Generation
|
||||
- group: Mac-Cloud Image Generation Key Vault
|
||||
- group: ${{ parameters.variable_group_name }}
|
||||
|
||||
steps:
|
||||
- pwsh: |
|
||||
@@ -64,6 +65,7 @@ jobs:
|
||||
'Using ssh communicator to connect:'
|
||||
)
|
||||
|
||||
packer init ${{ parameters.template_path }}
|
||||
packer build -on-error=abort `
|
||||
-var="vcenter_server=$(vcenter-server-v2)" `
|
||||
-var="vcenter_username=$(vcenter-username-v2)" `
|
||||
@@ -77,8 +79,8 @@ jobs:
|
||||
-var="github_api_pat=$(github_api_pat)" `
|
||||
-var="build_id=$(VirtualMachineName)" `
|
||||
-var="baseimage_name=${{ parameters.base_image_name }}" `
|
||||
-var="xcode_install_user=$(xcode-installation-user)" `
|
||||
-var="xcode_install_password=$(xcode-installation-password)" `
|
||||
-var="xcode_install_storage_url=$(xcode_install_storage_url)" `
|
||||
-var="xcode_install_sas=$(xcode_install_sas)" `
|
||||
-color=false `
|
||||
${{ parameters.template_path }} `
|
||||
| Where-Object {
|
||||
@@ -94,12 +96,8 @@ jobs:
|
||||
PACKER_LOG_PATH: $(Agent.TempDirectory)/packer-log.txt
|
||||
|
||||
- bash: |
|
||||
echo "Copy image output files"
|
||||
cp -R "images/image-output/software-report/." "$(Build.ArtifactStagingDirectory)/"
|
||||
|
||||
echo "Copy test results"
|
||||
cp -R "images/image-output/tests/." "$(Common.TestResultsDirectory)/"
|
||||
ls $(Common.TestResultsDirectory)
|
||||
echo "Copy software report files"
|
||||
cp -vR "images/image-output/software-report/." "$(Build.ArtifactStagingDirectory)/"
|
||||
|
||||
echo "Put VM name to 'VM_Done_Name' file"
|
||||
echo "$(VirtualMachineName)" > "$(Build.ArtifactStagingDirectory)/VM_Done_Name"
|
||||
@@ -107,7 +105,11 @@ jobs:
|
||||
|
||||
- bash: |
|
||||
cat "$(Build.ArtifactStagingDirectory)/systeminfo.md"
|
||||
displayName: Print software report
|
||||
displayName: Print markdown software report
|
||||
|
||||
- bash: |
|
||||
cat "$(Build.ArtifactStagingDirectory)/systeminfo.json"
|
||||
displayName: Print json software report
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name: macOS-10.15_$(date:yyyyMMdd)$(rev:.r)_unstable
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
displayName: Daily
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
always: true
|
||||
|
||||
trigger: none
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_label: 'macOS Catalina'
|
||||
base_image_name: 'clean-macOS-10.15-380Gb-runner'
|
||||
template_path: 'templates/macOS-10.15.json'
|
||||
target_datastore: 'ds-image'
|
||||
@@ -1,6 +1,6 @@
|
||||
name: macOS-11_$(date:yyyyMMdd)$(rev:.r)_unstable
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
- cron: '45 0 * * *'
|
||||
displayName: Daily
|
||||
branches:
|
||||
include:
|
||||
@@ -14,10 +14,15 @@ pr:
|
||||
include:
|
||||
- main
|
||||
|
||||
variables:
|
||||
- group: Mac-Cloud Image Generation
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_label: 'macOS Big Sur'
|
||||
base_image_name: 'clean-macOS-11-380Gb-runner'
|
||||
template_path: 'templates/macOS-11.json'
|
||||
template_path: 'templates/macOS-11.pkr.hcl'
|
||||
target_datastore: 'ds-image'
|
||||
variable_group_name: 'Mac-Cloud Image Generation'
|
||||
primary_agent: 'true'
|
||||
|
||||
@@ -14,10 +14,15 @@ pr:
|
||||
include:
|
||||
- main
|
||||
|
||||
variables:
|
||||
- group: Mac-Cloud Image Generation
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_label: 'macOS Monterey'
|
||||
base_image_name: 'clean-macOS-12-380Gb-runner'
|
||||
template_path: 'templates/macOS-12.json'
|
||||
template_path: 'templates/macOS-12.pkr.hcl'
|
||||
target_datastore: 'ds-image'
|
||||
variable_group_name: 'Mac-Cloud Image Generation'
|
||||
primary_agent: 'true'
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
name: macOS-11_$(date:yyyyMMdd)$(rev:.r)_unstable
|
||||
schedules:
|
||||
- cron: '45 0 * * 1'
|
||||
displayName: Weekly
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
always: true
|
||||
|
||||
trigger: none
|
||||
|
||||
variables:
|
||||
- group: Mac-Cloud Image Generation
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_label: 'macOS Big Sur'
|
||||
base_image_name: 'clean-macOS-11-380Gb-runner'
|
||||
template_path: 'templates/macOS-11.pkr.hcl'
|
||||
target_datastore: 'ds-image'
|
||||
variable_group_name: 'Mac-Cloud Secondary Image Generation'
|
||||
primary_agent: 'false'
|
||||
@@ -0,0 +1,23 @@
|
||||
name: macOS-12_$(date:yyyyMMdd)$(rev:.r)_unstable
|
||||
schedules:
|
||||
- cron: '45 1 * * 1'
|
||||
displayName: Weekly
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
always: true
|
||||
|
||||
trigger: none
|
||||
|
||||
variables:
|
||||
- group: Mac-Cloud Image Generation
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_label: 'macOS Monterey'
|
||||
base_image_name: 'clean-macOS-12-380Gb-runner'
|
||||
template_path: 'templates/macOS-12.pkr.hcl'
|
||||
target_datastore: 'ds-image'
|
||||
variable_group_name: 'Mac-Cloud Secondary Image Generation'
|
||||
primary_agent: 'false'
|
||||
@@ -41,6 +41,8 @@ param(
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VIPassword,
|
||||
|
||||
[string]$JobStatus,
|
||||
|
||||
[int32]$CpuCount,
|
||||
|
||||
[int32]$CoresPerSocketCount,
|
||||
@@ -62,9 +64,9 @@ try {
|
||||
}
|
||||
|
||||
$vm = Get-VM $VMName
|
||||
if ($env:AGENT_JOBSTATUS -eq 'Failed') {
|
||||
if (($env:AGENT_JOBSTATUS -and $env:AGENT_JOBSTATUS -eq 'Failed') -or ($JobStatus -and $JobStatus -eq 'failure')) {
|
||||
try {
|
||||
if($vm.PowerState -ne "PoweredOff") {
|
||||
if ($vm.PowerState -ne "PoweredOff") {
|
||||
Stop-VM -VM $vm -Confirm:$false -ErrorAction Stop | Out-Null
|
||||
}
|
||||
Set-VM -VM $vm -Name "${VMName}_failed" -Confirm:$false -ErrorAction Stop | Out-Null
|
||||
@@ -82,11 +84,3 @@ try {
|
||||
Write-Host "##vso[task.LogIssue type=error;]Failed to move VM '$VMName' to target datastore '$TargetDataStore'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Host "Change CPU count to $CpuCount, cores count to $CoresPerSocketCount, amount of RAM to $Memory"
|
||||
$vm | Set-VM -NumCPU $CpuCount -CoresPerSocket $CoresPerSocketCount -MemoryMB $Memory -Confirm:$false -ErrorAction Stop | Out-Null
|
||||
} catch {
|
||||
Write-Host "##vso[task.LogIssue type=error;]Failed to change specs for VM '$VMName'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -36,9 +36,11 @@ param(
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VIPassword,
|
||||
|
||||
[string]$TagCategory = "Busy",
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$Cluster,
|
||||
|
||||
[string]$Cluster
|
||||
[string]$TagCategory = "Busy"
|
||||
)
|
||||
|
||||
# Import helpers module
|
||||
@@ -65,7 +67,7 @@ function Select-DataStore {
|
||||
$availableDatastores = $availableClusterDatastores `
|
||||
| Where-Object { $_.FreeSpaceGB -ge $thresholdInGb } `
|
||||
| Where-Object {
|
||||
$vmOnDatastore = @((Get-ChildItem -Path $_.DatastoreBrowserPath).Name -notmatch "^\.").Count
|
||||
$vmOnDatastore = @((Get-ChildItem -Path $_.DatastoreBrowserPath).Name -notmatch '(^\.|vmkdump)').Count
|
||||
$vmOnDatastore -lt $vmCount } `
|
||||
| Group-Object -Property { $vmOnDatastore }
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
|
||||
This script sets resources for VM
|
||||
|
||||
.PARAMETER VMName
|
||||
VM name to resize (Example "macOS-10.15_20201012.4")
|
||||
|
||||
.PARAMETER VIServer
|
||||
vCenter address (Example "10.0.1.16")
|
||||
|
||||
.PARAMETER VIUserName
|
||||
vCenter username (Example "Administrator")
|
||||
|
||||
.PARAMETER VIPassword
|
||||
vCenter password (Example "12345678")
|
||||
|
||||
.PARAMETER CpuCount
|
||||
Target number of CPUs (Example "3")
|
||||
|
||||
.PARAMETER CoresPerSocketCount
|
||||
Target number of cores per socket (Example "3")
|
||||
|
||||
.PARAMETER Memory
|
||||
Target amount of memory in MB (Example "14336")
|
||||
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VMName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VIServer,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VIUserName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$VIPassword,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[int32]$CpuCount,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[int32]$CoresPerSocketCount,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[int64]$Memory
|
||||
)
|
||||
|
||||
# Import helpers module
|
||||
Import-Module $PSScriptRoot\helpers.psm1 -DisableNameChecking
|
||||
|
||||
# Connection to a vCenter Server system
|
||||
Connect-VCServer -VIServer $VIServer -VIUserName $VIUserName -VIPassword $VIPassword
|
||||
|
||||
$vm = Get-VM $VMName
|
||||
try {
|
||||
Write-Host "Change CPU count to $CpuCount, cores count to $CoresPerSocketCount, amount of RAM to $Memory"
|
||||
$vm | Set-VM -NumCPU $CpuCount -CoresPerSocket $CoresPerSocketCount -MemoryMB $Memory -Confirm:$false -ErrorAction Stop | Out-Null
|
||||
} catch {
|
||||
Write-Host "##vso[task.LogIssue type=error;]Failed to change specs for VM '$VMName'"
|
||||
exit 1
|
||||
}
|
||||
@@ -22,8 +22,8 @@ function Validate-Scripts {
|
||||
return $ScriptWithoutShebangLine
|
||||
}
|
||||
|
||||
$PathUbuntu = "./images/linux/scripts"
|
||||
$PathMacOS = "./images/macos/provision"
|
||||
$PathUbuntu = "./images/ubuntu/scripts"
|
||||
$PathMacOS = "./images/macos"
|
||||
$PatternUbuntu = "#!/bin/bash -e"
|
||||
$PatternMacOS = "#!/bin/bash -e -o pipefail"
|
||||
$ScriptsWithBrokenShebang = @()
|
||||
@@ -34,10 +34,10 @@ if ($ScriptsWithBrokenShebang.Length -gt 0) {
|
||||
$ScriptsWithBrokenShebang | ForEach-Object {
|
||||
Write-Host "##[error] '$_'"
|
||||
}
|
||||
Write-Host "`n`n##[error] Expected shebang for scripts in 'images/linux' folder is '$PatternUbuntu'"
|
||||
Write-Host "`n`n##[error] Expected shebang for scripts in 'images/ubuntu' folder is '$PatternUbuntu'"
|
||||
Write-Host "##[error] Expected shebang for scripts in 'images/macos' folder is '$PatternMacOS'"
|
||||
exit 1
|
||||
else {
|
||||
Write-Host "All scripts have correct shebang."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,397 +0,0 @@
|
||||
| Announcements |
|
||||
|-|
|
||||
| [Ubuntu 22.04 is generally available starting from August, 8](https://github.com/actions/virtual-environments/issues/5998) |
|
||||
| ['virtual-environments' repository name will be changed to 'runner-images' on August, 8th](https://github.com/actions/virtual-environments/issues/5967) |
|
||||
| [[all OSs] Android NDK 21 will be replaced in favor of 25 on August, 1st](https://github.com/actions/virtual-environments/issues/5930) |
|
||||
| [[all OSs] Android ndk-bundle along with old NDK versions will be deprecated on July, 24](https://github.com/actions/virtual-environments/issues/5879) |
|
||||
| [(Public Beta) Ubuntu 22.04 is now available](https://github.com/actions/virtual-environments/issues/5490) |
|
||||
***
|
||||
# Ubuntu 18.04.6 LTS
|
||||
- Linux kernel version: 5.4.0-1086-azure
|
||||
- Image Version: 20220729.1
|
||||
|
||||
## Installed Software
|
||||
### Language and Runtime
|
||||
- Bash 4.4.20(1)-release
|
||||
- Clang 9.0.0
|
||||
- Clang-format 9.0.0
|
||||
- Clang-tidy 9.0.0
|
||||
- Dash 0.5.8-2.10
|
||||
- Erlang 25.0.3 (Eshell 13.0.3)
|
||||
- Erlang rebar3 3.19.0
|
||||
- GNU C++ 7.5.0, 9.4.0, 10.3.0
|
||||
- GNU Fortran 7.5.0, 9.4.0, 10.3.0
|
||||
- Julia 1.7.3
|
||||
- Kotlin 1.7.10-release-333
|
||||
- Mono 6.12.0.182 (apt source repository: https://download.mono-project.com/repo/ubuntu stable-bionic main)
|
||||
- MSBuild 16.10.1.31701 (from /usr/lib/mono/msbuild/15.0/bin/MSBuild.dll)
|
||||
- Node 16.16.0
|
||||
- Perl 5.26.1
|
||||
- Python 2.7.17
|
||||
- Python3 3.6.9
|
||||
- Ruby 2.5.1p57
|
||||
- Swift 5.6.2
|
||||
|
||||
### Package Management
|
||||
- cpan 1.64
|
||||
- Helm 3.9.2
|
||||
- Homebrew 3.5.6
|
||||
- Miniconda 4.12.0
|
||||
- Npm 8.11.0
|
||||
- NuGet 6.2.1.2
|
||||
- Pip 9.0.1
|
||||
- Pip3 9.0.1
|
||||
- Pipx 1.0.0
|
||||
- RubyGems 2.7.6
|
||||
- Vcpkg (build from master \<281d2f2dc>)
|
||||
- Yarn 1.22.19
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------------- | ---------------------- |
|
||||
| CONDA | /usr/share/miniconda |
|
||||
| VCPKG_INSTALLATION_ROOT | /usr/local/share/vcpkg |
|
||||
|
||||
### Project Management
|
||||
- Ant 1.10.5
|
||||
- Gradle 7.5
|
||||
- Maven 3.8.6
|
||||
- Sbt 1.7.1
|
||||
|
||||
### Tools
|
||||
- Ansible 2.11.12
|
||||
- apt-fast 1.9.12
|
||||
- AzCopy 10.16.0 (available by `azcopy` and `azcopy10` aliases)
|
||||
- Bazel 5.2.0
|
||||
- Bazelisk 1.12.0
|
||||
- Bicep 0.8.9
|
||||
- Buildah 1.22.3 (apt source repository: https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable)
|
||||
- CMake 3.23.3
|
||||
- CodeQL Action Bundle 2.10.1
|
||||
- Docker Amazon ECR Credential Helper 0.6.0
|
||||
- Docker Compose v1 1.29.2
|
||||
- Docker Compose v2 2.6.1+azure-1
|
||||
- Docker-Buildx 0.8.2
|
||||
- Docker-Moby Client 20.10.17+azure-1
|
||||
- Docker-Moby Server 20.10.17+azure-1
|
||||
- Git 2.37.1 (apt source repository: ppa:git-core/ppa)
|
||||
- Git LFS 3.2.0 (apt source repository: https://packagecloud.io/install/repositories/github/git-lfs)
|
||||
- Git-ftp 1.3.1
|
||||
- Haveged 1.9.1
|
||||
- Heroku 7.60.2
|
||||
- HHVM (HipHop VM) 4.164.0
|
||||
- jq 1.5
|
||||
- Kind 0.14.0
|
||||
- Kubectl 1.24.3
|
||||
- Kustomize 4.5.6
|
||||
- Leiningen 2.9.8
|
||||
- MediaInfo 17.12
|
||||
- Mercurial 4.5.3
|
||||
- Minikube 1.26.0
|
||||
- n 9.0.0
|
||||
- Newman 5.3.2
|
||||
- nvm 0.39.1
|
||||
- OpenSSL 1.1.1-1ubuntu2.1~18.04.20
|
||||
- Packer 1.8.2
|
||||
- Parcel 2.6.2
|
||||
- PhantomJS 2.1.1
|
||||
- Podman 3.4.2 (apt source repository: https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable)
|
||||
- Pulumi 3.37.1
|
||||
- R 4.2.0
|
||||
- Skopeo 1.5.0 (apt source repository: https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable)
|
||||
- Sphinx Open Source Search Server 2.2.11
|
||||
- SVN 1.9.7
|
||||
- Terraform 1.2.6
|
||||
- yamllint 1.27.1
|
||||
- yq 4.26.1
|
||||
- zstd 1.5.2 (homebrew)
|
||||
|
||||
### CLI Tools
|
||||
- Alibaba Cloud CLI 3.0.124
|
||||
- AWS CLI 2.7.20
|
||||
- AWS CLI Session manager plugin 1.2.339.0
|
||||
- AWS SAM CLI 1.53.0
|
||||
- Azure CLI (azure-cli) 2.38.0 (installation method: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt)
|
||||
- Azure CLI (azure-devops) 0.25.0
|
||||
- GitHub CLI 2.14.3
|
||||
- Google Cloud SDK 369.0.0 (apt source repository: https://packages.cloud.google.com/apt)
|
||||
- Hub CLI 2.14.2
|
||||
- Netlify CLI 10.13.0
|
||||
- OpenShift CLI 4.10.24
|
||||
- ORAS CLI 0.13.0
|
||||
- Vercel CLI 27.3.3
|
||||
|
||||
### Java
|
||||
| Version | Vendor | Environment Variable |
|
||||
| ------------------- | --------------- | -------------------- |
|
||||
| 8.0.332+9 (default) | Eclipse Temurin | JAVA_HOME_8_X64 |
|
||||
| 11.0.16+8 | Eclipse Temurin | JAVA_HOME_11_X64 |
|
||||
| 12.0.2+10 | Adopt OpenJDK | JAVA_HOME_12_X64 |
|
||||
| 17.0.4+8 | Eclipse Temurin | JAVA_HOME_17_X64 |
|
||||
|
||||
### PHP
|
||||
| Tool | Version |
|
||||
| -------- | --------------------------------- |
|
||||
| PHP | 7.2.34 7.3.33 7.4.30 8.0.21 8.1.8 |
|
||||
| Composer | 2.3.10 |
|
||||
| PHPUnit | 8.5.28 |
|
||||
```
|
||||
Both Xdebug and PCOV extensions are installed, but only Xdebug is enabled.
|
||||
```
|
||||
### Haskell
|
||||
- Cabal 3.6.2.0
|
||||
- GHC 9.2.4
|
||||
- GHCup 0.1.17.8
|
||||
- Stack 2.7.5
|
||||
|
||||
### Rust Tools
|
||||
- Cargo 1.62.1
|
||||
- Rust 1.62.1
|
||||
- Rustdoc 1.62.1
|
||||
- Rustup 1.25.1
|
||||
|
||||
#### Packages
|
||||
- Bindgen 0.60.1
|
||||
- Cargo audit 0.17.0
|
||||
- Cargo clippy 0.1.62
|
||||
- Cargo outdated 0.11.1
|
||||
- Cbindgen 0.24.3
|
||||
- Rustfmt 1.4.38
|
||||
|
||||
### Browsers and Drivers
|
||||
- Google Chrome 103.0.5060.134
|
||||
- ChromeDriver 103.0.5060.134
|
||||
- Chromium 103.0.5060.0
|
||||
- Microsoft Edge 103.0.1264.77 (apt source repository: https://packages.microsoft.com/repos/edge)
|
||||
- Microsoft Edge WebDriver 103.0.1264.77
|
||||
- Selenium server 4.3.0
|
||||
- Mozilla Firefox 103.0
|
||||
- Geckodriver 0.31.0
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------- | ----------------------------------- |
|
||||
| CHROMEWEBDRIVER | /usr/local/share/chrome_driver |
|
||||
| EDGEWEBDRIVER | /usr/local/share/edge_driver |
|
||||
| GECKOWEBDRIVER | /usr/local/share/gecko_driver |
|
||||
| SELENIUM_JAR_PATH | /usr/share/java/selenium-server.jar |
|
||||
|
||||
### .NET Core SDK
|
||||
- 3.1.120 3.1.202 3.1.302 3.1.421 5.0.104 5.0.214 5.0.303 5.0.408 6.0.302
|
||||
|
||||
### .NET tools
|
||||
- nbgv 3.5.108+6e793d63d3
|
||||
|
||||
### Databases
|
||||
- MongoDB 5.0.10 (apt source repository: https://repo.mongodb.org/apt/ubuntu)
|
||||
- sqlite3 3.22.0
|
||||
|
||||
#### PostgreSQL
|
||||
- PostgreSQL 14.4 (apt source repository: https://apt.postgresql.org/pub/repos/apt/)
|
||||
- PostgreSQL Server (user:postgres)
|
||||
|
||||
```
|
||||
PostgreSQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start postgresql.service'
|
||||
```
|
||||
#### MySQL
|
||||
- MySQL 5.7.39
|
||||
- MySQL Server (user:root password:root)
|
||||
|
||||
```
|
||||
MySQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'
|
||||
```
|
||||
#### MS SQL Server Client Tools
|
||||
- sqlcmd 17.10.0001.1
|
||||
- SqlPackage 16.0.6161.0
|
||||
|
||||
### Cached Tools
|
||||
#### Go
|
||||
- 1.16.15
|
||||
- 1.17.12
|
||||
- 1.18.4
|
||||
|
||||
#### Node.js
|
||||
- 12.22.12
|
||||
- 14.20.0
|
||||
- 16.16.0
|
||||
|
||||
#### PyPy
|
||||
- 2.7.18 [PyPy 7.3.9]
|
||||
- 3.6.12 [PyPy 7.3.3]
|
||||
- 3.9.12 [PyPy 7.3.9]
|
||||
|
||||
#### Python
|
||||
- 2.7.18
|
||||
- 3.6.15
|
||||
- 3.7.13
|
||||
- 3.8.13
|
||||
- 3.9.13
|
||||
- 3.10.5
|
||||
|
||||
#### Ruby
|
||||
- 2.4.10
|
||||
- 2.5.9
|
||||
- 2.6.10
|
||||
- 2.7.6
|
||||
- 3.0.4
|
||||
- 3.1.2
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value | Architecture |
|
||||
| --------------- | ----------------------------------- | ------------ |
|
||||
| GOROOT_1_16_X64 | /opt/hostedtoolcache/go/1.16.15/x64 | x64 |
|
||||
| GOROOT_1_17_X64 | /opt/hostedtoolcache/go/1.17.12/x64 | x64 |
|
||||
| GOROOT_1_18_X64 | /opt/hostedtoolcache/go/1.18.4/x64 | x64 |
|
||||
|
||||
### PowerShell Tools
|
||||
- PowerShell 7.2.5
|
||||
|
||||
#### PowerShell Modules
|
||||
| Module | Version |
|
||||
| --------------- | ------- |
|
||||
| MarkdownPS | 1.9 |
|
||||
| Microsoft.Graph | 1.10.0 |
|
||||
| Pester | 5.3.3 |
|
||||
|
||||
#### Az PowerShell Modules
|
||||
- 7.5.0 3.1.0.zip 4.4.0.zip 5.9.0.zip 6.6.0.zip
|
||||
|
||||
### Web Servers
|
||||
| Name | Version | ConfigFile | ServiceStatus | ListenPort |
|
||||
| ------- | ------- | ------------------------- | ------------- | ---------- |
|
||||
| apache2 | 2.4.29 | /etc/apache2/apache2.conf | inactive | 80 |
|
||||
| nginx | 1.14.0 | /etc/nginx/nginx.conf | inactive | 80 |
|
||||
|
||||
### Android
|
||||
| Package Name | Version |
|
||||
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Android Command Line Tools | 7.0 |
|
||||
| Android Emulator | 31.2.10 |
|
||||
| Android SDK Build-tools | 33.0.0<br>32.0.0<br>31.0.0<br>30.0.0 30.0.1 30.0.2 30.0.3<br>29.0.0 29.0.1 29.0.2 29.0.3<br>28.0.0 28.0.1 28.0.2 28.0.3<br>27.0.0 27.0.1 27.0.2 27.0.3<br>26.0.0 26.0.1 26.0.2 26.0.3<br>25.0.0 25.0.1 25.0.2 25.0.3<br>24.0.0 24.0.1 24.0.2 24.0.3<br>23.0.1 23.0.2 23.0.3 |
|
||||
| Android SDK Platform-Tools | 33.0.2 |
|
||||
| Android SDK Platforms | android-33 (rev 2)<br>android-32 (rev 1)<br>android-31 (rev 1)<br>android-30 (rev 3)<br>android-29 (rev 5)<br>android-28 (rev 6)<br>android-27 (rev 3)<br>android-26 (rev 2)<br>android-25 (rev 3)<br>android-24 (rev 2)<br>android-23 (rev 3) |
|
||||
| Android SDK Tools | 26.1.1 |
|
||||
| Android Support Repository | 47.0.0 |
|
||||
| CMake | 3.10.2<br>3.18.1<br>3.22.1 |
|
||||
| Google APIs | addon-google_apis-google-21<br>addon-google_apis-google-22<br>addon-google_apis-google-23<br>addon-google_apis-google-24 |
|
||||
| Google Play services | 49 |
|
||||
| Google Repository | 58 |
|
||||
| NDK | 23.2.8568313<br>24.0.8215888<br>25.0.8775105 (default) |
|
||||
| SDK Patch Applier v4 | 1 |
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------------- | ------------------------------------------- |
|
||||
| ANDROID_HOME | /usr/local/lib/android/sdk |
|
||||
| ANDROID_NDK | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_HOME | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_LATEST_HOME | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_ROOT | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_SDK_ROOT | /usr/local/lib/android/sdk |
|
||||
|
||||
### Cached Docker images
|
||||
| Repository:Tag | Digest | Created |
|
||||
| ----------------------- | ------------------------------------------------------------------------ | ---------- |
|
||||
| alpine:3.12 | sha256:c75ac27b49326926b803b9ed43bf088bc220d22556de1bc5f72d742c91398f69 | 2022-04-05 |
|
||||
| alpine:3.13 | sha256:7769c38ce671534a0ddbf98633f17edbbd4a4e99cbc77ef42f9f7b78b6a1c6c2 | 2022-07-19 |
|
||||
| alpine:3.14 | sha256:43eb8f0d8215d8661f745f3d88563c026614d843b668b233bbe1666b6d026f61 | 2022-07-19 |
|
||||
| buildpack-deps:bullseye | sha256:025c1b80248fb37b1f5250aba9e578e1dc24066912d94d3fa37c21bf8ee98b30 | 2022-07-12 |
|
||||
| buildpack-deps:buster | sha256:0adb423c3161e19606330068d8d3d7710f1bb1b2233e6964ba329c79a96babb2 | 2022-07-12 |
|
||||
| buildpack-deps:stretch | sha256:78e995165a5788c2f55aed6e548d8f6c1534830d4310c870408fccb2da8c5b2e | 2022-06-23 |
|
||||
| debian:10 | sha256:0685c900f6e691bdda6980c0ed0779d20183bc58770059b64adb56cb8a3129f0 | 2022-07-12 |
|
||||
| debian:11 | sha256:2ce44bbc00a79113c296d9d25524e15d423b23303fdbbe20190d2f96e0aeb251 | 2022-07-12 |
|
||||
| debian:9 | sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be | 2022-06-23 |
|
||||
| moby/buildkit:latest | sha256:0dc312b04eac1b44cd2cad566deb1e886c753109208affbbec8384f381ff7f38 | 2022-05-06 |
|
||||
| node:12 | sha256:01627afeb110b3054ba4a1405541ca095c8bfca1cb6f2be9479c767a2711879e | 2022-04-20 |
|
||||
| node:12-alpine | sha256:d4b15b3d48f42059a15bd659be60afe21762aae9d6cbea6f124440895c27db68 | 2022-04-11 |
|
||||
| node:14 | sha256:8ae0e9298b6098f6641e35d0488477381f86fce04993a3f8060a7657db8fb928 | 2022-07-12 |
|
||||
| node:14-alpine | sha256:e22ee6a906e823dc592d6022a3f520676575b50320fe2c3916c88acb16f471ce | 2022-07-18 |
|
||||
| node:16 | sha256:4e85818bd0d023d4f9025730dc0640d3d8269e3d1a84ce6365eca8fbad7a3ee9 | 2022-07-12 |
|
||||
| node:16-alpine | sha256:aadb411a5d398d2141f36a61f469ab91b971e43988d6c74aa5204986e5fe18a1 | 2022-07-18 |
|
||||
| ubuntu:16.04 | sha256:20858ebbc96215d6c3c574f781133ebffdc7c18d98af4f294cc4c04871a6fe61 | 2021-08-31 |
|
||||
| ubuntu:18.04 | sha256:478caf1bec1afd54a58435ec681c8755883b7eb843a8630091890130b15a79af | 2022-06-06 |
|
||||
| ubuntu:20.04 | sha256:fd92c36d3cb9b1d027c4d2a72c6bf0125da82425fc2ca37c414d4f010180dc19 | 2022-06-06 |
|
||||
|
||||
### Installed apt packages
|
||||
| Name | Version |
|
||||
| ----------------- | --------------------------------- |
|
||||
| aria2 | 1.33.1-1 |
|
||||
| autoconf | 2.69-11 |
|
||||
| automake | 1:1.15.1-3ubuntu2 |
|
||||
| binutils | 2.30-21ubuntu1\~18.04.7 |
|
||||
| bison | 2:3.0.4.dfsg-1build1 |
|
||||
| brotli | 1.0.3-1ubuntu1.3 |
|
||||
| build-essential | 12.4ubuntu1 |
|
||||
| bzip2 | 1.0.6-8.1ubuntu0.2 |
|
||||
| coreutils | 8.28-1ubuntu1 |
|
||||
| curl | 7.58.0-2ubuntu3.19 |
|
||||
| dbus | 1.12.2-1ubuntu1.3 |
|
||||
| dnsutils | 1:9.11.3+dfsg-1ubuntu1.17 |
|
||||
| dpkg | 1.19.0.5ubuntu2.4 |
|
||||
| fakeroot | 1.22-2ubuntu1 |
|
||||
| file | 1:5.32-2ubuntu0.4 |
|
||||
| flex | 2.6.4-6 |
|
||||
| ftp | 0.17-34 |
|
||||
| gnupg2 | 2.2.4-1ubuntu1.6 |
|
||||
| haveged | 1.9.1-6 |
|
||||
| imagemagick | 8:6.9.7.4+dfsg-16ubuntu6.13 |
|
||||
| iproute2 | 4.15.0-2ubuntu1.3 |
|
||||
| iputils-ping | 3:20161105-1ubuntu3 |
|
||||
| jq | 1.5+dfsg-2 |
|
||||
| lib32z1 | 1:1.2.11.dfsg-0ubuntu2.1 |
|
||||
| libc++-dev | 6.0-2 |
|
||||
| libc++abi-dev | 6.0-2 |
|
||||
| libcurl3 | 7.58.0-2ubuntu3.19 |
|
||||
| libgbm-dev | 20.0.8-0ubuntu1\~18.04.1 |
|
||||
| libgconf-2-4 | 3.2.6-4ubuntu1 |
|
||||
| libgsl-dev | 2.4+dfsg-6 |
|
||||
| libgtk-3-0 | 3.22.30-1ubuntu4 |
|
||||
| libmagic-dev | 1:5.32-2ubuntu0.4 |
|
||||
| libmagickcore-dev | 8:6.9.7.4+dfsg-16ubuntu6.13 |
|
||||
| libmagickwand-dev | 8:6.9.7.4+dfsg-16ubuntu6.13 |
|
||||
| libsecret-1-dev | 0.18.6-1 |
|
||||
| libsqlite3-dev | 3.22.0-1ubuntu0.5 |
|
||||
| libtool | 2.4.6-2 |
|
||||
| libunwind8 | 1.2.1-8 |
|
||||
| libxkbfile-dev | 1:1.0.9-2 |
|
||||
| libxss1 | 1:1.2.2-1 |
|
||||
| locales | 2.27-3ubuntu1.6 |
|
||||
| m4 | 1.4.18-1 |
|
||||
| mediainfo | 17.12-1 |
|
||||
| mercurial | 4.5.3-1ubuntu2.2 |
|
||||
| net-tools | 1.60+git20161116.90da8a0-1ubuntu1 |
|
||||
| netcat | 1.10-41.1 |
|
||||
| openssh-client | 1:7.6p1-4ubuntu0.7 |
|
||||
| p7zip-full | 16.02+dfsg-6 |
|
||||
| p7zip-rar | 16.02-2 |
|
||||
| parallel | 20161222-1 |
|
||||
| pass | 1.7.1-3 |
|
||||
| patchelf | 0.9-1 |
|
||||
| pkg-config | 0.29.1-0ubuntu2 |
|
||||
| pollinate | 4.33-0ubuntu1\~18.04.2 |
|
||||
| python-setuptools | 39.0.1-2 |
|
||||
| rpm | 4.14.1+dfsg1-2 |
|
||||
| rsync | 3.1.2-2.1ubuntu1.4 |
|
||||
| shellcheck | 0.4.6-1 |
|
||||
| sphinxsearch | 2.2.11-2 |
|
||||
| sqlite3 | 3.22.0-1ubuntu0.5 |
|
||||
| ssh | 1:7.6p1-4ubuntu0.7 |
|
||||
| sshpass | 1.06-1 |
|
||||
| subversion | 1.9.7-4ubuntu1.1 |
|
||||
| sudo | 1.8.21p2-3ubuntu1.4 |
|
||||
| swig | 3.0.12-1 |
|
||||
| telnet | 0.17-41 |
|
||||
| texinfo | 6.5.0.dfsg.1-2 |
|
||||
| time | 1.7-25.1build1 |
|
||||
| tk | 8.6.0+9 |
|
||||
| tzdata | 2022a-0ubuntu0.18.04 |
|
||||
| unzip | 6.0-21ubuntu1.1 |
|
||||
| upx | 3.94-4 |
|
||||
| wget | 1.19.4-1ubuntu2.2 |
|
||||
| xorriso | 1.4.8-3 |
|
||||
| xvfb | 2:1.19.6-1ubuntu4.11 |
|
||||
| xz-utils | 5.2.2-1.3ubuntu0.1 |
|
||||
| zip | 3.0-11build1 |
|
||||
| zsync | 0.6.2-3ubuntu1 |
|
||||
|
||||
|
||||
@@ -1,407 +0,0 @@
|
||||
| Announcements |
|
||||
|-|
|
||||
| [Ubuntu 22.04 is generally available starting from August, 8](https://github.com/actions/virtual-environments/issues/5998) |
|
||||
| ['virtual-environments' repository name will be changed to 'runner-images' on August, 8th](https://github.com/actions/virtual-environments/issues/5967) |
|
||||
| [[all OSs] Android NDK 21 will be replaced in favor of 25 on August, 1st](https://github.com/actions/virtual-environments/issues/5930) |
|
||||
| [[all OSs] Android ndk-bundle along with old NDK versions will be deprecated on July, 24](https://github.com/actions/virtual-environments/issues/5879) |
|
||||
| [(Public Beta) Ubuntu 22.04 is now available](https://github.com/actions/virtual-environments/issues/5490) |
|
||||
***
|
||||
# Ubuntu 20.04.4 LTS
|
||||
- Linux kernel version: 5.15.0-1014-azure
|
||||
- Image Version: 20220729.1
|
||||
|
||||
## Installed Software
|
||||
### Language and Runtime
|
||||
- Bash 5.0.17(1)-release
|
||||
- Clang 10.0.0, 11.0.0, 12.0.0
|
||||
- Clang-format 10.0.0, 11.0.0, 12.0.0
|
||||
- Clang-tidy 10.0.0, 11.0.0, 12.0.0
|
||||
- Dash 0.5.10.2-6
|
||||
- Erlang 25.0.3 (Eshell 13.0.3)
|
||||
- Erlang rebar3 3.19.0
|
||||
- GNU C++ 9.4.0, 10.3.0
|
||||
- GNU Fortran 9.4.0, 10.3.0
|
||||
- Julia 1.7.3
|
||||
- Kotlin 1.7.10-release-333
|
||||
- Mono 6.12.0.182 (apt source repository: https://download.mono-project.com/repo/ubuntu stable-focal main)
|
||||
- MSBuild 16.10.1.31701 (from /usr/lib/mono/msbuild/15.0/bin/MSBuild.dll)
|
||||
- Node 16.16.0
|
||||
- Perl 5.30.0
|
||||
- Python 3.8.10
|
||||
- Python3 3.8.10
|
||||
- Ruby 2.7.0p0
|
||||
- Swift 5.6.2
|
||||
|
||||
### Package Management
|
||||
- cpan 1.64
|
||||
- Helm 3.9.2
|
||||
- Homebrew 3.5.6
|
||||
- Miniconda 4.12.0
|
||||
- Npm 8.11.0
|
||||
- NuGet 6.2.1.2
|
||||
- Pip 20.0.2
|
||||
- Pip3 20.0.2
|
||||
- Pipx 1.1.0
|
||||
- RubyGems 3.1.2
|
||||
- Vcpkg (build from master \<281d2f2dc>)
|
||||
- Yarn 1.22.19
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------------- | ---------------------- |
|
||||
| CONDA | /usr/share/miniconda |
|
||||
| VCPKG_INSTALLATION_ROOT | /usr/local/share/vcpkg |
|
||||
|
||||
### Project Management
|
||||
- Ant 1.10.7
|
||||
- Gradle 7.5
|
||||
- Lerna 5.3.0
|
||||
- Maven 3.8.6
|
||||
- Sbt 1.7.1
|
||||
|
||||
### Tools
|
||||
- Ansible 2.13.2
|
||||
- apt-fast 1.9.12
|
||||
- AzCopy 10.16.0 (available by `azcopy` and `azcopy10` aliases)
|
||||
- Bazel 5.2.0
|
||||
- Bazelisk 1.12.0
|
||||
- Bicep 0.8.9
|
||||
- Buildah 1.22.3 (apt source repository: https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable)
|
||||
- CMake 3.23.3
|
||||
- CodeQL Action Bundle 2.10.1
|
||||
- Docker Amazon ECR Credential Helper 0.6.0
|
||||
- Docker Compose v1 1.29.2
|
||||
- Docker Compose v2 2.6.1+azure-1
|
||||
- Docker-Buildx 0.8.2
|
||||
- Docker-Moby Client 20.10.17+azure-1
|
||||
- Docker-Moby Server 20.10.17+azure-1
|
||||
- Fastlane 2.208.0
|
||||
- Git 2.37.1 (apt source repository: ppa:git-core/ppa)
|
||||
- Git LFS 3.2.0 (apt source repository: https://packagecloud.io/install/repositories/github/git-lfs)
|
||||
- Git-ftp 1.6.0
|
||||
- Haveged 1.9.1
|
||||
- Heroku 7.60.2
|
||||
- HHVM (HipHop VM) 4.165.0
|
||||
- jq 1.6
|
||||
- Kind 0.14.0
|
||||
- Kubectl 1.24.3
|
||||
- Kustomize 4.5.6
|
||||
- Leiningen 2.9.8
|
||||
- MediaInfo 19.09
|
||||
- Mercurial 5.3.1
|
||||
- Minikube 1.26.0
|
||||
- n 9.0.0
|
||||
- Newman 5.3.2
|
||||
- nvm 0.39.1
|
||||
- OpenSSL 1.1.1f-1ubuntu2.16
|
||||
- Packer 1.8.2
|
||||
- Parcel 2.6.2
|
||||
- PhantomJS 2.1.1
|
||||
- Podman 3.4.2 (apt source repository: https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable)
|
||||
- Pulumi 3.37.1
|
||||
- R 4.2.1
|
||||
- Skopeo 1.5.0 (apt source repository: https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable)
|
||||
- Sphinx Open Source Search Server 2.2.11
|
||||
- SVN 1.13.0
|
||||
- Terraform 1.2.6
|
||||
- yamllint 1.27.1
|
||||
- yq 4.26.1
|
||||
- zstd 1.5.2 (homebrew)
|
||||
|
||||
### CLI Tools
|
||||
- Alibaba Cloud CLI 3.0.124
|
||||
- AWS CLI 2.7.20
|
||||
- AWS CLI Session manager plugin 1.2.339.0
|
||||
- AWS SAM CLI 1.53.0
|
||||
- Azure CLI (azure-cli) 2.38.0 (installation method: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt)
|
||||
- Azure CLI (azure-devops) 0.25.0
|
||||
- GitHub CLI 2.14.3
|
||||
- Google Cloud SDK 369.0.0 (apt source repository: https://packages.cloud.google.com/apt)
|
||||
- Hub CLI 2.14.2
|
||||
- Netlify CLI 10.13.0
|
||||
- OpenShift CLI 4.10.24
|
||||
- ORAS CLI 0.13.0
|
||||
- Vercel CLI 27.3.3
|
||||
|
||||
### Java
|
||||
| Version | Vendor | Environment Variable |
|
||||
| ------------------- | --------------- | -------------------- |
|
||||
| 8.0.332+9 | Eclipse Temurin | JAVA_HOME_8_X64 |
|
||||
| 11.0.16+8 (default) | Eclipse Temurin | JAVA_HOME_11_X64 |
|
||||
| 17.0.4+8 | Eclipse Temurin | JAVA_HOME_17_X64 |
|
||||
|
||||
### GraalVM
|
||||
| Version | Environment variables |
|
||||
| --------- | --------------------- |
|
||||
| CE 22.2.0 | GRAALVM_11_ROOT |
|
||||
|
||||
### PHP
|
||||
| Tool | Version |
|
||||
| -------- | ------------------- |
|
||||
| PHP | 7.4.30 8.0.21 8.1.8 |
|
||||
| Composer | 2.3.10 |
|
||||
| PHPUnit | 8.5.28 |
|
||||
```
|
||||
Both Xdebug and PCOV extensions are installed, but only Xdebug is enabled.
|
||||
```
|
||||
### Haskell
|
||||
- Cabal 3.6.2.0
|
||||
- GHC 9.2.4
|
||||
- GHCup 0.1.17.8
|
||||
- Stack 2.7.5
|
||||
|
||||
### Rust Tools
|
||||
- Cargo 1.62.1
|
||||
- Rust 1.62.1
|
||||
- Rustdoc 1.62.1
|
||||
- Rustup 1.25.1
|
||||
|
||||
#### Packages
|
||||
- Bindgen 0.60.1
|
||||
- Cargo audit 0.17.0
|
||||
- Cargo clippy 0.1.62
|
||||
- Cargo outdated 0.11.1
|
||||
- Cbindgen 0.24.3
|
||||
- Rustfmt 1.4.38
|
||||
|
||||
### Browsers and Drivers
|
||||
- Google Chrome 103.0.5060.134
|
||||
- ChromeDriver 103.0.5060.134
|
||||
- Chromium 103.0.5060.0
|
||||
- Microsoft Edge 103.0.1264.77 (apt source repository: https://packages.microsoft.com/repos/edge)
|
||||
- Microsoft Edge WebDriver 103.0.1264.77
|
||||
- Selenium server 4.3.0
|
||||
- Mozilla Firefox 103.0
|
||||
- Geckodriver 0.31.0
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------- | ----------------------------------- |
|
||||
| CHROMEWEBDRIVER | /usr/local/share/chrome_driver |
|
||||
| EDGEWEBDRIVER | /usr/local/share/edge_driver |
|
||||
| GECKOWEBDRIVER | /usr/local/share/gecko_driver |
|
||||
| SELENIUM_JAR_PATH | /usr/share/java/selenium-server.jar |
|
||||
|
||||
### .NET Core SDK
|
||||
- 3.1.120 3.1.202 3.1.302 3.1.421 5.0.104 5.0.214 5.0.303 5.0.408 6.0.302
|
||||
|
||||
### .NET tools
|
||||
- nbgv 3.5.108+6e793d63d3
|
||||
|
||||
### Databases
|
||||
- MongoDB 5.0.10 (apt source repository: https://repo.mongodb.org/apt/ubuntu)
|
||||
- sqlite3 3.31.1
|
||||
|
||||
#### PostgreSQL
|
||||
- PostgreSQL 14.4 (apt source repository: https://apt.postgresql.org/pub/repos/apt/)
|
||||
- PostgreSQL Server (user:postgres)
|
||||
|
||||
```
|
||||
PostgreSQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start postgresql.service'
|
||||
```
|
||||
#### MySQL
|
||||
- MySQL 8.0.30-0ubuntu0.20.04.2
|
||||
- MySQL Server (user:root password:root)
|
||||
|
||||
```
|
||||
MySQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'
|
||||
```
|
||||
#### MS SQL Server Client Tools
|
||||
- sqlcmd 17.10.0001.1
|
||||
- SqlPackage 16.0.6161.0
|
||||
|
||||
### Cached Tools
|
||||
#### Go
|
||||
- 1.16.15
|
||||
- 1.17.12
|
||||
- 1.18.4
|
||||
|
||||
#### Node.js
|
||||
- 12.22.12
|
||||
- 14.20.0
|
||||
- 16.16.0
|
||||
|
||||
#### PyPy
|
||||
- 2.7.18 [PyPy 7.3.9]
|
||||
- 3.6.12 [PyPy 7.3.3]
|
||||
- 3.7.13 [PyPy 7.3.9]
|
||||
- 3.8.13 [PyPy 7.3.9]
|
||||
- 3.9.12 [PyPy 7.3.9]
|
||||
|
||||
#### Python
|
||||
- 2.7.18
|
||||
- 3.6.15
|
||||
- 3.7.13
|
||||
- 3.8.13
|
||||
- 3.9.13
|
||||
- 3.10.5
|
||||
|
||||
#### Ruby
|
||||
- 2.5.9
|
||||
- 2.6.10
|
||||
- 2.7.6
|
||||
- 3.0.4
|
||||
- 3.1.2
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value | Architecture |
|
||||
| --------------- | ----------------------------------- | ------------ |
|
||||
| GOROOT_1_16_X64 | /opt/hostedtoolcache/go/1.16.15/x64 | x64 |
|
||||
| GOROOT_1_17_X64 | /opt/hostedtoolcache/go/1.17.12/x64 | x64 |
|
||||
| GOROOT_1_18_X64 | /opt/hostedtoolcache/go/1.18.4/x64 | x64 |
|
||||
|
||||
### PowerShell Tools
|
||||
- PowerShell 7.2.5
|
||||
|
||||
#### PowerShell Modules
|
||||
| Module | Version |
|
||||
| ---------------- | ------- |
|
||||
| MarkdownPS | 1.9 |
|
||||
| Microsoft.Graph | 1.10.0 |
|
||||
| Pester | 5.3.3 |
|
||||
| PSScriptAnalyzer | 1.20.0 |
|
||||
|
||||
#### Az PowerShell Modules
|
||||
- 7.5.0 3.1.0.zip 4.4.0.zip 5.9.0.zip 6.6.0.zip
|
||||
|
||||
### Web Servers
|
||||
| Name | Version | ConfigFile | ServiceStatus | ListenPort |
|
||||
| --------- | ------- | ------------------------- | ------------- | ---------- |
|
||||
| apache2 | 2.4.41 | /etc/apache2/apache2.conf | inactive | 80 |
|
||||
| mono-xsp4 | 4.7.1 | /etc/default/mono-xsp4 | active | 8084 |
|
||||
| nginx | 1.18.0 | /etc/nginx/nginx.conf | inactive | 80 |
|
||||
|
||||
### Android
|
||||
| Package Name | Version |
|
||||
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Android Command Line Tools | 7.0 |
|
||||
| Android Emulator | 31.2.10 |
|
||||
| Android SDK Build-tools | 33.0.0<br>32.0.0<br>31.0.0<br>30.0.0 30.0.1 30.0.2 30.0.3<br>29.0.0 29.0.1 29.0.2 29.0.3<br>28.0.0 28.0.1 28.0.2 28.0.3<br>27.0.0 27.0.1 27.0.2 27.0.3 |
|
||||
| Android SDK Platform-Tools | 33.0.2 |
|
||||
| Android SDK Platforms | android-33 (rev 2)<br>android-32 (rev 1)<br>android-31 (rev 1)<br>android-30 (rev 3)<br>android-29 (rev 5)<br>android-28 (rev 6)<br>android-27 (rev 3) |
|
||||
| Android SDK Tools | 26.1.1 |
|
||||
| Android Support Repository | 47.0.0 |
|
||||
| CMake | 3.10.2<br>3.18.1<br>3.22.1 |
|
||||
| Google Play services | 49 |
|
||||
| Google Repository | 58 |
|
||||
| NDK | 23.2.8568313<br>24.0.8215888<br>25.0.8775105 (default) |
|
||||
| SDK Patch Applier v4 | 1 |
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------------- | ------------------------------------------- |
|
||||
| ANDROID_HOME | /usr/local/lib/android/sdk |
|
||||
| ANDROID_NDK | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_HOME | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_LATEST_HOME | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_ROOT | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_SDK_ROOT | /usr/local/lib/android/sdk |
|
||||
|
||||
### Cached Docker images
|
||||
| Repository:Tag | Digest | Created |
|
||||
| ----------------------- | ------------------------------------------------------------------------ | ---------- |
|
||||
| alpine:3.12 | sha256:c75ac27b49326926b803b9ed43bf088bc220d22556de1bc5f72d742c91398f69 | 2022-04-05 |
|
||||
| alpine:3.13 | sha256:7769c38ce671534a0ddbf98633f17edbbd4a4e99cbc77ef42f9f7b78b6a1c6c2 | 2022-07-19 |
|
||||
| alpine:3.14 | sha256:43eb8f0d8215d8661f745f3d88563c026614d843b668b233bbe1666b6d026f61 | 2022-07-19 |
|
||||
| buildpack-deps:bullseye | sha256:025c1b80248fb37b1f5250aba9e578e1dc24066912d94d3fa37c21bf8ee98b30 | 2022-07-12 |
|
||||
| buildpack-deps:buster | sha256:0adb423c3161e19606330068d8d3d7710f1bb1b2233e6964ba329c79a96babb2 | 2022-07-12 |
|
||||
| buildpack-deps:stretch | sha256:78e995165a5788c2f55aed6e548d8f6c1534830d4310c870408fccb2da8c5b2e | 2022-06-23 |
|
||||
| debian:10 | sha256:0685c900f6e691bdda6980c0ed0779d20183bc58770059b64adb56cb8a3129f0 | 2022-07-12 |
|
||||
| debian:11 | sha256:2ce44bbc00a79113c296d9d25524e15d423b23303fdbbe20190d2f96e0aeb251 | 2022-07-12 |
|
||||
| debian:9 | sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be | 2022-06-23 |
|
||||
| moby/buildkit:latest | sha256:0dc312b04eac1b44cd2cad566deb1e886c753109208affbbec8384f381ff7f38 | 2022-05-06 |
|
||||
| node:12 | sha256:01627afeb110b3054ba4a1405541ca095c8bfca1cb6f2be9479c767a2711879e | 2022-04-20 |
|
||||
| node:12-alpine | sha256:d4b15b3d48f42059a15bd659be60afe21762aae9d6cbea6f124440895c27db68 | 2022-04-11 |
|
||||
| node:14 | sha256:8ae0e9298b6098f6641e35d0488477381f86fce04993a3f8060a7657db8fb928 | 2022-07-12 |
|
||||
| node:14-alpine | sha256:e22ee6a906e823dc592d6022a3f520676575b50320fe2c3916c88acb16f471ce | 2022-07-18 |
|
||||
| node:16 | sha256:4e85818bd0d023d4f9025730dc0640d3d8269e3d1a84ce6365eca8fbad7a3ee9 | 2022-07-12 |
|
||||
| node:16-alpine | sha256:aadb411a5d398d2141f36a61f469ab91b971e43988d6c74aa5204986e5fe18a1 | 2022-07-18 |
|
||||
| ubuntu:16.04 | sha256:20858ebbc96215d6c3c574f781133ebffdc7c18d98af4f294cc4c04871a6fe61 | 2021-08-31 |
|
||||
| ubuntu:18.04 | sha256:478caf1bec1afd54a58435ec681c8755883b7eb843a8630091890130b15a79af | 2022-06-06 |
|
||||
| ubuntu:20.04 | sha256:fd92c36d3cb9b1d027c4d2a72c6bf0125da82425fc2ca37c414d4f010180dc19 | 2022-06-06 |
|
||||
|
||||
### Installed apt packages
|
||||
| Name | Version |
|
||||
| ---------------------- | --------------------------------- |
|
||||
| acl | 2.2.53-6 |
|
||||
| aria2 | 1.35.0-1build1 |
|
||||
| autoconf | 2.69-11.1 |
|
||||
| automake | 1:1.16.1-4ubuntu6 |
|
||||
| binutils | 2.34-6ubuntu1.3 |
|
||||
| bison | 2:3.5.1+dfsg-1 |
|
||||
| brotli | 1.0.7-6ubuntu0.1 |
|
||||
| build-essential | 12.8ubuntu1.1 |
|
||||
| bzip2 | 1.0.8-2 |
|
||||
| coreutils | 8.30-3ubuntu2 |
|
||||
| curl | 7.68.0-1ubuntu2.12 |
|
||||
| dbus | 1.12.16-2ubuntu2.2 |
|
||||
| dnsutils | 1:9.16.1-0ubuntu2.10 |
|
||||
| dpkg | 1.19.7ubuntu3.2 |
|
||||
| fakeroot | 1.24-1 |
|
||||
| file | 1:5.38-4 |
|
||||
| flex | 2.6.4-6.2 |
|
||||
| fonts-noto-color-emoji | 0\~20200916-1\~ubuntu20.04.1 |
|
||||
| ftp | 0.17-34.1 |
|
||||
| gnupg2 | 2.2.19-3ubuntu2.2 |
|
||||
| haveged | 1.9.1-6ubuntu1 |
|
||||
| imagemagick | 8:6.9.10.23+dfsg-2.1ubuntu11.4 |
|
||||
| iproute2 | 5.5.0-1ubuntu1 |
|
||||
| iputils-ping | 3:20190709-3 |
|
||||
| jq | 1.6-1ubuntu0.20.04.1 |
|
||||
| lib32z1 | 1:1.2.11.dfsg-2ubuntu1.3 |
|
||||
| libc++-dev | 1:10.0-50\~exp1 |
|
||||
| libc++abi-dev | 1:10.0-50\~exp1 |
|
||||
| libcurl4 | 7.68.0-1ubuntu2.12 |
|
||||
| libgbm-dev | 21.2.6-0ubuntu0.1\~20.04.2 |
|
||||
| libgconf-2-4 | 3.2.6-6ubuntu1 |
|
||||
| libgsl-dev | 2.5+dfsg-6build1 |
|
||||
| libgtk-3-0 | 3.24.20-0ubuntu1.1 |
|
||||
| libmagic-dev | 1:5.38-4 |
|
||||
| libmagickcore-dev | 8:6.9.10.23+dfsg-2.1ubuntu11.4 |
|
||||
| libmagickwand-dev | 8:6.9.10.23+dfsg-2.1ubuntu11.4 |
|
||||
| libsecret-1-dev | 0.20.4-0ubuntu1 |
|
||||
| libsqlite3-dev | 3.31.1-4ubuntu0.3 |
|
||||
| libtool | 2.4.6-14 |
|
||||
| libunwind8 | 1.2.1-9build1 |
|
||||
| libxkbfile-dev | 1:1.1.0-1 |
|
||||
| libxss1 | 1:1.2.3-1 |
|
||||
| locales | 2.31-0ubuntu9.9 |
|
||||
| m4 | 1.4.18-4 |
|
||||
| mediainfo | 19.09-1build1 |
|
||||
| mercurial | 5.3.1-1ubuntu1 |
|
||||
| net-tools | 1.60+git20180626.aebd88e-1ubuntu1 |
|
||||
| netcat | 1.206-1ubuntu1 |
|
||||
| openssh-client | 1:8.2p1-4ubuntu0.5 |
|
||||
| p7zip-full | 16.02+dfsg-7build1 |
|
||||
| p7zip-rar | 16.02-3build1 |
|
||||
| parallel | 20161222-1.1 |
|
||||
| pass | 1.7.3-2 |
|
||||
| patchelf | 0.10-2build1 |
|
||||
| pkg-config | 0.29.1-0ubuntu4 |
|
||||
| pollinate | 4.33-3ubuntu1.20.04.1 |
|
||||
| python-is-python3 | 3.8.2-4 |
|
||||
| rpm | 4.14.2.1+dfsg1-1build2 |
|
||||
| rsync | 3.1.3-8ubuntu0.3 |
|
||||
| shellcheck | 0.7.0-2build2 |
|
||||
| sphinxsearch | 2.2.11-2ubuntu2 |
|
||||
| sqlite3 | 3.31.1-4ubuntu0.3 |
|
||||
| ssh | 1:8.2p1-4ubuntu0.5 |
|
||||
| sshpass | 1.06-1 |
|
||||
| subversion | 1.13.0-3ubuntu0.2 |
|
||||
| sudo | 1.8.31-1ubuntu1.2 |
|
||||
| swig | 4.0.1-5build1 |
|
||||
| telnet | 0.17-41.2build1 |
|
||||
| texinfo | 6.7.0.dfsg.2-5 |
|
||||
| time | 1.7-25.1build1 |
|
||||
| tk | 8.6.9+1 |
|
||||
| tzdata | 2022a-0ubuntu0.20.04 |
|
||||
| unzip | 6.0-25ubuntu1 |
|
||||
| upx | 3.95-2build1 |
|
||||
| wget | 1.20.3-1ubuntu2 |
|
||||
| xorriso | 1.5.2-1 |
|
||||
| xvfb | 2:1.20.13-1ubuntu1\~20.04.3 |
|
||||
| xz-utils | 5.2.4-1ubuntu1.1 |
|
||||
| zip | 3.0-11build1 |
|
||||
| zsync | 0.6.2-3ubuntu1 |
|
||||
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
| Announcements |
|
||||
|-|
|
||||
| [[All OSs] Go version 1.16 will be removed on August, 15th](https://github.com/actions/virtual-environments/issues/6024) |
|
||||
| [The Ubuntu 18.04 Actions runner image will begin deprecation on 8/8/22 and will be fully unsupported by 12/1/22](https://github.com/actions/virtual-environments/issues/6002) |
|
||||
| [Ubuntu 22.04 is generally available starting from August, 8](https://github.com/actions/virtual-environments/issues/5998) |
|
||||
| ['virtual-environments' repository name will be changed to 'runner-images' on August, 8th](https://github.com/actions/virtual-environments/issues/5967) |
|
||||
| [[all OSs] Android NDK 21 will be replaced in favor of 25 on August, 1st](https://github.com/actions/virtual-environments/issues/5930) |
|
||||
***
|
||||
# Ubuntu 22.04.1 LTS
|
||||
- Linux kernel version: 5.15.0-1014-azure
|
||||
- Image Version: 20220807.1
|
||||
|
||||
## Installed Software
|
||||
### Language and Runtime
|
||||
- Bash 5.1.16(1)-release
|
||||
- Clang 12.0.1, 13.0.1, 14.0.0
|
||||
- Clang-format 12.0.1, 13.0.1, 14.0.0
|
||||
- Clang-tidy 12.0.1, 13.0.1, 14.0.0
|
||||
- Dash 0.5.11+git20210903+057cd650a4ed-3build1
|
||||
- GNU C++ 9.4.0, 10.3.0, 11.2.0
|
||||
- GNU Fortran 9.4.0, 10.3.0, 11.2.0
|
||||
- Julia 1.7.3
|
||||
- Kotlin 1.7.10-release-333
|
||||
- Mono 6.12.0.182 (apt source repository: https://download.mono-project.com/repo/ubuntu stable-focal main)
|
||||
- MSBuild 16.10.1.31701 (from /usr/lib/mono/msbuild/15.0/bin/MSBuild.dll)
|
||||
- Node 16.16.0
|
||||
- Perl 5.34.0
|
||||
- Python 3.10.4
|
||||
- Python3 3.10.4
|
||||
- Ruby 3.0.2p107
|
||||
|
||||
### Package Management
|
||||
- cpan 1.64
|
||||
- Helm 3.9.2
|
||||
- Homebrew 3.5.8
|
||||
- Miniconda 4.12.0
|
||||
- Npm 8.11.0
|
||||
- NuGet 6.2.1.2
|
||||
- Pip 22.0.2
|
||||
- Pip3 22.0.2
|
||||
- Pipx 1.1.0
|
||||
- RubyGems 3.3.5
|
||||
- Vcpkg (build from master \<9b22b40c6>)
|
||||
- Yarn 1.22.19
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------------- | ---------------------- |
|
||||
| CONDA | /usr/share/miniconda |
|
||||
| VCPKG_INSTALLATION_ROOT | /usr/local/share/vcpkg |
|
||||
|
||||
### Project Management
|
||||
- Lerna 5.3.0
|
||||
|
||||
### Tools
|
||||
- Ansible 2.13.2
|
||||
- apt-fast 1.9.12
|
||||
- AzCopy 10.16.0 (available by `azcopy` and `azcopy10` aliases)
|
||||
- Bazel 5.2.0
|
||||
- Bazelisk 1.12.0
|
||||
- Bicep 0.9.1
|
||||
- Buildah 1.23.1
|
||||
- CMake 3.24.0
|
||||
- CodeQL Action Bundle 2.10.2
|
||||
- Docker Amazon ECR Credential Helper 0.6.0
|
||||
- Docker Compose v1 1.29.2
|
||||
- Docker Compose v2 2.9.0+azure-1
|
||||
- Docker-Buildx 0.8.2
|
||||
- Docker-Moby Client 20.10.17+azure-1
|
||||
- Docker-Moby Server 20.10.17+azure-1
|
||||
- Fastlane 2.208.0
|
||||
- Git 2.37.1 (apt source repository: ppa:git-core/ppa)
|
||||
- Git LFS 3.2.0 (apt source repository: https://packagecloud.io/install/repositories/github/git-lfs)
|
||||
- Git-ftp 1.6.0
|
||||
- Haveged 1.9.14
|
||||
- Heroku 7.62.0
|
||||
- jq 1.6
|
||||
- Kind 0.14.0
|
||||
- Kubectl 1.24.3
|
||||
- Kustomize 4.5.7
|
||||
- Leiningen 2.9.9
|
||||
- MediaInfo 21.09
|
||||
- Mercurial 6.1.1
|
||||
- Minikube 1.26.1
|
||||
- n 9.0.0
|
||||
- Newman 5.3.2
|
||||
- nvm 0.39.1
|
||||
- OpenSSL 3.0.2-0ubuntu1.6
|
||||
- Packer 1.8.3
|
||||
- Parcel 2.7.0
|
||||
- Podman 3.4.4
|
||||
- Pulumi 3.37.2
|
||||
- R 4.2.1
|
||||
- Skopeo 1.4.1
|
||||
- Sphinx Open Source Search Server 2.2.11
|
||||
- SVN 1.14.1
|
||||
- Terraform 1.2.6
|
||||
- yamllint 1.27.1
|
||||
- yq 4.27.2
|
||||
- zstd 1.5.2 (homebrew)
|
||||
|
||||
### CLI Tools
|
||||
- Alibaba Cloud CLI 3.0.124
|
||||
- AWS CLI 2.7.21
|
||||
- AWS CLI Session manager plugin 1.2.339.0
|
||||
- AWS SAM CLI 1.53.0
|
||||
- Azure CLI (azure-cli) 2.39.0 (installation method: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt)
|
||||
- Azure CLI (azure-devops) 0.25.0
|
||||
- GitHub CLI 2.14.3
|
||||
- Google Cloud SDK 369.0.0 (apt source repository: https://packages.cloud.google.com/apt)
|
||||
- Hub CLI 2.14.2
|
||||
- Netlify CLI 10.15.0
|
||||
- OpenShift CLI 4.10.25
|
||||
- ORAS CLI 0.13.0
|
||||
- Vercel CLI 27.3.7
|
||||
|
||||
### Java
|
||||
| Version | Vendor | Environment Variable |
|
||||
| ------------------- | --------------- | -------------------- |
|
||||
| 8.0.332+9 | Eclipse Temurin | JAVA_HOME_8_X64 |
|
||||
| 11.0.16+8 (default) | Eclipse Temurin | JAVA_HOME_11_X64 |
|
||||
| 17.0.4+8 | Eclipse Temurin | JAVA_HOME_17_X64 |
|
||||
|
||||
### GraalVM
|
||||
| Version | Environment variables |
|
||||
| --------- | --------------------- |
|
||||
| CE 22.2.0 | GRAALVM_11_ROOT |
|
||||
|
||||
### PHP
|
||||
| Tool | Version |
|
||||
| -------- | ------- |
|
||||
| PHP | 8.1.8 |
|
||||
| Composer | 2.3.10 |
|
||||
| PHPUnit | 8.5.28 |
|
||||
```
|
||||
Both Xdebug and PCOV extensions are installed, but only Xdebug is enabled.
|
||||
```
|
||||
### Haskell
|
||||
- Cabal 3.6.2.0
|
||||
- GHC 9.2.4
|
||||
- GHCup 0.1.18.0
|
||||
- Stack 2.7.5
|
||||
|
||||
### Rust Tools
|
||||
- Cargo 1.62.1
|
||||
- Rust 1.62.1
|
||||
- Rustdoc 1.62.1
|
||||
- Rustup 1.25.1
|
||||
|
||||
#### Packages
|
||||
- Bindgen 0.60.1
|
||||
- Cargo audit 0.17.0
|
||||
- Cargo clippy 0.1.62
|
||||
- Cargo outdated 0.11.1
|
||||
- Cbindgen 0.24.3
|
||||
- Rustfmt 1.4.38
|
||||
|
||||
### Browsers and Drivers
|
||||
- Google Chrome 104.0.5112.79
|
||||
- ChromeDriver 104.0.5112.79
|
||||
- Chromium 104.0.5112.0
|
||||
- Microsoft Edge 104.0.1293.47 (apt source repository: https://packages.microsoft.com/repos/edge)
|
||||
- Microsoft Edge WebDriver 104.0.1293.47
|
||||
- Selenium server 4.3.0
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------- | ----------------------------------- |
|
||||
| CHROMEWEBDRIVER | /usr/local/share/chrome_driver |
|
||||
| EDGEWEBDRIVER | /usr/local/share/edge_driver |
|
||||
| GECKOWEBDRIVER | |
|
||||
| SELENIUM_JAR_PATH | /usr/share/java/selenium-server.jar |
|
||||
|
||||
### .NET Core SDK
|
||||
- 6.0.302
|
||||
|
||||
### .NET tools
|
||||
- nbgv 3.5.109+bdfbd7dea6
|
||||
|
||||
### Databases
|
||||
- sqlite3 3.37.2
|
||||
|
||||
#### PostgreSQL
|
||||
- PostgreSQL 14.4 (apt source repository: https://apt.postgresql.org/pub/repos/apt/)
|
||||
- PostgreSQL Server (user:postgres)
|
||||
|
||||
```
|
||||
PostgreSQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start postgresql.service'
|
||||
```
|
||||
#### MySQL
|
||||
- MySQL 8.0.30-0ubuntu0.22.04.1
|
||||
- MySQL Server (user:root password:root)
|
||||
|
||||
```
|
||||
MySQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'
|
||||
```
|
||||
### Cached Tools
|
||||
#### Go
|
||||
- 1.17.13
|
||||
- 1.18.5
|
||||
- 1.19.0
|
||||
|
||||
#### Node.js
|
||||
- 14.20.0
|
||||
- 16.16.0
|
||||
|
||||
#### PyPy
|
||||
- 3.7.13 [PyPy 7.3.9]
|
||||
- 3.8.13 [PyPy 7.3.9]
|
||||
- 3.9.12 [PyPy 7.3.9]
|
||||
|
||||
#### Python
|
||||
- 3.7.13
|
||||
- 3.8.13
|
||||
- 3.9.13
|
||||
- 3.10.6
|
||||
|
||||
#### Ruby
|
||||
- 3.1.2
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value | Architecture |
|
||||
| --------------- | ----------------------------------- | ------------ |
|
||||
| GOROOT_1_17_X64 | /opt/hostedtoolcache/go/1.17.13/x64 | x64 |
|
||||
| GOROOT_1_18_X64 | /opt/hostedtoolcache/go/1.18.5/x64 | x64 |
|
||||
| GOROOT_1_19_X64 | /opt/hostedtoolcache/go/1.19.0/x64 | x64 |
|
||||
|
||||
### PowerShell Tools
|
||||
- PowerShell 7.2.5
|
||||
|
||||
#### PowerShell Modules
|
||||
| Module | Version |
|
||||
| ---------------- | ------- |
|
||||
| MarkdownPS | 1.9 |
|
||||
| Microsoft.Graph | 1.11.0 |
|
||||
| Pester | 5.3.3 |
|
||||
| PSScriptAnalyzer | 1.20.0 |
|
||||
|
||||
#### Az PowerShell Modules
|
||||
- 7.5.0
|
||||
|
||||
### Web Servers
|
||||
| Name | Version | ConfigFile | ServiceStatus | ListenPort |
|
||||
| ------- | ------- | ------------------------- | ------------- | ---------- |
|
||||
| apache2 | 2.4.52 | /etc/apache2/apache2.conf | inactive | 80 |
|
||||
| nginx | 1.18.0 | /etc/nginx/nginx.conf | inactive | 80 |
|
||||
|
||||
### Android
|
||||
| Package Name | Version |
|
||||
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Android Command Line Tools | 7.0 |
|
||||
| Android Emulator | 31.2.10 |
|
||||
| Android SDK Build-tools | 33.0.0<br>32.0.0<br>31.0.0<br>30.0.0 30.0.1 30.0.2 30.0.3<br>29.0.0 29.0.1 29.0.2 29.0.3<br>28.0.0 28.0.1 28.0.2 28.0.3<br>27.0.0 27.0.1 27.0.2 27.0.3 |
|
||||
| Android SDK Platform-Tools | 33.0.2 |
|
||||
| Android SDK Platforms | android-33 (rev 2)<br>android-32 (rev 1)<br>android-31 (rev 1)<br>android-30 (rev 3)<br>android-29 (rev 5)<br>android-28 (rev 6)<br>android-27 (rev 3) |
|
||||
| Android SDK Tools | 26.1.1 |
|
||||
| Android Support Repository | 47.0.0 |
|
||||
| CMake | 3.10.2<br>3.18.1<br>3.22.1 |
|
||||
| Google Play services | 49 |
|
||||
| Google Repository | 58 |
|
||||
| NDK | 23.2.8568313<br>24.0.8215888<br>25.0.8775105 (default) |
|
||||
| SDK Patch Applier v4 | 1 |
|
||||
|
||||
#### Environment variables
|
||||
| Name | Value |
|
||||
| ----------------------- | ------------------------------------------- |
|
||||
| ANDROID_HOME | /usr/local/lib/android/sdk |
|
||||
| ANDROID_NDK | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_HOME | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_LATEST_HOME | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_NDK_ROOT | /usr/local/lib/android/sdk/ndk/25.0.8775105 |
|
||||
| ANDROID_SDK_ROOT | /usr/local/lib/android/sdk |
|
||||
|
||||
### Cached Docker images
|
||||
| Repository:Tag | Digest | Created |
|
||||
| ----------------------- | ------------------------------------------------------------------------ | ---------- |
|
||||
| alpine:3.14 | sha256:43eb8f0d8215d8661f745f3d88563c026614d843b668b233bbe1666b6d026f61 | 2022-07-19 |
|
||||
| alpine:3.15 | sha256:0483110c51a764abe175a8cefb941541ca0bc0f628ce418d3a660338abf4622a | 2022-07-19 |
|
||||
| buildpack-deps:bullseye | sha256:d6974da710854c9c38d49666bd682abd99ef7aacdb92344d42a00f723e63cdb1 | 2022-08-02 |
|
||||
| buildpack-deps:buster | sha256:9d806f4d80f19e3fd315bbe2ca1aa59a6c1ad18aff7b3d9696b407572608d61e | 2022-08-02 |
|
||||
| debian:10 | sha256:fb9654aac57319592f1d51497c62001e7033eddf059355408a0b53f7c71f8d5f | 2022-08-02 |
|
||||
| debian:11 | sha256:82bab30ed448b8e2509aabe21f40f0607d905b7fd0dec72802627a20274eba55 | 2022-08-02 |
|
||||
| moby/buildkit:latest | sha256:0dc312b04eac1b44cd2cad566deb1e886c753109208affbbec8384f381ff7f38 | 2022-05-06 |
|
||||
| node:14 | sha256:7e1eabbcd7cbd0fdc296bc00fb2c80943ed0499c4ca3a148c83944708574dcf6 | 2022-08-02 |
|
||||
| node:14-alpine | sha256:e22ee6a906e823dc592d6022a3f520676575b50320fe2c3916c88acb16f471ce | 2022-07-18 |
|
||||
| node:16 | sha256:1ed1e17ccabb09038cfb8a965337ebcda51ef9e9d32082164c502d44d9731a02 | 2022-08-02 |
|
||||
| node:16-alpine | sha256:1908564153449b1c46b329e6ce2307e226bc566294f822f11c5a8bcef4eeaad7 | 2022-07-18 |
|
||||
| ubuntu:18.04 | sha256:eb1392bbdde63147bc2b4ff1a4053dcfe6d15e4dfd3cce29e9b9f52a4f88bc74 | 2022-08-02 |
|
||||
| ubuntu:20.04 | sha256:af5efa9c28de78b754777af9b4d850112cad01899a5d37d2617bb94dc63a49aa | 2022-08-02 |
|
||||
| ubuntu:22.04 | sha256:34fea4f31bf187bc915536831fd0afc9d214755bf700b5cdb1336c82516d154e | 2022-08-02 |
|
||||
|
||||
### Installed apt packages
|
||||
| Name | Version |
|
||||
| ---------------------- | --------------------------------- |
|
||||
| acl | 2.3.1-1 |
|
||||
| aria2 | 1.36.0-1 |
|
||||
| autoconf | 2.71-2 |
|
||||
| automake | 1:1.16.5-1.3 |
|
||||
| binutils | 2.38-3ubuntu1 |
|
||||
| bison | 2:3.8.2+dfsg-1build1 |
|
||||
| brotli | 1.0.9-2build6 |
|
||||
| build-essential | 12.9ubuntu3 |
|
||||
| bzip2 | 1.0.8-5build1 |
|
||||
| coreutils | 8.32-4.1ubuntu1 |
|
||||
| curl | 7.81.0-1ubuntu1.3 |
|
||||
| dbus | 1.12.20-2ubuntu4 |
|
||||
| dnsutils | 1:9.18.1-1ubuntu1.1 |
|
||||
| dpkg | 1.21.1ubuntu2.1 |
|
||||
| fakeroot | 1.28-1ubuntu1 |
|
||||
| file | 1:5.41-3 |
|
||||
| flex | 2.6.4-8build2 |
|
||||
| fonts-noto-color-emoji | 2.034-1 |
|
||||
| ftp | 20210827-4build1 |
|
||||
| gnupg2 | 2.2.27-3ubuntu2.1 |
|
||||
| haveged | 1.9.14-1ubuntu1 |
|
||||
| imagemagick | 8:6.9.11.60+dfsg-1.3build2 |
|
||||
| iproute2 | 5.15.0-1ubuntu2 |
|
||||
| iputils-ping | 3:20211215-1 |
|
||||
| jq | 1.6-2.1ubuntu3 |
|
||||
| lib32z1 | 1:1.2.11.dfsg-2ubuntu9 |
|
||||
| libc++-dev | 1:14.0-55\~exp2 |
|
||||
| libc++abi-dev | 1:14.0-55\~exp2 |
|
||||
| libcurl4 | 7.81.0-1ubuntu1.3 |
|
||||
| libgbm-dev | 22.0.5-0ubuntu0.1 |
|
||||
| libgconf-2-4 | 3.2.6-7ubuntu2 |
|
||||
| libgsl-dev | 2.7.1+dfsg-3 |
|
||||
| libgtk-3-0 | 3.24.33-1ubuntu2 |
|
||||
| libmagic-dev | 1:5.41-3 |
|
||||
| libmagickcore-dev | 8:6.9.11.60+dfsg-1.3build2 |
|
||||
| libmagickwand-dev | 8:6.9.11.60+dfsg-1.3build2 |
|
||||
| libsecret-1-dev | 0.20.5-2 |
|
||||
| libsqlite3-dev | 3.37.2-2 |
|
||||
| libssl-dev | 3.0.2-0ubuntu1.6 |
|
||||
| libtool | 2.4.6-15build2 |
|
||||
| libunwind8 | 1.3.2-2build2 |
|
||||
| libxkbfile-dev | 1:1.1.0-1build3 |
|
||||
| libxss1 | 1:1.2.3-1build2 |
|
||||
| locales | 2.35-0ubuntu3.1 |
|
||||
| m4 | 1.4.18-5ubuntu2 |
|
||||
| mediainfo | 22.03-1 |
|
||||
| mercurial | 6.1.1-1ubuntu1 |
|
||||
| net-tools | 1.60+git20181103.0eebece-1ubuntu5 |
|
||||
| netcat | 1.218-4ubuntu1 |
|
||||
| openssh-client | 1:8.9p1-3 |
|
||||
| p7zip-full | 16.02+dfsg-8 |
|
||||
| p7zip-rar | 16.02-3build1 |
|
||||
| parallel | 20210822+ds-2 |
|
||||
| pass | 1.7.4-5 |
|
||||
| patchelf | 0.14.3-1 |
|
||||
| pkg-config | 0.29.2-1ubuntu3 |
|
||||
| pollinate | 4.33-3ubuntu2 |
|
||||
| python-is-python3 | 3.9.2-2 |
|
||||
| rpm | 4.17.0+dfsg1-4build1 |
|
||||
| rsync | 3.2.3-8ubuntu3 |
|
||||
| shellcheck | 0.8.0-2 |
|
||||
| sphinxsearch | 2.2.11-8 |
|
||||
| sqlite3 | 3.37.2-2 |
|
||||
| ssh | 1:8.9p1-3 |
|
||||
| sshpass | 1.09-1 |
|
||||
| subversion | 1.14.1-3ubuntu0.22.04.1 |
|
||||
| sudo | 1.9.9-1ubuntu2 |
|
||||
| swig | 4.0.2-1ubuntu1 |
|
||||
| telnet | 0.17-44build1 |
|
||||
| texinfo | 6.8-4build1 |
|
||||
| time | 1.9-0.1build2 |
|
||||
| tk | 8.6.11+1build2 |
|
||||
| tzdata | 2022a-0ubuntu1 |
|
||||
| unzip | 6.0-26ubuntu3 |
|
||||
| upx | 3.96-3 |
|
||||
| wget | 1.21.2-2ubuntu1 |
|
||||
| xorriso | 1.5.4-2 |
|
||||
| xvfb | 2:21.1.3-2ubuntu2.1 |
|
||||
| xz-utils | 5.2.5-2ubuntu1 |
|
||||
| zip | 3.0-12build2 |
|
||||
| zsync | 0.6.2-3ubuntu1 |
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Name of pool supported by this image
|
||||
POOL_NAME="Ubuntu 1804"
|
||||
@@ -1,67 +0,0 @@
|
||||
function Get-ChromeVersion {
|
||||
$googleChromeVersion = google-chrome --version | Take-OutputPart -Part 2
|
||||
return "Google Chrome $googleChromeVersion"
|
||||
}
|
||||
|
||||
function Get-ChromeDriverVersion {
|
||||
$chromeDriverVersion = chromedriver --version | Take-OutputPart -Part 1
|
||||
return "ChromeDriver $chromeDriverVersion"
|
||||
}
|
||||
|
||||
function Get-FirefoxVersion {
|
||||
$firefoxVersion = firefox --version
|
||||
return $firefoxVersion
|
||||
}
|
||||
|
||||
function Get-GeckodriverVersion {
|
||||
$geckodriverVersion = geckodriver --version | Select-Object -First 1 | Take-OutputPart -Part 1
|
||||
return "Geckodriver $geckodriverVersion"
|
||||
}
|
||||
|
||||
function Get-ChromiumVersion {
|
||||
$chromiumVersion = chromium-browser --version | Take-OutputPart -Part 0,1
|
||||
return $chromiumVersion
|
||||
}
|
||||
|
||||
function Get-EdgeVersion {
|
||||
$edgeVersion = (microsoft-edge --version).trim()
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "microsoft-edge"
|
||||
return "$edgeVersion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
|
||||
function Get-EdgeDriverVersion {
|
||||
$edgeDriverVersion = msedgedriver --version | Take-OutputPart -Part 0,1,2,3
|
||||
return $edgeDriverVersion
|
||||
}
|
||||
|
||||
function Get-SeleniumVersion {
|
||||
$seleniumBinaryName = Get-ToolsetValue "selenium.binary_name"
|
||||
$fullSeleniumVersion = (Get-ChildItem "/usr/share/java/${seleniumBinaryName}-*").Name -replace "${seleniumBinaryName}-"
|
||||
return "Selenium server $fullSeleniumVersion"
|
||||
}
|
||||
|
||||
function Build-BrowserWebdriversEnvironmentTable {
|
||||
return @(
|
||||
@{
|
||||
"Name" = "CHROMEWEBDRIVER"
|
||||
"Value" = $env:CHROMEWEBDRIVER
|
||||
},
|
||||
@{
|
||||
"Name" = "EDGEWEBDRIVER"
|
||||
"Value" = $env:EDGEWEBDRIVER
|
||||
},
|
||||
@{
|
||||
"Name" = "GECKOWEBDRIVER"
|
||||
"Value" = $env:GECKOWEBDRIVER
|
||||
},
|
||||
@{
|
||||
"Name" = "SELENIUM_JAR_PATH"
|
||||
"Value" = $env:SELENIUM_JAR_PATH
|
||||
}
|
||||
) | ForEach-Object {
|
||||
[PSCustomObject] @{
|
||||
"Name" = $_.Name
|
||||
"Value" = $_.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
function Get-ToolcacheRubyVersions {
|
||||
$toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "Ruby"
|
||||
return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ }
|
||||
}
|
||||
|
||||
function Get-ToolcachePythonVersions {
|
||||
$toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "Python"
|
||||
return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ }
|
||||
}
|
||||
|
||||
function Get-ToolcachePyPyVersions {
|
||||
$toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "PyPy"
|
||||
Get-ChildItem -Path $toolcachePath -Name | Sort-Object { [Version] $_ } | ForEach-Object {
|
||||
$pypyRootPath = Join-Path $toolcachePath $_ "x64"
|
||||
[string]$pypyVersionOutput = & "$pypyRootPath/bin/python" -c "import sys;print(sys.version)"
|
||||
$pypyVersionOutput -match "^([\d\.]+) \(.+\) \[PyPy ([\d\.]+\S*) .+]$" | Out-Null
|
||||
return "{0} [PyPy {1}]" -f $Matches[1], $Matches[2]
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ToolcacheNodeVersions {
|
||||
$toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "node"
|
||||
return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ }
|
||||
}
|
||||
|
||||
function Get-ToolcacheGoVersions {
|
||||
$toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "go"
|
||||
return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ }
|
||||
}
|
||||
|
||||
function Build-GoEnvironmentTable {
|
||||
return Get-CachedToolInstances -Name "go" -VersionCommand "version" | ForEach-Object {
|
||||
$Version = [System.Version]($_.Version -Split(" "))[0]
|
||||
$Name = "GOROOT_$($Version.major)_$($Version.minor)_X64"
|
||||
$Value = (Get-Item env:\$Name).Value
|
||||
[PSCustomObject] @{
|
||||
"Name" = $Name
|
||||
"Value" = (Get-Item env:\$Name).Value
|
||||
"Architecture" = $_. Architecture
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Build-CachedToolsSection {
|
||||
$output = ""
|
||||
|
||||
$output += New-MDHeader "Go" -Level 4
|
||||
$output += New-MDList -Lines (Get-ToolcacheGoVersions) -Style Unordered
|
||||
|
||||
$output += New-MDHeader "Node.js" -Level 4
|
||||
$output += New-MDList -Lines (Get-ToolcacheNodeVersions) -Style Unordered
|
||||
|
||||
$output += New-MDHeader "PyPy" -Level 4
|
||||
$output += New-MDList -Lines (Get-ToolcachePyPyVersions) -Style Unordered
|
||||
|
||||
$output += New-MDHeader "Python" -Level 4
|
||||
$output += New-MDList -Lines (Get-ToolcachePythonVersions) -Style Unordered
|
||||
|
||||
$output += New-MDHeader "Ruby" -Level 4
|
||||
$output += New-MDList -Lines (Get-ToolcacheRubyVersions) -Style Unordered
|
||||
|
||||
return $output
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
function Get-BashVersion {
|
||||
$version = bash -c 'echo ${BASH_VERSION}'
|
||||
return "Bash $version"
|
||||
}
|
||||
|
||||
function Get-DashVersion {
|
||||
$version = dpkg-query -W -f '${Version}' dash
|
||||
return "Dash $version"
|
||||
}
|
||||
|
||||
function Get-CPPVersions {
|
||||
$result = Get-CommandResult "apt list --installed" -Multiline
|
||||
$cppVersions = $result.Output | Where-Object { $_ -match "g\+\+-\d+"} | ForEach-Object {
|
||||
& $_.Split("/")[0] --version | Select-Object -First 1 | Take-OutputPart -Part 3
|
||||
} | Sort-Object {[Version]$_}
|
||||
return "GNU C++ " + ($cppVersions -Join ", ")
|
||||
}
|
||||
|
||||
function Get-FortranVersions {
|
||||
$result = Get-CommandResult "apt list --installed" -Multiline
|
||||
$fortranVersions = $result.Output | Where-Object { $_ -match "^gfortran-\d+"} | ForEach-Object {
|
||||
$_ -match "now (?<version>\d+\.\d+\.\d+)-" | Out-Null
|
||||
$Matches.version
|
||||
} | Sort-Object {[Version]$_}
|
||||
return "GNU Fortran " + ($fortranVersions -Join ", ")
|
||||
}
|
||||
|
||||
function Get-ClangToolVersions {
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $ToolName,
|
||||
[string] $VersionLineMatcher = "${ToolName} version",
|
||||
[string] $VersionPattern = "\d+\.\d+\.\d+)-"
|
||||
)
|
||||
|
||||
$result = Get-CommandResult "apt list --installed" -Multiline
|
||||
$toolVersions = $result.Output | Where-Object { $_ -match "^${ToolName}-\d+"} | ForEach-Object {
|
||||
$clangCommand = ($_ -Split "/")[0]
|
||||
Invoke-Expression "$clangCommand --version" | Where-Object { $_ -match "${VersionLineMatcher}" } | ForEach-Object {
|
||||
$_ -match "${VersionLineMatcher} (?<version>${VersionPattern}" | Out-Null
|
||||
$Matches.version
|
||||
}
|
||||
} | Sort-Object {[Version]$_}
|
||||
|
||||
return $toolVersions -Join ", "
|
||||
}
|
||||
|
||||
function Get-ClangVersions {
|
||||
$clangVersions = Get-ClangToolVersions -ToolName "clang"
|
||||
return "Clang $clangVersions"
|
||||
}
|
||||
|
||||
function Get-ClangFormatVersions {
|
||||
$clangFormatVersions = Get-ClangToolVersions -ToolName "clang-format"
|
||||
return "Clang-format $clangFormatVersions"
|
||||
}
|
||||
|
||||
function Get-ClangTidyVersions {
|
||||
$clangFormatVersions = Get-ClangToolVersions -ToolName "clang-tidy" -VersionLineMatcher "LLVM version" -VersionPattern "\d+\.\d+\.\d+)"
|
||||
return "Clang-tidy $clangFormatVersions"
|
||||
}
|
||||
|
||||
|
||||
function Get-ErlangVersion {
|
||||
$erlangVersion = (erl -eval '{ok, Version} = file:read_file(filename:join([code:root_dir(), "releases", erlang:system_info(otp_release), ''OTP_VERSION''])), io:fwrite(Version), halt().' -noshell)
|
||||
$shellVersion = (erl -eval 'erlang:display(erlang:system_info(version)), halt().' -noshell).Trim('"')
|
||||
return "Erlang $erlangVersion (Eshell $shellVersion)"
|
||||
}
|
||||
|
||||
function Get-ErlangRebar3Version {
|
||||
$result = Get-CommandResult "rebar3 --version"
|
||||
$result.Output -match "rebar (?<version>(\d+.){2}\d+)" | Out-Null
|
||||
$rebarVersion = $Matches.version
|
||||
return "Erlang rebar3 $rebarVersion"
|
||||
}
|
||||
|
||||
function Get-MonoVersion {
|
||||
$monoVersion = mono --version | Out-String | Take-OutputPart -Part 4
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "mono"
|
||||
return "Mono $monoVersion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
|
||||
function Get-MsbuildVersion {
|
||||
$msbuildVersion = msbuild -version | Select-Object -Last 1
|
||||
$result = Select-String -Path (Get-Command msbuild).Source -Pattern "msbuild"
|
||||
$result -match "(?<path>\/\S*\.dll)" | Out-Null
|
||||
$msbuildPath = $Matches.path
|
||||
return "MSBuild $msbuildVersion (from $msbuildPath)"
|
||||
}
|
||||
|
||||
function Get-NuGetVersion {
|
||||
$nugetVersion = nuget help | Select-Object -First 1 | Take-OutputPart -Part 2
|
||||
return "NuGet $nugetVersion"
|
||||
}
|
||||
|
||||
function Get-NodeVersion {
|
||||
$nodeVersion = $(node --version).Substring(1)
|
||||
return "Node $nodeVersion"
|
||||
}
|
||||
|
||||
function Get-OpensslVersion {
|
||||
return "OpenSSL $(dpkg-query -W -f '${Version}' openssl)"
|
||||
}
|
||||
|
||||
function Get-PerlVersion {
|
||||
$version = $(perl -e 'print substr($^V,1)')
|
||||
return "Perl $version"
|
||||
}
|
||||
|
||||
function Get-PythonVersion {
|
||||
$result = Get-CommandResult "python --version"
|
||||
$version = $result.Output | Take-OutputPart -Part 1
|
||||
return "Python $version"
|
||||
}
|
||||
|
||||
function Get-Python3Version {
|
||||
$result = Get-CommandResult "python3 --version"
|
||||
$version = $result.Output | Take-OutputPart -Part 1
|
||||
return "Python3 $version"
|
||||
}
|
||||
|
||||
function Get-PowershellVersion {
|
||||
return $(pwsh --version)
|
||||
}
|
||||
|
||||
function Get-RubyVersion {
|
||||
$rubyVersion = ruby --version | Out-String | Take-OutputPart -Part 1
|
||||
return "Ruby $rubyVersion"
|
||||
}
|
||||
|
||||
function Get-SwiftVersion {
|
||||
$swiftVersion = swift --version | Out-String | Take-OutputPart -Part 2
|
||||
return "Swift $swiftVersion"
|
||||
}
|
||||
|
||||
function Get-KotlinVersion {
|
||||
$kotlinVersion = kotlin -version | Out-String | Take-OutputPart -Part 2
|
||||
return "Kotlin $kotlinVersion"
|
||||
}
|
||||
|
||||
function Get-JuliaVersion {
|
||||
$juliaVersion = julia --version | Take-OutputPart -Part 2
|
||||
return "Julia $juliaVersion"
|
||||
}
|
||||
|
||||
function Get-LernaVersion {
|
||||
$version = lerna -v
|
||||
return "Lerna $version"
|
||||
}
|
||||
|
||||
function Get-HomebrewVersion {
|
||||
$result = Get-CommandResult "brew -v"
|
||||
$result.Output -match "Homebrew (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$version = $Matches.version
|
||||
return "Homebrew $version"
|
||||
}
|
||||
|
||||
function Get-CpanVersion {
|
||||
$result = Get-CommandResult "cpan --version" -ExpectExitCode @(25, 255)
|
||||
$result.Output -match "version (?<version>\d+\.\d+) " | Out-Null
|
||||
$cpanVersion = $Matches.version
|
||||
return "cpan $cpanVersion"
|
||||
}
|
||||
|
||||
function Get-GemVersion {
|
||||
$result = Get-CommandResult "gem --version"
|
||||
$result.Output -match "(?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$gemVersion = $Matches.version
|
||||
return "RubyGems $gemVersion"
|
||||
}
|
||||
|
||||
function Get-MinicondaVersion {
|
||||
$condaVersion = conda --version
|
||||
return "Mini$condaVersion"
|
||||
}
|
||||
|
||||
function Get-HelmVersion {
|
||||
$(helm version) -match 'Version:"v(?<version>\d+\.\d+\.\d+)"' | Out-Null
|
||||
$helmVersion = $Matches.version
|
||||
return "Helm $helmVersion"
|
||||
}
|
||||
|
||||
function Get-NpmVersion {
|
||||
$npmVersion = npm --version
|
||||
return "Npm $npmVersion"
|
||||
}
|
||||
|
||||
function Get-YarnVersion {
|
||||
$yarnVersion = yarn --version
|
||||
return "Yarn $yarnVersion"
|
||||
}
|
||||
|
||||
function Get-ParcelVersion {
|
||||
$parcelVersion = parcel --version
|
||||
return "Parcel $parcelVersion"
|
||||
}
|
||||
|
||||
function Get-PipVersion {
|
||||
$result = Get-CommandResult "pip --version"
|
||||
$result.Output -match "pip (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$pipVersion = $Matches.version
|
||||
return "Pip $pipVersion"
|
||||
}
|
||||
|
||||
function Get-Pip3Version {
|
||||
$result = Get-CommandResult "pip3 --version"
|
||||
$result.Output -match "pip (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$pipVersion = $Matches.version
|
||||
return "Pip3 $pipVersion"
|
||||
}
|
||||
|
||||
function Get-VcpkgVersion {
|
||||
$result = Get-CommandResult "vcpkg version"
|
||||
$result.Output -match "version (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$vcpkgVersion = $Matches.version
|
||||
$commitId = git -C "/usr/local/share/vcpkg" rev-parse --short HEAD
|
||||
return "Vcpkg $vcpkgVersion (build from master \<$commitId>)"
|
||||
}
|
||||
|
||||
function Get-AntVersion {
|
||||
$result = ant -version | Out-String
|
||||
$result -match "version (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$antVersion = $Matches.version
|
||||
return "Ant $antVersion"
|
||||
}
|
||||
|
||||
function Get-GradleVersion {
|
||||
$gradleVersion = (gradle -v) -match "^Gradle \d" | Take-OutputPart -Part 1
|
||||
return "Gradle $gradleVersion"
|
||||
}
|
||||
|
||||
function Get-MavenVersion {
|
||||
$result = mvn -version | Out-String
|
||||
$result -match "Apache Maven (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$mavenVersion = $Matches.version
|
||||
return "Maven $mavenVersion"
|
||||
}
|
||||
|
||||
function Get-SbtVersion {
|
||||
$result = Get-CommandResult "sbt -version"
|
||||
$result.Output -match "sbt script version: (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$sbtVersion = $Matches.version
|
||||
return "Sbt $sbtVersion"
|
||||
}
|
||||
|
||||
function Get-PHPVersions {
|
||||
$result = Get-CommandResult "apt list --installed" -Multiline
|
||||
return $result.Output | Where-Object { $_ -match "^php\d+\.\d+/"} | ForEach-Object {
|
||||
$_ -match "now (\d+:)?(?<version>\d+\.\d+\.\d+)-" | Out-Null
|
||||
$Matches.version
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ComposerVersion {
|
||||
$composerVersion = (composer --version) -replace " version" | Take-OutputPart -Part 1
|
||||
return $composerVersion
|
||||
}
|
||||
|
||||
function Get-PHPUnitVersion {
|
||||
$(phpunit --version | Out-String) -match "PHPUnit (?<version>\d+\.\d+\.\d+)\s" | Out-Null
|
||||
return $Matches.version
|
||||
}
|
||||
|
||||
function Build-PHPTable {
|
||||
$php = @{
|
||||
"Tool" = "PHP"
|
||||
"Version" = "$(Get-PHPVersions -Join '<br>')"
|
||||
}
|
||||
$composer = @{
|
||||
"Tool" = "Composer"
|
||||
"Version" = Get-ComposerVersion
|
||||
}
|
||||
$phpunit = @{
|
||||
"Tool" = "PHPUnit"
|
||||
"Version" = Get-PHPUnitVersion
|
||||
}
|
||||
return @($php, $composer, $phpunit) | ForEach-Object {
|
||||
[PSCustomObject] @{
|
||||
"Tool" = $_.Tool
|
||||
"Version" = $_.Version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Build-PHPSection {
|
||||
$output = ""
|
||||
$output += New-MDHeader "PHP" -Level 3
|
||||
$output += Build-PHPTable | New-MDTable
|
||||
$output += New-MDCode -Lines @(
|
||||
"Both Xdebug and PCOV extensions are installed, but only Xdebug is enabled."
|
||||
)
|
||||
|
||||
return $output
|
||||
}
|
||||
|
||||
function Get-GHCVersion {
|
||||
$(ghc --version) -match "version (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$ghcVersion = $Matches.version
|
||||
return "GHC $ghcVersion"
|
||||
}
|
||||
|
||||
function Get-GHCupVersion {
|
||||
$(ghcup --version) -match "version v(?<version>\d+(\.\d+){2,})" | Out-Null
|
||||
$ghcVersion = $Matches.version
|
||||
return "GHCup $ghcVersion"
|
||||
}
|
||||
|
||||
function Get-CabalVersion {
|
||||
$(cabal --version | Out-String) -match "cabal-install version (?<version>\d+\.\d+\.\d+\.\d+)" | Out-Null
|
||||
$cabalVersion = $Matches.version
|
||||
return "Cabal $cabalVersion"
|
||||
}
|
||||
|
||||
function Get-StackVersion {
|
||||
$(stack --version | Out-String) -match "Version (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$stackVersion = $Matches.version
|
||||
return "Stack $stackVersion"
|
||||
}
|
||||
|
||||
function Get-AzModuleVersions {
|
||||
$azModuleVersions = Get-ChildItem /usr/share | Where-Object { $_ -match "az_\d+" } | Foreach-Object {
|
||||
$_.Name.Split("_")[1]
|
||||
}
|
||||
|
||||
$azModuleVersions = $azModuleVersions -join " "
|
||||
return $azModuleVersions
|
||||
}
|
||||
|
||||
function Get-PowerShellModules {
|
||||
$modules = (Get-ToolsetContent).powershellModules.name
|
||||
|
||||
$psModules = Get-Module -Name $modules -ListAvailable | Sort-Object Name | Group-Object Name
|
||||
$psModules | ForEach-Object {
|
||||
$moduleName = $_.Name
|
||||
$moduleVersions = ($_.group.Version | Sort-Object -Unique) -join '<br>'
|
||||
|
||||
[PSCustomObject]@{
|
||||
Module = $moduleName
|
||||
Version = $moduleVersions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-DotNetCoreSdkVersions {
|
||||
$unsortedDotNetCoreSdkVersion = dotnet --list-sdks list | ForEach-Object { $_ | Take-OutputPart -Part 0 }
|
||||
$dotNetCoreSdkVersion = $unsortedDotNetCoreSdkVersion -join " "
|
||||
return $dotNetCoreSdkVersion
|
||||
}
|
||||
|
||||
function Get-DotnetTools {
|
||||
$env:PATH = "/etc/skel/.dotnet/tools:$($env:PATH)"
|
||||
|
||||
$dotnetTools = (Get-ToolsetContent).dotnet.tools
|
||||
|
||||
$toolsList = @()
|
||||
|
||||
ForEach ($dotnetTool in $dotnetTools) {
|
||||
$toolsList += $dotnetTool.name + " " + (Invoke-Expression $dotnetTool.getversion)
|
||||
}
|
||||
|
||||
return $toolsList
|
||||
}
|
||||
|
||||
function Get-CachedDockerImages {
|
||||
$toolsetJson = Get-ToolsetContent
|
||||
$images = $toolsetJson.docker.images
|
||||
return $images
|
||||
}
|
||||
|
||||
function Get-CachedDockerImagesTableData {
|
||||
$allImages = sudo docker images --digests --format "*{{.Repository}}:{{.Tag}}|{{.Digest}} |{{.CreatedAt}}"
|
||||
$allImages.Split("*") | Where-Object { $_ } | ForEach-Object {
|
||||
$parts = $_.Split("|")
|
||||
[PSCustomObject] @{
|
||||
"Repository:Tag" = $parts[0]
|
||||
"Digest" = $parts[1]
|
||||
"Created" = $parts[2].split(' ')[0]
|
||||
}
|
||||
} | Sort-Object -Property "Repository:Tag"
|
||||
}
|
||||
|
||||
function Get-AptPackages {
|
||||
$apt = (Get-ToolsetContent).Apt
|
||||
$output = @()
|
||||
ForEach ($pkg in ($apt.common_packages + $apt.cmd_packages)) {
|
||||
$version = $(dpkg-query -W -f '${Version}' $pkg)
|
||||
if ($Null -eq $version) {
|
||||
$version = $(dpkg-query -W -f '${Version}' "$pkg*")
|
||||
}
|
||||
|
||||
$version = $version -replace '~','\~'
|
||||
|
||||
$output += [PSCustomObject] @{
|
||||
Name = $pkg
|
||||
Version = $version
|
||||
}
|
||||
}
|
||||
return ($output | Sort-Object Name)
|
||||
}
|
||||
|
||||
function Get-PipxVersion {
|
||||
$result = (Get-CommandResult "pipx --version").Output
|
||||
$result -match "(?<version>\d+\.\d+\.\d+\.?\d*)" | Out-Null
|
||||
$pipxVersion = $Matches.Version
|
||||
return "Pipx $pipxVersion"
|
||||
}
|
||||
|
||||
function Get-GraalVMVersion {
|
||||
$version = & "$env:GRAALVM_11_ROOT\bin\java" --version | Select-String -Pattern "GraalVM" | Take-OutputPart -Part 5,6
|
||||
return $version
|
||||
}
|
||||
|
||||
function Build-GraalVMTable {
|
||||
$version = Get-GraalVMVersion
|
||||
$envVariables = "GRAALVM_11_ROOT"
|
||||
|
||||
return [PSCustomObject] @{
|
||||
"Version" = $version
|
||||
"Environment variables" = $envVariables
|
||||
}
|
||||
}
|
||||
|
||||
function Build-PackageManagementEnvironmentTable {
|
||||
return @(
|
||||
@{
|
||||
"Name" = "CONDA"
|
||||
"Value" = $env:CONDA
|
||||
},
|
||||
@{
|
||||
"Name" = "VCPKG_INSTALLATION_ROOT"
|
||||
"Value" = $env:VCPKG_INSTALLATION_ROOT
|
||||
}
|
||||
) | ForEach-Object {
|
||||
[PSCustomObject] @{
|
||||
"Name" = $_.Name
|
||||
"Value" = $_.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
function Get-PostgreSqlVersion {
|
||||
$postgreSQLVersion = psql --version | Take-OutputPart -Part 2
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "postgresql"
|
||||
return "PostgreSQL $postgreSQLVersion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
|
||||
function Get-MongoDbVersion {
|
||||
$mongoDBVersion = mongod --version | Select-Object -First 1 | Take-OutputPart -Part 2 -Delimiter "v"
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "mongodb"
|
||||
return "MongoDB $mongoDBVersion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
|
||||
function Get-SqliteVersion {
|
||||
$sqliteVersion = sqlite3 --version | Take-OutputPart -Part 0
|
||||
return "sqlite3 $sqliteVersion"
|
||||
}
|
||||
|
||||
function Get-MySQLVersion {
|
||||
$mySQLVersion = mysqld --version | Take-OutputPart -Part 2
|
||||
if (Test-IsUbuntu18) {
|
||||
$mySQLVersion = $mySQLVersion | Take-OutputPart -Part 0 -Delimiter "-"
|
||||
}
|
||||
return "MySQL $mySQLVersion"
|
||||
}
|
||||
|
||||
function Get-SQLCmdVersion {
|
||||
$sqlcmdVersion = sqlcmd -? | Select-String -Pattern "Version" | Take-OutputPart -Part 1
|
||||
return "sqlcmd $sqlcmdVersion"
|
||||
}
|
||||
|
||||
function Get-SqlPackageVersion {
|
||||
$sqlPackageVersion = sqlpackage /version
|
||||
return "SqlPackage $sqlPackageVersion"
|
||||
}
|
||||
|
||||
function Build-PostgreSqlSection {
|
||||
$output = ""
|
||||
|
||||
$output += New-MDHeader "PostgreSQL" -Level 4
|
||||
$output += New-MDList -Style Unordered -Lines @(
|
||||
(Get-PostgreSqlVersion ),
|
||||
"PostgreSQL Server (user:postgres)"
|
||||
)
|
||||
$output += New-MDCode -Lines @(
|
||||
"PostgreSQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start postgresql.service'"
|
||||
)
|
||||
|
||||
return $output
|
||||
}
|
||||
|
||||
function Build-MySQLSection {
|
||||
$output = ""
|
||||
|
||||
$output += New-MDHeader "MySQL" -Level 4
|
||||
$output += New-MDList -Style Unordered -Lines @(
|
||||
(Get-MySQLVersion ),
|
||||
"MySQL Server (user:root password:root)"
|
||||
)
|
||||
$output += New-MDCode -Lines @(
|
||||
"MySQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'"
|
||||
)
|
||||
|
||||
return $output
|
||||
}
|
||||
|
||||
function Build-MSSQLToolsSection {
|
||||
$output = ""
|
||||
|
||||
$output += New-MDHeader "MS SQL Server Client Tools" -Level 4
|
||||
$output += New-MDList -Style Unordered -Lines @(
|
||||
(Get-SQLCmdVersion),
|
||||
(Get-SqlPackageVersion)
|
||||
)
|
||||
|
||||
return $output
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
param (
|
||||
[Parameter(Mandatory)][string]
|
||||
$OutputDirectory
|
||||
)
|
||||
|
||||
$global:ErrorActionPreference = "Stop"
|
||||
$global:ErrorView = "NormalView"
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
Import-Module MarkdownPS
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Android.psm1") -DisableNameChecking
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Browsers.psm1") -DisableNameChecking
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.CachedTools.psm1") -DisableNameChecking
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Common.psm1") -DisableNameChecking
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Databases.psm1") -DisableNameChecking
|
||||
Import-Module "$PSScriptRoot/../helpers/SoftwareReport.Helpers.psm1" -DisableNameChecking
|
||||
Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" -DisableNameChecking
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Java.psm1") -DisableNameChecking
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Rust.psm1") -DisableNameChecking
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Tools.psm1") -DisableNameChecking
|
||||
Import-Module (Join-Path $PSScriptRoot "SoftwareReport.WebServers.psm1") -DisableNameChecking
|
||||
|
||||
# Restore file owner in user profile
|
||||
Restore-UserOwner
|
||||
|
||||
$markdown = ""
|
||||
|
||||
$OSName = Get-OSName
|
||||
$markdown += New-MDHeader "$OSName" -Level 1
|
||||
|
||||
$kernelVersion = Get-KernelVersion
|
||||
$markdown += New-MDList -Style Unordered -Lines @(
|
||||
"$kernelVersion"
|
||||
"Image Version: $env:IMAGE_VERSION"
|
||||
)
|
||||
|
||||
$markdown += New-MDHeader "Installed Software" -Level 2
|
||||
$markdown += New-MDHeader "Language and Runtime" -Level 3
|
||||
|
||||
$runtimesList = @(
|
||||
(Get-BashVersion),
|
||||
(Get-DashVersion),
|
||||
(Get-CPPVersions),
|
||||
(Get-FortranVersions),
|
||||
(Get-MsbuildVersion),
|
||||
(Get-MonoVersion),
|
||||
(Get-NodeVersion),
|
||||
(Get-PerlVersion),
|
||||
(Get-PythonVersion),
|
||||
(Get-Python3Version),
|
||||
(Get-RubyVersion),
|
||||
(Get-JuliaVersion),
|
||||
(Get-ClangVersions),
|
||||
(Get-ClangFormatVersions),
|
||||
(Get-ClangTidyVersions),
|
||||
(Get-KotlinVersion)
|
||||
)
|
||||
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$runtimesList += @(
|
||||
(Get-ErlangVersion),
|
||||
(Get-ErlangRebar3Version),
|
||||
(Get-SwiftVersion)
|
||||
)
|
||||
}
|
||||
|
||||
$markdown += New-MDList -Style Unordered -Lines ($runtimesList | Sort-Object)
|
||||
|
||||
$markdown += New-MDHeader "Package Management" -Level 3
|
||||
|
||||
$packageManagementList = @(
|
||||
(Get-HomebrewVersion),
|
||||
(Get-CpanVersion),
|
||||
(Get-GemVersion),
|
||||
(Get-MinicondaVersion),
|
||||
(Get-NuGetVersion),
|
||||
(Get-HelmVersion),
|
||||
(Get-NpmVersion),
|
||||
(Get-YarnVersion),
|
||||
(Get-PipxVersion),
|
||||
(Get-PipVersion),
|
||||
(Get-Pip3Version),
|
||||
(Get-VcpkgVersion)
|
||||
)
|
||||
|
||||
$markdown += New-MDList -Style Unordered -Lines ($packageManagementList | Sort-Object)
|
||||
$markdown += New-MDHeader "Environment variables" -Level 4
|
||||
$markdown += Build-PackageManagementEnvironmentTable | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
|
||||
$markdown += New-MDHeader "Project Management" -Level 3
|
||||
$projectManagementList = @()
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$projectManagementList += @(
|
||||
(Get-AntVersion),
|
||||
(Get-GradleVersion),
|
||||
(Get-MavenVersion),
|
||||
(Get-SbtVersion)
|
||||
)
|
||||
}
|
||||
|
||||
if ((Test-IsUbuntu20) -or (Test-IsUbuntu22)) {
|
||||
$projectManagementList += @(
|
||||
(Get-LernaVersion)
|
||||
)
|
||||
}
|
||||
$markdown += New-MDList -Style Unordered -Lines ($projectManagementList | Sort-Object)
|
||||
|
||||
$markdown += New-MDHeader "Tools" -Level 3
|
||||
$toolsList = @(
|
||||
(Get-AnsibleVersion),
|
||||
(Get-AptFastVersion),
|
||||
(Get-AzCopyVersion),
|
||||
(Get-BazelVersion),
|
||||
(Get-BazeliskVersion),
|
||||
(Get-BicepVersion),
|
||||
(Get-CodeQLBundleVersion),
|
||||
(Get-CMakeVersion),
|
||||
(Get-DockerMobyClientVersion),
|
||||
(Get-DockerMobyServerVersion),
|
||||
(Get-DockerComposeV1Version),
|
||||
(Get-DockerComposeV2Version),
|
||||
(Get-DockerBuildxVersion),
|
||||
(Get-DockerAmazonECRCredHelperVersion),
|
||||
(Get-BuildahVersion),
|
||||
(Get-PodManVersion),
|
||||
(Get-SkopeoVersion),
|
||||
(Get-GitVersion),
|
||||
(Get-GitLFSVersion),
|
||||
(Get-GitFTPVersion),
|
||||
(Get-HavegedVersion),
|
||||
(Get-HerokuVersion),
|
||||
(Get-LeiningenVersion),
|
||||
(Get-SVNVersion),
|
||||
(Get-JqVersion),
|
||||
(Get-YqVersion),
|
||||
(Get-KindVersion),
|
||||
(Get-KubectlVersion),
|
||||
(Get-KustomizeVersion),
|
||||
(Get-MediainfoVersion),
|
||||
(Get-HGVersion),
|
||||
(Get-MinikubeVersion),
|
||||
(Get-NewmanVersion),
|
||||
(Get-NVersion),
|
||||
(Get-NvmVersion),
|
||||
(Get-OpensslVersion),
|
||||
(Get-PackerVersion),
|
||||
(Get-ParcelVersion),
|
||||
(Get-PulumiVersion),
|
||||
(Get-RVersion),
|
||||
(Get-SphinxVersion),
|
||||
(Get-TerraformVersion),
|
||||
(Get-YamllintVersion),
|
||||
(Get-ZstdVersion)
|
||||
)
|
||||
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$toolsList += @(
|
||||
(Get-PhantomJSVersion),
|
||||
(Get-HHVMVersion)
|
||||
)
|
||||
}
|
||||
|
||||
if ((Test-IsUbuntu20) -or (Test-IsUbuntu22)) {
|
||||
$toolsList += (Get-FastlaneVersion)
|
||||
}
|
||||
|
||||
$markdown += New-MDList -Style Unordered -Lines ($toolsList | Sort-Object)
|
||||
|
||||
$markdown += New-MDHeader "CLI Tools" -Level 3
|
||||
$markdown += New-MDList -Style Unordered -Lines (@(
|
||||
(Get-AlibabaCloudCliVersion),
|
||||
(Get-AWSCliVersion),
|
||||
(Get-AWSCliSessionManagerPluginVersion),
|
||||
(Get-AWSSAMVersion),
|
||||
(Get-AzureCliVersion),
|
||||
(Get-AzureDevopsVersion),
|
||||
(Get-GitHubCliVersion),
|
||||
(Get-GoogleCloudSDKVersion),
|
||||
(Get-HubCliVersion),
|
||||
(Get-NetlifyCliVersion),
|
||||
(Get-OCCliVersion),
|
||||
(Get-ORASCliVersion),
|
||||
(Get-VerselCliversion)
|
||||
) | Sort-Object
|
||||
)
|
||||
|
||||
$markdown += New-MDHeader "Java" -Level 3
|
||||
$markdown += Get-JavaVersions | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
|
||||
if ((Test-IsUbuntu20) -or (Test-IsUbuntu22)) {
|
||||
$markdown += New-MDHeader "GraalVM" -Level 3
|
||||
$markdown += Build-GraalVMTable | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
}
|
||||
|
||||
$markdown += Build-PHPSection
|
||||
|
||||
$markdown += New-MDHeader "Haskell" -Level 3
|
||||
$markdown += New-MDList -Style Unordered -Lines (@(
|
||||
(Get-GHCVersion),
|
||||
(Get-GHCupVersion),
|
||||
(Get-CabalVersion),
|
||||
(Get-StackVersion)
|
||||
) | Sort-Object
|
||||
)
|
||||
|
||||
$markdown += New-MDHeader "Rust Tools" -Level 3
|
||||
$markdown += New-MDList -Style Unordered -Lines (@(
|
||||
(Get-RustVersion),
|
||||
(Get-RustupVersion),
|
||||
(Get-RustdocVersion),
|
||||
(Get-CargoVersion)
|
||||
) | Sort-Object
|
||||
)
|
||||
|
||||
$markdown += New-MDHeader "Packages" -Level 4
|
||||
$markdown += New-MDList -Style Unordered -Lines (@(
|
||||
(Get-BindgenVersion),
|
||||
(Get-CargoAuditVersion),
|
||||
(Get-CargoOutdatedVersion),
|
||||
(Get-CargoClippyVersion),
|
||||
(Get-CbindgenVersion),
|
||||
(Get-RustfmtVersion)
|
||||
) | Sort-Object
|
||||
)
|
||||
|
||||
$markdown += New-MDHeader "Browsers and Drivers" -Level 3
|
||||
|
||||
$browsersAndDriversList = @(
|
||||
(Get-ChromeVersion),
|
||||
(Get-ChromeDriverVersion),
|
||||
(Get-ChromiumVersion),
|
||||
(Get-EdgeVersion),
|
||||
(Get-EdgeDriverVersion),
|
||||
(Get-SeleniumVersion)
|
||||
)
|
||||
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$browsersAndDriversList += @(
|
||||
(Get-FirefoxVersion),
|
||||
(Get-GeckodriverVersion)
|
||||
)
|
||||
}
|
||||
|
||||
$markdown += New-MDList -Style Unordered -Lines $browsersAndDriversList
|
||||
$markdown += New-MDHeader "Environment variables" -Level 4
|
||||
$markdown += Build-BrowserWebdriversEnvironmentTable | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
|
||||
$markdown += New-MDHeader ".NET Core SDK" -Level 3
|
||||
$markdown += New-MDList -Style Unordered -Lines @(
|
||||
(Get-DotNetCoreSdkVersions)
|
||||
)
|
||||
|
||||
$markdown += New-MDHeader ".NET tools" -Level 3
|
||||
$tools = Get-DotnetTools
|
||||
$markdown += New-MDList -Lines $tools -Style Unordered
|
||||
|
||||
$markdown += New-MDHeader "Databases" -Level 3
|
||||
$databaseLists = @(
|
||||
(Get-SqliteVersion)
|
||||
)
|
||||
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$databaseLists += @(
|
||||
(Get-MongoDbVersion)
|
||||
)
|
||||
}
|
||||
|
||||
$markdown += New-MDList -Style Unordered -Lines ( $databaseLists | Sort-Object )
|
||||
|
||||
$markdown += Build-PostgreSqlSection
|
||||
$markdown += Build-MySQLSection
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$markdown += Build-MSSQLToolsSection
|
||||
}
|
||||
|
||||
$markdown += New-MDHeader "Cached Tools" -Level 3
|
||||
$markdown += Build-CachedToolsSection
|
||||
|
||||
$markdown += New-MDHeader "Environment variables" -Level 4
|
||||
$markdown += Build-GoEnvironmentTable | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
|
||||
$markdown += New-MDHeader "PowerShell Tools" -Level 3
|
||||
$markdown += New-MDList -Lines (Get-PowershellVersion) -Style Unordered
|
||||
|
||||
$markdown += New-MDHeader "PowerShell Modules" -Level 4
|
||||
$markdown += Get-PowerShellModules | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
$markdown += New-MDHeader "Az PowerShell Modules" -Level 4
|
||||
$markdown += New-MDList -Style Unordered -Lines @(
|
||||
(Get-AzModuleVersions)
|
||||
)
|
||||
|
||||
$markdown += Build-WebServersSection
|
||||
|
||||
$markdown += New-MDHeader "Android" -Level 3
|
||||
$markdown += Build-AndroidTable | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
$markdown += New-MDHeader "Environment variables" -Level 4
|
||||
$markdown += Build-AndroidEnvironmentTable | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
|
||||
$markdown += New-MDHeader "Cached Docker images" -Level 3
|
||||
$markdown += Get-CachedDockerImagesTableData | New-MDTable
|
||||
$markdown += New-MDNewLine
|
||||
|
||||
$markdown += New-MDHeader "Installed apt packages" -Level 3
|
||||
$markdown += Get-AptPackages | New-MDTable
|
||||
|
||||
Test-BlankElement
|
||||
$markdown | Out-File -FilePath "${OutputDirectory}/Ubuntu-Readme.md"
|
||||
@@ -1,22 +0,0 @@
|
||||
function Get-JavaVersions {
|
||||
$javaToolcacheVersions = Get-ChildItem $env:AGENT_TOOLSDIRECTORY/Java*/* -Directory | Sort-Object { [int]$_.Name.Split(".")[0] }
|
||||
|
||||
$existingVersions = $javaToolcacheVersions | ForEach-Object {
|
||||
$majorVersion = $_.Name.split(".")[0]
|
||||
$fullVersion = $_.Name.Replace("-", "+")
|
||||
$defaultJavaPath = $env:JAVA_HOME
|
||||
$javaPath = Get-Item env:JAVA_HOME_${majorVersion}_X64
|
||||
|
||||
$defaultPostfix = ($javaPath.Value -eq $defaultJavaPath) ? " (default)" : ""
|
||||
$vendorName = ($_.FullName -like '*Java_Adopt_jdk*') ? "Adopt OpenJDK" : "Eclipse Temurin"
|
||||
|
||||
[PSCustomObject] @{
|
||||
"Version" = $fullVersion + $defaultPostfix
|
||||
"Vendor" = $vendorName
|
||||
"Environment Variable" = $javaPath.Name
|
||||
}
|
||||
}
|
||||
# Return all the vendors which are not Adopt, also look for version 12 of Adopt (Eclipse Temurin does not have this version)
|
||||
$versionsToReturn = $existingVersions | Where-Object {$_.Vendor -notlike "Adopt*" -or $_.Version.Split(".")[0] -eq 12}
|
||||
return $versionsToReturn
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
function Initialize-RustEnvironment {
|
||||
$env:PATH = "/etc/skel/.cargo/bin:/etc/skel/.rustup/bin:$($env:PATH)"
|
||||
$env:RUSTUP_HOME = "/etc/skel/.rustup"
|
||||
$env:CARGO_HOME = "/etc/skel/.cargo"
|
||||
}
|
||||
|
||||
function Get-RustVersion {
|
||||
Initialize-RustEnvironment
|
||||
$rustVersion = $(rustc --version) | Take-OutputPart -Part 1
|
||||
return "Rust $rustVersion"
|
||||
}
|
||||
|
||||
function Get-BindgenVersion {
|
||||
$bindgenVersion = $(bindgen --version) | Take-OutputPart -Part 1
|
||||
return "Bindgen $bindgenVersion"
|
||||
}
|
||||
|
||||
function Get-CargoVersion {
|
||||
$cargoVersion = $(cargo --version) | Take-OutputPart -Part 1
|
||||
return "Cargo $cargoVersion"
|
||||
}
|
||||
|
||||
function Get-CargoAuditVersion {
|
||||
$cargoAuditVersion = $(cargo-audit --version) | Take-OutputPart -Part 1
|
||||
return "Cargo audit $cargoAuditVersion"
|
||||
}
|
||||
|
||||
function Get-CargoOutdatedVersion {
|
||||
$cargoOutdatedVersion = cargo outdated --version | Take-OutputPart -Part 1
|
||||
return "Cargo outdated $cargoOutdatedVersion"
|
||||
}
|
||||
|
||||
function Get-CargoClippyVersion {
|
||||
$cargoClippyVersion = $(cargo-clippy --version) | Take-OutputPart -Part 1
|
||||
return "Cargo clippy $cargoClippyVersion"
|
||||
}
|
||||
|
||||
function Get-CbindgenVersion {
|
||||
$cbindgenVersion = $(cbindgen --version) | Take-OutputPart -Part 1
|
||||
return "Cbindgen $cbindgenVersion"
|
||||
}
|
||||
|
||||
function Get-RustupVersion {
|
||||
$rustupVersion = $(rustup --version) | Take-OutputPart -Part 1
|
||||
return "Rustup $rustupVersion"
|
||||
}
|
||||
|
||||
function Get-RustdocVersion {
|
||||
$rustdocVersion = $(rustdoc --version) | Take-OutputPart -Part 1
|
||||
return "Rustdoc $rustdocVersion"
|
||||
}
|
||||
|
||||
function Get-RustfmtVersion {
|
||||
$rustfmtVersion = $(rustfmt --version) | Take-OutputPart -Part 1 | Take-OutputPart -Part 0 -Delimiter "-"
|
||||
return "Rustfmt $rustfmtVersion"
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
function Get-AnsibleVersion {
|
||||
$ansibleVersion = (ansible --version)[0] -replace "[^\d.]"
|
||||
return "Ansible $ansibleVersion"
|
||||
}
|
||||
|
||||
function Get-AptFastVersion {
|
||||
$versionFileContent = Get-Content (which apt-fast) -Raw
|
||||
$match = [Regex]::Match($versionFileContent, '# apt-fast v(.+)\n')
|
||||
$aptFastVersion = $match.Groups[1].Value
|
||||
return "apt-fast $aptFastVersion"
|
||||
}
|
||||
|
||||
function Get-AzCopyVersion {
|
||||
$azcopyVersion = azcopy --version | Take-OutputPart -Part 2
|
||||
return "AzCopy $azcopyVersion (available by ``azcopy`` and ``azcopy10`` aliases)"
|
||||
}
|
||||
|
||||
function Get-BazelVersion {
|
||||
$bazelVersion = bazel --version | Select-String "bazel" | Take-OutputPart -Part 1
|
||||
return "Bazel $bazelVersion"
|
||||
}
|
||||
|
||||
function Get-BazeliskVersion {
|
||||
$result = Get-CommandResult "bazelisk version" -Multiline
|
||||
$bazeliskVersion = $result.Output | Select-String "Bazelisk version:" | Take-OutputPart -Part 2 | Take-OutputPart -Part 0 -Delimiter "v"
|
||||
return "Bazelisk $bazeliskVersion"
|
||||
}
|
||||
|
||||
function Get-BicepVersion {
|
||||
(bicep --version | Out-String) -match "bicep cli version (?<version>\d+\.\d+\.\d+)" | Out-Null
|
||||
$bicepVersion = $Matches.Version
|
||||
return "Bicep $bicepVersion"
|
||||
}
|
||||
|
||||
function Get-CodeQLBundleVersion {
|
||||
$CodeQLVersionsWildcard = Join-Path $Env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath "*"
|
||||
$CodeQLVersionPath = Get-ChildItem $CodeQLVersionsWildcard | Select-Object -First 1 -Expand FullName
|
||||
$CodeQLPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "codeql"
|
||||
$CodeQLVersion = & $CodeQLPath version --quiet
|
||||
return "CodeQL Action Bundle $CodeQLVersion"
|
||||
}
|
||||
|
||||
function Get-PodManVersion {
|
||||
$podmanVersion = podman --version | Take-OutputPart -Part 2
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "containers"
|
||||
return "Podman $podmanVersion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
return "Podman $podmanVersion"
|
||||
}
|
||||
|
||||
function Get-BuildahVersion {
|
||||
$buildahVersion = buildah --version | Take-OutputPart -Part 2
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "containers"
|
||||
return "Buildah $buildahVersion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
return "Buildah $buildahVersion"
|
||||
}
|
||||
|
||||
function Get-SkopeoVersion {
|
||||
$skopeoVersion = skopeo --version | Take-OutputPart -Part 2
|
||||
if ((Test-IsUbuntu18) -or (Test-IsUbuntu20)) {
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "containers"
|
||||
return "Skopeo $skopeoVersion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
return "Skopeo $skopeoVersion"
|
||||
}
|
||||
|
||||
function Get-CMakeVersion {
|
||||
$cmakeVersion = cmake --version | Select-Object -First 1 | Take-OutputPart -Part 2
|
||||
return "CMake $cmakeVersion"
|
||||
}
|
||||
|
||||
function Get-DockerComposeV1Version {
|
||||
$composeVersion = docker-compose -v | Take-OutputPart -Part 2 | Take-OutputPart -Part 0 -Delimiter ","
|
||||
return "Docker Compose v1 $composeVersion"
|
||||
}
|
||||
|
||||
function Get-DockerComposeV2Version {
|
||||
$composeVersion = docker compose version | Take-OutputPart -Part 3
|
||||
return "Docker Compose v2 $composeVersion"
|
||||
}
|
||||
|
||||
function Get-DockerMobyClientVersion {
|
||||
$dockerClientVersion = sudo docker version --format '{{.Client.Version}}'
|
||||
return "Docker-Moby Client $dockerClientVersion"
|
||||
}
|
||||
|
||||
function Get-DockerMobyServerVersion {
|
||||
$dockerServerVersion = sudo docker version --format '{{.Server.Version}}'
|
||||
return "Docker-Moby Server $dockerServerVersion"
|
||||
}
|
||||
|
||||
function Get-DockerBuildxVersion {
|
||||
$buildxVersion = docker buildx version | Take-OutputPart -Part 1 | Take-OutputPart -Part 0 -Delimiter "+"
|
||||
return "Docker-Buildx $buildxVersion"
|
||||
}
|
||||
|
||||
function Get-DockerAmazonECRCredHelperVersion {
|
||||
$ecrVersion = docker-credential-ecr-login -v | Select-String "Version:" | Take-OutputPart -Part 1
|
||||
return "Docker Amazon ECR Credential Helper $ecrVersion"
|
||||
}
|
||||
|
||||
function Get-GitVersion {
|
||||
$gitVersion = git --version | Take-OutputPart -Part -1
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "git-core"
|
||||
return "Git $gitVersion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
|
||||
function Get-GitLFSVersion {
|
||||
$result = Get-CommandResult "git-lfs --version"
|
||||
$gitlfsversion = $result.Output | Take-OutputPart -Part 0 | Take-OutputPart -Part 1 -Delimiter "/"
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "git-lfs"
|
||||
return "Git LFS $gitlfsversion (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
|
||||
function Get-GitFTPVersion {
|
||||
$gitftpVersion = git-ftp --version | Take-OutputPart -Part 2
|
||||
return "Git-ftp $gitftpVersion"
|
||||
}
|
||||
|
||||
function Get-GoogleCloudSDKVersion {
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "google-cloud-sdk"
|
||||
return "$(gcloud --version | Select-Object -First 1) (apt source repository: $aptSourceRepo)"
|
||||
}
|
||||
|
||||
function Get-HavegedVersion {
|
||||
$havegedVersion = dpkg-query --showformat='${Version}' --show haveged | Take-OutputPart -Part 0 -Delimiter "-"
|
||||
return "Haveged $havegedVersion"
|
||||
}
|
||||
|
||||
function Get-HerokuVersion {
|
||||
$herokuVersion = heroku version | Take-OutputPart -Part 0 | Take-OutputPart -Part 1 -Delimiter "/"
|
||||
return "Heroku $herokuVersion"
|
||||
}
|
||||
|
||||
function Get-HHVMVersion {
|
||||
$hhvmVersion = hhvm --version | Select-Object -First 1 | Take-OutputPart -Part 2
|
||||
return "HHVM (HipHop VM) $hhvmVersion"
|
||||
}
|
||||
|
||||
function Get-SVNVersion {
|
||||
$svnVersion = svn --version | Select-Object -First 1 | Take-OutputPart -Part 2
|
||||
return "SVN $svnVersion"
|
||||
}
|
||||
|
||||
function Get-KustomizeVersion {
|
||||
$kustomizeVersion = kustomize version --short | Take-OutputPart -Part 0 | Take-OutputPart -Part 1 -Delimiter "v"
|
||||
return "Kustomize $kustomizeVersion"
|
||||
}
|
||||
|
||||
function Get-KindVersion {
|
||||
$kindVersion = kind version | Take-OutputPart -Part 1 | Take-OutputPart -Part 0 -Delimiter "v"
|
||||
return "Kind $kindVersion"
|
||||
}
|
||||
|
||||
function Get-KubectlVersion {
|
||||
$kubectlVersion = (kubectl version --client --output=json | ConvertFrom-Json).clientVersion.gitVersion.Replace('v','')
|
||||
return "Kubectl $kubectlVersion"
|
||||
}
|
||||
|
||||
function Get-MinikubeVersion {
|
||||
$minikubeVersion = minikube version --short | Take-OutputPart -Part 0 -Delimiter "v"
|
||||
return "Minikube $minikubeVersion"
|
||||
}
|
||||
|
||||
function Get-HGVersion {
|
||||
$hgVersion = hg --version | Select-Object -First 1 | Take-OutputPart -Part -1 | Take-OutputPart -Part 0 -Delimiter ")"
|
||||
return "Mercurial $hgVersion"
|
||||
}
|
||||
|
||||
function Get-LeiningenVersion {
|
||||
return "$(lein -v | Take-OutputPart -Part 0,1)"
|
||||
}
|
||||
|
||||
function Get-MediainfoVersion {
|
||||
$mediainfoVersion = (mediainfo --version | Select-Object -Index 1 | Take-OutputPart -Part 2).Replace('v', '')
|
||||
return "MediaInfo $mediainfoVersion"
|
||||
}
|
||||
|
||||
function Get-NewmanVersion {
|
||||
return "Newman $(newman --version)"
|
||||
}
|
||||
|
||||
function Get-NVersion {
|
||||
$nVersion = (n --version).Replace('v', '')
|
||||
return "n $nVersion"
|
||||
}
|
||||
|
||||
function Get-NvmVersion {
|
||||
$nvmVersion = bash -c "source /etc/skel/.nvm/nvm.sh && nvm --version"
|
||||
return "nvm $nvmVersion"
|
||||
}
|
||||
|
||||
function Get-PackerVersion {
|
||||
# Packer 1.7.1 has a bug and outputs version to stderr instead of stdout https://github.com/hashicorp/packer/issues/10855
|
||||
$result = (Get-CommandResult "packer --version").Output
|
||||
$packerVersion = [regex]::matches($result, "(\d+.){2}\d+").Value
|
||||
return "Packer $packerVersion"
|
||||
}
|
||||
|
||||
function Get-PhantomJSVersion {
|
||||
return "PhantomJS $(phantomjs --version)"
|
||||
}
|
||||
|
||||
function Get-TerraformVersion {
|
||||
return (terraform version | Select-String "^Terraform").Line.Replace('v','')
|
||||
}
|
||||
|
||||
function Get-JqVersion {
|
||||
$jqVersion = jq --version | Take-OutputPart -Part 1 -Delimiter "-"
|
||||
return "jq $jqVersion"
|
||||
}
|
||||
|
||||
function Get-AzureCliVersion {
|
||||
$azcliVersion = (az version | ConvertFrom-Json).'azure-cli'
|
||||
$aptSourceRepo = Get-AptSourceRepository -PackageName "azure-cli"
|
||||
return "Azure CLI (azure-cli) $azcliVersion (installation method: $aptSourceRepo)"
|
||||
}
|
||||
|
||||
function Get-AzureDevopsVersion {
|
||||
$azdevopsVersion = (az version | ConvertFrom-Json).extensions.'azure-devops'
|
||||
return "Azure CLI (azure-devops) $azdevopsVersion"
|
||||
}
|
||||
|
||||
function Get-AlibabaCloudCliVersion {
|
||||
return "Alibaba Cloud CLI $(aliyun version)"
|
||||
}
|
||||
|
||||
function Get-AWSCliVersion {
|
||||
$result = Get-CommandResult "aws --version"
|
||||
$awsVersion = $result.Output | Take-OutputPart -Part 0 | Take-OutputPart -Part 1 -Delimiter "/"
|
||||
return "AWS CLI $awsVersion"
|
||||
}
|
||||
|
||||
function Get-AWSCliSessionManagerPluginVersion {
|
||||
$result = (Get-CommandResult "session-manager-plugin --version").Output
|
||||
return "AWS CLI Session manager plugin $result"
|
||||
}
|
||||
|
||||
function Get-AWSSAMVersion {
|
||||
return "AWS SAM CLI $(sam --version | Take-OutputPart -Part -1)"
|
||||
}
|
||||
|
||||
function Get-FastlaneVersion {
|
||||
$fastlaneVersion = fastlane --version | Select-String "^fastlane [0-9]" | Take-OutputPart -Part 1
|
||||
return "Fastlane $fastlaneVersion"
|
||||
}
|
||||
|
||||
function Get-HubCliVersion {
|
||||
$hubVersion = hub --version | Select-String "hub version" | Take-OutputPart -Part 2
|
||||
return "Hub CLI $hubVersion"
|
||||
}
|
||||
|
||||
function Get-GitHubCliVersion {
|
||||
$ghVersion = gh --version | Select-String "gh version" | Take-OutputPart -Part 2
|
||||
return "GitHub CLI $ghVersion"
|
||||
}
|
||||
|
||||
function Get-NetlifyCliVersion {
|
||||
$netlifyVersion = netlify --version | Take-OutputPart -Part 0 | Take-OutputPart -Part 1 -Delimiter "/"
|
||||
return "Netlify CLI $netlifyVersion"
|
||||
}
|
||||
|
||||
function Get-OCCliVersion {
|
||||
$ocVersion = oc version | Take-OutputPart -Part 2 | Take-OutputPart -Part 0 -Delimiter "-"
|
||||
return "OpenShift CLI $ocVersion"
|
||||
}
|
||||
|
||||
function Get-ORASCliVersion {
|
||||
$orasVersion = oras version | Select-String "^Version:" | Take-OutputPart -Part 1
|
||||
return "ORAS CLI $orasVersion"
|
||||
}
|
||||
|
||||
function Get-VerselCliversion {
|
||||
$result = Get-CommandResult "vercel --version" -Multiline
|
||||
return $result.Output | Select-Object -First 1
|
||||
}
|
||||
|
||||
function Get-PulumiVersion {
|
||||
$pulumiVersion = pulumi version | Take-OutputPart -Part 0 -Delimiter "v"
|
||||
return "Pulumi $pulumiVersion"
|
||||
}
|
||||
|
||||
function Get-RVersion {
|
||||
$rVersion = (Get-CommandResult "R --version | grep 'R version'").Output | Take-OutputPart -Part 2
|
||||
return "R $rVersion"
|
||||
}
|
||||
|
||||
function Get-SphinxVersion {
|
||||
$sphinxVersion = searchd -h | Select-Object -First 1 | Take-OutputPart -Part 1 | Take-OutputPart -Part 0 -Delimiter "-"
|
||||
return "Sphinx Open Source Search Server $sphinxVersion"
|
||||
}
|
||||
|
||||
function Get-YamllintVersion {
|
||||
return "$(yamllint --version)"
|
||||
}
|
||||
|
||||
function Get-ZstdVersion {
|
||||
$zstdVersion = zstd --version | Take-OutputPart -Part 1 -Delimiter "v" | Take-OutputPart -Part 0 -Delimiter ","
|
||||
return "zstd $zstdVersion (homebrew)"
|
||||
}
|
||||
|
||||
function Get-YqVersion {
|
||||
$yqVersion = ($(yq -V) -Split " ")[-1]
|
||||
return "yq $yqVersion"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
prefix=/usr/local/bin
|
||||
|
||||
for tool in apt apt-get apt-fast apt-key;do
|
||||
sudo rm -f $prefix/$tool
|
||||
done
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: repos.sh
|
||||
## Desc: Installs official Microsoft package repos for the distribution
|
||||
################################################################################
|
||||
|
||||
LSB_RELEASE=$(lsb_release -rs)
|
||||
|
||||
# Install Microsoft repository
|
||||
wget https://packages.microsoft.com/config/ubuntu/$LSB_RELEASE/packages-microsoft-prod.deb
|
||||
dpkg -i packages-microsoft-prod.deb
|
||||
|
||||
# update
|
||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common
|
||||
apt-get -yq update
|
||||
apt-get -yq dist-upgrade
|
||||
@@ -1,82 +0,0 @@
|
||||
function Get-CommandResult {
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $Command,
|
||||
[int[]] $ExpectExitCode = 0,
|
||||
[switch] $Multiline,
|
||||
[bool] $ValidateExitCode = $true
|
||||
)
|
||||
|
||||
# Bash trick to suppress and show error output because some commands write to stderr (for example, "python --version")
|
||||
$stdout = & bash -c "$Command 2>&1"
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
if ($ValidateExitCode) {
|
||||
if ($ExpectExitCode -notcontains $exitCode) {
|
||||
try {
|
||||
throw "StdOut: '$stdout' ExitCode: '$exitCode'"
|
||||
} catch {
|
||||
Write-Host $_.Exception.Message
|
||||
Write-Host $_.ScriptStackTrace
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @{
|
||||
Output = If ($Multiline -eq $true) { $stdout } else { [string]$stdout }
|
||||
ExitCode = $exitCode
|
||||
}
|
||||
}
|
||||
|
||||
function Get-OSName {
|
||||
lsb_release -ds
|
||||
}
|
||||
|
||||
function Get-KernelVersion {
|
||||
$kernelVersion = uname -r
|
||||
return "Linux kernel version: $kernelVersion"
|
||||
}
|
||||
|
||||
function Test-IsUbuntu18 {
|
||||
return (lsb_release -rs) -eq "18.04"
|
||||
}
|
||||
|
||||
function Test-IsUbuntu20 {
|
||||
return (lsb_release -rs) -eq "20.04"
|
||||
}
|
||||
|
||||
function Test-IsUbuntu22 {
|
||||
return (lsb_release -rs) -eq "22.04"
|
||||
}
|
||||
|
||||
function Get-ToolsetContent {
|
||||
$toolset = Join-Path $env:INSTALLER_SCRIPT_FOLDER "toolset.json"
|
||||
Get-Content $toolset -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Get-ToolsetValue {
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $KeyPath
|
||||
)
|
||||
|
||||
$jsonNode = Get-ToolsetContent
|
||||
|
||||
$pathParts = $KeyPath.Split(".")
|
||||
# try to walk through all arguments consequentially to resolve specific json node
|
||||
$pathParts | ForEach-Object {
|
||||
$jsonNode = $jsonNode.$_
|
||||
}
|
||||
return $jsonNode
|
||||
}
|
||||
|
||||
function Get-AndroidPackages {
|
||||
$androidSDKManagerPath = "/usr/local/lib/android/sdk/cmdline-tools/latest/bin/sdkmanager"
|
||||
$androidPackages = & $androidSDKManagerPath --list --verbose 2>&1
|
||||
return $androidPackages
|
||||
}
|
||||
|
||||
function Get-EnvironmentVariable($variable) {
|
||||
return [System.Environment]::GetEnvironmentVariable($variable)
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
function Take-OutputPart {
|
||||
param (
|
||||
[Parameter(ValueFromPipeline)]
|
||||
[string] $toolOutput,
|
||||
[string] $Delimiter = " ",
|
||||
[int[]] $Part
|
||||
)
|
||||
$parts = $toolOutput.Split($Delimiter, [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$selectedParts = $parts[$Part]
|
||||
return [string]::Join($Delimiter, $selectedParts)
|
||||
}
|
||||
|
||||
function New-MDNewLine {
|
||||
param (
|
||||
[int] $Count = 1
|
||||
)
|
||||
$newLineSymbol = [System.Environment]::NewLine
|
||||
return $newLineSymbol * $Count
|
||||
}
|
||||
|
||||
function Restore-UserOwner {
|
||||
sudo chown -R ${env:USER}: $env:HOME
|
||||
}
|
||||
|
||||
function Get-LinkTarget {
|
||||
param (
|
||||
[string] $inputPath
|
||||
)
|
||||
$link = Get-Item $inputPath | Select-Object -ExpandProperty Target
|
||||
if ($link) {
|
||||
return " -> $link"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function Get-PathWithLink {
|
||||
param (
|
||||
[string] $inputPath
|
||||
)
|
||||
$link = Get-LinkTarget($inputPath)
|
||||
return "${inputPath}${link}"
|
||||
}
|
||||
|
||||
function Get-CachedToolInstances
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Returns hastable of installed cached tools.
|
||||
|
||||
.DESCRIPTION
|
||||
Return hastable that contains versions and architectures for selected cached tool.
|
||||
|
||||
.PARAMETER Name
|
||||
Name of cached tool.
|
||||
|
||||
.PARAMETER VersionCommand
|
||||
Optional parameter. Command to return version of system default tool.
|
||||
|
||||
.EXAMPLE
|
||||
Get-CachedToolInstances -Name "Python" -VersionCommand "--version"
|
||||
|
||||
#>
|
||||
|
||||
param
|
||||
(
|
||||
[String] $Name,
|
||||
[String] $VersionCommand
|
||||
)
|
||||
|
||||
$toolInstances = @()
|
||||
$toolPath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath $Name
|
||||
|
||||
# Get all installed versions from TOOLSDIRECTORY folder
|
||||
$versions = Get-ChildItem $toolPath | Sort-Object { [System.Version]$_.Name }
|
||||
foreach ($version in $versions)
|
||||
{
|
||||
$instanceInfo = @{}
|
||||
|
||||
# Create instance hashtable
|
||||
[string]$instanceInfo.Path = Join-Path -Path $toolPath -ChildPath $version.Name
|
||||
[string]$instanceInfo.Version = $version.Name
|
||||
|
||||
# Get all architectures for current version
|
||||
[array]$instanceInfo.Architecture_Array = Get-ChildItem $version.FullName -Name -Directory | Where-Object { $_ -match "^x[0-9]{2}$" }
|
||||
[string]$instanceInfo.Architecture = $instanceInfo.Architecture_Array -Join ", "
|
||||
|
||||
# Add (default) postfix to version name, in case if current version is in environment path
|
||||
if (-not ([string]::IsNullOrEmpty($VersionCommand)))
|
||||
{
|
||||
$defaultVersion = $(& ($Name.ToLower()) $VersionCommand 2>&1)
|
||||
$defaultToolVersion = $defaultVersion | Select-String -Pattern "\d+\.\d+\.\d+" -AllMatches `
|
||||
| ForEach-Object { $_.Matches.Value }
|
||||
|
||||
if ([version]$version.Name -eq [version]$defaultToolVersion)
|
||||
{
|
||||
$instanceInfo.Version += " (Default)"
|
||||
}
|
||||
}
|
||||
|
||||
$toolInstances += $instanceInfo
|
||||
}
|
||||
|
||||
return $toolInstances
|
||||
}
|
||||
|
||||
function Get-AptSourceRepository {
|
||||
param([String] $PackageName)
|
||||
|
||||
$sourceUrl = Get-Content "$PSScriptRoot/../helpers/apt-sources.txt" | Select-String -Pattern $PackageName | Take-OutputPart -Part (1..3)
|
||||
return $sourceUrl
|
||||
}
|
||||
|
||||
function Test-BlankElement {
|
||||
param(
|
||||
[string] $Markdown
|
||||
)
|
||||
|
||||
$splitByLines = $Markdown.Split("`n")
|
||||
# Validate entry without version
|
||||
$blankVersions = $splitByLines -match "^-" -notmatch "Image Version|MySQL Server|Vcpkg|\d\." | Out-String
|
||||
|
||||
# Validate tables with blank rows
|
||||
$blankRows = ""
|
||||
for($i = 0; $i -lt $splitByLines.Length; $i++) {
|
||||
$addRows= $false
|
||||
$table = @()
|
||||
if ($splitByLines[$i].StartsWith("#") -and $splitByLines[$i+1].StartsWith("|")) {
|
||||
$table += $splitByLines[$i,($i+1),($i+2)]
|
||||
$i += 3
|
||||
$current = $splitByLines[$i]
|
||||
while ($current.StartsWith("|")) {
|
||||
$isBlankRow = $current.Substring(1, $current.LastIndexOf("|") - 2).Split("|").Trim() -contains ""
|
||||
if ($isBlankRow) {
|
||||
$table += $current
|
||||
$addRows = $true
|
||||
}
|
||||
$current = $splitByLines[++$i]
|
||||
}
|
||||
if ($addRows) {
|
||||
$blankRows += $table | Out-String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Display report
|
||||
$isReport = $false
|
||||
if ($blankVersions) {
|
||||
Write-Host "Software list with blank version:`n${blankVersions}"
|
||||
$isReport = $true
|
||||
}
|
||||
if ($blankRows) {
|
||||
Write-Host "Tables with blank rows:`n${blankRows}"
|
||||
$isReport = $true
|
||||
}
|
||||
if ($isReport) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" -DisableNameChecking
|
||||
|
||||
# Validates that tool is installed and in PATH
|
||||
function Validate-ToolExist($tool) {
|
||||
Get-Command $tool -ErrorAction SilentlyContinue | Should -BeTrue
|
||||
}
|
||||
|
||||
function Invoke-PesterTests {
|
||||
Param(
|
||||
[Parameter(Mandatory)][string] $TestFile,
|
||||
[string] $TestName
|
||||
)
|
||||
|
||||
$testPath = "/imagegeneration/tests/${TestFile}.Tests.ps1"
|
||||
if (-not (Test-Path $testPath)) {
|
||||
throw "Unable to find test file '$TestFile' on '$testPath'."
|
||||
}
|
||||
|
||||
# Check that Pester module is imported
|
||||
if (!(Get-Module "Pester")) {
|
||||
Import-Module Pester
|
||||
}
|
||||
|
||||
$configuration = [PesterConfiguration] @{
|
||||
Run = @{ Path = $testPath; PassThru = $true }
|
||||
Output = @{ Verbosity = "Detailed" }
|
||||
}
|
||||
if ($TestName) {
|
||||
$configuration.Filter.FullName = $TestName
|
||||
}
|
||||
|
||||
# Switch ErrorActionPreference to Stop temporary to make sure that tests will fail on silent errors too
|
||||
$backupErrorActionPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Stop"
|
||||
$results = Invoke-Pester -Configuration $configuration
|
||||
$ErrorActionPreference = $backupErrorActionPreference
|
||||
|
||||
# Fail in case if no tests are run
|
||||
if (-not ($results -and ($results.FailedCount -eq 0) -and (($results.PassedCount + $results.SkippedCount) -gt 0))) {
|
||||
$results
|
||||
throw "Test run has failed"
|
||||
}
|
||||
}
|
||||
|
||||
function ShouldReturnZeroExitCode {
|
||||
Param(
|
||||
[string] $ActualValue,
|
||||
[switch] $Negate,
|
||||
[string] $Because # This parameter is unused but we need it to match Pester asserts signature
|
||||
)
|
||||
|
||||
$result = Get-CommandResult $ActualValue -ValidateExitCode $false
|
||||
|
||||
[bool]$succeeded = $result.ExitCode -eq 0
|
||||
if ($Negate) { $succeeded = -not $succeeded }
|
||||
|
||||
if (-not $succeeded)
|
||||
{
|
||||
$commandOutputIndent = " " * 4
|
||||
$commandOutput = ($result.Output | ForEach-Object { "${commandOutputIndent}${_}" }) -join "`n"
|
||||
$failureMessage = "Command '${ActualValue}' has finished with exit code`n${commandOutput}"
|
||||
}
|
||||
|
||||
return [PSCustomObject] @{
|
||||
Succeeded = $succeeded
|
||||
FailureMessage = $failureMessage
|
||||
}
|
||||
}
|
||||
|
||||
function ShouldMatchCommandOutput {
|
||||
Param(
|
||||
[string] $ActualValue,
|
||||
[string] $RegularExpression,
|
||||
[switch] $Negate
|
||||
)
|
||||
|
||||
$output = (Get-CommandResult $ActualValue -ValidateExitCode $false).Output | Out-String
|
||||
[bool] $succeeded = $output -cmatch $RegularExpression
|
||||
|
||||
if ($Negate) {
|
||||
$succeeded = -not $succeeded
|
||||
}
|
||||
|
||||
$failureMessage = ''
|
||||
|
||||
if (-not $succeeded) {
|
||||
if ($Negate) {
|
||||
$failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to not match '$output', but it did match."
|
||||
}
|
||||
else {
|
||||
$failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to match '$output', but it did not match."
|
||||
}
|
||||
}
|
||||
|
||||
return [PSCustomObject] @{
|
||||
Succeeded = $succeeded
|
||||
FailureMessage = $failureMessage
|
||||
}
|
||||
}
|
||||
|
||||
If (Get-Command -Name Add-ShouldOperator -ErrorAction SilentlyContinue) {
|
||||
Add-ShouldOperator -Name ReturnZeroExitCode -InternalName ShouldReturnZeroExitCode -Test ${function:ShouldReturnZeroExitCode}
|
||||
Add-ShouldOperator -Name MatchCommandOutput -InternalName ShouldMatchCommandOutput -Test ${function:ShouldMatchCommandOutput}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: install.sh
|
||||
## Desc: Helper functions for installing tools
|
||||
################################################################################
|
||||
|
||||
download_with_retries() {
|
||||
# Due to restrictions of bash functions, positional arguments are used here.
|
||||
# In case if you using latest argument NAME, you should also set value to all previous parameters.
|
||||
# Example: download_with_retries $ANDROID_SDK_URL "." "android_sdk.zip"
|
||||
local URL="$1"
|
||||
local DEST="${2:-.}"
|
||||
local NAME="${3:-${URL##*/}}"
|
||||
local COMPRESSED="$4"
|
||||
|
||||
if [[ $COMPRESSED == "compressed" ]]; then
|
||||
local COMMAND="curl $URL -4 -sL --compressed -o '$DEST/$NAME' -w '%{http_code}'"
|
||||
else
|
||||
local COMMAND="curl $URL -4 -sL -o '$DEST/$NAME' -w '%{http_code}'"
|
||||
fi
|
||||
|
||||
echo "Downloading '$URL' to '${DEST}/${NAME}'..."
|
||||
retries=20
|
||||
interval=30
|
||||
while [ $retries -gt 0 ]; do
|
||||
((retries--))
|
||||
# Temporary disable exit on error to retry on non-zero exit code
|
||||
set +e
|
||||
http_code=$(eval $COMMAND)
|
||||
exit_code=$?
|
||||
if [ $http_code -eq 200 ] && [ $exit_code -eq 0 ]; then
|
||||
echo "Download completed"
|
||||
return 0
|
||||
else
|
||||
echo "Error — Either HTTP response code for '$URL' is wrong - '$http_code' or exit code is not 0 - '$exit_code'. Waiting $interval seconds before the next attempt, $retries attempts left"
|
||||
sleep 30
|
||||
fi
|
||||
# Enable exit on error back
|
||||
set -e
|
||||
done
|
||||
|
||||
echo "Could not download $URL"
|
||||
return 1
|
||||
}
|
||||
|
||||
## Use dpkg to figure out if a package has already been installed
|
||||
## Example use:
|
||||
## if ! IsPackageInstalled packageName; then
|
||||
## echo "packageName is not installed!"
|
||||
## fi
|
||||
IsPackageInstalled() {
|
||||
dpkg -S $1 &> /dev/null
|
||||
}
|
||||
|
||||
verlte() {
|
||||
sortedVersion=$(echo -e "$1\n$2" | sort -V | head -n1)
|
||||
[ "$1" = "$sortedVersion" ]
|
||||
}
|
||||
|
||||
get_toolset_path() {
|
||||
echo "/imagegeneration/installers/toolset.json"
|
||||
}
|
||||
|
||||
get_toolset_value() {
|
||||
local toolset_path=$(get_toolset_path)
|
||||
local query=$1
|
||||
echo "$(jq -r "$query" $toolset_path)"
|
||||
}
|
||||
|
||||
get_github_package_download_url() {
|
||||
local REPO_ORG=$1
|
||||
local FILTER=$2
|
||||
local VERSION=$3
|
||||
local SEARCH_IN_COUNT="100"
|
||||
|
||||
json=$(curl -s "https://api.github.com/repos/${REPO_ORG}/releases?per_page=${SEARCH_IN_COUNT}")
|
||||
|
||||
if [ -n "$VERSION" ]; then
|
||||
tagName=$(echo $json | jq -r '.[] | select(.prerelease==false).tag_name' | sort --unique --version-sort | egrep -v ".*-[a-z]|beta" | egrep "\w*${VERSION}" | tail -1)
|
||||
else
|
||||
tagName=$(echo $json | jq -r '.[] | select((.prerelease==false) and (.assets | length > 0)).tag_name' | sort --unique --version-sort | egrep -v ".*-[a-z]|beta" | tail -1)
|
||||
fi
|
||||
|
||||
downloadUrl=$(echo $json | jq -r ".[] | select(.tag_name==\"${tagName}\").assets[].browser_download_url | select(${FILTER})" | head -n 1)
|
||||
if [ -z "$downloadUrl" ]; then
|
||||
echo "Failed to parse a download url for the '${tagName}' tag using '${FILTER}' filter"
|
||||
exit 1
|
||||
fi
|
||||
echo $downloadUrl
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: install-helpers.sh
|
||||
## Desc: Helper functions for installing tools
|
||||
################################################################################
|
||||
|
||||
function isUbuntu18
|
||||
{
|
||||
lsb_release -d | grep -q 'Ubuntu 18'
|
||||
}
|
||||
|
||||
function isUbuntu20
|
||||
{
|
||||
lsb_release -d | grep -q 'Ubuntu 20'
|
||||
}
|
||||
|
||||
function isUbuntu22
|
||||
{
|
||||
lsb_release -d | grep -q 'Ubuntu 22'
|
||||
}
|
||||
|
||||
function getOSVersionLabel
|
||||
{
|
||||
lsb_release -cs
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
################################################################################
|
||||
## File: Configure-Toolset.ps1
|
||||
## Team: CI-Build
|
||||
## Desc: Configure toolset
|
||||
################################################################################
|
||||
|
||||
Import-Module "$env:HELPER_SCRIPTS/Tests.Helpers.psm1" -DisableNameChecking
|
||||
|
||||
function Get-ToolsetToolFullPath
|
||||
{
|
||||
param
|
||||
(
|
||||
[Parameter(Mandatory)] [string] $ToolName,
|
||||
[Parameter(Mandatory)] [string] $ToolVersion,
|
||||
[Parameter(Mandatory)] [string] $ToolArchitecture
|
||||
)
|
||||
|
||||
$toolPath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath $toolName
|
||||
$toolPathVersion = Join-Path -Path $toolPath -ChildPath $toolVersion
|
||||
$foundVersion = Get-Item $toolPathVersion | Sort-Object -Property {[version]$_.name} -Descending | Select-Object -First 1
|
||||
$installationDir = Join-Path -Path $foundVersion -ChildPath $toolArchitecture
|
||||
return $installationDir
|
||||
}
|
||||
|
||||
function Add-EnvironmentVariable
|
||||
{
|
||||
param
|
||||
(
|
||||
[Parameter(Mandatory)] [string] $Name,
|
||||
[Parameter(Mandatory)] [string] $Value,
|
||||
[string] $FilePath = "/etc/environment"
|
||||
)
|
||||
|
||||
$envVar = "{0}={1}" -f $name, $value
|
||||
Tee-Object -InputObject $envVar -FilePath $filePath -Append
|
||||
}
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "Configure toolset tools environment..."
|
||||
$toolsEnvironment = @{
|
||||
go = @{
|
||||
command = "ln -s {0}/bin/* /usr/bin/"
|
||||
variableTemplate = "GOROOT_{0}_{1}_X64"
|
||||
}
|
||||
}
|
||||
|
||||
$toolset = Get-Content -Path "$env:INSTALLER_SCRIPT_FOLDER/toolset.json" -Raw | ConvertFrom-Json
|
||||
|
||||
foreach ($tool in $toolset.toolcache)
|
||||
{
|
||||
$toolName = $tool.name
|
||||
$toolArch = $tool.arch
|
||||
$toolEnvironment = $toolsEnvironment[$toolName]
|
||||
|
||||
if (-not $toolEnvironment)
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
foreach ($toolVersion in $tool.versions)
|
||||
{
|
||||
Write-Host "Set $toolName $toolVersion environment variable..."
|
||||
$toolPath = Get-ToolsetToolFullPath -ToolName $toolName -ToolVersion $toolVersion -ToolArchitecture $toolArch
|
||||
$envName = $toolEnvironment.variableTemplate -f $toolVersion.split(".")
|
||||
|
||||
# Add environment variable name=value
|
||||
Add-EnvironmentVariable -Name $envName -Value $toolPath
|
||||
}
|
||||
|
||||
# Invoke command and add env variable for the default tool version
|
||||
$toolDefVersion = $tool.default
|
||||
if (-not $toolDefVersion)
|
||||
{
|
||||
continue
|
||||
}
|
||||
|
||||
$envDefName = $toolEnvironment.defaultVariable
|
||||
$toolPath = Get-ToolsetToolFullPath -ToolName $toolName -ToolVersion $toolDefVersion -ToolArchitecture $toolArch
|
||||
|
||||
if ($envDefName)
|
||||
{
|
||||
Write-Host "Set default $envDefName for $toolName $toolDefVersion environment variable..."
|
||||
Add-EnvironmentVariable -Name $envDefName -Value $toolPath
|
||||
}
|
||||
|
||||
if ($toolEnvironment.command)
|
||||
{
|
||||
$command = $toolEnvironment.command -f $toolPath
|
||||
Write-Host "Invoke $command command for default $toolName $toolDefVersion..."
|
||||
Invoke-Expression -Command $command
|
||||
}
|
||||
}
|
||||
|
||||
Invoke-PesterTests -TestFile "Toolset" -TestName "Toolset"
|
||||
@@ -1,40 +0,0 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
Import-Module "$env:HELPER_SCRIPTS/Tests.Helpers.psm1" -DisableNameChecking
|
||||
|
||||
# Get modules content from toolset
|
||||
$modules = (Get-ToolsetContent).azureModules
|
||||
$installPSModulePath = "/usr/share"
|
||||
|
||||
foreach ($module in $modules)
|
||||
{
|
||||
$moduleName = $module.name
|
||||
Write-Host "Installing ${moduleName} to the ${installPSModulePath} path..."
|
||||
foreach ($version in $module.versions)
|
||||
{
|
||||
$modulePath = Join-Path -Path $installPSModulePath -ChildPath "${moduleName}_${version}"
|
||||
Write-Host " - $version [$modulePath]"
|
||||
Save-Module -Path $modulePath -Name $moduleName -RequiredVersion $version -Force
|
||||
}
|
||||
|
||||
$assets = Invoke-RestMethod $module.url
|
||||
|
||||
# Get github release asset for each version
|
||||
foreach ($toolVersion in $module.zip_versions) {
|
||||
$asset = $assets | Where-Object version -eq $toolVersion `
|
||||
| Select-Object -ExpandProperty files `
|
||||
| Select-Object -First 1
|
||||
|
||||
Write-Host "Installing $($module.name) $toolVersion ..."
|
||||
if ($null -ne $asset) {
|
||||
Write-Host "Download $($asset.filename)"
|
||||
wget $asset.download_url -nv --retry-connrefused --tries=10 -P $installPSModulePath
|
||||
} else {
|
||||
Write-Host "Asset was not found in versions manifest"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Invoke-PesterTests -TestFile "PowerShellModules" -TestName "AzureModules"
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: aliyun-cli.sh
|
||||
## Desc: Installs Alibaba Cloud CLI
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/install.sh
|
||||
|
||||
# Install Alibaba Cloud CLI
|
||||
downloadUrl="https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz"
|
||||
download_with_retries $downloadUrl "/tmp"
|
||||
tar xzf /tmp/aliyun-cli-linux-*-amd64.tgz
|
||||
mv aliyun /usr/local/bin
|
||||
|
||||
invoke_tests "CLI.Tools" "Aliyun CLI"
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: android.sh
|
||||
## Desc: Installs Android SDK
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/os.sh
|
||||
source $HELPER_SCRIPTS/install.sh
|
||||
source $HELPER_SCRIPTS/etc-environment.sh
|
||||
|
||||
function filter_components_by_version {
|
||||
minimumVersion=$1
|
||||
shift
|
||||
toolsArr=("$@")
|
||||
|
||||
for item in ${toolsArr[@]}
|
||||
do
|
||||
# take the last argument after spliting string by ';'' and '-''
|
||||
version=$(echo "${item##*[-;]}")
|
||||
if verlte $minimumVersion $version
|
||||
then
|
||||
components+=($item)
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function get_full_ndk_version {
|
||||
majorVersion=$1
|
||||
ndkFullVersion=$($SDKMANAGER --list | grep "ndk;${majorVersion}.*" | awk '{gsub("ndk;", ""); print $1}' | sort -V | tail -n1)
|
||||
echo "$ndkFullVersion"
|
||||
}
|
||||
|
||||
# Set env variable for SDK Root (https://developer.android.com/studio/command-line/variables)
|
||||
ANDROID_ROOT=/usr/local/lib/android
|
||||
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
|
||||
SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager
|
||||
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" | tee -a /etc/environment
|
||||
|
||||
# ANDROID_HOME is deprecated, but older versions of Gradle rely on it
|
||||
echo "ANDROID_HOME=${ANDROID_SDK_ROOT}" | tee -a /etc/environment
|
||||
|
||||
# Create android sdk directory
|
||||
mkdir -p ${ANDROID_SDK_ROOT}
|
||||
|
||||
# Download the latest command line tools so that we can accept all of the licenses.
|
||||
# See https://developer.android.com/studio/#command-tools
|
||||
cmdlineToolsVersion=$(get_toolset_value '.android."cmdline-tools"')
|
||||
if [[ $cmdlineToolsVersion == "latest" ]]; then
|
||||
repositoryXmlUrl="https://dl.google.com/android/repository/repository2-1.xml"
|
||||
download_with_retries $repositoryXmlUrl "/tmp" "repository2-1.xml"
|
||||
cmdlineToolsVersion=$(
|
||||
yq -p=xml \
|
||||
'.sdk-repository.remotePackage[] | select(."+path" == "cmdline-tools;latest").archives.archive[].complete.url | select(contains("commandlinetools-linux"))' \
|
||||
/tmp/repository2-1.xml
|
||||
)
|
||||
|
||||
if [[ -z $cmdlineToolsVersion ]]; then
|
||||
echo "Failed to parse latest command-line tools version"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
cmdlineTools="android-cmdline-tools.zip"
|
||||
download_with_retries "https://dl.google.com/android/repository/${cmdlineToolsVersion}" "." $cmdlineTools
|
||||
unzip -qq $cmdlineTools -d ${ANDROID_SDK_ROOT}/cmdline-tools
|
||||
# Command line tools need to be placed in ${ANDROID_SDK_ROOT}/sdk/cmdline-tools/latest to determine SDK root
|
||||
mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest
|
||||
rm -f $cmdlineTools
|
||||
|
||||
# Check sdk manager installation
|
||||
${SDKMANAGER} --list 1>/dev/null
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo "Android SDK manager was installed"
|
||||
else
|
||||
echo "Android SDK manager was not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
minimumBuildToolVersion=$(get_toolset_value '.android.build_tools_min_version')
|
||||
minimumPlatformVersion=$(get_toolset_value '.android.platform_min_version')
|
||||
extras=$(get_toolset_value '.android.extra_list[]|"extras;" + .')
|
||||
addons=$(get_toolset_value '.android.addon_list[]|"add-ons;" + .')
|
||||
additional=$(get_toolset_value '.android.additional_tools[]')
|
||||
ANDROID_NDK_MAJOR_VERSIONS=($(get_toolset_value '.android.ndk.versions[]'))
|
||||
ANDROID_NDK_MAJOR_DEFAULT=$(get_toolset_value '.android.ndk.default')
|
||||
|
||||
components=("${extras[@]}" "${addons[@]}" "${additional[@]}")
|
||||
for ndk_version in "${ANDROID_NDK_MAJOR_VERSIONS[@]}"
|
||||
do
|
||||
ndk_full_version=$(get_full_ndk_version $ndk_version)
|
||||
components+=("ndk;$ndk_full_version")
|
||||
done
|
||||
|
||||
ANDROID_NDK_MAJOR_LATEST=(${ANDROID_NDK_MAJOR_VERSIONS[-1]})
|
||||
ndkDefaultFullVersion=$(get_full_ndk_version $ANDROID_NDK_MAJOR_DEFAULT)
|
||||
ndkLatestFullVersion=$(get_full_ndk_version $ANDROID_NDK_MAJOR_LATEST)
|
||||
ANDROID_NDK="$ANDROID_SDK_ROOT/ndk/$ndkDefaultFullVersion"
|
||||
# ANDROID_NDK, ANDROID_NDK_HOME, and ANDROID_NDK_ROOT variables should be set as many customer builds depend on them https://github.com/actions/virtual-environments/issues/5879
|
||||
echo "ANDROID_NDK=${ANDROID_NDK}" | tee -a /etc/environment
|
||||
echo "ANDROID_NDK_HOME=${ANDROID_NDK}" | tee -a /etc/environment
|
||||
echo "ANDROID_NDK_ROOT=${ANDROID_NDK}" | tee -a /etc/environment
|
||||
echo "ANDROID_NDK_LATEST_HOME=$ANDROID_SDK_ROOT/ndk/$ndkLatestFullVersion" | tee -a /etc/environment
|
||||
|
||||
availablePlatforms=($($SDKMANAGER --list | sed -n '/Available Packages:/,/^$/p' | grep "platforms;android-[0-9]" | cut -d"|" -f 1))
|
||||
allBuildTools=($($SDKMANAGER --list | grep "build-tools;" | cut -d"|" -f 1 | sort -u))
|
||||
availableBuildTools=$(echo ${allBuildTools[@]//*rc[0-9]/})
|
||||
|
||||
filter_components_by_version $minimumPlatformVersion "${availablePlatforms[@]}"
|
||||
filter_components_by_version $minimumBuildToolVersion "${availableBuildTools[@]}"
|
||||
|
||||
echo "y" | $SDKMANAGER ${components[@]}
|
||||
|
||||
# Old skdmanager from sdk tools doesn't work with Java > 8, set version 8 explicitly
|
||||
if isUbuntu20 || isUbuntu22; then
|
||||
sed -i "2i export JAVA_HOME=${JAVA_HOME_8_X64}" ${ANDROID_SDK_ROOT}/tools/bin/sdkmanager
|
||||
fi
|
||||
|
||||
# Add required permissions
|
||||
chmod -R a+rwx ${ANDROID_SDK_ROOT}
|
||||
|
||||
reloadEtcEnvironment
|
||||
invoke_tests "Android"
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: aws.sh
|
||||
## Desc: Installs the AWS CLI, Session Manager plugin for the AWS CLI, and AWS SAM CLI
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/os.sh
|
||||
source $HELPER_SCRIPTS/install.sh
|
||||
|
||||
download_with_retries "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" "/tmp" "awscliv2.zip"
|
||||
unzip -qq /tmp/awscliv2.zip -d /tmp
|
||||
/tmp/aws/install -i /usr/local/aws-cli -b /usr/local/bin
|
||||
|
||||
download_with_retries "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" "/tmp" "session-manager-plugin.deb"
|
||||
apt install /tmp/session-manager-plugin.deb
|
||||
|
||||
# Download & install the latest aws sam cli release
|
||||
zipName="aws-sam-cli-linux-x86_64.zip"
|
||||
zipUrl="https://github.com/aws/aws-sam-cli/releases/latest/download/${zipName}"
|
||||
download_with_retries $zipUrl "/tmp" $zipName
|
||||
unzip /tmp/${zipName} -d /tmp
|
||||
/tmp/install
|
||||
|
||||
invoke_tests "CLI.Tools" "AWS"
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: bicep.sh
|
||||
## Desc: Installs bicep cli
|
||||
################################################################################
|
||||
|
||||
source $HELPER_SCRIPTS/install.sh
|
||||
|
||||
# Install Bicep CLI
|
||||
download_with_retries "https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64" "." "bicep.bin"
|
||||
# Mark it as executable
|
||||
chmod +x ./bicep.bin
|
||||
# Add bicep to PATH (requires admin)
|
||||
sudo mv ./bicep.bin /usr/local/bin/bicep
|
||||
|
||||
invoke_tests "Tools" "Bicep"
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: cmake.sh
|
||||
## Desc: Installs CMake
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/install.sh
|
||||
|
||||
# Test to see if the software in question is already installed, if not install it
|
||||
echo "Checking to see if the installer script has already been run"
|
||||
if command -v cmake; then
|
||||
echo "cmake is already installed"
|
||||
else
|
||||
downloadUrl=$(get_github_package_download_url "Kitware/CMake" "endswith(\"inux-x86_64.sh\")")
|
||||
curl -sL ${downloadUrl} -o cmakeinstall.sh \
|
||||
&& chmod +x cmakeinstall.sh \
|
||||
&& ./cmakeinstall.sh --prefix=/usr/local --exclude-subdir \
|
||||
&& rm cmakeinstall.sh
|
||||
fi
|
||||
|
||||
invoke_tests "Tools" "Cmake"
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: codeql-bundle.sh
|
||||
## Desc: Install the CodeQL CLI Bundle to the toolcache.
|
||||
################################################################################
|
||||
|
||||
source $HELPER_SCRIPTS/install.sh
|
||||
|
||||
# Retrieve the name of the CodeQL bundle preferred by the Action (in the format codeql-bundle-YYYYMMDD).
|
||||
codeql_bundle_name="$(curl -sSL https://raw.githubusercontent.com/github/codeql-action/v2/src/defaults.json | jq -r .bundleVersion)"
|
||||
# Convert the bundle name to a version number (0.0.0-YYYYMMDD).
|
||||
codeql_bundle_version="0.0.0-${codeql_bundle_name##*-}"
|
||||
|
||||
extraction_directory="$AGENT_TOOLSDIRECTORY/CodeQL/$codeql_bundle_version/x64"
|
||||
mkdir -p "$extraction_directory"
|
||||
|
||||
echo "Downloading CodeQL bundle $codeql_bundle_version..."
|
||||
download_with_retries "https://github.com/github/codeql-action/releases/download/$codeql_bundle_name/codeql-bundle.tar.gz" "/tmp" "codeql-bundle.tar.gz"
|
||||
tar -xzf "/tmp/codeql-bundle.tar.gz" -C "$extraction_directory"
|
||||
|
||||
# Touch a special file that indicates to the CodeQL Action that this bundle was baked-in to the hosted runner images.
|
||||
touch "$extraction_directory/pinned-version"
|
||||
|
||||
# Touch a file to indicate to the toolcache that setting up CodeQL is complete.
|
||||
touch "$extraction_directory.complete"
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: snap-environment.sh
|
||||
## Desc: Update /etc/environment to include /snap/bin in PATH
|
||||
## because /etc/profile.d is ignored by `--norc` shell launch option
|
||||
################################################################################
|
||||
|
||||
# Source the helpers
|
||||
source $HELPER_SCRIPTS/etc-environment.sh
|
||||
|
||||
# Update /etc/environemnt
|
||||
prependEtcEnvironmentPath "/snap/bin"
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: containers.sh
|
||||
## Desc: Installs container tools: podman, buildah and skopeo onto the image
|
||||
################################################################################
|
||||
|
||||
source $HELPER_SCRIPTS/os.sh
|
||||
|
||||
install_packages=(podman buildah skopeo)
|
||||
|
||||
# Packages is available in the official Ubuntu upstream starting from Ubuntu 21
|
||||
if isUbuntu18 || isUbuntu20; then
|
||||
REPO_URL="https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable"
|
||||
source /etc/os-release
|
||||
sh -c "echo 'deb ${REPO_URL}/x${NAME}_${VERSION_ID}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
|
||||
wget -qnv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/x${NAME}_${VERSION_ID}/Release.key -O Release.key
|
||||
apt-key add Release.key
|
||||
fi
|
||||
|
||||
# Install podman, buildah, scopeo container's tools
|
||||
apt-get update
|
||||
apt-get -y install ${install_packages[@]}
|
||||
mkdir -p /etc/containers
|
||||
echo -e "[registries.search]\nregistries = ['docker.io', 'quay.io']" | tee /etc/containers/registries.conf
|
||||
|
||||
if isUbuntu18 || isUbuntu20; then
|
||||
# Remove source repo
|
||||
rm /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||
# Document source repo
|
||||
echo "containers $REPO_URL" >> $HELPER_SCRIPTS/apt-sources.txt
|
||||
fi
|
||||
|
||||
invoke_tests "Tools" "Containers"
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: docker-compose.sh
|
||||
## Desc: Installs Docker Compose
|
||||
################################################################################
|
||||
|
||||
# Install docker-compose v1 from releases
|
||||
URL="https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64"
|
||||
curl -L $URL -o /usr/local/bin/docker-compose
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
invoke_tests "Tools" "Docker-compose v1"
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
################################################################################
|
||||
## File: docker-moby.sh
|
||||
## Desc: Installs docker onto the image
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/install.sh
|
||||
|
||||
# Check to see if docker is already installed
|
||||
docker_package=moby
|
||||
echo "Determing if Docker ($docker_package) is installed"
|
||||
if ! IsPackageInstalled $docker_package; then
|
||||
echo "Docker ($docker_package) was not found. Installing..."
|
||||
apt-get remove -y moby-engine moby-cli
|
||||
apt-get update
|
||||
apt-get install -y moby-engine moby-cli
|
||||
apt-get install --no-install-recommends -y moby-buildx
|
||||
apt-get install -y moby-compose
|
||||
else
|
||||
echo "Docker ($docker_package) is already installed"
|
||||
fi
|
||||
|
||||
# Enable docker.service
|
||||
systemctl is-active --quiet docker.service || systemctl start docker.service
|
||||
systemctl is-enabled --quiet docker.service || systemctl enable docker.service
|
||||
|
||||
# Docker daemon takes time to come up after installing
|
||||
sleep 10
|
||||
docker info
|
||||
|
||||
# If credentials are provided, attempt to log into Docker Hub
|
||||
# with a paid account to avoid Docker Hub's rate limit.
|
||||
if [ "${DOCKERHUB_LOGIN}" ] && [ "${DOCKERHUB_PASSWORD}" ]; then
|
||||
docker login --username "${DOCKERHUB_LOGIN}" --password "${DOCKERHUB_PASSWORD}"
|
||||
fi
|
||||
|
||||
# Pull images
|
||||
images=$(get_toolset_value '.docker.images[]')
|
||||
for image in $images; do
|
||||
docker pull "$image"
|
||||
done
|
||||
|
||||
# Always attempt to logout so we do not leave our credentials on the built
|
||||
# image. Logout _should_ return a zero exit code even if no credentials were
|
||||
# stored from earlier.
|
||||
docker logout
|
||||
|
||||
# Install amazon-ecr-credential-helper
|
||||
aws_latest_release_url="https://api.github.com/repos/awslabs/amazon-ecr-credential-helper/releases/latest"
|
||||
aws_helper_url=$(curl "${authString[@]}" -sL $aws_latest_release_url | jq -r '.body' | awk -F'[()]' '/linux-amd64/ {print $2}')
|
||||
download_with_retries "$aws_helper_url" "/usr/bin" docker-credential-ecr-login
|
||||
chmod +x /usr/bin/docker-credential-ecr-login
|
||||
|
||||
invoke_tests "Tools" "Docker"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user