Compare commits
699 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b00af6926 | ||
|
|
ddf1be653e | ||
|
|
cede7e5ac0 | ||
|
|
964bd8ba61 | ||
|
|
a1db219fd2 | ||
|
|
241be1801b | ||
|
|
4d9c78003a | ||
|
|
375fe19f52 | ||
|
|
1895c63e89 | ||
|
|
33a00436b1 | ||
|
|
8ad232d96a | ||
|
|
01b7721b13 | ||
|
|
46b2fe0eba | ||
|
|
89784e86bd | ||
|
|
c4b14ac40d | ||
|
|
12b519a543 | ||
|
|
f75f6623b3 | ||
|
|
43b5a0ea6a | ||
|
|
3541954df8 | ||
|
|
e0d6d35b56 | ||
|
|
8cf7719476 | ||
|
|
d2188609e4 | ||
|
|
3e180eafb4 | ||
|
|
b8a3c39984 | ||
|
|
946468dc99 | ||
|
|
340a832884 | ||
|
|
388d08e245 | ||
|
|
8a78907977 | ||
|
|
6347e275d3 | ||
|
|
c8f19e0d96 | ||
|
|
ba6d816186 | ||
|
|
849de648f0 | ||
|
|
4d18044851 | ||
|
|
d6a6734b65 | ||
|
|
10319dd641 | ||
|
|
a8b945e36e | ||
|
|
719c56e754 | ||
|
|
cbf10bdb44 | ||
|
|
ee60c428b2 | ||
|
|
a410b4981f | ||
|
|
f973009b83 | ||
|
|
e0ace35525 | ||
|
|
cf49d9f784 | ||
|
|
79d5873445 | ||
|
|
c5e3e0ee70 | ||
|
|
6976f5f3f0 | ||
|
|
1f37ac83e4 | ||
|
|
36ebddf986 | ||
|
|
b496f8d70a | ||
|
|
9351eee3f1 | ||
|
|
d412d6502d | ||
|
|
b85f81d529 | ||
|
|
4140492d56 | ||
|
|
59e133d3bc | ||
|
|
90f6d4e563 | ||
|
|
3e31ee9455 | ||
|
|
d52c90ede4 | ||
|
|
9527d0dcbd | ||
|
|
51d21b8dab | ||
|
|
601fcf40c4 | ||
|
|
d537c3d523 | ||
|
|
a65c87b584 | ||
|
|
589a5f9aeb | ||
|
|
e767655ede | ||
|
|
62295e370c | ||
|
|
f818f5ccf5 | ||
|
|
f28d22d20f | ||
|
|
80af171a35 | ||
|
|
71c54ab212 | ||
|
|
c37ff3efce | ||
|
|
dd0e67540f | ||
|
|
8db41e7701 | ||
|
|
37569aae17 | ||
|
|
ffd6385dd5 | ||
|
|
abe3e0e7a5 | ||
|
|
70b5ed7067 | ||
|
|
9d8beb85d7 | ||
|
|
296fc484ba | ||
|
|
ad900fc8f1 | ||
|
|
f60c25aacf | ||
|
|
d6cf595899 | ||
|
|
9329ed1e37 | ||
|
|
90628dc5c1 | ||
|
|
73ae197d20 | ||
|
|
15fa6b82a5 | ||
|
|
d887e7fea5 | ||
|
|
871f0e73c0 | ||
|
|
d117cb8ed5 | ||
|
|
733d7fb55f | ||
|
|
6ae3481ae9 | ||
|
|
b7efd94147 | ||
|
|
a05b6a927e | ||
|
|
30648a7858 | ||
|
|
26bce5ddc0 | ||
|
|
bd708159a1 | ||
|
|
87ce637bd2 | ||
|
|
7698b1b5fd | ||
|
|
e256d886e0 | ||
|
|
6f268e3a40 | ||
|
|
57910470a9 | ||
|
|
890b6e9a28 | ||
|
|
b09b21e939 | ||
|
|
edf47a1737 | ||
|
|
5b8a1ce821 | ||
|
|
d47869ac2f | ||
|
|
8ea7d4cf84 | ||
|
|
fc49888ba2 | ||
|
|
d0d6a4d1a1 | ||
|
|
961370c43c | ||
|
|
902f4c957a | ||
|
|
7e1a4dea6c | ||
|
|
c78ef29bda | ||
|
|
305a4ab6dd | ||
|
|
7777107d83 | ||
|
|
67c72a0e86 | ||
|
|
e7a89a4a42 | ||
|
|
6761984f2c | ||
|
|
1630eafbe3 | ||
|
|
091bf3a64c | ||
|
|
e5a189b8c6 | ||
|
|
f68b34a485 | ||
|
|
e946b39960 | ||
|
|
0711d05232 | ||
|
|
9dd5dfdde2 | ||
|
|
024aa60209 | ||
|
|
32916f9a6f | ||
|
|
b670bb8d2c | ||
|
|
1abf6547ff | ||
|
|
3739fa401e | ||
|
|
377d62999f | ||
|
|
6f1f69683d | ||
|
|
b4bb4ca397 | ||
|
|
6b4fb434da | ||
|
|
9e70f53afa | ||
|
|
81885dec63 | ||
|
|
f59f3c3c83 | ||
|
|
18c66ae7ac | ||
|
|
d01c168450 | ||
|
|
f0a4a9e3c4 | ||
|
|
78e59a9400 | ||
|
|
a8f194259b | ||
|
|
c39fdcb8ac | ||
|
|
26e46a6e18 | ||
|
|
4ad7edc35e | ||
|
|
28eacdb2bb | ||
|
|
66b826a8e1 | ||
|
|
20c8a856a2 | ||
|
|
dd20bd5486 | ||
|
|
f8077d9fc4 | ||
|
|
16dfb7b5f5 | ||
|
|
aae0e16350 | ||
|
|
741a71b490 | ||
|
|
44ee578a3a | ||
|
|
f92f34dc54 | ||
|
|
dff9c9ad22 | ||
|
|
acae7eccc4 | ||
|
|
837739d7dd | ||
|
|
2567303fd7 | ||
|
|
4939ae1a2f | ||
|
|
58607e2ca0 | ||
|
|
cb021e3a25 | ||
|
|
d61936c06d | ||
|
|
3553eb1f2a | ||
|
|
c4a9560d53 | ||
|
|
bc63b7608e | ||
|
|
c2f508e261 | ||
|
|
a07dc0a511 | ||
|
|
d8db320722 | ||
|
|
24054f905f | ||
|
|
32ca815896 | ||
|
|
fa5949e191 | ||
|
|
1c0a98a0cc | ||
|
|
9d208e8795 | ||
|
|
72fe0cd6db | ||
|
|
734590b6bd | ||
|
|
7cc231e8b9 | ||
|
|
c2bf4ef7d4 | ||
|
|
439179e37f | ||
|
|
c333cb542c | ||
|
|
6a6546db8d | ||
|
|
a6d22a5a4b | ||
|
|
da5f94e62c | ||
|
|
1d886f2995 | ||
|
|
a6b6aa570e | ||
|
|
32b26a09c2 | ||
|
|
8fcaa4abf2 | ||
|
|
18e1f08156 | ||
|
|
bf5cc63bc5 | ||
|
|
271a94e0c7 | ||
|
|
c3f34db81e | ||
|
|
b7d8a769db | ||
|
|
c05af3b9b3 | ||
|
|
3e37a60635 | ||
|
|
9d47b6843f | ||
|
|
12ffac1f06 | ||
|
|
7991b4ab25 | ||
|
|
984fb702e5 | ||
|
|
019b88cedf | ||
|
|
55932cf3c7 | ||
|
|
1ff2c9bbd9 | ||
|
|
72235b0fd4 | ||
|
|
b8d391024a | ||
|
|
598bec0eab | ||
|
|
709bf7b246 | ||
|
|
8251c8f259 | ||
|
|
9c0bde0b29 | ||
|
|
2cbce963b7 | ||
|
|
64e6e686e0 | ||
|
|
dad3458ece | ||
|
|
e727e7adb6 | ||
|
|
af1c952700 | ||
|
|
ce2f7112a3 | ||
|
|
4650ed01eb | ||
|
|
0e2c73a36d | ||
|
|
139fed40dd | ||
|
|
ee88161808 | ||
|
|
04d54e8d82 | ||
|
|
c6c0d3d420 | ||
|
|
f4d70068b1 | ||
|
|
3fe5a8d0d6 | ||
|
|
29a124900c | ||
|
|
6670407c9c | ||
|
|
641b6067cd | ||
|
|
b1d61fa90b | ||
|
|
0c93cf2600 | ||
|
|
53753c0932 | ||
|
|
029cfaf1f8 | ||
|
|
66edc39ccb | ||
|
|
3f42f785ac | ||
|
|
fd4d32351b | ||
|
|
75d3895e84 | ||
|
|
d022d7f09e | ||
|
|
88b414b9a3 | ||
|
|
47c441ba40 | ||
|
|
7105e38544 | ||
|
|
4b0cdf9123 | ||
|
|
1c9fdc79c0 | ||
|
|
90d04990ca | ||
|
|
aa001ed2dc | ||
|
|
ce1c37dbca | ||
|
|
3a3bc568b3 | ||
|
|
b048b2684b | ||
|
|
9a1a81925e | ||
|
|
e38f3cc12c | ||
|
|
4fb356b19b | ||
|
|
cb6b8a9670 | ||
|
|
efc5237265 | ||
|
|
cb999af653 | ||
|
|
8d7d2fb1f1 | ||
|
|
eaff060bf9 | ||
|
|
6cc5adcd25 | ||
|
|
233625db20 | ||
|
|
96093984e4 | ||
|
|
ea0af4dc43 | ||
|
|
8fa5241a13 | ||
|
|
e38eb43955 | ||
|
|
3b46e88734 | ||
|
|
d6a58f33f3 | ||
|
|
d1c15bd0b7 | ||
|
|
1dc6ebbffc | ||
|
|
ee1e07f3d7 | ||
|
|
9e7b7fbb3a | ||
|
|
04fd330733 | ||
|
|
361b671799 | ||
|
|
b8f8ec8aa3 | ||
|
|
05d570d250 | ||
|
|
e8f11286dc | ||
|
|
48bc5637a0 | ||
|
|
7bfbe264c7 | ||
|
|
e37876afec | ||
|
|
02153356de | ||
|
|
a686c5f9aa | ||
|
|
ab7eb0a293 | ||
|
|
738485a655 | ||
|
|
0c751ea14f | ||
|
|
01f249d484 | ||
|
|
53f9f35233 | ||
|
|
9a890c7625 | ||
|
|
2bc23c468e | ||
|
|
6e1cde815c | ||
|
|
d1f1ed8957 | ||
|
|
a1a23b66c8 | ||
|
|
b4a8d0d783 | ||
|
|
fcc76809e3 | ||
|
|
1eae5cf272 | ||
|
|
ad1ee0f3dc | ||
|
|
0f99bfe7c4 | ||
|
|
dbba49540e | ||
|
|
5440ef7eb2 | ||
|
|
8d63b98212 | ||
|
|
d4b1e8f1be | ||
|
|
47d2a661bc | ||
|
|
7f46bcef61 | ||
|
|
4fd90b5d52 | ||
|
|
28c206da18 | ||
|
|
5e58735bb3 | ||
|
|
c06c391461 | ||
|
|
31c7144fff | ||
|
|
9175d26b3b | ||
|
|
f1d6f70cd2 | ||
|
|
b27b809ea9 | ||
|
|
1ec837089e | ||
|
|
089267837d | ||
|
|
c851322826 | ||
|
|
1b12db92ab | ||
|
|
6c0775ba59 | ||
|
|
0d381e4c91 | ||
|
|
7ccda7e99b | ||
|
|
567de2e115 | ||
|
|
cf49a9a6e7 | ||
|
|
41493993a2 | ||
|
|
1d10f85c28 | ||
|
|
d3d84524f5 | ||
|
|
cc04221516 | ||
|
|
f9bc50e262 | ||
|
|
9545e409d4 | ||
|
|
07bd8afc4a | ||
|
|
9ac8d7ec49 | ||
|
|
b4bdc8adee | ||
|
|
8299c04fc6 | ||
|
|
0b1b0a3f80 | ||
|
|
2773a621a2 | ||
|
|
2edae22a65 | ||
|
|
c06cf44fd7 | ||
|
|
7288e5d4a4 | ||
|
|
b62f501745 | ||
|
|
c199a51eeb | ||
|
|
b60d222c81 | ||
|
|
fb57fb714a | ||
|
|
bc390088b3 | ||
|
|
532e2ec8e1 | ||
|
|
6f4b6cec7e | ||
|
|
0b972c5b4b | ||
|
|
45e126bf07 | ||
|
|
318f28affd | ||
|
|
2214954c51 | ||
|
|
ec515f4fb5 | ||
|
|
3fb75707e7 | ||
|
|
c1850e0e20 | ||
|
|
b8d87e2d5a | ||
|
|
1f53fd85b4 | ||
|
|
564c93ee21 | ||
|
|
6769786241 | ||
|
|
0946e860f1 | ||
|
|
33366469a3 | ||
|
|
7d29efeb33 | ||
|
|
f0e9af1cf5 | ||
|
|
aec8e1db2d | ||
|
|
170fb64bfc | ||
|
|
b134c4b778 | ||
|
|
6a06f7b2ea | ||
|
|
5e0922395c | ||
|
|
514927c0b3 | ||
|
|
63f05e12ca | ||
|
|
b87d70c71c | ||
|
|
3f403d34a4 | ||
|
|
5d84e33be7 | ||
|
|
00a37b6289 | ||
|
|
963393e3d9 | ||
|
|
53a878bf61 | ||
|
|
5eac48094b | ||
|
|
90e8e02b0c | ||
|
|
0dcfe027b0 | ||
|
|
7d73c7fca9 | ||
|
|
d37862b123 | ||
|
|
df9b338279 | ||
|
|
97c7d176f0 | ||
|
|
f966b1ae6a | ||
|
|
8bc08d7716 | ||
|
|
ef4c0f961d | ||
|
|
42840827f8 | ||
|
|
13b6b9de60 | ||
|
|
74a21bab1a | ||
|
|
d1c7770949 | ||
|
|
66edf22ea3 | ||
|
|
8fffd10280 | ||
|
|
446dbde836 | ||
|
|
ef84a5a76a | ||
|
|
25a63956f3 | ||
|
|
eddd16283d | ||
|
|
c5efe7a17b | ||
|
|
7075b8b973 | ||
|
|
3f5957d30e | ||
|
|
bc29957d1e | ||
|
|
289835039a | ||
|
|
b1e08ef231 | ||
|
|
8a463767bf | ||
|
|
c598e4d289 | ||
|
|
a7624a2bf9 | ||
|
|
d20fcbd845 | ||
|
|
5d664855de | ||
|
|
8366cf0873 | ||
|
|
a41789a746 | ||
|
|
cde3251dd8 | ||
|
|
7c394bf735 | ||
|
|
76e0345609 | ||
|
|
d8820fa185 | ||
|
|
b6502693e4 | ||
|
|
f7e5402966 | ||
|
|
1e6a825412 | ||
|
|
c23065aacd | ||
|
|
04f5ba67a2 | ||
|
|
b87fa6715b | ||
|
|
f6f3298e03 | ||
|
|
6bfd781947 | ||
|
|
aff6331211 | ||
|
|
d547c39a16 | ||
|
|
3cea422365 | ||
|
|
ac39606386 | ||
|
|
12ae44d563 | ||
|
|
57b37d9005 | ||
|
|
9d5cc07567 | ||
|
|
75d4d767c6 | ||
|
|
0be4b86230 | ||
|
|
784d15b012 | ||
|
|
00f6045b12 | ||
|
|
b26f842de1 | ||
|
|
0ab2406db9 | ||
|
|
bf7e45439b | ||
|
|
0652273fe1 | ||
|
|
5927a54208 | ||
|
|
b46db59948 | ||
|
|
23b5a4729f | ||
|
|
8ae47bd490 | ||
|
|
1796d20399 | ||
|
|
5ac2447b85 | ||
|
|
db445c3a8e | ||
|
|
de2914978d | ||
|
|
09812a7bfc | ||
|
|
2eb3b541f4 | ||
|
|
e9791ff92c | ||
|
|
88516546fa | ||
|
|
9c75c55fa4 | ||
|
|
b9741e87bd | ||
|
|
c555478b54 | ||
|
|
3718372288 | ||
|
|
390b41bc26 | ||
|
|
530c5a64fb | ||
|
|
d285aaedc9 | ||
|
|
453fe18d7f | ||
|
|
5fae5cd308 | ||
|
|
7d7e225823 | ||
|
|
19f404799d | ||
|
|
3e4652dca3 | ||
|
|
45b08de874 | ||
|
|
310e26dda9 | ||
|
|
f4eb54b835 | ||
|
|
3968c12947 | ||
|
|
21c97d255f | ||
|
|
eb1053607a | ||
|
|
de7198e9dc | ||
|
|
0f30f47249 | ||
|
|
6b2ad8ebc8 | ||
|
|
1f302144ef | ||
|
|
d04c7a6ae4 | ||
|
|
9ca2cda8c7 | ||
|
|
1fd06ecbf9 | ||
|
|
97baad4cb1 | ||
|
|
fbd93ecf0d | ||
|
|
e941ccea92 | ||
|
|
d692483bc3 | ||
|
|
95cfbd0fff | ||
|
|
b3d1ed9e65 | ||
|
|
fe2b8c9fee | ||
|
|
2d7deef4e2 | ||
|
|
b4a70b02e3 | ||
|
|
c5c2364ed4 | ||
|
|
efcc4291a3 | ||
|
|
6ea6ee264d | ||
|
|
2865ba7632 | ||
|
|
2bed668d31 | ||
|
|
9dc24860f3 | ||
|
|
f01377b3bc | ||
|
|
7443dfac4c | ||
|
|
e6408e187c | ||
|
|
a02d282d3e | ||
|
|
f778f53744 | ||
|
|
95ea93564e | ||
|
|
d51029e86c | ||
|
|
1016699c94 | ||
|
|
63f63955e7 | ||
|
|
37be9fda9f | ||
|
|
0756133a7e | ||
|
|
83c5ab318b | ||
|
|
0c28957016 | ||
|
|
959084040d | ||
|
|
8a428c6936 | ||
|
|
48da23226c | ||
|
|
5f0c6e5102 | ||
|
|
29f1c6f50e | ||
|
|
4d187419ac | ||
|
|
58998f9cab | ||
|
|
cdc5ca8854 | ||
|
|
44e1e41266 | ||
|
|
33fda8605a | ||
|
|
5f9ed69299 | ||
|
|
7f1baea3b0 | ||
|
|
f75026e4b2 | ||
|
|
ce7a1a9c67 | ||
|
|
a62056fb19 | ||
|
|
f3434a8155 | ||
|
|
4e023ef517 | ||
|
|
97b80cb930 | ||
|
|
525b4e6a53 | ||
|
|
054eaf65b8 | ||
|
|
48fa796ab1 | ||
|
|
1873e022cc | ||
|
|
35a8062b58 | ||
|
|
636248ad67 | ||
|
|
4511c54fad | ||
|
|
7f3970541b | ||
|
|
4040f4d151 | ||
|
|
887d374c53 | ||
|
|
be4b87155d | ||
|
|
b987a7da4c | ||
|
|
7153fe5ad2 | ||
|
|
ccd8ba44d9 | ||
|
|
e7ef0f7a6c | ||
|
|
400b58c0e9 | ||
|
|
5257496214 | ||
|
|
e1bfe4a3ce | ||
|
|
f31cce8ec2 | ||
|
|
169ebfa72c | ||
|
|
7cace52ab5 | ||
|
|
95b93c60d9 | ||
|
|
5af1dcb8b1 | ||
|
|
6a61774fb7 | ||
|
|
ccbaca24f1 | ||
|
|
07b6048dc5 | ||
|
|
60dd34d066 | ||
|
|
28451d1e14 | ||
|
|
db95b6381f | ||
|
|
6b14c9bea4 | ||
|
|
742adc00fe | ||
|
|
52897cc16c | ||
|
|
c950568f1b | ||
|
|
845d7ff188 | ||
|
|
3bd8658da6 | ||
|
|
336a38081a | ||
|
|
01c2131436 | ||
|
|
c274231544 | ||
|
|
4a2864701c | ||
|
|
76ede10e0a | ||
|
|
274e01bb75 | ||
|
|
d75f763c99 | ||
|
|
5bc985663c | ||
|
|
df9e2e853f | ||
|
|
b4828a6f0a | ||
|
|
e99dd749a0 | ||
|
|
10ce7178c0 | ||
|
|
5c6a66eaf5 | ||
|
|
36d30bc985 | ||
|
|
a5152b82e9 | ||
|
|
e9af8a2595 | ||
|
|
84b5b60d49 | ||
|
|
8f60f42be3 | ||
|
|
583344138a | ||
|
|
016d021d5a | ||
|
|
115dc4bfa4 | ||
|
|
5b83febb23 | ||
|
|
c9d5c50402 | ||
|
|
fc839d2983 | ||
|
|
3bce96bbd5 | ||
|
|
6279be073b | ||
|
|
ea37132ce4 | ||
|
|
70eecd5289 | ||
|
|
380d03257f | ||
|
|
006de6da14 | ||
|
|
10aa80e8f5 | ||
|
|
013439af6d | ||
|
|
3408961155 | ||
|
|
f3b4a8d055 | ||
|
|
104af7e86f | ||
|
|
be39fbeff6 | ||
|
|
4109045fa4 | ||
|
|
90fd8023dd | ||
|
|
f67ad9c061 | ||
|
|
525e2bafee | ||
|
|
b65a9abf8e | ||
|
|
fec94aa53a | ||
|
|
3d4b345728 | ||
|
|
579975f08d | ||
|
|
3707b39fef | ||
|
|
f07387225b | ||
|
|
2648fb1bb1 | ||
|
|
d34715b4ba | ||
|
|
63af50bf98 | ||
|
|
456550c1d4 | ||
|
|
8174b88ec3 | ||
|
|
3233973748 | ||
|
|
bdfb1cf33e | ||
|
|
1c5fcd59e7 | ||
|
|
5cc960527e | ||
|
|
762c53fb8d | ||
|
|
ff20e67d07 | ||
|
|
c0cea013d1 | ||
|
|
5526bbba64 | ||
|
|
f0aa96ea8c | ||
|
|
e73007c398 | ||
|
|
fdc459ec5b | ||
|
|
bdb523ece1 | ||
|
|
164a9479ad | ||
|
|
e18adc781f | ||
|
|
33d89c2739 | ||
|
|
7cc9ab9083 | ||
|
|
4b4b7dc169 | ||
|
|
71ad5c5f05 | ||
|
|
39368bb5cb | ||
|
|
7a587ee8d1 | ||
|
|
77346527f3 | ||
|
|
1eba5833d5 | ||
|
|
83a747794e | ||
|
|
3e16d1da46 | ||
|
|
ae1860e859 | ||
|
|
2ebc8fdf2a | ||
|
|
be4023be66 | ||
|
|
7f4ad76298 | ||
|
|
0cbfaf98f3 | ||
|
|
631124e658 | ||
|
|
1685ee1ecb | ||
|
|
9b4d11f220 | ||
|
|
46a71296a9 | ||
|
|
1285588b62 | ||
|
|
d96392f65e | ||
|
|
d1c5a736ae | ||
|
|
6b1e038c5c | ||
|
|
eaab1aae28 | ||
|
|
31030343a2 | ||
|
|
325ca03a13 | ||
|
|
dea8e63df2 | ||
|
|
58421fd31a | ||
|
|
b961c96862 | ||
|
|
2d23c1b0f3 | ||
|
|
06952c224b | ||
|
|
2ea492c965 | ||
|
|
dbf84f6879 | ||
|
|
0fa3d6c462 | ||
|
|
d57f7aa03f | ||
|
|
d64f9f5401 | ||
|
|
a3029afc41 | ||
|
|
6a7d904fae | ||
|
|
d4043d3f86 | ||
|
|
b4902a4f58 | ||
|
|
ffe402f201 | ||
|
|
09cc7da282 | ||
|
|
2d2dad41f4 | ||
|
|
5f7c0a86dd | ||
|
|
fc1c631c98 | ||
|
|
89bdafacb8 | ||
|
|
73b6b3f129 | ||
|
|
b2a495f593 | ||
|
|
65ee904377 | ||
|
|
13f59230b5 | ||
|
|
36d2a0de1e | ||
|
|
a4db9fc8e5 | ||
|
|
9dae5ef83b | ||
|
|
e8842a740c | ||
|
|
0d3807ad09 | ||
|
|
5c27a249b7 | ||
|
|
7e41860b28 | ||
|
|
43ff92bbe7 | ||
|
|
28adc7e563 | ||
|
|
9788411995 | ||
|
|
0c9e8cc50e | ||
|
|
34d572c523 | ||
|
|
011b496b3f | ||
|
|
12b906eac6 | ||
|
|
20937d05c3 | ||
|
|
4943d37ccf | ||
|
|
3a8fd215de | ||
|
|
87572e8922 | ||
|
|
f1eedc7a01 | ||
|
|
b79e48dd77 | ||
|
|
18872194af | ||
|
|
bafd7ba282 | ||
|
|
b186481181 | ||
|
|
09ca6d11ad | ||
|
|
e68e4e786d | ||
|
|
ee638254c3 | ||
|
|
1e678905c4 | ||
|
|
10804c4b25 | ||
|
|
4bf9b4d41b | ||
|
|
1161872324 | ||
|
|
98cb570896 | ||
|
|
ed4ee3b58e | ||
|
|
066048f4de | ||
|
|
4b6b91c08b | ||
|
|
2980523a5b | ||
|
|
f2f9c043bf | ||
|
|
5d59cfd2c9 | ||
|
|
f94474e24f | ||
|
|
a63fc6d9ba | ||
|
|
076adeef80 | ||
|
|
a0e756317c | ||
|
|
252cb5f2f3 | ||
|
|
64288b4914 | ||
|
|
9ca6c6a315 | ||
|
|
3651ab5c0c | ||
|
|
b3f15e1ddc |
3
.ackrc
3
.ackrc
@@ -1,3 +1,6 @@
|
||||
--ignore-dir=old/
|
||||
--ignore-dir=tmp/
|
||||
--ignore-dir=vendor/
|
||||
--ignore-dir=releases/
|
||||
--ignore-dir=rpmbuild/
|
||||
--type-set=mcl:ext:mcl
|
||||
|
||||
@@ -23,3 +23,6 @@ indent_style = tab
|
||||
|
||||
[*.mcl]
|
||||
indent_style = tab
|
||||
|
||||
[*.txtar]
|
||||
indent_style = tab
|
||||
|
||||
5
.github/FUNDING.yml
vendored
5
.github/FUNDING.yml
vendored
@@ -1,2 +1,5 @@
|
||||
# You can add one username per supported platform and one custom link
|
||||
# You can add one username per supported platform and one custom link.
|
||||
custom: "https://paypal.me/purpleidea"
|
||||
github: purpleidea
|
||||
liberapay: purpleidea
|
||||
patreon: purpleidea
|
||||
|
||||
2
.github/settings.yml
vendored
2
.github/settings.yml
vendored
@@ -68,6 +68,8 @@ labels:
|
||||
color: e11d21
|
||||
- name: question
|
||||
color: cc317c
|
||||
- name: needinfo
|
||||
color: fbca04
|
||||
- name: wontfix
|
||||
color: ffffff
|
||||
# - name: first-timers-only
|
||||
|
||||
68
.github/workflows/test.yaml
vendored
Normal file
68
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
# Docs: https://help.github.com/en/articles/workflow-syntax-for-github-actions
|
||||
|
||||
# If the name is omitted, it uses the filename instead.
|
||||
#name: Test
|
||||
on:
|
||||
# Run on all pull requests.
|
||||
pull_request:
|
||||
#branches:
|
||||
#- master
|
||||
# Run on all pushes.
|
||||
push:
|
||||
# Run daily at 4am.
|
||||
schedule:
|
||||
- cron: 0 4 * * *
|
||||
|
||||
jobs:
|
||||
maketest:
|
||||
name: Test (${{ matrix.test_block }}) on ${{ matrix.os }} with golang ${{ matrix.golang_version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
GOPATH: /home/runner/work/mgmt/mgmt/go
|
||||
strategy:
|
||||
matrix:
|
||||
# TODO: Add tip when it's supported: https://github.com/actions/setup-go/issues/21
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# macos tests are currently failing in CI
|
||||
#- macos-latest
|
||||
golang_version:
|
||||
# TODO: add 1.21.x and tip
|
||||
# minimum required and latest published go_version
|
||||
- "1.20"
|
||||
test_block:
|
||||
- basic
|
||||
- shell
|
||||
- race
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
# Do not shallow fetch. The path can't be absolute, so we need to move it
|
||||
# to the expected location later.
|
||||
- name: Clone mgmt
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
path: ./go/src/github.com/purpleidea/mgmt
|
||||
|
||||
- name: Install Go ${{ matrix.golang_version }}
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.golang_version }}
|
||||
|
||||
# Install & configure ruby, fixes gem permissions error
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: head
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./go/src/github.com/purpleidea/mgmt
|
||||
run: |
|
||||
make deps
|
||||
|
||||
- name: Run test
|
||||
working-directory: ./go/src/github.com/purpleidea/mgmt
|
||||
run: |
|
||||
TEST_BLOCK="${{ matrix.test_block }}" make test
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,7 +7,6 @@ old/
|
||||
tmp/
|
||||
*WIP
|
||||
*_stringer.go
|
||||
bindata/*.go
|
||||
mgmt
|
||||
mgmt.static
|
||||
# crossbuild artifacts
|
||||
@@ -17,3 +16,5 @@ rpmbuild/
|
||||
releases/
|
||||
# vim swap files
|
||||
.*.sw[op]
|
||||
# prevent `echo foo 2>1` typo errors by making this file read-only
|
||||
1
|
||||
|
||||
33
.gitmodules
vendored
33
.gitmodules
vendored
@@ -1,33 +0,0 @@
|
||||
[submodule "vendor/github.com/coreos/etcd"]
|
||||
path = vendor/github.com/coreos/etcd
|
||||
url = https://github.com/coreos/etcd/
|
||||
[submodule "vendor/google.golang.org/grpc"]
|
||||
path = vendor/google.golang.org/grpc
|
||||
url = https://github.com/grpc/grpc-go
|
||||
[submodule "vendor/github.com/grpc-ecosystem/grpc-gateway"]
|
||||
path = vendor/github.com/grpc-ecosystem/grpc-gateway
|
||||
url = https://github.com/grpc-ecosystem/grpc-gateway
|
||||
[submodule "vendor/gopkg.in/fsnotify.v1"]
|
||||
path = vendor/gopkg.in/fsnotify.v1
|
||||
url = https://gopkg.in/fsnotify.v1
|
||||
[submodule "vendor/github.com/purpleidea/go-systemd"]
|
||||
path = vendor/github.com/purpleidea/go-systemd
|
||||
url = https://github.com/purpleidea/go-systemd
|
||||
[submodule "vendor/honnef.co/go/augeas"]
|
||||
path = vendor/honnef.co/go/augeas
|
||||
url = https://github.com/dominikh/go-augeas/
|
||||
[submodule "vendor/github.com/grpc-ecosystem/go-grpc-prometheus"]
|
||||
path = vendor/github.com/grpc-ecosystem/go-grpc-prometheus
|
||||
url = https://github.com/grpc-ecosystem/go-grpc-prometheus
|
||||
[submodule "vendor/github.com/ugorji/go"]
|
||||
path = vendor/github.com/ugorji/go
|
||||
url = https://github.com/ugorji/go
|
||||
[submodule "vendor/github.com/purpleidea/docker"]
|
||||
path = vendor/github.com/docker/docker
|
||||
url = https://github.com/purpleidea/docker
|
||||
[submodule "vendor/github.com/purpleidea/distribution"]
|
||||
path = vendor/github.com/docker/distribution
|
||||
url = https://github.com/purpleidea/distribution
|
||||
[submodule "vendor/github.com/purpleidea/go-connections"]
|
||||
path = vendor/github.com/docker/go-connections
|
||||
url = https://github.com/docker/go-connections
|
||||
14
.travis.yml
14
.travis.yml
@@ -24,21 +24,21 @@ install: 'make deps'
|
||||
matrix:
|
||||
fast_finish: false
|
||||
allow_failures:
|
||||
- go: 1.12.x
|
||||
- go: 1.21.x
|
||||
- go: tip
|
||||
- os: osx
|
||||
# include only one build for osx for a quicker build as the nr. of these runners are sparse
|
||||
include:
|
||||
- name: "basic tests"
|
||||
go: 1.11.x
|
||||
go: 1.20.x
|
||||
env: TEST_BLOCK=basic
|
||||
- name: "shell tests"
|
||||
go: 1.11.x
|
||||
go: 1.20.x
|
||||
env: TEST_BLOCK=shell
|
||||
- name: "race tests"
|
||||
go: 1.11.x
|
||||
go: 1.20.x
|
||||
env: TEST_BLOCK=race
|
||||
- go: 1.12.x
|
||||
- go: 1.21.x
|
||||
- go: tip
|
||||
- os: osx
|
||||
script: 'TEST_BLOCK="$TEST_BLOCK" make test'
|
||||
@@ -47,8 +47,8 @@ script: 'TEST_BLOCK="$TEST_BLOCK" make test'
|
||||
# with a value of: irc.freenode.net#mgmtconfig to eliminate noise from forks...
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- secure: htcuWAczm3C1zKC9vUfdRzhIXM1vtF+q0cLlQFXK1IQQlk693/pM30Mmf2L/9V2DVDeps+GyLdip0ARXD1DZEJV0lK+Ca1qbHdFP1r4Xv6l5+jaDb5Y88YU5LI8K758QShiZJojuQ1aO2j8xmmt9V0/5y5QwlpPeHbKYBOFPBX3HvlT9DhvwZNKGhBb4qJOEaPVOwq9IkN3DyQ456MHcJ3q3vF9Lb440uTuLsJNof2AbYZH8ZIHCSG2N8tBj2qhJOpWQboYtQJzE2pRaGkGBL4kYcHZSZMXX8sl4cBM1vx/IRUkvBxJUpLJz2gn/eRI+/gr59juZE2K0+FOLlx9dLnX626Y9xSViopBI6JsIoHJDqNC7aGaF2qaYulGYN65VNKVqmghjgt6JLmmiKeH10hYrJMMvt2rms8l4+5iwmCwXvhH/WU9edzk2p5wqERMnostJFEJib0zI3yzLoF0sdJs+veKtagzfayY2d2l7hlmt951IpqqVWldVgWUcQKVvi8gmRarbwFlK+5D7BEnkUDcLNly/cqf7BgEeX6YfF+FiR4pgfOhYvGCD+2q91NgWQXHBCxbyN0be1TVdkXD94f0Lkn94VyEJJ+PkPlG+rPgFwGcjqN4oEGkJeJmES2If05q2Ms1dJLwYQDL3+Py4lNMSdSWj24TzlFVhtwHepuw=
|
||||
#channels:
|
||||
# - secure: htcuWAczm3C1zKC9vUfdRzhIXM1vtF+q0cLlQFXK1IQQlk693/pM30Mmf2L/9V2DVDeps+GyLdip0ARXD1DZEJV0lK+Ca1qbHdFP1r4Xv6l5+jaDb5Y88YU5LI8K758QShiZJojuQ1aO2j8xmmt9V0/5y5QwlpPeHbKYBOFPBX3HvlT9DhvwZNKGhBb4qJOEaPVOwq9IkN3DyQ456MHcJ3q3vF9Lb440uTuLsJNof2AbYZH8ZIHCSG2N8tBj2qhJOpWQboYtQJzE2pRaGkGBL4kYcHZSZMXX8sl4cBM1vx/IRUkvBxJUpLJz2gn/eRI+/gr59juZE2K0+FOLlx9dLnX626Y9xSViopBI6JsIoHJDqNC7aGaF2qaYulGYN65VNKVqmghjgt6JLmmiKeH10hYrJMMvt2rms8l4+5iwmCwXvhH/WU9edzk2p5wqERMnostJFEJib0zI3yzLoF0sdJs+veKtagzfayY2d2l7hlmt951IpqqVWldVgWUcQKVvi8gmRarbwFlK+5D7BEnkUDcLNly/cqf7BgEeX6YfF+FiR4pgfOhYvGCD+2q91NgWQXHBCxbyN0be1TVdkXD94f0Lkn94VyEJJ+PkPlG+rPgFwGcjqN4oEGkJeJmES2If05q2Ms1dJLwYQDL3+Py4lNMSdSWj24TzlFVhtwHepuw=
|
||||
template:
|
||||
- "%{repository} (%{commit}: %{author}): %{message}"
|
||||
- "More info : %{build_url}"
|
||||
|
||||
2
AUTHORS
2
AUTHORS
@@ -6,7 +6,9 @@ This list is sorted alphabetically by first name.
|
||||
|
||||
Felix Frank
|
||||
James Shubin
|
||||
Joe Groocock
|
||||
Johan Bloemberg
|
||||
Jonathan Gold
|
||||
Julien Pivotto
|
||||
Paul Morgan
|
||||
Samuel Gélineau
|
||||
|
||||
8
COPYING
8
COPYING
@@ -1,7 +1,7 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
<https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
||||
16
COPYRIGHT
16
COPYRIGHT
@@ -1,5 +1,5 @@
|
||||
Mgmt
|
||||
Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,4 +13,16 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Additional permission under GNU GPL version 3 section 7
|
||||
|
||||
If you modify this program, or any covered work, by linking or combining it
|
||||
with embedded mcl code and modules (and that the embedded mcl code and
|
||||
modules which link with this program, contain a copy of their source code in
|
||||
the authoritative form) containing parts covered by the terms of any other
|
||||
license, the licensors of this program grant you additional permission to
|
||||
convey the resulting work. Furthermore, the licensors of this program grant
|
||||
the original author, James Shubin, additional permission to update this
|
||||
additional permission if he deems it necessary to achieve the goals of this
|
||||
additional permission.
|
||||
|
||||
333
Makefile
333
Makefile
@@ -1,5 +1,5 @@
|
||||
# Mgmt
|
||||
# Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
# Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
# Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,15 +13,31 @@
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or combining it
|
||||
# with embedded mcl code and modules (and that the embedded mcl code and
|
||||
# modules which link with this program, contain a copy of their source code in
|
||||
# the authoritative form) containing parts covered by the terms of any other
|
||||
# license, the licensors of this program grant you additional permission to
|
||||
# convey the resulting work. Furthermore, the licensors of this program grant
|
||||
# the original author, James Shubin, additional permission to update this
|
||||
# additional permission if he deems it necessary to achieve the goals of this
|
||||
# additional permission.
|
||||
|
||||
SHELL = /usr/bin/env bash
|
||||
.PHONY: all art cleanart version program lang path deps run race bindata generate build build-debug crossbuild clean test gofmt yamlfmt format docs rpmbuild mkdirs rpm srpm spec tar upload upload-sources upload-srpms upload-rpms upload-releases copr tag release funcgen
|
||||
.SILENT: clean bindata
|
||||
.PHONY: all art cleanart version program lang path deps run race generate build build-debug crossbuild clean test gofmt yamlfmt format docs
|
||||
.PHONY: rpmbuild mkdirs rpm srpm spec tar upload upload-sources upload-srpms upload-rpms upload-releases copr tag
|
||||
.PHONY: mkosi mkosi_fedora-latest mkosi_fedora-older mkosi_stream-latest mkosi_debian-stable mkosi_ubuntu-latest mkosi_archlinux
|
||||
.PHONY: release release_test releases_path release_binary_amd64 release_binary_arm64 release_fedora-latest release_fedora-older release_stream-latest release_debian-stable release_ubuntu-latest release_archlinux
|
||||
.PHONY: funcgen
|
||||
.SILENT: clean
|
||||
|
||||
# a large amount of output from this `find`, can cause `make` to be much slower!
|
||||
GO_FILES := $(shell find * -name '*.go' -not -path 'old/*' -not -path 'tmp/*')
|
||||
MCL_FILES := $(shell find lang/funcs/ -name '*.mcl' -not -path 'old/*' -not -path 'tmp/*')
|
||||
MCL_FILES := $(shell find lang/ -name '*.mcl' -not -path 'old/*' -not -path 'tmp/*')
|
||||
|
||||
SVERSION := $(or $(SVERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --dirty --always))
|
||||
VERSION := $(or $(VERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --abbrev=0))
|
||||
@@ -32,6 +48,13 @@ ifeq ($(VERSION),$(SVERSION))
|
||||
else
|
||||
RELEASE = untagged
|
||||
endif
|
||||
# debugging is harder if -trimpath is set, so disable it if env var is set
|
||||
# this is force enabled automatically when using the release target
|
||||
ifeq ($(MGMT_NOTRIMPATH),true)
|
||||
TRIMPATH =
|
||||
else
|
||||
TRIMPATH = -trimpath
|
||||
endif
|
||||
ARCH = $(uname -m)
|
||||
SPEC = rpmbuild/SPECS/$(PROGRAM).spec
|
||||
SOURCE = rpmbuild/SOURCES/$(PROGRAM)-$(VERSION).tar.bz2
|
||||
@@ -40,7 +63,7 @@ SRPM_BASE = $(PROGRAM)-$(VERSION)-$(RELEASE).src.rpm
|
||||
RPM = rpmbuild/RPMS/$(PROGRAM)-$(VERSION)-$(RELEASE).$(ARCH).rpm
|
||||
USERNAME := $(shell cat ~/.config/copr 2>/dev/null | grep username | awk -F '=' '{print $$2}' | tr -d ' ')
|
||||
SERVER = 'dl.fedoraproject.org'
|
||||
REMOTE_PATH = 'pub/alt/$(USERNAME)/$(PROGRAM)'
|
||||
REMOTE_PATH = '/srv/pub/alt/$(USERNAME)/$(PROGRAM)'
|
||||
ifneq ($(GOTAGS),)
|
||||
BUILD_FLAGS = -tags '$(GOTAGS)'
|
||||
endif
|
||||
@@ -49,9 +72,68 @@ GOOSARCHES ?= linux/amd64 linux/ppc64 linux/ppc64le linux/arm64 darwin/amd64
|
||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||
|
||||
RPM_PKG = releases/$(VERSION)/rpm/mgmt-$(VERSION)-1.x86_64.rpm
|
||||
DEB_PKG = releases/$(VERSION)/deb/mgmt_$(VERSION)_amd64.deb
|
||||
PACMAN_PKG = releases/$(VERSION)/pacman/mgmt-$(VERSION)-1-x86_64.pkg.tar.xz
|
||||
# The underscores separate the prefix name ("TOKEN") the distro ("BINARY",
|
||||
# "FEDORA-LATEST", etc...) and the arch ("AMD64"). The distro can have a dash.
|
||||
TOKEN_BINARY_AMD64 = $(shell grep -v '#' releases/binary-amd64.release)
|
||||
TOKEN_BINARY_ARM64 = $(shell grep -v '#' releases/binary-arm64.release)
|
||||
TOKEN_FEDORA-LATEST = $(shell grep -v '#' releases/fedora-latest.release)
|
||||
TOKEN_FEDORA-OLDER = $(shell grep -v '#' releases/fedora-older.release)
|
||||
TOKEN_STREAM-LATEST = $(shell grep -v '#' releases/stream-latest.release)
|
||||
TOKEN_DEBIAN-STABLE = $(shell grep -v '#' releases/debian-stable.release)
|
||||
TOKEN_UBUNTU-LATEST = $(shell grep -v '#' releases/ubuntu-latest.release)
|
||||
TOKEN_ARCHLINUX = $(shell grep -v '#' releases/archlinux.release)
|
||||
|
||||
FILE_BINARY_AMD64 = mgmt-linux-amd64-$(VERSION)
|
||||
FILE_BINARY_ARM64 = mgmt-linux-arm64-$(VERSION)
|
||||
# TODO: add ARCH onto these at the end, eg _AMD64
|
||||
FILE_FEDORA-LATEST = mgmt-$(TOKEN_FEDORA-LATEST)-$(VERSION)-1.x86_64.rpm
|
||||
FILE_FEDORA-OLDER = mgmt-$(TOKEN_FEDORA-OLDER)-$(VERSION)-1.x86_64.rpm
|
||||
FILE_STREAM-LATEST = mgmt-$(TOKEN_STREAM-LATEST)-$(VERSION)-1.x86_64.rpm
|
||||
FILE_DEBIAN-STABLE = mgmt_$(TOKEN_DEBIAN-STABLE)_$(VERSION)_amd64.deb
|
||||
FILE_UBUNTU-LATEST = mgmt_$(TOKEN_UBUNTU-LATEST)_$(VERSION)_amd64.deb
|
||||
FILE_ARCHLINUX = mgmt-$(TOKEN_ARCHLINUX)-$(VERSION)-1-x86_64.pkg.tar.xz
|
||||
|
||||
PKG_BINARY_AMD64 = releases/$(VERSION)/$(TOKEN_BINARY_AMD64)/$(FILE_BINARY_AMD64)
|
||||
PKG_BINARY_ARM64 = releases/$(VERSION)/$(TOKEN_BINARY_ARM64)/$(FILE_BINARY_ARM64)
|
||||
PKG_FEDORA-LATEST = releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/$(FILE_FEDORA-LATEST)
|
||||
PKG_FEDORA-OLDER = releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/$(FILE_FEDORA-OLDER)
|
||||
PKG_STREAM-LATEST = releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/$(FILE_STREAM-LATEST)
|
||||
PKG_DEBIAN-STABLE = releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/$(FILE_DEBIAN-STABLE)
|
||||
PKG_UBUNTU-LATEST = releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/$(FILE_UBUNTU-LATEST)
|
||||
PKG_ARCHLINUX = releases/$(VERSION)/$(TOKEN_ARCHLINUX)/$(FILE_ARCHLINUX)
|
||||
|
||||
DEP_BINARY_AMD64 =
|
||||
ifneq ($(TOKEN_BINARY_AMD64),)
|
||||
DEP_BINARY_AMD64 = $(PKG_BINARY_AMD64)
|
||||
endif
|
||||
DEP_BINARY_ARM64 =
|
||||
ifneq ($(TOKEN_BINARY_ARM64),)
|
||||
DEP_BINARY_ARM64 = $(PKG_BINARY_ARM64)
|
||||
endif
|
||||
DEP_FEDORA-LATEST =
|
||||
ifneq ($(TOKEN_FEDORA-LATEST),)
|
||||
DEP_FEDORA-LATEST = $(PKG_FEDORA-LATEST)
|
||||
endif
|
||||
DEP_FEDORA-OLDER =
|
||||
ifneq ($(TOKEN_FEDORA-OLDER),)
|
||||
DEP_FEDORA-OLDER = $(PKG_FEDORA-OLDER)
|
||||
endif
|
||||
DEP_STREAM-LATEST =
|
||||
ifneq ($(TOKEN_STREAM-LATEST),)
|
||||
DEP_STREAM-LATEST = $(PKG_STREAM-LATEST)
|
||||
endif
|
||||
DEP_DEBIAN-STABLE =
|
||||
ifneq ($(TOKEN_DEBIAN-STABLE),)
|
||||
DEP_DEBIAN-STABLE = $(PKG_DEBIAN-STABLE)
|
||||
endif
|
||||
DEP_UBUNTU-LATEST =
|
||||
ifneq ($(TOKEN_UBUNTU-LATEST),)
|
||||
DEP_UBUNTU-LATEST = $(PKG_UBUNTU-LATEST)
|
||||
endif
|
||||
DEP_ARCHLINUX =
|
||||
ifneq ($(TOKEN_ARCHLINUX),)
|
||||
DEP_ARCHLINUX = $(PKG_ARCHLINUX)
|
||||
endif
|
||||
|
||||
SHA256SUMS = releases/$(VERSION)/SHA256SUMS
|
||||
SHA256SUMS_ASC = $(SHA256SUMS).asc
|
||||
@@ -116,11 +198,6 @@ run: ## run mgmt
|
||||
race:
|
||||
find . -maxdepth 1 -type f -name '*.go' -not -name '*_test.go' | xargs go run -race -ldflags "-X main.program=$(PROGRAM) -X main.version=$(SVERSION)"
|
||||
|
||||
# generate go files from non-go source
|
||||
bindata: ## generate go files from non-go sources
|
||||
$(MAKE) --quiet -C bindata
|
||||
$(MAKE) --quiet -C lang/funcs
|
||||
|
||||
generate:
|
||||
go generate
|
||||
|
||||
@@ -132,10 +209,10 @@ lang: ## generates the lexer/parser for the language frontend
|
||||
$(PROGRAM): build/mgmt-${GOHOSTOS}-${GOHOSTARCH} ## build an mgmt binary for current host os/arch
|
||||
cp -a $< $@
|
||||
|
||||
$(PROGRAM).static: $(GO_FILES) $(MCL_FILES)
|
||||
$(PROGRAM).static: $(GO_FILES) $(MCL_FILES) go.mod go.sum
|
||||
@echo "Building: $(PROGRAM).static, version: $(SVERSION)..."
|
||||
go generate
|
||||
go build -a -installsuffix cgo -tags netgo -ldflags '-extldflags "-static" -X main.program=$(PROGRAM) -X main.version=$(SVERSION) -s -w' -o $(PROGRAM).static $(BUILD_FLAGS);
|
||||
go build $(TRIMPATH) -a -installsuffix cgo -tags netgo -ldflags '-extldflags "-static" -X main.program=$(PROGRAM) -X main.version=$(SVERSION) -s -w' -o $(PROGRAM).static $(BUILD_FLAGS);
|
||||
|
||||
build: LDFLAGS=-s -w ## build a fresh mgmt binary
|
||||
build: $(PROGRAM)
|
||||
@@ -147,26 +224,23 @@ build-debug: $(PROGRAM)
|
||||
# extract os and arch from target pattern
|
||||
GOOS=$(firstword $(subst -, ,$*))
|
||||
GOARCH=$(lastword $(subst -, ,$*))
|
||||
build/mgmt-%: $(GO_FILES) $(MCL_FILES) | bindata lang funcgen
|
||||
build/mgmt-%: $(GO_FILES) $(MCL_FILES) go.mod go.sum | lang funcgen
|
||||
@echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..."
|
||||
@# reassigning GOOS and GOARCH to make build command copy/pastable
|
||||
@# go 1.10+ requires specifying the package for ldflags
|
||||
@if go version | grep -qE 'go1.9'; then \
|
||||
time env GOOS=${GOOS} GOARCH=${GOARCH} go build -i -ldflags "-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS); \
|
||||
else \
|
||||
time env GOOS=${GOOS} GOARCH=${GOARCH} go build -i -ldflags=$(PKGNAME)="-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS); \
|
||||
fi
|
||||
time env GOOS=${GOOS} GOARCH=${GOARCH} go build $(TRIMPATH) -ldflags=$(PKGNAME)="-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS)
|
||||
|
||||
# create a list of binary file names to use as make targets
|
||||
# to use this you might want to run something like:
|
||||
# GOOSARCHES='linux/arm64' GOTAGS='noaugeas novirt' make crossbuild
|
||||
# and the output will end up in build/
|
||||
crossbuild_targets = $(addprefix build/mgmt-,$(subst /,-,${GOOSARCHES}))
|
||||
crossbuild: ${crossbuild_targets}
|
||||
|
||||
clean: ## clean things up
|
||||
$(MAKE) --quiet -C bindata clean
|
||||
$(MAKE) --quiet -C lang/funcs clean
|
||||
$(MAKE) --quiet -C test clean
|
||||
$(MAKE) --quiet -C lang clean
|
||||
rm -f lang/funcs/core/generated_funcs.go || true
|
||||
rm -f lang/funcs/core/generated_funcs_test.go || true
|
||||
$(MAKE) --quiet -C misc/mkosi clean
|
||||
rm -f lang/core/generated_funcs.go || true
|
||||
rm -f lang/core/generated_funcs_test.go || true
|
||||
[ ! -e $(PROGRAM) ] || rm $(PROGRAM)
|
||||
rm -f *_stringer.go # generated by `go generate`
|
||||
rm -f *_mock.go # generated by `go generate`
|
||||
@@ -174,6 +248,8 @@ clean: ## clean things up
|
||||
rm -f build/mgmt-*
|
||||
|
||||
test: build ## run tests
|
||||
@# recursively run make in child dir named test
|
||||
@$(MAKE) --quiet -C test
|
||||
./test.sh
|
||||
|
||||
# create all test targets for make tab completion (eg: make test-gofmt)
|
||||
@@ -343,18 +419,105 @@ copr: upload-srpms ## build in copr
|
||||
tag: ## tags a new release
|
||||
./misc/tag.sh
|
||||
|
||||
#
|
||||
# mkosi
|
||||
#
|
||||
mkosi: mkosi_fedora-latest mkosi_fedora-older mkosi_stream-latest mkosi_debian-stable mkosi_ubuntu-latest mkosi_archlinux ## builds distro packages via mkosi
|
||||
|
||||
mkosi_fedora-latest: releases/$(VERSION)/.mkdir
|
||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||
|
||||
mkosi_fedora-older: releases/$(VERSION)/.mkdir
|
||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||
|
||||
mkosi_stream-latest: releases/$(VERSION)/.mkdir
|
||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||
|
||||
mkosi_debian-stable: releases/$(VERSION)/.mkdir
|
||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||
|
||||
mkosi_ubuntu-latest: releases/$(VERSION)/.mkdir
|
||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||
|
||||
mkosi_archlinux: releases/$(VERSION)/.mkdir
|
||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||
|
||||
#
|
||||
# release
|
||||
#
|
||||
release: TRIMPATH = -trimpath
|
||||
release: releases/$(VERSION)/mgmt-release.url ## generates and uploads a release
|
||||
|
||||
releases/$(VERSION)/mgmt-release.url: $(RPM_PKG) $(DEB_PKG) $(PACMAN_PKG) $(SHA256SUMS_ASC)
|
||||
releases_path:
|
||||
@#Don't put any other output or dependencies in here or they'll show!
|
||||
@echo "releases/$(VERSION)/"
|
||||
|
||||
release_test: $(DEP_BINARY_AMD64) $(DEP_BINARY_ARM64) $(DEP_FEDORA-LATEST) $(DEP_FEDORA-OLDER) $(DEP_STREAM-LATEST) $(DEP_DEBIAN-STABLE) $(DEP_UBUNTU-LATEST) $(DEP_ARCHLINUX) $(SHA256SUMS_ASC)
|
||||
@echo '$$< denotes ‘the first dependency of the current rule’.'
|
||||
@echo '> '"$<"
|
||||
@echo
|
||||
@echo '$$@ denotes ‘the target of the current rule’.'
|
||||
@echo '> '"$@"
|
||||
@echo
|
||||
@echo '$$^ denotes ‘the dependencies of the current rule’.'
|
||||
@echo '> '"$^"
|
||||
@echo
|
||||
@echo '$$* denotes ‘the stem with which the pattern of the current rule matched’.'
|
||||
@echo '> '"$*"
|
||||
@echo
|
||||
@echo "TOKEN_BINARY_AMD64: $(TOKEN_BINARY_AMD64)"
|
||||
@echo "DEP_BINARY_AMD64: $(DEP_BINARY_AMD64)"
|
||||
@echo
|
||||
@echo "TOKEN_BINARY_ARM64: $(TOKEN_BINARY_ARM64)"
|
||||
@echo "DEP_BINARY_ARM64: $(DEP_BINARY_ARM64)"
|
||||
@echo
|
||||
@echo "TOKEN_FEDORA-LATEST: $(TOKEN_FEDORA-LATEST)"
|
||||
@echo "DEP_FEDORA-LATEST: $(DEP_FEDORA-LATEST)"
|
||||
@echo
|
||||
@echo "TOKEN_FEDORA-OLDER: $(TOKEN_FEDORA-OLDER)"
|
||||
@echo "DEP_FEDORA-OLDER: $(DEP_FEDORA-OLDER)"
|
||||
@echo
|
||||
@echo "TOKEN_STREAM-LATEST: $(TOKEN_STREAM-LATEST)"
|
||||
@echo "DEP_STREAM-LATEST: $(DEP_STREAM-LATEST)"
|
||||
@echo
|
||||
@echo "TOKEN_DEBIAN-STABLE: $(TOKEN_DEBIAN-STABLE)"
|
||||
@echo "DEP_DEBIAN-STABLE: $(DEP_DEBIAN-STABLE)"
|
||||
@echo
|
||||
@echo "TOKEN_UBUNTU-LATEST: $(TOKEN_UBUNTU-LATEST)"
|
||||
@echo "DEP_UBUNTU-LATEST: $(DEP_UBUNTU-LATEST)"
|
||||
@echo
|
||||
@echo "TOKEN_ARCHLINUX: $(TOKEN_ARCHLINUX)"
|
||||
@echo "DEP_ARCHLINUX: $(DEP_ARCHLINUX)"
|
||||
|
||||
release_binary_amd64: $(PKG_BINARY_AMD64)
|
||||
release_binary_arm64: $(PKG_BINARY_ARM64)
|
||||
release_fedora-latest: $(PKG_FEDORA-LATEST)
|
||||
release_fedora-older: $(PKG_FEDORA-OLDER)
|
||||
release_stream-latest: $(PKG_STREAM-LATEST)
|
||||
release_debian-stable: $(PKG_DEBIAN-STABLE)
|
||||
release_ubuntu-latest: $(PKG_UBUNTU-LATEST)
|
||||
release_archlinux: $(PKG_ARCHLINUX)
|
||||
|
||||
releases/$(VERSION)/mgmt-release.url: $(DEP_BINARY_AMD64) $(DEP_BINARY_ARM64) $(DEP_FEDORA-LATEST) $(DEP_FEDORA-OLDER) $(DEP_STREAM-LATEST) $(DEP_DEBIAN-STABLE) $(DEP_UBUNTU-LATEST) $(DEP_ARCHLINUX) $(SHA256SUMS_ASC)
|
||||
@echo "Pushing git tag $(VERSION) to origin..."
|
||||
git push origin $(VERSION)
|
||||
@echo "Creating github release..."
|
||||
hub release create \
|
||||
-F <( echo -e "$(VERSION)\n";echo "Verify the signatures of all packages before you use them. The signing key can be downloaded from https://purpleidea.com/contact/#pgp-key to verify the release." ) \
|
||||
-a $(RPM_PKG) \
|
||||
-a $(DEB_PKG) \
|
||||
-a $(PACMAN_PKG) \
|
||||
` [ -e $(PKG_BINARY_AMD64) ] && printf -- "-a $(PKG_BINARY_AMD64)" ` \
|
||||
` [ -e $(PKG_BINARY_ARM64) ] && printf -- "-a $(PKG_BINARY_ARM64)" ` \
|
||||
` [ -e $(PKG_FEDORA-LATEST) ] && printf -- "-a $(PKG_FEDORA-LATEST)" ` \
|
||||
` [ -e $(PKG_FEDORA-OLDER) ] && printf -- "-a $(PKG_FEDORA-OLDER)" ` \
|
||||
` [ -e $(PKG_STREAM-LATEST) ] && printf -- "-a $(PKG_STREAM-LATEST)" ` \
|
||||
` [ -e $(PKG_DEBIAN-STABLE) ] && printf -- "-a $(PKG_DEBIAN-STABLE)" ` \
|
||||
` [ -e $(PKG_UBUNTU-LATEST) ] && printf -- "-a $(PKG_UBUNTU-LATEST)" ` \
|
||||
` [ -e $(PKG_ARCHLINUX) ] && printf -- "-a $(PKG_ARCHLINUX)" ` \
|
||||
-a $(SHA256SUMS_ASC) \
|
||||
$(VERSION) \
|
||||
> releases/$(VERSION)/mgmt-release.url \
|
||||
@@ -362,32 +525,92 @@ releases/$(VERSION)/mgmt-release.url: $(RPM_PKG) $(DEB_PKG) $(PACMAN_PKG) $(SHA2
|
||||
|| rm -f releases/$(VERSION)/mgmt-release.url
|
||||
|
||||
releases/$(VERSION)/.mkdir:
|
||||
mkdir -p releases/$(VERSION)/{deb,rpm,pacman}/ && touch releases/$(VERSION)/.mkdir
|
||||
mkdir -p \
|
||||
` [ "$(TOKEN_BINARY_AMD64)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_BINARY_AMD64)/" ` \
|
||||
` [ "$(TOKEN_BINARY_ARM64)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_BINARY_ARM64)/" ` \
|
||||
` [ "$(TOKEN_FEDORA-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/" ` \
|
||||
` [ "$(TOKEN_FEDORA-OLDER)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/" ` \
|
||||
` [ "$(TOKEN_STREAM-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/" ` \
|
||||
` [ "$(TOKEN_DEBIAN-STABLE)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/" ` \
|
||||
` [ "$(TOKEN_UBUNTU-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/" ` \
|
||||
` [ "$(TOKEN_ARCHLINUX)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_ARCHLINUX)/" ` \
|
||||
&& touch releases/$(VERSION)/.mkdir
|
||||
|
||||
releases/$(VERSION)/rpm/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@echo "Generating: rpm changelog..."
|
||||
./misc/make-rpm-changelog.sh $(VERSION)
|
||||
# These are defined conditionally, since if the token is empty, they warn!
|
||||
ifneq ($(TOKEN_BINARY_AMD64),)
|
||||
$(PKG_BINARY_AMD64): build/mgmt-linux-amd64 releases/$(VERSION)/.mkdir
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; cp -a build/mgmt-linux-amd64 $(PKG_BINARY_AMD64)
|
||||
endif
|
||||
ifneq ($(TOKEN_BINARY_ARM64),)
|
||||
$(PKG_BINARY_ARM64): build/mgmt-linux-arm64 releases/$(VERSION)/.mkdir
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; cp -a build/mgmt-linux-arm64 $(PKG_BINARY_ARM64)
|
||||
endif
|
||||
ifneq ($(TOKEN_FEDORA-LATEST),)
|
||||
releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
||||
|
||||
$(RPM_PKG): releases/$(VERSION)/rpm/changelog
|
||||
@echo "Building: rpm package..."
|
||||
./misc/fpm-pack.sh rpm $(VERSION) libvirt-devel augeas-devel
|
||||
$(PKG_FEDORA-LATEST): releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/changelog
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-LATEST)" libvirt-devel augeas-devel
|
||||
endif
|
||||
ifneq ($(TOKEN_FEDORA-OLDER),)
|
||||
releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
||||
|
||||
releases/$(VERSION)/deb/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@echo "Generating: deb changelog..."
|
||||
./misc/make-deb-changelog.sh $(VERSION)
|
||||
$(PKG_FEDORA-OLDER): releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/changelog
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-OLDER)" libvirt-devel augeas-devel
|
||||
endif
|
||||
ifneq ($(TOKEN_STREAM-LATEST),)
|
||||
releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
||||
|
||||
$(DEB_PKG): releases/$(VERSION)/deb/changelog
|
||||
@echo "Building: deb package..."
|
||||
./misc/fpm-pack.sh deb $(VERSION) libvirt-dev libaugeas-dev
|
||||
$(PKG_STREAM-LATEST): releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/changelog
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_STREAM-LATEST)" libvirt-devel augeas-devel
|
||||
endif
|
||||
ifneq ($(TOKEN_DEBIAN-STABLE),)
|
||||
releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
|
||||
|
||||
$(PACMAN_PKG): $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@echo "Building: pacman package..."
|
||||
./misc/fpm-pack.sh pacman $(VERSION) libvirt augeas
|
||||
$(PKG_DEBIAN-STABLE): releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/changelog
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_DEBIAN-STABLE)" libvirt-dev libaugeas-dev
|
||||
endif
|
||||
ifneq ($(TOKEN_UBUNTU-LATEST),)
|
||||
releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
|
||||
|
||||
$(SHA256SUMS): $(RPM_PKG) $(DEB_PKG) $(PACMAN_PKG)
|
||||
$(PKG_UBUNTU-LATEST): releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/changelog
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_UBUNTU-LATEST)" libvirt-dev libaugeas-dev
|
||||
endif
|
||||
ifneq ($(TOKEN_ARCHLINUX),)
|
||||
$(PKG_ARCHLINUX): $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_ARCHLINUX)" libvirt augeas
|
||||
endif
|
||||
|
||||
$(SHA256SUMS): $(DEP_BINARY_AMD64) $(DEP_BINARY_ARM64) $(DEP_FEDORA-LATEST) $(DEP_FEDORA-OLDER) $(DEP_STREAM-LATEST) $(DEP_DEBIAN-STABLE) $(DEP_UBUNTU-LATEST) $(DEP_ARCHLINUX)
|
||||
@# remove the directory separator in the SHA256SUMS file
|
||||
@echo "Generating: sha256 sum..."
|
||||
sha256sum $(RPM_PKG) $(DEB_PKG) $(PACMAN_PKG) | awk -F '/| ' '{print $$1" "$$6}' > $(SHA256SUMS)
|
||||
sha256sum \
|
||||
` [ -e $(PKG_BINARY_AMD64) ] && printf -- "$(PKG_BINARY_AMD64)" ` \
|
||||
` [ -e $(PKG_BINARY_ARM64) ] && printf -- "$(PKG_BINARY_ARM64)" ` \
|
||||
` [ -e $(PKG_FEDORA-LATEST) ] && printf -- "$(PKG_FEDORA-LATEST)" ` \
|
||||
` [ -e $(PKG_FEDORA-OLDER) ] && printf -- "$(PKG_FEDORA-OLDER)" ` \
|
||||
` [ -e $(PKG_STREAM-LATEST) ] && printf -- "$(PKG_STREAM-LATEST)" ` \
|
||||
` [ -e $(PKG_DEBIAN-STABLE) ] && printf -- "$(PKG_DEBIAN-STABLE)" ` \
|
||||
` [ -e $(PKG_UBUNTU-LATEST) ] && printf -- "$(PKG_UBUNTU-LATEST)" ` \
|
||||
` [ -e $(PKG_ARCHLINUX) ] && printf -- "$(PKG_ARCHLINUX)" ` \
|
||||
| awk -F '/| ' '{print $$1" "$$6}' > $(SHA256SUMS)
|
||||
|
||||
$(SHA256SUMS_ASC): $(SHA256SUMS)
|
||||
@echo "Signing sha256 sum..."
|
||||
@@ -413,14 +636,10 @@ help: ## show this help screen
|
||||
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
@echo ''
|
||||
|
||||
funcgen: lang/funcs/core/generated_funcs_test.go lang/funcs/core/generated_funcs.go
|
||||
funcgen: lang/core/generated_funcs.go
|
||||
|
||||
lang/funcs/core/generated_funcs_test.go: lang/funcs/funcgen/*.go lang/funcs/core/funcgen.yaml lang/funcs/funcgen/templates/generated_funcs_test.go.tpl
|
||||
@echo "Generating: funcs test..."
|
||||
@go run lang/funcs/funcgen/*.go -templates lang/funcs/funcgen/templates/generated_funcs_test.go.tpl 2>/dev/null
|
||||
|
||||
lang/funcs/core/generated_funcs.go: lang/funcs/funcgen/*.go lang/funcs/core/funcgen.yaml lang/funcs/funcgen/templates/generated_funcs.go.tpl
|
||||
lang/core/generated_funcs.go: lang/funcs/funcgen/*.go lang/core/funcgen.yaml lang/funcs/funcgen/templates/generated_funcs.go.tpl
|
||||
@echo "Generating: funcs..."
|
||||
@go run lang/funcs/funcgen/*.go -templates lang/funcs/funcgen/templates/generated_funcs.go.tpl 2>/dev/null
|
||||
@go run `find lang/funcs/funcgen/ -maxdepth 1 -type f -name '*.go' -not -name '*_test.go'` -templates=lang/funcs/funcgen/templates/generated_funcs.go.tpl >/dev/null
|
||||
|
||||
# vim: ts=8
|
||||
|
||||
25
README.md
25
README.md
@@ -3,9 +3,10 @@
|
||||
[](art/)
|
||||
|
||||
[](https://goreportcard.com/report/github.com/purpleidea/mgmt)
|
||||
[](http://travis-ci.org/purpleidea/mgmt)
|
||||
[](https://godoc.org/github.com/purpleidea/mgmt)
|
||||
[](https://webchat.freenode.net/?channels=#mgmtconfig)
|
||||
[](https://github.com/purpleidea/mgmt/actions/)
|
||||
[](https://godocs.io/github.com/purpleidea/mgmt)
|
||||
[](https://matrix.to/#/#mgmtconfig:matrix.org)
|
||||
[](https://web.libera.chat/?channels=#mgmtconfig)
|
||||
[](https://www.patreon.com/purpleidea)
|
||||
[](https://liberapay.com/purpleidea/donate)
|
||||
|
||||
@@ -21,7 +22,7 @@ ensure that your file server is set to read-only when it's friday.
|
||||
import "datetime"
|
||||
$is_friday = datetime.weekday(datetime.now()) == "friday"
|
||||
file "/srv/files/" {
|
||||
state => "exists",
|
||||
state => $const.res.file.state.exists,
|
||||
mode => if $is_friday { # this updates the mode, the instant it changes!
|
||||
"0550"
|
||||
} else {
|
||||
@@ -65,11 +66,11 @@ Come join us in the `mgmt` community!
|
||||
|
||||
| Medium | Link |
|
||||
|---|---|
|
||||
| IRC | [#mgmtconfig](https://webchat.freenode.net/?channels=#mgmtconfig) on Freenode |
|
||||
| Matrix | [#mgmtconfig](https://matrix.to/#/#mgmtconfig:matrix.org) on Matrix.org |
|
||||
| IRC | [#mgmtconfig](https://web.libera.chat/?channels=#mgmtconfig) on Libera.Chat |
|
||||
| Twitter | [@mgmtconfig](https://twitter.com/mgmtconfig) & [#mgmtconfig](https://twitter.com/hashtag/mgmtconfig) |
|
||||
| Mailing list | [mgmtconfig-list@redhat.com](https://www.redhat.com/mailman/listinfo/mgmtconfig-list) |
|
||||
| Mailing list | [looking for a new home, suggestions welcome](https://gitlab.freedesktop.org/freedesktop/freedesktop/-/issues/1082) |
|
||||
| Patreon | [purpleidea](https://www.patreon.com/purpleidea) on Patreon |
|
||||
| Liberapay | [purpleidea](https://liberapay.com/purpleidea/donate) on Liberapay |
|
||||
|
||||
## Status:
|
||||
|
||||
@@ -97,8 +98,9 @@ Please read, enjoy and help improve our documentation!
|
||||
| [style guide](docs/style-guide.md) | for mgmt developers |
|
||||
| [godoc API reference](https://godoc.org/github.com/purpleidea/mgmt) | for mgmt developers |
|
||||
| [prometheus guide](docs/prometheus.md) | for everyone |
|
||||
| [puppet guide](docs/puppet-guide.md) | for puppet sysadmins |
|
||||
| [development](docs/development.md) | for mgmt developers |
|
||||
| [videos](docs/on-the-web.md) | for everyone |
|
||||
| [blogs](docs/on-the-web.md) | for everyone |
|
||||
|
||||
## Questions:
|
||||
|
||||
@@ -107,12 +109,13 @@ If you have a well phrased question that might benefit others, consider asking
|
||||
it by sending a patch to the [FAQ](docs/faq.md) section. I'll merge your
|
||||
question, and a patch with the answer!
|
||||
|
||||
## Roadmap:
|
||||
## Get involved:
|
||||
|
||||
Feel free to grab one of the straightforward [#mgmtlove](https://github.com/purpleidea/mgmt/labels/mgmtlove)
|
||||
issues if you're a first time contributor to the project or if you're unsure
|
||||
about what to hack on! Please get involved by working on one of these items or
|
||||
by suggesting something else!
|
||||
by suggesting something else! There are some lower priority issues and harder
|
||||
issues available in our [TODO](TODO.md) file. Please have a look.
|
||||
|
||||
## Bugs:
|
||||
|
||||
@@ -126,6 +129,6 @@ We'd love to have your patches! Please send them by email, or as a pull request.
|
||||
|
||||
## On the web:
|
||||
|
||||
[Read what people are saying and publishing about mgmt!](docs/on-the-web.md)
|
||||
[Blog posts and recorded talks about mgmt are listed here!](docs/on-the-web.md)
|
||||
|
||||
Happy hacking!
|
||||
|
||||
65
TODO.md
65
TODO.md
@@ -1,10 +1,18 @@
|
||||
# TODO
|
||||
|
||||
If you're looking for something to do, look here!
|
||||
Let us know if you're working on one of the items.
|
||||
If you'd like something to work on, ping @purpleidea and I'll create an issue
|
||||
tailored especially for you! Just let me know your approximate golang skill
|
||||
level and how many hours you'd like to spend on the patch.
|
||||
Here is a TODO list of longstanding items that are either lower-priority, or
|
||||
more involved in terms of time, skill-level, and/or motivation.
|
||||
|
||||
Please have a look, and let us know if you're working on one of the items. It's
|
||||
best to open an issue to track your progress and to discuss any implementation
|
||||
questions you might have.
|
||||
|
||||
Lastly, if you'd like something different to work on, please ping @purpleidea
|
||||
and I'll create an issue tailored especially for your approximate golang skill
|
||||
level and available time commitment in terms of hours you'd need to spend on the
|
||||
patch.
|
||||
|
||||
Happy Hacking!
|
||||
|
||||
## Package resource
|
||||
|
||||
@@ -19,7 +27,7 @@ level and how many hours you'd like to spend on the patch.
|
||||
|
||||
## Svc resource
|
||||
|
||||
- [ ] base resource improvements
|
||||
- [ ] refreshonly support [:heart:](https://github.com/purpleidea/mgmt/issues/464)
|
||||
|
||||
## Exec resource
|
||||
|
||||
@@ -33,33 +41,14 @@ level and how many hours you'd like to spend on the patch.
|
||||
|
||||
- [ ] automatic edges to file resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
|
||||
|
||||
## Virt (libvirt) resource
|
||||
|
||||
- [ ] base resource improvements [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
|
||||
|
||||
## Net (systemd-networkd) resource
|
||||
|
||||
- [ ] base resource
|
||||
|
||||
## Nspawn (systemd-nspawn) resource
|
||||
|
||||
- [ ] base resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
|
||||
|
||||
## Mount (systemd-mount) resource
|
||||
|
||||
- [ ] base resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
|
||||
|
||||
## Cron (systemd-timer) resource
|
||||
|
||||
- [ ] base resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
|
||||
|
||||
## Http resource
|
||||
|
||||
- [ ] base resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
|
||||
|
||||
## Etcd improvements
|
||||
|
||||
- [ ] fix embedded etcd master race
|
||||
- [ ] fix etcd race bug that only happens during CI testing (intermittently
|
||||
failing test case issue)
|
||||
|
||||
## Torrent/dht file transfer
|
||||
|
||||
@@ -69,17 +58,33 @@ level and how many hours you'd like to spend on the patch.
|
||||
|
||||
- [ ] base plumbing
|
||||
|
||||
## Resource improvements
|
||||
|
||||
- [ ] more reversible resources implemented
|
||||
- [ ] more "cloud" resources
|
||||
|
||||
## Language improvements
|
||||
|
||||
- [ ] more core functions
|
||||
- [ ] automatic language formatter, ala `gofmt`
|
||||
- [ ] gedit/gnome-builder/gtksourceview syntax highlighting
|
||||
- [ ] vim syntax highlighting
|
||||
- [x] emacs syntax highlighting: see `misc/emacs/`
|
||||
- [ ] emacs syntax highlighting: see `misc/emacs/` (needs updating)
|
||||
- [ ] exposed $error variable for feedback in the language
|
||||
- [ ] improve the printf function to add %[]s, %[]f ([]str, []float) and map,
|
||||
struct, nested etc... %v would be nice too!
|
||||
- [ ] add line/col/file annotations to AST so we can get locations of errors
|
||||
that the parser finds
|
||||
- [ ] add more error messages with the `%error` pattern in parser.y
|
||||
- [ ] we should have helper functions or language sugar to pull a field out of a
|
||||
struct, or a value out of a map, or an index out of a list, etc...
|
||||
|
||||
## Engine improvements
|
||||
|
||||
- [ ] add a "waiting for func" message in the func engine to notify the user
|
||||
about slow functions...
|
||||
|
||||
## Other
|
||||
|
||||
- [ ] better error/retry handling
|
||||
- [ ] deb package target in Makefile
|
||||
- [ ] reproducible builds
|
||||
- [ ] add your suggestions!
|
||||
|
||||
11
Vagrantfile
vendored
11
Vagrantfile
vendored
@@ -6,7 +6,7 @@ Vagrant.configure(2) do |config|
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
|
||||
config.vm.define "mgmt-dev" do |instance|
|
||||
instance.vm.box = "fedora/28-cloud-base"
|
||||
instance.vm.box = "bento/fedora-31"
|
||||
end
|
||||
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
@@ -23,8 +23,7 @@ Vagrant.configure(2) do |config|
|
||||
config.vm.provision "file", source: "vagrant/mgmt.bashrc", destination: ".mgmt.bashrc"
|
||||
config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig"
|
||||
|
||||
# copied from make-deps.sh (with added git)
|
||||
config.vm.provision "shell", inline: "dnf install -y libvirt-devel golang golang-googlecode-tools-stringer hg git make gem"
|
||||
config.vm.provision "shell", inline: "dnf install -y golang git make"
|
||||
|
||||
# set up packagekit
|
||||
config.vm.provision "shell" do |shell|
|
||||
@@ -39,8 +38,10 @@ Vagrant.configure(2) do |config|
|
||||
script = <<-SCRIPT
|
||||
grep -q 'mgmt\.bashrc' ~/.bashrc || echo '. ~/.mgmt.bashrc' >>~/.bashrc
|
||||
. ~/.mgmt.bashrc
|
||||
go get -u github.com/purpleidea/mgmt
|
||||
cd ~/gopath/src/github.com/purpleidea/mgmt
|
||||
mkdir -p ~/gopath/src/github.com/purpleidea
|
||||
cd ~/gopath/src/github.com/purpleidea
|
||||
git clone https://github.com/purpleidea/mgmt --recursive
|
||||
cd mgmt
|
||||
make deps
|
||||
SCRIPT
|
||||
config.vm.provision "shell" do |shell|
|
||||
|
||||
BIN
art/mgmt.png
BIN
art/mgmt.png
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 683 KiB |
BIN
art/mgmt_poohbear_meme.jpg
Normal file
BIN
art/mgmt_poohbear_meme.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -1,42 +0,0 @@
|
||||
# Mgmt
|
||||
# Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
# Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# The bindata target generates go files from any source defined below. To use
|
||||
# the files, import the generated "bindata" package and use:
|
||||
# `bytes, err := bindata.Asset("FILEPATH")`
|
||||
# where FILEPATH is the path of the original input file relative to `bindata/`.
|
||||
# To get a list of files stored in this "bindata" package, you can use:
|
||||
# `paths := bindata.AssetNames()` and `paths, err := bindata.AssetDir(name)`
|
||||
# to get a list of files with a directory prefix.
|
||||
|
||||
.PHONY: build clean
|
||||
default: build
|
||||
|
||||
build: bindata.go
|
||||
|
||||
# add more input files as dependencies at the end here...
|
||||
bindata.go: ../COPYING
|
||||
@echo "Generating: bindata..."
|
||||
# go-bindata --pkg bindata -o <OUTPUT> <INPUT>
|
||||
go-bindata --pkg bindata -o ./$@ $^
|
||||
# gofmt the output file
|
||||
gofmt -s -w $@
|
||||
@ROOT=$$(dirname "$${BASH_SOURCE}")/.. && $$ROOT/misc/header.sh '$@'
|
||||
|
||||
clean:
|
||||
# remove generated bindata.go
|
||||
@ROOT=$$(dirname "$${BASH_SOURCE}")/.. && rm -f bindata.go
|
||||
165
cli/cli.go
Normal file
165
cli/cli.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// Package cli handles all of the core command line parsing. It's the first
|
||||
// entry point after the real main function, and it imports and runs our core
|
||||
// "lib".
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
"github.com/alexflint/go-arg"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if _, err := arg.NewParser(arg.Config{}, &Args{}); err != nil { // sanity check
|
||||
panic(errwrap.Wrapf(err, "invalid args cli struct"))
|
||||
}
|
||||
}
|
||||
|
||||
// CLI is the entry point for using mgmt normally from the CLI.
|
||||
func CLI(ctx context.Context, data *cliUtil.Data) error {
|
||||
// test for sanity
|
||||
if data == nil {
|
||||
return fmt.Errorf("this CLI was not run correctly")
|
||||
}
|
||||
if data.Program == "" || data.Version == "" {
|
||||
return fmt.Errorf("program was not compiled correctly")
|
||||
}
|
||||
if data.Copying == "" {
|
||||
return fmt.Errorf("program copyrights were removed, can't run")
|
||||
}
|
||||
|
||||
args := Args{}
|
||||
args.version = data.Version // copy this in
|
||||
args.description = data.Tagline
|
||||
|
||||
config := arg.Config{
|
||||
Program: data.Program,
|
||||
}
|
||||
parser, err := arg.NewParser(config, &args)
|
||||
if err != nil {
|
||||
// programming error
|
||||
return errwrap.Wrapf(err, "cli config error")
|
||||
}
|
||||
err = parser.Parse(data.Args[1:]) // XXX: args[0] needs to be dropped
|
||||
if err == arg.ErrHelp {
|
||||
parser.WriteHelp(os.Stdout)
|
||||
return nil
|
||||
}
|
||||
if err == arg.ErrVersion {
|
||||
fmt.Printf("%s\n", data.Version) // byon: bring your own newline
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
//parser.WriteHelp(os.Stdout) // TODO: is doing this helpful?
|
||||
return cliUtil.CliParseError(err) // consistent errors
|
||||
}
|
||||
|
||||
// display the license
|
||||
if args.License {
|
||||
fmt.Printf("%s", data.Copying) // file comes with a trailing nl
|
||||
return nil
|
||||
}
|
||||
|
||||
if ok, err := args.Run(ctx, data); err != nil {
|
||||
return err
|
||||
} else if ok { // did we activate one of the commands?
|
||||
return nil
|
||||
}
|
||||
|
||||
// print help if no subcommands are set
|
||||
parser.WriteHelp(os.Stdout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args is the CLI parsing structure and type of the parsed result. This
|
||||
// particular struct is the top-most one.
|
||||
type Args struct {
|
||||
// XXX: We cannot have both subcommands and a positional argument.
|
||||
// XXX: I think it's a bug of this library that it can't handle argv[0].
|
||||
//Argv0 string `arg:"positional"`
|
||||
|
||||
License bool `arg:"--license" help:"display the license and exit"`
|
||||
|
||||
RunCmd *RunArgs `arg:"subcommand:run" help:"run code on this machine"`
|
||||
|
||||
DeployCmd *DeployArgs `arg:"subcommand:deploy" help:"deploy code into a cluster"`
|
||||
|
||||
// This never runs, it gets preempted in the real main() function.
|
||||
// XXX: Can we do it nicely with the new arg parser? can it ignore all args?
|
||||
EtcdCmd *EtcdArgs `arg:"subcommand:etcd" help:"run standalone etcd"`
|
||||
|
||||
// version is a private handle for our version string.
|
||||
version string `arg:"-"` // ignored from parsing
|
||||
|
||||
// description is a private handle for our description string.
|
||||
description string `arg:"-"` // ignored from parsing
|
||||
}
|
||||
|
||||
// Version returns the version string. Implementing this signature is part of
|
||||
// the API for the cli library.
|
||||
func (obj *Args) Version() string {
|
||||
return obj.version
|
||||
}
|
||||
|
||||
// Description returns a description string. Implementing this signature is part
|
||||
// of the API for the cli library.
|
||||
func (obj *Args) Description() string {
|
||||
return obj.description
|
||||
}
|
||||
|
||||
// Run executes the correct subcommand. It errors if there's ever an error. It
|
||||
// returns true if we did activate one of the subcommands. It returns false if
|
||||
// we did not. This information is used so that the top-level parser can return
|
||||
// usage or help information if no subcommand activates.
|
||||
func (obj *Args) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||
if cmd := obj.RunCmd; cmd != nil {
|
||||
return cmd.Run(ctx, data)
|
||||
}
|
||||
|
||||
if cmd := obj.DeployCmd; cmd != nil {
|
||||
return cmd.Run(ctx, data)
|
||||
}
|
||||
|
||||
// NOTE: we could return true, fmt.Errorf("...") if more than one did
|
||||
return false, nil // nobody activated
|
||||
}
|
||||
|
||||
// EtcdArgs is the CLI parsing structure and type of the parsed result. This
|
||||
// particular one is empty because the `etcd` subcommand is preempted in the
|
||||
// real main() function.
|
||||
type EtcdArgs struct{}
|
||||
255
cli/deploy.go
Normal file
255
cli/deploy.go
Normal file
@@ -0,0 +1,255 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||
"github.com/purpleidea/mgmt/etcd/client"
|
||||
"github.com/purpleidea/mgmt/etcd/deployer"
|
||||
etcdfs "github.com/purpleidea/mgmt/etcd/fs"
|
||||
"github.com/purpleidea/mgmt/gapi"
|
||||
"github.com/purpleidea/mgmt/lib"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
git "github.com/go-git/go-git/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// DeployArgs is the CLI parsing structure and type of the parsed result. This
|
||||
// particular one contains all the common flags for the `deploy` subcommand
|
||||
// which all frontends can use.
|
||||
type DeployArgs struct {
|
||||
Seeds []string `arg:"--seeds,env:MGMT_SEEDS" help:"default etc client endpoint"`
|
||||
Noop bool `arg:"--noop" help:"globally force all resources into no-op mode"`
|
||||
Sema int `arg:"--sema" default:"-1" help:"globally add a semaphore to all resources with this lock count"`
|
||||
NoGit bool `arg:"--no-git" help:"don't look at git commit id for safe deploys"`
|
||||
Force bool `arg:"--force" help:"force a new deploy, even if the safety chain would break"`
|
||||
|
||||
DeployEmpty *cliUtil.EmptyArgs `arg:"subcommand:empty" help:"deploy empty payload"`
|
||||
DeployLang *cliUtil.LangArgs `arg:"subcommand:lang" help:"deploy lang (mcl) payload"`
|
||||
DeployYaml *cliUtil.YamlArgs `arg:"subcommand:yaml" help:"deploy yaml graph payload"`
|
||||
}
|
||||
|
||||
// Run executes the correct subcommand. It errors if there's ever an error. It
|
||||
// returns true if we did activate one of the subcommands. It returns false if
|
||||
// we did not. This information is used so that the top-level parser can return
|
||||
// usage or help information if no subcommand activates. This particular Run is
|
||||
// the run for the main `deploy` subcommand. This always requires a frontend to
|
||||
// deploy to the cluster, but if you don't want a graph, you can use the `empty`
|
||||
// frontend. The engine backend is agnostic to which frontend is deployed, in
|
||||
// fact, you can deploy with multiple different frontends, one after another, on
|
||||
// the same engine.
|
||||
func (obj *DeployArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||
var name string
|
||||
var args interface{}
|
||||
if cmd := obj.DeployEmpty; cmd != nil {
|
||||
name = cliUtil.LookupSubcommand(obj, cmd) // "empty"
|
||||
args = cmd
|
||||
}
|
||||
if cmd := obj.DeployLang; cmd != nil {
|
||||
name = cliUtil.LookupSubcommand(obj, cmd) // "lang"
|
||||
args = cmd
|
||||
}
|
||||
if cmd := obj.DeployYaml; cmd != nil {
|
||||
name = cliUtil.LookupSubcommand(obj, cmd) // "yaml"
|
||||
args = cmd
|
||||
}
|
||||
|
||||
// XXX: workaround https://github.com/alexflint/go-arg/issues/239
|
||||
gapiNames := gapi.Names() // list of registered names
|
||||
if l := len(obj.Seeds); name == "" && l > 1 {
|
||||
elem := obj.Seeds[l-2] // second to last element
|
||||
if util.StrInList(elem, gapiNames) {
|
||||
return false, cliUtil.CliParseError(cliUtil.MissingEquals) // consistent errors
|
||||
}
|
||||
}
|
||||
|
||||
fn, exists := gapi.RegisteredGAPIs[name]
|
||||
if !exists {
|
||||
return false, nil // did not activate
|
||||
}
|
||||
gapiObj := fn()
|
||||
|
||||
program, version := data.Program, data.Version
|
||||
Logf := func(format string, v ...interface{}) {
|
||||
data.Flags.Logf("deploy: "+format, v...)
|
||||
}
|
||||
|
||||
// TODO: consider adding a timeout based on an args.Timeout flag ?
|
||||
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
cliUtil.Hello(program, version, data.Flags) // say hello!
|
||||
defer Logf("goodbye!")
|
||||
|
||||
var hash, pHash string
|
||||
if !obj.NoGit {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf(err, "could not get current working directory")
|
||||
}
|
||||
repo, err := git.PlainOpen(wd)
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf(err, "could not open git repo")
|
||||
}
|
||||
|
||||
head, err := repo.Head()
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf(err, "could not read git HEAD")
|
||||
}
|
||||
|
||||
hash = head.Hash().String() // current commit id
|
||||
Logf("hash: %s", hash)
|
||||
|
||||
lo := &git.LogOptions{
|
||||
From: head.Hash(),
|
||||
}
|
||||
commits, err := repo.Log(lo)
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf(err, "could not read git log")
|
||||
}
|
||||
if _, err := commits.Next(); err != nil { // skip over HEAD
|
||||
return false, errwrap.Wrapf(err, "could not read HEAD in git log") // weird!
|
||||
}
|
||||
commit, err := commits.Next()
|
||||
if err == nil { // errors are okay, we might be empty
|
||||
pHash = commit.Hash.String() // previous commit id
|
||||
}
|
||||
Logf("previous deploy hash: %s", pHash)
|
||||
if obj.Force {
|
||||
pHash = "" // don't check this :(
|
||||
}
|
||||
if hash == "" {
|
||||
return false, errwrap.Wrapf(err, "could not get git deploy hash")
|
||||
}
|
||||
}
|
||||
|
||||
uniqueid := uuid.New() // panic's if it can't generate one :P
|
||||
|
||||
etcdClient := client.NewClientFromSeedsNamespace(
|
||||
obj.Seeds, // endpoints
|
||||
lib.NS,
|
||||
)
|
||||
if err := etcdClient.Init(); err != nil {
|
||||
return false, errwrap.Wrapf(err, "client Init failed")
|
||||
}
|
||||
defer func() {
|
||||
err := errwrap.Wrapf(etcdClient.Close(), "client Close failed")
|
||||
if err != nil {
|
||||
// TODO: cause the final exit code to be non-zero
|
||||
Logf("client cleanup error: %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
simpleDeploy := &deployer.SimpleDeploy{
|
||||
Client: etcdClient,
|
||||
Debug: data.Flags.Debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
Logf("deploy: "+format, v...)
|
||||
},
|
||||
}
|
||||
if err := simpleDeploy.Init(); err != nil {
|
||||
return false, errwrap.Wrapf(err, "deploy Init failed")
|
||||
}
|
||||
defer func() {
|
||||
err := errwrap.Wrapf(simpleDeploy.Close(), "deploy Close failed")
|
||||
if err != nil {
|
||||
// TODO: cause the final exit code to be non-zero
|
||||
Logf("deploy cleanup error: %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// get max id (from all the previous deploys)
|
||||
max, err := simpleDeploy.GetMaxDeployID(ctx)
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf(err, "error getting max deploy id")
|
||||
}
|
||||
// find the latest id
|
||||
var id = max + 1 // next id
|
||||
Logf("previous max deploy id: %d", max)
|
||||
|
||||
etcdFs := &etcdfs.Fs{
|
||||
Client: etcdClient,
|
||||
// TODO: using a uuid is meant as a temporary measure, i hate them
|
||||
Metadata: lib.MetadataPrefix + fmt.Sprintf("/deploy/%d-%s", id, uniqueid),
|
||||
DataPrefix: lib.StoragePrefix,
|
||||
|
||||
Debug: data.Flags.Debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
Logf("fs: "+format, v...)
|
||||
},
|
||||
}
|
||||
|
||||
info := &gapi.Info{
|
||||
Args: args,
|
||||
Flags: &gapi.Flags{
|
||||
Noop: obj.Noop,
|
||||
Sema: obj.Sema,
|
||||
//Update: obj.Update,
|
||||
},
|
||||
|
||||
Fs: etcdFs,
|
||||
Debug: data.Flags.Debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
// TODO: is this a sane prefix to use here?
|
||||
data.Flags.Logf("cli: "+format, v...)
|
||||
},
|
||||
}
|
||||
|
||||
deploy, err := gapiObj.Cli(info)
|
||||
if err != nil {
|
||||
return false, cliUtil.CliParseError(err) // consistent errors
|
||||
}
|
||||
if deploy == nil { // not used
|
||||
return false, fmt.Errorf("not enough information specified")
|
||||
}
|
||||
|
||||
// redundant
|
||||
deploy.Noop = obj.Noop
|
||||
deploy.Sema = obj.Sema
|
||||
|
||||
str, err := deploy.ToB64()
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf(err, "encoding error")
|
||||
}
|
||||
|
||||
// this nominally checks the previous git hash matches our expectation
|
||||
if err := simpleDeploy.AddDeploy(ctx, id, hash, pHash, &str); err != nil {
|
||||
return false, errwrap.Wrapf(err, "could not create deploy id `%d`", id)
|
||||
}
|
||||
Logf("success, id: %d", id)
|
||||
return true, nil
|
||||
}
|
||||
233
cli/run.go
Normal file
233
cli/run.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||
"github.com/purpleidea/mgmt/gapi"
|
||||
"github.com/purpleidea/mgmt/lib"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// RunArgs is the CLI parsing structure and type of the parsed result. This
|
||||
// particular one contains all the common flags for the `run` subcommand which
|
||||
// all frontends can use.
|
||||
type RunArgs struct {
|
||||
lib.Config // embedded config (can't be a pointer) https://github.com/alexflint/go-arg/issues/240
|
||||
|
||||
RunEmpty *cliUtil.EmptyArgs `arg:"subcommand:empty" help:"run empty payload"`
|
||||
RunLang *cliUtil.LangArgs `arg:"subcommand:lang" help:"run lang (mcl) payload"`
|
||||
RunYaml *cliUtil.YamlArgs `arg:"subcommand:yaml" help:"run yaml graph payload"`
|
||||
}
|
||||
|
||||
// Run executes the correct subcommand. It errors if there's ever an error. It
|
||||
// returns true if we did activate one of the subcommands. It returns false if
|
||||
// we did not. This information is used so that the top-level parser can return
|
||||
// usage or help information if no subcommand activates. This particular Run is
|
||||
// the run for the main `run` subcommand. This always requires a frontend to
|
||||
// start the engine, but if you don't want a graph, you can use the `empty`
|
||||
// frontend. The engine backend is agnostic to which frontend is running, in
|
||||
// fact, you can deploy with multiple different frontends, one after another, on
|
||||
// the same engine.
|
||||
func (obj *RunArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||
var name string
|
||||
var args interface{}
|
||||
if cmd := obj.RunEmpty; cmd != nil {
|
||||
name = cliUtil.LookupSubcommand(obj, cmd) // "empty"
|
||||
args = cmd
|
||||
}
|
||||
if cmd := obj.RunLang; cmd != nil {
|
||||
name = cliUtil.LookupSubcommand(obj, cmd) // "lang"
|
||||
args = cmd
|
||||
}
|
||||
if cmd := obj.RunYaml; cmd != nil {
|
||||
name = cliUtil.LookupSubcommand(obj, cmd) // "yaml"
|
||||
args = cmd
|
||||
}
|
||||
|
||||
// XXX: workaround https://github.com/alexflint/go-arg/issues/239
|
||||
lists := [][]string{
|
||||
obj.Seeds,
|
||||
obj.ClientURLs,
|
||||
obj.ServerURLs,
|
||||
obj.AdvertiseClientURLs,
|
||||
obj.AdvertiseServerURLs,
|
||||
}
|
||||
gapiNames := gapi.Names() // list of registered names
|
||||
for _, list := range lists {
|
||||
if l := len(list); name == "" && l > 1 {
|
||||
elem := list[l-2] // second to last element
|
||||
if util.StrInList(elem, gapiNames) {
|
||||
return false, cliUtil.CliParseError(cliUtil.MissingEquals) // consistent errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn, exists := gapi.RegisteredGAPIs[name]
|
||||
if !exists {
|
||||
return false, nil // did not activate
|
||||
}
|
||||
gapiObj := fn()
|
||||
|
||||
main := &lib.Main{}
|
||||
main.Config = &obj.Config // pass in all the parsed data
|
||||
|
||||
main.Program, main.Version = data.Program, data.Version
|
||||
main.Debug, main.Logf = data.Flags.Debug, data.Flags.Logf // no prefix
|
||||
Logf := func(format string, v ...interface{}) {
|
||||
data.Flags.Logf("main: "+format, v...)
|
||||
}
|
||||
|
||||
cliUtil.Hello(main.Program, main.Version, data.Flags) // say hello!
|
||||
defer Logf("goodbye!")
|
||||
|
||||
// create a memory backed temporary filesystem for storing runtime data
|
||||
mmFs := afero.NewMemMapFs()
|
||||
afs := &afero.Afero{Fs: mmFs} // wrap so that we're implementing ioutil
|
||||
standaloneFs := &util.AferoFs{Afero: afs}
|
||||
main.DeployFs = standaloneFs
|
||||
|
||||
info := &gapi.Info{
|
||||
Args: args,
|
||||
Flags: &gapi.Flags{
|
||||
Hostname: obj.Hostname,
|
||||
Noop: obj.Noop,
|
||||
Sema: obj.Sema,
|
||||
//Update: obj.Update,
|
||||
},
|
||||
|
||||
Fs: standaloneFs,
|
||||
Debug: data.Flags.Debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
data.Flags.Logf("cli: "+format, v...)
|
||||
},
|
||||
}
|
||||
|
||||
deploy, err := gapiObj.Cli(info)
|
||||
if err != nil {
|
||||
return false, cliUtil.CliParseError(err) // consistent errors
|
||||
}
|
||||
|
||||
if cmd := obj.RunLang; cmd != nil && cmd.OnlyUnify && deploy == nil {
|
||||
return true, nil // we end early
|
||||
}
|
||||
if cmd := obj.RunLang; cmd != nil && cmd.OnlyDownload && deploy == nil {
|
||||
return true, nil // we end early
|
||||
}
|
||||
main.Deploy = deploy
|
||||
if main.Deploy == nil {
|
||||
// nobody activated, but we'll still watch the etcd deploy chan,
|
||||
// and if there is deployed code that's ready to run, we'll run!
|
||||
data.Flags.Logf("main: no frontend selected (no GAPI activated)")
|
||||
}
|
||||
|
||||
if err := main.Validate(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := main.Init(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// install the exit signal handler
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
exit := make(chan struct{})
|
||||
defer close(exit)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// must have buffer for max number of signals
|
||||
signals := make(chan os.Signal, 3+1) // 3 * ^C + 1 * SIGTERM
|
||||
signal.Notify(signals, os.Interrupt) // catch ^C
|
||||
//signal.Notify(signals, os.Kill) // catch signals
|
||||
signal.Notify(signals, syscall.SIGTERM)
|
||||
var count uint8
|
||||
for {
|
||||
select {
|
||||
case sig := <-signals: // any signal will do
|
||||
if sig != os.Interrupt {
|
||||
data.Flags.Logf("interrupted by signal")
|
||||
main.Interrupt(fmt.Errorf("killed by %v", sig))
|
||||
return
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
data.Flags.Logf("interrupted by ^C")
|
||||
main.Exit(nil)
|
||||
case 1:
|
||||
data.Flags.Logf("interrupted by ^C (fast pause)")
|
||||
main.FastExit(nil)
|
||||
case 2:
|
||||
data.Flags.Logf("interrupted by ^C (hard interrupt)")
|
||||
main.Interrupt(nil)
|
||||
}
|
||||
count++
|
||||
|
||||
case <-exit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
reterr := main.Run()
|
||||
if reterr != nil {
|
||||
// log the error message returned
|
||||
if data.Flags.Debug {
|
||||
data.Flags.Logf("main: %+v", reterr)
|
||||
}
|
||||
}
|
||||
|
||||
if err := main.Close(); err != nil {
|
||||
if data.Flags.Debug {
|
||||
data.Flags.Logf("main: Close: %+v", err)
|
||||
}
|
||||
if reterr == nil {
|
||||
return false, err
|
||||
}
|
||||
reterr = errwrap.Append(reterr, err)
|
||||
}
|
||||
|
||||
if reterr != nil {
|
||||
return false, reterr
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
104
cli/util/args.go
Normal file
104
cli/util/args.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LookupSubcommand returns the name of the subcommand in the obj, of a struct.
|
||||
// This is useful for determining the name of the subcommand that was activated.
|
||||
// It returns an empty string if a specific name was not found.
|
||||
func LookupSubcommand(obj interface{}, st interface{}) string {
|
||||
val := reflect.ValueOf(obj)
|
||||
if val.Kind() == reflect.Ptr { // max one de-referencing
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(st) // value of the struct
|
||||
typ := val.Type()
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
f := val.Field(i) // value of the field
|
||||
if f.Interface() != v.Interface() {
|
||||
continue
|
||||
}
|
||||
|
||||
field := typ.Field(i)
|
||||
alias, ok := field.Tag.Lookup("arg")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// XXX: `arg` needs a split by comma first or fancier parsing
|
||||
prefix := "subcommand"
|
||||
split := strings.Split(alias, ":")
|
||||
if len(split) != 2 || split[0] != prefix {
|
||||
continue
|
||||
}
|
||||
|
||||
return split[1] // found
|
||||
}
|
||||
return "" // not found
|
||||
}
|
||||
|
||||
// EmptyArgs is the empty CLI parsing structure and type of the parsed result.
|
||||
type EmptyArgs struct{}
|
||||
|
||||
// LangArgs is the lang CLI parsing structure and type of the parsed result.
|
||||
type LangArgs struct {
|
||||
// Input is the input mcl code or file path or any input specification.
|
||||
Input string `arg:"positional,required"`
|
||||
|
||||
// TODO: removed (temporarily?)
|
||||
//Stdin bool `arg:"--stdin" help:"use passthrough stdin"`
|
||||
|
||||
Download bool `arg:"--download" help:"download any missing imports"`
|
||||
OnlyDownload bool `arg:"--only-download" help:"stop after downloading any missing imports"`
|
||||
Update bool `arg:"--update" help:"update all dependencies to the latest versions"`
|
||||
|
||||
OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"`
|
||||
SkipUnify bool `arg:"--skip-unify" help:"skip type unification"`
|
||||
UnifySolver *string `arg:"--unify-name" help:"pick a specific unification solver"`
|
||||
UnifyOptimizations []string `arg:"--unify-optimizations" help:"list of unification optimizations to request (experts only)"`
|
||||
|
||||
Depth int `arg:"--depth" default:"-1" help:"max recursion depth limit (-1 is unlimited)"`
|
||||
|
||||
// The default of 0 means any error is a failure by default.
|
||||
Retry int `arg:"--depth" help:"max number of retries (-1 is unlimited)"`
|
||||
|
||||
ModulePath string `arg:"--module-path,env:MGMT_MODULE_PATH" help:"choose the modules path (absolute)"`
|
||||
}
|
||||
|
||||
// YamlArgs is the yaml CLI parsing structure and type of the parsed result.
|
||||
type YamlArgs struct {
|
||||
// Input is the input yaml code or file path or any input specification.
|
||||
Input string `arg:"positional,required"`
|
||||
}
|
||||
47
cli/util/hello.go
Normal file
47
cli/util/hello.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Hello is a simple helper function to print a hello message and time.
|
||||
func Hello(program, version string, flags Flags) {
|
||||
var start = time.Now().UnixNano()
|
||||
if program == "" {
|
||||
program = "<unknown>"
|
||||
}
|
||||
fmt.Println(fmt.Sprintf("This is: %s, version: %s", program, version))
|
||||
fmt.Println("Copyright (C) 2013-2024+ James Shubin and the project contributors")
|
||||
fmt.Println("Written by James Shubin <james@shubin.ca> and the project contributors")
|
||||
flags.Logf("main: start: %v", start)
|
||||
}
|
||||
98
cli/util/util.go
Normal file
98
cli/util/util.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// Package util has some CLI related utility code.
|
||||
package util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
// Error is a constant error type that implements error.
|
||||
type Error string
|
||||
|
||||
// Error fulfills the error interface of this type.
|
||||
func (e Error) Error() string { return string(e) }
|
||||
|
||||
const (
|
||||
// MissingEquals means we probably hit the parsing bug.
|
||||
// XXX: see: https://github.com/alexflint/go-arg/issues/239
|
||||
MissingEquals = Error("missing equals sign for list element")
|
||||
)
|
||||
|
||||
// CliParseError returns a consistent error if we have a CLI parsing issue.
|
||||
func CliParseError(err error) error {
|
||||
return errwrap.Wrapf(err, "cli parse error")
|
||||
}
|
||||
|
||||
// Flags are some constant flags which are used throughout the program.
|
||||
type Flags struct {
|
||||
Debug bool // add additional log messages
|
||||
Logf func(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// Data is a struct of values that we usually pass to the main CLI function.
|
||||
type Data struct {
|
||||
Program string
|
||||
Version string
|
||||
Copying string
|
||||
Tagline string
|
||||
Flags Flags
|
||||
Args []string // os.Args usually
|
||||
}
|
||||
|
||||
// SafeProgram returns the correct program string when given a buggy variant.
|
||||
func SafeProgram(program string) string {
|
||||
// FIXME: in sub commands, the cli package appends a space and the sub
|
||||
// command name at the end. hack around this by only using the first bit
|
||||
// see: https://github.com/urfave/cli/issues/783 for more details...
|
||||
split := strings.Split(program, " ")
|
||||
program = split[0]
|
||||
//if program == "" {
|
||||
// program = "<unknown>"
|
||||
//}
|
||||
return program
|
||||
}
|
||||
|
||||
// LogSetup changes some of the core logger package settings.
|
||||
func LogSetup(debug bool) {
|
||||
// TODO: Move these log package initialization steps to the top main.go?
|
||||
logFlags := log.LstdFlags
|
||||
if debug {
|
||||
logFlags = logFlags + log.Lshortfile
|
||||
}
|
||||
logFlags = logFlags - log.Ldate // remove the date for now
|
||||
log.SetFlags(logFlags)
|
||||
|
||||
log.SetOutput(os.Stderr)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// Package converger is a facility for reporting the converged state.
|
||||
package converger
|
||||
@@ -29,7 +41,7 @@ import (
|
||||
)
|
||||
|
||||
// New builds a new converger coordinator.
|
||||
func New(timeout int64) *Coordinator {
|
||||
func New(timeout int) *Coordinator {
|
||||
return &Coordinator{
|
||||
timeout: timeout,
|
||||
|
||||
@@ -61,7 +73,7 @@ func New(timeout int64) *Coordinator {
|
||||
type Coordinator struct {
|
||||
// timeout must be zero (instant) or greater seconds to run. If it's -1
|
||||
// then this is disabled, and we never run stateFns.
|
||||
timeout int64
|
||||
timeout int
|
||||
|
||||
// mutex is used for controlling access to status and lastid.
|
||||
mutex *sync.RWMutex
|
||||
@@ -365,7 +377,7 @@ func (obj *Coordinator) Status() map[*UID]bool {
|
||||
// Timeout returns the timeout in seconds that converger was created with. This
|
||||
// is useful to avoid passing in the timeout value separately when you're
|
||||
// already passing in the Coordinator struct.
|
||||
func (obj *Coordinator) Timeout() int64 {
|
||||
func (obj *Coordinator) Timeout() int {
|
||||
return obj.timeout
|
||||
}
|
||||
|
||||
@@ -375,7 +387,7 @@ func (obj *Coordinator) Timeout() int64 {
|
||||
type UID struct {
|
||||
// timeout is a copy of the main timeout. It could eventually be used
|
||||
// for per-UID timeouts too.
|
||||
timeout int64
|
||||
timeout int
|
||||
// isConverged stores the convergence state of this particular UID.
|
||||
isConverged bool
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,9 +13,21 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// +build !root
|
||||
//go:build !root
|
||||
|
||||
package converger
|
||||
|
||||
|
||||
14
debian/copyright
vendored
14
debian/copyright
vendored
@@ -3,7 +3,7 @@ Upstream-Name: mgmt
|
||||
Source: <https://github.com/purpleidea/mgmt>
|
||||
|
||||
Files: *
|
||||
Copyright: Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
Copyright: Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
License: GPL-3.0
|
||||
|
||||
License: GPL-3.0
|
||||
@@ -19,3 +19,15 @@ License: GPL-3.0
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Additional permission under GNU GPL version 3 section 7
|
||||
|
||||
If you modify this program, or any covered work, by linking or combining it
|
||||
with embedded mcl code and modules (and that the embedded mcl code and
|
||||
modules which link with this program, contain a copy of their source code in
|
||||
the authoritative form) containing parts covered by the terms of any other
|
||||
license, the licensors of this program grant you additional permission to
|
||||
convey the resulting work. Furthermore, the licensors of this program grant
|
||||
the original author, James Shubin, additional permission to update this
|
||||
additional permission if he deems it necessary to achieve the goals of this
|
||||
additional permission.
|
||||
|
||||
16
doc.go
16
doc.go
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// Package main provides the main entrypoint for using the `mgmt` software.
|
||||
package main
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
FROM golang:1.9
|
||||
FROM golang:1.20
|
||||
|
||||
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>
|
||||
|
||||
# Set the reset cache variable
|
||||
# Read more here: http://czerasz.com/2014/11/13/docker-tip-and-tricks/#use-refreshedat-variable-for-better-cache-control
|
||||
ENV REFRESHED_AT 2017-11-16
|
||||
ENV REFRESHED_AT 2020-09-23
|
||||
|
||||
# Update the package list to be able to use required packages
|
||||
RUN apt-get update
|
||||
|
||||
20
docker/Dockerfile-fedora.build
Normal file
20
docker/Dockerfile-fedora.build
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM fedora:38
|
||||
LABEL org.opencontainers.image.authors="laurent.indermuehle@pm.me"
|
||||
|
||||
ENV GOPATH=/root/gopath
|
||||
ENV PATH=/root/gopath/bin:/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/go/bin:/usr/local/bin
|
||||
ENV LD_LIBRARY_PATH=/usr/lib64
|
||||
ENV PKG_CONFIG_PATH=/usr/lib64/pkgconfig
|
||||
|
||||
# This forces make-deps.sh to install Ragel 6.1 instead of 7.0
|
||||
ENV DOCKER=true
|
||||
|
||||
RUN dnf -y install wget unzip git make which gcc gcc-c++ ruby golang
|
||||
|
||||
RUN mkdir -p $GOPATH/src/github.com/purpleidea
|
||||
WORKDIR $GOPATH/src/github.com/purpleidea
|
||||
RUN git clone --recursive https://github.com/purpleidea/mgmt mgmt
|
||||
WORKDIR $GOPATH/src/github.com/purpleidea/mgmt
|
||||
RUN make deps
|
||||
RUN make build
|
||||
CMD ["/bin/bash"]
|
||||
@@ -6,7 +6,7 @@ ENV PATH=/opt/rh/rh-ruby22/root/usr/bin:/root/gopath/bin:/usr/local/sbin:/sbin:/
|
||||
ENV LD_LIBRARY_PATH=/opt/rh/rh-ruby22/root/usr/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
|
||||
ENV PKG_CONFIG_PATH=/opt/rh/rh-ruby22/root/usr/lib64/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}
|
||||
|
||||
RUN yum -y install epel-release wget unzip git make which centos-release-scl gcc && sed -i "s/enabled=0/enabled=1/" /etc/yum.repos.d/epel-testing.repo && yum -y install rh-ruby22 && wget -O /opt/go1.9.1.linux-amd64.tar.gz https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz && tar -C /usr/local -xzf /opt/go1.9.1.linux-amd64.tar.gz
|
||||
RUN yum -y install epel-release wget unzip git make which centos-release-scl gcc && sed -i "s/enabled=0/enabled=1/" /etc/yum.repos.d/epel-testing.repo && yum -y install rh-ruby22 && wget -O /opt/go1.20.11.linux-amd64.tar.gz https://storage.googleapis.com/golang/go1.20.11.linux-amd64.tar.gz && tar -C /usr/local -xzf /opt/go1.20.11.linux-amd64.tar.gz
|
||||
RUN mkdir -p $GOPATH/src/github.com/purpleidea && cd $GOPATH/src/github.com/purpleidea && git clone --recursive https://github.com/purpleidea/mgmt
|
||||
RUN go get -u gopkg.in/alecthomas/gometalinter.v1 && cd $GOPATH/src/github.com/purpleidea/mgmt && make deps && make build
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.11
|
||||
FROM golang:1.20
|
||||
|
||||
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>
|
||||
|
||||
@@ -27,8 +27,5 @@ WORKDIR /home/$USER_NAME/mgmt
|
||||
# Install dependencies
|
||||
RUN make deps
|
||||
|
||||
# Chown $GOPATH
|
||||
RUN chown -R ${USER_ID}:${GROUP_ID} /go
|
||||
|
||||
# Change user
|
||||
USER ${USER_NAME}
|
||||
|
||||
@@ -51,7 +51,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'mgmt'
|
||||
copyright = u'2013-2019+ James Shubin and the project contributors'
|
||||
copyright = u'2013-2024+ James Shubin and the project contributors'
|
||||
author = u'James Shubin'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
|
||||
@@ -28,7 +28,7 @@ required for running the _test_ suite.
|
||||
|
||||
### Build
|
||||
|
||||
* `golang` 1.11 or higher (required, available in some distros and distributed
|
||||
* `golang` 1.20 or higher (required, available in some distros and distributed
|
||||
as a binary officially by [golang.org](https://golang.org/dl/))
|
||||
|
||||
### Runtime
|
||||
|
||||
@@ -122,38 +122,15 @@ entire set of running mgmt agents will need to all simultaneously converge for
|
||||
the group to exit. This is particularly useful for bootstrapping new clusters
|
||||
which need to exchange information that is only available at run time.
|
||||
|
||||
This existed in earlier versions of mgmt as a `--remote` option, but it has been
|
||||
removed and is being ported to a more powerful variant where you can remote
|
||||
execute via a `remote` resource.
|
||||
|
||||
#### Blog post
|
||||
|
||||
You can read the introductory blog post about this topic here:
|
||||
[https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/](https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/)
|
||||
|
||||
### Puppet support
|
||||
|
||||
You can supply a Puppet manifest instead of creating the (YAML) graph manually.
|
||||
Puppet must be installed and in `mgmt`'s search path. You also need the
|
||||
[ffrank-mgmtgraph Puppet module](https://forge.puppet.com/ffrank/mgmtgraph).
|
||||
|
||||
Invoke `mgmt` with the `--puppet` switch, which supports 3 variants:
|
||||
|
||||
1. Request the configuration from the Puppet Master (like `puppet agent` does)
|
||||
|
||||
`mgmt run puppet --puppet agent`
|
||||
|
||||
2. Compile a local manifest file (like `puppet apply`)
|
||||
|
||||
`mgmt run puppet --puppet /path/to/my/manifest.pp`
|
||||
|
||||
3. Compile an ad hoc manifest from the commandline (like `puppet apply -e`)
|
||||
|
||||
`mgmt run puppet --puppet 'file { "/etc/ntp.conf": ensure => file }'`
|
||||
|
||||
For more details and caveats see [puppet-guide.md](puppet-guide.md).
|
||||
|
||||
#### Blog post
|
||||
|
||||
An introductory post on the Puppet support is on
|
||||
[Felix's blog](http://ffrank.github.io/features/2016/06/19/puppet-powered-mgmt/).
|
||||
|
||||
## Reference
|
||||
|
||||
Please note that there are a number of undocumented options. For more
|
||||
@@ -250,6 +227,43 @@ integer, then that value is the max size for that semaphore. Valid semaphore
|
||||
id's include: `some_id`, `hello:42`, `not:smart:4` and `:13`. It is expected
|
||||
that the last bare example be only used by the engine to add a global semaphore.
|
||||
|
||||
#### Rewatch
|
||||
|
||||
Boolean. Rewatch specifies whether we re-run the Watch worker during a graph
|
||||
swap if it has errored. When doing a graph compare to swap the graphs, if this
|
||||
is true, and this particular worker has errored, then we'll remove it and add it
|
||||
back as a new vertex, thus causing it to run again. This is different from the
|
||||
`Retry` metaparam which applies during the normal execution. It is only when
|
||||
this is exhausted that we're in permanent worker failure, and only then can we
|
||||
rely on this metaparam.
|
||||
|
||||
#### Realize
|
||||
|
||||
Boolean. Realize ensures that the resource is guaranteed to converge at least
|
||||
once before a potential graph swap removes or changes it. This guarantee is
|
||||
useful for fast changing graphs, to ensure that the brief creation of a resource
|
||||
is seen. This guarantee does not prevent against the engine quitting normally,
|
||||
and it can't guarantee it if the resource is blocked because of a failed
|
||||
pre-requisite resource.
|
||||
*XXX: This is currently not implemented!*
|
||||
|
||||
#### Reverse
|
||||
|
||||
Boolean. Reverse is a property that some resources can implement that specifies
|
||||
that some "reverse" operation should happen when that resource "disappears". A
|
||||
disappearance happens when a resource is defined in one instance of the graph,
|
||||
and is gone in the subsequent one. This disappearance can happen if it was
|
||||
previously in an if statement that then becomes false.
|
||||
|
||||
This is helpful for building robust programs with the engine. The engine adds a
|
||||
"reversed" resource to that subsequent graph to accomplish the desired "reverse"
|
||||
mechanics. The specifics of what this entails is a property of the particular
|
||||
resource that is being "reversed".
|
||||
|
||||
It might be wise to combine the use of this meta parameter with the use of the
|
||||
`realize` meta parameter to ensure that your reversed resource actually runs at
|
||||
least once, if there's a chance that it might be gone for a while.
|
||||
|
||||
### Lang metadata file
|
||||
|
||||
Any module *must* have a metadata file in its root. It must be named
|
||||
@@ -323,33 +337,11 @@ collision with this globally defined semaphore. The size value must be greater
|
||||
than zero at this time. The traditional non-parallel execution found in config
|
||||
management tools such as `Puppet` can be obtained with `--sema 1`.
|
||||
|
||||
#### `--remote <graph.yaml>`
|
||||
|
||||
Point to a graph file to run on the remote host specified within. This parameter
|
||||
can be used multiple times if you'd like to remotely run on multiple hosts in
|
||||
parallel.
|
||||
|
||||
#### `--allow-interactive`
|
||||
|
||||
Allow interactive prompting for SSH passwords if there is no authentication
|
||||
method that works.
|
||||
|
||||
#### `--ssh-priv-id-rsa`
|
||||
|
||||
Specify the path for finding SSH keys. This defaults to `~/.ssh/id_rsa`. To
|
||||
never use this method of authentication, set this to the empty string.
|
||||
|
||||
#### `--cconns`
|
||||
|
||||
The maximum number of concurrent remote ssh connections to run. This defaults
|
||||
to `0`, which means unlimited.
|
||||
|
||||
#### `--no-caching`
|
||||
|
||||
Don't allow remote caching of the remote execution binary. This will require
|
||||
the binary to be copied over for every remote execution, but it limits the
|
||||
likelihood that there is leftover information from the configuration process.
|
||||
|
||||
#### `--prefix <path>`
|
||||
|
||||
Specify a path to a custom working directory prefix. This directory will get
|
||||
@@ -367,8 +359,8 @@ default prefix. This can't be combined with the `--prefix` option.
|
||||
If this option is specified, we will attempt to fall back to a temporary prefix
|
||||
if the primary prefix couldn't be created. This is useful for avoiding failures
|
||||
in environments where the primary prefix may or may not be available, but you'd
|
||||
like to try. The canonical example is when running `mgmt` with `--remote` there
|
||||
might be a cached copy of the binary in the primary prefix, but in case there's
|
||||
like to try. The canonical example is when running `mgmt` with remote execution
|
||||
there might be a cached copy of the binary in the primary prefix, but if there's
|
||||
no binary available continue working in a temporary directory to avoid failure.
|
||||
|
||||
### Compilation options
|
||||
@@ -451,7 +443,7 @@ To report any bugs, please file a ticket at: [https://github.com/purpleidea/mgmt
|
||||
|
||||
## Authors
|
||||
|
||||
Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
|
||||
Please see the
|
||||
[AUTHORS](https://github.com/purpleidea/mgmt/tree/master/AUTHORS) file
|
||||
|
||||
156
docs/faq.md
156
docs/faq.md
@@ -53,10 +53,11 @@ find a number of tutorials online.
|
||||
3. Spend between four to six hours with the [golang tour](https://tour.golang.org/).
|
||||
Skip over the longer problems, but try and get a solid overview of everything.
|
||||
If you forget something, you can always go back and repeat those parts.
|
||||
4. Connect to our [#mgmtconfig](https://webchat.freenode.net/?channels=#mgmtconfig)
|
||||
IRC channel on the [Freenode](https://freenode.net/) network. You can use any
|
||||
IRC client that you'd like, but the [hosted web portal](https://webchat.freenode.net/?channels=#mgmtconfig)
|
||||
will suffice if you don't know what else to use.
|
||||
4. Connect to our [#mgmtconfig](https://web.libera.chat/?channels=#mgmtconfig)
|
||||
IRC channel on the [Libera.Chat](https://libera.chat/) network. You can use any
|
||||
IRC client that you'd like, but the [hosted web portal](https://web.libera.chat/?channels=#mgmtconfig)
|
||||
will suffice if you don't know what else to use. [Here are a few suggestions for
|
||||
alternative clients.](https://libera.chat/guides/clients)
|
||||
5. Now it's time to try and starting writing a patch! We have tagged a bunch of
|
||||
[open issues as #mgmtlove](https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%3Amgmtlove)
|
||||
for new users to have somewhere to get involved. Look through them to see if
|
||||
@@ -215,16 +216,76 @@ requires a number of seconds as an argument.
|
||||
./mgmt run lang examples/lang/hello0.mcl --converged-timeout=5
|
||||
```
|
||||
|
||||
### When I try to build `mgmt` I see: `no Go files in $GOPATH/src/github.com/purpleidea/mgmt/bindata`.
|
||||
### Can I run `mgmt` for type-checking only?
|
||||
|
||||
Due to the arcane way that `golang` designed its `$GOPATH`, the main project
|
||||
directory must be inside your `$GOPATH`, and at the appropriate FQDN. This is:
|
||||
`$GOPATH/src/github.com/purpleidea/mgmt/`. If you have your project root outside
|
||||
of that directory, then you may get this error when you try to build it. In this
|
||||
case there is likely a `go get` version of the project at this location. Remove
|
||||
it and replace it with your git cloned directory. In my case, I like to work on
|
||||
things in `~/code/mgmt/`, so that path is a symlink that points to the long
|
||||
project directory.
|
||||
Yes, you can, add the `--only-unify` option to the lang frontend while using the
|
||||
`run` command, and it will exit after type unification.
|
||||
|
||||
#### Example:
|
||||
|
||||
```
|
||||
./mgmt run --tmp-prefix lang --only-unify examples/lang/hello0.mcl
|
||||
```
|
||||
|
||||
It will also print how long it took on either success or failure. Keep in mind
|
||||
that even if you pass type unification, an `mgmt` run can still fail later on
|
||||
for other reasons, although these are mostly runtime considerations.
|
||||
|
||||
### Why is type unification happening twice with `mgmt run`?
|
||||
|
||||
When you use the `run` action, it runs all of the compile time checks (including
|
||||
type unification) that are possible, and then packages everything up into a
|
||||
deployable object and runs the same mechanism that `mgmt deploy` uses, sending
|
||||
the deploy to itself. At this point, mgmt starts up as a server, and receives
|
||||
the deploy. It will then need to run type unification again before running the
|
||||
code.
|
||||
|
||||
You can skip the first type unification check by adding the `--skip-unify`
|
||||
option to the lang frontend when using the `run` command.
|
||||
|
||||
You can also skip this check when running the `deploy` action, but if your code
|
||||
doesn't pass, you might be deploying broken code. This is not recommended.
|
||||
|
||||
#### Example:
|
||||
|
||||
```
|
||||
./mgmt run --tmp-prefix lang --skip-unify examples/lang/hello0.mcl
|
||||
```
|
||||
|
||||
### Why does my file resource error with `no such file or directory`?
|
||||
|
||||
If you create a file resource and only specify the content like this:
|
||||
|
||||
```
|
||||
file "/tmp/foo" {
|
||||
content => "hello world\n",
|
||||
}
|
||||
```
|
||||
|
||||
Then this will attempt to set the contents of that file to the desired string,
|
||||
but *only* if that file already exists. If you'd like to ensure that it also
|
||||
gets created in case it is not present, then you must also specify the state:
|
||||
|
||||
```
|
||||
file "/tmp/foo" {
|
||||
state => $const.res.file.state.exists,
|
||||
content => "hello world\n",
|
||||
}
|
||||
```
|
||||
|
||||
Similar logic applies for situations when you only specify the `mode` parameter.
|
||||
|
||||
This all turns out to be more safe and "correct", in that it would error and
|
||||
prevent masking an error for a situation when you expected a file to already be
|
||||
at that location. It also turns out to simplify the internals significantly, and
|
||||
remove an ambiguous scenario with the reversible file resource.
|
||||
|
||||
### Why do function names inside of templates include underscores?
|
||||
|
||||
The golang template library which we use to implement the template() function
|
||||
doesn't support the dot notation, so we import all our normal functions, and
|
||||
just replace dots with underscores. As an example, the standard `datetime.print`
|
||||
function is shown within mcl scripts as datetime_print after being imported.
|
||||
|
||||
### On startup `mgmt` hangs after: `etcd: server: starting...`.
|
||||
|
||||
@@ -259,6 +320,65 @@ an instance of mgmt running, or if a related file locking issue occurred. To
|
||||
solve this, shutdown and running mgmt process, run `rm mgmt` to remove the file,
|
||||
and then get a new one by running `make` again.
|
||||
|
||||
### Type unification error: "could not unify types: 2 unconsumed generators".
|
||||
|
||||
Look carefully at the following code:
|
||||
|
||||
```
|
||||
$num = 42
|
||||
print "hello" {
|
||||
msg => "My favourite number is ${num}",
|
||||
}
|
||||
```
|
||||
|
||||
What's actually happening is that we can't unify `str + int`, because our
|
||||
addition operator only supports `str + str`, `int + int`, and `float + float`.
|
||||
The string interpolation feature can only combine strings. One solution is to
|
||||
instead write:
|
||||
|
||||
```
|
||||
$num = "42" # now this is a string
|
||||
print "hello" {
|
||||
msg => "My favourite number is ${num}",
|
||||
}
|
||||
```
|
||||
|
||||
Yes we know the compiler gives horrible error messages, and yes we would
|
||||
absolutely love your help improving this.
|
||||
|
||||
### The run and deploy commands don't parse correctly when used with `--seeds`.
|
||||
|
||||
If you're running a command with `--seeds`, `--server-urls`, or `--client-urls`,
|
||||
then make sure you are using an equals sign between the flag name and the value.
|
||||
For example, if you were to run:
|
||||
|
||||
```
|
||||
# wrong invocation!
|
||||
mgmt deploy --no-git --seeds http://127.0.0.1:2379 lang code/test.mcl
|
||||
```
|
||||
|
||||
Then the `--seeds` flag would interpret `lang` and `code/test.mcl` as additional
|
||||
seeds. This flag as well as the other aforementioned ones all accept multiple
|
||||
values. Use an equals sign to guarantee you enter the correct data, eg:
|
||||
|
||||
```
|
||||
# better invocation! (note the equals sign)
|
||||
mgmt deploy --no-git --seeds=http://127.0.0.1:2379 lang code/test.mcl
|
||||
```
|
||||
|
||||
This is caused by a parsing peculiarity of the CLI library that we are using.
|
||||
This is tracked upstream at: [https://github.com/alexflint/go-arg/issues/239](https://github.com/alexflint/go-arg/issues/239).
|
||||
We have a workaround in place to mitigate it and attempt to show you a helpful
|
||||
error message, but it's also documented here in the meantime. The error you will
|
||||
see is: `cli parse error: missing equals sign for list element`.
|
||||
|
||||
### The docs speaks of `--remote` but the CLI errors out?
|
||||
|
||||
The `--remote` flag existed in an earlier version of mgmt. It was removed and
|
||||
will be replaced with a more powerful version, which is a "remote" resource. The
|
||||
code is mostly ready but it's not finished. If you'd like to help finish it or
|
||||
sponsor the work, please let me know.
|
||||
|
||||
### Does this support Windows? OSX? GNU Hurd?
|
||||
|
||||
Mgmt probably works best on Linux, because that's what most developers use for
|
||||
@@ -321,6 +441,14 @@ Don't blindly use the tools that others tell you to. Learn what they do, think
|
||||
for yourself, and become a power user today! That process led us to using
|
||||
`git submodules`. Hopefully you'll come to the same conclusions that we did.
|
||||
|
||||
**UPDATE:**
|
||||
|
||||
After golang made it virtually impossible to build without `go.mod` stuff, we've
|
||||
switched to it since golang 1.16. I still think the above approach was better,
|
||||
and that the `go mod` tooling should have been a layer on top of git submodules
|
||||
so that we don't grow yet another lock file format, and existing folks who are
|
||||
comfortable with `git` can use those tools directly.
|
||||
|
||||
### Did you know that there is a band named `MGMT`?
|
||||
|
||||
I didn't realize this when naming the project, and it is accidental. After much
|
||||
@@ -334,7 +462,7 @@ which definitely existed before the band did.
|
||||
|
||||
### You didn't answer my question, or I have a question!
|
||||
|
||||
It's best to ask on [IRC](https://webchat.freenode.net/?channels=#mgmtconfig)
|
||||
It's best to ask on [IRC](https://web.libera.chat/?channels=#mgmtconfig)
|
||||
to see if someone can help you. If you don't get a response from IRC, you can
|
||||
contact me through my [technical blog](https://purpleidea.com/contact/) and I'll
|
||||
do my best to help. If you have a good question, please add it as a patch to
|
||||
|
||||
@@ -37,10 +37,12 @@ available types and values in the mgmt language. It is very easy to use, and
|
||||
should be fairly intuitive. Most of what you'll need to know can be inferred
|
||||
from looking at example code.
|
||||
|
||||
To implement a function, you'll need to create a file in
|
||||
[`lang/funcs/simple/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simple/).
|
||||
The function should be implemented as a `FuncValue` in our type system. It is
|
||||
then registered with the engine during `init()`. An example explains it best:
|
||||
To implement a function, you'll need to create a file that imports the
|
||||
[`lang/funcs/simple/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simple/)
|
||||
module. It should probably get created in the correct directory inside of:
|
||||
[`lang/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/core/). The
|
||||
function should be implemented as a `FuncValue` in our type system. It is then
|
||||
registered with the engine during `init()`. An example explains it best:
|
||||
|
||||
### Example
|
||||
|
||||
@@ -50,14 +52,15 @@ package simple
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
// you must register your functions in init when the program starts up
|
||||
func init() {
|
||||
// Example function that squares an int and prints out answer as an str.
|
||||
Register("talkingsquare", &types.FuncValue{
|
||||
T: types.NewType("func(a int) str"), // declare the signature
|
||||
simple.ModuleRegister(ModuleName, "talkingsquare", &types.FuncValue{
|
||||
T: types.NewType("func(int) str"), // declare the signature
|
||||
V: func(input []types.Value) (types.Value, error) {
|
||||
i := input[0].Int() // get first arg as an int64
|
||||
// must return the above specified value
|
||||
@@ -109,15 +112,20 @@ As with the simple, non-polymorphic API, you can only implement [pure](https://e
|
||||
functions, without writing too much boilerplate code. They will be automatically
|
||||
re-evaluated as needed when their input values change.
|
||||
|
||||
To implement a function, you'll need to create a file in
|
||||
[`lang/funcs/simplepoly/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simplepoly/).
|
||||
The function should be implemented as a list of `FuncValue`'s in our type
|
||||
system. It is then registered with the engine during `init()`. You may also use
|
||||
the `variant` type in your type definitions. This special type will never be
|
||||
seen inside a running program, and will get converted to a concrete type if a
|
||||
To implement a function, you'll need to create a file that imports the
|
||||
[`lang/funcs/simplepoly/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simplepoly/)
|
||||
module. It should probably get created in the correct directory inside of:
|
||||
[`lang/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/core/). The
|
||||
function should be implemented as a list of `FuncValue`'s in our type system. It
|
||||
is then registered with the engine during `init()`. You may also use the
|
||||
`variant` type in your type definitions. This special type will never be seen
|
||||
inside a running program, and will get converted to a concrete type if a
|
||||
suitable match to this signature can be found. Be warned that signatures which
|
||||
contain too many variants, or which are very general, might be hard for the
|
||||
compiler to match, and ambiguous type graphs make for user compiler errors.
|
||||
compiler to match, and ambiguous type graphs make for user compiler errors. The
|
||||
top-level type must still be a function type, it may only contain variants as
|
||||
part of its signature. It is probably more difficult to unify a function if its
|
||||
return type is a variant, as opposed to if one of its args was.
|
||||
|
||||
An example explains it best:
|
||||
|
||||
@@ -127,11 +135,13 @@ An example explains it best:
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simplepoly"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// You may use the simplepoly.ModuleRegister method to register your
|
||||
// function if it's in a module, as seen in the simple function example.
|
||||
simplepoly.Register("len", []*types.FuncValue{
|
||||
{
|
||||
T: types.NewType("func([]variant) int"),
|
||||
@@ -190,7 +200,7 @@ if it meets your needs. Most functions will be able to use that API. If you
|
||||
really need something more powerful, then you can use the regular function API.
|
||||
What follows are each of the method signatures and a description of each.
|
||||
|
||||
### Default
|
||||
### Info
|
||||
|
||||
```golang
|
||||
Info() *interfaces.Info
|
||||
@@ -229,27 +239,6 @@ use in the other methods.
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *FooFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.closeChan = make(chan struct{}) // shutdown signal
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Close
|
||||
|
||||
```golang
|
||||
Close() error
|
||||
```
|
||||
|
||||
This is called to cleanup the function. It usually causes the stream to
|
||||
shutdown. Even if `Stream()` decided to shutdown early, it might still get
|
||||
called. It is usually called by the engine to tell the function to shutdown.
|
||||
|
||||
#### Example
|
||||
|
||||
```golang
|
||||
// Close runs some shutdown code for this function and turns off the stream.
|
||||
func (obj *FooFunc) Close() error {
|
||||
close(obj.closeChan) // send a signal to tell the stream to close
|
||||
return nil
|
||||
}
|
||||
```
|
||||
@@ -257,23 +246,24 @@ func (obj *FooFunc) Close() error {
|
||||
### Stream
|
||||
|
||||
```golang
|
||||
Stream() error
|
||||
Stream(context.Context) error
|
||||
```
|
||||
|
||||
`Stream` is where the real _work_ is done. This method is started by the
|
||||
language function engine. It will run this function while simultaneously sending
|
||||
it values on the `input` channel. It will only send a complete set of input
|
||||
it values on the `Input` channel. It will only send a complete set of input
|
||||
values. You should send a value to the output channel when you have decided that
|
||||
one should be produced. Make sure to only use input values of the expected type
|
||||
as declared in the `Info` struct, and send values of the similarly declared
|
||||
appropriate return type. Failure to do so will may result in a panic and
|
||||
sadness.
|
||||
sadness. You must shutdown if the input context cancels. You must close the
|
||||
`Output` channel if you are done generating new values and/or when you shutdown.
|
||||
|
||||
#### Example
|
||||
|
||||
```golang
|
||||
// Stream returns the single value that was generated and then closes.
|
||||
func (obj *FooFunc) Stream() error {
|
||||
func (obj *FooFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
var result string
|
||||
for {
|
||||
@@ -290,7 +280,7 @@ func (obj *FooFunc) Stream() error {
|
||||
|
||||
result = fmt.Sprintf("the input is: %d", ix)
|
||||
|
||||
case <-obj.closeChan:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -299,7 +289,7 @@ func (obj *FooFunc) Stream() error {
|
||||
V: result,
|
||||
}:
|
||||
|
||||
case <-obj.closeChan:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -330,8 +320,6 @@ type FooFunc struct {
|
||||
init *interfaces.Init
|
||||
|
||||
// this space can be used if needed
|
||||
|
||||
closeChan chan struct{} // shutdown signal
|
||||
}
|
||||
```
|
||||
|
||||
@@ -354,7 +342,9 @@ method instead.
|
||||
|
||||
```golang
|
||||
// moduleName is already set to "math" by the math package. Do this in `init`.
|
||||
funcs.ModuleRegister(moduleName, "cos", func() interfaces.Func { return &CosFunc{} })
|
||||
funcs.ModuleRegister(moduleName, "cos", func() interfaces.Func {
|
||||
return &CosFunc{}
|
||||
})
|
||||
```
|
||||
|
||||
### Composite functions
|
||||
@@ -435,6 +425,11 @@ generator to build your `FuncValue` implementations, and pass in the unique
|
||||
signature to each one as you are building them. Using a generator is a common
|
||||
technique which was mentioned previously.
|
||||
|
||||
One obvious situation where this might occur is if your function doesn't take
|
||||
any inputs! An example `math.fortytwo()` function was implemented that
|
||||
demonstrates the use of function generators to pass the type signatures into the
|
||||
implementations.
|
||||
|
||||
### Where can I find more information about mgmt?
|
||||
|
||||
Additional blog posts, videos and other material [is available!](https://github.com/purpleidea/mgmt/blob/master/docs/on-the-web.md).
|
||||
|
||||
@@ -14,4 +14,3 @@ Welcome to mgmt's documentation!
|
||||
quick-start-guide
|
||||
resource-guide
|
||||
prometheus
|
||||
puppet-guide
|
||||
|
||||
@@ -54,7 +54,7 @@ can be impossible to infer the item's type.
|
||||
|
||||
An unordered set of unique keys of the same type and corresponding value pairs
|
||||
of another type, eg:
|
||||
`{"boiling" => 100, "freezing" => 0, "room" => "25", "house" => 22, "canada" => -30,}`.
|
||||
`{"boiling" => 100, "freezing" => 0, "room" => 25, "house" => 22, "canada" => -30,}`.
|
||||
That is to say, all of the keys must have the same type, and all of the values
|
||||
must have the same type. You can use any type for either, although it is
|
||||
probably advisable to avoid using very complex types as map keys.
|
||||
@@ -206,7 +206,7 @@ value to use if that boolean is true. You can do this with the resource-specific
|
||||
$b = true # change me to false and then try editing the file manually
|
||||
file "/tmp/mgmt-elvis" {
|
||||
content => $b ?: "hello world\n",
|
||||
state => "exists",
|
||||
state => $const.res.file.state.exists,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -293,7 +293,7 @@ send notifications. You may have multiples of these per resource, including
|
||||
multiple `Depend` lines if necessary. Each of these properties also supports the
|
||||
conditional inclusion `elvis` operator as well.
|
||||
|
||||
For example, you may write is:
|
||||
For example, you may write:
|
||||
|
||||
```mcl
|
||||
$b = true # for example purposes
|
||||
@@ -402,6 +402,38 @@ time.
|
||||
Recursive classes are not currently supported and it is not clear if they will
|
||||
be in the future. Discussion about this topic is welcome on the mailing list.
|
||||
|
||||
A class names can contain colons to indicate it is nested inside of the class in
|
||||
the same scope which is named with the prefix indicated by colon separation.
|
||||
Instead of needing to repeatedly indent the child classes, we can instead prefix
|
||||
them at the definition site (where created with the class keyword) with the name
|
||||
of the parent class, followed by a colon, to get the desired embedded sugar.
|
||||
|
||||
For example, instead of writing:
|
||||
|
||||
```mcl
|
||||
class base() {
|
||||
class inner() {
|
||||
class deepest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can instead write:
|
||||
|
||||
```mcl
|
||||
class base() {
|
||||
}
|
||||
class base:inner() {
|
||||
}
|
||||
class base:inner:deepest() {
|
||||
}
|
||||
```
|
||||
|
||||
Of course, you can only access any of the inner classes by first including
|
||||
(with the include keyword) a parent class, and then subsequently including the
|
||||
inner one.
|
||||
|
||||
#### Include
|
||||
|
||||
The `include` statement causes the previously defined class to produce the
|
||||
@@ -414,6 +446,57 @@ parameters, then the same class can even be called with different signatures.
|
||||
Whether the output is useful and whether there is a unique type unification
|
||||
solution is dependent on your code.
|
||||
|
||||
Classes can be included under a new scoped prefix by using the `as` field and an
|
||||
identifier. When used in this manner, the captured scope of the class at its
|
||||
definition site are made available in the scope of the include. Variables,
|
||||
functions, and child classes are all exported.
|
||||
|
||||
Variables are available in the include scope:
|
||||
|
||||
```mcl
|
||||
import "fmt"
|
||||
|
||||
class c1 {
|
||||
test "t1" {} # gets pulled out
|
||||
$x = "hello" # gets exported
|
||||
}
|
||||
include c1 as i1
|
||||
|
||||
test "print0" {
|
||||
anotherstr => fmt.printf("%s", $i1.x), # hello
|
||||
onlyshow => ["AnotherStr",], # displays nicer
|
||||
}
|
||||
```
|
||||
|
||||
Classes are also available in the new scope:
|
||||
|
||||
```mcl
|
||||
import "fmt"
|
||||
class c1 {
|
||||
test "t1" {} # gets pulled out
|
||||
$x = "hello" # gets exported
|
||||
|
||||
class c0 {
|
||||
test "t2" {}
|
||||
$x = "goodbye"
|
||||
}
|
||||
}
|
||||
include c1 as i1
|
||||
include i1.c0 as i0
|
||||
|
||||
test "print0" {
|
||||
anotherstr => fmt.printf("%s", $i1.x), # hello
|
||||
onlyshow => ["AnotherStr",], # displays nicer
|
||||
}
|
||||
test "print1" {
|
||||
anotherstr => fmt.printf("%s", $i0.x), # goodbye
|
||||
onlyshow => ["AnotherStr",], # displays nicer
|
||||
}
|
||||
```
|
||||
|
||||
Of course classes can be parameterized too, and those variables specified during
|
||||
the `include`.
|
||||
|
||||
#### Import
|
||||
|
||||
The `import` statement imports a scope into the specified namespace. A scope can
|
||||
@@ -502,6 +585,10 @@ and can be used for other scenarios in which one statement or expression would
|
||||
be better represented by a larger AST. Most nodes in the AST simply return their
|
||||
own node address, and do not modify the AST.
|
||||
|
||||
This stage also implements the class nesting when it finds class names with
|
||||
colons that should be nested inside of a base class. Currently this does modify
|
||||
the AST for efficiency and simplicity.
|
||||
|
||||
#### Scope propagation
|
||||
|
||||
Scope propagation passes the parent scope (starting with the top-level, built-in
|
||||
@@ -563,11 +650,11 @@ would like to propose a more logical or performant variant.
|
||||
|
||||
#### Function graph generation
|
||||
|
||||
At this point we have a fully type AST. The AST must now be transformed into a
|
||||
At this point we have a fully typed AST. The AST must now be transformed into a
|
||||
directed, acyclic graph (DAG) data structure that represents the flow of data as
|
||||
necessary for everything to be reactive. Note that this graph is *different*
|
||||
from the resource graph which is produced and sent to the engine. It is just a
|
||||
coincidence that both happen to be DAG's. (You don't freak out when you see a
|
||||
coincidence that both happen to be DAG's. (You aren't surprised when you see a
|
||||
list data structure show up in more than one place, do you?)
|
||||
|
||||
To produce this graph, each node has a `Graph` method which it can call. This
|
||||
@@ -575,9 +662,8 @@ starts at the top most node, and is called down through the AST. The edges in
|
||||
the graphs must represent the individual expression values which are passed
|
||||
from node to node. The names of the edges must match the function type argument
|
||||
names which are used in the definition of the corresponding function. These
|
||||
corresponding functions must exist for each expression node and are produced by
|
||||
calling that expression's `Func` method. These are usually called by the
|
||||
function engine during function creation and validation.
|
||||
corresponding functions must exist for each expression node and are produced as
|
||||
the vertices of this returned graph. This is built for the function engine.
|
||||
|
||||
#### Function engine creation and validation
|
||||
|
||||
@@ -677,13 +763,13 @@ one value must be produced.
|
||||
|
||||
```golang
|
||||
Please see the example functions in
|
||||
[lang/funcs/core/](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/).
|
||||
[lang/core/](https://github.com/purpleidea/mgmt/tree/master/lang/core/).
|
||||
```
|
||||
|
||||
### Stream
|
||||
|
||||
```golang
|
||||
Stream() error
|
||||
Stream(context.Context) error
|
||||
```
|
||||
|
||||
Stream is called by the function engine when it is ready for your function to
|
||||
@@ -692,29 +778,14 @@ value. Failure to produce at least one value will probably cause the function
|
||||
engine to hang waiting for your output. This function must close the `Output`
|
||||
channel when it has no more values to send. The engine will close the `Input`
|
||||
channel when it has no more values to send. This may or may not influence
|
||||
whether or not you close the `Output` channel.
|
||||
whether or not you close the `Output` channel. You must shutdown if the input
|
||||
context cancels.
|
||||
|
||||
#### Example
|
||||
|
||||
```golang
|
||||
Please see the example functions in
|
||||
[lang/funcs/core/](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/).
|
||||
```
|
||||
|
||||
### Close
|
||||
|
||||
```golang
|
||||
Close() error
|
||||
```
|
||||
|
||||
Close asks the particular function to shutdown its `Stream()` function and
|
||||
return.
|
||||
|
||||
#### Example
|
||||
|
||||
```golang
|
||||
Please see the example functions in
|
||||
[lang/funcs/core/](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/).
|
||||
[lang/core/](https://github.com/purpleidea/mgmt/tree/master/lang/core/).
|
||||
```
|
||||
|
||||
### Polymorphic Function API
|
||||
|
||||
@@ -44,3 +44,18 @@ if we missed something that you think is relevant!
|
||||
| James Shubin | blog | [Mgmt Configuration Language](https://purpleidea.com/blog/2018/02/05/mgmt-configuration-language/) |
|
||||
| James Shubin | video | [Recording from CfgMgmtCamp.eu 2018](https://www.youtube.com/watch?v=NxObmwZDyrI) |
|
||||
| Jonathan Gold | blog | [Go Netlink and Select](https://jonathangold.ca/blog/go-netlink-and-select/) |
|
||||
| James Shubin | video | [Recording from DevOpsDays Montreal 2018](https://www.youtube.com/watch?v=1i38c5cooHo) |
|
||||
| James Shubin | video | [Recording from FOSDEM Minimalistic Languages Devroom 2019](https://video.fosdem.org/2019/K.4.201/mgmtconfig.webm) |
|
||||
| James Shubin | video | [Recording from FOSDEM Infra Management Devroom 2019](https://video.fosdem.org/2019/UB2.252A/mgmt.webm) |
|
||||
| James Shubin | video | [Recording from FOSDEM Graph Processing Devroom 2019](https://video.fosdem.org/2019/H.1308/graph_mgmt_config.webm) |
|
||||
| James Shubin | video | [Recording from FOSDEM Virtualization Devroom 2019](https://video.fosdem.org/2019/H.2213/vai_real_time_virtualization_automation.webm) |
|
||||
| James Shubin | video | [Recording from FOSDEM Containers Devroom 2019](https://video.fosdem.org/2019/UA2.114/containers_mgmt.webm) |
|
||||
| James Shubin | video | [Recording from FOSDEM Monitoring Devroom 2019](https://video.fosdem.org/2019/UB2.252A/real_time_merging_of_config_management_and_monitoring.webm) |
|
||||
| James Shubin | blog | [Mgmt Configuration Language: Class and Include](https://purpleidea.com/blog/2019/07/26/class-and-include-in-mgmt/) |
|
||||
| James Shubin | video | [Recording from FOSDEM 2020, Main Track (History)](https://video.fosdem.org/2020/Janson/automation.webm) |
|
||||
| James Shubin | video | [Recording from FOSDEM 2020, Infra Management Devroom](https://video.fosdem.org/2020/UA2.120/mgmt.webm) |
|
||||
| James Shubin | video | [Recording from FOSDEM 2020, Minimalistic Languages Devroom](https://video.fosdem.org/2020/AW1.125/mgmtconfigmore.webm) |
|
||||
| James Shubin | video | [Recording from CfgMgmtCamp.eu 2020](https://www.youtube.com/watch?v=Kd7FAORFtsc) |
|
||||
| James Shubin | video | [Recording from CfgMgmtCamp.eu 2023](https://www.youtube.com/watch?v=FeRGRj8w0BU) |
|
||||
| James Shubin | video | [Recording from FOSDEM 2024, Golang Devroom](https://video.fosdem.org/2024/ud2218a/fosdem-2024-2575-single-binary-full-stack-provisioning.mp4) |
|
||||
| James Shubin | video | [Recording from CfgMgmtCamp.eu 2024](https://www.youtube.com/watch?v=vBt9lpGD4bc) |
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
# Puppet guide
|
||||
|
||||
`mgmt` can use Puppet as its source for the configuration graph.
|
||||
This document goes into detail on how this works, and lists
|
||||
some pitfalls and limitations.
|
||||
|
||||
For basic instructions on how to use the Puppet support, see
|
||||
the [main documentation](documentation.md#puppet-support).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need Puppet installed in your system. It is not important how you
|
||||
get it. On the most common Linux distributions, you can use packages
|
||||
from the OS maintainer, or upstream Puppet repositories. An alternative
|
||||
that will also work on OSX is the `puppet` Ruby gem. It also has the
|
||||
advantage that you can install any desired version in your home directory
|
||||
or any other location.
|
||||
|
||||
Any release of Puppet's 3.x and 4.x series should be suitable for use with
|
||||
`mgmt`. Most importantly, make sure to install the `ffrank-mgmtgraph` Puppet
|
||||
module (referred to below as "the translator module").
|
||||
|
||||
```
|
||||
puppet module install ffrank-mgmtgraph
|
||||
```
|
||||
|
||||
Please note that the module is not required on your Puppet master (if you
|
||||
use a master/agent setup). It's needed on the machine that runs `mgmt`.
|
||||
You can install the module on the master anyway, so that it gets distributed
|
||||
to your agents through Puppet's `pluginsync` mechanism.
|
||||
|
||||
### Testing the Puppet side
|
||||
|
||||
The following command should run successfully and print a YAML hash on your
|
||||
terminal:
|
||||
|
||||
```puppet
|
||||
puppet mgmtgraph print --code 'file { "/tmp/mgmt-test": ensure => present }'
|
||||
```
|
||||
|
||||
You can use this CLI to test any manifests before handing them straight
|
||||
to `mgmt`.
|
||||
|
||||
## Writing a suitable manifest
|
||||
|
||||
### Unsupported attributes
|
||||
|
||||
`mgmt` inherited its resource module from Puppet, so by and large, it's quite
|
||||
possible to express `mgmt` graphs in terms of Puppet manifests. However,
|
||||
there isn't (and likely never will be) full feature parity between the
|
||||
respective resource types. In consequence, a manifest can have semantics that
|
||||
cannot be transferred to `mgmt`.
|
||||
|
||||
For example, at the time of writing this, the `file` type in `mgmt` had no
|
||||
notion of permissions (the file `mode`) yet. This lead to the following
|
||||
warning (among others that will be discussed below):
|
||||
|
||||
```
|
||||
$ puppet mgmtgraph print --code 'file { "/tmp/foo": mode => "0600" }'
|
||||
Warning: cannot translate: File[/tmp/foo] { mode => "600" } (attribute is ignored)
|
||||
```
|
||||
|
||||
This is a heads-up for the user, because the resulting `mgmt` graph will
|
||||
in fact not pass this information to the `/tmp/foo` file resource, and
|
||||
`mgmt` will ignore this file's permissions. Including such attributes in
|
||||
manifests that are written expressly for `mgmt` is not sensible and should
|
||||
be avoided.
|
||||
|
||||
### Unsupported resources
|
||||
|
||||
Puppet has a fairly large number of
|
||||
[built-in types](https://docs.puppet.com/puppet/latest/reference/type.html),
|
||||
and countless more are available through
|
||||
[modules](https://forge.puppet.com/). It's unlikely that all of them will
|
||||
eventually receive native counterparts in `mgmt`.
|
||||
|
||||
When encountering an unknown resource, the translator module will replace
|
||||
it with an `exec` resource in its output. This resource will run the equivalent
|
||||
of a `puppet resource` command to make Puppet apply the original resource
|
||||
itself. This has quite abysmal performance, because processing such a
|
||||
resource requires the forking of at least one Puppet process (two if it
|
||||
is found to be out of sync). This comes with considerable overhead.
|
||||
On most systems, starting up any Puppet command takes several seconds.
|
||||
Compared to the split second that the actual work usually takes,
|
||||
this overhead can amount to several orders of magnitude.
|
||||
|
||||
Avoid Puppet types that `mgmt` does not implement (yet).
|
||||
|
||||
### Avoiding common warnings
|
||||
|
||||
Many resource parameters in Puppet take default values. For the most part,
|
||||
the translator module just ignores them. However, there are cases in which
|
||||
Puppet will default to convenient behavior that `mgmt` cannot quite replicate.
|
||||
For example, translating a plain `file` resource will lead to a warning message:
|
||||
|
||||
```
|
||||
$ puppet mgmtgraph print --code 'file { "/tmp/mgmt-test": }'
|
||||
Warning: File[/tmp/mgmt-test] uses the 'puppet' file bucket, which mgmt cannot do. There will be no backup copies!
|
||||
```
|
||||
|
||||
The reason is that per default, Puppet assumes the following parameter value
|
||||
(among others)
|
||||
|
||||
```puppet
|
||||
file { "/tmp/mgmt-test":
|
||||
backup => 'puppet',
|
||||
}
|
||||
```
|
||||
|
||||
To avoid this, specify the parameter explicitly:
|
||||
|
||||
```bash
|
||||
puppet mgmtgraph print --code 'file { "/tmp/mgmt-test": backup => false }'
|
||||
```
|
||||
|
||||
This is tedious in a more complex manifest. A good simplification is the
|
||||
following [resource default](https://docs.puppet.com/puppet/latest/reference/lang_defaults.html)
|
||||
anywhere on the top scope of your manifest:
|
||||
|
||||
```puppet
|
||||
File { backup => false }
|
||||
```
|
||||
|
||||
If you encounter similar warnings from other types and/or parameters,
|
||||
use the same approach to silence them if possible.
|
||||
|
||||
## Configuring Puppet
|
||||
|
||||
Since `mgmt` uses an actual Puppet CLI behind the scenes, you might
|
||||
need to tweak some of Puppet's runtime options in order to make it
|
||||
do what you want. Reasons for this could be among the following:
|
||||
|
||||
* You use the `--puppet agent` variant and need to configure
|
||||
`servername`, `certname` and other master/agent-related options.
|
||||
* You don't want runtime information to end up in the `vardir`
|
||||
that is used by your regular `puppet agent`.
|
||||
* You install specific Puppet modules for `mgmt` in a non-standard
|
||||
location.
|
||||
|
||||
`mgmt` exposes only one Puppet option in order to allow you to
|
||||
control all of them, through its `--puppet-conf` option. It allows
|
||||
you to specify which `puppet.conf` file should be used during
|
||||
translation.
|
||||
|
||||
```
|
||||
mgmt run puppet --puppet /opt/my-manifest.pp --puppet-conf /etc/mgmt/puppet.conf
|
||||
```
|
||||
|
||||
Within this file, you can just specify any needed options in the
|
||||
`[main]` section:
|
||||
|
||||
```
|
||||
[main]
|
||||
server=mgmt-master.example.net
|
||||
vardir=/var/lib/mgmt/puppet
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
Please see the [README](https://github.com/ffrank/puppet-mgmtgraph/blob/master/README.md)
|
||||
of the translator module for the current state of supported and unsupported
|
||||
language features.
|
||||
|
||||
You should probably make sure to always use the latest release of
|
||||
both `ffrank-mgmtgraph` and `ffrank-yamlresource` (the latter is
|
||||
getting pulled in as a dependency of the former).
|
||||
|
||||
## Using Puppet in conjunction with the mcl lang
|
||||
|
||||
The graph that Puppet generates for `mgmt` can be united with a graph
|
||||
that is created from native `mgmt` code in its mcl language. This is
|
||||
useful when you are in the process of replacing Puppet with mgmt. You
|
||||
can translate your custom modules into mgmt's language one by one,
|
||||
and let mgmt run the current mix.
|
||||
|
||||
Instead of the usual `--puppet-conf` flag and argv for `puppet` and `mcl` input,
|
||||
you need to use alternative flags to make this work:
|
||||
|
||||
* `--lp-lang` to specify the mcl input
|
||||
* `--lp-puppet` to specify the puppet input
|
||||
* `--lp-puppet-conf` to point to the optional puppet.conf file
|
||||
|
||||
`mgmt` will derive a graph that contains all edges and vertices from
|
||||
both inputs. You essentially get two unrelated subgraphs that run in
|
||||
parallel. To form edges between these subgraphs, you have to define
|
||||
special vertices that will be merged. This works through a hard-coded
|
||||
naming scheme.
|
||||
|
||||
### Mixed graph example 1 - No merges
|
||||
|
||||
```mcl
|
||||
# lang
|
||||
file "/tmp/mgmt_dir/" { state => "present" }
|
||||
file "/tmp/mgmt_dir/a" { state => "present" }
|
||||
```
|
||||
|
||||
```puppet
|
||||
# puppet
|
||||
file { "/tmp/puppet_dir": ensure => "directory" }
|
||||
file { "/tmp/puppet_dir/a": ensure => "file" }
|
||||
```
|
||||
|
||||
These very simple inputs (including implicit edges from directory to
|
||||
respective file) result in two subgraphs that do not relate.
|
||||
|
||||
```
|
||||
File[/tmp/mgmt_dir/] -> File[/tmp/mgmt_dir/a]
|
||||
|
||||
File[/tmp/puppet_dir] -> File[/tmp/puppet_dir/a]
|
||||
```
|
||||
|
||||
### Mixed graph example 2 - Merged vertex
|
||||
|
||||
In order to have merged vertices in the resulting graph, you will
|
||||
need to include special resources and classes in the respective
|
||||
input code.
|
||||
|
||||
* On the lang side, add `noop` resources with names starting in `puppet_`.
|
||||
* On the Puppet side, add **empty** classes with names starting in `mgmt_`.
|
||||
|
||||
```mcl
|
||||
# lang
|
||||
noop "puppet_handover_to_mgmt" {}
|
||||
file "/tmp/mgmt_dir/" { state => "present" }
|
||||
file "/tmp/mgmt_dir/a" { state => "present" }
|
||||
|
||||
Noop["puppet_handover_to_mgmt"] -> File["/tmp/mgmt_dir/"]
|
||||
```
|
||||
|
||||
```puppet
|
||||
# puppet
|
||||
class mgmt_handover_to_mgmt {}
|
||||
include mgmt_handover_to_mgmt
|
||||
|
||||
file { "/tmp/puppet_dir": ensure => "directory" }
|
||||
file { "/tmp/puppet_dir/a": ensure => "file" }
|
||||
|
||||
File["/tmp/puppet_dir/a"] -> Class["mgmt_handover_to_mgmt"]
|
||||
```
|
||||
|
||||
The new `noop` resource is merged with the new class, resulting in
|
||||
the following graph:
|
||||
|
||||
```
|
||||
File[/tmp/puppet_dir] -> File[/tmp/puppet_dir/a]
|
||||
|
|
||||
V
|
||||
Noop[handover_to_mgmt]
|
||||
|
|
||||
V
|
||||
File[/tmp/mgmt_dir/] -> File[/tmp/mgmt_dir/a]
|
||||
```
|
||||
|
||||
You put all your ducks in a row, and the resources from the Puppet input
|
||||
run before those from the mcl input.
|
||||
|
||||
**Note:** The names of the `noop` and the class must be identical after the
|
||||
respective prefix. The common part (here, `handover_to_mgmt`) becomes the name
|
||||
of the merged resource.
|
||||
|
||||
## Mixed graph example 3 - Multiple merges
|
||||
|
||||
In most scenarios, it will not be possible to define a single handover
|
||||
point like in the previous example. For example, if some Puppet resources
|
||||
need to run in between two stages of native resources, you need at least
|
||||
two merged vertices:
|
||||
|
||||
```mcl
|
||||
# lang
|
||||
noop "puppet_handover" {}
|
||||
noop "puppet_handback" {}
|
||||
file "/tmp/mgmt_dir/" { state => "present" }
|
||||
file "/tmp/mgmt_dir/a" { state => "present" }
|
||||
file "/tmp/mgmt_dir/puppet_subtree/state-file" { state => "present" }
|
||||
|
||||
File["/tmp/mgmt_dir/"] -> Noop["puppet_handover"]
|
||||
Noop["puppet_handback"] -> File["/tmp/mgmt_dir/puppet_subtree/state-file"]
|
||||
```
|
||||
|
||||
```puppet
|
||||
# puppet
|
||||
class mgmt_handover {}
|
||||
class mgmt_handback {}
|
||||
|
||||
include mgmt_handover, mgmt_handback
|
||||
|
||||
class important_stuff {
|
||||
file { "/tmp/mgmt_dir/puppet_subtree":
|
||||
ensure => "directory"
|
||||
}
|
||||
# ...
|
||||
}
|
||||
|
||||
Class["mgmt_handover"] -> Class["important_stuff"] -> Class["mgmt_handback"]
|
||||
```
|
||||
|
||||
The resulting graph looks roughly like this:
|
||||
|
||||
```
|
||||
File[/tmp/mgmt_dir/] -> File[/tmp/mgmt_dir/a]
|
||||
|
|
||||
V
|
||||
Noop[handover] -> ( class important_stuff resources )
|
||||
|
|
||||
V
|
||||
Noop[handback]
|
||||
|
|
||||
V
|
||||
File[/tmp/mgmt_dir/puppet_subtree/state-file]
|
||||
```
|
||||
|
||||
You can add arbitrary numbers of merge pairs to your code bases,
|
||||
with relationships as needed. From our limited experience, code
|
||||
readability suffers quite a lot from these, however. We advise
|
||||
to keep these structures simple.
|
||||
@@ -21,6 +21,8 @@ to build your own.
|
||||
|
||||
### Downloading a pre-built release:
|
||||
|
||||
This method is not recommended because those packages are now very old.
|
||||
|
||||
The latest releases can be found [here](https://github.com/purpleidea/mgmt/releases/).
|
||||
An alternate mirror is available [here](https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/).
|
||||
|
||||
@@ -37,13 +39,13 @@ You'll need some dependencies, including `golang`, and some associated tools.
|
||||
|
||||
#### Installing golang
|
||||
|
||||
* You need golang version 1.11 or greater installed.
|
||||
* You need golang version 1.20 or greater installed.
|
||||
* To install on rpm style systems: `sudo dnf install golang`
|
||||
* To install on apt style systems: `sudo apt install golang`
|
||||
* To install on macOS systems install [Homebrew](https://brew.sh)
|
||||
and run: `brew install go`
|
||||
* You can run `go version` to check the golang version.
|
||||
* If your distro is tool old, you may need to [download](https://golang.org/dl/)
|
||||
* If your distro is too old, you may need to [download](https://golang.org/dl/)
|
||||
a newer golang version.
|
||||
|
||||
#### Setting up golang
|
||||
@@ -63,13 +65,11 @@ export GOPATH=$HOME/gopath
|
||||
|
||||
#### Getting the mgmt code and associated dependencies
|
||||
|
||||
* Download the `mgmt` code into the `GOPATH`, and switch to that directory:
|
||||
* Download the `mgmt` code and switch to that directory:
|
||||
|
||||
```shell
|
||||
[ -z "$GOPATH" ] && mkdir ~/go/ || mkdir -p $GOPATH/src/github.com/purpleidea/
|
||||
cd $GOPATH/src/github.com/purpleidea/ || cd ~/go/
|
||||
git clone --recursive https://github.com/purpleidea/mgmt/
|
||||
cd $GOPATH/src/github.com/purpleidea/mgmt/ || cd ~/go/src/github.com/purpleidea/mgmt/
|
||||
git clone --recursive https://github.com/purpleidea/mgmt/ ~/mgmt/
|
||||
cd ~/mgmt/
|
||||
```
|
||||
|
||||
* Add `$GOPATH/bin` to `$PATH`
|
||||
@@ -97,6 +97,21 @@ At the moment we have:
|
||||
Please contribute more and help improve these! We'd especially like to see a
|
||||
Debian package!
|
||||
|
||||
### Building from a container:
|
||||
|
||||
This method avoids polluting your workstation with the dependencies for the
|
||||
build. Here is an example using Fedora, Podman and Buildah:
|
||||
|
||||
```shell
|
||||
git clone --recursive https://github.com/purpleidea/mgmt/ ~/mgmt/
|
||||
cd ~/mgmt/docker
|
||||
buildah build -f Dockerfile-fedora.build -t mgmt_build
|
||||
podman run -d -it --name mgmt_build localhost/mgmt_build
|
||||
podman cp mgmt_build:/src/github.com/purpleidea/mgmt/mgmt /tmp/mgmt
|
||||
sudo mv /tmp/mgmt /usr/local/bin # be sure this is in your $PATH
|
||||
sudo chown root:root /usr/local/bin/mgmt
|
||||
```
|
||||
|
||||
## Running mgmt
|
||||
|
||||
* Run `mgmt run --tmp-prefix lang examples/lang/hello0.mcl` to try out a very
|
||||
|
||||
51
docs/release-notes/0.0.10
Normal file
51
docs/release-notes/0.0.10
Normal file
@@ -0,0 +1,51 @@
|
||||
I've just released version 0.0.10 of mgmt!
|
||||
|
||||
NEWS
|
||||
|
||||
57 files changed, 1991 insertions(+), 752 deletions(-)
|
||||
|
||||
* There's a new resource called `KV`. Short examples exist, but I
|
||||
haven't yet published a whole integration showing the usefulness.
|
||||
|
||||
* A major race was fixed. The issue of what to do with BackPokes during
|
||||
start/pause was never previously solved. I had this as an open issue on
|
||||
my whiteboard for a while, and I finally got some time to work through
|
||||
it. The answer wasn't that difficult, but I think it was shrouded in
|
||||
some tunnel vision. See the commit messages and source comments for the
|
||||
details.
|
||||
|
||||
* The GAPI grew four new associated World API methods: StrWatch,
|
||||
StrGet, StrSet, StrDel, and the associated etcd backed implementations.
|
||||
These are quite useful when combined with the KV resource.
|
||||
|
||||
* There are now P/V style counting semaphores available as metaparams.
|
||||
This is particularly cool because the implementation is (AFAIK,
|
||||
assuming no bugs) dead-lock free! This is mentioned in my recent blog
|
||||
post.
|
||||
|
||||
* See the git log for more NEWS, and sorry for anything notable I left
|
||||
out!
|
||||
|
||||
BUGS
|
||||
|
||||
* There's a `concurrent map write` bug in the semaphore implementation
|
||||
which is fixed in git master. Since it was a race, it was only caught
|
||||
after this release was made. I should also figure out if the sema check
|
||||
should go after the BackPoke or not.
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started!
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
James Shubin, Julien Pivotto, Michael Borden.
|
||||
We had 3 unique committers since 0.0.9, and have had 30 overall.
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
59
docs/release-notes/0.0.11
Normal file
59
docs/release-notes/0.0.11
Normal file
@@ -0,0 +1,59 @@
|
||||
I've just released version 0.0.11 of mgmt!
|
||||
|
||||
NEWS
|
||||
|
||||
20 files changed, 579 insertions(+), 126 deletions(-)
|
||||
|
||||
* Added a missing mutex around the semaphore map which prevents
|
||||
occasional panics
|
||||
|
||||
* Removed exec pollint param which is not needed because of the poll
|
||||
metaparam
|
||||
|
||||
* Fixed a state rechecking bug in exec resource (things are faster now)
|
||||
|
||||
* Fixed the major annoyance of exec resources receiving main's signals.
|
||||
If we would ^C the main mgmt, the child processes would receive this
|
||||
too which was incorrect.
|
||||
|
||||
* Fixed the deadlock on resource errors. This meant that previously if
|
||||
a resource failed, we would deadlock the graph from shutting down. This
|
||||
was bad and I'm glad it's now fixed. Sorry about that!
|
||||
|
||||
* Improved the backpoke logic to not require semaphores since we used
|
||||
to take the lock even when we were going to backpoke which was
|
||||
unnecessary.
|
||||
|
||||
* Added fast pausing to the graph. This means that a ^C or a pause
|
||||
transition used to wait for the whole graph to roll through, but it now
|
||||
finishes after the currently running resources finish executing. Read
|
||||
the commit messages for more background here including the discussion
|
||||
about a possible Interrupt() addition to the resource API.
|
||||
|
||||
* Prometheus support has been updated!
|
||||
|
||||
* See the git log for more NEWS, and sorry for anything notable I left
|
||||
out!
|
||||
|
||||
BUGS
|
||||
|
||||
* We're in pretty good shape now. There are some small issues, but
|
||||
nothing major that I don't know about.
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started!
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
James Shubin, Julien Pivotto
|
||||
We had 2 unique committers since 0.0.10, and have had 30 overall.
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
87
docs/release-notes/0.0.12
Normal file
87
docs/release-notes/0.0.12
Normal file
@@ -0,0 +1,87 @@
|
||||
I've just released version 0.0.12 of mgmt!
|
||||
|
||||
Sorry if I've been more quiet than usual, I've had to focus a lot of my
|
||||
time on GlusterFS related features ($dayjob official directives) and
|
||||
work there.
|
||||
|
||||
One goal is to try and use libmgmt to either wholly or partially re-
|
||||
implement glusterd. As a result, a lot of my upstream focus has been
|
||||
re-prioritized to features needed for that effort.
|
||||
|
||||
I wrote a PoC called gd3: https://github.com/purpleidea/gd3
|
||||
(It has now bit-rotted compared to upstream mgmt, but is easy to fix.)
|
||||
The initial scope looks like it will be much smaller, but hopefully
|
||||
this is interesting to you too. Ping me if you'd like to help.
|
||||
|
||||
We desperately need your contributions if we're going to get mgmt
|
||||
standalone into a MVP. To motivate you, there's some great new stuff
|
||||
that landed since 0.0.11, including:
|
||||
|
||||
* a great new YAML parser from contributor Mildred
|
||||
|
||||
* a huge pgraph refactor (to remove internal deps and cycles)
|
||||
|
||||
* a great amount of new tests and testing
|
||||
|
||||
and so much more...
|
||||
|
||||
NEWS
|
||||
|
||||
76 files changed, 7549 insertions(+), 4269 deletions(-)
|
||||
|
||||
* The svc resource now also supports user services
|
||||
|
||||
* There's a fabulous new yaml parser that fixes a longstanding issue in
|
||||
my original code. Try it with --yaml2. I'll remove the old one
|
||||
eventually. Thanks to contributor Mildred for this great work!
|
||||
|
||||
* Refactored the lib/ etcd usage into the GAPI's for a cleaner main
|
||||
|
||||
* World API grew some new methods for libmgmt users
|
||||
|
||||
* pgraph refactor and cleanup, now it's a fairly clean standalone pkg
|
||||
|
||||
* pgraph functions to flatten/merge in subgraphs along with examples
|
||||
|
||||
* Giant resource refactor to hopefully make things more logical, and to
|
||||
simplify the resource API. This also introduces the mgraph struct to
|
||||
add the higher level graph knowledge outside of pgraph.
|
||||
|
||||
* A partial implementation of a "Graph" (recursive subgraph?) resource!
|
||||
See the code for details, as help is wanted to finish this. This should
|
||||
help elucidate what the most elegant design for the mgmt core should
|
||||
be.
|
||||
|
||||
* Send/Recv support for the exec resource as output, stdout, and stderr
|
||||
|
||||
* GAPI improvements to support exit requests and fast pausing
|
||||
|
||||
* AutoEdge API improvements including a fix+test for a regression
|
||||
|
||||
* A possible fix for the possible etcd server startup race
|
||||
|
||||
* A fun amount of new tests all over including for gometalinter
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
BUGS
|
||||
|
||||
* We fixed a bunch of stuff, and added more tests!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started!
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
James Shubin, Julien Pivotto, Mildred Ki'Lya
|
||||
We had 3 unique committers since 0.0.11, and have had 30 overall.
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
89
docs/release-notes/0.0.13
Normal file
89
docs/release-notes/0.0.13
Normal file
@@ -0,0 +1,89 @@
|
||||
I've just released version 0.0.13 of mgmt!
|
||||
|
||||
I guess this is an appropriate number for a scary October release.
|
||||
|
||||
We recently re-licensed to the more permissive GPL. I put a lot of
|
||||
thought into it, and also wrote up a short blog post about some of the
|
||||
reasoning. It's here:
|
||||
|
||||
https://ttboj.wordpress.com/2017/10/17/copyleft-is-dead-long-live-copyl
|
||||
eft/
|
||||
|
||||
If you read to the end you'll also see that Red Hat is not funding my
|
||||
mgmt work anymore. This is too bad, and means I'll only have small
|
||||
amounts of personal time available to work on this. If you'd like to
|
||||
help fund my work, or know someone who'd like to, please let me know!
|
||||
|
||||
Having said that, there's some great new stuff that landed since
|
||||
0.0.12, including:
|
||||
|
||||
* new resources from new contributor Jonathan Gold (aws, group, user)
|
||||
|
||||
* an HCL frontend from new contributor Chris McKenzie
|
||||
|
||||
* polish in a number of places including in the nspawn resource
|
||||
|
||||
and so much more... If you'd prefer to have releases more often, then
|
||||
please let me know! Lastly, the language and a number of associated
|
||||
parts are on its way. I hope to push this monster patch to git master
|
||||
before February.
|
||||
|
||||
It's also worth mentioning that we have 17 resources now! wow.
|
||||
|
||||
NEWS
|
||||
|
||||
140 files changed, 3921 insertions(+), 848 deletions(-)
|
||||
|
||||
* Many improvements to tests, testing and small fixes to avoid false-
|
||||
failures on travis.
|
||||
|
||||
* golint now reports 0 issues.
|
||||
|
||||
* An HCL frontend if you'd prefer that to the YAML. Also a great
|
||||
example of how to plug in a new frontend.
|
||||
|
||||
* An update to golang 1.8 as the minimum version required.
|
||||
|
||||
* Bump the etcd version to 3.2.6+ -- Looking forward to a 3.3 release
|
||||
which should probably include some patches we upstreamed.
|
||||
|
||||
* Addition of new user and group resources. These also include a bunch
|
||||
of automatic edges.
|
||||
|
||||
* Addition of an AWS resource! I've wanted this for a while, as it
|
||||
demonstrates nicely how event based cloud resources can fit nicely into
|
||||
our design. There's still a lot to do, and we have some suggestions for
|
||||
Amazon too. If you have a contact there, please put me in touch!
|
||||
|
||||
* Our nspawn resource is more polished now.
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started!
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is free and friendly. You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
AdnanLFC, Arthur Mello, ChrisMcKenzie, Dennis Kliban, Ismael Puerto,
|
||||
James Shubin, Jonathan Gold, Juan Luis de Sousa-Valadas Castaño, Juan-
|
||||
Luis de Sousa-Valadas Castaño (although I suspect the last two are the
|
||||
same ;))
|
||||
|
||||
We had 9 unique committers since 0.0.12, and have had 38 overall.
|
||||
Run 'git log 0.0.12..0.0.13' to see what has changed since 0.0.12
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
81
docs/release-notes/0.0.14
Normal file
81
docs/release-notes/0.0.14
Normal file
@@ -0,0 +1,81 @@
|
||||
I've just released version 0.0.14 of mgmt!
|
||||
|
||||
> 118 files changed, 2688 insertions(+), 974 deletions(-)
|
||||
|
||||
There's some great new stuff that landed since
|
||||
0.0.13, including:
|
||||
|
||||
* amazon AWS EC2 resource is now in git master.
|
||||
|
||||
* more automatic edges from new contributor Guillaume Herail (xiu)
|
||||
|
||||
* a move to golang 1.8 or higher
|
||||
|
||||
and so much more... This will probably be the last release before the
|
||||
language lands in git master. It's a pretty giant patch coming :/
|
||||
|
||||
NEWS
|
||||
|
||||
* We're > 1k stars on GitHub now. It's a silly metric, but ¯\_(ツ)_/¯
|
||||
|
||||
* Jonathan Gold has done a lot of hard work on the AWS EC2 resource,
|
||||
and it's now in git master. There are still many things we'd like to
|
||||
do, but it's a great start on what is a *monster* resource, and
|
||||
hopefully it will inspire others to get involved.
|
||||
|
||||
In particular, it was a great learning experience (for me in
|
||||
particular!) about how bad the EC2 golang API is. There are some
|
||||
notable design bugs we found, and if anyone from Amazon engineering
|
||||
would like to reach out to us, we'd love to provide some constructive
|
||||
ideas.
|
||||
|
||||
* Guillaume Herail (xiu) wrote some really nice patches, and picked up
|
||||
on the autoedges API very quickly. Hopefully he'll have time to work on
|
||||
even more!
|
||||
|
||||
* Paul Morgan sent us some nice shell fixups-- many more exist in an
|
||||
open PR that didn't make it into this release. Hopefully we'll get
|
||||
those merged by 0.0.15
|
||||
|
||||
* Felix Frank did a few Puppet compiler fix ups. I think he's been
|
||||
refreshing his work with new resources recently...
|
||||
|
||||
* We're looking for help writing Google, DigitalOcean, Rackspace, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
EVENTS
|
||||
|
||||
There are a bunch of upcoming mgmt talks and events! Stay tuned for
|
||||
details in the coming email, but TL;DR: Linux Conf Australia, FOSDEM, &
|
||||
CfgMgmtCamp.eu -- from three different speakers, and including a
|
||||
hackathon too!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started! Expect many new tagged #mgmtlove
|
||||
issues within the month.
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is free and friendly. You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Felix Frank, Guillaume Herail, James Shubin, Jonathan Gold,
|
||||
jonathangold, Julien Pivotto, Paul Morgan, Toshaan Bharvani
|
||||
We had 8 unique committers since 0.0.13, and have had 41 overall.
|
||||
run 'git log 0.0.13..0.0.14' to see what has changed since 0.0.13
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
116
docs/release-notes/0.0.15
Normal file
116
docs/release-notes/0.0.15
Normal file
@@ -0,0 +1,116 @@
|
||||
I've just released version 0.0.15 of mgmt!
|
||||
|
||||
> 328 files changed, 29869 insertions(+), 943 deletions(-)
|
||||
|
||||
(Yeah, that's almost 30k+ LOC)
|
||||
|
||||
There's some great new stuff that landed since 0.0.14, including:
|
||||
|
||||
* THE LANGUAGE (mcl)
|
||||
|
||||
* "Deploys": a distributed way to push code into your cluster
|
||||
|
||||
* Scheduling (as a reactive function)
|
||||
|
||||
* Better testing
|
||||
|
||||
* a move to etcd 3.3+ and golang 1.9+
|
||||
|
||||
and so much more... This is a monster release. Please try out the
|
||||
language and all the other new features today :)
|
||||
|
||||
NEWS
|
||||
|
||||
* We released the language. Please play around with it :) It's time to
|
||||
get used to this cool new paradigm. Learn more from the...
|
||||
|
||||
Blog post:
|
||||
https://purpleidea.com/blog/2018/02/05/mgmt-configuration-language/
|
||||
|
||||
Video:
|
||||
https://www.youtube.com/watch?v=NxObmwZDyrI
|
||||
|
||||
Docs:
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/language-guide.md
|
||||
|
||||
Function guide:
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/function-guide.md
|
||||
|
||||
And tons of code all over git master. Check the lang/ folder.
|
||||
|
||||
* There is a reactive scheduler in the language. Use your imagination,
|
||||
or play around with:
|
||||
https://github.com/purpleidea/mgmt/blob/3ad7097c8aa7eab7f895aab9af22338
|
||||
c0cf82986/lang/funcs/core/schedule_polyfunc.go#L18
|
||||
|
||||
* There is a "deploys" feature. It's not documented yet. You should
|
||||
poke around if you're curious. Consider this an early soft release.
|
||||
|
||||
* There is a FS implementation to store files in a POSIX-like layer on
|
||||
top of etcd. It's used by deploys. It needs more tests though :)
|
||||
|
||||
* The language grew two "simple" API's for implementing functions, so
|
||||
that new functionality can be exposed in the mgmt language.
|
||||
|
||||
* The language grew two ways to specify edges between resources: either
|
||||
internal to the resource, or externally as standalone edges.
|
||||
|
||||
* The language now supports optional resource parameters via the
|
||||
"elvis" operator. This keeps things type safe and avoids needing an
|
||||
"undef" or "nil" in the language. This operator also works for edge
|
||||
declarations.
|
||||
|
||||
* New contributor Johan Bloemberg has been on fire sending patches!
|
||||
He has already made some great improvements to our Makefile for
|
||||
testing, and the addition of the env* functions in the language, with
|
||||
much more code pending in open PR's.
|
||||
|
||||
* Initial debian packaging has been added. It now needs a maintainer to
|
||||
build, upload, and love it :)
|
||||
|
||||
* We have an early emacs major mode for "mcl", our language.
|
||||
|
||||
* Lots of new documentation has been added. Particularly for developers
|
||||
wanting to contribute to the project.
|
||||
|
||||
* We're looking for help writing Google, DigitalOcean, Rackspace, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many new tagged #mgmtlove issues were tagged:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved.
|
||||
This is free and friendly. You get to improve your skills,
|
||||
and we get
|
||||
some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Carsten Thiel, dsx, James Shubin, Joe Julian, Johan Bloemberg, Jonathan
|
||||
Gold, jonathangold, karimb, Oliver Frommel, Peter Oliver, Toshaan
|
||||
Bharvani, Wim
|
||||
We had 12 unique committers since 0.0.14, and have had 48 overall.
|
||||
run 'git log 0.0.14..0.0.15' to see what has changed since 0.0.14
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
104
docs/release-notes/0.0.16
Normal file
104
docs/release-notes/0.0.16
Normal file
@@ -0,0 +1,104 @@
|
||||
I've just released version 0.0.16 of mgmt!
|
||||
|
||||
> 220 files changed, 14243 insertions(+), 9491 deletions(-)
|
||||
|
||||
Woo...
|
||||
|
||||
There's some great new stuff that landed since 0.0.15, including:
|
||||
|
||||
* A giant engine re-write! (Makes resource writing more elegant too.)
|
||||
|
||||
* New resources!!
|
||||
|
||||
* New language features!!!
|
||||
|
||||
and so much more... This is a monster release. Please try out the new
|
||||
features today :)
|
||||
|
||||
NEWS
|
||||
|
||||
* New resources include: net, mount and docker:container. Jonathan was
|
||||
responsible for all of these. Please take them for a spin! He's looking
|
||||
for a job too, and would probably be happy to get paid to work on mgmt.
|
||||
|
||||
* We're > 1.5k stars on GitHub now. It's a silly metric, but ¯\_(ツ)_/¯
|
||||
|
||||
* A giant engine refactoring/re-write was done. This cleaned up the
|
||||
code significantly, and made it more elegant to write resources.
|
||||
Unfortunately there is one small bug that I missed and that I haven't
|
||||
fixed yet. It rarely happens except during some of our tests during
|
||||
shutdown, which causes intermittent failures. It shouldn't block you
|
||||
playing with mgmt.
|
||||
|
||||
* The language "class" and "include" statements have been added. These
|
||||
are important pieces for writing reusable modules which are coming
|
||||
soon. Try them out! (This comes with a bunch of tests too.)
|
||||
|
||||
* We have an integration testing framework. It's pretty cool, it spins
|
||||
up a full mgmt cluster and runs stuff. Try it out or add some tests.
|
||||
|
||||
* I had fun fixing a big bug: 06ee05026b0c743d19c7d62675f8ddeabdc8dd4f
|
||||
|
||||
* I removed the remote execution functionality from core. I realized it
|
||||
could be re-written as a resource, and it was also in the way from some
|
||||
other cleanups that were more important. Half the new code is done,
|
||||
ping me if this is a priority for you or you want to help.
|
||||
|
||||
* I also removed the HCL front-end, because mcl is usable enough to be
|
||||
more fun to play with, and I wanted to refactor some code. If someone
|
||||
really wants it back, let me know.
|
||||
|
||||
* We have some release building scripts in git master, so you can now
|
||||
download pre-built (with fpm) RPM, DEB, or PACMAN packages! They're
|
||||
signed too. https://github.com/purpleidea/mgmt/releases/tag/0.0.16
|
||||
|
||||
* We're looking for help writing Google, DigitalOcean, Rackspace, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
MISC
|
||||
|
||||
I took a bit of a break recently to catch up on some life stuff, but I
|
||||
think I'm back on track. While git master hasn't been especially busy,
|
||||
there's an active feature branch at feat/import which contains some fun
|
||||
stuff, with a very WIP giant patch still sitting on my machine. I hope
|
||||
to finish it up soon and then do another release. That branch contains
|
||||
one of the last big features before I'll really be ready to run mgmt on
|
||||
my personal servers!
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many new tagged #mgmtlove issues were tagged:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved.
|
||||
This is free and friendly. You get to improve your skills,
|
||||
and we get
|
||||
some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Alan Jenkins, James Shubin, jesus m. rodriguez, Jonathan Gold,
|
||||
jonathangold, Lauri Ojansivu, phaer
|
||||
We had 7 unique committers since 0.0.15, and have had 52 overall.
|
||||
run 'git log 0.0.15..0.0.16' to see what has changed since 0.0.15
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
111
docs/release-notes/0.0.17
Normal file
111
docs/release-notes/0.0.17
Normal file
@@ -0,0 +1,111 @@
|
||||
I've just released version 0.0.17 of mgmt!
|
||||
|
||||
> 269 files changed, 13281 insertions(+), 1633 deletions(-)
|
||||
|
||||
There's some very useful stuff that landed since 0.0.16, including:
|
||||
|
||||
* Modules and import system now exists!
|
||||
* A lot of tests, fixes and a huge new test infra was added
|
||||
* Merging puppet with mcl code is now possible (madness!)
|
||||
* We made a small change to the cli UI
|
||||
* Bump to golang 1.10 (please update your environments)
|
||||
|
||||
And much more...
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are available here:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.17
|
||||
|
||||
NEWS
|
||||
|
||||
* One of the biggest missing features was the lack of a module/import
|
||||
system. After some initial exploration on what turned out to be a dead-
|
||||
end, I found what I think is a very elegant approach, which is now in
|
||||
this release. Please try it out, there are docs available. I hope to
|
||||
write a blog post about it soon.
|
||||
|
||||
(There's one additional "kind" of import that I'm considering, similar
|
||||
to a macro "#include", that I might add. To be determined. Let me know
|
||||
if you find anything missing as of today.)
|
||||
|
||||
* Felix added the first version of his mcl+puppet frontend. This should
|
||||
allow you to more cleverly merge legacy puppet environments with mcl
|
||||
code. It's fantastic, take a look.
|
||||
|
||||
* Jonathan added a systemd-timer resource. This is a great replacement
|
||||
for cron.
|
||||
|
||||
* We changed the CLI ui to improve the determinism of the frontend
|
||||
selection. Basically we changed from: `mgmt run --lang code.mcl` to:
|
||||
`mgmt run lang --lang code.mcl`. Remember to put --tmp-prefix after
|
||||
`run` directly where it is used.
|
||||
|
||||
* We made a whole bunch of cleanups to the test infra, added new test
|
||||
infra for testing complex mcl modules and the import/module system, and
|
||||
of course added new tests.
|
||||
|
||||
* You can pass a list of strings as the resource name to build that
|
||||
many resources. (Looping/iteration!)
|
||||
|
||||
* You can specify all the metaparams and auto-* properties in mcl now.
|
||||
|
||||
* Native mcl code can be used to write imported core code.
|
||||
|
||||
* There was a bug that snuck into the pkg res. This has now been fixed.
|
||||
|
||||
* A small, long-time copy+pasta error bug was fixed in Exec.
|
||||
|
||||
* Virtually all the imports of the "log" package are at the top-level
|
||||
now. This will make moving to a new logger easier in the future. I have
|
||||
an innovative logger idea that I have a design for that I'll eventually
|
||||
get to.
|
||||
|
||||
* A few workarounds for occasional test failures were added. Some
|
||||
legacy code needs a cleanup, and it's not done yet. Fortunately, none
|
||||
of these issues seem to occur in real-life as far as I can tell, and
|
||||
are caused by closing down mgmt at weird times.
|
||||
|
||||
* Found a bug (now fixed) in the upstream lexer Yikes! See:
|
||||
57ce3fa587897d74634c1216af67dd42252c64e5
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many new tagged #mgmtlove issues were tagged:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Felix Frank, James Shubin, Jonathan Gold, Kevin Kuehler, Michael Lesko-
|
||||
Krleza, Tom Payne, Vincent Membré
|
||||
We had 7 unique committers since 0.0.16, and have had 56 overall.
|
||||
run 'git log 0.0.16..0.0.17' to see what has changed since 0.0.16
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
106
docs/release-notes/0.0.18
Normal file
106
docs/release-notes/0.0.18
Normal file
@@ -0,0 +1,106 @@
|
||||
I've just released version 0.0.18 of mgmt!
|
||||
|
||||
> 202 files changed, 5606 insertions(+), 1880 deletions(-)
|
||||
|
||||
There's some great stuff that landed since 0.0.17, including:
|
||||
|
||||
* A re-write of the core engine algorithm
|
||||
* Tests are very stable
|
||||
* At least three old bugs were killed
|
||||
* An infra to write tests for individual resources was added
|
||||
|
||||
And much more...
|
||||
|
||||
This adds a significant amount of polish and bug fixes to mgmt. We're
|
||||
on the home stretch to MVP!
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are available here:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.18
|
||||
|
||||
NEWS
|
||||
|
||||
* There was a rare race that would panic the engine on shutdown. This
|
||||
only seemed to happen in CPU/system call starved environments like
|
||||
travis-ci. This was due to some ignorance when writing this early part
|
||||
of the code base. The algorithm has been re-written, and this also
|
||||
removed the use of at least one mutex. Things should be stable now, and
|
||||
also much faster. (Although perf was never an issue.)
|
||||
|
||||
* A hidden race/deadlock in the exec resource was found and killed.
|
||||
Woo! Some new tools to help find these and other problems are in misc/
|
||||
|
||||
* The early converger code was re-written. I was not as knowledgeable
|
||||
about golang in the very beginning, and this code needed refreshing. It
|
||||
contained a rare deadlock which needed to be killed.
|
||||
|
||||
* Toshaan added an uptime() function.
|
||||
|
||||
* Julien added a method for generating some simple functions for the
|
||||
language.
|
||||
|
||||
* Lander added two new functions.
|
||||
|
||||
* James added a new readfile() function, and other examples.
|
||||
|
||||
* The template function now allows you to use imported functions. They
|
||||
use underscores instead of periods for namespace separation due to a
|
||||
limitation in the template library.
|
||||
|
||||
* Kevin and I killed a tricky race in the SocketSet code! Woo :) Kevin
|
||||
also added a great cpucount() fact!
|
||||
|
||||
* James gave a number of presentations at FOSDEM. Some recordings are
|
||||
available: https://purpleidea.com/talks/
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
BUGS
|
||||
|
||||
* An unfortunate bug in the type unification code was found. This can
|
||||
cause small code bases to take a lot of ram/cpu to run. This will be
|
||||
prioritized in an upcoming release. Until then you'll have to avoid
|
||||
fancy type unification. (Specify types you know when it has to guess.)
|
||||
|
||||
If efficient type unification algorithms are your specialty, please let
|
||||
us know, we'd like your help!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many tagged #mgmtlove issues exist:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Felix Frank, James Shubin, Jeff Waugh, Johan Bloemberg, Julien Pivotto,
|
||||
Kevin Kuehler, Lander Van den Bulcke, Toshaan Bharvani
|
||||
We had 8 unique committers since 0.0.17, and have had 58 overall.
|
||||
run 'git log 0.0.17..0.0.18' to see what has changed since 0.0.17
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
131
docs/release-notes/0.0.19
Normal file
131
docs/release-notes/0.0.19
Normal file
@@ -0,0 +1,131 @@
|
||||
I've just released version 0.0.19 of mgmt!
|
||||
|
||||
> 361 files changed, 10451 insertions(+), 3919 deletions(-)
|
||||
|
||||
This is a very important (and huge) release and has some important
|
||||
fixes that landed since 0.0.18, including:
|
||||
|
||||
* A huge re-write of the elastic etcd clustering code base
|
||||
* A significant improvement in the type unification algorithm
|
||||
* An important import/class scoping bug was discovered and fixed
|
||||
* New mcl functions and resource improvements
|
||||
|
||||
And much more...
|
||||
|
||||
What comes next is just polish, new features and small bug fixes!
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are available here:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.19
|
||||
|
||||
NEWS
|
||||
|
||||
* A giant etcd re-write was completed and merged. The elastic
|
||||
clustering algorithm is not perfect, however it should suffice for most
|
||||
use cases, and it's always possible to point mgmt at an external etcd
|
||||
cluster if you don't understand the limitations of the automatic
|
||||
clustering algorithm. The important part is that the core code is much
|
||||
cleaner now, so hopefully races and bugs of ignorance are gone now. :)
|
||||
|
||||
* I found an unfortunate bug in the type unification algorithm that
|
||||
severely impacted performance for some types of code bases. This is now
|
||||
fixed, and I hope we should not experience problems again! Special
|
||||
thanks to Sam for talking me through the problem and understanding the
|
||||
space better! Woo \o/
|
||||
|
||||
* An important import/class scoping bug was fixed. Thanks to Nicolas
|
||||
for the bug report. We also added tests for this too!
|
||||
|
||||
* Nicolas also added our first os detection function. os.is_debian and
|
||||
os.is_redhat are now in core. Get your favourite os added today!
|
||||
|
||||
* The polymorphic len function can also check str length.
|
||||
|
||||
* The exec resource got a big cleanup. It also learned the interrupt
|
||||
trait so that long running commands can be forcefully killed if need
|
||||
be.
|
||||
|
||||
* A fancy new test infra for testing functions over time was added.
|
||||
Anytime we want to check our individual FRP functions are working as
|
||||
expected, this is an easy way to add a test. This way, if we ever find
|
||||
a bug, we can drop in a test with the fix. This actually helped find a
|
||||
very subtle bug in readfile that nobody had experienced yet!
|
||||
|
||||
* File res with state => exists but no content now performs as
|
||||
expected.
|
||||
|
||||
* Improved send/recv, since it was neglected a bit. Hopefully it ends
|
||||
up being a useful primitive.
|
||||
|
||||
* Added a new synchronization primitive that I'm called
|
||||
SubscribedSignal. I found it very useful for building some of my code,
|
||||
and I hope you'll find it useful too. I'd offer it upstream to the sync
|
||||
package if Google didn't force their crappy CLA nonsense on everyone
|
||||
who wanted to send a patch. :/ Death by 1000 paper cuts, I guess.
|
||||
|
||||
* Added a match function in the new regexp core package. Try it out and
|
||||
add some more functions!
|
||||
|
||||
* Wouter has been testing mgmt and filling all sorts of useful bug
|
||||
reports. We fixed at least one related to a report, and more are
|
||||
planned for the next release. Wouter also sent in one cleanup patch to
|
||||
remove some dead code. Welcome to the project!
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
BUGS
|
||||
|
||||
* There are a few known issues, in particular with some over eager
|
||||
checking done in the Validate portion of two resources, that should
|
||||
actually be runtime checks in CheckApply. As a result, if you intend to
|
||||
change some state during the graph execution, the resource won't see
|
||||
it. These should be very easy to fix if someone is interested in
|
||||
writing the patch!
|
||||
|
||||
TALKS
|
||||
|
||||
* James will be presenting at this year's OSDC in Berlin. There will be
|
||||
a workshop: https://osdc.de/events/mgmt-config-workshop/
|
||||
and a talk:
|
||||
https://osdc.de/events/mgmt-config-the-future-of-your-autonomous-datacentre/
|
||||
Sign up soon if you want to guarantee a spot, as they're limited!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many tagged #mgmtlove issues exist:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Adam Sigal, Felix Frank, James Shubin, Jonathan Gold, Michael Schubert,
|
||||
Mitch Fossen, Nicolas Charles, Wouter Dullaert
|
||||
We had 8 unique committers since 0.0.18, and have had 63 overall.
|
||||
run 'git log 0.0.18..0.0.19' to see what has changed since 0.0.18
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
145
docs/release-notes/0.0.20
Normal file
145
docs/release-notes/0.0.20
Normal file
@@ -0,0 +1,145 @@
|
||||
I've just released version 0.0.20 of mgmt!
|
||||
|
||||
> 295 files changed, 8585 insertions(+), 1413 deletions(-)
|
||||
|
||||
This was a very challenging release but it includes many useful changes
|
||||
since 0.0.19, including:
|
||||
|
||||
* Function values / lambdas exist and are first-class
|
||||
* Over 70 new tests have been added!
|
||||
* Core functions, classes, and globals can now be written in mcl code
|
||||
* A new compiler "Ordering" step was added
|
||||
|
||||
And much more...
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are available here:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.20
|
||||
|
||||
NEWS
|
||||
|
||||
* After a long and challenging road, I finally got function values and
|
||||
lambdas merged. If you find any bugs, please let me know! You can now
|
||||
treat functions as first-class values, and even use them as closures by
|
||||
capturing variable state.
|
||||
|
||||
* Over 70 new tests were added, most of which test the behaviour of the
|
||||
new functions.
|
||||
|
||||
* Core packages always allowed you to write new functions in pure
|
||||
golang, but now you can implement new functions, classes, and even
|
||||
variables in native mcl code! It's still desirable and perhaps
|
||||
efficient in some cases to want native golang implementations, but all
|
||||
good programs self-host some of their stdlib in their own language
|
||||
eventually.
|
||||
|
||||
* A new compiler step called "Ordering" was added. It's hidden inside
|
||||
the SetScope step, but now lets us generate code ordering graphs and
|
||||
determine exactly what to run first.
|
||||
|
||||
* Light copying of Node's allows more correct and efficient function
|
||||
graphs that can share common vertices and edges. For example, if two
|
||||
different closures capture a variable $x, they'll both use the same
|
||||
copy when running the function, since the compiler can prove if they're
|
||||
identical.
|
||||
|
||||
* Improved the type system slightly to allow advanced type comparisons.
|
||||
|
||||
* The type unification algorithm was improved. Hopefully it should
|
||||
solve all scenarios without needing the recursive solver which was very
|
||||
slow. If you find a case that isn't speedy, please let us know!
|
||||
|
||||
* Added subtest listing by using -short and -v in a test. This lets you
|
||||
know what's available and how to run individual sub tests easily.
|
||||
|
||||
* Support for the systemd STATE_DIRECTORY and xdg cache dir was added
|
||||
by new contributor John! Thanks!
|
||||
|
||||
* New contributor Adam added a pgraph test.
|
||||
|
||||
* A bug in the systemd mount resource was fixed. Hopefully it should
|
||||
work correctly now.
|
||||
|
||||
* A bug that prevented us from allowing nested system imports was
|
||||
fixed. Nest away! This will pave the way for us to automatically import
|
||||
most of the golang standard library by doing: import "golang/regexp" or
|
||||
golang/whatever".
|
||||
|
||||
* Added an example showing that unicode is allowed in strings.
|
||||
|
||||
* Fixed a rare race in the engine.
|
||||
|
||||
* Added some new core functions including math.mod and datetime
|
||||
improvements.
|
||||
|
||||
* Changed the API to remove the use of --lang. This avoids the
|
||||
stuttering.
|
||||
|
||||
* Moved to golang 1.11 and etcd 3.3.13. The later includes a fix for an
|
||||
un-catchable error scenario which we fixed in etcd.
|
||||
|
||||
* Improved the pgraph library significantly so that we can generate
|
||||
better graphs with accurate vertices based on the vertex pointers.
|
||||
|
||||
* Added ArchLinux OS family detection.
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
BUGS
|
||||
|
||||
* There are a few known issues with some disabled code paths in the new
|
||||
function value code. These issues don't *need* to be fixed, but if they
|
||||
are, then we should see a slight performance increase. Happy to have
|
||||
someone dig into these, and they shouldn't bother anyone.
|
||||
|
||||
* Some of the pre-built binaries might not work on your system. We need
|
||||
to start building them with the right dependencies so that `ldd`
|
||||
related things are happy. For now, please try building yourself if the
|
||||
build doesn't work for you, or help improve our build system.
|
||||
|
||||
TALKS
|
||||
|
||||
I'll be giving a talk at an upcoming mini-conference in Montreal. If
|
||||
you're interested in attending, please let me know.
|
||||
If you'd like to give an mgmt talk somewhere, please let me know!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many tagged #mgmtlove issues exist:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Adam Sigal, Christian Rebischke, Felix Frank, James Shubin, Jan
|
||||
Martens, Johan Bloemberg, John Hooks, Ward Vandewege
|
||||
We had 8 unique committers since 0.0.19, and have had 67 overall.
|
||||
run 'git log 0.0.19..0.0.20' to see what has changed since 0.0.19
|
||||
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
133
docs/release-notes/0.0.21
Normal file
133
docs/release-notes/0.0.21
Normal file
@@ -0,0 +1,133 @@
|
||||
I've just released version 0.0.21 of mgmt!
|
||||
|
||||
> 178 files changed, 4351 insertions(+), 829 deletions(-)
|
||||
|
||||
This was a very lonely release but it includes some very useful
|
||||
additions since 0.0.20, including:
|
||||
|
||||
* The first mgmt meme!
|
||||
* Working distro packages for Fedora, Debian, Ubuntu and Arch!
|
||||
* Reversible resources!
|
||||
* A deploy package to let you read files from the active deploy
|
||||
* Improved file resource behaviours
|
||||
|
||||
And much more...
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are available here:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.21
|
||||
|
||||
NEWS
|
||||
|
||||
* Someone made a cool mgmt meme. Look in art/mgmt_*_meme.jpg
|
||||
|
||||
* Distro packages are now built properly, so they should all work now.
|
||||
We have builds for Fedora-30, Fedora-29, Debian-10, Ubuntu-Bionic, and
|
||||
Archlinux. If you'd like a build for a different distro/version please
|
||||
let me know.
|
||||
|
||||
* We finally got rid of the old `Compare(...) bool` API, and moved to
|
||||
`Cmp(...) error`. We'll now get more useful information from Res
|
||||
compares when they differ. Thanks to new contributor Donald Bakong for
|
||||
working on this. He's ramping up his golang contributions, so we expect
|
||||
to see more from him in the future!
|
||||
|
||||
* We now have reversible resources. Basically if you create a resource
|
||||
and specify the reverse metaparam, eg: `Meta:reverse => true`, then if
|
||||
a resource is removed (either because a new version of code doesn't
|
||||
have it anymore OR more importantly if it was inside an `if` block that
|
||||
became false) then the engine will perform some "reverse" action for
|
||||
it. For a file, if it was added, we'll remove it. If it was edited,
|
||||
we'll undo the edit. If we added ugo+w, we'll remove that. And so on.
|
||||
The engine bits are done, and as well so have the file resource bits.
|
||||
It should be easy to add this for any other resource where it makes
|
||||
sense. This will likely be a very powerful feature that we use a lot.
|
||||
|
||||
* The file resource was changed slightly so that by default the "state"
|
||||
is undefined. As a result, if you want a file to be created and none is
|
||||
present, you need to specify the state. Otherwise specifying "content"
|
||||
will only edit a file if it already exists, and otherwise be an error.
|
||||
It turns out this is actually a better behaviour, even if it's not
|
||||
necessarily intuitive for puppet users. It turns out it simplifies the
|
||||
code drastically and makes the reversible file resource much more
|
||||
logical. It seems that Puppet and Ansible got this wrong, but Cfengine
|
||||
got it right. IIRC. Do you agree? (Look at the code!)
|
||||
|
||||
* We now catch CR \r characters in code so that you don't wonder why
|
||||
the compiler is telling you about unexpected whitespace. This should
|
||||
make your life easier.
|
||||
|
||||
* You can now read files from within the deploy. This can be used for
|
||||
templates or anything else. This was one of the last missing things
|
||||
that was blocking me from writing useful mcl modules.
|
||||
|
||||
* Fixed a copy-pasta bug where the != operator (for strings only) was
|
||||
actually doing an ==. Woops! The good news is that we've been shaking
|
||||
out silly bugs because I've been using mgmt more and more. Hopefully
|
||||
there aren't any woops ones like this left!
|
||||
|
||||
* A bunch of function, class, and include tests were added. We're
|
||||
getting really well tested!
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
BUGS
|
||||
|
||||
* Function values as args don't work yet. This is blocking us from
|
||||
implementing functions like map/reduce/filter, because they'd want to
|
||||
receive a function as input. TBH, I'm a bit tunnel-visioned on this,
|
||||
because I'm not the compilers genius that you are. If you can help,
|
||||
please let me know. I'll be posting a bunch of test cases that show
|
||||
what's needed shortly.
|
||||
|
||||
* Three patches have been submitted to mkosi to support the image
|
||||
building I've been doing. They're not merged yet, so you'll have to
|
||||
apply them yourself if you want to make your own distro images. This
|
||||
isn't a major requirement anyone should have, but if they're not
|
||||
merged, we'll store them here and apply them as needed.
|
||||
|
||||
TALKS
|
||||
|
||||
I'll be in Belgium for FOSDEM and CfgMgmtCamp in 2020. If I'm lucky
|
||||
I'll have at least one mgmt presentation. I might also consider going
|
||||
to DevConf.cz if I get a talk accepted. Feel to ping me if you'd like
|
||||
to hack, get consulting, training, etc while I'm in Europe!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
|
||||
you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many tagged #mgmtlove issues exist:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
bjanssens, Donald Bakong, James Shubin
|
||||
We had 3 unique committers since 0.0.20, and have had 69 overall.
|
||||
run 'git log 0.0.20..0.0.21' to see what has changed since 0.0.20
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
160
docs/release-notes/0.0.22
Normal file
160
docs/release-notes/0.0.22
Normal file
@@ -0,0 +1,160 @@
|
||||
I've just released version 0.0.22 of mgmt!
|
||||
|
||||
> 579 files changed, 17984 insertions(+), 3136 deletions(-)
|
||||
|
||||
Compared to the last release, this is a monster. Previously:
|
||||
|
||||
> 178 files changed, 4351 insertions(+), 829 deletions(-)
|
||||
|
||||
I apologize for not doing a release earlier, but to be quite honest,
|
||||
I've been busy, the people who are playing with mgmt are doing their
|
||||
own builds anyways, and there are still some unimplemented, pre-
|
||||
production features missing.
|
||||
|
||||
Also, I started a new job. News on my blog and how (if at all) it
|
||||
affects mgmt is there.
|
||||
|
||||
This is still an incredibly important release, so let's cover some of
|
||||
the points!
|
||||
|
||||
* This is the last release before we switch to go.mod
|
||||
* IRC channel moved to #mgmtconfig on libera.chat after Freenode died
|
||||
* New resources including tftp, dhcp, and http (all as servers, wow!)
|
||||
* New string interpolation implementation with many tests
|
||||
* Resource fields can accept complex structs and other types now
|
||||
* Improved type unification solver and new invariants like generator
|
||||
* A new polymorphic function API interface
|
||||
|
||||
And much more...
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are NOT available here for this release:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.22
|
||||
|
||||
NEWS
|
||||
|
||||
* The file resource supports building files from other "fragments".
|
||||
This is magic and automatic and real-time. Docs and examples are in the
|
||||
repo.
|
||||
|
||||
* The file resource (and others) have a new trait and queryable API to
|
||||
make decisions based on what other resources are in the graph.
|
||||
|
||||
* The file resource has a "purge" option to remove unmanaged files from
|
||||
a managed directory.
|
||||
|
||||
* A lot of built-in functions are auto-generated from the stdlib. Most
|
||||
things you would want are now present, particularly if they're pure
|
||||
functions.
|
||||
|
||||
* There's a new consul KV resource.
|
||||
|
||||
* File resources support symbolic modes now!
|
||||
|
||||
* New tftp related resources are now present. They're great and I use
|
||||
them to provision things!
|
||||
|
||||
* A docker image resource was added.
|
||||
|
||||
* First-class constants now exist. So you can do
|
||||
$const.res.file.state.exists instead of typing "exists" which is prone
|
||||
to typos. This is more verbose, but it's safer if that's your priority.
|
||||
|
||||
* We found a bug with fuzzing! Cool, thanks Patrick!
|
||||
|
||||
* We have dhcp server related resources. This is pretty cool when
|
||||
combined with the tftp resource and you can provision a lot of stuff
|
||||
from your laptop and one binary now.
|
||||
|
||||
* We also have http server resources. Combined with the tftp and dhcp
|
||||
resources mgmt starts to look like a powerful tool to greenfield a new
|
||||
datacentre and then take over and manage it continuous. All from a
|
||||
single, type-safe, code base. Of course you can do other things with
|
||||
this, and I'm looking forward to seeing the ideas that I haven't
|
||||
thought of yet! PS: An http:ui has been partially implemented too. Ping
|
||||
if you want to know more.
|
||||
|
||||
* Resource fields couldn't previously accept anonymous structs as types
|
||||
because of how golang built its reflect library. Joe found an elegant
|
||||
workaround, thanks!
|
||||
|
||||
* The type unification solver was improved to support some new
|
||||
invariants. This makes a lot of new things possible, and was done to
|
||||
support new complex functions including the eventual addition of map,
|
||||
reduce, and filter. One of the new invariants is a "generator"
|
||||
invariant, so that unification can take into account the entire
|
||||
relevant parts of the AST. It's not a textbook CS implementation, but
|
||||
it's based on sound theory I think, and it seems to work great. If you
|
||||
find an edge case, please let us know.
|
||||
|
||||
* The polymorphic function interface was changed to use the new
|
||||
unification logic. This makes a lot more sense. All the functions have
|
||||
been ported to the new interface.
|
||||
|
||||
* 42
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, etc
|
||||
resources if anyone is interested, reach out to us. Particularly if
|
||||
there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
BUGS
|
||||
|
||||
* Function values as args don't work yet. This is blocking us from
|
||||
implementing functions like map/reduce/filter, because they'd want to
|
||||
receive a function as input. TBH, I'm a bit tunnel-visioned on this,
|
||||
because I'm not the compilers genius that you are. If you can help,
|
||||
please let me know. I'll be posting a bunch of test cases that show
|
||||
what's needed shortly.
|
||||
|
||||
(This is the exact message I wrote last time. I've made a lot of
|
||||
progress since then, but motivation here has been low. Reach out if you
|
||||
can help.)
|
||||
|
||||
TALKS
|
||||
|
||||
Hopefully CfgMgmtCamp in 2022 will be back on. If we're lucky and safe,
|
||||
maybe I can travel there. TBD... Feel to ping me if you'd like to hack,
|
||||
talk, whatever if I'm in Europe.
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Libera IRC, or ping this list if
|
||||
you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many tagged #mgmtlove issues exist:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
Although asking in IRC is the best way to find something to work on.
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Adam Sigal, Ahmed Al-Hulaibi, David Randall, Derek Buckley, Donald
|
||||
Bakong, Felix Frank, Francois Rompre-Lanctot, Ivan Pejić, James Shubin,
|
||||
Jean-Philippe Evrard, Jimmy Tang, Joe Groocock, Jonathan Gold, Julien
|
||||
Pivotto, Kenneth Hoste, Matthew Lesko-Krleza, Patrick Meyer, viq, Yohan
|
||||
Belval
|
||||
We had 19 unique committers since 0.0.21, and have had 82 overall.
|
||||
run 'git log 0.0.21..0.0.22' to see what has changed since 0.0.21
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
102
docs/release-notes/0.0.23
Normal file
102
docs/release-notes/0.0.23
Normal file
@@ -0,0 +1,102 @@
|
||||
I've just released version 0.0.23 of mgmt!
|
||||
|
||||
> 424 files changed, 7051 insertions(+), 2256 deletions(-)
|
||||
|
||||
This is a fairly quiet release, and I'm mostly doing this to have a
|
||||
permanent tag before I start really breaking git master.
|
||||
|
||||
I'd like to apologize for things being kind of quiet. I've had to focus
|
||||
on life and making money to pay my bills, and I've been struggling a
|
||||
bit to complete some of the tougher algorithmic parts that I think are
|
||||
necessary for a solid MVP. Hopefully I will succeed, but to do so, it's
|
||||
going to be easier if I break git master and then sort things out
|
||||
later.
|
||||
|
||||
I'm feeling optimistic about the future, although help with some
|
||||
complex concurrent programming would certainly be appreciated.
|
||||
|
||||
With that, here are a few highlights from the release:
|
||||
|
||||
* We're using go.mod, but I'm not keeping it up-to-date regularly yet.
|
||||
|
||||
* We have an os.system(`cmd`) function!
|
||||
|
||||
* We replaced go-bindata with the embed package.
|
||||
|
||||
* We added a hetzner vm resource.
|
||||
|
||||
* We did a giant lang/ package refactor.
|
||||
|
||||
* Printf supports %v now (any type).
|
||||
|
||||
And much more...
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are NOT available here for this release:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.23
|
||||
|
||||
NEWS
|
||||
|
||||
* We have our Libera IRC channel bridged to Matrix, so you can join
|
||||
that way too.
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, Hetzner,
|
||||
etc, resources if anyone is interested, reach out to us. Particularly
|
||||
if there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
BUGS
|
||||
|
||||
* Function values as args don't work yet. This is blocking us from
|
||||
implementing functions like map/reduce/filter, because they'd want to
|
||||
receive a function as input.
|
||||
|
||||
(This is the exact message I wrote last time. I've made a lot of
|
||||
progress since then, but motivation here has been low. Reach out if you
|
||||
can help.)
|
||||
|
||||
TALKS
|
||||
|
||||
I don't have anything planned until CfgMgmtCamp 2024. If you'd like to
|
||||
book me for a private event, or sponsor my travel for your conference,
|
||||
please let me know.
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Libera IRC or Matrix, or ping this
|
||||
list if you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many tagged #mgmtlove issues exist:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
Although asking in IRC is the best way to find something to work on.
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
dantefromhell, James Shubin, Jef Masereel, Joe Groocock, Samuel
|
||||
Gélineau
|
||||
We had 5 unique committers since 0.0.22, and have had 85 overall.
|
||||
run 'git log 0.0.22..0.0.23' to see what has changed since 0.0.22
|
||||
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
161
docs/release-notes/0.0.24
Normal file
161
docs/release-notes/0.0.24
Normal file
@@ -0,0 +1,161 @@
|
||||
I've just released version 0.0.24 of mgmt!
|
||||
|
||||
|
||||
> 600 files changed, 13622 insertions(+), 6907 deletions(-)
|
||||
|
||||
This is a huge and hugely important release! It has been a long time
|
||||
coming. We have lambdas! I could not have done this without the
|
||||
unrelentingly supportive and brilliant Samuel Gélineau.
|
||||
|
||||
Getting past the blockers and tricky code changes was all thanks to
|
||||
him. Cleanups, polish and making it more golang idiomatic was my doing.
|
||||
(The easy stuff.) If Sam wanted to be a golang expert, he could have
|
||||
done it all, but it was more sensible than I do all the mundane and
|
||||
filler stuff.
|
||||
|
||||
With that, here are a few highlights from the release:
|
||||
|
||||
* We have working lambdas, including iter.map =D
|
||||
|
||||
* We have a new function engine!
|
||||
|
||||
* We have improved type unification!
|
||||
|
||||
* We have an improved resource engine!
|
||||
|
||||
* We have improved many of the internal API's.
|
||||
|
||||
* We have so many tests.
|
||||
|
||||
And much more...
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are NOT available here for this release:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.24
|
||||
|
||||
NEWS
|
||||
|
||||
* Our main tests are now in the excellent .txtar format. This makes
|
||||
things much easier to manage.
|
||||
|
||||
* The iter.map function can be named as such due to parser tricks! No
|
||||
need to name it xmap anymore!
|
||||
|
||||
* The unification solver has been greatly improved. We can infer a lot
|
||||
more about function types.
|
||||
|
||||
* The resources API uses the context package for closing Watch and
|
||||
returning early from CheckApply. The next step would be to remove all
|
||||
the resource-specific timeout code and make that a metaparam.
|
||||
|
||||
* A new sync primitive has been added in case you'd like to use it
|
||||
somewhere. We'd love help adding an even more complex one. Look in
|
||||
util/sync.go for more information.
|
||||
|
||||
* Sam added some beautiful type inference debugging that makes things
|
||||
easier for those familiar with the standard literature.
|
||||
|
||||
* An important bug in standalone etcd has been fixed. While embedded
|
||||
etcd and automatic clustering isn't "supported" (it's buggy) the
|
||||
status-quo of using your own etcd cluster is stable, and you can even
|
||||
use the embedded etcd server in standalone mode...
|
||||
|
||||
* This means you can run `mgmt etcd` and get the standard etcd binary
|
||||
behviour that you'd get from running `etcd` normally. This makes it
|
||||
easy to use both together since you only need to transport one binary
|
||||
around. (And maybe mgmt will do that for you!)
|
||||
|
||||
* I fixed and cleaned up some sketchy code in the resource engine. I
|
||||
had been unmotivated to fix this for a while because I really wanted
|
||||
lambdas first, but now that they are in, I took a good look at the
|
||||
code, made some fixes, and I'm really happy with it now.
|
||||
|
||||
* Metaparams are appropriately stateful between graph switches now.
|
||||
Retry is the easy example. Limit/Burst need to be ported if you care
|
||||
about these fine details.
|
||||
|
||||
* A RetryReset metaparam has been added. This is another good example
|
||||
of how powerful metaparams are and how much potential there is for
|
||||
building systems with these and future ones.
|
||||
|
||||
* A bunch of internal API's have been updated. This makes it better for
|
||||
function and resource writers! Some GAPI changes also got pushed
|
||||
through that make things clearer for those reading code.
|
||||
|
||||
* We have a listlookup function. It's still missing syntactic sugar
|
||||
though!
|
||||
|
||||
* Our new function graph engine is called "dage". I think it's pretty
|
||||
clever. There's a chance there is still a bug inside, but it's unclear.
|
||||
Please report any issues. If you have some large machines I can test
|
||||
very large and fast graphs on, please let me know.
|
||||
|
||||
* Lambdas really work now! The txn and ref/gc code is pretty fantastic.
|
||||
|
||||
* Many bugs have been killed!
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, Hetzner,
|
||||
etc, resources if anyone is interested, reach out to us. Particularly
|
||||
if there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
BUGS/TODO
|
||||
|
||||
* Function values getting _passed_ to resources doesn't work yet, but
|
||||
it's not a blocker, but it would definitely be useful. We're looking
|
||||
into it.
|
||||
|
||||
* Function graphs are unnecessarily dynamic. We might make them more
|
||||
static so that we don't need as many transactions. This is really a
|
||||
compiler optimization and not a bug, but it's something important we'd
|
||||
like to have.
|
||||
|
||||
* Running two Txn's during the same pause would be really helpful. I'm
|
||||
not sure how much of a performance improvement we'd get from this, but
|
||||
it would sure be interesting to build. If you want to build a fancy
|
||||
synchronization primitive, then let us know! Again this is not a bug.
|
||||
|
||||
TALKS
|
||||
|
||||
I don't have anything planned until CfgMgmtCamp 2024. If you'd like to
|
||||
book me for a private event, or sponsor my travel for your conference,
|
||||
please let me know.
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Libera IRC or Matrix, or ping this
|
||||
list if you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many tagged #mgmtlove issues exist:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
Although asking in IRC/matrix is the best way to find something to work
|
||||
on.
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
James Shubin, Kaushal, Laurent Indermuehle, Ofek Atar, Samuel Gélineau
|
||||
We had 5 unique committers since 0.0.23, and have had 88 overall.
|
||||
run 'git log 0.0.23..0.0.24' to see what has changed since 0.0.23
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
343
docs/release-notes/0.0.25
Normal file
343
docs/release-notes/0.0.25
Normal file
@@ -0,0 +1,343 @@
|
||||
I've just released version 0.0.25 of mgmt!
|
||||
|
||||
> 686 files changed, 28391 insertions(+), 6935 deletions(-)
|
||||
|
||||
This is the first release that I consider to be generally useful at
|
||||
solving real-world problems, without needing to be an mgmt expert. It's
|
||||
also the first release that includes a very real `mcl` codebase. An
|
||||
accompanying blog post is also available:
|
||||
https://purpleidea.com/blog/2024/03/27/a-new-provisioning-tool/
|
||||
|
||||
With that, here are a few highlights from the release:
|
||||
|
||||
* We have a new mgmt partner program. Please sign-up for early access
|
||||
to these release notes, along with other special privileges. Details
|
||||
at: https://bit.ly/mgmt-partner-program
|
||||
|
||||
* You can build self-contained mgmt binaries that contain a custom
|
||||
application. An initial "provisioning tool" has been built in this way.
|
||||
Please see the blog post for more details.
|
||||
|
||||
* Default lookup functions exist in the language, with syntactic sugar
|
||||
(the || operator) , so you can get a default value if one doesn't
|
||||
exist, eg: $some_struct->some_struct_field || "some_default_value".
|
||||
|
||||
* Resource fields can now accept interface{} (any) types.
|
||||
|
||||
* A panic feature now exists in the language.
|
||||
|
||||
* The exec resource has new `donecmd` and `creates` fields. Of note,
|
||||
`creates` supports watches too!
|
||||
|
||||
* Send/recv now works for autogrouped resources!
|
||||
|
||||
* Added `include as` (for classes) to the language. Nested
|
||||
(sugar/hierarchical) classes are now supported to make this more
|
||||
powerful!
|
||||
|
||||
* Stats are printed if the function engine is waiting for too long.
|
||||
|
||||
* There's a new http:flag resource, and also an http:proxy resource so
|
||||
that we can have caching http proxies!
|
||||
|
||||
* Added a firewalld resource for opening ports!
|
||||
|
||||
* Added a dhcp:range resource which is very powerful and has a fun API!
|
||||
|
||||
* Added the "embedded" and "entry" packages, for building standalone
|
||||
tools. This goes perfectly with the new CLI library that we ported
|
||||
everything to.
|
||||
|
||||
And much more...
|
||||
|
||||
|
||||
DOWNLOAD
|
||||
|
||||
Prebuilt binaries are available here for this release:
|
||||
https://github.com/purpleidea/mgmt/releases/tag/0.0.25
|
||||
|
||||
They can also be found on the Fedora mirror:
|
||||
https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/0.0.25/
|
||||
|
||||
|
||||
NEWS
|
||||
|
||||
* We changed the logical operators in mcl to use well-known English
|
||||
tokens: OR, AND, NOT. (but in lowercase of course)
|
||||
|
||||
* The history function has been temporarily removed from the syntactic
|
||||
core. We'll add it back if we find it's useful to have sugar!
|
||||
|
||||
* A bunch of lexer/parser cleanups and improvements were made.
|
||||
|
||||
* Default lookup functions for lists, maps, and structs have been
|
||||
added. These come with syntactic sugar as mentioned above. (We plan to
|
||||
keep this syntax, but we're open to feedback and changes if they're
|
||||
good.)
|
||||
|
||||
* Resources can accept the interface{} (any) type, although this should
|
||||
be used sparingly.
|
||||
|
||||
* We added a new mcl test suite that checks resource output too!
|
||||
|
||||
* Added a new `value` resource. This is a special resource kind that
|
||||
can be used for building some powerful state machines. Recommended for
|
||||
experienced users only.
|
||||
|
||||
* Improved the golang function generation to allow functions that take
|
||||
[]str, so now we have a bunch more functions (like join) in our stdlib
|
||||
for free.
|
||||
|
||||
* Add some mac address formatting functions. (core/net)
|
||||
|
||||
* Added a panic resource and panic function into the core language.
|
||||
This is useful for safely shutting down a running mcl program to
|
||||
prevent something disastrous or unhandled.
|
||||
|
||||
* Added a `donecmd` field to the exec resource. This runs a command
|
||||
after a successful CheckApply. This replaces the `&& echo foo > done`
|
||||
pattern that you'd see in some code.
|
||||
|
||||
* Added a new internal `local` API that can be used for local machine
|
||||
operations. So far, read/writing/watching values that are stored
|
||||
locally.
|
||||
|
||||
* Added `value` functions which bridge the `value` resource via the
|
||||
`local` API. To be used sparingly!
|
||||
|
||||
* Bumped to golang 1.20, and we'll probably move again before the next
|
||||
release.
|
||||
|
||||
* Allow send/recv with autogrouped resources. This adds many
|
||||
possibilities, in particular with the server style resources.
|
||||
|
||||
* Added a bunch of tests for sneaky corner cases. Some of these were
|
||||
hard to write, but I think they're worth it.
|
||||
|
||||
* ExprBind is now monomorphic! This was a design mistake that we
|
||||
introduced, but have since repaired. We now have far fewer copies
|
||||
running in the function graph, and things are much more efficient. This
|
||||
means lambdas can only have one type when used at two different call
|
||||
sites, which is much more logical, safer, faster and memory efficient.
|
||||
|
||||
* Added an --only-unify option if you want to test your code but not
|
||||
run it.
|
||||
|
||||
* Added a concat function for the common case of interpolation. This
|
||||
makes type unification significantly faster.
|
||||
|
||||
* Eliminated some "benign" races. You might find this commit
|
||||
interesting to read: bc63b7608e84f60bf9d568188814d411a0688738
|
||||
|
||||
* A pgraph bug was found and fixed. A test was added too! It's amazing
|
||||
this was here for so long, it just shows how subtle graph
|
||||
datastructures can be.
|
||||
|
||||
* Added `include as` (for classes) to the language which lets our
|
||||
classes produce values which can then be used elsewhere. I decided this
|
||||
feature would be necessary after writing a bunch of mcl. It does have
|
||||
an extraneous scoping bug, but not anything that causes problems.
|
||||
|
||||
* Nested classes are now supported. This lets you write the equivalent
|
||||
of nested classes, without actually having to nest them! This is not
|
||||
inheritance, but rather a way of handling scope and passing it
|
||||
downwards.
|
||||
|
||||
* Improved the Ordering compiler step to catch a bunch of unhandled
|
||||
bugs. Sam is a genius and was able to figure out some of these using
|
||||
wizardry.
|
||||
|
||||
* Added some convert functions to the mcl package.
|
||||
|
||||
* Allow edges with colons...
|
||||
|
||||
* ...Because we now support a new hierarchical autogrouping algorithm!
|
||||
This let's us have some very powerful resources.
|
||||
|
||||
* ...Like http:*, dhcp:*, and so on, but we could even go deeper!
|
||||
|
||||
* Fixed a super sneaky bug with resource swapping. Due to how we Cmp,
|
||||
this now preserves state more often, and in particular when we need it.
|
||||
I'm fairly certain that some code in a WIP branch of mine was actually
|
||||
blocked because of this issue. Pleased to run into it again, but now
|
||||
with a fix in place!
|
||||
|
||||
* Added an http:flag resource. This let's a `wget` or similar call back
|
||||
to the http:server to kick off an action.
|
||||
|
||||
* The http:flag resource supports directories now.
|
||||
|
||||
* Stats are printed if the function engine is waiting for too long.
|
||||
This is mostly useful for developers who are building new functions and
|
||||
have a bug in their midst!
|
||||
|
||||
* We added a --skip-unify option to prevent the double unification when
|
||||
running locally. When using `mgmt run` to test locally, we type check,
|
||||
and then deploy to ourselves, which then naturally type checks again.
|
||||
This skips the first one, which would be unsafe generally, but is
|
||||
perfectly safe when we're running a single instance.
|
||||
|
||||
* Added a new http:proxy resource, and then tweaked it's API, and then
|
||||
added http streaming. This is an incredibly powerful resource that lets
|
||||
us build a caching http proxy with a single resource. I can't wait to
|
||||
see what else it gets used for. I'm using it for provisioning. It's not
|
||||
performance optimized at the moment as it uses a single mutex for
|
||||
everything, but this could be extended if we wanted to scale this out.
|
||||
|
||||
* Added a ton of measuring/timing of common operations. This confirmed
|
||||
my belief that autoedges were slower than necessary. There are two ways
|
||||
to improve this. We might end up doing either one or both. Autogrouping
|
||||
is currently much faster than needed, so no improvements planned for
|
||||
now!
|
||||
|
||||
* Started to clean up the internal FS API's. It would be really great
|
||||
if the core golang team would add something so we could get rid of the
|
||||
afero external interfaces.
|
||||
|
||||
* Added an "embedded" package to offer API's related to embedded mcl
|
||||
programs! This lets us build standalone binaries which are powered by
|
||||
mcl.
|
||||
|
||||
* Moved to a new CLI (go-arg) library. This has a few downsides, but
|
||||
they are fixable upstream, and this vastly improved our code quality
|
||||
and API's. This needed to happen, what with the mess that was
|
||||
urfave/cli. Look at our diff's, they're really elegant! This let us
|
||||
clean up our lib structs as well!
|
||||
|
||||
* Added an "entry" package to kick-off the embedded API stuff. This
|
||||
uses the new CLI API's that we just built. The end-user now has a
|
||||
really easy time building new tools.
|
||||
|
||||
* Added a bunch of util functions to aid in building certain standalone
|
||||
tools. I'm willing to accept more contributions in this space if
|
||||
they're sane, and related to our general mission. Please ask and then
|
||||
send patches if you're unsure.
|
||||
|
||||
* Added a firewalld resource which makes opening up ports automatic
|
||||
when we need them. Perfect for the standalone laptop use-case.
|
||||
|
||||
* Made type unification cancellable in case you get into a long-running
|
||||
scenario and want to end early.
|
||||
|
||||
* Added a `creates` field to the exec resource. Very useful, and also
|
||||
supports watches! This is very useful for the common uses of exec.
|
||||
|
||||
* Added a dhcp:range resource to offer any number of IP addresses to
|
||||
devices that we don't know the mac addresses of in advance. This makes
|
||||
building a provisioning tool even more ergonomic.
|
||||
|
||||
* Optimized the name invariants since we can usually avoid an exclusive
|
||||
invariant in the common case. This roughly halved the type unification
|
||||
time. More improvements coming too!
|
||||
|
||||
* Caught a sneaky list type that could get through type unification
|
||||
when it was interpolated alone. This now enforces the string invariant
|
||||
when we specify it, which is an important language design distinction.
|
||||
We added tests for this of course too!
|
||||
|
||||
* The "log" package has been entirely refactored and is only visible in
|
||||
one place at the top of the program. Nice! I have a design for a
|
||||
"better logger / user interface" if we ever want to improve on this.
|
||||
|
||||
* Added release targets for standalone binary builds. I also improved
|
||||
the Makefile release magic significantly.
|
||||
|
||||
* Made a lot of small "polish" improvements to various resources.
|
||||
|
||||
* Most interestingly, an embedded provisioner application has been
|
||||
built and made available in full. Please test and share with others.
|
||||
Hopefully this will encourage more interest in the project.
|
||||
|
||||
* We're looking for help writing Amazon, Google, DigitalOcean, Hetzner,
|
||||
etc, resources if anyone is interested, reach out to us. Particularly
|
||||
if there is support from those organizations as well.
|
||||
|
||||
* Many other bug fixes, changes, etc...
|
||||
|
||||
* See the git log for more NEWS, and for anything notable I left out!
|
||||
|
||||
|
||||
BUGS/TODO
|
||||
|
||||
* Function values getting _passed_ to resources doesn't work yet, but
|
||||
it's not a blocker, but it would definitely be useful. We're looking
|
||||
into it.
|
||||
|
||||
* Function graphs are unnecessarily dynamic. We might make them more
|
||||
static so that we don't need as many transactions. This is really a
|
||||
compiler optimization and not a bug, but it's something important we'd
|
||||
like to have.
|
||||
|
||||
* Running two Txn's during the same pause would be really helpful. I'm
|
||||
not sure how much of a performance improvement we'd get from this, but
|
||||
it would sure be interesting to build. If you want to build a fancy
|
||||
synchronization primitive, then let us know! Again this is not a bug.
|
||||
|
||||
* Type unification performance can be improved drastically. I will have
|
||||
to implement the fast algorithm so that we can scale to very large mcl
|
||||
programs. Help is wanted if you are familiar with "unionfind" and/or
|
||||
type unification.
|
||||
|
||||
|
||||
TALKS
|
||||
|
||||
I don't have anything planned until CfgMgmtCamp 2025. If you'd like to
|
||||
book me for a private event, or sponsor my travel for your conference,
|
||||
please let me know.
|
||||
|
||||
I recently gave two talks: one at CfgMgmtCamp 2024, and one at FOSDEM
|
||||
in the golang room. Both are available online and demonstrated an
|
||||
earlier version of the provisioning tool which is fully available
|
||||
today. The talks can be found here: https://purpleidea.com/talks/
|
||||
|
||||
|
||||
PARTNER PROGRAM
|
||||
|
||||
We have a new mgmt partner program which gets you early access to
|
||||
releases, bug fixes, support, and many other goodies. Please sign-up
|
||||
today: https://bit.ly/mgmt-partner-program
|
||||
|
||||
|
||||
MISC
|
||||
|
||||
Our mailing list host (Red Hat) is no longer letting non-Red Hat
|
||||
employees use their infrastructure. We're looking for a new home. I've
|
||||
opened a ticket with Freedesktop. If you have any sway with them or
|
||||
other recommendations, please let me know:
|
||||
https://gitlab.freedesktop.org/freedesktop/freedesktop/-/issues/1082
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium
|
||||
and hard issues available! You're also welcome to suggest your own!
|
||||
Please join us in #mgmtconfig on Libera IRC or Matrix (preferred) and
|
||||
ping us if you'd like help getting started! For details please see:
|
||||
|
||||
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
|
||||
tribute-to-the-project-if-i-dont-know-golang
|
||||
|
||||
Many tagged #mgmtlove issues exist:
|
||||
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
|
||||
3Amgmtlove
|
||||
|
||||
Although asking in IRC/matrix is the best way to find something to work
|
||||
on.
|
||||
|
||||
|
||||
MENTORING
|
||||
|
||||
We offer mentoring for new golang/mgmt hackers who want to get
|
||||
involved. This is fun and friendly! You get to improve your skills,
|
||||
and we get some patches in return. Ping me off-list for details.
|
||||
|
||||
|
||||
THANKS
|
||||
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release:
|
||||
Eng Zer Jun, James Shubin, Oliver Lowe, Samuel Gélineau
|
||||
We had 4 unique committers since 0.0.24, and have had 90 overall.
|
||||
run 'git log 0.0.24..0.0.25' to see what has changed since 0.0.24
|
||||
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
71
docs/release-notes/0.0.9
Normal file
71
docs/release-notes/0.0.9
Normal file
@@ -0,0 +1,71 @@
|
||||
I've just released 0.0.9!
|
||||
|
||||
Release tags are now signed with my GPG key.
|
||||
|
||||
From now on, I'm going to try and write a release email to the list
|
||||
with information about each release. Here we go!
|
||||
|
||||
NEWS
|
||||
|
||||
* There were far more commits that went into this release than I was
|
||||
expecting. I delayed a while because of some nasty races and deadlocks
|
||||
I encountered when cleaning some cruft out of the engine.
|
||||
|
||||
134 files changed, 5394 insertions(+), 2168 deletions(-)
|
||||
|
||||
* We grew initial integration with Prometheus thanks to new contributor
|
||||
`roidelapluie`.
|
||||
|
||||
* The file resource can now chown/chmod files! Thanks to new
|
||||
contributor `mildred`.
|
||||
|
||||
* The virt resource can now hotplug/hotunplug cpus. This made for some
|
||||
dope demos at CfgMgmtCamp. Special thanks to pkrempa for helping me
|
||||
with the libvirt API.
|
||||
|
||||
* Native testing of all the golang code is now enabled again. I broke
|
||||
this when I split mgmt up into multiple packages. (Sorry!) Golang tests
|
||||
in foo_test.go should now run and be tested automatically. I'd
|
||||
especially like it if someone wrote some for pgraph/pgraph.go:
|
||||
GraphSync().
|
||||
|
||||
* Documentation has been significantly improved. We have a resource
|
||||
guide (for writing new native resources) and much more. We also now
|
||||
build the docs as a RTD project:
|
||||
https://mgmt.readthedocs.io/en/master/
|
||||
Patches welcome to make it style as pretty as GitHub markdown does.
|
||||
|
||||
* See the git log for more NEWS, and sorry for anything notable I left
|
||||
out!
|
||||
|
||||
BUGS
|
||||
|
||||
* I hope to resolve this soon, but it shouldn't block further
|
||||
development on git master for now. The issue: if you make extremely
|
||||
high speed graph changes to graphs involving the virt resource, it can
|
||||
eventually cause a panic. This is being tracked here:
|
||||
https://github.com/libvirt/libvirt-go/issues/7 (help welcome!)
|
||||
|
||||
GLUSTERFS
|
||||
|
||||
I've started working on some stuff for Glusterd so that it can embed
|
||||
mgmt as a lib, and use the resource model to simplify cluster
|
||||
management. This will involve writing Gluster resources in mgmt
|
||||
(volume, brick, etc...) and all sorts of other fun stuff. If you'd like
|
||||
to participate, please LMK!
|
||||
|
||||
MISC
|
||||
|
||||
We're still looking for new contributors, and there are easy, medium and hard issues available! You're also welcome to suggest your own! Please join us in #mgmtconfig on Freenode IRC, or ping this list if you'd like help getting started!
|
||||
|
||||
THANKS
|
||||
|
||||
We had 12 unique committers since 0.0.8, and have had 29 overall.
|
||||
Thanks (alphabetically) to everyone who contributed to the latest
|
||||
release: Daniele Sluijters, Daniel P. Berrange, Felix Frank, goberghen,
|
||||
James Shubin, Julien Pivotto, Kaushal M, Lars Kulseng, Mildred Ki'Lya,
|
||||
Sean Jones, Steve Milner, Tom Ritserveldt.
|
||||
|
||||
Happy hacking,
|
||||
James
|
||||
@purpleidea
|
||||
@@ -60,10 +60,7 @@ it is not specified, but others cannot, and some might poorly infer if the
|
||||
struct name is ambiguous.
|
||||
|
||||
If you'd like your resource to be accessible by the `YAML` graph API (GAPI),
|
||||
then you'll need to include the appropriate YAML fields as shown below. This is
|
||||
used by the `Puppet` compiler as well, so make sure you include these struct
|
||||
tags if you want existing `Puppet` code to be able to run using the `mgmt`
|
||||
engine.
|
||||
then you'll need to include the appropriate YAML fields as shown below.
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -105,7 +102,7 @@ when parameters take a zero value, whenever this is possible.)
|
||||
|
||||
```golang
|
||||
// Default returns some sensible defaults for this resource.
|
||||
func (obj *FooRes) Default() Res {
|
||||
func (obj *FooRes) Default() engine.Res {
|
||||
return &FooRes{
|
||||
Answer: 42, // sometimes, defaults shouldn't be the zero value
|
||||
}
|
||||
@@ -177,10 +174,10 @@ this. In other words, you should expect `Validate` to have run first, but you
|
||||
shouldn't allow `Init` to dangerously `rm -rf /$the_world` if your code only
|
||||
checks `$the_world` in `Validate`. Remember to always program safely!
|
||||
|
||||
### Close
|
||||
### Cleanup
|
||||
|
||||
```golang
|
||||
Close() error
|
||||
Cleanup() error
|
||||
```
|
||||
|
||||
This is called to cleanup after the resource. It is usually not necessary, but
|
||||
@@ -192,8 +189,8 @@ loop.
|
||||
#### Example
|
||||
|
||||
```golang
|
||||
// Close runs some cleanup code for this resource.
|
||||
func (obj *FooRes) Close() error {
|
||||
// Cleanup is run by the engine to clean up after the resource is done.
|
||||
func (obj *FooRes) Cleanup() error {
|
||||
err := obj.conn.Close() // close some internal connection
|
||||
obj.someMap = nil // free up some large data structure from memory
|
||||
return err
|
||||
@@ -206,7 +203,7 @@ on an error if something went wrong.
|
||||
### CheckApply
|
||||
|
||||
```golang
|
||||
CheckApply(apply bool) (checkOK bool, err error)
|
||||
CheckApply(ctx context.Context, apply bool) (checkOK bool, err error)
|
||||
```
|
||||
|
||||
`CheckApply` is where the real _work_ is done. Under normal circumstances, this
|
||||
@@ -215,7 +212,8 @@ should return: `(true, nil)`. If the `apply` variable is set to `true`, then
|
||||
this means that we should then proceed to run the changes required to bring the
|
||||
resource into the correct state. If the `apply` variable is set to `false`, then
|
||||
the resource is operating in _noop_ mode and _no operational changes_ should be
|
||||
made!
|
||||
made! The ctx should be monitored in case a shutdown has been requested. This
|
||||
may be used if a timeout occurred, or if the user shutdown the engine.
|
||||
|
||||
After having executed the necessary operations to bring the resource back into
|
||||
the desired state, or after having detected that the state was incorrect, but
|
||||
@@ -234,7 +232,7 @@ to `CheckApply`.
|
||||
|
||||
```golang
|
||||
// CheckApply does the idempotent work of checking and applying resource state.
|
||||
func (obj *FooRes) CheckApply(apply bool) (bool, error) {
|
||||
func (obj *FooRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
// check the state
|
||||
if state_is_okay { return true, nil } // done early! :)
|
||||
|
||||
@@ -277,7 +275,7 @@ will likely find the state to now be correct.
|
||||
### Watch
|
||||
|
||||
```golang
|
||||
Watch() error
|
||||
Watch(ctx context.Context) error
|
||||
```
|
||||
|
||||
`Watch` is a main loop that runs and sends messages when it detects that the
|
||||
@@ -304,23 +302,25 @@ If the resource is activated in `polling` mode, the `Watch` method will not get
|
||||
executed. As a result, the resource must still work even if the main loop is not
|
||||
running.
|
||||
|
||||
You must make sure to cleanup any running code or goroutines before Watch exits.
|
||||
|
||||
#### Select
|
||||
|
||||
The lifetime of most resources `Watch` method should be spent in an infinite
|
||||
loop that is bounded by a `select` call. The `select` call is the point where
|
||||
our method hands back control to the engine (and the kernel) so that we can
|
||||
sleep until something of interest wakes us up. In this loop we must wait until
|
||||
we get a shutdown event from the engine via the `<-obj.init.Done` channel, which
|
||||
we get a shutdown event from the engine via the `<-ctx.Done()` channel, which
|
||||
closes when we'd like to shut everything down. At this point you should cleanup,
|
||||
and let `Watch` close.
|
||||
|
||||
#### Events
|
||||
|
||||
If the `<-obj.init.Done` channel closes, we should shutdown our resource. When
|
||||
When we want to send an event, we use the `Event` helper function. This
|
||||
automatically marks the resource state as `dirty`. If you're unsure, it's not
|
||||
harmful to send the event. This will ultimately cause `CheckApply` to run. This
|
||||
method can block if the resource is being paused.
|
||||
If the `<-ctx.Done()` channel closes, we should shutdown our resource. When we
|
||||
want to send an event, we use the `Event` helper function. This automatically
|
||||
marks the resource state as `dirty`. If you're unsure, it's not harmful to send
|
||||
the event. This will ultimately cause `CheckApply` to run. This method can block
|
||||
if the resource is being paused.
|
||||
|
||||
#### Startup
|
||||
|
||||
@@ -347,7 +347,7 @@ sending out erroneous `Event` messages to keep things alive until it finishes.
|
||||
|
||||
```golang
|
||||
// Watch is the listener and main loop for this resource.
|
||||
func (obj *FooRes) Watch() error {
|
||||
func (obj *FooRes) Watch(ctx context.Context) error {
|
||||
// setup the Foo resource
|
||||
var err error
|
||||
if err, obj.foo = OpenFoo(); err != nil {
|
||||
@@ -371,7 +371,7 @@ func (obj *FooRes) Watch() error {
|
||||
case err := <-obj.foo.Errors:
|
||||
return err // will cause a retry or permanent failure
|
||||
|
||||
case <-obj.init.Done: // signal for shutdown request
|
||||
case <-ctx.Done(): // signal for shutdown request
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -553,11 +553,6 @@ ready to detect changes.
|
||||
Event sends an event notifying the engine of a possible state change. It is
|
||||
only called from within `Watch`.
|
||||
|
||||
### Done
|
||||
|
||||
Done is a channel that closes when the engine wants us to shutdown. It is only
|
||||
called from within `Watch`.
|
||||
|
||||
### Refresh
|
||||
|
||||
Refresh returns whether the resource received a notification. This flag can be
|
||||
@@ -582,15 +577,15 @@ It is only called from within `CheckApply`.
|
||||
World provides a connection to the outside world. This is most often used for
|
||||
communicating with the distributed database. It can be used in `Init`,
|
||||
`CheckApply` and `Watch`. Use with discretion and understanding of the internals
|
||||
if needed in `Close`.
|
||||
if needed in `Cleanup`.
|
||||
|
||||
### VarDir
|
||||
|
||||
VarDir is a facility for local storage. It is used to return a path to a
|
||||
directory which may be used for temporary storage. It should be cleaned up on
|
||||
resource `Close` if the resource would like to delete the contents. The resource
|
||||
should not assume that the initial directory is empty, and it should be cleaned
|
||||
on `Init` if that is a requirement.
|
||||
resource `Cleanup` if the resource would like to delete the contents. The
|
||||
resource should not assume that the initial directory is empty, and it should be
|
||||
cleaned on `Init` if that is a requirement.
|
||||
|
||||
### Debug
|
||||
|
||||
@@ -625,7 +620,7 @@ func init() { // special golang method that runs once
|
||||
|
||||
To support YAML unmarshalling for your resource, you must implement an
|
||||
additional method. It is recommended if you want to use your resource with the
|
||||
`Puppet` compiler.
|
||||
`yaml` compiler.
|
||||
|
||||
```golang
|
||||
UnmarshalYAML(unmarshal func(interface{}) error) error // optional
|
||||
@@ -642,8 +637,8 @@ The signature intentionally matches what is required to satisfy the `go-yaml`
|
||||
#### Example
|
||||
|
||||
```golang
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct.
|
||||
// It is primarily useful for setting the defaults.
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
|
||||
// primarily useful for setting the defaults.
|
||||
func (obj *FooRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type rawRes FooRes // indirection to avoid infinite recursion
|
||||
|
||||
@@ -680,10 +675,10 @@ receiving one. This can _only_ be done inside of the `CheckApply` function!
|
||||
|
||||
```golang
|
||||
// inside CheckApply, probably near the top
|
||||
if val, exists := obj.init.Recv()["SomeKey"]; exists {
|
||||
obj.init.Logf("the SomeKey param was sent to us from: %s.%s", val.Res, val.Key)
|
||||
if val, exists := obj.init.Recv()["some_key"]; exists {
|
||||
obj.init.Logf("the some_key param was sent to us from: %s.%s", val.Res, val.Key)
|
||||
if val.Changed {
|
||||
obj.init.Logf("the SomeKey param was just updated!")
|
||||
obj.init.Logf("the some_key param was just updated!")
|
||||
// you may want to invalidate some local cache
|
||||
}
|
||||
}
|
||||
@@ -727,16 +722,16 @@ two calls, this is much more difficult. A common example is that a resource
|
||||
might want to open a connection to `dbus` or `http` to do resource state testing
|
||||
and applying. If the methods are combined, there's no need to open and close
|
||||
them twice. A counter argument might be that you could open the connection in
|
||||
`Init`, and close it in `Close`, however you might not want that open for the
|
||||
`Init`, and close it in `Cleanup`, however you might not want that open for the
|
||||
full lifetime of the resource if you only change state occasionally.
|
||||
2. Suppose you came up with a really good reason why you wanted the two methods
|
||||
to be separate. It turns out that the current `CheckApply` can wrap this easily.
|
||||
It would look approximately like this:
|
||||
|
||||
```golang
|
||||
func (obj *FooRes) CheckApply(apply bool) (bool, error) {
|
||||
func (obj *FooRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
// my private split implementation of check and apply
|
||||
if c, err := obj.check(); err != nil {
|
||||
if c, err := obj.check(ctx); err != nil {
|
||||
return false, err // we errored
|
||||
} else if c {
|
||||
return true, nil // state was good!
|
||||
@@ -746,7 +741,7 @@ func (obj *FooRes) CheckApply(apply bool) (bool, error) {
|
||||
return false, nil // state needs fixing, but apply is false
|
||||
}
|
||||
|
||||
err := obj.apply() // errors if failure or unable to apply
|
||||
err := obj.apply(ctx) // errors if failure or unable to apply
|
||||
|
||||
return false, err // always return false, with an optional error
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ You might want to look at the [generated documentation](https://godoc.org/github
|
||||
for more up-to-date information about these resources.
|
||||
|
||||
* [Augeas](#Augeas): Manipulate files using augeas.
|
||||
* [Consul:KV](#ConsulKV): Set keys in a Consul datastore.
|
||||
* [Docker](#Docker):[Container](#Container) Manage docker containers.
|
||||
* [Exec](#Exec): Execute shell commands on the system.
|
||||
* [File](#File): Manage files and directories.
|
||||
@@ -32,6 +33,8 @@ for more up-to-date information about these resources.
|
||||
* [Print](#Print): Print messages to the console.
|
||||
* [Svc](#Svc): Manage system systemd services.
|
||||
* [Test](#Test): A mostly harmless resource that is used for internal testing.
|
||||
* [Tftp:File](#TftpFile): Add files to the small embedded embedded tftp server.
|
||||
* [Tftp:Server](#TftpServer): Run a small embedded tftp server.
|
||||
* [Timer](#Timer): Manage system systemd services.
|
||||
* [User](#User): Manage system users.
|
||||
* [Virt](#Virt): Manage virtual machines with libvirt.
|
||||
@@ -69,9 +72,9 @@ identified by a trailing slash in their path name. File have no such slash.
|
||||
It has the following properties:
|
||||
|
||||
* `path`: absolute file path (directories have a trailing slash here)
|
||||
* `state`: either `exists`, `absent`, or undefined
|
||||
* `content`: raw file content
|
||||
* `state`: either `exists` (the default value) or `absent`
|
||||
* `mode`: octal unix file permissions
|
||||
* `mode`: octal unix file permissions or symbolic string
|
||||
* `owner`: username or uid for the file owner
|
||||
* `group`: group name or gid for the file group
|
||||
|
||||
@@ -79,6 +82,16 @@ It has the following properties:
|
||||
|
||||
The path property specifies the file or directory that we are managing.
|
||||
|
||||
### State
|
||||
|
||||
The state property describes the action we'd like to apply for the resource. The
|
||||
possible values are: `exists` and `absent`. If you do not specify either of
|
||||
these, it is undefined. Without specifying this value as `exists`, another param
|
||||
cannot cause a file to get implicitly created. When specifying this value as
|
||||
`absent`, you should not specify any other params that would normally change the
|
||||
file. For example, if you specify `content` and this param is `absent`, then you
|
||||
will get an engine validation error.
|
||||
|
||||
### Content
|
||||
|
||||
The content property is a string that specifies the desired file contents.
|
||||
@@ -88,10 +101,12 @@ The content property is a string that specifies the desired file contents.
|
||||
The source property points to a source file or directory path that we wish to
|
||||
copy over and use as the desired contents for our resource.
|
||||
|
||||
### State
|
||||
### Fragments
|
||||
|
||||
The state property describes the action we'd like to apply for the resource. The
|
||||
possible values are: `exists` and `absent`.
|
||||
The fragments property lets you specify a list of files to concatenate together
|
||||
to make up the contents of this file. They will be combined in the order that
|
||||
they are listed in. If one of the files specified is a directory, then the
|
||||
files in that top-level directory will be themselves combined together and used.
|
||||
|
||||
### Recurse
|
||||
|
||||
@@ -104,6 +119,12 @@ The force property is required if we want the file resource to be able to change
|
||||
a file into a directory or vice-versa. If such a change is needed, but the force
|
||||
property is not set to `true`, then this file resource will error.
|
||||
|
||||
### Purge
|
||||
|
||||
The purge property is used when this file represents a directory, and we'd like
|
||||
to remove any unmanaged files from within it. Please note that any unmanaged
|
||||
files in a directory with this flag set will be irreversibly deleted.
|
||||
|
||||
## Group
|
||||
|
||||
The group resource manages the system groups from `/etc/group`.
|
||||
@@ -206,6 +227,16 @@ The service resource is still very WIP. Please help us by improving it!
|
||||
|
||||
The test resource is mostly harmless and is used for internal tests.
|
||||
|
||||
## Tftp:File
|
||||
|
||||
This adds files to the running tftp server. It's useful because it allows you to
|
||||
add individual files without needing to create them on disk.
|
||||
|
||||
## Tftp:Server
|
||||
|
||||
Run a small embedded tftp server. This doesn't apply any state, but instead runs
|
||||
a pure golang tftp server in the Watch loop.
|
||||
|
||||
## Timer
|
||||
|
||||
This resource needs better documentation. Please help us by improving it!
|
||||
|
||||
@@ -61,6 +61,12 @@ Occasionally inline, two line source code comments are used within a function.
|
||||
These should usually be balanced so that you don't have one line with 78
|
||||
characters and the second with only four. Split the comment between the two.
|
||||
|
||||
### Default values
|
||||
|
||||
Whenever a constant or function parameter is defined, try and have the safer or
|
||||
default value be the `zero` value. For example, instead of `const NoDanger`, use
|
||||
`const AllowDanger` so that the `false` value is the safe scenario.
|
||||
|
||||
### Method receiver naming
|
||||
|
||||
[Contrary](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)
|
||||
@@ -84,6 +90,75 @@ func (obj *Foo) Bar(baz string) int {
|
||||
}
|
||||
```
|
||||
|
||||
### Variable naming
|
||||
|
||||
We prefer shorter, scoped variables rather than `unnecessarilyLongIdentifiers`.
|
||||
Remember the scoping rules and feel free to use new variables where appropriate.
|
||||
For example, in a short string snippet you can use `s` instead of `myString`, as
|
||||
well as other common choices. `i` is a common `int` counter, `f` for files, `fn`
|
||||
for functions, `x` for something else and so on.
|
||||
|
||||
### Variable re-use
|
||||
|
||||
Feel free to create and use new variables instead of attempting to re-use the
|
||||
same string. For example, if a function input arg is named `s`, you can use a
|
||||
new variable to receive the first computation result on `s` instead of storing
|
||||
it back into the original `s`. This avoids confusion if a different part of the
|
||||
code wants to read the original input, and it avoids any chance of edit by
|
||||
reference of the original callers copy of the variable.
|
||||
|
||||
#### Example
|
||||
|
||||
```golang
|
||||
MyNotIdealFunc(s string, b bool) string {
|
||||
if !b {
|
||||
return s + "hey"
|
||||
}
|
||||
s = strings.Replace(s, "blah", "", -1) // not ideal (re-use of `s` var)
|
||||
return s
|
||||
}
|
||||
|
||||
MyOkayFunc(s string, b bool) string {
|
||||
if !b {
|
||||
return s + "hey"
|
||||
}
|
||||
s2 := strings.Replace(s, "blah", "", -1) // doesn't re-use `s` variable
|
||||
return s2
|
||||
}
|
||||
|
||||
MyGreatFunc(s string, b bool) string {
|
||||
if !b {
|
||||
return s + "hey"
|
||||
}
|
||||
return strings.Replace(s, "blah", "", -1) // even cleaner
|
||||
}
|
||||
```
|
||||
|
||||
### Constants in code
|
||||
|
||||
If a function takes a specifier (often a bool) it's sometimes better to name
|
||||
that variable (often with a `const`) rather than leaving a naked `bool` in the
|
||||
code. For example, `x := MyFoo("blah", false)` is less clear than
|
||||
`const useMagic = false; x := MyFoo("blah", useMagic)`.
|
||||
|
||||
### Empty slice declarations
|
||||
|
||||
When declaring a new empty slice, there are three different mechanisms:
|
||||
|
||||
1. `a := []string{}`
|
||||
|
||||
2. `var a []string`
|
||||
|
||||
3. `a := make([]string, 0)`
|
||||
|
||||
In general, we prefer the first method because we find that it is succinct, and
|
||||
very readable. The third method is the least recommended because you're adding
|
||||
extra data that a smart compiler could probably figure out on its own. There are
|
||||
performance implications between these three methods, so unless your code is in
|
||||
a fast path or memory constrained environment where this matters (and that you
|
||||
ideally have proof of this) please use the methods as ordered as much as
|
||||
possible.
|
||||
|
||||
### Consistent ordering
|
||||
|
||||
In general we try to preserve a logical ordering in source files which usually
|
||||
@@ -96,6 +171,23 @@ declared in the interface.
|
||||
When implementing code for the various types in the language, please follow this
|
||||
order: `bool`, `str`, `int`, `float`, `list`, `map`, `struct`, `func`.
|
||||
|
||||
For other aspects where you have a set of items, try to be internally consistent
|
||||
as well. For example, if you have two switch statements with `A`, `B`, and `C`,
|
||||
please use the same ordering for these elements elsewhere that they appear in
|
||||
the code and in the commentary if it is not illogical to do so.
|
||||
|
||||
### Product identifiers
|
||||
|
||||
Try to avoid references in the code to `mgmt` or a specific program name string
|
||||
if possible. This makes it easier to rename code if we ever pick a better name
|
||||
or support `libmgmt` better if we embed it. You can use the `Program` variable
|
||||
which is available in numerous places if you want a string to put in the logs.
|
||||
|
||||
It is also recommended to avoid the `go` (programming language name) string if
|
||||
possible. Try to use `golang` if required, since the word `go` is already
|
||||
overloaded, and in particular it was even already used by the
|
||||
[`go!`](https://en.wikipedia.org/wiki/Go!_(programming_language)).
|
||||
|
||||
## Overview for mcl code
|
||||
|
||||
The `mcl` language is quite new, so this guide will probably change over time as
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
@@ -68,7 +80,8 @@ type AutoEdge interface {
|
||||
Test([]bool) bool // call until false
|
||||
}
|
||||
|
||||
// ResUID is a unique identifier for a resource, namely it's name, and the kind ("type").
|
||||
// ResUID is a unique identifier for a resource, namely it's name, and the kind
|
||||
// ("type").
|
||||
type ResUID interface {
|
||||
fmt.Stringer // String() string
|
||||
|
||||
@@ -104,9 +117,9 @@ func (obj *BaseUID) String() string {
|
||||
}
|
||||
|
||||
// IFF looks at two UID's and if and only if they are equivalent, returns true.
|
||||
// If they are not equivalent, it returns false.
|
||||
// Most resources will want to override this method, since it does the important
|
||||
// work of actually discerning if two resources are identical in function.
|
||||
// If they are not equivalent, it returns false. Most resources will want to
|
||||
// override this method, since it does the important work of actually discerning
|
||||
// if two resources are identical in function.
|
||||
func (obj *BaseUID) IFF(uid ResUID) bool {
|
||||
res, ok := uid.(*BaseUID)
|
||||
if !ok {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,9 +13,21 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// +build !root
|
||||
//go:build !root
|
||||
|
||||
package engine
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
@@ -39,10 +51,11 @@ type GroupableRes interface {
|
||||
SetAutoGroupMeta(*AutoGroupMeta)
|
||||
|
||||
// GroupCmp compares two resources and decides if they're suitable for
|
||||
//grouping. This usually needs to be unique to your resource.
|
||||
// grouping. This usually needs to be unique to your resource.
|
||||
GroupCmp(res GroupableRes) error
|
||||
|
||||
// GroupRes groups resource argument (res) into self.
|
||||
// GroupRes groups resource argument (res) into self. Callers of this
|
||||
// method should probably also run SetParent.
|
||||
GroupRes(res GroupableRes) error
|
||||
|
||||
// IsGrouped determines if we are grouped.
|
||||
@@ -54,8 +67,15 @@ type GroupableRes interface {
|
||||
// GetGroup returns everyone grouped inside me.
|
||||
GetGroup() []GroupableRes // return everyone grouped inside me
|
||||
|
||||
// SetGroup sets the grouped resources into me.
|
||||
// SetGroup sets the grouped resources into me. Callers of this method
|
||||
// should probably also run SetParent.
|
||||
SetGroup([]GroupableRes)
|
||||
|
||||
// Parent returns the parent groupable resource that I am inside of.
|
||||
Parent() GroupableRes
|
||||
|
||||
// SetParent tells a particular grouped resource who their parent is.
|
||||
SetParent(res GroupableRes)
|
||||
}
|
||||
|
||||
// AutoGroupMeta provides some parameters specific to automatic grouping.
|
||||
|
||||
172
engine/cmp.go
172
engine/cmp.go
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
@@ -21,6 +33,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
// ResCmp compares two resources by checking multiple aspects. This is the main
|
||||
@@ -106,6 +119,7 @@ func ResCmp(r1, r2 Res) error {
|
||||
for k := range ix {
|
||||
// compare sub resources
|
||||
if err := ResCmp(ix[k], jx[k]); err != nil {
|
||||
//fmt.Printf("bad Cmp: %+v <> %+v for: %+v <> %+v err: %+v\n", r1, r2, ix[k], jx[k], err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -121,14 +135,29 @@ func ResCmp(r1, r2 Res) error {
|
||||
v1 := r1r.Recv()
|
||||
v2 := r2r.Recv()
|
||||
|
||||
// XXX: Our Send/Recv in the lib/main.go doesn't seem to be
|
||||
// pulling this in, so this always compares differently. We can
|
||||
// comment it out for now, since it's not too consequential.
|
||||
// XXX: Find out what the issue is and fix it for here and send.
|
||||
// XXX: The below errors are commented out until this is fixed.
|
||||
if (v1 == nil) != (v2 == nil) { // xor
|
||||
return fmt.Errorf("recv params differ")
|
||||
//return fmt.Errorf("recv params differ")
|
||||
}
|
||||
if v1 != nil && v2 != nil {
|
||||
// TODO: until we hit this code path, don't allow
|
||||
// comparing anything that has this set to non-zero
|
||||
if len(v1) != 0 || len(v2) != 0 {
|
||||
return fmt.Errorf("recv params exist")
|
||||
if len(v1) != len(v2) {
|
||||
//return fmt.Errorf("recv param lengths differ")
|
||||
}
|
||||
for key, send1 := range v1 { // map[string]*engine.Send
|
||||
send2, exists := v2[key]
|
||||
if !exists {
|
||||
//return fmt.Errorf("recv param key %s doesn't exist", key)
|
||||
}
|
||||
if (send1 == nil) != (send2 == nil) { // xor
|
||||
//return fmt.Errorf("recv param key %s send differs", key)
|
||||
}
|
||||
if send1 != nil && send2 != nil && send1.Key != send2.Key {
|
||||
//return fmt.Errorf("recv param key %s send key differs (%v != %v)", key, send1.Key, send2.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,13 +171,29 @@ func ResCmp(r1, r2 Res) error {
|
||||
s1 := r1s.Sent()
|
||||
s2 := r2s.Sent()
|
||||
|
||||
// XXX: Our Send/Recv in the lib/main.go doesn't seem to be
|
||||
// pulling this in, so this always compares differently. We can
|
||||
// comment it out for now, since it's not too consequential.
|
||||
// XXX: Find out what the issue is and fix it for here and recv.
|
||||
// XXX: The below errors are commented out until this is fixed.
|
||||
if (s1 == nil) != (s2 == nil) { // xor
|
||||
return fmt.Errorf("send params differ")
|
||||
//return fmt.Errorf("send params differ")
|
||||
}
|
||||
if s1 != nil && s2 != nil {
|
||||
// TODO: until we hit this code path, don't allow
|
||||
// adapting anything that has this set to non-nil
|
||||
return fmt.Errorf("send params exist")
|
||||
// TODO: reflect.DeepEqual?
|
||||
//return fmt.Errorf("send params exist")
|
||||
}
|
||||
}
|
||||
|
||||
// compare meta params for resources with reversible traits
|
||||
r1v, ok1 := r1.(ReversibleRes)
|
||||
r2v, ok2 := r2.(ReversibleRes)
|
||||
if ok1 != ok2 {
|
||||
return fmt.Errorf("reversible differs") // they must be different (optional)
|
||||
}
|
||||
if ok1 && ok2 {
|
||||
if r1v.ReversibleMeta().Cmp(r2v.ReversibleMeta()) != nil {
|
||||
return fmt.Errorf("reversible differs")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,14 +294,29 @@ func AdaptCmp(r1, r2 CompatibleRes) error {
|
||||
v1 := r1r.Recv()
|
||||
v2 := r2r.Recv()
|
||||
|
||||
// XXX: Our Send/Recv in the lib/main.go doesn't seem to be
|
||||
// pulling this in, so this always compares differently. We can
|
||||
// comment it out for now, since it's not too consequential.
|
||||
// XXX: Find out what the issue is and fix it for here and send.
|
||||
// XXX: The below errors are commented out until this is fixed.
|
||||
if (v1 == nil) != (v2 == nil) { // xor
|
||||
return fmt.Errorf("recv params differ")
|
||||
//return fmt.Errorf("recv params differ")
|
||||
}
|
||||
if v1 != nil && v2 != nil {
|
||||
// TODO: until we hit this code path, don't allow
|
||||
// adapting anything that has this set to non-zero
|
||||
if len(v1) != 0 || len(v2) != 0 {
|
||||
return fmt.Errorf("recv params exist")
|
||||
if len(v1) != len(v2) {
|
||||
//return fmt.Errorf("recv param lengths differ")
|
||||
}
|
||||
for key, send1 := range v1 { // map[string]*engine.Send
|
||||
send2, exists := v2[key]
|
||||
if !exists {
|
||||
//return fmt.Errorf("recv param key %s doesn't exist", key)
|
||||
}
|
||||
if (send1 == nil) != (send2 == nil) { // xor
|
||||
//return fmt.Errorf("recv param key %s send differs", key)
|
||||
}
|
||||
if send1 != nil && send2 != nil && send1.Key != send2.Key {
|
||||
//return fmt.Errorf("recv param key %s send key differs (%v != %v)", key, send1.Key, send2.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,13 +330,29 @@ func AdaptCmp(r1, r2 CompatibleRes) error {
|
||||
s1 := r1s.Sent()
|
||||
s2 := r2s.Sent()
|
||||
|
||||
// XXX: Our Send/Recv in the lib/main.go doesn't seem to be
|
||||
// pulling this in, so this always compares differently. We can
|
||||
// comment it out for now, since it's not too consequential.
|
||||
// XXX: Find out what the issue is and fix it for here and recv.
|
||||
// XXX: The below errors are commented out until this is fixed.
|
||||
if (s1 == nil) != (s2 == nil) { // xor
|
||||
return fmt.Errorf("send params differ")
|
||||
//return fmt.Errorf("send params differ")
|
||||
}
|
||||
if s1 != nil && s2 != nil {
|
||||
// TODO: until we hit this code path, don't allow
|
||||
// adapting anything that has this set to non-nil
|
||||
return fmt.Errorf("send params exist")
|
||||
// TODO: reflect.DeepEqual?
|
||||
//return fmt.Errorf("send params exist")
|
||||
}
|
||||
}
|
||||
|
||||
// compare meta params for resources with reversible traits
|
||||
r1v, ok1 := r1.(ReversibleRes)
|
||||
r2v, ok2 := r2.(ReversibleRes)
|
||||
if ok1 != ok2 {
|
||||
return fmt.Errorf("reversible differs") // they must be different (optional)
|
||||
}
|
||||
if ok1 && ok2 {
|
||||
if r1v.ReversibleMeta().Cmp(r2v.ReversibleMeta()) != nil {
|
||||
return fmt.Errorf("reversible differs")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,9 +372,11 @@ func VertexCmpFn(v1, v2 pgraph.Vertex) (bool, error) {
|
||||
return false, fmt.Errorf("v2 is not a Res")
|
||||
}
|
||||
|
||||
if ResCmp(r1, r2) != nil {
|
||||
if err := ResCmp(r1, r2); err != nil {
|
||||
//fmt.Printf("bad Cmp: %p %+v <> %p %+v err: %+v\n", r1, r1, r2, r2, err)
|
||||
return false, nil
|
||||
}
|
||||
//fmt.Printf("ok Cmp: %p %+v <> %p %+v\n", r1, r1, r2, r2)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -317,3 +395,55 @@ func EdgeCmpFn(e1, e2 pgraph.Edge) (bool, error) {
|
||||
}
|
||||
return edge1.Cmp(edge2) == nil, nil
|
||||
}
|
||||
|
||||
// ResGraphMapper compares two graphs, and gives us a mapping from new to old
|
||||
// based on the resource kind and name only. This allows us to know which
|
||||
// previous resource might have data to pass on to the new version in the next
|
||||
// generation.
|
||||
// FIXME: Optimize this for performance since it runs a lot...
|
||||
func ResGraphMapper(oldGraph, newGraph *pgraph.Graph) (map[RecvableRes]RecvableRes, error) {
|
||||
mapper := make(map[RecvableRes]RecvableRes) // new -> old based on name and kind only?
|
||||
cmp := func(r1, r2 Res) error {
|
||||
if r1.Kind() != r2.Kind() {
|
||||
return fmt.Errorf("kind differs")
|
||||
}
|
||||
if r1.Name() != r2.Name() {
|
||||
return fmt.Errorf("name differs")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XXX: run this as a topological sort or reverse topological sort?
|
||||
for v := range newGraph.Adjacency() { // loop through the vertices (resources)
|
||||
r, ok := v.(RecvableRes)
|
||||
if !ok {
|
||||
continue // skip
|
||||
}
|
||||
fn := func(vv pgraph.Vertex) (bool, error) {
|
||||
rr, ok := vv.(Res)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("not a Res")
|
||||
}
|
||||
|
||||
if err := cmp(rr, r); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
vertex, err := oldGraph.VertexMatchFn(fn)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "VertexMatchFn failed")
|
||||
}
|
||||
if vertex == nil {
|
||||
continue // skip (error?)
|
||||
}
|
||||
res, ok := vertex.(RecvableRes)
|
||||
if !ok {
|
||||
continue // skip (error?)
|
||||
}
|
||||
|
||||
mapper[r] = res
|
||||
}
|
||||
|
||||
return mapper, nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
@@ -81,6 +93,7 @@ func ResCopy(r CopyableRes) (CopyableRes, error) {
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("resource wasn't groupable")
|
||||
}
|
||||
g2.SetParent(dst) // store who my parent is
|
||||
grouped = append(grouped, g2)
|
||||
}
|
||||
dst.SetGroup(grouped)
|
||||
@@ -106,6 +119,16 @@ func ResCopy(r CopyableRes) (CopyableRes, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// copy meta params for resources with reversible traits
|
||||
if x, ok := r.(ReversibleRes); ok {
|
||||
dst, ok := res.(ReversibleRes)
|
||||
if !ok {
|
||||
// programming error
|
||||
panic("reversible interfaces are illogical")
|
||||
}
|
||||
dst.SetReversibleMeta(x.ReversibleMeta()) // no need to copy atm
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
||||
33
engine/doc.go
Normal file
33
engine/doc.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// Package engine represents the implementation of the resource engine that runs
|
||||
// the graph of resources in real-time. This package has the common imports that
|
||||
// most consumers use directly.
|
||||
package engine
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
@@ -26,4 +38,7 @@ func (e Error) Error() string { return string(e) }
|
||||
const (
|
||||
// ErrClosed means we couldn't complete a task because we had closed.
|
||||
ErrClosed = Error("closed")
|
||||
|
||||
// ErrBackPoke means we're postponing due to a needed backpoke.
|
||||
ErrBackPoke = Error("backpoke")
|
||||
)
|
||||
|
||||
101
engine/fs.go
101
engine/fs.go
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
@@ -23,39 +35,64 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// from the ioutil package:
|
||||
// NopCloser(r io.Reader) io.ReadCloser // not implemented here
|
||||
// ReadAll(r io.Reader) ([]byte, error)
|
||||
// ReadDir(dirname string) ([]os.FileInfo, error)
|
||||
// ReadFile(filename string) ([]byte, error)
|
||||
// TempDir(dir, prefix string) (name string, err error)
|
||||
// TempFile(dir, prefix string) (f *os.File, err error) // slightly different here
|
||||
// WriteFile(filename string, data []byte, perm os.FileMode) error
|
||||
|
||||
// Fs is an interface that represents this file system API that we support.
|
||||
// TODO: this should be in the gapi package or elsewhere.
|
||||
// Fs is an interface that represents the file system API that we support.
|
||||
// TODO: rename this to FS for consistency with the io/fs.FS naming scheme
|
||||
type Fs interface {
|
||||
//fmt.Stringer // TODO: add this method?
|
||||
afero.Fs // TODO: why doesn't this interface exist in the os pkg?
|
||||
|
||||
// URI returns a unique string handle to access this filesystem.
|
||||
URI() string // returns the URI for this file system
|
||||
|
||||
//DirExists(path string) (bool, error)
|
||||
//Exists(path string) (bool, error)
|
||||
//FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error)
|
||||
//FileContainsBytes(filename string, subslice []byte) (bool, error)
|
||||
//FullBaseFsPath(basePathFs *BasePathFs, relativePath string) string
|
||||
//GetTempDir(subPath string) string
|
||||
//IsDir(path string) (bool, error)
|
||||
//IsEmpty(path string) (bool, error)
|
||||
//NeuterAccents(s string) string
|
||||
//ReadAll(r io.Reader) ([]byte, error) // not needed, same as ioutil
|
||||
afero.Fs // TODO: why doesn't this interface exist in the os pkg?
|
||||
|
||||
// FS is the read-only filesystem interface from the io/fs.FS package.
|
||||
//fs.FS // io/fs.FS
|
||||
|
||||
// ReadDir reads the named directory and returns a list of directory
|
||||
// entries sorted by filename.
|
||||
//
|
||||
// This mimics the signature from io/fs.ReadDirFS and has the same docs.
|
||||
//
|
||||
// XXX: Not currently implemented because of legacy Afero.Fs above
|
||||
//ReadDir(name string) ([]fs.DirEntry, error) // io/fs.ReadDirFS
|
||||
|
||||
// ReadFile reads the named file and returns its contents. A successful
|
||||
// call returns a nil error, not io.EOF. (Because ReadFile reads the
|
||||
// whole file, the expected EOF from the final Read is not treated as an
|
||||
// error to be reported.)
|
||||
//
|
||||
// The caller is permitted to modify the returned byte slice. This
|
||||
// method should return a copy of the underlying data.
|
||||
//
|
||||
// This mimics the signature from io/fs.ReadFileFS and has the same
|
||||
// docs.
|
||||
ReadFile(name string) ([]byte, error) // io/fs.ReadFileFS
|
||||
|
||||
// Stat returns a FileInfo describing the file. If there is an error, it
|
||||
// should be of type *fs.PathError.
|
||||
//
|
||||
// This mimics the signature from io/fs.StatFS and has the same docs.
|
||||
//
|
||||
// XXX: Not currently implemented because of legacy Afero.Fs above
|
||||
//Stat(name string) (FileInfo, error) // io/fs.StatFS
|
||||
|
||||
// afero.Fs versions:
|
||||
|
||||
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||
ReadFile(filename string) ([]byte, error)
|
||||
//SafeWriteReader(path string, r io.Reader) (err error)
|
||||
TempDir(dir, prefix string) (name string, err error)
|
||||
TempFile(dir, prefix string) (f afero.File, err error) // slightly different from upstream
|
||||
//UnicodeSanitize(s string) string
|
||||
//Walk(root string, walkFn filepath.WalkFunc) error
|
||||
WriteFile(filename string, data []byte, perm os.FileMode) error
|
||||
//WriteReader(path string, r io.Reader) (err error)
|
||||
}
|
||||
|
||||
// WriteableFS is our internal filesystem interface for filesystems we write to.
|
||||
// It can wrap whatever implementations we want.
|
||||
type WriteableFS interface {
|
||||
Fs
|
||||
|
||||
// WriteFile writes data to the named file, creating it if necessary. If
|
||||
// the file does not exist, WriteFile creates it with permissions perm
|
||||
// (before umask); otherwise WriteFile truncates it before writing,
|
||||
// without changing permissions. Since Writefile requires multiple
|
||||
// system calls to complete, a failure mid-operation can leave the file
|
||||
// in a partially written state.
|
||||
//
|
||||
// This mimics the internal os.WriteFile function and has the same docs.
|
||||
WriteFile(name string, data []byte, perm os.FileMode) error
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,17 +13,31 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
@@ -39,14 +53,18 @@ func (obj *Engine) OKTimestamp(vertex pgraph.Vertex) bool {
|
||||
// be bad.
|
||||
func (obj *Engine) BadTimestamps(vertex pgraph.Vertex) []pgraph.Vertex {
|
||||
vs := []pgraph.Vertex{}
|
||||
ts := obj.state[vertex].timestamp
|
||||
obj.state[vertex].mutex.RLock() // concurrent read start
|
||||
ts := obj.state[vertex].timestamp // race
|
||||
obj.state[vertex].mutex.RUnlock() // concurrent read end
|
||||
// these are all the vertices pointing TO vertex, eg: ??? -> vertex
|
||||
for _, v := range obj.graph.IncomingGraphVertices(vertex) {
|
||||
// If the vertex has a greater timestamp than any prerequisite,
|
||||
// then we can't run right now. If they're equal (eg: initially
|
||||
// with a value of 0) then we also can't run because we should
|
||||
// let our pre-requisites go first.
|
||||
t := obj.state[v].timestamp
|
||||
obj.state[v].mutex.RLock() // concurrent read start
|
||||
t := obj.state[v].timestamp // race
|
||||
obj.state[v].mutex.RUnlock() // concurrent read end
|
||||
if obj.Debug {
|
||||
obj.Logf("OKTimestamp: %d >= %d (%s): !%t", ts, t, v.String(), ts >= t)
|
||||
}
|
||||
@@ -59,7 +77,7 @@ func (obj *Engine) BadTimestamps(vertex pgraph.Vertex) []pgraph.Vertex {
|
||||
}
|
||||
|
||||
// Process is the primary function to execute a particular vertex in the graph.
|
||||
func (obj *Engine) Process(vertex pgraph.Vertex) error {
|
||||
func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
|
||||
res, isRes := vertex.(engine.Res)
|
||||
if !isRes {
|
||||
return fmt.Errorf("vertex is not a Res")
|
||||
@@ -83,7 +101,9 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
|
||||
|
||||
}
|
||||
wg.Wait()
|
||||
return nil // can't continue until timestamp is in sequence
|
||||
|
||||
// can't continue until timestamp is in sequence, defer for now
|
||||
return engine.ErrBackPoke
|
||||
}
|
||||
|
||||
// semaphores!
|
||||
@@ -108,21 +128,41 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
|
||||
|
||||
// sendrecv!
|
||||
// connect any senders to receivers and detect if values changed
|
||||
// this actually checks and sends into resource trees recursively...
|
||||
if res, ok := vertex.(engine.RecvableRes); ok {
|
||||
if updated, err := obj.SendRecv(res); err != nil {
|
||||
if obj.Debug {
|
||||
obj.Logf("SendRecv: %s", res) // receiving here
|
||||
}
|
||||
if updated, err := SendRecv(res, nil); err != nil {
|
||||
return errwrap.Wrapf(err, "could not SendRecv")
|
||||
} else if len(updated) > 0 {
|
||||
for _, changed := range updated {
|
||||
if changed { // at least one was updated
|
||||
// invalidate cache, mark as dirty
|
||||
obj.state[vertex].tuid.StopTimer()
|
||||
obj.state[vertex].isStateOK = false
|
||||
break
|
||||
//for _, s := range graph.UpdatedStrings(updated) {
|
||||
// obj.Logf("SendRecv: %s", s)
|
||||
//}
|
||||
for r, m := range updated { // map[engine.RecvableRes]map[string]*engine.Send
|
||||
v, ok := r.(pgraph.Vertex)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
_, stateExists := obj.state[v] // autogrouped children probably don't have a state
|
||||
if !stateExists {
|
||||
continue
|
||||
}
|
||||
for s, send := range m {
|
||||
if !send.Changed {
|
||||
continue
|
||||
}
|
||||
obj.Logf("Send/Recv: %v.%s -> %v.%s", send.Res, send.Key, r, s)
|
||||
// if send.Changed == true, at least one was updated
|
||||
// invalidate cache, mark as dirty
|
||||
obj.state[v].setDirty()
|
||||
//break // we might have more vertices now
|
||||
}
|
||||
|
||||
// re-validate after we change any values
|
||||
if err := engine.Validate(r); err != nil {
|
||||
return errwrap.Wrapf(err, "failed Validate after SendRecv")
|
||||
}
|
||||
}
|
||||
// re-validate after we change any values
|
||||
if err := engine.Validate(res); err != nil {
|
||||
return errwrap.Wrapf(err, "failed Validate after SendRecv")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,18 +184,18 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
|
||||
// Check cached state, to skip CheckApply, but can't skip if refreshing!
|
||||
// If the resource doesn't implement refresh, skip the refresh test.
|
||||
// FIXME: if desired, check that we pass through refresh notifications!
|
||||
if (!refresh || !isRefreshableRes) && obj.state[vertex].isStateOK {
|
||||
if (!refresh || !isRefreshableRes) && obj.state[vertex].isStateOK.Load() { // mutex RLock/RUnlock
|
||||
checkOK, err = true, nil
|
||||
|
||||
} else if noop && (refresh && isRefreshableRes) { // had a refresh to do w/ noop!
|
||||
checkOK, err = false, nil // therefore the state is wrong
|
||||
|
||||
// run the CheckApply!
|
||||
} else {
|
||||
// run the CheckApply!
|
||||
obj.Logf("%s: CheckApply(%t)", res, !noop)
|
||||
// if this fails, don't UpdateTimestamp()
|
||||
checkOK, err = res.CheckApply(!noop)
|
||||
obj.Logf("%s: CheckApply(%t): Return(%t, %+v)", res, !noop, checkOK, err)
|
||||
checkOK, err = res.CheckApply(ctx, !noop)
|
||||
obj.Logf("%s: CheckApply(%t): Return(%t, %s)", res, !noop, checkOK, engineUtil.CleanError(err))
|
||||
}
|
||||
|
||||
if checkOK && err != nil { // should never return this way
|
||||
@@ -172,7 +212,9 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
|
||||
// if CheckApply ran without noop and without error, state should be good
|
||||
if !noop && err == nil { // aka !noop || checkOK
|
||||
obj.state[vertex].tuid.StartTimer()
|
||||
obj.state[vertex].isStateOK = true // reset
|
||||
//obj.state[vertex].mutex.Lock()
|
||||
obj.state[vertex].isStateOK.Store(true) // reset
|
||||
//obj.state[vertex].mutex.Unlock()
|
||||
if refresh {
|
||||
obj.SetUpstreamRefresh(vertex, false) // refresh happened, clear the request
|
||||
if isRefreshableRes {
|
||||
@@ -209,7 +251,9 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
|
||||
wg := &sync.WaitGroup{}
|
||||
// update this timestamp *before* we poke or the poked
|
||||
// nodes might fail due to having a too old timestamp!
|
||||
obj.state[vertex].timestamp = time.Now().UnixNano() // update timestamp
|
||||
obj.state[vertex].mutex.Lock() // concurrent write start
|
||||
obj.state[vertex].timestamp = time.Now().UnixNano() // update timestamp (race)
|
||||
obj.state[vertex].mutex.Unlock() // concurrent write end
|
||||
for _, v := range obj.graph.OutgoingGraphVertices(vertex) {
|
||||
if !obj.OKTimestamp(v) {
|
||||
// there is at least another one that will poke this...
|
||||
@@ -255,6 +299,16 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
|
||||
return fmt.Errorf("permanently limited (rate != Inf, burst = 0)")
|
||||
}
|
||||
|
||||
// initialize or reinitialize the meta state for this resource uid
|
||||
obj.mlock.Lock()
|
||||
if _, exists := obj.metas[engine.PtrUID(res)]; !exists || res.MetaParams().Reset {
|
||||
obj.metas[engine.PtrUID(res)] = &engine.MetaState{
|
||||
CheckApplyRetry: res.MetaParams().Retry, // lookup the retry value
|
||||
}
|
||||
}
|
||||
metas := obj.metas[engine.PtrUID(res)] // handle
|
||||
obj.mlock.Unlock()
|
||||
|
||||
//defer close(obj.state[vertex].stopped) // done signal
|
||||
|
||||
obj.state[vertex].cuid = obj.Converger.Register()
|
||||
@@ -272,7 +326,7 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
|
||||
defer close(obj.state[vertex].eventsChan) // we close this on behalf of res
|
||||
|
||||
// This is a close reverse-multiplexer. If any of the channels
|
||||
// close, then it will cause the doneChan to close. That way,
|
||||
// close, then it will cause the doneCtx to cancel. That way,
|
||||
// multiple different folks can send a close signal, without
|
||||
// every worrying about duplicate channel close panics.
|
||||
obj.state[vertex].wg.Add(1)
|
||||
@@ -284,12 +338,13 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
|
||||
case <-obj.state[vertex].processDone:
|
||||
case <-obj.state[vertex].watchDone:
|
||||
case <-obj.state[vertex].limitDone:
|
||||
case <-obj.state[vertex].retryDone:
|
||||
case <-obj.state[vertex].removeDone:
|
||||
case <-obj.state[vertex].eventsDone:
|
||||
}
|
||||
|
||||
// the main "done" signal gets activated here!
|
||||
close(obj.state[vertex].doneChan)
|
||||
obj.state[vertex].doneCtxCancel() // cancels doneCtx
|
||||
}()
|
||||
|
||||
var err error
|
||||
@@ -308,7 +363,7 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
|
||||
case <-timer.C: // the wait is over
|
||||
return errDelayExpired // special
|
||||
|
||||
case <-obj.state[vertex].init.Done:
|
||||
case <-obj.state[vertex].doneCtx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -319,13 +374,21 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
|
||||
}
|
||||
} else if interval := res.MetaParams().Poll; interval > 0 { // poll instead of watching :(
|
||||
obj.state[vertex].cuid.StartTimer()
|
||||
err = obj.state[vertex].poll(interval)
|
||||
err = obj.state[vertex].poll(obj.state[vertex].doneCtx, interval)
|
||||
obj.state[vertex].cuid.StopTimer() // clean up nicely
|
||||
} else {
|
||||
obj.state[vertex].cuid.StartTimer()
|
||||
obj.Logf("Watch(%s)", vertex)
|
||||
err = res.Watch() // run the watch normally
|
||||
obj.Logf("Watch(%s): Exited(%+v)", vertex, err)
|
||||
if obj.Debug {
|
||||
obj.Logf("%s: Watch...", vertex)
|
||||
}
|
||||
err = res.Watch(obj.state[vertex].doneCtx) // run the watch normally
|
||||
if obj.Debug {
|
||||
if s := engineUtil.CleanError(err); err != nil {
|
||||
obj.Logf("%s: Watch Error: %s", vertex, s)
|
||||
} else {
|
||||
obj.Logf("%s: Watch Exited...", vertex)
|
||||
}
|
||||
}
|
||||
obj.state[vertex].cuid.StopTimer() // clean up nicely
|
||||
}
|
||||
if err == nil { // || err == engine.ErrClosed
|
||||
@@ -359,13 +422,19 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
|
||||
|
||||
// If this exits cleanly, we must unblock the reverse-multiplexer.
|
||||
// I think this additional close is unnecessary, but it's not harmful.
|
||||
defer close(obj.state[vertex].eventsDone) // causes doneChan to close
|
||||
defer close(obj.state[vertex].eventsDone) // causes doneCtx to cancel
|
||||
limiter := rate.NewLimiter(res.MetaParams().Limit, res.MetaParams().Burst)
|
||||
var reserv *rate.Reservation
|
||||
var reterr error
|
||||
var failed bool // has Process permanently failed?
|
||||
var closed bool // has the resumeSignal channel closed?
|
||||
Loop:
|
||||
for { // process loop
|
||||
// This is the main select where things happen and where we exit
|
||||
// from. It's similar to the two "satellite" select's which we
|
||||
// might spend some time in if we're retrying or rate limiting.
|
||||
// This select is also the main event receiver and is also the
|
||||
// only place where we read from the poke channel.
|
||||
select {
|
||||
case err, ok := <-obj.state[vertex].eventsChan: // read from watch channel
|
||||
if !ok {
|
||||
@@ -376,7 +445,7 @@ Loop:
|
||||
// we then save so we can return it to the caller of us.
|
||||
if err != nil {
|
||||
failed = true
|
||||
close(obj.state[vertex].watchDone) // causes doneChan to close
|
||||
close(obj.state[vertex].watchDone) // causes doneCtx to cancel
|
||||
reterr = errwrap.Append(reterr, err) // permanent failure
|
||||
continue
|
||||
}
|
||||
@@ -394,9 +463,33 @@ Loop:
|
||||
obj.Logf("poke received")
|
||||
}
|
||||
reserv = nil // we didn't receive a real event here...
|
||||
}
|
||||
if failed { // don't Process anymore if we've already failed...
|
||||
continue Loop
|
||||
|
||||
case _, ok := <-obj.state[vertex].pauseSignal: // one message
|
||||
if !ok {
|
||||
obj.state[vertex].pauseSignal = nil
|
||||
continue // this is not a new pause message
|
||||
}
|
||||
// NOTE: If we allowed a doneCtx below to let us out
|
||||
// of the resumeSignal wait, then we could loop around
|
||||
// and run this again, causing a panic. Instead of this
|
||||
// being made safe with a sync.Once, we instead run a
|
||||
// close() call inside of the vertexRemoveFn function,
|
||||
// which should unblock resumeSignal so we can shutdown.
|
||||
|
||||
// we are paused now, and waiting for resume or exit...
|
||||
select {
|
||||
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
|
||||
if !ok {
|
||||
closed = true
|
||||
}
|
||||
// resumed!
|
||||
// pass through to allow a Process to try to run
|
||||
// TODO: consider adding this fast pause here...
|
||||
//if obj.fastPause {
|
||||
// obj.Logf("fast pausing on resume")
|
||||
// continue
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
// drop redundant pokes
|
||||
@@ -408,31 +501,8 @@ Loop:
|
||||
}
|
||||
}
|
||||
|
||||
// pause if one was requested...
|
||||
select {
|
||||
case <-obj.state[vertex].pauseSignal: // channel closes
|
||||
// NOTE: If we allowed a doneChan below to let us out
|
||||
// of the resumeSignal wait, then we could loop around
|
||||
// and run this again, causing a panic. Instead of this
|
||||
// being made safe with a sync.Once, we instead run a
|
||||
// Resume() call inside of the vertexRemoveFn function,
|
||||
// which should unblock it when we're going to need to.
|
||||
obj.state[vertex].pausedAck.Ack() // send ack
|
||||
// we are paused now, and waiting for resume or exit...
|
||||
select {
|
||||
case <-obj.state[vertex].resumeSignal: // channel closes
|
||||
// resumed!
|
||||
// pass through to allow a Process to try to run
|
||||
// TODO: consider adding this fast pause here...
|
||||
//if obj.fastPause {
|
||||
// obj.Logf("fast pausing on resume")
|
||||
// continue
|
||||
//}
|
||||
}
|
||||
default:
|
||||
// no pause requested, keep going...
|
||||
}
|
||||
if failed { // don't Process anymore if we've already failed...
|
||||
// don't Process anymore if we've already failed or shutdown...
|
||||
if failed || closed {
|
||||
continue Loop
|
||||
}
|
||||
|
||||
@@ -442,10 +512,13 @@ Loop:
|
||||
d = reserv.DelayFrom(time.Now())
|
||||
}
|
||||
if reserv != nil && d > 0 { // delay
|
||||
obj.state[vertex].init.Logf("limited (rate: %v/sec, burst: %d, next: %v)", res.MetaParams().Limit, res.MetaParams().Burst, d)
|
||||
obj.state[vertex].init.Logf("limited (rate: %v/sec, burst: %d, next: %dms)", res.MetaParams().Limit, res.MetaParams().Burst, d/time.Millisecond)
|
||||
timer := time.NewTimer(time.Duration(d) * time.Millisecond)
|
||||
LimitWait:
|
||||
for {
|
||||
// This "satellite" select doesn't need a poke
|
||||
// channel because we're already in "event
|
||||
// received" mode, and poke doesn't block.
|
||||
select {
|
||||
case <-timer.C: // the wait is over
|
||||
break LimitWait
|
||||
@@ -457,7 +530,7 @@ Loop:
|
||||
}
|
||||
if e != nil {
|
||||
failed = true
|
||||
close(obj.state[vertex].limitDone) // causes doneChan to close
|
||||
close(obj.state[vertex].limitDone) // causes doneCtx to cancel
|
||||
reterr = errwrap.Append(reterr, e) // permanent failure
|
||||
break LimitWait
|
||||
}
|
||||
@@ -466,19 +539,34 @@ Loop:
|
||||
}
|
||||
// TODO: does this get added in properly?
|
||||
limiter.ReserveN(time.Now(), 1) // one event
|
||||
|
||||
// this pause/resume block is the same as the upper main one
|
||||
case _, ok := <-obj.state[vertex].pauseSignal:
|
||||
if !ok {
|
||||
obj.state[vertex].pauseSignal = nil
|
||||
break LimitWait
|
||||
}
|
||||
select {
|
||||
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
|
||||
if !ok {
|
||||
closed = true
|
||||
}
|
||||
// resumed!
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.Stop() // it's nice to cleanup
|
||||
obj.state[vertex].init.Logf("rate limiting expired!")
|
||||
}
|
||||
if failed { // don't Process anymore if we've already failed...
|
||||
// don't Process anymore if we've already failed or shutdown...
|
||||
if failed || closed {
|
||||
continue Loop
|
||||
}
|
||||
// end of limit delay
|
||||
|
||||
// retry...
|
||||
var err error
|
||||
var retry = res.MetaParams().Retry // lookup the retry value
|
||||
//var retry = res.MetaParams().Retry // lookup the retry value
|
||||
var delay uint64
|
||||
RetryLoop:
|
||||
for { // retry loop
|
||||
@@ -486,6 +574,10 @@ Loop:
|
||||
timer := time.NewTimer(time.Duration(delay) * time.Millisecond)
|
||||
RetryWait:
|
||||
for {
|
||||
// This "satellite" select doesn't need
|
||||
// a poke channel because we're already
|
||||
// in "event received" mode, and poke
|
||||
// doesn't block.
|
||||
select {
|
||||
case <-timer.C: // the wait is over
|
||||
break RetryWait
|
||||
@@ -497,7 +589,7 @@ Loop:
|
||||
}
|
||||
if e != nil {
|
||||
failed = true
|
||||
close(obj.state[vertex].limitDone) // causes doneChan to close
|
||||
close(obj.state[vertex].retryDone) // causes doneCtx to cancel
|
||||
reterr = errwrap.Append(reterr, e) // permanent failure
|
||||
break RetryWait
|
||||
}
|
||||
@@ -506,38 +598,68 @@ Loop:
|
||||
}
|
||||
// TODO: does this get added in properly?
|
||||
limiter.ReserveN(time.Now(), 1) // one event
|
||||
|
||||
// this pause/resume block is the same as the upper main one
|
||||
case _, ok := <-obj.state[vertex].pauseSignal:
|
||||
if !ok {
|
||||
obj.state[vertex].pauseSignal = nil
|
||||
break RetryWait
|
||||
}
|
||||
select {
|
||||
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
|
||||
if !ok {
|
||||
closed = true
|
||||
}
|
||||
// resumed!
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.Stop() // it's nice to cleanup
|
||||
delay = 0 // reset
|
||||
obj.state[vertex].init.Logf("the CheckApply delay expired!")
|
||||
}
|
||||
if failed { // don't Process anymore if we've already failed...
|
||||
// don't Process anymore if we've already failed or shutdown...
|
||||
if failed || closed {
|
||||
continue Loop
|
||||
}
|
||||
|
||||
if obj.Debug {
|
||||
obj.Logf("Process(%s)", vertex)
|
||||
}
|
||||
err = obj.Process(vertex)
|
||||
if obj.Debug {
|
||||
obj.Logf("Process(%s): Return(%+v)", vertex, err)
|
||||
backPoke := false
|
||||
err = obj.Process(obj.state[vertex].doneCtx, vertex)
|
||||
if err == engine.ErrBackPoke {
|
||||
backPoke = true
|
||||
err = nil // for future code safety
|
||||
}
|
||||
if err == nil {
|
||||
if obj.Debug && backPoke {
|
||||
obj.Logf("Process(%s): BackPoke!", vertex)
|
||||
}
|
||||
if obj.Debug && !backPoke {
|
||||
obj.Logf("Process(%s): Return(%s)", vertex, engineUtil.CleanError(err))
|
||||
}
|
||||
if err == nil && !backPoke && res.MetaParams().RetryReset { // reset it on success!
|
||||
metas.CheckApplyRetry = res.MetaParams().Retry // lookup the retry value
|
||||
}
|
||||
if err == nil || backPoke {
|
||||
break RetryLoop
|
||||
}
|
||||
// we've got an error...
|
||||
delay = res.MetaParams().Delay
|
||||
|
||||
if retry < 0 { // infinite retries
|
||||
if metas.CheckApplyRetry < 0 { // infinite retries
|
||||
continue
|
||||
}
|
||||
if retry > 0 { // don't decrement past 0
|
||||
retry--
|
||||
obj.state[vertex].init.Logf("retrying CheckApply after %.4f seconds (%d left)", float64(delay)/1000, retry)
|
||||
if metas.CheckApplyRetry > 0 { // don't decrement past 0
|
||||
metas.CheckApplyRetry--
|
||||
obj.state[vertex].init.Logf(
|
||||
"retrying CheckApply after %.4f seconds (%d left)",
|
||||
float64(delay)/1000,
|
||||
metas.CheckApplyRetry,
|
||||
)
|
||||
continue
|
||||
}
|
||||
//if retry == 0 { // optional
|
||||
//if metas.CheckApplyRetry == 0 { // optional
|
||||
// err = errwrap.Wrapf(err, "permanent process error")
|
||||
//}
|
||||
|
||||
@@ -545,7 +667,7 @@ Loop:
|
||||
// this dies. If Process fails permanently, we ask it
|
||||
// to exit right here... (It happens when we loop...)
|
||||
failed = true
|
||||
close(obj.state[vertex].processDone) // causes doneChan to close
|
||||
close(obj.state[vertex].processDone) // causes doneCtx to cancel
|
||||
reterr = errwrap.Append(reterr, err) // permanent failure
|
||||
continue
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package autoedge
|
||||
|
||||
@@ -89,6 +101,9 @@ func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...int
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It would be great to ensure we didn't add any graph cycles here, but
|
||||
// instead of checking now, we'll move the check into the main loop.
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
@@ -74,10 +86,10 @@ func (obj *wrappedGrouper) VertexCmp(v1, v2 pgraph.Vertex) error {
|
||||
return fmt.Errorf("v2 is not a GroupableRes")
|
||||
}
|
||||
|
||||
if r1.Kind() != r2.Kind() { // we must group similar kinds
|
||||
// TODO: maybe future resources won't need this limitation?
|
||||
return fmt.Errorf("the two resources aren't the same kind")
|
||||
}
|
||||
// Some resources of different kinds can now group together!
|
||||
//if r1.Kind() != r2.Kind() { // we must group similar kinds
|
||||
// return fmt.Errorf("the two resources aren't the same kind")
|
||||
//}
|
||||
// someone doesn't want to group!
|
||||
if r1.AutoGroupMeta().Disabled || r2.AutoGroupMeta().Disabled {
|
||||
return fmt.Errorf("one of the autogroup flags is false")
|
||||
@@ -109,6 +121,7 @@ func (obj *wrappedGrouper) VertexMerge(v1, v2 pgraph.Vertex) (v pgraph.Vertex, e
|
||||
if err = r1.GroupRes(r2); err != nil { // GroupRes skips stupid groupings
|
||||
return // return early on error
|
||||
}
|
||||
r2.SetParent(r1) // store who my parent is
|
||||
|
||||
// merging two resources into one should yield the sum of their semas
|
||||
if semas := r2.MetaParams().Sema; len(semas) > 0 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package autogroup
|
||||
|
||||
@@ -54,7 +66,7 @@ func AutoGroup(ag engine.AutoGrouper, g *pgraph.Graph, debug bool, logf func(for
|
||||
logf("!VertexMerge for: %s into: %s", wStr, vStr)
|
||||
|
||||
} else { // success!
|
||||
logf("success for: %s into: %s", wStr, vStr)
|
||||
logf("%s into %s", wStr, vStr)
|
||||
merged = true // woo
|
||||
}
|
||||
|
||||
@@ -66,5 +78,8 @@ func AutoGroup(ag engine.AutoGrouper, g *pgraph.Graph, debug bool, logf func(for
|
||||
}
|
||||
}
|
||||
|
||||
// It would be great to ensure we didn't add any graph cycles here, but
|
||||
// instead of checking now, we'll move the check into the main loop.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,13 +13,26 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// +build !root
|
||||
//go:build !root
|
||||
|
||||
package autogroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
@@ -62,15 +75,15 @@ func (obj *NoopResTest) Init(init *engine.Init) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *NoopResTest) Close() error {
|
||||
func (obj *NoopResTest) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *NoopResTest) Watch() error {
|
||||
func (obj *NoopResTest) Watch(context.Context) error {
|
||||
return nil // not needed
|
||||
}
|
||||
|
||||
func (obj *NoopResTest) CheckApply(apply bool) (checkOK bool, err error) {
|
||||
func (obj *NoopResTest) CheckApply(ctx context.Context, apply bool) (checkOK bool, err error) {
|
||||
return true, nil // state is always okay
|
||||
}
|
||||
|
||||
@@ -189,6 +202,8 @@ func (obj *testGrouper) VertexMerge(v1, v2 pgraph.Vertex) (v pgraph.Vertex, err
|
||||
if err := r1.GroupRes(r2); err != nil { // group them first
|
||||
return nil, err
|
||||
}
|
||||
r2.SetParent(r1) // store who my parent is
|
||||
|
||||
// HACK: update the name so it matches full list of self+grouped
|
||||
res := v1.(engine.GroupableRes)
|
||||
names := strings.Split(res.Name(), ",") // load in stored names
|
||||
@@ -595,10 +610,12 @@ func TestPgraphGrouping11(t *testing.T) {
|
||||
runGraphCmp(t, g1, g2)
|
||||
}
|
||||
|
||||
/*
|
||||
// simple merge 1
|
||||
// a1 a2 a1,a2
|
||||
// \ / >>> | (arrows point downwards)
|
||||
// b b
|
||||
*/
|
||||
func TestPgraphGrouping12(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
@@ -620,10 +637,12 @@ func TestPgraphGrouping12(t *testing.T) {
|
||||
runGraphCmp(t, g1, g2)
|
||||
}
|
||||
|
||||
/*
|
||||
// simple merge 2
|
||||
// b b
|
||||
// / \ >>> | (arrows point downwards)
|
||||
// a1 a2 a1,a2
|
||||
*/
|
||||
func TestPgraphGrouping13(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
@@ -645,10 +664,12 @@ func TestPgraphGrouping13(t *testing.T) {
|
||||
runGraphCmp(t, g1, g2)
|
||||
}
|
||||
|
||||
/*
|
||||
// triple merge
|
||||
// a1 a2 a3 a1,a2,a3
|
||||
// \ | / >>> | (arrows point downwards)
|
||||
// b b
|
||||
*/
|
||||
func TestPgraphGrouping14(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
@@ -673,12 +694,14 @@ func TestPgraphGrouping14(t *testing.T) {
|
||||
runGraphCmp(t, g1, g2)
|
||||
}
|
||||
|
||||
/*
|
||||
// chain merge
|
||||
// a1 a1
|
||||
// / \ |
|
||||
// b1 b2 >>> b1,b2 (arrows point downwards)
|
||||
// \ / |
|
||||
// c1 c1
|
||||
*/
|
||||
func TestPgraphGrouping15(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
@@ -708,6 +731,7 @@ func TestPgraphGrouping15(t *testing.T) {
|
||||
runGraphCmp(t, g1, g2)
|
||||
}
|
||||
|
||||
/*
|
||||
// re-attach 1 (outer)
|
||||
// technically the second possibility is valid too, depending on which order we
|
||||
// merge edges in, and if we don't filter out any unnecessary edges afterwards!
|
||||
@@ -716,6 +740,7 @@ func TestPgraphGrouping15(t *testing.T) {
|
||||
// b1 / >>> b1 OR b1 / (arrows point downwards)
|
||||
// | / | | /
|
||||
// c1 c1 c1
|
||||
*/
|
||||
func TestPgraphGrouping16(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
@@ -730,25 +755,40 @@ func TestPgraphGrouping16(t *testing.T) {
|
||||
g1.AddEdge(b1, c1, e2)
|
||||
g1.AddEdge(a2, c1, e3)
|
||||
}
|
||||
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||
//g2, _ := pgraph.NewGraph("g2") // expected result
|
||||
//{
|
||||
// a := NewNoopResTest("a1,a2")
|
||||
// b1 := NewNoopResTest("b1")
|
||||
// c1 := NewNoopResTest("c1")
|
||||
// e1 := NE("e1,e3")
|
||||
// e2 := NE("e2,e3") // e3 gets "merged through" to BOTH edges!
|
||||
// g2.AddEdge(a, b1, e1)
|
||||
// g2.AddEdge(b1, c1, e2)
|
||||
//}
|
||||
//runGraphCmp(t, g1, g2)
|
||||
g3, _ := pgraph.NewGraph("g3") // alternative expected result
|
||||
{
|
||||
a := NewNoopResTest("a1,a2")
|
||||
b1 := NewNoopResTest("b1")
|
||||
c1 := NewNoopResTest("c1")
|
||||
e1 := NE("e1,e3")
|
||||
e2 := NE("e2,e3") // e3 gets "merged through" to BOTH edges!
|
||||
g2.AddEdge(a, b1, e1)
|
||||
g2.AddEdge(b1, c1, e2)
|
||||
e1 := NE("e1")
|
||||
e2 := NE("e2")
|
||||
e3 := NE("e3")
|
||||
g3.AddEdge(a, b1, e1)
|
||||
g3.AddEdge(b1, c1, e2)
|
||||
g3.AddEdge(a, c1, e3)
|
||||
}
|
||||
runGraphCmp(t, g1, g2)
|
||||
runGraphCmp(t, g1, g3)
|
||||
}
|
||||
|
||||
/*
|
||||
// re-attach 2 (inner)
|
||||
// a1 b2 a1
|
||||
// | / |
|
||||
// b1 / >>> b1,b2 (arrows point downwards)
|
||||
// | / |
|
||||
// c1 c1
|
||||
*/
|
||||
func TestPgraphGrouping17(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
@@ -776,13 +816,16 @@ func TestPgraphGrouping17(t *testing.T) {
|
||||
runGraphCmp(t, g1, g2)
|
||||
}
|
||||
|
||||
/*
|
||||
// re-attach 3 (double)
|
||||
// similar to "re-attach 1", technically there is a second possibility for this
|
||||
// a2 a1 b2 a1,a2
|
||||
// \ | / |
|
||||
// \ b1 / >>> b1,b2 (arrows point downwards)
|
||||
// \ | / |
|
||||
// c1 c1
|
||||
// TODO: verify this second possibility manually
|
||||
// a2 a1 b2 a1,a2 a1,a2
|
||||
// \ | / | | \
|
||||
// \ b1 / >>> b1,b2 OR b1,b2 / (arrows point downwards)
|
||||
// \ | / | | /
|
||||
// c1 c1 c1
|
||||
*/
|
||||
func TestPgraphGrouping18(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
@@ -800,23 +843,38 @@ func TestPgraphGrouping18(t *testing.T) {
|
||||
g1.AddEdge(a2, c1, e3)
|
||||
g1.AddEdge(b2, c1, e4)
|
||||
}
|
||||
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||
//g2, _ := pgraph.NewGraph("g2") // expected result
|
||||
//{
|
||||
// a := NewNoopResTest("a1,a2")
|
||||
// b := NewNoopResTest("b1,b2")
|
||||
// c1 := NewNoopResTest("c1")
|
||||
// e1 := NE("e1,e3")
|
||||
// e2 := NE("e2,e3,e4") // e3 gets "merged through" to BOTH edges!
|
||||
// g2.AddEdge(a, b, e1)
|
||||
// g2.AddEdge(b, c1, e2)
|
||||
//}
|
||||
//runGraphCmp(t, g1, g2)
|
||||
g3, _ := pgraph.NewGraph("g3") // alternative expected result
|
||||
{
|
||||
a := NewNoopResTest("a1,a2")
|
||||
b := NewNoopResTest("b1,b2")
|
||||
c1 := NewNoopResTest("c1")
|
||||
e1 := NE("e1,e3")
|
||||
e2 := NE("e2,e3,e4") // e3 gets "merged through" to BOTH edges!
|
||||
g2.AddEdge(a, b, e1)
|
||||
g2.AddEdge(b, c1, e2)
|
||||
e1 := NE("e1")
|
||||
e2 := NE("e2,e4")
|
||||
e3 := NE("e3")
|
||||
g3.AddEdge(a, b, e1)
|
||||
g3.AddEdge(b, c1, e2)
|
||||
g3.AddEdge(a, c1, e3)
|
||||
}
|
||||
runGraphCmp(t, g1, g2)
|
||||
runGraphCmp(t, g1, g3)
|
||||
}
|
||||
|
||||
/*
|
||||
// connected merge 0, (no change!)
|
||||
// a1 a1
|
||||
// \ >>> \ (arrows point downwards)
|
||||
// a2 a2
|
||||
*/
|
||||
func TestPgraphGroupingConnected0(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
@@ -835,12 +893,14 @@ func TestPgraphGroupingConnected0(t *testing.T) {
|
||||
runGraphCmp(t, g1, g2)
|
||||
}
|
||||
|
||||
/*
|
||||
// connected merge 1, (no change!)
|
||||
// a1 a1
|
||||
// \ \
|
||||
// b >>> b (arrows point downwards)
|
||||
// \ \
|
||||
// a2 a2
|
||||
*/
|
||||
func TestPgraphGroupingConnected1(t *testing.T) {
|
||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package autogroup
|
||||
|
||||
@@ -43,8 +55,24 @@ func (ag *baseGrouper) Init(g *pgraph.Graph) error {
|
||||
if ag.graph != nil {
|
||||
return fmt.Errorf("the init method has already been called")
|
||||
}
|
||||
ag.graph = g // pointer
|
||||
ag.vertices = ag.graph.VerticesSorted() // cache in deterministic order!
|
||||
ag.graph = g // pointer
|
||||
|
||||
// We sort deterministically, first by kind, and then by name. In
|
||||
// particular, longer kind chunks sort first. So http:ui:text should
|
||||
// appear before http:server and http:ui. This is a hack so that if we
|
||||
// are doing hierarchical automatic grouping, it gives the http:ui:text
|
||||
// a chance to get grouped into http:ui, before http:ui gets grouped
|
||||
// into http:server, because once that happens, http:ui:text will never
|
||||
// get grouped, and this won't work properly. This works, because when
|
||||
// we start comparing iteratively the list of resources, it does this
|
||||
// with a O(n^2) loop that compares the X and Y zero indexes first, and
|
||||
// and then continues along. If the "longer" resources appear first,
|
||||
// then they'll group together first. We should probably put this into
|
||||
// a new Grouper struct, but for now we might as well leave it here.
|
||||
//vertices := ag.graph.VerticesSorted() // formerly
|
||||
vertices := RHVSort(ag.graph.Vertices())
|
||||
|
||||
ag.vertices = vertices // cache in deterministic order!
|
||||
ag.i = 0
|
||||
ag.j = 0
|
||||
if len(ag.vertices) == 0 { // empty graph
|
||||
@@ -89,6 +117,19 @@ func (ag *baseGrouper) VertexNext() (v1, v2 pgraph.Vertex, err error) {
|
||||
ag.done = true
|
||||
}
|
||||
}
|
||||
// TODO: is this index swap better or even valid?
|
||||
//if ag.i < l {
|
||||
// ag.i++
|
||||
//}
|
||||
//if ag.i == l {
|
||||
// ag.i = 0
|
||||
// if ag.j < l {
|
||||
// ag.j++
|
||||
// }
|
||||
// if ag.j == l {
|
||||
// ag.done = true
|
||||
// }
|
||||
//}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -110,7 +151,7 @@ func (ag *baseGrouper) VertexMerge(v1, v2 pgraph.Vertex) (v pgraph.Vertex, err e
|
||||
return nil, fmt.Errorf("vertexMerge needs to be overridden")
|
||||
}
|
||||
|
||||
// EdgeMerge can be overridden, since it just simple returns the first edge.
|
||||
// EdgeMerge can be overridden, since it just simply returns the first edge.
|
||||
func (ag *baseGrouper) EdgeMerge(e1, e2 pgraph.Edge) pgraph.Edge {
|
||||
return e1 // noop
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package autogroup
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,18 +13,35 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package autogroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
// VertexMerge merges v2 into v1 by reattaching the edges where appropriate,
|
||||
// and then by deleting v2 from the graph. Since more than one edge between two
|
||||
// vertices is not allowed, duplicate edges are merged as well. an edge merge
|
||||
// VertexMerge merges v2 into v1 by reattaching the edges where appropriate, and
|
||||
// then by deleting v2 from the graph. Since more than one edge between two
|
||||
// vertices is not allowed, duplicate edges are merged as well. An edge merge
|
||||
// function can be provided if you'd like to control how you merge the edges!
|
||||
func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgraph.Vertex, pgraph.Vertex) (pgraph.Vertex, error), edgeMergeFn func(pgraph.Edge, pgraph.Edge) pgraph.Edge) error {
|
||||
// methodology
|
||||
@@ -112,8 +129,17 @@ func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgrap
|
||||
// note: This branch isn't used if the vertexMergeFn
|
||||
// decides to just merge logically on its own instead
|
||||
// of actually returning something that we then merge.
|
||||
v1 = v // TODO: ineffassign?
|
||||
v1 = v // XXX: ineffassign?
|
||||
//*v1 = *v
|
||||
|
||||
// Ensure that everything still validates. (For safety!)
|
||||
r, ok := v1.(engine.Res) // TODO: v ?
|
||||
if !ok {
|
||||
return fmt.Errorf("not a Res")
|
||||
}
|
||||
if err := engine.Validate(r); err != nil {
|
||||
return errwrap.Wrapf(err, "the Res did not Validate")
|
||||
}
|
||||
}
|
||||
}
|
||||
g.DeleteVertex(v2) // remove grouped vertex
|
||||
@@ -124,3 +150,67 @@ func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgrap
|
||||
}
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// RHVSlice is a linear list of vertices. It can be sorted by the Kind, taking
|
||||
// into account the hierarchy of names separated by colons. Afterwards, it uses
|
||||
// String() to avoid the non-determinism in the map type. RHV stands for Reverse
|
||||
// Hierarchical Vertex, meaning the hierarchical topology of the vertex
|
||||
// (resource) names are used.
|
||||
type RHVSlice []pgraph.Vertex
|
||||
|
||||
// Len returns the length of the slice of vertices.
|
||||
func (obj RHVSlice) Len() int { return len(obj) }
|
||||
|
||||
// Swap swaps two elements in the slice.
|
||||
func (obj RHVSlice) Swap(i, j int) { obj[i], obj[j] = obj[j], obj[i] }
|
||||
|
||||
// Less returns the smaller element in the sort order according to the
|
||||
// aforementioned rules.
|
||||
// XXX: Add some tests to make sure I didn't get any "reverse" part backwards.
|
||||
func (obj RHVSlice) Less(i, j int) bool {
|
||||
resi, oki := obj[i].(engine.Res)
|
||||
resj, okj := obj[j].(engine.Res)
|
||||
if !oki || !okj || resi.Kind() == "" || resj.Kind() == "" {
|
||||
// One of these isn't a normal Res, so just compare normally.
|
||||
return obj[i].String() > obj[j].String() // reverse
|
||||
}
|
||||
|
||||
si := strings.Split(resi.Kind(), ":")
|
||||
sj := strings.Split(resj.Kind(), ":")
|
||||
// both lengths should each be at least one now
|
||||
li := len(si)
|
||||
lj := len(sj)
|
||||
|
||||
if li != lj { // eg: http:ui vs. http:ui:text
|
||||
return li > lj // reverse
|
||||
}
|
||||
|
||||
// same number of chunks
|
||||
for k := 0; k < li; k++ {
|
||||
if si[k] != sj[k] { // lhs chunk differs
|
||||
return si[k] > sj[k] // reverse
|
||||
}
|
||||
|
||||
// if the chunks are the same, we continue...
|
||||
}
|
||||
|
||||
// They must all have the same chunks, so finally we compare the names.
|
||||
return resi.Name() > resj.Name() // reverse
|
||||
}
|
||||
|
||||
// Sort is a convenience method.
|
||||
func (obj RHVSlice) Sort() { sort.Sort(obj) }
|
||||
|
||||
// RHVSort returns a deterministically sorted slice of all vertices in the list.
|
||||
// The order is sorted by the Kind, taking into account the hierarchy of names
|
||||
// separated by colons. Afterwards, it uses String() to avoid the
|
||||
// non-determinism in the map type. RHV stands for Reverse Hierarchical Vertex,
|
||||
// meaning the hierarchical topology of the vertex (resource) names are used.
|
||||
func RHVSort(vertices []pgraph.Vertex) []pgraph.Vertex {
|
||||
var vs []pgraph.Vertex
|
||||
for _, v := range vertices { // copy first
|
||||
vs = append(vs, v)
|
||||
}
|
||||
sort.Sort(RHVSlice(vs)) // add determinism
|
||||
return vs
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,8 +13,23 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// Package graph contains the actual implementation of the resource graph engine
|
||||
// that runs the graph of resources in real-time. This package has the algorithm
|
||||
// that runs all the graph transitions.
|
||||
package graph
|
||||
|
||||
import (
|
||||
@@ -25,24 +40,34 @@ import (
|
||||
|
||||
"github.com/purpleidea/mgmt/converger"
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
"github.com/purpleidea/mgmt/engine/local"
|
||||
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
"github.com/purpleidea/mgmt/util/semaphore"
|
||||
)
|
||||
|
||||
const (
|
||||
// StateDir is the name of the sub directory where all the local
|
||||
// resource state is stored.
|
||||
StateDir = "state"
|
||||
)
|
||||
|
||||
// Engine encapsulates a generic graph and manages its operations.
|
||||
type Engine struct {
|
||||
Program string
|
||||
Version string
|
||||
Hostname string
|
||||
World engine.World
|
||||
|
||||
Converger *converger.Coordinator
|
||||
Local *local.API
|
||||
World engine.World
|
||||
|
||||
// Prefix is a unique directory prefix which can be used. It should be
|
||||
// created if needed.
|
||||
Prefix string
|
||||
Converger *converger.Coordinator
|
||||
|
||||
Debug bool
|
||||
Logf func(format string, v ...interface{})
|
||||
Prefix string
|
||||
Debug bool
|
||||
Logf func(format string, v ...interface{})
|
||||
|
||||
graph *pgraph.Graph
|
||||
nextGraph *pgraph.Graph
|
||||
@@ -50,6 +75,9 @@ type Engine struct {
|
||||
waits map[pgraph.Vertex]*sync.WaitGroup // wg for the Worker func
|
||||
wlock *sync.Mutex // lock around waits map
|
||||
|
||||
mlock *sync.Mutex // metas lock
|
||||
metas map[engine.ResPtrUID]*engine.MetaState // meta state
|
||||
|
||||
slock *sync.Mutex // semaphore lock
|
||||
semas map[string]*semaphore.Semaphore
|
||||
|
||||
@@ -86,6 +114,9 @@ func (obj *Engine) Init() error {
|
||||
obj.waits = make(map[pgraph.Vertex]*sync.WaitGroup)
|
||||
obj.wlock = &sync.Mutex{}
|
||||
|
||||
obj.mlock = &sync.Mutex{}
|
||||
obj.metas = make(map[engine.ResPtrUID]*engine.MetaState)
|
||||
|
||||
obj.slock = &sync.Mutex{}
|
||||
obj.semas = make(map[string]*semaphore.Semaphore)
|
||||
|
||||
@@ -127,7 +158,7 @@ func (obj *Engine) Validate() error {
|
||||
}
|
||||
|
||||
if err := engine.Validate(res); err != nil {
|
||||
return errwrap.Wrapf(err, "the Res did not Validate")
|
||||
return fmt.Errorf("%s did not Validate: %v", res, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -144,8 +175,21 @@ func (obj *Engine) Apply(fn func(*pgraph.Graph) error) error {
|
||||
// it errors, then the running graph wasn't changed. It is recommended that you
|
||||
// pause the engine before running this, and resume it after you're done.
|
||||
func (obj *Engine) Commit() error {
|
||||
// It would be safer to lock this, but it would be slower and mask bugs.
|
||||
//obj.mutex.Lock()
|
||||
//defer obj.mutex.Unlock()
|
||||
|
||||
// TODO: Does this hurt performance or graph changes ?
|
||||
|
||||
activeMetas := make(map[engine.ResPtrUID]struct{})
|
||||
for vertex := range obj.state {
|
||||
res, ok := vertex.(engine.Res)
|
||||
if !ok { // should not happen, previously validated
|
||||
return fmt.Errorf("not a Res")
|
||||
}
|
||||
activeMetas[engine.PtrUID(res)] = struct{}{} // add
|
||||
}
|
||||
|
||||
start := []func() error{} // functions to run after graphsync to start...
|
||||
vertexAddFn := func(vertex pgraph.Vertex) error {
|
||||
// some of these validation steps happen before this Commit step
|
||||
@@ -163,20 +207,22 @@ func (obj *Engine) Commit() error {
|
||||
return fmt.Errorf("the Res state already exists")
|
||||
}
|
||||
|
||||
activeMetas[engine.PtrUID(res)] = struct{}{} // add
|
||||
|
||||
if obj.Debug {
|
||||
obj.Logf("Validate(%s)", res)
|
||||
}
|
||||
err := engine.Validate(res)
|
||||
if obj.Debug {
|
||||
obj.Logf("Validate(%s): Return(%+v)", res, err)
|
||||
obj.Logf("Validate(%s): Return(%s)", res, engineUtil.CleanError(err))
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "the Res did not Validate")
|
||||
}
|
||||
|
||||
// FIXME: is res.Name() sufficiently unique to use as a UID here?
|
||||
pathUID := fmt.Sprintf("%s-%s", res.Kind(), res.Name())
|
||||
statePrefix := fmt.Sprintf("%s/", path.Join(obj.Prefix, "state", pathUID))
|
||||
pathUID := engineUtil.ResPathUID(res)
|
||||
statePrefix := fmt.Sprintf("%s/", path.Join(obj.statePrefix(), pathUID))
|
||||
|
||||
// don't create this unless it *will* be used
|
||||
//if err := os.MkdirAll(statePrefix, 0770); err != nil {
|
||||
// return errwrap.Wrapf(err, "can't create state prefix")
|
||||
@@ -184,15 +230,17 @@ func (obj *Engine) Commit() error {
|
||||
|
||||
obj.waits[vertex] = &sync.WaitGroup{}
|
||||
obj.state[vertex] = &State{
|
||||
//Graph: obj.graph, // TODO: what happens if we swap the graph?
|
||||
Graph: obj.graph, // Update if we swap the graph!
|
||||
Vertex: vertex,
|
||||
|
||||
Program: obj.Program,
|
||||
Version: obj.Version,
|
||||
Hostname: obj.Hostname,
|
||||
|
||||
//Converger: obj.Converger,
|
||||
Local: obj.Local,
|
||||
World: obj.World,
|
||||
Prefix: statePrefix,
|
||||
//Converger: obj.Converger,
|
||||
|
||||
Debug: obj.Debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
@@ -220,10 +268,18 @@ func (obj *Engine) Commit() error {
|
||||
obj.wlock.Unlock()
|
||||
}()
|
||||
|
||||
obj.Logf("Worker(%s)", v)
|
||||
if obj.Debug || true {
|
||||
obj.Logf("%s: Working...", v)
|
||||
}
|
||||
// contains the Watch and CheckApply loops
|
||||
err := obj.Worker(v)
|
||||
obj.Logf("Worker(%s): Exited(%+v)", v, err)
|
||||
if obj.Debug || true {
|
||||
if s := engineUtil.CleanError(err); err != nil {
|
||||
obj.Logf("%s: Error: %s", v, s)
|
||||
} else {
|
||||
obj.Logf("%s: Exited...", v)
|
||||
}
|
||||
}
|
||||
obj.state[v].workerErr = err // store the error
|
||||
// If the Rewatch metaparam is true, then this will get
|
||||
// restarted if we do a graph cmp swap. This is why the
|
||||
@@ -238,15 +294,21 @@ func (obj *Engine) Commit() error {
|
||||
|
||||
free := []func() error{} // functions to run after graphsync to reset...
|
||||
vertexRemoveFn := func(vertex pgraph.Vertex) error {
|
||||
res, ok := vertex.(engine.Res)
|
||||
if !ok { // should not happen, previously validated
|
||||
return fmt.Errorf("not a Res")
|
||||
}
|
||||
delete(activeMetas, engine.PtrUID(res))
|
||||
|
||||
// wait for exit before starting new graph!
|
||||
close(obj.state[vertex].removeDone) // causes doneChan to close
|
||||
obj.state[vertex].Resume() // unblock from resume
|
||||
obj.waits[vertex].Wait() // sync
|
||||
close(obj.state[vertex].removeDone) // causes doneCtx to cancel
|
||||
close(obj.state[vertex].resumeSignal) // unblock (it only closes here)
|
||||
obj.waits[vertex].Wait() // sync
|
||||
|
||||
// close the state and resource
|
||||
// FIXME: will this mess up the sync and block the engine?
|
||||
if err := obj.state[vertex].Close(); err != nil {
|
||||
return errwrap.Wrapf(err, "the Res did not Close")
|
||||
if err := obj.state[vertex].Cleanup(); err != nil {
|
||||
return errwrap.Wrapf(err, "the Res did not Cleanup")
|
||||
}
|
||||
|
||||
// delete to free up memory from old graphs
|
||||
@@ -302,6 +364,23 @@ func (obj *Engine) Commit() error {
|
||||
if err := obj.graph.GraphSync(obj.nextGraph, vertexCmpFn, vertexAddFn, vertexRemoveFn, engine.EdgeCmpFn); err != nil {
|
||||
return errwrap.Wrapf(err, "error running graph sync")
|
||||
}
|
||||
|
||||
// This happens after GraphSync when vertexRemoveFn and vertexAddFn are
|
||||
// done running. Those two modified the activeMetas map. It's important
|
||||
// that vertexRemoveFn runs before vertexAddFn, but GraphSync guarantees
|
||||
// that, and it would be kind of illogical to not run things that way.
|
||||
metaGC := make(map[engine.ResPtrUID]struct{}) // which metas should we garbage collect?
|
||||
obj.mlock.Lock()
|
||||
for ptrUID := range obj.metas {
|
||||
if _, exists := activeMetas[ptrUID]; !exists {
|
||||
metaGC[ptrUID] = struct{}{}
|
||||
}
|
||||
}
|
||||
for ptrUID := range metaGC {
|
||||
delete(obj.metas, ptrUID) // otherwise, this could grow forever
|
||||
}
|
||||
obj.mlock.Unlock()
|
||||
|
||||
// We run these afterwards, so that we don't unnecessarily start anyone
|
||||
// if GraphSync failed in some way. Otherwise we'd have to do clean up!
|
||||
for _, fn := range start {
|
||||
@@ -322,14 +401,14 @@ func (obj *Engine) Commit() error {
|
||||
// the changes that we'd made to the previously primary graph. This is
|
||||
// because this function is meant to atomically swap the graphs safely.
|
||||
|
||||
// TODO: update all the `State` structs with the new Graph pointer
|
||||
//for _, vertex := range obj.graph.Vertices() {
|
||||
// state, exists := obj.state[vertex]
|
||||
// if !exists {
|
||||
// continue
|
||||
// }
|
||||
// state.Graph = obj.graph // update pointer to graph
|
||||
//}
|
||||
// Update all the `State` structs with the new Graph pointer.
|
||||
for _, vertex := range obj.graph.Vertices() {
|
||||
state, exists := obj.state[vertex]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
state.Graph = obj.graph // update pointer to graph
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -337,8 +416,13 @@ func (obj *Engine) Commit() error {
|
||||
// Resume runs the currently active graph. It also un-pauses the graph if it was
|
||||
// paused. Very little that is interesting should happen here. It all happens in
|
||||
// the Commit method. After Commit, new things are already started, but we still
|
||||
// need to Resume any pre-existing resources.
|
||||
// need to Resume any pre-existing resources. Do not call this concurrently with
|
||||
// the Pause method.
|
||||
func (obj *Engine) Resume() error {
|
||||
// It would be safer to lock this, but it would be slower and mask bugs.
|
||||
//obj.mutex.Lock()
|
||||
//defer obj.mutex.Unlock()
|
||||
|
||||
if !obj.paused {
|
||||
return fmt.Errorf("already resumed")
|
||||
}
|
||||
@@ -351,8 +435,22 @@ func (obj *Engine) Resume() error {
|
||||
reversed := pgraph.Reverse(topoSort)
|
||||
|
||||
for _, vertex := range reversed {
|
||||
// The very first resume is skipped as those resources are
|
||||
// already running! We could do that by checking here, but it is
|
||||
// more convenient to just have a state struct field (paused) to
|
||||
// track things for this instead. As a bonus, it helps us know
|
||||
// if a resource is paused or not if we print for debugging.
|
||||
//if !obj.state[vertex].initialStartupDone {
|
||||
// obj.state[vertex].initialStartupDone = true
|
||||
// continue
|
||||
//}
|
||||
|
||||
//obj.state[vertex].starter = (indegree[vertex] == 0)
|
||||
obj.state[vertex].Resume() // doesn't error
|
||||
// This always works because if a resource errored while it was
|
||||
// paused, then we're in the paused state and we can still exit
|
||||
// from there. If a resource errors when we're trying to Pause
|
||||
// then it will only succeed without error if the resource ACKs.
|
||||
}
|
||||
// we wait for everyone to start before exiting!
|
||||
obj.paused = false
|
||||
@@ -373,6 +471,10 @@ func (obj *Engine) SetFastPause() {
|
||||
|
||||
// Pause the active, running graph.
|
||||
func (obj *Engine) Pause(fastPause bool) error {
|
||||
// It would be safer to lock this, but it would be slower and mask bugs.
|
||||
//obj.mutex.Lock()
|
||||
//defer obj.mutex.Unlock()
|
||||
|
||||
if obj.paused {
|
||||
return fmt.Errorf("already paused")
|
||||
}
|
||||
@@ -394,8 +496,10 @@ func (obj *Engine) Pause(fastPause bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close triggers a shutdown. Engine must be already paused before this is run.
|
||||
func (obj *Engine) Close() error {
|
||||
// Shutdown the engine. Engine must be already paused before this is run. It is
|
||||
// actually just a Load of an empty graph and a Commit. It waits for all the
|
||||
// resources to exit before returning.
|
||||
func (obj *Engine) Shutdown() error {
|
||||
emptyGraph, reterr := pgraph.NewGraph("empty")
|
||||
|
||||
// this is a graph switch (graph sync) that switches to an empty graph!
|
||||
@@ -416,3 +520,8 @@ func (obj *Engine) Close() error {
|
||||
func (obj *Engine) Graph() *pgraph.Graph {
|
||||
return obj.graph
|
||||
}
|
||||
|
||||
// statePrefix returns the dir where all the resource state is stored locally.
|
||||
func (obj *Engine) statePrefix() string {
|
||||
return fmt.Sprintf("%s/", path.Join(obj.Prefix, StateDir))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,9 +13,21 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// +build !root
|
||||
//go:build !root
|
||||
|
||||
package graph
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
|
||||
311
engine/graph/reverse.go
Normal file
311
engine/graph/reverse.go
Normal file
@@ -0,0 +1,311 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// ReverseFile is the file name in the resource state dir where any
|
||||
// reversal information is stored.
|
||||
ReverseFile = "reverse"
|
||||
|
||||
// ReversePerm is the permissions mode used to create the ReverseFile.
|
||||
ReversePerm = 0600
|
||||
)
|
||||
|
||||
// Reversals adds the reversals onto the loaded graph. This should happen last,
|
||||
// and before Commit.
|
||||
func (obj *Engine) Reversals() error {
|
||||
if obj.nextGraph == nil {
|
||||
return fmt.Errorf("there is no active graph to add reversals to")
|
||||
}
|
||||
|
||||
// Initially get all of the reversals to seek out all possible errors.
|
||||
// XXX: The engine needs to know where data might have been stored if we
|
||||
// XXX: want to potentially allow alternate read/write paths, like etcd.
|
||||
// XXX: In this scenario, we'd have to store a token somewhere to let us
|
||||
// XXX: know to look elsewhere for the special ReversalList read method.
|
||||
data, err := obj.ReversalList() // (map[string]string, error)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "the reversals had errors")
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil // end early
|
||||
}
|
||||
|
||||
resMatch := func(r1, r2 engine.Res) bool { // simple match on UID only!
|
||||
if r1.Kind() != r2.Kind() {
|
||||
return false
|
||||
}
|
||||
if r1.Name() != r2.Name() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
resInList := func(needle engine.Res, haystack []engine.Res) bool {
|
||||
for _, res := range haystack {
|
||||
if resMatch(needle, res) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if obj.Debug {
|
||||
obj.Logf("decoding %d reversals...", len(data))
|
||||
}
|
||||
resources := []engine.Res{}
|
||||
|
||||
// do this in a sorted order so that it errors deterministically
|
||||
sorted := []string{}
|
||||
for key := range data {
|
||||
sorted = append(sorted, key)
|
||||
}
|
||||
sort.Strings(sorted)
|
||||
for _, key := range sorted {
|
||||
val := data[key]
|
||||
// XXX: replace this ResToB64 method with one that stores it in
|
||||
// a human readable format, in case someone wants to hack and
|
||||
// edit it manually.
|
||||
// XXX: we probably want this to be YAML, it works with the diff
|
||||
// too...
|
||||
r, err := engineUtil.B64ToRes(val)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "error decoding res with UID: `%s`", key)
|
||||
}
|
||||
|
||||
res, ok := r.(engine.ReversibleRes)
|
||||
if !ok {
|
||||
// this requirement is here to keep things simpler...
|
||||
return errwrap.Wrapf(err, "decoded res with UID: `%s` was not reversible", key)
|
||||
}
|
||||
|
||||
matchFn := func(vertex pgraph.Vertex) (bool, error) {
|
||||
r, ok := vertex.(engine.Res)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("not a Res")
|
||||
}
|
||||
if !resMatch(r, res) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// FIXME: not efficient, we could build a cache-map first
|
||||
vertex, err := obj.nextGraph.VertexMatchFn(matchFn) // (Vertex, error)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "error searching graph for match")
|
||||
}
|
||||
if vertex != nil { // found one!
|
||||
continue // it doesn't need reversing yet
|
||||
}
|
||||
|
||||
// TODO: check for (incompatible?) duplicates instead
|
||||
if resInList(res, resources) { // we've already got this one...
|
||||
continue
|
||||
}
|
||||
|
||||
// We set this in two different places to be safe. It ensures
|
||||
// that we erase the reversal state file after we've used it.
|
||||
res.ReversibleMeta().Reversal = true // set this for later...
|
||||
|
||||
resources = append(resources, res)
|
||||
}
|
||||
|
||||
if len(resources) == 0 {
|
||||
return nil // end early
|
||||
}
|
||||
|
||||
// Now that we've passed the chance of any errors, we modify the graph.
|
||||
obj.Logf("adding %d reversals...", len(resources))
|
||||
for _, res := range resources {
|
||||
obj.nextGraph.AddVertex(res)
|
||||
}
|
||||
// TODO: Do we want a way for stored reversals to add edges too?
|
||||
|
||||
// It would be great to ensure we didn't add any graph cycles here, but
|
||||
// instead of checking now, we'll move the check into the main loop.
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReversalList returns all the available pending reversal data on this host. It
|
||||
// can then be decoded by whatever method is appropriate for.
|
||||
func (obj *Engine) ReversalList() (map[string]string, error) {
|
||||
result := make(map[string]string) // some key to contents
|
||||
|
||||
dir := obj.statePrefix() // loop through this dir...
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, errwrap.Wrapf(err, "error reading list of state dirs")
|
||||
} else if err != nil {
|
||||
return result, nil // nothing found, no state dir exists yet
|
||||
}
|
||||
|
||||
for _, x := range files {
|
||||
key := x.Name() // some uid for the resource
|
||||
file := path.Join(dir, key, ReverseFile)
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, errwrap.Wrapf(err, "could not read reverse file: %s", file)
|
||||
} else if err != nil {
|
||||
continue // file does not exist, skip
|
||||
}
|
||||
|
||||
// file exists!
|
||||
str := string(content)
|
||||
result[key] = str // save
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ReversalInit performs the reversal initialization steps if necessary for this
|
||||
// resource.
|
||||
func (obj *State) ReversalInit() error {
|
||||
res, ok := obj.Vertex.(engine.ReversibleRes)
|
||||
if !ok {
|
||||
return nil // nothing to do
|
||||
}
|
||||
|
||||
if res.ReversibleMeta().Disabled {
|
||||
return nil // nothing to do, reversal isn't enabled
|
||||
}
|
||||
|
||||
// If the reversal is enabled, but we are the result of a previous
|
||||
// reversal, then this will overwrite that older reversal request, and
|
||||
// our resource should be designed to deal with that. This happens if we
|
||||
// return a reversible resource as the reverse of a resource that was
|
||||
// reversed. It's probably fairly rare.
|
||||
if res.ReversibleMeta().Reversal {
|
||||
obj.Logf("triangle reversal") // warn!
|
||||
}
|
||||
|
||||
r, err := res.Reversed()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not reverse: %s", res.String())
|
||||
}
|
||||
if r == nil {
|
||||
return nil // this can't be reversed, or isn't implemented here
|
||||
}
|
||||
|
||||
// We set this in two different places to be safe. It ensures that we
|
||||
// erase the reversal state file after we've used it.
|
||||
r.ReversibleMeta().Reversal = true // set this for later...
|
||||
|
||||
// XXX: replace this ResToB64 method with one that stores it in a human
|
||||
// readable format, in case someone wants to hack and edit it manually.
|
||||
// XXX: we probably want this to be YAML, it works with the diff too...
|
||||
str, err := engineUtil.ResToB64(r)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not encode: %s", res.String())
|
||||
}
|
||||
|
||||
// TODO: put this method on traits.Reversible as part of the interface?
|
||||
return obj.ReversalWrite(str, res.ReversibleMeta().Overwrite) // Store!
|
||||
}
|
||||
|
||||
// ReversalCleanup performs the reversal shutdown steps if necessary for this
|
||||
// resource.
|
||||
func (obj *State) ReversalCleanup() error {
|
||||
res, ok := obj.Vertex.(engine.ReversibleRes)
|
||||
if !ok {
|
||||
return nil // nothing to do
|
||||
}
|
||||
|
||||
// Don't check res.ReversibleMeta().Disabled because we're removing the
|
||||
// previous one. That value only applies if we're doing a new reversal.
|
||||
|
||||
if !res.ReversibleMeta().Reversal {
|
||||
return nil // nothing to erase, we're not a reversal resource
|
||||
}
|
||||
|
||||
if !obj.isStateOK.Load() { // did we successfully reverse? (mutex RLock/RUnlock)
|
||||
obj.Logf("did not complete reversal") // warn
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: put this method on traits.Reversible as part of the interface?
|
||||
return obj.ReversalDelete() // Erase our reversal instructions.
|
||||
}
|
||||
|
||||
// ReversalWrite stores the reversal state information for this resource.
|
||||
func (obj *State) ReversalWrite(str string, overwrite bool) error {
|
||||
dir, err := obj.varDir("") // private version
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not get VarDir for reverse")
|
||||
}
|
||||
file := path.Join(dir, ReverseFile) // return a unique file
|
||||
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return errwrap.Wrapf(err, "could not read reverse file: %s", file)
|
||||
}
|
||||
|
||||
// file exists and we shouldn't overwrite if different
|
||||
if err == nil && !overwrite {
|
||||
// compare to existing file
|
||||
oldStr := string(content)
|
||||
if str != oldStr {
|
||||
obj.Logf("existing, pending, reversible resource exists")
|
||||
//obj.Logf("diff:")
|
||||
//obj.Logf("") // TODO: print the diff w/o secret values
|
||||
return fmt.Errorf("existing, pending, reversible resource exists")
|
||||
}
|
||||
}
|
||||
|
||||
return os.WriteFile(file, []byte(str), ReversePerm)
|
||||
}
|
||||
|
||||
// ReversalDelete removes the reversal state information for this resource.
|
||||
func (obj *State) ReversalDelete() error {
|
||||
dir, err := obj.varDir("") // private version
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not get VarDir for reverse")
|
||||
}
|
||||
file := path.Join(dir, ReverseFile) // return a unique file
|
||||
|
||||
// FIXME: why do we see these removals when there isn't a state file?
|
||||
if err = os.Remove(file); os.IsNotExist(err) {
|
||||
return nil // ignore missing files
|
||||
}
|
||||
|
||||
return errwrap.Wrapf(err, "could not remove reverse state file")
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,9 +13,21 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// +build !root
|
||||
//go:build !root
|
||||
|
||||
package graph
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,33 +13,114 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
// RecvFn represents a custom Recv function which can be used in place of the
|
||||
// stock, built-in one. This is needed if we want to receive from a different
|
||||
// resource data source than our own. (Only for special occasions of course!)
|
||||
type RecvFn func(engine.RecvableRes) (map[string]*engine.Send, error)
|
||||
|
||||
// SendRecv pulls in the sent values into the receive slots. It is called by the
|
||||
// receiver and must be given as input the full resource struct to receive on.
|
||||
// It applies the loaded values to the resource.
|
||||
func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) {
|
||||
recv := res.Recv()
|
||||
if obj.Debug {
|
||||
// NOTE: this could expose private resource data like passwords
|
||||
obj.Logf("%s: SendRecv: %+v", res, recv)
|
||||
// It applies the loaded values to the resource. It is called recursively, as it
|
||||
// recurses into any grouped resources found within the first receiver. It
|
||||
// returns a map of resource pointer, to resource field key, to changed boolean.
|
||||
func SendRecv(res engine.RecvableRes, fn RecvFn) (map[engine.RecvableRes]map[string]*engine.Send, error) {
|
||||
updated := make(map[engine.RecvableRes]map[string]*engine.Send) // list of updated keys
|
||||
if groupableRes, ok := res.(engine.GroupableRes); ok {
|
||||
for _, x := range groupableRes.GetGroup() { // grouped elements
|
||||
recvableRes, ok := x.(engine.RecvableRes)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
//if obj.Debug {
|
||||
// obj.Logf("SendRecv: %s: grouped: %s", res, x) // receiving here
|
||||
//}
|
||||
// We need to recurse here so that autogrouped resources
|
||||
// inside autogrouped resources would work... In case we
|
||||
// work correctly. We just need to make sure that things
|
||||
// are grouped in the correct order, but that is not our
|
||||
// problem! Recurse and merge in the changed results...
|
||||
innerUpdated, err := SendRecv(recvableRes, fn)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "recursive SendRecv error")
|
||||
}
|
||||
for r, m := range innerUpdated { // res ptr, map
|
||||
if _, exists := updated[r]; !exists {
|
||||
updated[r] = make(map[string]*engine.Send)
|
||||
}
|
||||
for s, send := range m { // map[string]*engine.Send
|
||||
b := send.Changed
|
||||
// don't overwrite in case one exists...
|
||||
if old, exists := updated[r][s]; exists {
|
||||
b = b || old.Changed // unlikely i think
|
||||
}
|
||||
if _, exists := updated[r][s]; !exists {
|
||||
newSend := &engine.Send{
|
||||
Res: send.Res,
|
||||
Key: send.Key,
|
||||
Changed: b,
|
||||
}
|
||||
updated[r][s] = newSend
|
||||
}
|
||||
updated[r][s].Changed = b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var updated = make(map[string]bool) // list of updated keys
|
||||
|
||||
var err error
|
||||
for k, v := range recv {
|
||||
updated[k] = false // default
|
||||
v.Changed = false // reset to the default
|
||||
recv := res.Recv()
|
||||
if fn != nil {
|
||||
recv, err = fn(res) // use a custom Recv function
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
keys := []string{}
|
||||
for k := range recv { // map[string]*Send
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
//if obj.Debug && len(keys) > 0 {
|
||||
// // NOTE: this could expose private resource data like passwords
|
||||
// obj.Logf("SendRecv: %s recv: %+v", res, strings.Join(keys, ", "))
|
||||
//}
|
||||
for k, v := range recv { // map[string]*Send
|
||||
// v.Res // SendableRes // a handle to the resource which is sending a value
|
||||
// v.Key // string // the key in the resource that we're sending
|
||||
if _, exists := updated[res]; !exists {
|
||||
updated[res] = make(map[string]*engine.Send)
|
||||
}
|
||||
|
||||
//updated[res][k] = false // default
|
||||
v.Changed = false // reset to the default
|
||||
updated[res][k] = v // default
|
||||
|
||||
var st interface{} = v.Res // old style direct send/recv
|
||||
if true { // new style send/recv API
|
||||
@@ -71,7 +152,7 @@ func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) {
|
||||
}
|
||||
|
||||
obj1 := reflect.Indirect(reflect.ValueOf(st))
|
||||
type1 := obj1.Type()
|
||||
//type1 := obj1.Type()
|
||||
value1 := obj1.FieldByName(key1)
|
||||
kind1 := value1.Kind()
|
||||
|
||||
@@ -89,52 +170,110 @@ func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) {
|
||||
}
|
||||
|
||||
obj2 := reflect.Indirect(reflect.ValueOf(res)) // pass in full struct
|
||||
type2 := obj2.Type()
|
||||
//type2 := obj2.Type()
|
||||
value2 := obj2.FieldByName(key2)
|
||||
kind2 := value2.Kind()
|
||||
|
||||
if obj.Debug {
|
||||
obj.Logf("Send(%s) has %v: %v", type1, kind1, value1)
|
||||
obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
|
||||
//orig := value1
|
||||
dest := value2 // save the o.g. because we need the real dest!
|
||||
|
||||
// NOTE: Reminder: obj1 comes from st and it is the *<Res>Sends
|
||||
// struct which contains whichever fields that resource sends.
|
||||
// For example, this might be *TestSends for the Test resource.
|
||||
// The receiver is obj2 and that is actually the resource struct
|
||||
// which is a *<Res> and which gets it's fields directly set on.
|
||||
// For example, this might be *TestRes for the Test resource.
|
||||
//fmt.Printf("obj1(%T): %+v\n", obj1, obj1)
|
||||
//fmt.Printf("obj2(%T): %+v\n", obj2, obj2)
|
||||
// Lastly, remember that many of the type incompatibilities are
|
||||
// caught during type unification, and so we might have overly
|
||||
// relaxed the checks here and something could slip by. If we
|
||||
// find something, this code will need new checks added back.
|
||||
|
||||
// Here we unpack one-level, and then leave the complex stuff
|
||||
// for the Into() method below.
|
||||
// for kind1 == reflect.Interface || kind1 == reflect.Ptr // wrong
|
||||
// if kind1 == reflect.Interface || kind1 == reflect.Ptr // wrong
|
||||
// for kind1 == reflect.Interface // wrong
|
||||
if kind1 == reflect.Interface {
|
||||
value1 = value1.Elem() // un-nest one interface
|
||||
kind1 = value1.Kind()
|
||||
}
|
||||
|
||||
// i think we probably want the same kind, at least for now...
|
||||
if kind1 != kind2 {
|
||||
e := fmt.Errorf("kind mismatch between %s: %s and %s: %s", v.Res, kind1, res, kind2)
|
||||
err = errwrap.Append(err, e) // list of errors
|
||||
continue
|
||||
// This second block is identical, but it's just accidentally
|
||||
// symmetrical. The types of input structs are different shapes.
|
||||
// for kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
|
||||
// if kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
|
||||
// for kind2 == reflect.Interface // wrong
|
||||
if kind2 == reflect.Interface {
|
||||
value2 = value2.Elem() // un-nest one interface
|
||||
kind2 = value2.Kind()
|
||||
}
|
||||
|
||||
// if the types don't match, we can't use send->recv
|
||||
// FIXME: do we want to relax this for string -> *string ?
|
||||
if e := TypeCmp(value1, value2); e != nil {
|
||||
e := errwrap.Wrapf(e, "type mismatch between %s and %s", v.Res, res)
|
||||
err = errwrap.Append(err, e) // list of errors
|
||||
continue
|
||||
}
|
||||
//if obj.Debug {
|
||||
// obj.Logf("Send(%s) has %v: %v", type1, kind1, value1)
|
||||
// obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
|
||||
//}
|
||||
|
||||
// Skip this check in favour of the more complex Into() below...
|
||||
//if kind1 != kind2 {
|
||||
// e := fmt.Errorf("send/recv kind mismatch between %s: %s and %s: %s", v.Res, kind1, res, kind2)
|
||||
// err = errwrap.Append(err, e) // list of errors
|
||||
// continue
|
||||
//}
|
||||
|
||||
// Skip this check in favour of the more complex Into() below...
|
||||
//if e := TypeCmp(value1, value2); e != nil {
|
||||
// e := errwrap.Wrapf(e, "type mismatch between %s and %s", v.Res, res)
|
||||
// err = errwrap.Append(err, e) // list of errors
|
||||
// continue
|
||||
//}
|
||||
|
||||
// if we can't set, then well this is pointless!
|
||||
if !value2.CanSet() {
|
||||
if !dest.CanSet() {
|
||||
e := fmt.Errorf("can't set %s.%s", res, k)
|
||||
err = errwrap.Append(err, e) // list of errors
|
||||
continue
|
||||
}
|
||||
|
||||
// if we can't interface, we can't compare...
|
||||
if !value1.CanInterface() || !value2.CanInterface() {
|
||||
if !value1.CanInterface() {
|
||||
e := fmt.Errorf("can't interface %s.%s", v.Res, v.Key)
|
||||
err = errwrap.Append(err, e) // list of errors
|
||||
continue
|
||||
}
|
||||
if !value2.CanInterface() {
|
||||
e := fmt.Errorf("can't interface %s.%s", res, k)
|
||||
err = errwrap.Append(err, e) // list of errors
|
||||
continue
|
||||
}
|
||||
|
||||
// if the values aren't equal, we're changing the receiver
|
||||
if !reflect.DeepEqual(value1.Interface(), value2.Interface()) {
|
||||
// TODO: can we catch the panics here in case they happen?
|
||||
value2.Set(value1) // do it for all types that match
|
||||
updated[k] = true // we updated this key!
|
||||
v.Changed = true // tag this key as updated!
|
||||
obj.Logf("SendRecv: %s.%s -> %s.%s", v.Res, v.Key, res, k)
|
||||
if reflect.DeepEqual(value1.Interface(), value2.Interface()) {
|
||||
continue // skip as they're the same, no error needed
|
||||
}
|
||||
|
||||
// TODO: can we catch the panics here in case they happen?
|
||||
|
||||
fv, e := types.ValueOf(value1)
|
||||
if e != nil {
|
||||
e := errwrap.Wrapf(e, "bad value %s.%s", v.Res, v.Key)
|
||||
err = errwrap.Append(err, e) // list of errors
|
||||
continue
|
||||
}
|
||||
|
||||
// mutate the struct field dest with the mcl data in fv
|
||||
if e := types.Into(fv, dest); e != nil {
|
||||
// runtime error, probably from using value res
|
||||
e := errwrap.Wrapf(e, "mismatch: %s.%s (%s) -> %s.%s (%s)", v.Res, v.Key, kind1, res, k, kind2)
|
||||
err = errwrap.Append(err, e) // list of errors
|
||||
continue
|
||||
}
|
||||
//dest.Set(orig) // do it for all types that match
|
||||
//updated[res][k] = true // we updated this key!
|
||||
v.Changed = true // tag this key as updated!
|
||||
updated[res][k] = v // we updated this key!
|
||||
//obj.Logf("SendRecv: %s.%s -> %s.%s (%+v)", v.Res, v.Key, res, k, fv) // fv may be private data
|
||||
}
|
||||
return updated, err
|
||||
}
|
||||
@@ -150,3 +289,19 @@ func TypeCmp(a, b reflect.Value) error {
|
||||
|
||||
return nil // identical Type()'s
|
||||
}
|
||||
|
||||
// UpdatedStrings returns a list of strings showing what was updated after a
|
||||
// Send/Recv run returned the updated datastructure. This is useful for logs.
|
||||
func UpdatedStrings(updated map[engine.RecvableRes]map[string]*engine.Send) []string {
|
||||
out := []string{}
|
||||
for r, m := range updated { // map[engine.RecvableRes]map[string]*engine.Send
|
||||
for s, send := range m {
|
||||
if !send.Changed {
|
||||
continue
|
||||
}
|
||||
x := fmt.Sprintf("%v.%s -> %v.%s", send.Res, send.Key, r, s)
|
||||
out = append(out, x)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,26 +13,41 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/converger"
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
"github.com/purpleidea/mgmt/engine/local"
|
||||
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
// State stores some state about the resource it is mapped to.
|
||||
type State struct {
|
||||
// Graph is a pointer to the graph that this vertex is part of.
|
||||
//Graph pgraph.Graph
|
||||
Graph *pgraph.Graph
|
||||
|
||||
// Vertex is the pointer in the graph that this state corresponds to. It
|
||||
// can be converted to a `Res` if necessary.
|
||||
@@ -40,39 +55,51 @@ type State struct {
|
||||
Vertex pgraph.Vertex
|
||||
|
||||
Program string
|
||||
Version string
|
||||
Hostname string
|
||||
World engine.World
|
||||
|
||||
//Converger *converger.Coordinator
|
||||
|
||||
Local *local.API
|
||||
World engine.World
|
||||
|
||||
// Prefix is a unique directory prefix which can be used. It should be
|
||||
// created if needed.
|
||||
Prefix string
|
||||
|
||||
//Converger *converger.Coordinator
|
||||
|
||||
// Debug turns on additional output and behaviours.
|
||||
Debug bool
|
||||
|
||||
// Logf is the logging function that should be used to display messages.
|
||||
Logf func(format string, v ...interface{})
|
||||
|
||||
timestamp int64 // last updated timestamp
|
||||
isStateOK bool // is state OK or do we need to run CheckApply ?
|
||||
workerErr error // did the Worker error?
|
||||
timestamp int64 // last updated timestamp
|
||||
isStateOK *atomic.Bool // is state OK or do we need to run CheckApply ?
|
||||
workerErr error // did the Worker error?
|
||||
|
||||
// doneChan closes when Watch should shut down. When any of the
|
||||
mutex *sync.RWMutex // used for editing state properties
|
||||
|
||||
// doneCtx is cancelled when Watch should shut down. When any of the
|
||||
// following channels close, it causes this to close.
|
||||
doneChan chan struct{}
|
||||
doneCtx context.Context
|
||||
|
||||
// doneCtxCancel is the cancel function for doneCtx.
|
||||
doneCtxCancel func()
|
||||
|
||||
// processDone is closed when the Process/CheckApply function fails
|
||||
// permanently, and wants to cause Watch to exit.
|
||||
processDone chan struct{}
|
||||
// watchDone is closed when the Watch function fails permanently, and we
|
||||
// close this to signal we should definitely exit. (Often redundant.)
|
||||
watchDone chan struct{} // could be shared with limitDone
|
||||
watchDone chan struct{} // could be shared with limitDone or retryDone
|
||||
// limitDone is closed when the Watch function fails permanently, and we
|
||||
// close this to signal we should definitely exit. This happens inside
|
||||
// of the limit loop of the Process section of Worker.
|
||||
limitDone chan struct{} // could be shared with watchDone
|
||||
limitDone chan struct{} // could be shared with watchDone or retryDone
|
||||
// retryDone is closed when the Watch function fails permanently, and we
|
||||
// close this to signal we should definitely exit. This happens inside
|
||||
// of the retry loop of the Process section of Worker.
|
||||
retryDone chan struct{} // could be shared with watchDone or limitDone
|
||||
// removeDone is closed when the vertexRemoveFn method asks for an exit.
|
||||
// This happens when we're switching graphs. The switch to an "empty" is
|
||||
// the equivalent of asking for a final shutdown.
|
||||
@@ -94,14 +121,15 @@ type State struct {
|
||||
// to send on since it is buffered.
|
||||
pokeChan chan struct{} // outgoing from resource
|
||||
|
||||
// paused represents if this particular res is paused or not.
|
||||
// paused represents if this particular res is paused or not. This is
|
||||
// primarily used to avoid running an unnecessary Resume on the first
|
||||
// run of this resource.
|
||||
paused bool
|
||||
// pauseSignal closes to request a pause of this resource.
|
||||
// pauseSignal receives a message to request a pause of this resource.
|
||||
pauseSignal chan struct{}
|
||||
// resumeSignal closes to request a resume of this resource.
|
||||
// resumeSignal receives a message to resume this resource. The channel
|
||||
// closes when the resource is removed from the graph.
|
||||
resumeSignal chan struct{}
|
||||
// pausedAck is used to send an ack message saying that we've paused.
|
||||
pausedAck *util.EasyAck
|
||||
|
||||
wg *sync.WaitGroup // used for all vertex specific processes
|
||||
|
||||
@@ -130,11 +158,15 @@ func (obj *State) Init() error {
|
||||
return fmt.Errorf("the Logf function is missing")
|
||||
}
|
||||
|
||||
obj.doneChan = make(chan struct{})
|
||||
obj.isStateOK = &atomic.Bool{}
|
||||
|
||||
obj.mutex = &sync.RWMutex{}
|
||||
obj.doneCtx, obj.doneCtxCancel = context.WithCancel(context.Background())
|
||||
|
||||
obj.processDone = make(chan struct{})
|
||||
obj.watchDone = make(chan struct{})
|
||||
obj.limitDone = make(chan struct{})
|
||||
obj.retryDone = make(chan struct{})
|
||||
obj.removeDone = make(chan struct{})
|
||||
obj.eventsDone = make(chan struct{})
|
||||
|
||||
@@ -144,8 +176,7 @@ func (obj *State) Init() error {
|
||||
|
||||
//obj.paused = false // starts off as started
|
||||
obj.pauseSignal = make(chan struct{})
|
||||
//obj.resumeSignal = make(chan struct{}) // happens on pause
|
||||
//obj.pausedAck = util.NewEasyAck() // happens on pause
|
||||
obj.resumeSignal = make(chan struct{})
|
||||
|
||||
obj.wg = &sync.WaitGroup{}
|
||||
|
||||
@@ -154,12 +185,12 @@ func (obj *State) Init() error {
|
||||
|
||||
obj.init = &engine.Init{
|
||||
Program: obj.Program,
|
||||
Version: obj.Version,
|
||||
Hostname: obj.Hostname,
|
||||
|
||||
// Watch:
|
||||
Running: obj.event,
|
||||
Event: obj.event,
|
||||
Done: obj.doneChan,
|
||||
|
||||
// CheckApply:
|
||||
Refresh: func() bool {
|
||||
@@ -169,33 +200,72 @@ func (obj *State) Init() error {
|
||||
}
|
||||
return res.Refresh()
|
||||
},
|
||||
Send: func(st interface{}) error {
|
||||
res, ok := obj.Vertex.(engine.SendableRes)
|
||||
if !ok {
|
||||
panic("res does not support the Sendable trait")
|
||||
}
|
||||
// XXX: type check this
|
||||
//expected := res.Sends()
|
||||
//if err := XXX_TYPE_CHECK(expected, st); err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
return res.Send(st) // send the struct
|
||||
},
|
||||
Recv: func() map[string]*engine.Send { // TODO: change this API?
|
||||
res, ok := obj.Vertex.(engine.RecvableRes)
|
||||
if !ok {
|
||||
panic("res does not support the Recvable trait")
|
||||
Send: engine.GenerateSendFunc(res),
|
||||
Recv: engine.GenerateRecvFunc(res),
|
||||
|
||||
// FIXME: pass in a safe, limited query func instead?
|
||||
// TODO: not implemented, use FilteredGraph
|
||||
//Graph: func() *pgraph.Graph {
|
||||
// _, ok := obj.Vertex.(engine.CanGraphQueryRes)
|
||||
// if !ok {
|
||||
// panic("res does not support the GraphQuery trait")
|
||||
// }
|
||||
// return obj.Graph // we return in a func so it's fresh!
|
||||
//},
|
||||
|
||||
FilteredGraph: func() (*pgraph.Graph, error) {
|
||||
graph, err := pgraph.NewGraph("filtered")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Recv()
|
||||
|
||||
// filter graph and build a new one...
|
||||
adjacency := obj.Graph.Adjacency()
|
||||
for v1 := range adjacency {
|
||||
// check we're allowed
|
||||
r1, ok := v1.(engine.GraphQueryableRes)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// pass in information on requestor...
|
||||
if err := r1.GraphQueryAllowed(
|
||||
engine.GraphQueryableOptionKind(res.Kind()),
|
||||
engine.GraphQueryableOptionName(res.Name()),
|
||||
// TODO: add more information...
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
graph.AddVertex(v1)
|
||||
|
||||
for v2, edge := range adjacency[v1] {
|
||||
r2, ok := v2.(engine.GraphQueryableRes)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// pass in information on requestor...
|
||||
if err := r2.GraphQueryAllowed(
|
||||
engine.GraphQueryableOptionKind(res.Kind()),
|
||||
engine.GraphQueryableOptionName(res.Name()),
|
||||
// TODO: add more information...
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
//graph.AddVertex(v2) // redundant
|
||||
graph.AddEdge(v1, v2, edge)
|
||||
}
|
||||
}
|
||||
|
||||
return graph, nil // we return in a func so it's fresh!
|
||||
},
|
||||
|
||||
Local: obj.Local,
|
||||
World: obj.World,
|
||||
VarDir: obj.varDir,
|
||||
|
||||
Debug: obj.Debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
obj.Logf("resource: "+format, v...)
|
||||
obj.Logf(format, v...)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -203,9 +273,15 @@ func (obj *State) Init() error {
|
||||
if obj.Debug {
|
||||
obj.Logf("Init(%s)", res)
|
||||
}
|
||||
|
||||
// write the reverse request to the disk...
|
||||
if err := obj.ReversalInit(); err != nil {
|
||||
return err // TODO: test this code path...
|
||||
}
|
||||
|
||||
err := res.Init(obj.init)
|
||||
if obj.Debug {
|
||||
obj.Logf("Init(%s): Return(%+v)", res, err)
|
||||
obj.Logf("Init(%s): Return(%s)", res, engineUtil.CleanError(err))
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not Init() resource")
|
||||
@@ -214,9 +290,9 @@ func (obj *State) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close shuts down and performs any cleanup. This is most akin to a "post" or
|
||||
// Cleanup shuts down and performs any cleanup. This is most akin to a "post" or
|
||||
// cleanup command as the initiator for closing a vertex happens in graph sync.
|
||||
func (obj *State) Close() error {
|
||||
func (obj *State) Cleanup() error {
|
||||
res, isRes := obj.Vertex.(engine.Res)
|
||||
if !isRes {
|
||||
return fmt.Errorf("vertex is not a Res")
|
||||
@@ -236,12 +312,23 @@ func (obj *State) Close() error {
|
||||
if obj.Debug {
|
||||
obj.Logf("Close(%s)", res)
|
||||
}
|
||||
err := res.Close()
|
||||
if obj.Debug {
|
||||
obj.Logf("Close(%s): Return(%+v)", res, err)
|
||||
|
||||
var reverr error
|
||||
// clear the reverse request from the disk...
|
||||
if err := obj.ReversalCleanup(); err != nil {
|
||||
// TODO: test this code path...
|
||||
// TODO: should this be an error or a warning?
|
||||
reverr = err
|
||||
}
|
||||
|
||||
return err
|
||||
reterr := res.Cleanup()
|
||||
if obj.Debug {
|
||||
obj.Logf("Close(%s): Return(%s)", res, engineUtil.CleanError(reterr))
|
||||
}
|
||||
|
||||
reterr = errwrap.Append(reterr, reverr)
|
||||
|
||||
return reterr
|
||||
}
|
||||
|
||||
// Poke sends a notification on the poke channel. This channel is used to notify
|
||||
@@ -251,37 +338,28 @@ func (obj *State) Close() error {
|
||||
// callers are expected to make sure that they don't leave any of these running
|
||||
// by the time the Worker() shuts down.
|
||||
func (obj *State) Poke() {
|
||||
// redundant
|
||||
//if len(obj.pokeChan) > 0 {
|
||||
// return
|
||||
//}
|
||||
|
||||
select {
|
||||
case obj.pokeChan <- struct{}{}:
|
||||
default: // if chan is now full because more than one poke happened...
|
||||
}
|
||||
}
|
||||
|
||||
// Pause pauses this resource. It should not be called on any already paused
|
||||
// Pause pauses this resource. It must not be called on any already paused
|
||||
// resource. It will block until the resource pauses with an acknowledgment, or
|
||||
// until an exit for that resource is seen. If the latter happens it will error.
|
||||
// It is NOT thread-safe with the Resume() method so only call either one at a
|
||||
// time.
|
||||
// It must not be called concurrently with either the Resume() method or itself,
|
||||
// so only call these one at a time and alternate between the two.
|
||||
func (obj *State) Pause() error {
|
||||
if obj.paused {
|
||||
return fmt.Errorf("already paused")
|
||||
panic("already paused")
|
||||
}
|
||||
|
||||
obj.pausedAck = util.NewEasyAck()
|
||||
obj.resumeSignal = make(chan struct{}) // build the resume signal
|
||||
close(obj.pauseSignal)
|
||||
obj.Poke() // unblock and notice the pause if necessary
|
||||
|
||||
// wait for ack (or exit signal)
|
||||
select {
|
||||
case <-obj.pausedAck.Wait(): // we got it!
|
||||
case obj.pauseSignal <- struct{}{}:
|
||||
// we're paused
|
||||
case <-obj.doneChan:
|
||||
|
||||
case <-obj.doneCtx.Done():
|
||||
return engine.ErrClosed
|
||||
}
|
||||
obj.paused = true
|
||||
@@ -289,23 +367,22 @@ func (obj *State) Pause() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resume unpauses this resource. It can be safely called on a brand-new
|
||||
// resource that has just started running without incident. It is NOT
|
||||
// thread-safe with the Pause() method, so only call either one at a time.
|
||||
// Resume unpauses this resource. It can be safely called once on a brand-new
|
||||
// resource that has just started running, without incident. It must not be
|
||||
// called concurrently with either the Pause() method or itself, so only call
|
||||
// these one at a time and alternate between the two.
|
||||
func (obj *State) Resume() {
|
||||
// TODO: do we need a mutex around Resume?
|
||||
// This paused check prevents unnecessary "resume" calls to the resource
|
||||
// on its first run, since resources start in the running state!
|
||||
if !obj.paused { // no need to unpause brand-new resources
|
||||
return
|
||||
}
|
||||
|
||||
obj.pauseSignal = make(chan struct{}) // rebuild for next pause
|
||||
close(obj.resumeSignal)
|
||||
//obj.Poke() // not needed, we're already waiting for resume
|
||||
select {
|
||||
case obj.resumeSignal <- struct{}{}:
|
||||
}
|
||||
|
||||
obj.paused = false
|
||||
|
||||
// no need to wait for it to resume
|
||||
//return // implied
|
||||
}
|
||||
|
||||
// event is a helper function to send an event to the CheckApply process loop.
|
||||
@@ -317,7 +394,7 @@ func (obj *State) event() {
|
||||
obj.setDirty() // assume we're initially dirty
|
||||
|
||||
select {
|
||||
case obj.eventsChan <- nil:
|
||||
case obj.eventsChan <- nil: // blocks! (this is unbuffered)
|
||||
// send!
|
||||
}
|
||||
|
||||
@@ -328,11 +405,13 @@ func (obj *State) event() {
|
||||
// CheckApply will have some work to do in order to converge it.
|
||||
func (obj *State) setDirty() {
|
||||
obj.tuid.StopTimer()
|
||||
obj.isStateOK = false
|
||||
//obj.mutex.Lock()
|
||||
obj.isStateOK.Store(false) // concurrent write
|
||||
//obj.mutex.Unlock()
|
||||
}
|
||||
|
||||
// poll is a replacement for Watch when the Poll metaparameter is used.
|
||||
func (obj *State) poll(interval uint32) error {
|
||||
func (obj *State) poll(ctx context.Context, interval uint32) error {
|
||||
// create a time.Ticker for the given interval
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
@@ -344,7 +423,7 @@ func (obj *State) poll(interval uint32) error {
|
||||
case <-ticker.C: // received the timer event
|
||||
obj.init.Logf("polling...")
|
||||
|
||||
case <-obj.init.Done: // signal for shutdown request
|
||||
case <-ctx.Done(): // signal for shutdown request
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package graph
|
||||
|
||||
|
||||
82
engine/graphqueryable.go
Normal file
82
engine/graphqueryable.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
// GraphQueryableRes is the interface that must be implemented if you want your
|
||||
// resource to be allowed to be queried from another resource in the graph. This
|
||||
// is done as a form of explicit authorization tracking so that we can consider
|
||||
// security aspects more easily. Ultimately, all resource code should be
|
||||
// trusted, but it's still a good idea to know if a particular resource is even
|
||||
// able to access information about another one, and if your resource doesn't
|
||||
// add the trait supporting this, then it won't be allowed.
|
||||
type GraphQueryableRes interface {
|
||||
Res // implement everything in Res but add the additional requirements
|
||||
|
||||
// GraphQueryAllowed returns nil if you're allowed to query the graph.
|
||||
GraphQueryAllowed(...GraphQueryableOption) error
|
||||
}
|
||||
|
||||
// GraphQueryableOption is an option that can be used to specify the
|
||||
// authentication.
|
||||
type GraphQueryableOption func(*GraphQueryableOptions)
|
||||
|
||||
// GraphQueryableOptions represents the different possible configurable options.
|
||||
type GraphQueryableOptions struct {
|
||||
// Kind is the kind of the resource making the access.
|
||||
Kind string
|
||||
// Name is the name of the resource making the access.
|
||||
Name string
|
||||
// TODO: add more options if needed
|
||||
}
|
||||
|
||||
// Apply is a helper function to apply a list of options to the struct. You
|
||||
// should initialize it with defaults you want, and then apply any you've
|
||||
// received like this.
|
||||
func (obj *GraphQueryableOptions) Apply(opts ...GraphQueryableOption) {
|
||||
for _, optionFunc := range opts { // apply the options
|
||||
optionFunc(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// GraphQueryableOptionKind tells the GraphQueryAllowed function what the
|
||||
// resource kind is.
|
||||
func GraphQueryableOptionKind(kind string) GraphQueryableOption {
|
||||
return func(gqo *GraphQueryableOptions) {
|
||||
gqo.Kind = kind
|
||||
}
|
||||
}
|
||||
|
||||
// GraphQueryableOptionName tells the GraphQueryAllowed function what the
|
||||
// resource name is.
|
||||
func GraphQueryableOptionName(name string) GraphQueryableOption {
|
||||
return func(gqo *GraphQueryableOptions) {
|
||||
gqo.Name = name
|
||||
}
|
||||
}
|
||||
334
engine/local/local.go
Normal file
334
engine/local/local.go
Normal file
@@ -0,0 +1,334 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// Package local contains functions and interfaces that are shared between
|
||||
// functions and resources. It's similar to the "world" functionality, except
|
||||
// that it only involves local operations that stay within a single machine or
|
||||
// local mgmt instance.
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
)
|
||||
|
||||
// API implements the base handle for all the methods in this package. If we
|
||||
// were going to have more than one implementation for all of these, then this
|
||||
// would be an interface instead, and different packages would implement it.
|
||||
// Since this is not the expectation for the local API, it's all self-contained.
|
||||
type API struct {
|
||||
Prefix string
|
||||
Debug bool
|
||||
Logf func(format string, v ...interface{})
|
||||
|
||||
// Each piece of the API can take a handle here.
|
||||
*Value
|
||||
}
|
||||
|
||||
// Init initializes the API before first use. It returns itself so it can be
|
||||
// chained for API aesthetical purposes.
|
||||
func (obj *API) Init() *API {
|
||||
obj.Value = &Value{}
|
||||
obj.Value.Init(&ValueInit{
|
||||
Prefix: obj.Prefix,
|
||||
Debug: obj.Debug,
|
||||
Logf: obj.Logf,
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
// ValueInit are the init values that the Value API needs to work correctly.
|
||||
type ValueInit struct {
|
||||
Prefix string
|
||||
Debug bool
|
||||
Logf func(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// Value is the API for getting, setting, and watching local values.
|
||||
type Value struct {
|
||||
init *ValueInit
|
||||
mutex *sync.Mutex
|
||||
prefix string
|
||||
prefixExists bool // is it okay to use the prefix?
|
||||
values map[string]interface{}
|
||||
notify map[chan struct{}]string // one chan (unique ptr) for each watch
|
||||
skipread map[string]struct{}
|
||||
}
|
||||
|
||||
// Init runs some initialization code for the Value API.
|
||||
func (obj *Value) Init(init *ValueInit) {
|
||||
obj.init = init
|
||||
obj.mutex = &sync.Mutex{}
|
||||
obj.prefix = fmt.Sprintf("%s/", path.Join(obj.init.Prefix, "value"))
|
||||
obj.values = make(map[string]interface{})
|
||||
obj.notify = make(map[chan struct{}]string)
|
||||
obj.skipread = make(map[string]struct{})
|
||||
|
||||
// We don't need to, or want to, load any of the keys from disk
|
||||
// initially, because (1) this would consume memory for keys we never
|
||||
// use, and (2) we can load them on first read instead.
|
||||
// TODO: build in some sort of expiry system that deletes keys older
|
||||
// than X weeks to prevent infinite growth of the on-disk database.
|
||||
}
|
||||
|
||||
// ValueGet pulls a value out of a local in-memory, key-value store that is
|
||||
// backed by on-disk storage. While each value is intended to have an underlying
|
||||
// type, we use the `any` or empty `interface{}` value to represent each value
|
||||
// instead of a `types.Value` because it's more generic, and not limited to
|
||||
// being used with the language type system. If the value doesn't exist, we
|
||||
// return a nil value and no error.
|
||||
func (obj *Value) ValueGet(ctx context.Context, key string) (interface{}, error) {
|
||||
prefix, err := obj.getPrefix()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj.mutex.Lock()
|
||||
defer obj.mutex.Unlock()
|
||||
|
||||
var val interface{}
|
||||
//var err error
|
||||
if _, skip := obj.skipread[key]; skip {
|
||||
val, err = valueRead(ctx, prefix, key) // must return val == nil if missing
|
||||
if err != nil {
|
||||
// We had an actual read issue. Report this and stop
|
||||
// because it means we might not be allowing our
|
||||
// cold-cache warming if we ignored it.
|
||||
return nil, err
|
||||
}
|
||||
// File not found errors are masked in the valueRead function
|
||||
}
|
||||
|
||||
// Anything in memory, will override whatever we might have read.
|
||||
value, exists := obj.values[key]
|
||||
if !exists {
|
||||
// disable future disk reads since the cache is now warm!
|
||||
obj.skipread[key] = struct{}{}
|
||||
return val, nil // if val is nil, we didn't find it
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ValueSet sets a value to our in-memory, key-value store that is backed by
|
||||
// on-disk storage. If you provide a nil value, this is the equivalent of
|
||||
// removing or deleting the value.
|
||||
func (obj *Value) ValueSet(ctx context.Context, key string, value interface{}) error {
|
||||
prefix, err := obj.getPrefix()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj.mutex.Lock()
|
||||
defer obj.mutex.Unlock()
|
||||
|
||||
// Write to state dir on disk first. If ctx cancels, we assume it's not
|
||||
// written or it doesn't matter because we're cancelling, meaning we're
|
||||
// shutting down, so our local cache can be invalidated anyways.
|
||||
|
||||
if value == nil { // remove/delete
|
||||
if err := valueRemove(ctx, prefix, key); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := valueWrite(ctx, prefix, key, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if value == nil { // remove/delete
|
||||
delete(obj.values, key)
|
||||
} else {
|
||||
obj.values[key] = value // store to in-memory map
|
||||
}
|
||||
|
||||
// We still notify on remove/delete!
|
||||
for ch, k := range obj.notify { // send notifications to any watchers...
|
||||
if k != key { // there might be more than one watcher per key
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case ch <- struct{}{}: // must be async and not block forever
|
||||
// send
|
||||
|
||||
// We don't ever exit here, because that would be the equivalent
|
||||
// of dropping a notification on the floor. This loop is
|
||||
// non-blocking, and so it's okay to just finish it up quickly.
|
||||
//case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueWatch watches a value from our in-memory, key-value store that is backed
|
||||
// by on-disk storage. Conveniently, it never has to watch the on-disk storage,
|
||||
// because after the initial startup which always sends a single startup event,
|
||||
// it suffices to watch the in-memory store for events!
|
||||
func (obj *Value) ValueWatch(ctx context.Context, key string) (chan struct{}, error) {
|
||||
// No need to look at the prefix on disk, because we can do all our
|
||||
// watches from memory!
|
||||
//prefix, err := obj.getPrefix()
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
|
||||
obj.mutex.Lock()
|
||||
defer obj.mutex.Unlock()
|
||||
|
||||
notifyCh := make(chan struct{}, 1) // so we can async send
|
||||
obj.notify[notifyCh] = key // add (while within the mutex)
|
||||
notifyCh <- struct{}{} // startup signal, send one!
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer func() { // cleanup
|
||||
obj.mutex.Lock()
|
||||
defer obj.mutex.Unlock()
|
||||
delete(obj.notify, notifyCh) // free memory (in mutex)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-notifyCh:
|
||||
if !ok {
|
||||
// programming error
|
||||
panic("unexpected channel closure")
|
||||
}
|
||||
// recv
|
||||
|
||||
case <-ctx.Done():
|
||||
break // we exit
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
// send
|
||||
|
||||
case <-ctx.Done():
|
||||
break // we exit
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// getPrefix gets the prefix dir to use, or errors if it can't make one. It
|
||||
// makes it on first use, and returns quickly from any future calls to it.
|
||||
func (obj *Value) getPrefix() (string, error) {
|
||||
// NOTE: Moving this mutex to just below the first early return, would
|
||||
// be a benign race, but as it turns out, it's possible that a compiler
|
||||
// would see this behaviour as "undefined" and things might not work as
|
||||
// intended. It could perhaps be replaced with a sync/atomic primitive
|
||||
// if we wanted better performance here.
|
||||
obj.mutex.Lock()
|
||||
defer obj.mutex.Unlock()
|
||||
|
||||
if obj.prefixExists { // former race read
|
||||
return obj.prefix, nil
|
||||
}
|
||||
|
||||
// MkdirAll instead of Mkdir because we have no idea if the parent
|
||||
// local/ directory was already made yet or not. (If at all.) If path is
|
||||
// already a directory, MkdirAll does nothing and returns nil. (Good!)
|
||||
// TODO: I hope MkdirAll is thread-safe on path creation in case another
|
||||
// future local API tries to make the base (parent) directory too!
|
||||
if err := os.MkdirAll(obj.prefix, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
obj.prefixExists = true // former race write
|
||||
|
||||
return obj.prefix, nil
|
||||
}
|
||||
|
||||
func valueRead(ctx context.Context, prefix, key string) (interface{}, error) {
|
||||
// TODO: implement ctx cancellation
|
||||
// TODO: replace with my path library
|
||||
if !strings.HasSuffix(prefix, "/") {
|
||||
return nil, fmt.Errorf("prefix is not a dir")
|
||||
}
|
||||
if !strings.HasPrefix(prefix, "/") {
|
||||
return nil, fmt.Errorf("prefix is not absolute")
|
||||
}
|
||||
p := fmt.Sprintf("%s%s", prefix, key)
|
||||
|
||||
b, err := os.ReadFile(p)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil // not found
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// file exists!
|
||||
s := string(b)
|
||||
s = strings.TrimSpace(s) // get rid of any newline
|
||||
|
||||
return util.B64ToValue(s)
|
||||
}
|
||||
|
||||
func valueWrite(ctx context.Context, prefix, key string, value interface{}) error {
|
||||
// TODO: implement ctx cancellation
|
||||
// TODO: replace with my path library
|
||||
if !strings.HasSuffix(prefix, "/") {
|
||||
return fmt.Errorf("prefix is not a dir")
|
||||
}
|
||||
if !strings.HasPrefix(prefix, "/") {
|
||||
return fmt.Errorf("prefix is not absolute")
|
||||
}
|
||||
p := fmt.Sprintf("%s%s", prefix, key)
|
||||
|
||||
s, err := util.ValueToB64(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s += "\n" // files end with a newline
|
||||
return os.WriteFile(p, []byte(s), 0600)
|
||||
}
|
||||
|
||||
func valueRemove(ctx context.Context, prefix, key string) error {
|
||||
// TODO: implement ctx cancellation
|
||||
// TODO: replace with my path library
|
||||
if !strings.HasSuffix(prefix, "/") {
|
||||
return fmt.Errorf("prefix is not a dir")
|
||||
}
|
||||
if !strings.HasPrefix(prefix, "/") {
|
||||
return fmt.Errorf("prefix is not absolute")
|
||||
}
|
||||
p := fmt.Sprintf("%s%s", prefix, key)
|
||||
|
||||
if err := os.Remove(p); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil // ignore not found errors
|
||||
}
|
||||
62
engine/local/local_test.go
Normal file
62
engine/local/local_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
//go:build !root
|
||||
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
tmpdir := fmt.Sprintf("%s/", t.TempDir()) // gets cleaned up at end, new dir for each call
|
||||
key := "test1"
|
||||
value := 42
|
||||
if err := valueWrite(context.Background(), tmpdir, key, value); err != nil {
|
||||
t.Errorf("error: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if val, err := valueRead(context.Background(), tmpdir, key); err != nil {
|
||||
t.Errorf("error: %+v", err)
|
||||
return
|
||||
} else if !reflect.DeepEqual(value, val) {
|
||||
t.Errorf("error: not equal: %+v != %+v", val, value)
|
||||
//return
|
||||
}
|
||||
|
||||
if err := valueRemove(context.Background(), tmpdir, key); err != nil {
|
||||
t.Errorf("error: %+v", err)
|
||||
//return
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
@@ -36,8 +48,9 @@ var DefaultMetaParams = &MetaParams{
|
||||
Poll: 0, // defaults to watching for events
|
||||
Limit: rate.Inf, // defaults to no limit
|
||||
Burst: 0, // no burst needed on an infinite rate
|
||||
Reset: false,
|
||||
//Sema: []string{},
|
||||
Rewatch: true,
|
||||
Rewatch: false,
|
||||
Realize: false, // true would be more awesome, but unexpected for users
|
||||
}
|
||||
|
||||
@@ -64,9 +77,16 @@ type MetaParams struct {
|
||||
// reason to want to do something differently for the Watch errors.
|
||||
|
||||
// Retry is the number of times to retry on error. Use -1 for infinite.
|
||||
// This value is used for both Watch and CheckApply.
|
||||
Retry int16 `yaml:"retry"`
|
||||
|
||||
// Delay is the number of milliseconds to wait between retries.
|
||||
// RetryReset resets the retry count for CheckApply if it succeeds. This
|
||||
// value is currently different from the count used for Watch.
|
||||
// TODO: Consider resetting retry count for watch if it sends an event?
|
||||
RetryReset bool `yaml:"retryreset"`
|
||||
|
||||
// Delay is the number of milliseconds to wait between retries. This
|
||||
// value is used for both Watch and CheckApply.
|
||||
Delay uint64 `yaml:"delay"`
|
||||
|
||||
// Poll is the number of seconds between poll intervals. Use 0 to Watch.
|
||||
@@ -78,6 +98,15 @@ type MetaParams struct {
|
||||
// Burst is the number of events to allow in a burst.
|
||||
Burst int `yaml:"burst"`
|
||||
|
||||
// Reset causes the meta param state to reset when the resource changes.
|
||||
// What this means is if you have a resource of a specific kind and name
|
||||
// and in the subsequent graph it changes because one of its params
|
||||
// changed, normally the Retry, and other params will remember their
|
||||
// state, and you'll not reset the retry counter, however if this is
|
||||
// true, then it will get reset. Note that any normal reset mechanisms
|
||||
// built into retry are not affected by this.
|
||||
Reset bool `yaml:"reset"`
|
||||
|
||||
// Sema is a list of semaphore ids in the form `id` or `id:count`. If
|
||||
// you don't specify a count, then 1 is assumed. The sema of `foo` which
|
||||
// has a count equal to 1, is different from a sema named `foo:1` which
|
||||
@@ -90,7 +119,9 @@ type MetaParams struct {
|
||||
// and add it back as a new vertex, thus causing it to run again. This
|
||||
// is different from the Retry metaparam which applies during the normal
|
||||
// execution. It is only when this is exhausted that we're in permanent
|
||||
// worker failure, and only then can we rely on this metaparam.
|
||||
// worker failure, and only then can we rely on this metaparam. This is
|
||||
// false by default, as the frequency of graph changes makes it unlikely
|
||||
// that you wanted this enabled on most resources.
|
||||
Rewatch bool `yaml:"rewatch"`
|
||||
|
||||
// Realize ensures that the resource is guaranteed to converge at least
|
||||
@@ -133,6 +164,9 @@ func (obj *MetaParams) Cmp(meta *MetaParams) error {
|
||||
if obj.Burst != meta.Burst {
|
||||
return fmt.Errorf("values for Burst are different")
|
||||
}
|
||||
if obj.Reset != meta.Reset {
|
||||
return fmt.Errorf("values for Reset are different")
|
||||
}
|
||||
|
||||
if err := util.SortedStrSliceCompare(obj.Sema, meta.Sema); err != nil {
|
||||
return errwrap.Wrapf(err, "values for Sema are different")
|
||||
@@ -180,6 +214,7 @@ func (obj *MetaParams) Copy() *MetaParams {
|
||||
Poll: obj.Poll,
|
||||
Limit: obj.Limit, // FIXME: can we copy this type like this? test me!
|
||||
Burst: obj.Burst,
|
||||
Reset: obj.Reset,
|
||||
Sema: sema,
|
||||
Rewatch: obj.Rewatch,
|
||||
Realize: obj.Realize,
|
||||
@@ -200,3 +235,18 @@ func (obj *MetaParams) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
*obj = MetaParams(raw) // restore from indirection with type conversion!
|
||||
return nil
|
||||
}
|
||||
|
||||
// MetaState is some local meta param state that is saved between resources of
|
||||
// the same kind+name unique ID. Even though the vertex/resource pointer might
|
||||
// change during a graph switch (because the params changed) we might be
|
||||
// logically referring to the same resource, and we might want to preserve some
|
||||
// data across that switch. The common example is the resource retry count. If a
|
||||
// resource failed three times, and we had a limit of five before it would be a
|
||||
// permanent failure, then we don't want to reset this counter just because we
|
||||
// changed a parameter (field) of the resource. This doesn't mean we don't want
|
||||
// to ever reset these counts. For that, flip on the reset meta param.
|
||||
type MetaState struct {
|
||||
|
||||
// CheckApplyRetry is the current retry count for CheckApply.
|
||||
CheckApplyRetry int16
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,9 +13,21 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// +build !root
|
||||
//go:build !root
|
||||
|
||||
package engine
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,14 +13,29 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine/local"
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
@@ -29,8 +44,8 @@ import (
|
||||
// TODO: should each resource be a sub-package?
|
||||
var registeredResources = map[string]func() Res{}
|
||||
|
||||
// RegisterResource registers a new resource by providing a constructor
|
||||
// function that returns a resource object ready to be unmarshalled from YAML.
|
||||
// RegisterResource registers a new resource by providing a constructor function
|
||||
// that returns a resource object ready to be unmarshalled from YAML.
|
||||
func RegisterResource(kind string, fn func() Res) {
|
||||
f := fn()
|
||||
if kind == "" {
|
||||
@@ -86,6 +101,9 @@ type Init struct {
|
||||
// Program is the name of the program.
|
||||
Program string
|
||||
|
||||
// Version is the version of the program.
|
||||
Version string
|
||||
|
||||
// Hostname is the uuid for the host.
|
||||
Hostname string
|
||||
|
||||
@@ -97,10 +115,6 @@ type Init struct {
|
||||
// Event sends an event notifying the engine of a possible state change.
|
||||
Event func()
|
||||
|
||||
// Done returns a channel that will close to signal to us that it's time
|
||||
// for us to shutdown.
|
||||
Done chan struct{}
|
||||
|
||||
// Called from within CheckApply:
|
||||
|
||||
// Refresh returns whether the resource received a notification. This
|
||||
@@ -120,6 +134,25 @@ type Init struct {
|
||||
|
||||
// Other functionality:
|
||||
|
||||
// Graph is a function that returns the current graph. The returned
|
||||
// value won't be valid after a graphsync so make sure to call this when
|
||||
// you are about to use it, and discard it right after.
|
||||
// FIXME: it might be better to offer a safer, more limited, GraphQuery?
|
||||
//Graph func() *pgraph.Graph // TODO: not implemented, use FilteredGraph
|
||||
|
||||
// FilteredGraph is a function that returns a filtered variant of the
|
||||
// current graph. Only resource that have allowed themselves to be added
|
||||
// into this graph will appear. If they did not consent, then those
|
||||
// vertices and any associated edges, will not be present.
|
||||
FilteredGraph func() (*pgraph.Graph, error)
|
||||
|
||||
// TODO: GraphQuery offers an interface to query the resource graph.
|
||||
|
||||
// Local has a bunch of methods and properties which are useful for
|
||||
// operations on the local machine and for communication between
|
||||
// functions and resources.
|
||||
Local *local.API
|
||||
|
||||
// World provides a connection to the outside world. This is most often
|
||||
// used for communicating with the distributed database.
|
||||
World World
|
||||
@@ -175,17 +208,21 @@ type Res interface {
|
||||
// and data from the engine.
|
||||
Init(*Init) error
|
||||
|
||||
// Close is run by the engine to clean up after the resource is done.
|
||||
Close() error
|
||||
// Cleanup is run by the engine to clean up after the resource is done.
|
||||
Cleanup() error
|
||||
|
||||
// Watch is run by the engine to monitor for state changes. If it
|
||||
// detects any, it notifies the engine which will usually run CheckApply
|
||||
// in response.
|
||||
Watch() error
|
||||
// in response. If the input context cancels, we must shutdown.
|
||||
Watch(context.Context) error
|
||||
|
||||
// CheckApply determines if the state of the resource is correct and if
|
||||
// asked to with the `apply` variable, applies the requested state.
|
||||
CheckApply(apply bool) (checkOK bool, err error)
|
||||
// asked to with the `apply` variable, applies the requested state. If
|
||||
// the input context cancels, we must return as quickly as possible. We
|
||||
// should never exit immediately if this would cause permanent
|
||||
// corruption of some sort. However it doesn't mean that a resource was
|
||||
// taken to the desired state.
|
||||
CheckApply(ctx context.Context, apply bool) (checkOK bool, err error)
|
||||
|
||||
// Cmp compares itself to another resource and returns an error if they
|
||||
// are not equivalent. This is more strict than the Adapts method of the
|
||||
@@ -205,6 +242,22 @@ func Stringer(res Res) string {
|
||||
return Repr(res.Kind(), res.Name())
|
||||
}
|
||||
|
||||
// ResPtrUID is a unique identifier that is consistent for the kind and name of
|
||||
// the resource only. This was formerly a string, but a struct is more precise.
|
||||
// The result is suitable as a unique map key.
|
||||
type ResPtrUID struct {
|
||||
kind string
|
||||
name string
|
||||
}
|
||||
|
||||
// PtrUID generates a ResPtrUID from a resource. The result is suitable as a
|
||||
// unique map key.
|
||||
func PtrUID(res Res) ResPtrUID {
|
||||
// the use of "repr" is kind of arbitrary as long as it's unique
|
||||
//return ResPtrUID(Repr(res.Kind(), res.Name()))
|
||||
return ResPtrUID{kind: res.Kind(), name: res.Name()}
|
||||
}
|
||||
|
||||
// Validate validates a resource by checking multiple aspects. This is the main
|
||||
// entry point for running all the validation steps on a resource.
|
||||
func Validate(res Res) error {
|
||||
@@ -227,8 +280,8 @@ func Validate(res Res) error {
|
||||
// the Interrupt method to shutdown the resource quickly. Running this method
|
||||
// may leave the resource in a partial state, however this may be desired if you
|
||||
// want a faster exit or if you'd prefer a partial state over letting the
|
||||
// resource complete in a situation where you made an error and you wish to
|
||||
// exit quickly to avoid data loss. It is usually triggered after multiple ^C
|
||||
// resource complete in a situation where you made an error and you wish to exit
|
||||
// quickly to avoid data loss. It is usually triggered after multiple ^C
|
||||
// signals.
|
||||
type InterruptableRes interface {
|
||||
Res
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,24 +13,35 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
// +build !noaugeas
|
||||
//go:build !noaugeas
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
"github.com/purpleidea/mgmt/engine/traits"
|
||||
"github.com/purpleidea/mgmt/recwatch"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
"github.com/purpleidea/mgmt/util/recwatch"
|
||||
|
||||
// FIXME: we vendor go/augeas because master requires augeas 1.6.0
|
||||
// and libaugeas-dev-1.6.0 is not yet available in a PPA.
|
||||
"honnef.co/go/augeas"
|
||||
)
|
||||
|
||||
@@ -51,24 +62,24 @@ type AugeasRes struct {
|
||||
init *engine.Init
|
||||
|
||||
// File is the path to the file targeted by this resource.
|
||||
File string `yaml:"file"`
|
||||
File string `lang:"file" yaml:"file"`
|
||||
|
||||
// Lens is the lens used by this resource. If specified, mgmt
|
||||
// will lower the augeas overhead by only loading that lens.
|
||||
Lens string `yaml:"lens"`
|
||||
Lens string `lang:"lens" yaml:"lens"`
|
||||
|
||||
// Sets is a list of changes that will be applied to the file, in the form of
|
||||
// ["path", "value"]. mgmt will run augeas.Get() before augeas.Set(), to
|
||||
// prevent changing the file when it is not needed.
|
||||
Sets []*AugeasSet `yaml:"sets"`
|
||||
// Sets is a list of changes that will be applied to the file, in the
|
||||
// form of ["path", "value"]. mgmt will run augeas.Get() before
|
||||
// augeas.Set(), to prevent changing the file when it is not needed.
|
||||
Sets []*AugeasSet `lang:"sets" yaml:"sets"`
|
||||
|
||||
recWatcher *recwatch.RecWatcher // used to watch the changed files
|
||||
}
|
||||
|
||||
// AugeasSet represents a key/value pair of settings to be applied.
|
||||
type AugeasSet struct {
|
||||
Path string `yaml:"path"` // The relative path to the value to be changed.
|
||||
Value string `yaml:"value"` // The value to be set on the given Path.
|
||||
Path string `lang:"path" yaml:"path"` // The relative path to the value to be changed.
|
||||
Value string `lang:"value" yaml:"value"` // The value to be set on the given Path.
|
||||
}
|
||||
|
||||
// Cmp compares this set with another one.
|
||||
@@ -119,15 +130,15 @@ func (obj *AugeasRes) Init(init *engine.Init) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is run by the engine to clean up after the resource is done.
|
||||
func (obj *AugeasRes) Close() error {
|
||||
// Cleanup is run by the engine to clean up after the resource is done.
|
||||
func (obj *AugeasRes) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch is the primary listener for this resource and it outputs events.
|
||||
// Taken from the File resource.
|
||||
// Watch is the primary listener for this resource and it outputs events. This
|
||||
// was taken from the File resource.
|
||||
// FIXME: DRY - This is taken from the file resource
|
||||
func (obj *AugeasRes) Watch() error {
|
||||
func (obj *AugeasRes) Watch(ctx context.Context) error {
|
||||
var err error
|
||||
obj.recWatcher, err = recwatch.NewRecWatcher(obj.File, false)
|
||||
if err != nil {
|
||||
@@ -156,7 +167,7 @@ func (obj *AugeasRes) Watch() error {
|
||||
}
|
||||
send = true
|
||||
|
||||
case <-obj.init.Done: // closed by the engine to signal shutdown
|
||||
case <-ctx.Done(): // closed by the engine to signal shutdown
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -169,7 +180,7 @@ func (obj *AugeasRes) Watch() error {
|
||||
}
|
||||
|
||||
// checkApplySet runs CheckApply for one element of the AugeasRes.Set
|
||||
func (obj *AugeasRes) checkApplySet(apply bool, ag *augeas.Augeas, set *AugeasSet) (bool, error) {
|
||||
func (obj *AugeasRes) checkApplySet(ctx context.Context, apply bool, ag *augeas.Augeas, set *AugeasSet) (bool, error) {
|
||||
fullpath := fmt.Sprintf("/files/%v/%v", obj.File, set.Path)
|
||||
|
||||
// We do not check for errors because errors are also thrown when
|
||||
@@ -194,7 +205,7 @@ func (obj *AugeasRes) checkApplySet(apply bool, ag *augeas.Augeas, set *AugeasSe
|
||||
}
|
||||
|
||||
// CheckApply method for Augeas resource.
|
||||
func (obj *AugeasRes) CheckApply(apply bool) (bool, error) {
|
||||
func (obj *AugeasRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
obj.init.Logf("CheckApply: %s", obj.File)
|
||||
// By default we do not set any option to augeas, we use the defaults.
|
||||
opts := augeas.None
|
||||
@@ -231,7 +242,7 @@ func (obj *AugeasRes) CheckApply(apply bool) (bool, error) {
|
||||
|
||||
checkOK := true
|
||||
for _, set := range obj.Sets {
|
||||
if setCheckOK, err := obj.checkApplySet(apply, &ag, set); err != nil {
|
||||
if setCheckOK, err := obj.checkApplySet(ctx, apply, &ag, set); err != nil {
|
||||
return false, errwrap.Wrapf(err, "augeas: error during CheckApply of one Set")
|
||||
} else if !setCheckOK {
|
||||
checkOK = false
|
||||
@@ -301,8 +312,8 @@ func (obj *AugeasRes) UIDs() []engine.ResUID {
|
||||
return []engine.ResUID{x}
|
||||
}
|
||||
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct.
|
||||
// It is primarily useful for setting the defaults.
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
|
||||
// primarily useful for setting the defaults.
|
||||
func (obj *AugeasRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type rawRes AugeasRes // indirection to avoid infinite recursion
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package resources
|
||||
|
||||
@@ -24,7 +36,7 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -121,8 +133,8 @@ const (
|
||||
)
|
||||
|
||||
// AwsRegions is a list of all AWS regions generated using ec2.DescribeRegions.
|
||||
// cn-north-1 and us-gov-west-1 are not returned, probably due to security.
|
||||
// List available at http://docs.aws.amazon.com/general/latest/gr/rande.html
|
||||
// cn-north-1 and us-gov-west-1 are not returned, probably due to security. List
|
||||
// available at http://docs.aws.amazon.com/general/latest/gr/rande.html
|
||||
var AwsRegions = []string{
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
@@ -147,26 +159,42 @@ var AwsRegions = []string{
|
||||
// http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
|
||||
type AwsEc2Res struct {
|
||||
traits.Base // add the base methods without re-implementation
|
||||
traits.Sendable
|
||||
|
||||
init *engine.Init
|
||||
|
||||
State string `yaml:"state"` // state: running, stopped, terminated
|
||||
Region string `yaml:"region"` // region must match an element of AwsRegions
|
||||
Type string `yaml:"type"` // type of ec2 instance, eg: t2.micro
|
||||
ImageID string `yaml:"imageid"` // imageid must be available on the chosen region
|
||||
// State must be running, stopped, or terminated.
|
||||
State string `lang:"state" yaml:"state"`
|
||||
|
||||
// Region must match one of the AwsRegions. This list is static at the
|
||||
// moment.
|
||||
Region string `lang:"region" yaml:"region"`
|
||||
|
||||
// Type of ec2 instance, eg: t2.micro for example.
|
||||
Type string `lang:"type" yaml:"type"`
|
||||
|
||||
// ImageID to use, and note that it must be available on the chosen
|
||||
// region.
|
||||
ImageID string `lang:"imageid" yaml:"imageid"`
|
||||
|
||||
// WatchEndpoint is the public url of the sns endpoint, eg:
|
||||
// http://server:12345/ for example.
|
||||
WatchEndpoint string `lang:"watchendpoint" yaml:"watchendpoint"`
|
||||
|
||||
// WatchListenAddr is the local address or port that the sns listens on,
|
||||
// eg: 10.0.0.0:23456 or 23456.
|
||||
WatchListenAddr string `lang:"watchlistenaddr" yaml:"watchlistenaddr"`
|
||||
|
||||
WatchEndpoint string `yaml:"watchendpoint"` // the public url of the sns endpoint, eg: http://server:12345/
|
||||
WatchListenAddr string `yaml:"watchlistenaddr"` // the local address or port that the sns listens on, eg: 10.0.0.0:23456 or 23456
|
||||
// ErrorOnMalformedPost controls whether or not malformed HTTP post
|
||||
// requests, that cause JSON decoder errors, will also make the engine
|
||||
// shut down. If ErrorOnMalformedPost set to true and an error occurs,
|
||||
// Watch() will return the error and the engine will shut down.
|
||||
ErrorOnMalformedPost bool `yaml:"erroronmalformedpost"`
|
||||
ErrorOnMalformedPost bool `lang:"erroronmalformedpost" yaml:"erroronmalformedpost"`
|
||||
|
||||
// UserData is used to run bash and cloud-init commands on first launch.
|
||||
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
|
||||
// for documantation and examples.
|
||||
UserData string `yaml:"userdata"`
|
||||
UserData string `lang:"userdata" yaml:"userdata"`
|
||||
|
||||
client *ec2.EC2 // client session for AWS API calls
|
||||
|
||||
@@ -187,7 +215,8 @@ type AwsEc2Res struct {
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
// chanStruct defines the type for a channel used to pass events and errors to watch.
|
||||
// chanStruct defines the type for a channel used to pass events and errors to
|
||||
// watch.
|
||||
type chanStruct struct {
|
||||
event awsEc2Event
|
||||
state string
|
||||
@@ -233,7 +262,8 @@ type ruleDetail struct {
|
||||
State []string `json:"state"`
|
||||
}
|
||||
|
||||
// postData is the format of the messages received and decoded by snsPostHandler().
|
||||
// postData is the format of the messages received and decoded by
|
||||
// snsPostHandler().
|
||||
type postData struct {
|
||||
Type string `json:"Type"`
|
||||
MessageID string `json:"MessageId"`
|
||||
@@ -247,7 +277,8 @@ type postData struct {
|
||||
SigningCertURL string `json:"SigningCertURL"`
|
||||
}
|
||||
|
||||
// postMsg is used to unmarshal the postData message if it's an event notification.
|
||||
// postMsg is used to unmarshal the postData message if it's an event
|
||||
// notification.
|
||||
type postMsg struct {
|
||||
InstanceID string `json:"instance-id"`
|
||||
State string `json:"state"`
|
||||
@@ -385,10 +416,11 @@ func (obj *AwsEc2Res) Init(init *engine.Init) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close cleans up when we're done. This is needed to delete some of the AWS
|
||||
// Cleanup cleans up when we're done. This is needed to delete some of the AWS
|
||||
// objects created for the SNS endpoint.
|
||||
func (obj *AwsEc2Res) Close() error {
|
||||
func (obj *AwsEc2Res) Cleanup() error {
|
||||
var errList error
|
||||
// XXX: do these in a defer of Watch instead.
|
||||
// clean up sns objects created by Init/snsWatch
|
||||
if obj.snsClient != nil {
|
||||
// delete the topic and associated subscriptions
|
||||
@@ -406,15 +438,16 @@ func (obj *AwsEc2Res) Close() error {
|
||||
}
|
||||
|
||||
// Watch is the primary listener for this resource and it outputs events.
|
||||
func (obj *AwsEc2Res) Watch() error {
|
||||
func (obj *AwsEc2Res) Watch(ctx context.Context) error {
|
||||
if obj.WatchListenAddr != "" {
|
||||
return obj.snsWatch()
|
||||
return obj.snsWatch(ctx)
|
||||
}
|
||||
return obj.longpollWatch()
|
||||
return obj.longpollWatch(ctx)
|
||||
}
|
||||
|
||||
// longpollWatch uses the ec2 api's built in methods to watch ec2 resource state.
|
||||
func (obj *AwsEc2Res) longpollWatch() error {
|
||||
// longpollWatch uses the ec2 api's built in methods to watch ec2 resource
|
||||
// state.
|
||||
func (obj *AwsEc2Res) longpollWatch(ctx context.Context) error {
|
||||
send := false
|
||||
|
||||
// We tell the engine that we're running right away. This is not correct,
|
||||
@@ -422,7 +455,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
|
||||
obj.init.Running() // when started, notify engine that we're running
|
||||
|
||||
// cancellable context used for exiting cleanly
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
innerCtx, cancel := context.WithCancel(context.TODO())
|
||||
|
||||
// clean up when we're done
|
||||
defer obj.wg.Wait()
|
||||
@@ -457,7 +490,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
|
||||
}
|
||||
|
||||
// wait for the instance state to change
|
||||
state, err := stateWaiter(ctx, instance, obj.client)
|
||||
state, err := stateWaiter(innerCtx, instance, obj.client)
|
||||
if err != nil {
|
||||
select {
|
||||
case obj.awsChan <- &chanStruct{
|
||||
@@ -498,7 +531,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
|
||||
send = true
|
||||
}
|
||||
|
||||
case <-obj.init.Done: // closed by the engine to signal shutdown
|
||||
case <-ctx.Done(): // closed by the engine to signal shutdown
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -510,11 +543,11 @@ func (obj *AwsEc2Res) longpollWatch() error {
|
||||
}
|
||||
|
||||
// snsWatch uses amazon's SNS and CloudWatchEvents APIs to get instance state-
|
||||
// change notifications pushed to the http endpoint (snsServer) set up below.
|
||||
// In Init() a CloudWatch rule is created along with a corresponding SNS topic
|
||||
// that it can publish to. snsWatch creates an http server which listens for
|
||||
// messages published to the topic and processes them accordingly.
|
||||
func (obj *AwsEc2Res) snsWatch() error {
|
||||
// change notifications pushed to the http endpoint (snsServer) set up below. In
|
||||
// Init() a CloudWatch rule is created along with a corresponding SNS topic that
|
||||
// it can publish to. snsWatch creates an http server which listens for messages
|
||||
// published to the topic and processes them accordingly.
|
||||
func (obj *AwsEc2Res) snsWatch(ctx context.Context) error {
|
||||
send := false
|
||||
defer obj.wg.Wait()
|
||||
// create the sns listener
|
||||
@@ -529,9 +562,9 @@ func (obj *AwsEc2Res) snsWatch() error {
|
||||
}
|
||||
// close the listener and shutdown the sns server when we're done
|
||||
defer func() {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), SnsServerShutdownTimeout*time.Second)
|
||||
innerCtx, cancel := context.WithTimeout(context.TODO(), SnsServerShutdownTimeout*time.Second)
|
||||
defer cancel()
|
||||
if err := snsServer.Shutdown(ctx); err != nil {
|
||||
if err := snsServer.Shutdown(innerCtx); err != nil {
|
||||
if err != context.Canceled {
|
||||
obj.init.Logf("error stopping sns endpoint: %s", err)
|
||||
return
|
||||
@@ -592,7 +625,7 @@ func (obj *AwsEc2Res) snsWatch() error {
|
||||
obj.init.Logf("State: %v", msg.event)
|
||||
send = true
|
||||
|
||||
case <-obj.init.Done: // closed by the engine to signal shutdown
|
||||
case <-ctx.Done(): // closed by the engine to signal shutdown
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -604,7 +637,7 @@ func (obj *AwsEc2Res) snsWatch() error {
|
||||
}
|
||||
|
||||
// CheckApply method for AwsEc2 resource.
|
||||
func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
|
||||
func (obj *AwsEc2Res) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
obj.init.Logf("CheckApply(%t)", apply)
|
||||
|
||||
// find the instance we need to check
|
||||
@@ -714,7 +747,7 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
|
||||
}
|
||||
|
||||
// context to cancel the waiter if it takes too long
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), waitTimeout*time.Second)
|
||||
innerCtx, cancel := context.WithTimeout(ctx, waitTimeout*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// wait until the state converges
|
||||
@@ -723,11 +756,11 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
|
||||
}
|
||||
switch obj.State {
|
||||
case ec2.InstanceStateNameRunning:
|
||||
err = obj.client.WaitUntilInstanceRunningWithContext(ctx, waitInput)
|
||||
err = obj.client.WaitUntilInstanceRunningWithContext(innerCtx, waitInput)
|
||||
case ec2.InstanceStateNameStopped:
|
||||
err = obj.client.WaitUntilInstanceStoppedWithContext(ctx, waitInput)
|
||||
err = obj.client.WaitUntilInstanceStoppedWithContext(innerCtx, waitInput)
|
||||
case ec2.InstanceStateNameTerminated:
|
||||
err = obj.client.WaitUntilInstanceTerminatedWithContext(ctx, waitInput)
|
||||
err = obj.client.WaitUntilInstanceTerminatedWithContext(innerCtx, waitInput)
|
||||
default:
|
||||
return false, errwrap.Wrapf(err, "unrecognized instance state")
|
||||
}
|
||||
@@ -751,45 +784,37 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
|
||||
|
||||
// Cmp compares two resources and returns an error if they are not equivalent.
|
||||
func (obj *AwsEc2Res) Cmp(r engine.Res) error {
|
||||
if !obj.Compare(r) {
|
||||
return fmt.Errorf("did not compare")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compare two resources and return if they are equivalent.
|
||||
func (obj *AwsEc2Res) Compare(r engine.Res) bool {
|
||||
// we can only compare AwsEc2Res to others of the same resource kind
|
||||
res, ok := r.(*AwsEc2Res)
|
||||
if !ok {
|
||||
return false
|
||||
return fmt.Errorf("not a %s", obj.Kind())
|
||||
}
|
||||
|
||||
if obj.State != res.State {
|
||||
return false
|
||||
return fmt.Errorf("the State differs")
|
||||
}
|
||||
if obj.Region != res.Region {
|
||||
return false
|
||||
return fmt.Errorf("the Region differs")
|
||||
}
|
||||
if obj.Type != res.Type {
|
||||
return false
|
||||
return fmt.Errorf("the Type differs")
|
||||
}
|
||||
if obj.ImageID != res.ImageID {
|
||||
return false
|
||||
return fmt.Errorf("the ImageID differs")
|
||||
}
|
||||
if obj.WatchEndpoint != res.WatchEndpoint {
|
||||
return false
|
||||
return fmt.Errorf("the WatchEndpoint differs")
|
||||
}
|
||||
if obj.WatchListenAddr != res.WatchListenAddr {
|
||||
return false
|
||||
return fmt.Errorf("the WatchListenAddr differs")
|
||||
}
|
||||
if obj.ErrorOnMalformedPost != res.ErrorOnMalformedPost {
|
||||
return false
|
||||
return fmt.Errorf("the ErrorOnMalformedPost differs")
|
||||
}
|
||||
if obj.UserData != res.UserData {
|
||||
return false
|
||||
return fmt.Errorf("the UserData differs")
|
||||
}
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *AwsEc2Res) prependName() string {
|
||||
@@ -803,8 +828,8 @@ type AwsEc2UID struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// UIDs includes all params to make a unique identification of this object.
|
||||
// Most resources only return one, although some resources can return multiple.
|
||||
// UIDs includes all params to make a unique identification of this object. Most
|
||||
// resources only return one, although some resources can return multiple.
|
||||
func (obj *AwsEc2Res) UIDs() []engine.ResUID {
|
||||
x := &AwsEc2UID{
|
||||
BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()},
|
||||
@@ -813,8 +838,8 @@ func (obj *AwsEc2Res) UIDs() []engine.ResUID {
|
||||
return []engine.ResUID{x}
|
||||
}
|
||||
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct.
|
||||
// It is primarily useful for setting the defaults.
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
|
||||
// primarily useful for setting the defaults.
|
||||
func (obj *AwsEc2Res) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type rawRes AwsEc2Res // indirection to avoid infinite recursion
|
||||
|
||||
@@ -950,8 +975,8 @@ func (obj *AwsEc2Res) snsVerifySignature(post postData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// snsGetCert downloads and parses the signing certificate from the provided
|
||||
// URL for message verification.
|
||||
// snsGetCert downloads and parses the signing certificate from the provided URL
|
||||
// for message verification.
|
||||
func (obj *AwsEc2Res) snsGetCert(url string) (*x509.Certificate, error) {
|
||||
// only download valid certificates from amazon
|
||||
matchURL, err := regexp.MatchString(SnsCertURLRegex, url)
|
||||
@@ -967,7 +992,7 @@ func (obj *AwsEc2Res) snsGetCert(url string) (*x509.Certificate, error) {
|
||||
return nil, errwrap.Wrapf(err, "http get error")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "error reading post body")
|
||||
}
|
||||
@@ -1025,7 +1050,7 @@ func (obj *AwsEc2Res) snsMakeTopic() (string, error) {
|
||||
}
|
||||
obj.init.Logf("Created SNS Topic")
|
||||
if topic.TopicArn == nil {
|
||||
return "", fmt.Errorf("TopicArn is nil")
|
||||
return "", fmt.Errorf("the TopicArn is nil")
|
||||
}
|
||||
return *topic.TopicArn, nil
|
||||
}
|
||||
@@ -1043,8 +1068,8 @@ func (obj *AwsEc2Res) snsDeleteTopic(topicArn string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// snsSubscribe subscribes the endpoint to the sns topic.
|
||||
// Returning SubscriptionArn here is useless as it is still pending confirmation.
|
||||
// snsSubscribe subscribes the endpoint to the sns topic. Returning
|
||||
// SubscriptionArn here is useless as it is still pending confirmation.
|
||||
func (obj *AwsEc2Res) snsSubscribe(endpoint string, topicArn string) error {
|
||||
// subscribe to the topic
|
||||
subInput := &sns.SubscribeInput{
|
||||
@@ -1060,8 +1085,8 @@ func (obj *AwsEc2Res) snsSubscribe(endpoint string, topicArn string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// snsConfirmSubscription confirms the sns subscription.
|
||||
// Returning SubscriptionArn here is useless as it is still pending confirmation.
|
||||
// snsConfirmSubscription confirms the sns subscription. Returning
|
||||
// SubscriptionArn here is useless as it is still pending confirmation.
|
||||
func (obj *AwsEc2Res) snsConfirmSubscription(topicArn string, token string) error {
|
||||
// confirm the subscription
|
||||
csInput := &sns.ConfirmSubscriptionInput{
|
||||
@@ -1113,7 +1138,8 @@ func (obj *AwsEc2Res) snsProcessEvent(message, instanceName string) (awsEc2Event
|
||||
return awsEc2EventNone, nil
|
||||
}
|
||||
|
||||
// snsAuthorize adds the necessary permission for cloudwatch to publish to the SNS topic.
|
||||
// snsAuthorize adds the necessary permission for cloudwatch to publish to the
|
||||
// SNS topic.
|
||||
func (obj *AwsEc2Res) snsAuthorizeCloudWatch(topicArn string) error {
|
||||
// get the topic attributes, including the security policy
|
||||
gaInput := &sns.GetTopicAttributesInput{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
@@ -13,7 +13,19 @@
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package resources
|
||||
|
||||
@@ -57,7 +69,6 @@ type ConfigEtcdRes struct {
|
||||
sizeFlag bool
|
||||
|
||||
interruptChan chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// Default returns some sensible defaults for this resource.
|
||||
@@ -83,26 +94,22 @@ func (obj *ConfigEtcdRes) Init(init *engine.Init) error {
|
||||
obj.init = init // save for later
|
||||
|
||||
obj.interruptChan = make(chan struct{})
|
||||
obj.wg = &sync.WaitGroup{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is run by the engine to clean up after the resource is done.
|
||||
func (obj *ConfigEtcdRes) Close() error {
|
||||
obj.wg.Wait() // bonus
|
||||
// Cleanup is run by the engine to clean up after the resource is done.
|
||||
func (obj *ConfigEtcdRes) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch is the primary listener for this resource and it outputs events.
|
||||
func (obj *ConfigEtcdRes) Watch() error {
|
||||
obj.wg.Add(1)
|
||||
defer obj.wg.Done()
|
||||
// FIXME: add timeout to context
|
||||
// The obj.init.Done channel is closed by the engine to signal shutdown.
|
||||
ctx, cancel := util.ContextWithCloser(context.Background(), obj.init.Done)
|
||||
func (obj *ConfigEtcdRes) Watch(ctx context.Context) error {
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
innerCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
ch, err := obj.init.World.IdealClusterSizeWatch(util.CtxWithWg(ctx, obj.wg))
|
||||
ch, err := obj.init.World.IdealClusterSizeWatch(util.CtxWithWg(innerCtx, wg))
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not watch ideal cluster size")
|
||||
}
|
||||
@@ -121,7 +128,8 @@ Loop:
|
||||
}
|
||||
// pass through and send an event
|
||||
|
||||
case <-obj.init.Done: // closed by the engine to signal shutdown
|
||||
case <-ctx.Done(): // closed by the engine to signal shutdown
|
||||
return nil
|
||||
}
|
||||
|
||||
obj.init.Event() // notify engine of an event (this can block)
|
||||
@@ -134,10 +142,10 @@ Loop:
|
||||
// to zero, then it *won't* try and change it away from zero, because it assumes
|
||||
// that someone has requested a shutdown. If the value is seen on first startup,
|
||||
// then it will change it, because it might be a zero from the previous cluster.
|
||||
func (obj *ConfigEtcdRes) sizeCheckApply(apply bool) (bool, error) {
|
||||
func (obj *ConfigEtcdRes) sizeCheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait() // this must be above the defer cancel() call
|
||||
ctx, cancel := context.WithTimeout(context.Background(), sizeCheckApplyTimeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, sizeCheckApplyTimeout)
|
||||
defer cancel()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -186,17 +194,17 @@ func (obj *ConfigEtcdRes) sizeCheckApply(apply bool) (bool, error) {
|
||||
}
|
||||
|
||||
// CheckApply method for Noop resource. Does nothing, returns happy!
|
||||
func (obj *ConfigEtcdRes) CheckApply(apply bool) (bool, error) {
|
||||
func (obj *ConfigEtcdRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
checkOK := true
|
||||
|
||||
if c, err := obj.sizeCheckApply(apply); err != nil {
|
||||
if c, err := obj.sizeCheckApply(ctx, apply); err != nil {
|
||||
return false, err
|
||||
} else if !c {
|
||||
checkOK = false
|
||||
}
|
||||
|
||||
// TODO: add more config settings management here...
|
||||
//if c, err := obj.TODOCheckApply(apply); err != nil {
|
||||
//if c, err := obj.TODOCheckApply(ctx, apply); err != nil {
|
||||
// return false, err
|
||||
//} else if !c {
|
||||
// checkOK = false
|
||||
@@ -229,8 +237,8 @@ func (obj *ConfigEtcdRes) Interrupt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct.
|
||||
// It is primarily useful for setting the defaults.
|
||||
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
|
||||
// primarily useful for setting the defaults.
|
||||
func (obj *ConfigEtcdRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type rawRes ConfigEtcdRes // indirection to avoid infinite recursion
|
||||
|
||||
|
||||
296
engine/resources/consul_kv.go
Normal file
296
engine/resources/consul_kv.go
Normal file
@@ -0,0 +1,296 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/engine"
|
||||
"github.com/purpleidea/mgmt/engine/traits"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
func init() {
|
||||
engine.RegisterResource("consul:kv", func() engine.Res { return &ConsulKVRes{} })
|
||||
}
|
||||
|
||||
// ConsulKVRes is a resource that writes a value into a Consul datastore. The
|
||||
// name of the resource can either be the key name, or the concatenation of the
|
||||
// server address and the key name: http://127.0.0.1:8500/my-key. If the param
|
||||
// keys are specified, then those are used. If the Name cannot be properly
|
||||
// parsed by url.Parse, then it will be considered as the Key's value. If the
|
||||
// Key is specified explicitly, then we won't use anything from the Name.
|
||||
type ConsulKVRes struct {
|
||||
traits.Base
|
||||
init *engine.Init
|
||||
|
||||
// Key is the name of the key. Defaults to the name of the resource.
|
||||
Key string `lang:"key" yaml:"key"`
|
||||
|
||||
// Value is the value for the key.
|
||||
Value string `lang:"value" yaml:"value"`
|
||||
|
||||
// Scheme is the URI scheme for the Consul server. Default: http.
|
||||
Scheme string `lang:"scheme" yaml:"scheme"`
|
||||
|
||||
// Address is the address of the Consul server. Default: 127.0.0.1:8500.
|
||||
Address string `lang:"address" yaml:"address"`
|
||||
|
||||
// Token is used to provide an ACL token to use for this resource.
|
||||
Token string `lang:"token" yaml:"token"`
|
||||
|
||||
client *api.Client
|
||||
config *api.Config // needed to close the idle connections
|
||||
once bool // safety token
|
||||
key string // cache the key name to avoid re-running the parser
|
||||
}
|
||||
|
||||
// Default returns some sensible defaults for this resource.
|
||||
func (obj *ConsulKVRes) Default() engine.Res {
|
||||
return &ConsulKVRes{}
|
||||
}
|
||||
|
||||
// Validate if the params passed in are valid data.
|
||||
func (obj *ConsulKVRes) Validate() error {
|
||||
s, _, k := obj.inputParser()
|
||||
if k == "" {
|
||||
return fmt.Errorf("the Key is empty")
|
||||
}
|
||||
if s != "" && s != "http" && s != "https" {
|
||||
return fmt.Errorf("unknown Scheme")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init runs some startup code for this resource.
|
||||
func (obj *ConsulKVRes) Init(init *engine.Init) error {
|
||||
obj.init = init // save for later
|
||||
|
||||
s, a, k := obj.inputParser()
|
||||
|
||||
obj.config = api.DefaultConfig()
|
||||
if s != "" {
|
||||
obj.config.Scheme = s
|
||||
}
|
||||
if a != "" {
|
||||
obj.config.Address = obj.Address
|
||||
}
|
||||
obj.key = k // store the key
|
||||
obj.init.Logf("using consul key: %s", obj.key)
|
||||
|
||||
if obj.Token != "" {
|
||||
obj.config.Token = obj.Token
|
||||
}
|
||||
|
||||
var err error
|
||||
obj.client, err = api.NewClient(obj.config)
|
||||
return errwrap.Wrapf(err, "could not create Consul client")
|
||||
}
|
||||
|
||||
// Cleanup is run by the engine to clean up after the resource is done.
|
||||
func (obj *ConsulKVRes) Cleanup() error {
|
||||
if obj.config != nil && obj.config.Transport != nil {
|
||||
obj.config.Transport.CloseIdleConnections()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch is the listener and main loop for this resource and it outputs events.
|
||||
func (obj *ConsulKVRes) Watch(ctx context.Context) error {
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
|
||||
ch := make(chan error)
|
||||
exit := make(chan struct{})
|
||||
|
||||
kv := obj.client.KV()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
defer wg.Done()
|
||||
|
||||
opts := &api.QueryOptions{RequireConsistent: true}
|
||||
innerCtx, cancel := util.ContextWithCloser(context.Background(), exit)
|
||||
defer cancel()
|
||||
opts = opts.WithContext(innerCtx)
|
||||
|
||||
for {
|
||||
_, meta, err := kv.Get(obj.key, opts)
|
||||
select {
|
||||
case ch <- err: // send
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// WaitIndex = 0, which means that it is the
|
||||
// first time we run the query, as we are about
|
||||
// to change the WaitIndex to make a blocking
|
||||
// query, we can consider the watch started.
|
||||
opts.WaitIndex = meta.LastIndex
|
||||
if opts.WaitIndex != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !obj.once {
|
||||
obj.init.Running()
|
||||
obj.once = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Unexpected situation, bug in consul API...
|
||||
select {
|
||||
case ch <- fmt.Errorf("unexpected behaviour in Consul API"):
|
||||
case <-ctx.Done(): // signal for shutdown request
|
||||
}
|
||||
|
||||
case <-ctx.Done(): // signal for shutdown request
|
||||
}
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
defer close(exit)
|
||||
for {
|
||||
select {
|
||||
case err, ok := <-ch:
|
||||
if !ok { // channel shutdown
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "unknown %s watcher error", obj)
|
||||
}
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("event!")
|
||||
}
|
||||
obj.init.Event()
|
||||
|
||||
case <-ctx.Done(): // signal for shutdown request
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckApply is run to check the state and, if apply is true, to apply the
|
||||
// necessary changes to reach the desired state. This is run before Watch and
|
||||
// again if Watch finds a change occurring to the state.
|
||||
func (obj *ConsulKVRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
// XXX: use ctx for get and put
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("consul key: %s", obj.key)
|
||||
}
|
||||
kv := obj.client.KV()
|
||||
pair, _, err := kv.Get(obj.key, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if pair != nil && string(pair.Value) == obj.Value {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !apply {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
p := &api.KVPair{Key: obj.key, Value: []byte(obj.Value)}
|
||||
_, err = kv.Put(p, nil)
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Cmp compares two resources and return if they are equivalent.
|
||||
func (obj *ConsulKVRes) Cmp(r engine.Res) error {
|
||||
res, ok := r.(*ConsulKVRes)
|
||||
if !ok {
|
||||
return fmt.Errorf("not a %s", obj.Kind())
|
||||
}
|
||||
|
||||
if obj.Key != res.Key {
|
||||
return fmt.Errorf("the Key param differs")
|
||||
}
|
||||
if obj.Value != res.Value {
|
||||
return fmt.Errorf("the Value param differs")
|
||||
}
|
||||
if obj.Scheme != res.Scheme {
|
||||
return fmt.Errorf("the Scheme param differs")
|
||||
}
|
||||
if obj.Address != res.Address {
|
||||
return fmt.Errorf("the Address param differs")
|
||||
}
|
||||
if obj.Token != res.Token {
|
||||
return fmt.Errorf("the Token param differs")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// inputParser parses the Name() of a resource and extracts the scheme, address,
|
||||
// and key name of a consul key. We don't have an error, because if we have one,
|
||||
// then it means the input must be a raw key. Output of this function is scheme,
|
||||
// address (includes hostname and port), and key. This also takes our parameters
|
||||
// in to account, and applies the correct overrides if they are specified there.
|
||||
func (obj *ConsulKVRes) inputParser() (string, string, string) {
|
||||
// If the key is specified explicitly, then we're not going to parse the
|
||||
// resource name for a pattern, and we use our given params as they are.
|
||||
if obj.Key != "" {
|
||||
return obj.Scheme, obj.Address, obj.Key
|
||||
}
|
||||
|
||||
// Now we parse...
|
||||
u, err := url.Parse(obj.Name())
|
||||
if err != nil {
|
||||
// If this didn't work, then we know it's explicitly a raw key.
|
||||
return obj.Scheme, obj.Address, obj.Name()
|
||||
}
|
||||
|
||||
// Otherwise, we use the parse result, and we overwrite any of the
|
||||
// fields if we have an explicit param that was specified.
|
||||
k := u.Path
|
||||
s := u.Scheme
|
||||
a := u.Host
|
||||
|
||||
//if obj.Key != "" { // this is now guaranteed to never happen
|
||||
// k = obj.Key
|
||||
//}
|
||||
if obj.Scheme != "" {
|
||||
s = obj.Scheme
|
||||
}
|
||||
if obj.Address != "" {
|
||||
a = obj.Address
|
||||
}
|
||||
|
||||
return s, a, k
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user