Compare commits
515 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e40819d617 | ||
|
|
7331d3a7ee | ||
|
|
95f353c6a4 | ||
|
|
5044ef4e8a | ||
|
|
3c61d088ab | ||
|
|
315a493565 | ||
|
|
6268b61a7d | ||
|
|
3f202c6a7a | ||
|
|
d46c43df5a | ||
|
|
1538befc93 | ||
|
|
1af334f2ce | ||
|
|
d30ea571f1 | ||
|
|
d30ff6cfae | ||
|
|
1d3f2dbe3c | ||
|
|
ca6e7ad432 | ||
|
|
f92afe9ae4 | ||
|
|
483cc22c32 | ||
|
|
2f3bd72491 | ||
|
|
6499fcb1e0 | ||
|
|
12a0600d38 | ||
|
|
cace2bacb8 | ||
|
|
05d440114a | ||
|
|
b392285e1d | ||
|
|
a713c08585 | ||
|
|
8e8e831e73 | ||
|
|
86b95b2c0b | ||
|
|
4a578ca40c | ||
|
|
a60148f370 | ||
|
|
00366de67b | ||
|
|
a08ba0b0e9 | ||
|
|
81b102ed7f | ||
|
|
c8f911ec5d | ||
|
|
7694da4241 | ||
|
|
a0d500a602 | ||
|
|
553172992f | ||
|
|
e6d614f4dd | ||
|
|
3107dfbd08 | ||
|
|
802823dcb0 | ||
|
|
5858c8b501 | ||
|
|
2561dba8f5 | ||
|
|
f5806e0617 | ||
|
|
e9dbb7b86c | ||
|
|
28f5b8331a | ||
|
|
5ff4f0456a | ||
|
|
82c614f2d9 | ||
|
|
50265d2303 | ||
|
|
ecee84aa28 | ||
|
|
2e146e8c8e | ||
|
|
097efdd66a | ||
|
|
5764c977f1 | ||
|
|
4d30772b3b | ||
|
|
8472b1ebf2 | ||
|
|
e1070d3e13 | ||
|
|
98d7f294eb | ||
|
|
517fc1e05b | ||
|
|
c2f75d64a6 | ||
|
|
380004b1dc | ||
|
|
28a443d11d | ||
|
|
a600e11100 | ||
|
|
7b45f94bb0 | ||
|
|
acdd6476f2 | ||
|
|
018d3efc90 | ||
|
|
b40d10a366 | ||
|
|
a88034ab06 | ||
|
|
907d2ad1a1 | ||
|
|
3bd6986fde | ||
|
|
43bd847bad | ||
|
|
0c0583adc8 | ||
|
|
c642b5eeae | ||
|
|
69e84fbbed | ||
|
|
f8b06f32ec | ||
|
|
59a20f53eb | ||
|
|
83fd8b7e54 | ||
|
|
098ab20ec9 | ||
|
|
a2ce9e890d | ||
|
|
be7a5399e3 | ||
|
|
3fb492f6aa | ||
|
|
e4f062b006 | ||
|
|
422719c345 | ||
|
|
71a1efde99 | ||
|
|
ed84c5460c | ||
|
|
0222a682fc | ||
|
|
1cd4af5838 | ||
|
|
d1aaf6e82b | ||
|
|
52a71f9515 | ||
|
|
3c665174cc | ||
|
|
93eb8b2b76 | ||
|
|
1692235498 | ||
|
|
a6bcd4b92b | ||
|
|
d065cddf5e | ||
|
|
20d4809e8e | ||
|
|
b074386c26 | ||
|
|
b140b2dfeb | ||
|
|
8e3d959500 | ||
|
|
8c886bbe7c | ||
|
|
7d204dfb74 | ||
|
|
583f90dc7b | ||
|
|
85e1d6c0e8 | ||
|
|
2c967e3897 | ||
|
|
202a8e1fba | ||
|
|
e6085d77ff | ||
|
|
10f82c6566 | ||
|
|
3d11b2caaf | ||
|
|
f8037a1f99 | ||
|
|
067eef9007 | ||
|
|
e45d9be065 | ||
|
|
d24149518c | ||
|
|
d403f18b2a | ||
|
|
1f12150d8f | ||
|
|
d3a7cefcc6 | ||
|
|
a8c8f09aa3 | ||
|
|
b03fdeccae | ||
|
|
6c12e8a29b | ||
|
|
310452542b | ||
|
|
b514022713 | ||
|
|
c937280664 | ||
|
|
898b58e3e7 | ||
|
|
74119a0a53 | ||
|
|
d6914d3437 | ||
|
|
fdfa03685c | ||
|
|
149a85fcde | ||
|
|
65f26769ae | ||
|
|
6397c8f930 | ||
|
|
761030b5b8 | ||
|
|
9a752da13d | ||
|
|
13fc711657 | ||
|
|
6419f931ee | ||
|
|
562138cb74 | ||
|
|
8aac770bcb | ||
|
|
80e8c9cadc | ||
|
|
87b3dda867 | ||
|
|
b9e093cd6b | ||
|
|
06a023ca66 | ||
|
|
ccb4c6244d | ||
|
|
4489e5ce6e | ||
|
|
8df82f0301 | ||
|
|
57b4a7efce | ||
|
|
fd508fbc0d | ||
|
|
a4f368fc9f | ||
|
|
e7b57a32fd | ||
|
|
06cc63fcb6 | ||
|
|
e34212a10b | ||
|
|
5f6e07b5e8 | ||
|
|
1465c5cdc9 | ||
|
|
29eebd0d07 | ||
|
|
5bbc06d8bc | ||
|
|
9a5f6a5bd3 | ||
|
|
2e774215e4 | ||
|
|
1327752725 | ||
|
|
118f266211 | ||
|
|
87a2dfc8f9 | ||
|
|
b88ac4603f | ||
|
|
28e81bcca3 | ||
|
|
3d0660559e | ||
|
|
48dc9ad099 | ||
|
|
fd3a2a1f0f | ||
|
|
c6e9175e3f | ||
|
|
1a39472734 | ||
|
|
bfa88e9b1c | ||
|
|
a0972c0752 | ||
|
|
8dc0d44513 | ||
|
|
8594b6e2a9 | ||
|
|
82cac572ca | ||
|
|
da4f69cd87 | ||
|
|
e6cb776eb6 | ||
|
|
7557114b4e | ||
|
|
001e1a5da0 | ||
|
|
6f3c3c318b | ||
|
|
654e376be7 | ||
|
|
211121cdca | ||
|
|
f2d4cac92d | ||
|
|
c5dc9c7650 | ||
|
|
7596f5b572 | ||
|
|
8e9c3b6c1e | ||
|
|
a93c98402a | ||
|
|
b04ee4ba22 | ||
|
|
65b104ea55 | ||
|
|
562eb643fc | ||
|
|
80178422db | ||
|
|
e94f39bf2c | ||
|
|
6c1a33066a | ||
|
|
beca0c3ae6 | ||
|
|
7517c83953 | ||
|
|
0354082f89 | ||
|
|
4abcd9cf01 | ||
|
|
c974820c56 | ||
|
|
88670ae7a1 | ||
|
|
d0ed004b24 | ||
|
|
6de7d8b254 | ||
|
|
bfb5d983c1 | ||
|
|
0a183dfff9 | ||
|
|
8b54306eb9 | ||
|
|
fd86b35ce3 | ||
|
|
d9f8dd53c1 | ||
|
|
ccb0e55d5a | ||
|
|
74f747e80b | ||
|
|
aa03b5ce2f | ||
|
|
e747e12002 | ||
|
|
d1753c592a | ||
|
|
7a35bef7ac | ||
|
|
e10e92596f | ||
|
|
28253c4bd2 | ||
|
|
f2976deb02 | ||
|
|
14577a0c46 | ||
|
|
4e18c9c67a | ||
|
|
d326917432 | ||
|
|
ad4eb86262 | ||
|
|
5c73e7c582 | ||
|
|
dc33d9aab7 | ||
|
|
cdc2439f89 | ||
|
|
318ee0d002 | ||
|
|
653299a88f | ||
|
|
6066cbf075 | ||
|
|
2b3a41fefa | ||
|
|
5ca9f7fa38 | ||
|
|
201cf091d5 | ||
|
|
09e53bfd3f | ||
|
|
3c661ab674 | ||
|
|
415e22abe2 | ||
|
|
3b754d5324 | ||
|
|
7a568627e9 | ||
|
|
328360eea8 | ||
|
|
7ae3ba4483 | ||
|
|
351a61c0cd | ||
|
|
c12452b3ce | ||
|
|
0e92d190cc | ||
|
|
453cd4409e | ||
|
|
51cf1e2921 | ||
|
|
dc45c90ccd | ||
|
|
6782d65577 | ||
|
|
68ee163eb1 | ||
|
|
bc4b5d96b0 | ||
|
|
909dbb531d | ||
|
|
a2654bdc69 | ||
|
|
edcb04d1a9 | ||
|
|
29ec867ac7 | ||
|
|
22873b3c3f | ||
|
|
ede5db18d7 | ||
|
|
964b1dc58a | ||
|
|
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 |
1
.ackrc
1
.ackrc
@@ -3,3 +3,4 @@
|
|||||||
--ignore-dir=vendor/
|
--ignore-dir=vendor/
|
||||||
--ignore-dir=releases/
|
--ignore-dir=releases/
|
||||||
--ignore-dir=rpmbuild/
|
--ignore-dir=rpmbuild/
|
||||||
|
--type-set=mcl:ext:mcl
|
||||||
|
|||||||
@@ -23,3 +23,6 @@ indent_style = tab
|
|||||||
|
|
||||||
[*.mcl]
|
[*.mcl]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.txtar]
|
||||||
|
indent_style = tab
|
||||||
|
|||||||
8
.github/workflows/test.yaml
vendored
8
.github/workflows/test.yaml
vendored
@@ -27,9 +27,9 @@ jobs:
|
|||||||
# macos tests are currently failing in CI
|
# macos tests are currently failing in CI
|
||||||
#- macos-latest
|
#- macos-latest
|
||||||
golang_version:
|
golang_version:
|
||||||
# TODO: add 1.20.x and tip
|
# TODO: add 1.24.x and tip
|
||||||
# minimum required and latest published go_version
|
# minimum required and latest published go_version
|
||||||
- 1.19
|
- "1.23"
|
||||||
test_block:
|
test_block:
|
||||||
- basic
|
- basic
|
||||||
- shell
|
- shell
|
||||||
@@ -40,14 +40,14 @@ jobs:
|
|||||||
# Do not shallow fetch. The path can't be absolute, so we need to move it
|
# Do not shallow fetch. The path can't be absolute, so we need to move it
|
||||||
# to the expected location later.
|
# to the expected location later.
|
||||||
- name: Clone mgmt
|
- name: Clone mgmt
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
path: ./go/src/github.com/purpleidea/mgmt
|
path: ./go/src/github.com/purpleidea/mgmt
|
||||||
|
|
||||||
- name: Install Go ${{ matrix.golang_version }}
|
- name: Install Go ${{ matrix.golang_version }}
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.golang_version }}
|
go-version: ${{ matrix.golang_version }}
|
||||||
|
|
||||||
|
|||||||
3
.lycheeignore
Normal file
3
.lycheeignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# list URLs that should be excluded for lychee link checher
|
||||||
|
https://roidelapluie.be
|
||||||
|
https://github.com/purpleidea/mgmt/commit
|
||||||
63
.travis.yml
63
.travis.yml
@@ -1,63 +0,0 @@
|
|||||||
language: go
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
go_import_path: github.com/purpleidea/mgmt
|
|
||||||
sudo: true
|
|
||||||
dist: xenial
|
|
||||||
# travis requires that you update manually, and provides this key to trigger it
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
before_install:
|
|
||||||
# print some debug information to help catch the constant travis regressions
|
|
||||||
- if [ -e /etc/apt/sources.list.d/ ]; then sudo ls -l /etc/apt/sources.list.d/; fi
|
|
||||||
# workaround broken travis NO_PUBKEY errors
|
|
||||||
- if [ -e /etc/apt/sources.list.d/rabbitmq_rabbitmq-server.list ]; then sudo rm -f /etc/apt/sources.list.d/rabbitmq_rabbitmq-server.list; fi
|
|
||||||
- if [ -e /etc/apt/sources.list.d/github_git-lfs.list ]; then sudo rm -f /etc/apt/sources.list.d/github_git-lfs.list; fi
|
|
||||||
# as per a number of comments online, this might mitigate some flaky fails...
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6; fi
|
|
||||||
# apt update tends to be flaky in travis, retry up to 3 times on failure
|
|
||||||
# https://docs.travis-ci.com/user/common-build-problems/#travis_retry
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then travis_retry travis_retry sudo apt update; fi
|
|
||||||
- git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
|
|
||||||
- git fetch --unshallow
|
|
||||||
install: 'make deps'
|
|
||||||
matrix:
|
|
||||||
fast_finish: false
|
|
||||||
allow_failures:
|
|
||||||
- go: 1.20.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.19.x
|
|
||||||
env: TEST_BLOCK=basic
|
|
||||||
- name: "shell tests"
|
|
||||||
go: 1.19.x
|
|
||||||
env: TEST_BLOCK=shell
|
|
||||||
- name: "race tests"
|
|
||||||
go: 1.19.x
|
|
||||||
env: TEST_BLOCK=race
|
|
||||||
- go: 1.20.x
|
|
||||||
- go: tip
|
|
||||||
- os: osx
|
|
||||||
script: 'TEST_BLOCK="$TEST_BLOCK" make test'
|
|
||||||
|
|
||||||
# the "secure" channel value is the result of running: ./misc/travis-encrypt.sh
|
|
||||||
# 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=
|
|
||||||
template:
|
|
||||||
- "%{repository} (%{commit}: %{author}): %{message}"
|
|
||||||
- "More info : %{build_url}"
|
|
||||||
on_success: always
|
|
||||||
on_failure: always
|
|
||||||
use_notice: false
|
|
||||||
skip_join: false
|
|
||||||
email:
|
|
||||||
recipients:
|
|
||||||
- secure: qNkgP6QLl6VXpFQIxas2wggxvIiOmm1/hGRXm4BXsSFzHsJPvMamA3E1HEC7H+luiWTny1jtGSGgTJPV9CX1LtQV0g0S4ThaAvWuKvk3rXO8IVd++iA/Lh1s1H6JdKM0dJtLqFICawjeci4tOQzSvrM2eCBWqT0UYsrQsGHB6AF31GNAH0Acqd5cYeL+ZpbCN+hQEznAZQ7546N25TwqieI8Lg7nisA+lwYYwsaC2+f5RIeyvvKjQv3wzEdBAQ9CI9WQiTOUBnUnyYxMrdomQ/XGF66QnZy9vq5nEP83IFtuhPvSamL7ceT+yJW0jDyBi8sYEV7On7eXzjyHbiYpF4YHcJrFnf5RyV4kQGd6/SC8iZwK4Is4eyeAjDFTC+JafLajw9R9x9bK43BwlRAWOZxjFKe0cU/BVAjmlz87vHgUho2P41+0a5XfajfU6VhA5QFPK6rNH7W1CnA7D/0LmS0yaqJM1OCrm6LfoZEMhe0DxTJ9uWJbr0x1sYao6q8H4xYk+fyRgoBAr2TxYU7kXx8ThiRdzuQ8izdbojlzTYLe8liZMIsjL0axLsLK7YBWrjJUcDFDjR/DqmVxPrvbVFbCi9ChmBw0WmbJvDY0FV8T8dO8wCjg9JEmprAmWPyq0g/F87LFK4tAZqQFJGjP1qwsR9jdwdNTKeCdY656f/Y=
|
|
||||||
on_failure: change
|
|
||||||
on_success: change
|
|
||||||
1
AUTHORS
1
AUTHORS
@@ -11,3 +11,4 @@ Johan Bloemberg
|
|||||||
Jonathan Gold
|
Jonathan Gold
|
||||||
Julien Pivotto
|
Julien Pivotto
|
||||||
Paul Morgan
|
Paul Morgan
|
||||||
|
Samuel Gélineau
|
||||||
|
|||||||
8
COPYING
8
COPYING
@@ -1,7 +1,7 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
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
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
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.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
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.
|
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,
|
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.
|
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
|
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
|
The GNU General Public License does not permit incorporating your program
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
may consider it more useful to permit linking proprietary applications with
|
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
|
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
|
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
|
Mgmt
|
||||||
Copyright (C) 2013-2023+ James Shubin and the project contributors
|
Copyright (C) James Shubin and the project contributors
|
||||||
Written by James Shubin <james@shubin.ca> 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
|
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.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
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.
|
||||||
|
|||||||
267
Makefile
267
Makefile
@@ -1,5 +1,5 @@
|
|||||||
# Mgmt
|
# Mgmt
|
||||||
# Copyright (C) 2013-2023+ James Shubin and the project contributors
|
# Copyright (C) James Shubin and the project contributors
|
||||||
# Written by James Shubin <james@shubin.ca> 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
|
# This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,19 +13,31 @@
|
|||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# 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
|
SHELL = /usr/bin/env bash
|
||||||
.PHONY: all art cleanart version program lang path deps run race generate build build-debug crossbuild clean test gofmt yamlfmt format docs
|
.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: rpmbuild mkdirs rpm srpm spec tar upload upload-sources upload-srpms upload-rpms upload-releases copr tag
|
||||||
.PHONY: mkosi mkosi_fedora-30 mkosi_fedora-29 mkosi_centos-7 mkosi_debian-10 mkosi_ubuntu-bionic mkosi_archlinux
|
.PHONY: mkosi mkosi_fedora-latest mkosi_fedora-older mkosi_stream-latest mkosi_debian-stable mkosi_ubuntu-latest mkosi_archlinux
|
||||||
.PHONY: release releases_path release_fedora-30 release_fedora-29 release_centos-7 release_debian-10 release_ubuntu-bionic release_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
|
.PHONY: funcgen
|
||||||
.SILENT: clean
|
.SILENT: clean
|
||||||
|
|
||||||
# a large amount of output from this `find`, can cause `make` to be much slower!
|
# 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/*')
|
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))
|
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))
|
VERSION := $(or $(VERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --abbrev=0))
|
||||||
@@ -51,7 +63,7 @@ SRPM_BASE = $(PROGRAM)-$(VERSION)-$(RELEASE).src.rpm
|
|||||||
RPM = rpmbuild/RPMS/$(PROGRAM)-$(VERSION)-$(RELEASE).$(ARCH).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 ' ')
|
USERNAME := $(shell cat ~/.config/copr 2>/dev/null | grep username | awk -F '=' '{print $$2}' | tr -d ' ')
|
||||||
SERVER = 'dl.fedoraproject.org'
|
SERVER = 'dl.fedoraproject.org'
|
||||||
REMOTE_PATH = 'pub/alt/$(USERNAME)/$(PROGRAM)'
|
REMOTE_PATH = '/srv/pub/alt/$(USERNAME)/$(PROGRAM)'
|
||||||
ifneq ($(GOTAGS),)
|
ifneq ($(GOTAGS),)
|
||||||
BUILD_FLAGS = -tags '$(GOTAGS)'
|
BUILD_FLAGS = -tags '$(GOTAGS)'
|
||||||
endif
|
endif
|
||||||
@@ -60,27 +72,69 @@ GOOSARCHES ?= linux/amd64 linux/ppc64 linux/ppc64le linux/arm64 darwin/amd64
|
|||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
|
|
||||||
TOKEN_FEDORA-30 = fedora-30
|
# The underscores separate the prefix name ("TOKEN") the distro ("BINARY",
|
||||||
TOKEN_FEDORA-29 = fedora-29
|
# "FEDORA-LATEST", etc...) and the arch ("AMD64"). The distro can have a dash.
|
||||||
TOKEN_CENTOS-7 = centos-7
|
TOKEN_BINARY_AMD64 = $(shell grep -v '#' releases/binary-amd64.release)
|
||||||
TOKEN_DEBIAN-10 = debian-10
|
TOKEN_BINARY_ARM64 = $(shell grep -v '#' releases/binary-arm64.release)
|
||||||
TOKEN_UBUNTU-BIONIC = ubuntu-bionic
|
TOKEN_FEDORA-LATEST = $(shell grep -v '#' releases/fedora-latest.release)
|
||||||
TOKEN_ARCHLINUX = archlinux
|
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_FEDORA-30 = mgmt-$(TOKEN_FEDORA-30)-$(VERSION)-1.x86_64.rpm
|
FILE_BINARY_AMD64 = mgmt-linux-amd64-$(VERSION)
|
||||||
FILE_FEDORA-29 = mgmt-$(TOKEN_FEDORA-29)-$(VERSION)-1.x86_64.rpm
|
FILE_BINARY_ARM64 = mgmt-linux-arm64-$(VERSION)
|
||||||
FILE_CENTOS-7 = mgmt-$(TOKEN_CENTOS-7)-$(VERSION)-1.x86_64.rpm
|
# TODO: add ARCH onto these at the end, eg _AMD64
|
||||||
FILE_DEBIAN-10 = mgmt_$(TOKEN_DEBIAN-10)_$(VERSION)_amd64.deb
|
FILE_FEDORA-LATEST = mgmt-$(TOKEN_FEDORA-LATEST)-$(VERSION)-1.x86_64.rpm
|
||||||
FILE_UBUNTU-BIONIC = mgmt_$(TOKEN_UBUNTU-BIONIC)_$(VERSION)_amd64.deb
|
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
|
FILE_ARCHLINUX = mgmt-$(TOKEN_ARCHLINUX)-$(VERSION)-1-x86_64.pkg.tar.xz
|
||||||
|
|
||||||
PKG_FEDORA-30 = releases/$(VERSION)/$(TOKEN_FEDORA-30)/$(FILE_FEDORA-30)
|
PKG_BINARY_AMD64 = releases/$(VERSION)/$(TOKEN_BINARY_AMD64)/$(FILE_BINARY_AMD64)
|
||||||
PKG_FEDORA-29 = releases/$(VERSION)/$(TOKEN_FEDORA-29)/$(FILE_FEDORA-29)
|
PKG_BINARY_ARM64 = releases/$(VERSION)/$(TOKEN_BINARY_ARM64)/$(FILE_BINARY_ARM64)
|
||||||
PKG_CENTOS-7 = releases/$(VERSION)/$(TOKEN_CENTOS-7)/$(FILE_CENTOS-7)
|
PKG_FEDORA-LATEST = releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/$(FILE_FEDORA-LATEST)
|
||||||
PKG_DEBIAN-10 = releases/$(VERSION)/$(TOKEN_DEBIAN-10)/$(FILE_DEBIAN-10)
|
PKG_FEDORA-OLDER = releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/$(FILE_FEDORA-OLDER)
|
||||||
PKG_UBUNTU-BIONIC = releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/$(FILE_UBUNTU-BIONIC)
|
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)
|
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 = releases/$(VERSION)/SHA256SUMS
|
||||||
SHA256SUMS_ASC = $(SHA256SUMS).asc
|
SHA256SUMS_ASC = $(SHA256SUMS).asc
|
||||||
|
|
||||||
@@ -171,6 +225,8 @@ build-debug: $(PROGRAM)
|
|||||||
GOOS=$(firstword $(subst -, ,$*))
|
GOOS=$(firstword $(subst -, ,$*))
|
||||||
GOARCH=$(lastword $(subst -, ,$*))
|
GOARCH=$(lastword $(subst -, ,$*))
|
||||||
build/mgmt-%: $(GO_FILES) $(MCL_FILES) go.mod go.sum | lang funcgen
|
build/mgmt-%: $(GO_FILES) $(MCL_FILES) go.mod go.sum | lang funcgen
|
||||||
|
@# If you need to run `go mod tidy` then this can trigger.
|
||||||
|
@if [ "$(PKGNAME)" = "" ]; then echo "\$$(PKGNAME) is empty, test with: go list ."; exit 42; fi
|
||||||
@echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..."
|
@echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..."
|
||||||
time env GOOS=${GOOS} GOARCH=${GOARCH} go build $(TRIMPATH) -ldflags=$(PKGNAME)="-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS)
|
time env GOOS=${GOOS} GOARCH=${GOARCH} go build $(TRIMPATH) -ldflags=$(PKGNAME)="-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS)
|
||||||
|
|
||||||
@@ -185,8 +241,8 @@ clean: ## clean things up
|
|||||||
$(MAKE) --quiet -C test clean
|
$(MAKE) --quiet -C test clean
|
||||||
$(MAKE) --quiet -C lang clean
|
$(MAKE) --quiet -C lang clean
|
||||||
$(MAKE) --quiet -C misc/mkosi clean
|
$(MAKE) --quiet -C misc/mkosi clean
|
||||||
rm -f lang/funcs/core/generated_funcs.go || true
|
rm -f lang/core/generated_funcs.go || true
|
||||||
rm -f lang/funcs/core/generated_funcs_test.go || true
|
rm -f lang/core/generated_funcs_test.go || true
|
||||||
[ ! -e $(PROGRAM) ] || rm $(PROGRAM)
|
[ ! -e $(PROGRAM) ] || rm $(PROGRAM)
|
||||||
rm -f *_stringer.go # generated by `go generate`
|
rm -f *_stringer.go # generated by `go generate`
|
||||||
rm -f *_mock.go # generated by `go generate`
|
rm -f *_mock.go # generated by `go generate`
|
||||||
@@ -368,25 +424,25 @@ tag: ## tags a new release
|
|||||||
#
|
#
|
||||||
# mkosi
|
# mkosi
|
||||||
#
|
#
|
||||||
mkosi: mkosi_fedora-30 mkosi_fedora-29 mkosi_centos-7 mkosi_debian-10 mkosi_ubuntu-bionic mkosi_archlinux ## builds distro packages via 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-30: releases/$(VERSION)/.mkdir
|
mkosi_fedora-latest: releases/$(VERSION)/.mkdir
|
||||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||||
|
|
||||||
mkosi_fedora-29: releases/$(VERSION)/.mkdir
|
mkosi_fedora-older: releases/$(VERSION)/.mkdir
|
||||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||||
|
|
||||||
mkosi_centos-7: releases/$(VERSION)/.mkdir
|
mkosi_stream-latest: releases/$(VERSION)/.mkdir
|
||||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||||
|
|
||||||
mkosi_debian-10: releases/$(VERSION)/.mkdir
|
mkosi_debian-stable: releases/$(VERSION)/.mkdir
|
||||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||||
|
|
||||||
mkosi_ubuntu-bionic: releases/$(VERSION)/.mkdir
|
mkosi_ubuntu-latest: releases/$(VERSION)/.mkdir
|
||||||
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
|
||||||
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
|
||||||
|
|
||||||
@@ -404,25 +460,66 @@ releases_path:
|
|||||||
@#Don't put any other output or dependencies in here or they'll show!
|
@#Don't put any other output or dependencies in here or they'll show!
|
||||||
@echo "releases/$(VERSION)/"
|
@echo "releases/$(VERSION)/"
|
||||||
|
|
||||||
release_fedora-30: $(PKG_FEDORA-30)
|
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)
|
||||||
release_fedora-29: $(PKG_FEDORA-29)
|
@echo '$$< denotes ‘the first dependency of the current rule’.'
|
||||||
release_centos-7: $(PKG_CENTOS-7)
|
@echo '> '"$<"
|
||||||
release_debian-10: $(PKG_DEBIAN-10)
|
@echo
|
||||||
release_ubuntu-bionic: $(PKG_UBUNTU-BIONIC)
|
@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)
|
release_archlinux: $(PKG_ARCHLINUX)
|
||||||
|
|
||||||
releases/$(VERSION)/mgmt-release.url: $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX) $(SHA256SUMS_ASC)
|
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..."
|
@echo "Pushing git tag $(VERSION) to origin..."
|
||||||
git push origin $(VERSION)
|
git push origin $(VERSION)
|
||||||
@echo "Creating github release..."
|
@echo "Creating github release..."
|
||||||
hub release create \
|
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." ) \
|
-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 $(PKG_FEDORA-30) \
|
` [ -e $(PKG_BINARY_AMD64) ] && printf -- "-a $(PKG_BINARY_AMD64)" ` \
|
||||||
-a $(PKG_FEDORA-29) \
|
` [ -e $(PKG_BINARY_ARM64) ] && printf -- "-a $(PKG_BINARY_ARM64)" ` \
|
||||||
-a $(PKG_CENTOS-7) \
|
` [ -e $(PKG_FEDORA-LATEST) ] && printf -- "-a $(PKG_FEDORA-LATEST)" ` \
|
||||||
-a $(PKG_DEBIAN-10) \
|
` [ -e $(PKG_FEDORA-OLDER) ] && printf -- "-a $(PKG_FEDORA-OLDER)" ` \
|
||||||
-a $(PKG_UBUNTU-BIONIC) \
|
` [ -e $(PKG_STREAM-LATEST) ] && printf -- "-a $(PKG_STREAM-LATEST)" ` \
|
||||||
-a $(PKG_ARCHLINUX) \
|
` [ -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) \
|
-a $(SHA256SUMS_ASC) \
|
||||||
$(VERSION) \
|
$(VERSION) \
|
||||||
> releases/$(VERSION)/mgmt-release.url \
|
> releases/$(VERSION)/mgmt-release.url \
|
||||||
@@ -430,56 +527,92 @@ releases/$(VERSION)/mgmt-release.url: $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CE
|
|||||||
|| rm -f releases/$(VERSION)/mgmt-release.url
|
|| rm -f releases/$(VERSION)/mgmt-release.url
|
||||||
|
|
||||||
releases/$(VERSION)/.mkdir:
|
releases/$(VERSION)/.mkdir:
|
||||||
mkdir -p releases/$(VERSION)/{$(TOKEN_FEDORA-30),$(TOKEN_FEDORA-29),$(TOKEN_CENTOS-7),$(TOKEN_DEBIAN-10),$(TOKEN_UBUNTU-BIONIC),$(TOKEN_ARCHLINUX)}/ && 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)/$(TOKEN_FEDORA-30)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
# 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)/'} ; echo "Generating: $${distro} changelog..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
||||||
|
|
||||||
$(PKG_FEDORA-30): releases/$(VERSION)/$(TOKEN_FEDORA-30)/changelog
|
$(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)/'} ; echo "Building: $${distro} package..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-30)" libvirt-devel augeas-devel
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-LATEST)" libvirt-devel augeas-devel
|
||||||
|
endif
|
||||||
releases/$(VERSION)/$(TOKEN_FEDORA-29)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
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)/'} ; echo "Generating: $${distro} changelog..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
||||||
|
|
||||||
$(PKG_FEDORA-29): releases/$(VERSION)/$(TOKEN_FEDORA-29)/changelog
|
$(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)/'} ; echo "Building: $${distro} package..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-29)" libvirt-devel augeas-devel
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-OLDER)" libvirt-devel augeas-devel
|
||||||
|
endif
|
||||||
releases/$(VERSION)/$(TOKEN_CENTOS-7)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
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)/'} ; echo "Generating: $${distro} changelog..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
|
||||||
|
|
||||||
$(PKG_CENTOS-7): releases/$(VERSION)/$(TOKEN_CENTOS-7)/changelog
|
$(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)/'} ; echo "Building: $${distro} package..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_CENTOS-7)" libvirt-devel augeas-devel
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_STREAM-LATEST)" libvirt-devel augeas-devel
|
||||||
|
endif
|
||||||
releases/$(VERSION)/$(TOKEN_DEBIAN-10)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
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)/'} ; echo "Generating: $${distro} changelog..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
|
||||||
|
|
||||||
$(PKG_DEBIAN-10): releases/$(VERSION)/$(TOKEN_DEBIAN-10)/changelog
|
$(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)/'} ; echo "Building: $${distro} package..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_DEBIAN-10)" libvirt-dev libaugeas-dev
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_DEBIAN-STABLE)" libvirt-dev libaugeas-dev
|
||||||
|
endif
|
||||||
releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
|
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)/'} ; echo "Generating: $${distro} changelog..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
|
||||||
|
|
||||||
$(PKG_UBUNTU-BIONIC): releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/changelog
|
$(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)/'} ; echo "Building: $${distro} package..."
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_UBUNTU-BIONIC)" libvirt-dev libaugeas-dev
|
@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
|
$(PKG_ARCHLINUX): $(PROGRAM) releases/$(VERSION)/.mkdir
|
||||||
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
|
@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
|
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_ARCHLINUX)" libvirt augeas
|
||||||
|
endif
|
||||||
|
|
||||||
$(SHA256SUMS): $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX)
|
$(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
|
@# remove the directory separator in the SHA256SUMS file
|
||||||
@echo "Generating: sha256 sum..."
|
@echo "Generating: sha256 sum..."
|
||||||
sha256sum $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX) | 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)
|
$(SHA256SUMS_ASC): $(SHA256SUMS)
|
||||||
@echo "Signing sha256 sum..."
|
@echo "Signing sha256 sum..."
|
||||||
@@ -505,9 +638,9 @@ help: ## show this help screen
|
|||||||
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
@echo ''
|
@echo ''
|
||||||
|
|
||||||
funcgen: lang/funcs/core/generated_funcs.go
|
funcgen: lang/core/generated_funcs.go
|
||||||
|
|
||||||
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..."
|
@echo "Generating: funcs..."
|
||||||
@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
|
@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
|
||||||
|
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -5,17 +5,24 @@
|
|||||||
[](https://goreportcard.com/report/github.com/purpleidea/mgmt)
|
[](https://goreportcard.com/report/github.com/purpleidea/mgmt)
|
||||||
[](https://github.com/purpleidea/mgmt/actions/)
|
[](https://github.com/purpleidea/mgmt/actions/)
|
||||||
[](https://godocs.io/github.com/purpleidea/mgmt)
|
[](https://godocs.io/github.com/purpleidea/mgmt)
|
||||||
|
[](https://matrix.to/#/#mgmtconfig:matrix.org)
|
||||||
[](https://web.libera.chat/?channels=#mgmtconfig)
|
[](https://web.libera.chat/?channels=#mgmtconfig)
|
||||||
[](https://www.patreon.com/purpleidea)
|
[](https://www.patreon.com/purpleidea)
|
||||||
[](https://liberapay.com/purpleidea/donate)
|
[](https://liberapay.com/purpleidea/donate)
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> [Resource reference guide now available!](https://mgmtconfig.com/docs/resources/)
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> [Function reference guide now available!](https://mgmtconfig.com/docs/functions/)
|
||||||
|
|
||||||
## About:
|
## About:
|
||||||
|
|
||||||
`Mgmt` is a real-time automation tool. It is familiar to existing configuration
|
`Mgmt` is a real-time automation tool. It is familiar to existing configuration
|
||||||
management software, but is drastically more powerful as it can allow you to
|
management software, but is drastically more powerful as it can allow you to
|
||||||
build real-time, closed-loop feedback systems, in a very safe way, and with a
|
build real-time, closed-loop feedback systems, in a very safe way, and with a
|
||||||
surprisingly small amout of our `mcl` code. For example, the following code will
|
surprisingly small amount of our `mcl` code. For example, the following code
|
||||||
ensure that your file server is set to read-only when it's friday.
|
will ensure that your file server is set to read-only when it's friday.
|
||||||
|
|
||||||
```mcl
|
```mcl
|
||||||
import "datetime"
|
import "datetime"
|
||||||
@@ -65,11 +72,11 @@ Come join us in the `mgmt` community!
|
|||||||
|
|
||||||
| Medium | Link |
|
| Medium | Link |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| Matrix | [#mgmtconfig](https://matrix.to/#/#mgmtconfig:matrix.org) on Matrix.org |
|
||||||
| IRC | [#mgmtconfig](https://web.libera.chat/?channels=#mgmtconfig) on Libera.Chat |
|
| IRC | [#mgmtconfig](https://web.libera.chat/?channels=#mgmtconfig) on Libera.Chat |
|
||||||
| Twitter | [@mgmtconfig](https://twitter.com/mgmtconfig) & [#mgmtconfig](https://twitter.com/hashtag/mgmtconfig) |
|
| 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 |
|
| Patreon | [purpleidea](https://www.patreon.com/purpleidea) on Patreon |
|
||||||
| Liberapay | [purpleidea](https://liberapay.com/purpleidea/donate) on Liberapay |
|
|
||||||
|
|
||||||
## Status:
|
## Status:
|
||||||
|
|
||||||
@@ -91,10 +98,14 @@ Please read, enjoy and help improve our documentation!
|
|||||||
| [quick start guide](docs/quick-start-guide.md) | for everyone |
|
| [quick start guide](docs/quick-start-guide.md) | for everyone |
|
||||||
| [frequently asked questions](docs/faq.md) | for everyone |
|
| [frequently asked questions](docs/faq.md) | for everyone |
|
||||||
| [general documentation](docs/documentation.md) | for everyone |
|
| [general documentation](docs/documentation.md) | for everyone |
|
||||||
|
| [resource reference](https://mgmtconfig.com/docs/resources/) | for everyone |
|
||||||
|
| [function reference](https://mgmtconfig.com/docs/functions/) | for everyone |
|
||||||
| [language guide](docs/language-guide.md) | for everyone |
|
| [language guide](docs/language-guide.md) | for everyone |
|
||||||
| [function guide](docs/function-guide.md) | for mgmt developers |
|
| [function guide](docs/function-guide.md) | for mgmt developers |
|
||||||
| [resource guide](docs/resource-guide.md) | for mgmt developers |
|
| [resource guide](docs/resource-guide.md) | for mgmt developers |
|
||||||
| [style guide](docs/style-guide.md) | for mgmt developers |
|
| [style guide](docs/style-guide.md) | for mgmt developers |
|
||||||
|
| [contributing guide](docs/contributing.md) | for mgmt contributors |
|
||||||
|
| [service API guide](docs/service-guide.md) | for external developers |
|
||||||
| [godoc API reference](https://godoc.org/github.com/purpleidea/mgmt) | for mgmt developers |
|
| [godoc API reference](https://godoc.org/github.com/purpleidea/mgmt) | for mgmt developers |
|
||||||
| [prometheus guide](docs/prometheus.md) | for everyone |
|
| [prometheus guide](docs/prometheus.md) | for everyone |
|
||||||
| [puppet guide](docs/puppet-guide.md) | for puppet sysadmins |
|
| [puppet guide](docs/puppet-guide.md) | for puppet sysadmins |
|
||||||
|
|||||||
183
cli/cli.go
Normal file
183
cli/cli.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"`
|
||||||
|
|
||||||
|
SetupCmd *SetupArgs `arg:"subcommand:setup" help:"setup some bootstrapping tasks"`
|
||||||
|
|
||||||
|
FirstbootCmd *FirstbootArgs `arg:"subcommand:firstboot" help:"run some tasks on first boot"`
|
||||||
|
|
||||||
|
DocsCmd *DocsGenerateArgs `arg:"subcommand:docs" help:"generate documentation"`
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd := obj.SetupCmd; cmd != nil {
|
||||||
|
return cmd.Run(ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd := obj.FirstbootCmd; cmd != nil {
|
||||||
|
return cmd.Run(ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd := obj.DocsCmd; 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{}
|
||||||
265
cli/deploy.go
Normal file
265
cli/deploy.go
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"`
|
||||||
|
DeployPuppet *cliUtil.PuppetArgs `arg:"subcommand:puppet" help:"deploy puppet graph payload"`
|
||||||
|
DeployLangPuppet *cliUtil.LangPuppetArgs `arg:"subcommand:langpuppet" help:"deploy langpuppet 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
|
||||||
|
}
|
||||||
|
if cmd := obj.DeployPuppet; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "puppet"
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
if cmd := obj.DeployLangPuppet; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "langpuppet"
|
||||||
|
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
|
||||||
|
}
|
||||||
150
cli/docs.go
Normal file
150
cli/docs.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||||
|
"github.com/purpleidea/mgmt/docs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DocsGenerateArgs is the CLI parsing structure and type of the parsed result.
|
||||||
|
// This particular one contains all the common flags for the `docs generate`
|
||||||
|
// subcommand.
|
||||||
|
type DocsGenerateArgs struct {
|
||||||
|
docs.Config // embedded config (can't be a pointer) https://github.com/alexflint/go-arg/issues/240
|
||||||
|
|
||||||
|
DocsGenerate *cliUtil.DocsGenerateArgs `arg:"subcommand:generate" help:"generate documentation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 `docs` subcommand.
|
||||||
|
func (obj *DocsGenerateArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var args interface{}
|
||||||
|
if cmd := obj.DocsGenerate; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "generate"
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
_ = name
|
||||||
|
|
||||||
|
Logf := func(format string, v ...interface{}) {
|
||||||
|
// Don't block this globally...
|
||||||
|
//if !data.Flags.Debug {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
data.Flags.Logf("main: "+format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var api docs.API
|
||||||
|
|
||||||
|
if cmd := obj.DocsGenerate; cmd != nil {
|
||||||
|
api = &docs.Generate{
|
||||||
|
DocsGenerateArgs: args.(*cliUtil.DocsGenerateArgs),
|
||||||
|
Config: obj.Config,
|
||||||
|
Program: data.Program,
|
||||||
|
Version: data.Version,
|
||||||
|
Debug: data.Flags.Debug,
|
||||||
|
Logf: Logf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if api == nil {
|
||||||
|
return false, nil // nothing found (display help!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't use these for the setup command in normal operation.
|
||||||
|
if data.Flags.Debug {
|
||||||
|
cliUtil.Hello(data.Program, data.Version, data.Flags) // say hello!
|
||||||
|
defer Logf("goodbye!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// install the exit signal handler
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
defer wg.Wait()
|
||||||
|
exit := make(chan struct{})
|
||||||
|
defer close(exit)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer cancel()
|
||||||
|
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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch count {
|
||||||
|
case 0:
|
||||||
|
data.Flags.Logf("interrupted by ^C")
|
||||||
|
cancel()
|
||||||
|
case 1:
|
||||||
|
data.Flags.Logf("interrupted by ^C (fast pause)")
|
||||||
|
cancel()
|
||||||
|
case 2:
|
||||||
|
data.Flags.Logf("interrupted by ^C (hard interrupt)")
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
|
case <-exit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := api.Main(ctx); err != nil {
|
||||||
|
if data.Flags.Debug {
|
||||||
|
data.Flags.Logf("main: %+v", err)
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
151
cli/firstboot.go
Normal file
151
cli/firstboot.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||||
|
"github.com/purpleidea/mgmt/firstboot"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FirstbootArgs is the CLI parsing structure and type of the parsed result.
|
||||||
|
// This particular one contains all the common flags for the `firstboot`
|
||||||
|
// subcommand.
|
||||||
|
type FirstbootArgs struct {
|
||||||
|
firstboot.Config // embedded config (can't be a pointer) https://github.com/alexflint/go-arg/issues/240
|
||||||
|
|
||||||
|
FirstbootStart *cliUtil.FirstbootStartArgs `arg:"subcommand:start" help:"start firstboot service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 `firstboot` subcommand. The firstboot command as a
|
||||||
|
// service that lets you run commands once on the first boot of a system.
|
||||||
|
func (obj *FirstbootArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var args interface{}
|
||||||
|
if cmd := obj.FirstbootStart; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "pkg"
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
_ = name
|
||||||
|
|
||||||
|
Logf := func(format string, v ...interface{}) {
|
||||||
|
// Don't block this globally...
|
||||||
|
//if !data.Flags.Debug {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
data.Flags.Logf("main: "+format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var api firstboot.API
|
||||||
|
|
||||||
|
if cmd := obj.FirstbootStart; cmd != nil {
|
||||||
|
api = &firstboot.Start{
|
||||||
|
FirstbootStartArgs: args.(*cliUtil.FirstbootStartArgs),
|
||||||
|
Config: obj.Config,
|
||||||
|
Program: data.Program,
|
||||||
|
Version: data.Version,
|
||||||
|
Debug: data.Flags.Debug,
|
||||||
|
Logf: Logf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if api == nil {
|
||||||
|
return false, nil // nothing found (display help!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't use these for the setup command in normal operation.
|
||||||
|
if data.Flags.Debug {
|
||||||
|
cliUtil.Hello(data.Program, data.Version, data.Flags) // say hello!
|
||||||
|
defer Logf("goodbye!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// install the exit signal handler
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
defer wg.Wait()
|
||||||
|
exit := make(chan struct{})
|
||||||
|
defer close(exit)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer cancel()
|
||||||
|
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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch count {
|
||||||
|
case 0:
|
||||||
|
data.Flags.Logf("interrupted by ^C")
|
||||||
|
cancel()
|
||||||
|
case 1:
|
||||||
|
data.Flags.Logf("interrupted by ^C (fast pause)")
|
||||||
|
cancel()
|
||||||
|
case 2:
|
||||||
|
data.Flags.Logf("interrupted by ^C (hard interrupt)")
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
|
case <-exit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := api.Main(ctx); err != nil {
|
||||||
|
if data.Flags.Debug {
|
||||||
|
data.Flags.Logf("main: %+v", err)
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
243
cli/run.go
Normal file
243
cli/run.go
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"`
|
||||||
|
RunPuppet *cliUtil.PuppetArgs `arg:"subcommand:puppet" help:"run puppet graph payload"`
|
||||||
|
RunLangPuppet *cliUtil.LangPuppetArgs `arg:"subcommand:langpuppet" help:"run a combined lang/puppet 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
|
||||||
|
}
|
||||||
|
if cmd := obj.RunPuppet; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "puppet"
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
if cmd := obj.RunLangPuppet; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "langpuppet"
|
||||||
|
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
|
||||||
|
}
|
||||||
180
cli/setup.go
Normal file
180
cli/setup.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||||
|
"github.com/purpleidea/mgmt/setup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetupArgs is the CLI parsing structure and type of the parsed result. This
|
||||||
|
// particular one contains all the common flags for the `setup` subcommand.
|
||||||
|
type SetupArgs struct {
|
||||||
|
setup.Config // embedded config (can't be a pointer) https://github.com/alexflint/go-arg/issues/240
|
||||||
|
|
||||||
|
SetupPkg *cliUtil.SetupPkgArgs `arg:"subcommand:pkg" help:"setup packages"`
|
||||||
|
SetupSvc *cliUtil.SetupSvcArgs `arg:"subcommand:svc" help:"setup services"`
|
||||||
|
SetupFirstboot *cliUtil.SetupFirstbootArgs `arg:"subcommand:firstboot" help:"setup firstboot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 `setup` subcommand. The setup command does some
|
||||||
|
// bootstrap work to help get things going.
|
||||||
|
func (obj *SetupArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var args interface{}
|
||||||
|
if cmd := obj.SetupPkg; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "pkg"
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
if cmd := obj.SetupSvc; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "svc"
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
if cmd := obj.SetupFirstboot; cmd != nil {
|
||||||
|
name = cliUtil.LookupSubcommand(obj, cmd) // "firstboot"
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
_ = name
|
||||||
|
|
||||||
|
Logf := func(format string, v ...interface{}) {
|
||||||
|
// Don't block this globally...
|
||||||
|
//if !data.Flags.Debug {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
data.Flags.Logf("main: "+format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var api setup.API
|
||||||
|
|
||||||
|
if cmd := obj.SetupPkg; cmd != nil {
|
||||||
|
api = &setup.Pkg{
|
||||||
|
SetupPkgArgs: args.(*cliUtil.SetupPkgArgs),
|
||||||
|
Config: obj.Config,
|
||||||
|
Program: data.Program,
|
||||||
|
Version: data.Version,
|
||||||
|
Debug: data.Flags.Debug,
|
||||||
|
Logf: Logf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cmd := obj.SetupSvc; cmd != nil {
|
||||||
|
api = &setup.Svc{
|
||||||
|
SetupSvcArgs: args.(*cliUtil.SetupSvcArgs),
|
||||||
|
Config: obj.Config,
|
||||||
|
Program: data.Program,
|
||||||
|
Version: data.Version,
|
||||||
|
Debug: data.Flags.Debug,
|
||||||
|
Logf: Logf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cmd := obj.SetupFirstboot; cmd != nil {
|
||||||
|
api = &setup.Firstboot{
|
||||||
|
SetupFirstbootArgs: args.(*cliUtil.SetupFirstbootArgs),
|
||||||
|
Config: obj.Config,
|
||||||
|
Program: data.Program,
|
||||||
|
Version: data.Version,
|
||||||
|
Debug: data.Flags.Debug,
|
||||||
|
Logf: Logf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if api == nil {
|
||||||
|
return false, nil // nothing found (display help!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't use these for the setup command in normal operation.
|
||||||
|
if data.Flags.Debug {
|
||||||
|
cliUtil.Hello(data.Program, data.Version, data.Flags) // say hello!
|
||||||
|
defer Logf("goodbye!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// install the exit signal handler
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
defer wg.Wait()
|
||||||
|
exit := make(chan struct{})
|
||||||
|
defer close(exit)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer cancel()
|
||||||
|
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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch count {
|
||||||
|
case 0:
|
||||||
|
data.Flags.Logf("interrupted by ^C")
|
||||||
|
cancel()
|
||||||
|
case 1:
|
||||||
|
data.Flags.Logf("interrupted by ^C (fast pause)")
|
||||||
|
cancel()
|
||||||
|
case 2:
|
||||||
|
data.Flags.Logf("interrupted by ^C (hard interrupt)")
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
|
case <-exit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := api.Main(ctx); err != nil {
|
||||||
|
if data.Flags.Debug {
|
||||||
|
data.Flags.Logf("main: %+v", err)
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
198
cli/util/args.go
Normal file
198
cli/util/args.go
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PuppetArgs is the puppet CLI parsing structure and type of the parsed result.
|
||||||
|
type PuppetArgs struct {
|
||||||
|
// Input is the input puppet code or file path or just "agent".
|
||||||
|
Input string `arg:"positional,required"`
|
||||||
|
|
||||||
|
// PuppetConf is the optional path to a puppet.conf config file.
|
||||||
|
PuppetConf string `arg:"--puppet-conf" help:"full path to the puppet.conf file to use"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LangPuppetArgs is the langpuppet CLI parsing structure and type of the parsed
|
||||||
|
// result.
|
||||||
|
type LangPuppetArgs struct {
|
||||||
|
// LangInput is the input mcl code or file path or any input specification.
|
||||||
|
LangInput string `arg:"--lang,required" help:"the input parameter for the lang module"`
|
||||||
|
|
||||||
|
// PuppetInput is the input puppet code or file path or just "agent".
|
||||||
|
PuppetInput string `arg:"--puppet,required" help:"the input parameter for the puppet module"`
|
||||||
|
|
||||||
|
// copy-pasted from PuppetArgs
|
||||||
|
|
||||||
|
// PuppetConf is the optional path to a puppet.conf config file.
|
||||||
|
PuppetConf string `arg:"--puppet-conf" help:"full path to the puppet.conf file to use"`
|
||||||
|
|
||||||
|
// end PuppetArgs
|
||||||
|
|
||||||
|
// copy-pasted from LangArgs
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
|
||||||
|
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)"`
|
||||||
|
|
||||||
|
// end LangArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupPkgArgs is the setup service CLI parsing structure and type of the
|
||||||
|
// parsed result.
|
||||||
|
type SetupPkgArgs struct {
|
||||||
|
Distro string `arg:"--distro" help:"build for this distro"`
|
||||||
|
Sudo bool `arg:"--sudo" help:"include sudo in the command"`
|
||||||
|
Exec bool `arg:"--exec" help:"actually run these commands"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupSvcArgs is the setup service CLI parsing structure and type of the
|
||||||
|
// parsed result.
|
||||||
|
type SetupSvcArgs struct {
|
||||||
|
BinaryPath string `arg:"--binary-path" help:"path to the binary"`
|
||||||
|
Install bool `arg:"--install" help:"install the systemd mgmt service"`
|
||||||
|
Start bool `arg:"--start" help:"start the mgmt service"`
|
||||||
|
Enable bool `arg:"--enable" help:"enable the mgmt service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupFirstbootArgs is the setup service CLI parsing structure and type of the
|
||||||
|
// parsed result.
|
||||||
|
type SetupFirstbootArgs struct {
|
||||||
|
BinaryPath string `arg:"--binary-path" help:"path to the binary"`
|
||||||
|
Mkdir bool `arg:"--mkdir" help:"make the necessary firstboot dirs"`
|
||||||
|
Install bool `arg:"--install" help:"install the systemd firstboot service"`
|
||||||
|
Start bool `arg:"--start" help:"start the firstboot service (typically not used)"`
|
||||||
|
Enable bool `arg:"--enable" help:"enable the firstboot service"`
|
||||||
|
|
||||||
|
FirstbootStartArgs // Include these options if we want to specify them.
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstbootStartArgs is the firstboot service CLI parsing structure and type of
|
||||||
|
// the parsed result.
|
||||||
|
type FirstbootStartArgs struct {
|
||||||
|
LockFilePath string `arg:"--lock-file-path" help:"path to the lock file"`
|
||||||
|
ScriptsDir string `arg:"--scripts-dir" help:"path to the scripts dir"`
|
||||||
|
DoneDir string `arg:"--done-dir" help:"dir to move done scripts to"`
|
||||||
|
LoggingDir string `arg:"--logging-dir" help:"directory to store logs in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DocsGenerateArgs is the docgen utility CLI parsing structure and type of the
|
||||||
|
// parsed result.
|
||||||
|
type DocsGenerateArgs struct {
|
||||||
|
Output string `arg:"--output" help:"output path to write to"`
|
||||||
|
RootDir string `arg:"--root-dir" help:"path to mgmt source dir"`
|
||||||
|
NoResources bool `arg:"--no-resources" help:"skip resource doc generation"`
|
||||||
|
NoFunctions bool `arg:"--no-functions" help:"skip function doc generation"`
|
||||||
|
}
|
||||||
47
cli/util/hello.go
Normal file
47
cli/util/hello.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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) 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) 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
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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 is a facility for reporting the converged state.
|
||||||
package converger
|
package converger
|
||||||
@@ -29,7 +41,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// New builds a new converger coordinator.
|
// New builds a new converger coordinator.
|
||||||
func New(timeout int64) *Coordinator {
|
func New(timeout int) *Coordinator {
|
||||||
return &Coordinator{
|
return &Coordinator{
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
|
||||||
@@ -61,7 +73,7 @@ func New(timeout int64) *Coordinator {
|
|||||||
type Coordinator struct {
|
type Coordinator struct {
|
||||||
// timeout must be zero (instant) or greater seconds to run. If it's -1
|
// timeout must be zero (instant) or greater seconds to run. If it's -1
|
||||||
// then this is disabled, and we never run stateFns.
|
// then this is disabled, and we never run stateFns.
|
||||||
timeout int64
|
timeout int
|
||||||
|
|
||||||
// mutex is used for controlling access to status and lastid.
|
// mutex is used for controlling access to status and lastid.
|
||||||
mutex *sync.RWMutex
|
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
|
// 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
|
// is useful to avoid passing in the timeout value separately when you're
|
||||||
// already passing in the Coordinator struct.
|
// already passing in the Coordinator struct.
|
||||||
func (obj *Coordinator) Timeout() int64 {
|
func (obj *Coordinator) Timeout() int {
|
||||||
return obj.timeout
|
return obj.timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +387,7 @@ func (obj *Coordinator) Timeout() int64 {
|
|||||||
type UID struct {
|
type UID struct {
|
||||||
// timeout is a copy of the main timeout. It could eventually be used
|
// timeout is a copy of the main timeout. It could eventually be used
|
||||||
// for per-UID timeouts too.
|
// for per-UID timeouts too.
|
||||||
timeout int64
|
timeout int
|
||||||
// isConverged stores the convergence state of this particular UID.
|
// isConverged stores the convergence state of this particular UID.
|
||||||
isConverged bool
|
isConverged bool
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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.
|
||||||
|
|
||||||
//go:build !root
|
//go:build !root
|
||||||
|
|
||||||
|
|||||||
14
debian/copyright
vendored
14
debian/copyright
vendored
@@ -3,7 +3,7 @@ Upstream-Name: mgmt
|
|||||||
Source: <https://github.com/purpleidea/mgmt>
|
Source: <https://github.com/purpleidea/mgmt>
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: Copyright (C) 2013-2023+ James Shubin and the project contributors
|
Copyright: Copyright (C) James Shubin and the project contributors
|
||||||
License: GPL-3.0
|
License: GPL-3.0
|
||||||
|
|
||||||
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
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://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.
|
||||||
|
|||||||
16
doc.go
16
doc.go
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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 provides the main entrypoint for using the `mgmt` software.
|
||||||
package main
|
package main
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.19
|
FROM golang:1.23
|
||||||
|
|
||||||
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>
|
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM fedora:38
|
FROM fedora:41
|
||||||
LABEL org.opencontainers.image.authors="laurent.indermuehle@pm.me"
|
LABEL org.opencontainers.image.authors="laurent.indermuehle@pm.me"
|
||||||
|
|
||||||
ENV GOPATH=/root/gopath
|
ENV GOPATH=/root/gopath
|
||||||
|
|||||||
@@ -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 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}}
|
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.19.12.linux-amd64.tar.gz https://storage.googleapis.com/golang/go1.19.12.linux-amd64.tar.gz && tar -C /usr/local -xzf /opt/go1.19.12.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.23.5.linux-amd64.tar.gz https://storage.googleapis.com/golang/go1.23.5.linux-amd64.tar.gz && tar -C /usr/local -xzf /opt/go1.23.5.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 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
|
RUN go get -u gopkg.in/alecthomas/gometalinter.v1 && cd $GOPATH/src/github.com/purpleidea/mgmt && make deps && make build
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.19
|
FROM golang:1.23
|
||||||
|
|
||||||
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>
|
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'mgmt'
|
project = u'mgmt'
|
||||||
copyright = u'2013-2023+ James Shubin and the project contributors'
|
copyright = u'Copyright (C) James Shubin and the project contributors'
|
||||||
author = u'James Shubin'
|
author = u'James Shubin'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
|||||||
96
docs/contributing.md
Normal file
96
docs/contributing.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
What follows is a short guide with information for participants who wish to
|
||||||
|
contribute to the project. It hopes to set both some expectations and boundaries
|
||||||
|
so that we both benefit.
|
||||||
|
|
||||||
|
## Small patches
|
||||||
|
|
||||||
|
If you have a small patch which you believe is straightforward, should be easy
|
||||||
|
to merge, and isn't overly onerous on your time to write, please feel free to
|
||||||
|
send it our way without asking first. Bug fixes are excellent examples of small
|
||||||
|
patches. Please make sure to familiarize yourself with the rough coding style of
|
||||||
|
the project first, and read through the [style guide](style-guide.md).
|
||||||
|
|
||||||
|
## Making an excellent small patch
|
||||||
|
|
||||||
|
As a special case: We'd like to avoid minimal effort, one-off, drive-by patches
|
||||||
|
by bots and contributors looking to increase their "activity" numbers. As an
|
||||||
|
example: a patch which fixes a small linting issue isn't rousing, but a patch
|
||||||
|
that adds a linter test _and_ fixes a small linting issue is, because it shows
|
||||||
|
you put in more effort.
|
||||||
|
|
||||||
|
## Medium patches
|
||||||
|
|
||||||
|
Medium sized patches are especially welcome. Good examples of these patches
|
||||||
|
can include writing a new `mgmt` resource or function. You'll generally need
|
||||||
|
some knowledge of golang interfaces and concurrency to write these patches.
|
||||||
|
Before writing one of these, please make sure you understand some basics about
|
||||||
|
the project and how the tool works. After this, it is recommended that you join
|
||||||
|
our discussion channel to suggest the idea, and ideally include the actual API
|
||||||
|
you'd like to propose before writing the code and sending a patch.
|
||||||
|
|
||||||
|
## Making an excellent medium patch proposal
|
||||||
|
|
||||||
|
The "API" of a resource is the type signature of the resource struct, and the
|
||||||
|
"API" of a function is the type signature or signatures that it supports. (Since
|
||||||
|
functions can be polymorphic, more than one signature can be possible!) A good
|
||||||
|
proposal would likely also comment on the mechanisms the resources or functions
|
||||||
|
would use to watch for events, to check state, and to apply changes. If these
|
||||||
|
mechanisms need new dependencies, a brief survey of which dependencies are
|
||||||
|
available and why you recommend a particular one is encouraged.
|
||||||
|
|
||||||
|
## Large patches or structural and core patches
|
||||||
|
|
||||||
|
Please do not send us large, core or structurally significant patches without
|
||||||
|
first getting our approval and without getting some medium patches in first.
|
||||||
|
These patches take a lot of effort to review, and we don't want to skimp on our
|
||||||
|
commitment to that if we can't muster it. Instead grow our relationship with you
|
||||||
|
on the medium-sized patches first. (A core patch might refer to something that
|
||||||
|
touches either the function engine, resource engine, compiler internals, or
|
||||||
|
something that is part of one of the internal API's.)
|
||||||
|
|
||||||
|
## Expectations and boundaries
|
||||||
|
|
||||||
|
When interacting with the project and soliciting feedback (either for design or
|
||||||
|
during a code review) please keep in mind that the project (unfortunately!) has
|
||||||
|
time constraints and so must prioritize how it handles workloads. If you are
|
||||||
|
someone who has successfully sent in small patches, we will be more willing to
|
||||||
|
spend time mentoring your medium sized patches and so on. Think of it this way:
|
||||||
|
as you show that you're contributing to the project, we'll contribute more to
|
||||||
|
you. Put another way: we can't afford to spend large amounts of time discussing
|
||||||
|
potential patches with you, just to end up nowhere. Build up your reputation
|
||||||
|
with us, and we hope to help grow our symbiosis with you all the while as you
|
||||||
|
grow too!
|
||||||
|
|
||||||
|
## Energy output
|
||||||
|
|
||||||
|
The same goes for users and issue creators. There are times when we simply don't
|
||||||
|
have the cycles to discuss or litigate an issue with you. We wish we did have
|
||||||
|
more time, but it is finite, and running a project is not free. Therefore,
|
||||||
|
please keep in mind that you don't automatically qualify for free support or
|
||||||
|
attention.
|
||||||
|
|
||||||
|
## Attention seeking behaviours
|
||||||
|
|
||||||
|
Some folks spend too much time starting discussions, commenting on issues,
|
||||||
|
"planning" and otherwise displaying attention seeking behaviours. Please avoid
|
||||||
|
doing this as much as possible, especially if you are not already a major
|
||||||
|
contributor to the project. While it may be well intentioned, if it is
|
||||||
|
indistinguishable to us from intentional interference, then it's not welcome
|
||||||
|
behaviour. Remember that Free Software is not free to write. If you require more
|
||||||
|
attention, then either contribute more to the project, or consider paying for a
|
||||||
|
[support contract](https://mgmtconfig.com/).
|
||||||
|
|
||||||
|
## Consulting
|
||||||
|
|
||||||
|
Having said all that, there are some folks who want to do some longer-term
|
||||||
|
planning to decide if our core design and architecture is right for them to
|
||||||
|
invest in. If that's the case, and you aren't already a well-known project
|
||||||
|
contributor, please [contact](https://mgmtconfig.com/) us for a consulting
|
||||||
|
quote. We have packages available for both individuals and businesses.
|
||||||
|
|
||||||
|
## Respect
|
||||||
|
|
||||||
|
Please be mindful and respectful of others when interacting with the project and
|
||||||
|
its contributors. If you cannot abide by that, you may no longer be welcome.
|
||||||
@@ -16,7 +16,7 @@ be working properly.
|
|||||||
|
|
||||||
## Using Docker
|
## Using Docker
|
||||||
|
|
||||||
Alternatively, you can check out the [docker-guide](docker-guide.md) in order to
|
Alternatively, you can check out the [docker folder](../docker/) in order to
|
||||||
develop or deploy using docker. This method is not endorsed or supported, so use
|
develop or deploy using docker. This method is not endorsed or supported, so use
|
||||||
at your own risk, as it might not be working properly.
|
at your own risk, as it might not be working properly.
|
||||||
|
|
||||||
@@ -28,8 +28,9 @@ required for running the _test_ suite.
|
|||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
* `golang` 1.19 or higher (required, available in some distros and distributed
|
* A modern `golang` version. The version available in the current Fedora
|
||||||
as a binary officially by [golang.org](https://golang.org/dl/))
|
releases is usually supported. This is also distributed as a binary officially
|
||||||
|
by [golang.org](https://golang.org/dl/).
|
||||||
|
|
||||||
### Runtime
|
### Runtime
|
||||||
|
|
||||||
|
|||||||
50
docs/docs.go
Normal file
50
docs/docs.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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 docs provides a tool that generates documentation from the source.
|
||||||
|
//
|
||||||
|
// ./mgmt docs generate --output /tmp/docs.json && cat /tmp/docs.json | jq
|
||||||
|
package docs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API is the simple interface we expect for any setup items.
|
||||||
|
type API interface {
|
||||||
|
// Main runs everything for this setup item.
|
||||||
|
Main(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is a struct of all the configuration values which are shared by all of
|
||||||
|
// the setup utilities. By including this as a separate struct, it can be used
|
||||||
|
// as part of the API if we want.
|
||||||
|
type Config struct {
|
||||||
|
//Foo string `arg:"--foo,env:MGMT_DOCGEN_FOO" help:"Foo..."` // TODO: foo
|
||||||
|
}
|
||||||
@@ -133,13 +133,13 @@ You can read the introductory blog post about this topic here:
|
|||||||
|
|
||||||
### Puppet support
|
### Puppet support
|
||||||
|
|
||||||
You can supply a Puppet manifest instead of creating the (YAML) graph manually.
|
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
|
Puppet must be installed and in `mgmt`'s search path. You also need the
|
||||||
[ffrank-mgmtgraph Puppet module](https://forge.puppet.com/ffrank/mgmtgraph).
|
[ffrank-mgmtgraph puppet module](https://forge.puppet.com/ffrank/mgmtgraph).
|
||||||
|
|
||||||
Invoke `mgmt` with the `--puppet` switch, which supports 3 variants:
|
Invoke `mgmt` with the `--puppet` switch, which supports 3 variants:
|
||||||
|
|
||||||
1. Request the configuration from the Puppet Master (like `puppet agent` does)
|
1. Request the configuration from the puppet server (like `puppet agent` does)
|
||||||
|
|
||||||
`mgmt run puppet --puppet agent`
|
`mgmt run puppet --puppet agent`
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ For more details and caveats see [puppet-guide.md](puppet-guide.md).
|
|||||||
|
|
||||||
#### Blog post
|
#### Blog post
|
||||||
|
|
||||||
An introductory post on the Puppet support is on
|
An introductory post on the puppet support is on
|
||||||
[Felix's blog](http://ffrank.github.io/features/2016/06/19/puppet-powered-mgmt/).
|
[Felix's blog](http://ffrank.github.io/features/2016/06/19/puppet-powered-mgmt/).
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
@@ -274,6 +274,29 @@ and it can't guarantee it if the resource is blocked because of a failed
|
|||||||
pre-requisite resource.
|
pre-requisite resource.
|
||||||
*XXX: This is currently not implemented!*
|
*XXX: This is currently not implemented!*
|
||||||
|
|
||||||
|
#### Dollar
|
||||||
|
|
||||||
|
Boolean. Dollar allows you to have a resource name that starts with a `$` sign.
|
||||||
|
This is false by default. This helps you catch cases when you write code like:
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
$foo = "/tmp/file1"
|
||||||
|
file "$foo" {} # incorrect!
|
||||||
|
```
|
||||||
|
|
||||||
|
The above code would ignore the `$foo` variable and attempt to make a file named
|
||||||
|
`$foo` which would obviously not work. To correctly interpolate a variable, you
|
||||||
|
need to surround the name with curly braces.
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
$foo = "/tmp/file1"
|
||||||
|
file "${foo}" {} # correct!
|
||||||
|
```
|
||||||
|
|
||||||
|
This meta param is a safety measure to make your life easier. It works for all
|
||||||
|
resources. If someone comes up with a resource which would routinely start with
|
||||||
|
a dollar sign, then we can revisit the default for this resource kind.
|
||||||
|
|
||||||
#### Reverse
|
#### Reverse
|
||||||
|
|
||||||
Boolean. Reverse is a property that some resources can implement that specifies
|
Boolean. Reverse is a property that some resources can implement that specifies
|
||||||
@@ -362,29 +385,13 @@ size of 42, you can expect a semaphore if named: `:42`. It is expected that
|
|||||||
consumers of the semaphore metaparameter always include a prefix to avoid a
|
consumers of the semaphore metaparameter always include a prefix to avoid a
|
||||||
collision with this globally defined semaphore. The size value must be greater
|
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
|
than zero at this time. The traditional non-parallel execution found in config
|
||||||
management tools such as `Puppet` can be obtained with `--sema 1`.
|
management tools such as `puppet` can be obtained with `--sema 1`.
|
||||||
|
|
||||||
#### `--allow-interactive`
|
|
||||||
|
|
||||||
Allow interactive prompting for SSH passwords if there is no authentication
|
|
||||||
method that works.
|
|
||||||
|
|
||||||
#### `--ssh-priv-id-rsa`
|
#### `--ssh-priv-id-rsa`
|
||||||
|
|
||||||
Specify the path for finding SSH keys. This defaults to `~/.ssh/id_rsa`. To
|
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.
|
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>`
|
#### `--prefix <path>`
|
||||||
|
|
||||||
Specify a path to a custom working directory prefix. This directory will get
|
Specify a path to a custom working directory prefix. This directory will get
|
||||||
@@ -453,7 +460,7 @@ directory in the git source repository. It is available from:
|
|||||||
|
|
||||||
### Systemd:
|
### Systemd:
|
||||||
|
|
||||||
See [`misc/mgmt.service`](misc/mgmt.service) for a sample systemd unit file.
|
See [`misc/mgmt.service`](../misc/mgmt.service) for a sample systemd unit file.
|
||||||
This unit file is part of the RPM.
|
This unit file is part of the RPM.
|
||||||
|
|
||||||
To specify your custom options for `mgmt` on a systemd distro:
|
To specify your custom options for `mgmt` on a systemd distro:
|
||||||
@@ -486,7 +493,7 @@ To report any bugs, please file a ticket at: [https://github.com/purpleidea/mgmt
|
|||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
Copyright (C) 2013-2023+ James Shubin and the project contributors
|
Copyright (C) James Shubin and the project contributors
|
||||||
|
|
||||||
Please see the
|
Please see the
|
||||||
[AUTHORS](https://github.com/purpleidea/mgmt/tree/master/AUTHORS) file
|
[AUTHORS](https://github.com/purpleidea/mgmt/tree/master/AUTHORS) file
|
||||||
|
|||||||
162
docs/faq.md
162
docs/faq.md
@@ -216,6 +216,42 @@ requires a number of seconds as an argument.
|
|||||||
./mgmt run lang examples/lang/hello0.mcl --converged-timeout=5
|
./mgmt run lang examples/lang/hello0.mcl --converged-timeout=5
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Can I run `mgmt` for type-checking only?
|
||||||
|
|
||||||
|
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`?
|
### 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:
|
If you create a file resource and only specify the content like this:
|
||||||
@@ -244,9 +280,76 @@ 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
|
at that location. It also turns out to simplify the internals significantly, and
|
||||||
remove an ambiguous scenario with the reversible file resource.
|
remove an ambiguous scenario with the reversible file resource.
|
||||||
|
|
||||||
|
### Package resources error with: "The name is not activatable", what's wrong?
|
||||||
|
|
||||||
|
You may see an error like:
|
||||||
|
|
||||||
|
`main: error running auto edges: The name is not activatable`
|
||||||
|
|
||||||
|
This can happen because the mgmt `pkg` resource uses a library and daemon called
|
||||||
|
`PackageKit` to install packages. If it is not installed, then it cannot do its
|
||||||
|
work. On Fedora system you may wish to run `dnf install /usr/bin/pkcon` or on a
|
||||||
|
Debian system you may wish to run `apt install packagekit-tools`.
|
||||||
|
|
||||||
|
PackageKit is excellent because it provides both an API and an event system to
|
||||||
|
watch the package database for changes, and it abstracts away the differences
|
||||||
|
between the various package managers. If you'd prefer to not need to install
|
||||||
|
this tool, then you can contribute a native `pkg:rpm` and `pkg:deb` resource to
|
||||||
|
mgmt!
|
||||||
|
|
||||||
|
### When running mgmt, it says: "module path error: can't find a module path".
|
||||||
|
|
||||||
|
You might get an error along the lines of:
|
||||||
|
|
||||||
|
```
|
||||||
|
could not set scope: import scope `git://github.com/purpleidea/mgmt/modules/some_module_name/` failed: module path error: can't find a module path
|
||||||
|
```
|
||||||
|
|
||||||
|
This usually means that you haven't specified the directory that mgmt should use
|
||||||
|
when looking for modules. This could happen when using mgmt interactively or
|
||||||
|
when it's being run as a service. In such cases you may want the main invocation
|
||||||
|
to look something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
mgmt run lang --module-path '/etc/mgmt/modules/' /etc/mgmt/main.mcl
|
||||||
|
```
|
||||||
|
|
||||||
|
### I get an error: "cannot open shared object file: No such file or directory".
|
||||||
|
|
||||||
|
Mgmt currently uses two libraries that depend on `.so` files being installed on
|
||||||
|
the host. Those are for `augeas` and `libvirt`. If those dependencies are not
|
||||||
|
present, then mgmt will not run. The complete error might look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
mgmt: error while loading shared libraries: libvirt-lxc.so.0: cannot open shared object file: No such file or directory
|
||||||
|
```
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
```
|
||||||
|
mgmt: error while loading shared libraries: libaugeas.so.0: cannot open shared object file: No such file or directory
|
||||||
|
```
|
||||||
|
|
||||||
|
or something similar. There are two solutions to this:
|
||||||
|
|
||||||
|
1. Use a build that doesn't include one or both of those features. You can build
|
||||||
|
that like: `GOTAGS="noaugeas novirt nodocker" make build`.
|
||||||
|
|
||||||
|
2. Install those dependencies. On a Fedora machine you might want to run:
|
||||||
|
|
||||||
|
```
|
||||||
|
dnf install libvirt-devel augeas-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
On a Debian machine you might want to run:
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install libvirt-dev libaugeas-dev
|
||||||
|
```
|
||||||
|
|
||||||
### Why do function names inside of templates include underscores?
|
### Why do function names inside of templates include underscores?
|
||||||
|
|
||||||
The golang template library which we use to implement the template() function
|
The golang template library which we use to implement the golang.template() func
|
||||||
doesn't support the dot notation, so we import all our normal functions, and
|
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`
|
just replace dots with underscores. As an example, the standard `datetime.print`
|
||||||
function is shown within mcl scripts as datetime_print after being imported.
|
function is shown within mcl scripts as datetime_print after being imported.
|
||||||
@@ -284,6 +387,63 @@ 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,
|
solve this, shutdown and running mgmt process, run `rm mgmt` to remove the file,
|
||||||
and then get a new one by running `make` again.
|
and then get a new one by running `make` again.
|
||||||
|
|
||||||
|
### Type unification error with string interpolation.
|
||||||
|
|
||||||
|
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}",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first example will usually error with something along the lines of:
|
||||||
|
|
||||||
|
`unify error with: topLevel(func() { <built-in:concat> }): type error: int != str`
|
||||||
|
|
||||||
|
Now you know why this specific case doesn't work! We may reconsider allowing
|
||||||
|
other types to be pulled into interpolation in the future. If you have a good
|
||||||
|
case for this, then let us know.
|
||||||
|
|
||||||
|
### 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 docs speaks of `--remote` but the CLI errors out?
|
||||||
|
|
||||||
The `--remote` flag existed in an earlier version of mgmt. It was removed and
|
The `--remote` flag existed in an earlier version of mgmt. It was removed and
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ from looking at example code.
|
|||||||
To implement a function, you'll need to create a file that imports the
|
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/)
|
[`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:
|
module. It should probably get created in the correct directory inside of:
|
||||||
[`lang/funcs/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/).
|
[`lang/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/core/). The
|
||||||
The function should be implemented as a `FuncValue` in our type system. It is
|
function should be implemented as a `simple.Scaffold` in our API. It is then
|
||||||
then registered with the engine during `init()`. An example explains it best:
|
registered with the engine during `init()`. An example explains it best:
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
@@ -50,6 +50,7 @@ then registered with the engine during `init()`. An example explains it best:
|
|||||||
package simple
|
package simple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||||
@@ -59,9 +60,10 @@ import (
|
|||||||
// you must register your functions in init when the program starts up
|
// you must register your functions in init when the program starts up
|
||||||
func init() {
|
func init() {
|
||||||
// Example function that squares an int and prints out answer as an str.
|
// Example function that squares an int and prints out answer as an str.
|
||||||
simple.ModuleRegister(ModuleName, "talkingsquare", &types.FuncValue{
|
|
||||||
|
simple.ModuleRegister(ModuleName, "talkingsquare", &simple.Scaffold{
|
||||||
T: types.NewType("func(int) str"), // declare the signature
|
T: types.NewType("func(int) str"), // declare the signature
|
||||||
V: func(input []types.Value) (types.Value, error) {
|
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
|
||||||
i := input[0].Int() // get first arg as an int64
|
i := input[0].Int() // get first arg as an int64
|
||||||
// must return the above specified value
|
// must return the above specified value
|
||||||
return &types.StrValue{
|
return &types.StrValue{
|
||||||
@@ -87,109 +89,41 @@ mgmt engine to shutdown. It should be seen as the equivalent to calling a
|
|||||||
Ideally, your functions should never need to error. You should never cause a
|
Ideally, your functions should never need to error. You should never cause a
|
||||||
real `panic()`, since this could have negative consequences to the system.
|
real `panic()`, since this could have negative consequences to the system.
|
||||||
|
|
||||||
## Simple Polymorphic Function API
|
|
||||||
|
|
||||||
Most functions should be implemented using the simple function API. If they need
|
|
||||||
to have multiple polymorphic forms under the same name, then you can use this
|
|
||||||
API. This is useful for situations when it would be unhelpful to name the
|
|
||||||
functions differently, or when the number of possible signatures for the
|
|
||||||
function would be infinite.
|
|
||||||
|
|
||||||
The canonical example of this is the `len` function which returns the number of
|
|
||||||
elements in either a `list` or a `map`. Since lists and maps are two different
|
|
||||||
types, you can see that polymorphism is more convenient than requiring a
|
|
||||||
`listlen` and `maplen` function. Nevertheless, it is also required because a
|
|
||||||
`list of int` is a different type than a `list of str`, which is a different
|
|
||||||
type than a `list of list of str` and so on. As you can see the number of
|
|
||||||
possible input types for such a `len` function is infinite.
|
|
||||||
|
|
||||||
Another downside to implementing your functions with this API is that they will
|
|
||||||
*not* be made available for use inside templates. This is a limitation of the
|
|
||||||
`golang` template library. In the future if this limitation proves to be
|
|
||||||
significantly annoying, we might consider writing our own template library.
|
|
||||||
|
|
||||||
As with the simple, non-polymorphic API, you can only implement [pure](https://en.wikipedia.org/wiki/Pure_function)
|
|
||||||
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 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/funcs/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/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. 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:
|
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
|
package simple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/lang/funcs/simplepoly"
|
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||||
"github.com/purpleidea/mgmt/lang/types"
|
"github.com/purpleidea/mgmt/lang/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// You may use the simplepoly.ModuleRegister method to register your
|
// This is the actual definition of the `len` function.
|
||||||
// function if it's in a module, as seen in the simple function example.
|
simple.Register("len", &simple.Scaffold{
|
||||||
simplepoly.Register("len", []*types.FuncValue{
|
T: types.NewType("func(?1) int"), // contains a unification var
|
||||||
{
|
C: simple.TypeMatch([]string{ // match on any of these sigs
|
||||||
T: types.NewType("func([]variant) int"),
|
"func(str) int",
|
||||||
V: Len,
|
"func([]?1) int",
|
||||||
},
|
"func(map{?1: ?2}) int",
|
||||||
{
|
}),
|
||||||
T: types.NewType("func({variant: variant}) int"),
|
// The implementation is left as an exercise for the reader.
|
||||||
V: Len,
|
F: Len,
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the number of elements in a list or the number of key pairs in a
|
|
||||||
// map. It can operate on either of these types.
|
|
||||||
func Len(input []types.Value) (types.Value, error) {
|
|
||||||
var length int
|
|
||||||
switch k := input[0].Type().Kind; k {
|
|
||||||
case types.KindList:
|
|
||||||
length = len(input[0].List())
|
|
||||||
case types.KindMap:
|
|
||||||
length = len(input[0].Map())
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported kind: %+v", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.IntValue{
|
|
||||||
V: int64(length),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This simple polymorphic function can accept an infinite number of signatures, of
|
## Simple Polymorphic Function API
|
||||||
which there are two basic forms. Both forms return an `int` as is seen above.
|
|
||||||
The first form takes a `[]variant` which means a `list` of `variant`'s, which
|
|
||||||
means that it can be a list of any type, since `variant` itself is not a
|
|
||||||
concrete type. The second form accepts a `{variant: variant}`, which means that
|
|
||||||
it accepts any form of `map` as input.
|
|
||||||
|
|
||||||
The implementation for both of these forms is the same: it is handled by the
|
Most functions should be implemented using the simple function API. If they need
|
||||||
same `Len` function which is clever enough to be able to deal with any of the
|
to have multiple polymorphic forms under the same name, with each resultant type
|
||||||
type signatures possible from those two patterns.
|
match needing to be paired to a different implementation, then you can use this
|
||||||
|
API. This is useful for situations when the functions differ in output type
|
||||||
At compile time, if your `mcl` code type checks correctly, a concrete type will
|
only.
|
||||||
be known for each and every usage of the `len` function, and specific values
|
|
||||||
will be passed in for this code to compute the length of. As usual, make sure to
|
|
||||||
only write safe code that will not panic! A panic is a bug. If you really cannot
|
|
||||||
continue, then you must return an error.
|
|
||||||
|
|
||||||
## Function API
|
## Function API
|
||||||
|
|
||||||
@@ -342,7 +276,9 @@ method instead.
|
|||||||
|
|
||||||
```golang
|
```golang
|
||||||
// moduleName is already set to "math" by the math package. Do this in `init`.
|
// 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
|
### Composite functions
|
||||||
@@ -356,23 +292,6 @@ We don't expect this functionality to be particularly useful or common, as it's
|
|||||||
probably easier and preferable to simply import common golang library code into
|
probably easier and preferable to simply import common golang library code into
|
||||||
multiple different functions instead.
|
multiple different functions instead.
|
||||||
|
|
||||||
## Polymorphic Function API
|
|
||||||
|
|
||||||
The polymorphic function API is an API that lets you implement functions which
|
|
||||||
do not necessarily have a single static function signature. After compile time,
|
|
||||||
all functions must have a static function signature. We also know that there
|
|
||||||
might be different ways you would want to call `printf`, such as:
|
|
||||||
`printf("the %s is %d", "answer", 42)` or `printf("3 * 2 = %d", 3 * 2)`. Since
|
|
||||||
you couldn't implement the infinite number of possible signatures, this API lets
|
|
||||||
you write code which can be coerced into different forms. This makes
|
|
||||||
implementing what would appear to be generic or polymorphic, instead of
|
|
||||||
something that is actually static and that still has the static type safety
|
|
||||||
properties that were guaranteed by the mgmt language.
|
|
||||||
|
|
||||||
Since this is an advanced topic, it is not described in full at this time. For
|
|
||||||
more information please have a look at the source code comments, some of the
|
|
||||||
existing implementations, and ask around in the community.
|
|
||||||
|
|
||||||
## Frequently asked questions
|
## Frequently asked questions
|
||||||
|
|
||||||
(Send your questions as a patch to this FAQ! I'll review it, merge it, and
|
(Send your questions as a patch to this FAQ! I'll review it, merge it, and
|
||||||
|
|||||||
795
docs/generate.go
Normal file
795
docs/generate.go
Normal file
@@ -0,0 +1,795 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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 docs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||||
|
docsUtil "github.com/purpleidea/mgmt/docs/util"
|
||||||
|
"github.com/purpleidea/mgmt/engine"
|
||||||
|
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||||
|
"github.com/purpleidea/mgmt/lang/funcs"
|
||||||
|
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||||
|
"github.com/purpleidea/mgmt/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// JSONSuffix is the output extension for the generated documentation.
|
||||||
|
JSONSuffix = ".json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generate is the main entrypoint for this command. It generates everything.
|
||||||
|
type Generate struct {
|
||||||
|
*cliUtil.DocsGenerateArgs // embedded config
|
||||||
|
Config // embedded Config
|
||||||
|
|
||||||
|
// Program is the name of this program, usually set at compile time.
|
||||||
|
Program string
|
||||||
|
|
||||||
|
// Version is the version of this program, usually set at compile time.
|
||||||
|
Version string
|
||||||
|
|
||||||
|
// Debug represents if we're running in debug mode or not.
|
||||||
|
Debug bool
|
||||||
|
|
||||||
|
// Logf is a logger which should be used.
|
||||||
|
Logf func(format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main runs everything for this setup item.
|
||||||
|
func (obj *Generate) Main(ctx context.Context) error {
|
||||||
|
if err := obj.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := obj.Run(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate verifies that the structure has acceptable data stored within.
|
||||||
|
func (obj *Generate) Validate() error {
|
||||||
|
if obj == nil {
|
||||||
|
return fmt.Errorf("data is nil")
|
||||||
|
}
|
||||||
|
if obj.Program == "" {
|
||||||
|
return fmt.Errorf("program is empty")
|
||||||
|
}
|
||||||
|
if obj.Version == "" {
|
||||||
|
return fmt.Errorf("version is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run performs the desired actions to generate the documentation.
|
||||||
|
func (obj *Generate) Run(ctx context.Context) error {
|
||||||
|
|
||||||
|
outputFile := obj.DocsGenerateArgs.Output
|
||||||
|
if outputFile == "" || !strings.HasSuffix(outputFile, JSONSuffix) {
|
||||||
|
return fmt.Errorf("must specify output")
|
||||||
|
}
|
||||||
|
// support relative paths too!
|
||||||
|
if !strings.HasPrefix(outputFile, "/") {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputFile = filepath.Join(wd, outputFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Debug {
|
||||||
|
obj.Logf("output: %s", outputFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the directory exists.
|
||||||
|
//d := filepath.Dir(outputFile)
|
||||||
|
//if err := os.MkdirAll(d, 0750); err != nil {
|
||||||
|
// return fmt.Errorf("could not make output dir at: %s", d)
|
||||||
|
//}
|
||||||
|
|
||||||
|
resources, err := obj.genResources()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
functions, err := obj.genFunctions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &Output{
|
||||||
|
Version: safeVersion(obj.Version),
|
||||||
|
Resources: resources,
|
||||||
|
Functions: functions,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b = append(b, '\n') // needs a trailing newline
|
||||||
|
|
||||||
|
if err := os.WriteFile(outputFile, b, 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
obj.Logf("wrote: %s", outputFile)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Generate) getResourceInfo(kind, filename, structName string) (*ResourceInfo, error) {
|
||||||
|
rootDir := obj.DocsGenerateArgs.RootDir
|
||||||
|
if rootDir == "" {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rootDir = wd + "/" // add a trailing slash
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(rootDir, "/") || !strings.HasSuffix(rootDir, "/") {
|
||||||
|
return nil, fmt.Errorf("bad root dir: %s", rootDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filename might be "noop.go" for example
|
||||||
|
p := filepath.Join(rootDir, engine.ResourcesRelDir, filename)
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
|
||||||
|
// f is a: https://golang.org/pkg/go/ast/#File
|
||||||
|
f, err := parser.ParseFile(fset, p, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// mcl field name to golang field name
|
||||||
|
mapping, err := engineUtil.LangFieldNameToStructFieldName(kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// golang field name to mcl field name
|
||||||
|
nameMap, err := util.MapSwap(mapping)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// mcl field name to mcl type
|
||||||
|
typMap, err := engineUtil.LangFieldNameToStructType(kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ri := &ResourceInfo{}
|
||||||
|
// Populate the fields, even if they don't have a comment.
|
||||||
|
ri.Name = structName // golang name
|
||||||
|
ri.Kind = kind // duplicate data
|
||||||
|
ri.File = filename
|
||||||
|
ri.Fields = make(map[string]*ResourceFieldInfo)
|
||||||
|
for mclFieldName, fieldName := range mapping {
|
||||||
|
typ, exists := typMap[mclFieldName]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ri.Fields[mclFieldName] = &ResourceFieldInfo{
|
||||||
|
Name: fieldName,
|
||||||
|
Type: typ.String(),
|
||||||
|
Desc: "", // empty for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousComment *ast.CommentGroup
|
||||||
|
|
||||||
|
// Walk through the AST...
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
|
||||||
|
// Comments above the struct appear as a node right _before_ we
|
||||||
|
// find the struct, so if we see one, save it for later...
|
||||||
|
if cg, ok := node.(*ast.CommentGroup); ok {
|
||||||
|
previousComment = cg
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
typeSpec, ok := node.(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
name := typeSpec.Name.Name // name is now known!
|
||||||
|
|
||||||
|
// If the struct isn't what we're expecting, then move on...
|
||||||
|
if name != structName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the TypeSpec is a named struct type that we want...
|
||||||
|
st, ok := typeSpec.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we have the struct we want...
|
||||||
|
|
||||||
|
var comment *ast.CommentGroup
|
||||||
|
if typeSpec.Doc != nil {
|
||||||
|
// I don't know how to even get here...
|
||||||
|
comment = typeSpec.Doc // found!
|
||||||
|
|
||||||
|
} else if previousComment != nil {
|
||||||
|
comment = previousComment // found!
|
||||||
|
previousComment = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ri.Desc = commentCleaner(comment)
|
||||||
|
|
||||||
|
// Iterate over the fields of the struct
|
||||||
|
for _, field := range st.Fields.List {
|
||||||
|
// Check if the field has a comment associated with it
|
||||||
|
if field.Doc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(field.Names) < 1 { // XXX: why does this happen?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldName := field.Names[0].Name
|
||||||
|
if fieldName == "" { // Can this happen?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isPrivate(fieldName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mclFieldName, exists := nameMap[fieldName]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ri.Fields[mclFieldName].Desc = commentCleaner(field.Doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return ri, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Generate) genResources() (map[string]*ResourceInfo, error) {
|
||||||
|
resources := make(map[string]*ResourceInfo)
|
||||||
|
if obj.DocsGenerateArgs.NoResources {
|
||||||
|
return resources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := engine.RegisteredResourcesNames()
|
||||||
|
sort.Strings(r)
|
||||||
|
for _, kind := range r {
|
||||||
|
metadata, err := docsUtil.LookupResource(kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(kind, "_") {
|
||||||
|
// TODO: Should we display these somehow?
|
||||||
|
// built-in resource
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ri, err := obj.getResourceInfo(kind, metadata.Filename, metadata.Typename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ri.Name == "" {
|
||||||
|
return nil, fmt.Errorf("empty resource name: %s", kind)
|
||||||
|
}
|
||||||
|
if ri.File == "" {
|
||||||
|
return nil, fmt.Errorf("empty resource file: %s", kind)
|
||||||
|
}
|
||||||
|
if ri.Desc == "" {
|
||||||
|
obj.Logf("empty resource desc: %s", kind)
|
||||||
|
}
|
||||||
|
fields := []string{}
|
||||||
|
for field := range ri.Fields {
|
||||||
|
fields = append(fields, field)
|
||||||
|
}
|
||||||
|
sort.Strings(fields)
|
||||||
|
for _, field := range fields {
|
||||||
|
if ri.Fields[field].Desc == "" {
|
||||||
|
obj.Logf("empty resource (%s) field desc: %s", kind, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resources[kind] = ri
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Generate) getFunctionInfo(pkg, name string, metadata *docsUtil.Metadata) (*FunctionInfo, error) {
|
||||||
|
rootDir := obj.DocsGenerateArgs.RootDir
|
||||||
|
if rootDir == "" {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rootDir = wd + "/" // add a trailing slash
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(rootDir, "/") || !strings.HasSuffix(rootDir, "/") {
|
||||||
|
return nil, fmt.Errorf("bad root dir: %s", rootDir)
|
||||||
|
}
|
||||||
|
if metadata.Filename == "" {
|
||||||
|
return nil, fmt.Errorf("empty filename for: %s.%s", pkg, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filename might be "pow.go" for example and contain a rel dir
|
||||||
|
p := filepath.Join(rootDir, funcs.FunctionsRelDir, metadata.Filename)
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
|
||||||
|
// f is a: https://golang.org/pkg/go/ast/#File
|
||||||
|
f, err := parser.ParseFile(fset, p, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fi := &FunctionInfo{}
|
||||||
|
fi.Name = metadata.Typename
|
||||||
|
fi.File = metadata.Filename
|
||||||
|
|
||||||
|
var previousComment *ast.CommentGroup
|
||||||
|
found := false
|
||||||
|
|
||||||
|
rawFunc := func(node ast.Node) (*ast.CommentGroup, string) {
|
||||||
|
fd, ok := node.(*ast.FuncDecl)
|
||||||
|
if !ok {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
return fd.Doc, fd.Name.Name // name is now known!
|
||||||
|
}
|
||||||
|
|
||||||
|
rawStruct := func(node ast.Node) (*ast.CommentGroup, string) {
|
||||||
|
typeSpec, ok := node.(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the TypeSpec is a named struct type that we want...
|
||||||
|
if _, ok := typeSpec.Type.(*ast.StructType); !ok {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeSpec.Doc, typeSpec.Name.Name // name is now known!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk through the AST...
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
|
||||||
|
// Comments above the struct appear as a node right _before_ we
|
||||||
|
// find the struct, so if we see one, save it for later...
|
||||||
|
if cg, ok := node.(*ast.CommentGroup); ok {
|
||||||
|
previousComment = cg
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, name := rawFunc(node) // First see if it's a raw func.
|
||||||
|
if name == "" {
|
||||||
|
doc, name = rawStruct(node) // Otherwise it's a struct.
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the func isn't what we're expecting, then move on...
|
||||||
|
if name != metadata.Typename {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var comment *ast.CommentGroup
|
||||||
|
if doc != nil {
|
||||||
|
// I don't know how to even get here...
|
||||||
|
comment = doc // found!
|
||||||
|
|
||||||
|
} else if previousComment != nil {
|
||||||
|
comment = previousComment // found!
|
||||||
|
previousComment = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi.Desc = commentCleaner(comment)
|
||||||
|
found = true
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
//return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Generate) genFunctions() (map[string]*FunctionInfo, error) {
|
||||||
|
functions := make(map[string]*FunctionInfo)
|
||||||
|
if obj.DocsGenerateArgs.NoFunctions {
|
||||||
|
return functions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m := funcs.Map() // map[string]func() interfaces.Func
|
||||||
|
names := []string{}
|
||||||
|
for name := range m {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Slice(names, func(i, j int) bool {
|
||||||
|
a := names[i]
|
||||||
|
b := names[j]
|
||||||
|
// TODO: do a sorted-by-package order.
|
||||||
|
return a < b
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
//v := m[name]
|
||||||
|
//fn := v()
|
||||||
|
fn := m[name]()
|
||||||
|
|
||||||
|
// eg: golang/strings.has_suffix
|
||||||
|
sp := strings.Split(name, ".")
|
||||||
|
if len(sp) == 0 {
|
||||||
|
return nil, fmt.Errorf("unexpected empty function")
|
||||||
|
}
|
||||||
|
if len(sp) > 2 {
|
||||||
|
return nil, fmt.Errorf("unexpected function name: %s", name)
|
||||||
|
}
|
||||||
|
n := sp[0]
|
||||||
|
p := sp[0]
|
||||||
|
if len(sp) == 1 { // built-in
|
||||||
|
p = "" // no package!
|
||||||
|
}
|
||||||
|
if len(sp) == 2 { // normal import
|
||||||
|
n = sp[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(n, "_") {
|
||||||
|
// TODO: Should we display these somehow?
|
||||||
|
// built-in function
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var sig *string
|
||||||
|
//iface := ""
|
||||||
|
if x := fn.Info().Sig; x != nil {
|
||||||
|
s := x.String()
|
||||||
|
sig = &s
|
||||||
|
//iface = "simple"
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := &docsUtil.Metadata{}
|
||||||
|
|
||||||
|
// XXX: maybe we need a better way to get this?
|
||||||
|
mdFunc, ok := fn.(interfaces.MetadataFunc)
|
||||||
|
if !ok {
|
||||||
|
// Function doesn't tell us what the data is, let's try
|
||||||
|
// to get it automatically...
|
||||||
|
metadata.Typename = funcs.GetFunctionName(fn) // works!
|
||||||
|
metadata.Filename = "" // XXX: How can we get this?
|
||||||
|
|
||||||
|
// XXX: We only need this back-channel metadata store
|
||||||
|
// because we don't know how to get the filename without
|
||||||
|
// manually writing code in each function. Alternatively
|
||||||
|
// we could add a New() method to each struct and then
|
||||||
|
// we could modify the struct instead of having it be
|
||||||
|
// behind a copy which is needed to get new copies!
|
||||||
|
var err error
|
||||||
|
metadata, err = docsUtil.LookupFunction(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if mdFunc == nil {
|
||||||
|
// programming error
|
||||||
|
return nil, fmt.Errorf("unexpected empty metadata for function: %s", name)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
metadata = mdFunc.GetMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata == nil {
|
||||||
|
return nil, fmt.Errorf("unexpected nil metadata for function: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This may be an empty func name if the function did not know
|
||||||
|
// how to get it. (This is normal for automatic regular funcs.)
|
||||||
|
if metadata.Typename == "" {
|
||||||
|
metadata.Typename = funcs.GetFunctionName(fn) // works!
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := obj.getFunctionInfo(p, n, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We may not get any fields added if we can't find anything...
|
||||||
|
fi.Name = metadata.Typename
|
||||||
|
fi.Package = p
|
||||||
|
fi.Func = n
|
||||||
|
fi.File = metadata.Filename
|
||||||
|
//fi.Desc = desc
|
||||||
|
fi.Signature = sig
|
||||||
|
|
||||||
|
// Hack for golang generated functions!
|
||||||
|
if strings.HasPrefix(fi.Package, "golang/") && fi.File == "generated_funcs.go" {
|
||||||
|
pkg := fi.Package[len("golang/"):]
|
||||||
|
frag := strings.TrimPrefix(fi.Name, strings.Title(strings.Join(strings.Split(pkg, "/"), ""))) // yuck
|
||||||
|
fi.File = fmt.Sprintf("https://pkg.go.dev/%s#%s", pkg, frag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Func == "" {
|
||||||
|
return nil, fmt.Errorf("empty function name: %s", name)
|
||||||
|
}
|
||||||
|
if fi.File == "" {
|
||||||
|
return nil, fmt.Errorf("empty function file: %s", name)
|
||||||
|
}
|
||||||
|
if fi.Desc == "" {
|
||||||
|
obj.Logf("empty function desc: %s", name)
|
||||||
|
}
|
||||||
|
if fi.Signature == nil {
|
||||||
|
obj.Logf("empty function sig: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
functions[name] = fi
|
||||||
|
}
|
||||||
|
|
||||||
|
return functions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output is the type of the final data that will be for the json output.
|
||||||
|
type Output struct {
|
||||||
|
// Version is the sha1 or ref name of this specific version. This is
|
||||||
|
// used if we want to generate documentation with links matching the
|
||||||
|
// correct version. If unspecified then this assumes git master.
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
// Resources contains the collection of every available resource!
|
||||||
|
// FIXME: should this be a list instead?
|
||||||
|
Resources map[string]*ResourceInfo `json:"resources"`
|
||||||
|
|
||||||
|
// Functions contains the collection of every available function!
|
||||||
|
// FIXME: should this be a list instead?
|
||||||
|
Functions map[string]*FunctionInfo `json:"functions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceInfo stores some information about each resource.
|
||||||
|
type ResourceInfo struct {
|
||||||
|
// Name is the golang name of this resource.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Kind is the kind of this resource.
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
|
||||||
|
// File is the file name where this resource exists.
|
||||||
|
File string `json:"file"`
|
||||||
|
|
||||||
|
// Desc explains what this resource does.
|
||||||
|
Desc string `json:"description"`
|
||||||
|
|
||||||
|
// Fields is a collection of each resource field and corresponding info.
|
||||||
|
Fields map[string]*ResourceFieldInfo `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceFieldInfo stores some information about each field in each resource.
|
||||||
|
type ResourceFieldInfo struct {
|
||||||
|
// Name is what this field is called in golang format.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Type is the mcl type for this field.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Desc explains what this field does.
|
||||||
|
Desc string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FunctionInfo stores some information about each function.
|
||||||
|
type FunctionInfo struct {
|
||||||
|
// Name is the golang name of this function. This may be an actual
|
||||||
|
// function if used by the simple API, or the name of a struct.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Package is the import name to use to get to this function.
|
||||||
|
Package string `json:"package"`
|
||||||
|
|
||||||
|
// Func is the name of the function in that package.
|
||||||
|
Func string `json:"func"`
|
||||||
|
|
||||||
|
// File is the file name where this function exists.
|
||||||
|
File string `json:"file"`
|
||||||
|
|
||||||
|
// Desc explains what this function does.
|
||||||
|
Desc string `json:"description"`
|
||||||
|
|
||||||
|
// Signature is the type signature of this function. If empty then the
|
||||||
|
// signature is not known statically and it may be polymorphic.
|
||||||
|
Signature *string `json:"signature,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// commentCleaner takes a comment group and returns it as a clean string. It
|
||||||
|
// removes the spurious newlines and programmer-focused comments. If there are
|
||||||
|
// blank lines, it replaces them with a single newline. The idea is that the
|
||||||
|
// webpage formatter would replace the newline with a <br /> or similar. This
|
||||||
|
// code is a modified alternative of the ast.CommentGroup.Text() function.
|
||||||
|
func commentCleaner(g *ast.CommentGroup) string {
|
||||||
|
if g == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
comments := make([]string, len(g.List))
|
||||||
|
for i, c := range g.List {
|
||||||
|
comments[i] = c.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := make([]string, 0, 10) // most comments are less than 10 lines
|
||||||
|
for _, c := range comments {
|
||||||
|
// Remove comment markers.
|
||||||
|
// The parser has given us exactly the comment text.
|
||||||
|
switch c[1] {
|
||||||
|
case '/':
|
||||||
|
//-style comment (no newline at the end)
|
||||||
|
c = c[2:]
|
||||||
|
if len(c) == 0 {
|
||||||
|
// empty line
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if isDevComment(c[1:]) { // get rid of one space
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c[0] == ' ' {
|
||||||
|
// strip first space - required for Example tests
|
||||||
|
c = c[1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
//if isDirective(c) {
|
||||||
|
// // Ignore //go:noinline, //line, and so on.
|
||||||
|
// continue
|
||||||
|
//}
|
||||||
|
case '*':
|
||||||
|
/*-style comment */
|
||||||
|
c = c[2 : len(c)-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split on newlines.
|
||||||
|
cl := strings.Split(c, "\n")
|
||||||
|
|
||||||
|
// Walk lines, stripping trailing white space and adding to list.
|
||||||
|
for _, l := range cl {
|
||||||
|
lines = append(lines, stripTrailingWhitespace(l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading blank lines; convert runs of interior blank lines to a
|
||||||
|
// single blank line.
|
||||||
|
n := 0
|
||||||
|
for _, line := range lines {
|
||||||
|
if line != "" || n > 0 && lines[n-1] != "" {
|
||||||
|
lines[n] = line
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines = lines[0:n]
|
||||||
|
|
||||||
|
// Concatenate all of these together. Blank lines should be a newline.
|
||||||
|
s := ""
|
||||||
|
for i, line := range lines {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s += line
|
||||||
|
if i < len(lines)-1 { // Is there another line?
|
||||||
|
if lines[i+1] == "" {
|
||||||
|
s += "\n" // Will eventually be a line break.
|
||||||
|
} else {
|
||||||
|
s += " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should we use unicode.IsSpace instead?
|
||||||
|
func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' }
|
||||||
|
|
||||||
|
// TODO: should we replace with a strings package stdlib function?
|
||||||
|
func stripTrailingWhitespace(s string) string {
|
||||||
|
i := len(s)
|
||||||
|
for i > 0 && isWhitespace(s[i-1]) {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
return s[0:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPrivate specifies if a field name is "private" or not.
|
||||||
|
func isPrivate(fieldName string) bool {
|
||||||
|
if fieldName == "" {
|
||||||
|
panic("invalid field name")
|
||||||
|
}
|
||||||
|
x := fieldName[0:1]
|
||||||
|
|
||||||
|
if strings.ToLower(x) == x {
|
||||||
|
return true // it was already private
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDevComment tells us that the comment is for developers only!
|
||||||
|
func isDevComment(comment string) bool {
|
||||||
|
if strings.HasPrefix(comment, "TODO:") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(comment, "FIXME:") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(comment, "XXX:") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeVersion parses the main version string and returns a short hash for us.
|
||||||
|
// For example, we might get a string of 0.0.26-176-gabcdef012-dirty as input,
|
||||||
|
// and we'd want to return abcdef012.
|
||||||
|
func safeVersion(version string) string {
|
||||||
|
const dirty = "-dirty"
|
||||||
|
|
||||||
|
s := version
|
||||||
|
if strings.HasSuffix(s, dirty) { // helpful dirty remover
|
||||||
|
s = s[0 : len(s)-len(dirty)]
|
||||||
|
}
|
||||||
|
|
||||||
|
ix := strings.LastIndex(s, "-")
|
||||||
|
if ix == -1 { // assume we have a standalone version (future proofing?)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s = s[ix+1:]
|
||||||
|
|
||||||
|
// From the `git describe` man page: The "g" prefix stands for "git" and
|
||||||
|
// is used to allow describing the version of a software depending on
|
||||||
|
// the SCM the software is managed with. This is useful in an
|
||||||
|
// environment where people may use different SCMs.
|
||||||
|
const g = "g"
|
||||||
|
if strings.HasPrefix(s, g) {
|
||||||
|
s = s[len(g):]
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
@@ -283,6 +283,14 @@ one of many ways you can perform iterative tasks that you might have
|
|||||||
traditionally used a `for` loop for instead. This is preferred, because flow
|
traditionally used a `for` loop for instead. This is preferred, because flow
|
||||||
control is error-prone and can make for less readable code.
|
control is error-prone and can make for less readable code.
|
||||||
|
|
||||||
|
The single `str` variation, may only be used when it is possible for the
|
||||||
|
compiler to determine statically that the value is of that type. Otherwise, it
|
||||||
|
will assume it to be a list of strings. Programmers should explicitly wrap their
|
||||||
|
variables in a string by interpolation to force this static `str` determination,
|
||||||
|
or in square brackets to force a list. The former is generally preferable
|
||||||
|
because it generates a smaller function graph since it doesn't need to build a
|
||||||
|
list.
|
||||||
|
|
||||||
##### Internal edges
|
##### Internal edges
|
||||||
|
|
||||||
Resources may also declare edges internally. The edges may point to or from
|
Resources may also declare edges internally. The edges may point to or from
|
||||||
@@ -337,6 +345,28 @@ to express a relationship between three resources. The first character in the
|
|||||||
resource kind must be capitalized so that the parser can't ascertain
|
resource kind must be capitalized so that the parser can't ascertain
|
||||||
unambiguously that we are referring to a dependency relationship.
|
unambiguously that we are referring to a dependency relationship.
|
||||||
|
|
||||||
|
##### Edge naming
|
||||||
|
|
||||||
|
Each edge must have a unique name of type `str` that is used to uniquely
|
||||||
|
identify that edge, and can be used in the functioning of the edge at its
|
||||||
|
discretion.
|
||||||
|
|
||||||
|
Alternatively, the name value may be a list of strings `[]str` to build a list
|
||||||
|
of edges, each with a name from that list.
|
||||||
|
|
||||||
|
Using this construct is a veiled form of looping (iteration). This technique is
|
||||||
|
one of many ways you can perform iterative tasks that you might have
|
||||||
|
traditionally used a `for` loop for instead. This is preferred, because flow
|
||||||
|
control is error-prone and can make for less readable code.
|
||||||
|
|
||||||
|
The single `str` variation, may only be used when it is possible for the
|
||||||
|
compiler to determine statically that the value is of that type. Otherwise, it
|
||||||
|
will assume it to be a list of strings. Programmers should explicitly wrap their
|
||||||
|
variables in a string by interpolation to force this static `str` determination,
|
||||||
|
or in square brackets to force a list. The former is generally preferable
|
||||||
|
because it generates a smaller function graph since it doesn't need to build a
|
||||||
|
list.
|
||||||
|
|
||||||
#### Class
|
#### Class
|
||||||
|
|
||||||
A class is a grouping structure that bind's a list of statements to a name in
|
A class is a grouping structure that bind's a list of statements to a name in
|
||||||
@@ -402,6 +432,38 @@ time.
|
|||||||
Recursive classes are not currently supported and it is not clear if they will
|
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.
|
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
|
#### Include
|
||||||
|
|
||||||
The `include` statement causes the previously defined class to produce the
|
The `include` statement causes the previously defined class to produce the
|
||||||
@@ -414,6 +476,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
|
Whether the output is useful and whether there is a unique type unification
|
||||||
solution is dependent on your code.
|
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
|
#### Import
|
||||||
|
|
||||||
The `import` statement imports a scope into the specified namespace. A scope can
|
The `import` statement imports a scope into the specified namespace. A scope can
|
||||||
@@ -478,7 +591,7 @@ Lexing is done using [nex](https://github.com/blynn/nex). It is a pure-golang
|
|||||||
implementation which is similar to _Lex_ or _Flex_, but which produces golang
|
implementation which is similar to _Lex_ or _Flex_, but which produces golang
|
||||||
code instead of C. It integrates reasonably well with golang's _yacc_ which is
|
code instead of C. It integrates reasonably well with golang's _yacc_ which is
|
||||||
used for parsing. The token definitions are in:
|
used for parsing. The token definitions are in:
|
||||||
[lang/lexer.nex](https://github.com/purpleidea/mgmt/tree/master/lang/lexer.nex).
|
[lang/lexer.nex](https://github.com/purpleidea/mgmt/tree/master/lang/parser/lexer.nex).
|
||||||
Lexing and parsing run together by calling the `LexParse` method.
|
Lexing and parsing run together by calling the `LexParse` method.
|
||||||
|
|
||||||
#### Parsing
|
#### Parsing
|
||||||
@@ -490,7 +603,7 @@ and trial and error. One small advantage yacc has over standard yacc is that it
|
|||||||
can produce error messages from examples. The best documentation is to examine
|
can produce error messages from examples. The best documentation is to examine
|
||||||
the source. There is a short write up available [here](https://research.swtch.com/yyerror).
|
the source. There is a short write up available [here](https://research.swtch.com/yyerror).
|
||||||
The yacc file exists at:
|
The yacc file exists at:
|
||||||
[lang/parser.y](https://github.com/purpleidea/mgmt/tree/master/lang/parser.y).
|
[lang/parser.y](https://github.com/purpleidea/mgmt/tree/master/lang/parser/parser.y).
|
||||||
Lexing and parsing run together by calling the `LexParse` method.
|
Lexing and parsing run together by calling the `LexParse` method.
|
||||||
|
|
||||||
#### Interpolation
|
#### Interpolation
|
||||||
@@ -502,6 +615,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
|
be better represented by a larger AST. Most nodes in the AST simply return their
|
||||||
own node address, and do not modify the AST.
|
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
|
||||||
|
|
||||||
Scope propagation passes the parent scope (starting with the top-level, built-in
|
Scope propagation passes the parent scope (starting with the top-level, built-in
|
||||||
@@ -522,23 +639,27 @@ so that each `Expr` node in the AST knows what to expect. Type annotation is
|
|||||||
allowed in situations when you want to explicitly specify a type, or when the
|
allowed in situations when you want to explicitly specify a type, or when the
|
||||||
compiler cannot deduce it, however, most of it can usually be inferred.
|
compiler cannot deduce it, however, most of it can usually be inferred.
|
||||||
|
|
||||||
For type inferrence to work, each node in the AST implements a `Unify` method
|
For type inference to work, each `Stmt` node in the AST implements a `TypeCheck`
|
||||||
which is able to return a list of invariants that must hold true. This starts at
|
method which is able to return a list of invariants that must hold true. This
|
||||||
the top most AST node, and gets called through to it's children to assemble a
|
starts at the top most AST node, and gets called through to it's children to
|
||||||
giant list of invariants. The invariants can take different forms. They can
|
assemble a giant list of invariants. The invariants all have the same form. They
|
||||||
specify that a particular expression must have a particular type, or they can
|
specify that a particular expression corresponds to two particular types which
|
||||||
specify that two expressions must have the same types. More complex invariants
|
may both contain unification variables.
|
||||||
allow you to specify relationships between different types and expressions.
|
|
||||||
Furthermore, invariants can allow you to specify that only one invariant out of
|
Each `Expr` node in the AST implements an `Infer` and `Check` method. The
|
||||||
a set must hold true.
|
`Infer` method returns the type of that node along with a list of invariants as
|
||||||
|
described above. Unification variables can of course be used throughout. The
|
||||||
|
`Check` method always uses a generic check implementation and generally doesn't
|
||||||
|
need to be implemented by the user.
|
||||||
|
|
||||||
Once the list of invariants has been collected, they are run through an
|
Once the list of invariants has been collected, they are run through an
|
||||||
invariant solver. The solver can return either return successfully or with an
|
invariant solver. The solver can return either return successfully or with an
|
||||||
error. If the solver returns successfully, it means that it has found a trivial
|
error. If the solver returns successfully, it means that it has found a single
|
||||||
mapping between every expression and it's corresponding type. At this point it
|
mapping between every expression and it's corresponding type. At this point it
|
||||||
is a simple task to run `SetType` on every expression so that the types are
|
is a simple task to run `SetType` on every expression so that the types are
|
||||||
known. If the solver returns in error, it is usually due to one of two
|
known. During this stage, each SetType method verifies that it's a compatible
|
||||||
possibilities:
|
type that it can use. If either that method or if the solver returns in error,
|
||||||
|
it is usually due to one of two possibilities:
|
||||||
|
|
||||||
1. Ambiguity
|
1. Ambiguity
|
||||||
|
|
||||||
@@ -558,8 +679,8 @@ possibilities:
|
|||||||
always happens if the user has made a type error in their program.
|
always happens if the user has made a type error in their program.
|
||||||
|
|
||||||
Only one solver currently exists, but it is possible to easily plug in an
|
Only one solver currently exists, but it is possible to easily plug in an
|
||||||
alternate implementation if someone more skilled in the art of solver design
|
alternate implementation if someone wants to experiment with the art of solver
|
||||||
would like to propose a more logical or performant variant.
|
design and would like to propose a more logical or performant variant.
|
||||||
|
|
||||||
#### Function graph generation
|
#### Function graph generation
|
||||||
|
|
||||||
@@ -600,8 +721,9 @@ If you'd like to create a built-in, core function, you'll need to implement the
|
|||||||
function API interface named `Func`. It can be found in
|
function API interface named `Func`. It can be found in
|
||||||
[lang/interfaces/func.go](https://github.com/purpleidea/mgmt/tree/master/lang/interfaces/func.go).
|
[lang/interfaces/func.go](https://github.com/purpleidea/mgmt/tree/master/lang/interfaces/func.go).
|
||||||
Your function must have a specific type. For example, a simple math function
|
Your function must have a specific type. For example, a simple math function
|
||||||
might have a signature of `func(x int, y int) int`. As you can see, all the
|
might have a signature of `func(x int, y int) int`. The simple functions have
|
||||||
types are known _before_ compile time.
|
their types known _before_ compile time. You may also include unification
|
||||||
|
variables in the function signature as long as the top-level type is a function.
|
||||||
|
|
||||||
A separate discussion on this matter can be found in the [function guide](function-guide.md).
|
A separate discussion on this matter can be found in the [function guide](function-guide.md).
|
||||||
|
|
||||||
@@ -629,6 +751,12 @@ added in the future. This method is usually called before any other, and should
|
|||||||
not depend on any other method being called first. Other methods must not depend
|
not depend on any other method being called first. Other methods must not depend
|
||||||
on this method being called first.
|
on this method being called first.
|
||||||
|
|
||||||
|
If you use any unification variables in the function signature, then your
|
||||||
|
function will *not* be made available for use inside templates. This is a
|
||||||
|
limitation of the `golang` templating library. In the future if this limitation
|
||||||
|
proves to be significantly annoying, we might consider writing our own template
|
||||||
|
library.
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
@@ -639,6 +767,18 @@ func (obj *FooFunc) Info() *interfaces.Info {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
This example contains unification variables.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
func (obj *FooFunc) Info() *interfaces.Info {
|
||||||
|
return &interfaces.Info{
|
||||||
|
Sig: types.NewType("func(a ?1, b ?2, foo [?3]) ?1"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Init
|
### Init
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
@@ -676,7 +816,7 @@ one value must be produced.
|
|||||||
|
|
||||||
```golang
|
```golang
|
||||||
Please see the example functions in
|
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
|
### Stream
|
||||||
@@ -698,52 +838,70 @@ context cancels.
|
|||||||
|
|
||||||
```golang
|
```golang
|
||||||
Please see the example functions in
|
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
|
### BuildableFunc Function API
|
||||||
|
|
||||||
For some functions, it might be helpful to be able to implement a function once,
|
For some functions, it might be helpful to have a function which needs a "build"
|
||||||
but to have multiple polymorphic variants that can be chosen at compile time.
|
step which is run after type unification. This step can be used to build the
|
||||||
For this more advanced topic, you will need to use the
|
function using the determined type, but it may also just be used for checking
|
||||||
[Polymorphic Function API](#polymorphic-function-api). This will help with code
|
that unification picked a valid solution.
|
||||||
reuse when you have a small, finite number of possible type signatures, and also
|
|
||||||
for more complicated cases where you might have an infinite number of possible
|
|
||||||
type signatures. (eg: `[]str`, or `[][]str`, or `[][][]str`, etc...)
|
|
||||||
|
|
||||||
Suppose you want to implement a function which can assume different type
|
Suppose you want to implement a function which can assume different type
|
||||||
signatures. The mgmt language does not support polymorphic types-- you must use
|
signatures. The mgmt language does not support polymorphic types-- you must use
|
||||||
static types throughout the language, however, it is legal to implement a
|
static types throughout the language, however, it is legal to implement a
|
||||||
function which can take different specific type signatures based on how it is
|
function which can take different specific type signatures based on how it is
|
||||||
used. For example, you might wish to add a math function which could take the
|
used. For example, you might wish to add a math function which could take the
|
||||||
form of `func(x int, x int) int` or `func(x float, x float) float` depending on
|
form of `func(x int, y int) int` or `func(x float, y float) float` depending on
|
||||||
the input values. You might also want to implement a function which takes an
|
the input values. For this case you could use a signature containing unification
|
||||||
arbitrary number of input arguments (the number must be statically fixed at the
|
variables, eg: `func(x ?1, y ?1) ?1`. At the end the buildable function would
|
||||||
compile time of your program though) and which returns a string.
|
need to check that it received a `?1` type of either `int` or `float`, since
|
||||||
|
this function might not support doing math on strings. Remember that type
|
||||||
|
unification can only return zero or one solutions, it's not possible to return
|
||||||
|
more than one, which is why this secondary validation step is a brilliant way to
|
||||||
|
filter out invalid solutions without needing to encode them as algebraic
|
||||||
|
conditions during the solver state, which would otherwise make it exponential.
|
||||||
|
|
||||||
The `PolyFunc` interface adds additional methods which you must implement to
|
### InferableFunc Function API
|
||||||
satisfy such a function implementation. If you'd like to implement such a
|
|
||||||
function, then please notify the project authors, and they will expand this
|
|
||||||
section with a longer description of the process.
|
|
||||||
|
|
||||||
#### Examples
|
You might also want to implement a function which takes an arbitrary number of
|
||||||
|
input arguments (the number must be statically fixed at the compile time of your
|
||||||
|
program though) and which returns a string or something else.
|
||||||
|
|
||||||
What follows are a few examples that might help you understand some of the
|
The `InferableFunc` interface adds ad additional `FuncInfer` method which you
|
||||||
language details.
|
must implement to satisfy such a function implementation. This lets you
|
||||||
|
dynamically generate a type signature (including unification variables) and a
|
||||||
|
list of invariants before running the type unification solver. It takes as input
|
||||||
|
a list of the statically known input types and input values (if any) and as well
|
||||||
|
the number of input arguments specified. This is usually enough information to
|
||||||
|
generate a fixed type signature of a fixed size.
|
||||||
|
|
||||||
##### Example Foo
|
Using this API should generally be pretty rare, but it is how certain special
|
||||||
|
functions such as `fmt.printf` are built. If you'd like to implement such a
|
||||||
TODO: please add an example here!
|
function, then please notify the project authors as we're curious about your
|
||||||
|
use case.
|
||||||
##### Example Bar
|
|
||||||
|
|
||||||
TODO: please add an example here!
|
|
||||||
|
|
||||||
## Frequently asked questions
|
## Frequently asked questions
|
||||||
|
|
||||||
(Send your questions as a patch to this FAQ! I'll review it, merge it, and
|
(Send your questions as a patch to this FAQ! I'll review it, merge it, and
|
||||||
respond by commit with the answer.)
|
respond by commit with the answer.)
|
||||||
|
|
||||||
|
### Why am I getting a deploy.readfile error when the file actually exists?
|
||||||
|
|
||||||
|
You may be seeing an error like:
|
||||||
|
|
||||||
|
`readfile`: open /*/files/foo: file does not exist can't read file `/files/foo`?
|
||||||
|
|
||||||
|
If you look, the `foo` file is indeed in the `files/` directory. The problem is
|
||||||
|
that the `files/` directory won't be seen if you didn't specify to include it as
|
||||||
|
part of your deploy. To do so, chances are that all you need to do is add a
|
||||||
|
`metadata.yaml` file into the parent directory to that files folder. This will
|
||||||
|
be used as the entrypoint instead of the naked `main.mcl` file that you have
|
||||||
|
there, and with that metadata entrypoint, you get a default `files/` directory
|
||||||
|
added. You can of course change the `files/` path by setting a key in the
|
||||||
|
`metadata.yaml` file, but we recommend you leave it as the default.
|
||||||
|
|
||||||
### What is the difference between `ExprIf` and `StmtIf`?
|
### What is the difference between `ExprIf` and `StmtIf`?
|
||||||
|
|
||||||
The language contains both an `if` expression, and and `if` statement. An `if`
|
The language contains both an `if` expression, and and `if` statement. An `if`
|
||||||
|
|||||||
@@ -21,15 +21,15 @@ if we missed something that you think is relevant!
|
|||||||
| Felix Frank | blog | [Puppet Powered Mgmt (puppet to mgmt tl;dr)](https://ffrank.github.io/features/2016/06/19/puppet-powered-mgmt/) |
|
| Felix Frank | blog | [Puppet Powered Mgmt (puppet to mgmt tl;dr)](https://ffrank.github.io/features/2016/06/19/puppet-powered-mgmt/) |
|
||||||
| James Shubin | blog | [Automatic clustering in mgmt](https://purpleidea.com/blog/2016/06/20/automatic-clustering-in-mgmt/) |
|
| James Shubin | blog | [Automatic clustering in mgmt](https://purpleidea.com/blog/2016/06/20/automatic-clustering-in-mgmt/) |
|
||||||
| James Shubin | video | [Recording from CoreOSFest 2016](https://www.youtube.com/watch?v=KVmDCUA42wc&html5=1) |
|
| James Shubin | video | [Recording from CoreOSFest 2016](https://www.youtube.com/watch?v=KVmDCUA42wc&html5=1) |
|
||||||
| James Shubin | video | [Recording from DebConf16](http://meetings-archive.debian.net/pub/debian-meetings/2016/debconf16/Next_Generation_Config_Mgmt.webm) ([Slides](https://annex.debconf.org//debconf-share/debconf16/slides/15-next-generation-config-mgmt.pdf)) |
|
| James Shubin | video | [Recording from DebConf16](http://meetings-archive.debian.net/pub/debian-meetings/2016/debconf16/Next_Generation_Config_Mgmt.webm) |
|
||||||
| Felix Frank | blog | [Edging It All In (puppet and mgmt edges)](https://ffrank.github.io/features/2016/07/12/edging-it-all-in/) |
|
| Felix Frank | blog | [Edging It All In (puppet and mgmt edges)](https://ffrank.github.io/features/2016/07/12/edging-it-all-in/) |
|
||||||
| Felix Frank | blog | [Translating All The Things (puppet to mgmt translation warnings)](https://ffrank.github.io/features/2016/08/19/translating-all-the-things/) |
|
| Felix Frank | blog | [Translating All The Things (puppet to mgmt translation warnings)](https://ffrank.github.io/features/2016/08/19/translating-all-the-things/) |
|
||||||
| James Shubin | video | [Recording from systemd.conf 2016](https://www.youtube.com/watch?v=jB992Zb3nH0&html5=1) |
|
| James Shubin | video | [Recording from systemd.conf 2016](https://www.youtube.com/watch?v=_TowsFAWWRA) |
|
||||||
| James Shubin | blog | [Remote execution in mgmt](https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/) |
|
| James Shubin | blog | [Remote execution in mgmt](https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/) |
|
||||||
| James Shubin | video | [Recording from High Load Strategy 2016](https://vimeo.com/191493409) |
|
| James Shubin | video | [Recording from High Load Strategy 2016](https://www.youtube.com/watch?v=-4g14KUVPVk) |
|
||||||
| James Shubin | video | [Recording from NLUUG 2016](https://www.youtube.com/watch?v=MmpwOQAb_SE&html5=1) |
|
| James Shubin | video | [Recording from NLUUG 2016](https://www.youtube.com/watch?v=0vO93ni1zos) |
|
||||||
| James Shubin | blog | [Send/Recv in mgmt](https://purpleidea.com/blog/2016/12/07/sendrecv-in-mgmt/) |
|
| James Shubin | blog | [Send/Recv in mgmt](https://purpleidea.com/blog/2016/12/07/sendrecv-in-mgmt/) |
|
||||||
| Julien Pivotto | blog | [Augeas resource for mgmt](https://roidelapluie.be/blog/2017/02/14/mgmt-augeas/) |
|
| Julien Pivotto | blog | [Augeas resource for mgmt](https://purpleidea.com/cached/mgmt-augeas.html) (Cached from: https://roidelapluie.be/blog/2017/02/14/mgmt-augeas/) |
|
||||||
| James Shubin | blog | [Metaparameters in mgmt](https://purpleidea.com/blog/2017/03/01/metaparameters-in-mgmt/) |
|
| James Shubin | blog | [Metaparameters in mgmt](https://purpleidea.com/blog/2017/03/01/metaparameters-in-mgmt/) |
|
||||||
| James Shubin | video | [Recording from Incontro DevOps 2017](https://vimeo.com/212241877) |
|
| James Shubin | video | [Recording from Incontro DevOps 2017](https://vimeo.com/212241877) |
|
||||||
| Yves Brissaud | blog | [mgmt aux HumanTalks Grenoble (french)](http://log.winsos.net/2017/04/12/mgmt-aux-human-talks-grenoble.html) |
|
| Yves Brissaud | blog | [mgmt aux HumanTalks Grenoble (french)](http://log.winsos.net/2017/04/12/mgmt-aux-human-talks-grenoble.html) |
|
||||||
@@ -57,3 +57,7 @@ if we missed something that you think is relevant!
|
|||||||
| James Shubin | video | [Recording from FOSDEM 2020, Minimalistic Languages Devroom](https://video.fosdem.org/2020/AW1.125/mgmtconfigmore.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 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 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) |
|
||||||
|
| James Shubin | blog | [Mgmt Configuration Language: Functions](https://purpleidea.com/blog/2024/11/22/functions-in-mgmt/) |
|
||||||
|
| James Shubin | blog | [Modules and imports in mgmt](https://purpleidea.com/blog/2024/12/03/modules-and-imports-in-mgmt/) |
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ be avoided.
|
|||||||
### Unsupported resources
|
### Unsupported resources
|
||||||
|
|
||||||
Puppet has a fairly large number of
|
Puppet has a fairly large number of
|
||||||
[built-in types](https://docs.puppet.com/puppet/latest/reference/type.html),
|
[built-in types](https://www.puppet.com/docs/puppet/8/cheatsheet_core_types.html),
|
||||||
and countless more are available through
|
and countless more are available through
|
||||||
[modules](https://forge.puppet.com/). It's unlikely that all of them will
|
[modules](https://forge.puppet.com/). It's unlikely that all of them will
|
||||||
eventually receive native counterparts in `mgmt`.
|
eventually receive native counterparts in `mgmt`.
|
||||||
@@ -95,7 +95,8 @@ For example, translating a plain `file` resource will lead to a warning message:
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ puppet mgmtgraph print --code 'file { "/tmp/mgmt-test": }'
|
$ 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!
|
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
|
The reason is that per default, Puppet assumes the following parameter value
|
||||||
@@ -114,7 +115,7 @@ puppet mgmtgraph print --code 'file { "/tmp/mgmt-test": backup => false }'
|
|||||||
```
|
```
|
||||||
|
|
||||||
This is tedious in a more complex manifest. A good simplification is the
|
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)
|
following [resource default](https://www.puppet.com/docs/puppet/8/lang_defaults)
|
||||||
anywhere on the top scope of your manifest:
|
anywhere on the top scope of your manifest:
|
||||||
|
|
||||||
```puppet
|
```puppet
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ to build your own.
|
|||||||
|
|
||||||
### Downloading a pre-built release:
|
### 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/).
|
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/).
|
An alternate mirror is available [here](https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/).
|
||||||
|
|
||||||
@@ -39,7 +37,7 @@ You'll need some dependencies, including `golang`, and some associated tools.
|
|||||||
|
|
||||||
#### Installing golang
|
#### Installing golang
|
||||||
|
|
||||||
* You need golang version 1.19 or greater installed.
|
* You need a modern golang version installed.
|
||||||
* To install on rpm style systems: `sudo dnf install golang`
|
* To install on rpm style systems: `sudo dnf install golang`
|
||||||
* To install on apt style systems: `sudo apt install golang`
|
* To install on apt style systems: `sudo apt install golang`
|
||||||
* To install on macOS systems install [Homebrew](https://brew.sh)
|
* To install on macOS systems install [Homebrew](https://brew.sh)
|
||||||
@@ -103,13 +101,14 @@ This method avoids polluting your workstation with the dependencies for the
|
|||||||
build. Here is an example using Fedora, Podman and Buildah:
|
build. Here is an example using Fedora, Podman and Buildah:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone --recursive https://github.com/purpleidea/mgmt/ ~/mgmt/
|
git clone --recursive https://github.com/purpleidea/mgmt/
|
||||||
cd ~/mgmt/docker
|
cd mgmt
|
||||||
buildah build -f Dockerfile-fedora.build -t mgmt_build
|
docker build -t mgmt -f docker/Dockerfile .
|
||||||
podman run -d -it --name mgmt_build localhost/mgmt_build
|
docker run --rm --entrypoint cat mgmt mgmt > mgmt
|
||||||
podman cp mgmt_build:/src/github.com/purpleidea/mgmt/mgmt /tmp/mgmt
|
chmod +x mgmt
|
||||||
sudo mv /tmp/mgmt /usr/local/bin # be sure this is in your $PATH
|
./mgmt --version
|
||||||
sudo chown root:root /usr/local/bin/mgmt
|
# you could now copy the mgmt binary somewhere into your $PATH
|
||||||
|
# e.g., /usr/local/bin/ to make it accessible from anywhere
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running mgmt
|
## Running mgmt
|
||||||
|
|||||||
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
|
||||||
142
docs/release-notes/0.0.26
Normal file
142
docs/release-notes/0.0.26
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
I've just released version 0.0.26 of mgmt!
|
||||||
|
|
||||||
|
> 16 files changed, 869 insertions(+), 181 deletions(-)
|
||||||
|
|
||||||
|
Hot off the heels of the recent large release (0.0.25) I've just
|
||||||
|
released an incremental update...
|
||||||
|
|
||||||
|
See more here:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
* Type unification for the provisioning tool is about 40x faster.
|
||||||
|
|
||||||
|
* We fix a small bug related to the upcoming fedora 40 release.
|
||||||
|
|
||||||
|
And much more...
|
||||||
|
|
||||||
|
|
||||||
|
DOWNLOAD
|
||||||
|
|
||||||
|
Prebuilt binaries are available here for this release:
|
||||||
|
https://github.com/purpleidea/mgmt/releases/tag/0.0.26
|
||||||
|
|
||||||
|
They can also be found on the Fedora mirror:
|
||||||
|
https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/0.0.26/
|
||||||
|
|
||||||
|
|
||||||
|
NEWS
|
||||||
|
|
||||||
|
* Added old release notes into git
|
||||||
|
|
||||||
|
* We now skip over unreleased Fedora versions (like "40 Beta") when
|
||||||
|
trying to automatically determine the latest stable release.
|
||||||
|
|
||||||
|
* Type unification was structurally refactored to make way for a bunch
|
||||||
|
of future improvements and generally to modernize the code.
|
||||||
|
|
||||||
|
* Added some unification optimizations and a unification flag
|
||||||
|
optimizations system to allow solvers to support special flags. One of
|
||||||
|
these new flags was used for the provisioner code with a substantial
|
||||||
|
improvement in type unification time by about 40x.
|
||||||
|
|
||||||
|
* New cli args are also available for using these flags.
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
* General 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:
|
||||||
|
James Shubin
|
||||||
|
We had 1 unique committers since 0.0.25, and have had 90 overall.
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
@@ -61,8 +61,8 @@ struct name is ambiguous.
|
|||||||
|
|
||||||
If you'd like your resource to be accessible by the `YAML` graph API (GAPI),
|
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
|
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
|
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`
|
tags if you want existing `puppet` code to be able to run using the `mgmt`
|
||||||
engine.
|
engine.
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
@@ -623,7 +623,7 @@ func init() { // special golang method that runs once
|
|||||||
|
|
||||||
To support YAML unmarshalling for your resource, you must implement an
|
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
|
additional method. It is recommended if you want to use your resource with the
|
||||||
`Puppet` compiler.
|
`puppet` compiler.
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
UnmarshalYAML(unmarshal func(interface{}) error) error // optional
|
UnmarshalYAML(unmarshal func(interface{}) error) error // optional
|
||||||
@@ -678,10 +678,10 @@ receiving one. This can _only_ be done inside of the `CheckApply` function!
|
|||||||
|
|
||||||
```golang
|
```golang
|
||||||
// inside CheckApply, probably near the top
|
// inside CheckApply, probably near the top
|
||||||
if val, exists := obj.init.Recv()["SomeKey"]; exists {
|
if val, exists := obj.init.Recv()["some_key"]; exists {
|
||||||
obj.init.Logf("the SomeKey param was sent to us from: %s.%s", val.Res, val.Key)
|
obj.init.Logf("the some_key param was sent to us from: %s.%s", val.Res, val.Key)
|
||||||
if val.Changed {
|
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
|
// you may want to invalidate some local cache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -716,7 +716,7 @@ Higher level resource collections will be possible once the `mgmt` DSL is ready.
|
|||||||
### Why does the resource API have `CheckApply` instead of two separate methods?
|
### Why does the resource API have `CheckApply` instead of two separate methods?
|
||||||
|
|
||||||
In an early version we actually had both "parts" as separate methods, namely:
|
In an early version we actually had both "parts" as separate methods, namely:
|
||||||
`StateOK` (Check) and `Apply`, but the [decision](58f41eddd9c06b183f889f15d7c97af81b0331cc)
|
`StateOK` (Check) and `Apply`, but the [decision](https://github.com/purpleidea/mgmt/commit/58f41eddd9c06b183f889f15d7c97af81b0331cc)
|
||||||
was made to merge the two into a single method. There are two reasons for this:
|
was made to merge the two into a single method. There are two reasons for this:
|
||||||
|
|
||||||
1. Many situations would involve the engine running both `Check` and `Apply`. If
|
1. Many situations would involve the engine running both `Check` and `Apply`. If
|
||||||
|
|||||||
145
docs/service-guide.md
Normal file
145
docs/service-guide.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# Service API design guide
|
||||||
|
|
||||||
|
This document is intended as a short instructional design guide in building a
|
||||||
|
service management API. It is certainly intended for someone who wishes to use
|
||||||
|
`mgmt` resources and functions to interact with their facilities, however it may
|
||||||
|
be of more general use as well. Hopefully this will help you make smarter design
|
||||||
|
considerations early on, and prevent some amount of unnecessary technical debt.
|
||||||
|
|
||||||
|
## Main aspects
|
||||||
|
|
||||||
|
What follows are some of the most common considerations which you may wish to
|
||||||
|
take into account when building your service. This list is non-exhaustive. Of
|
||||||
|
particular note, as of the writing of this document, many of these designs are
|
||||||
|
not taken into account or not well-handled or implemented by the major API
|
||||||
|
("cloud") providers.
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
#### The status-quo
|
||||||
|
|
||||||
|
Many services naturally require you to authenticate yourself. Usually the
|
||||||
|
initial user who sets up the account and provides credit card details will need
|
||||||
|
to download secret credentials in order to access the service. The onus is on
|
||||||
|
the user to keep those credentials private, and to prevent leaking them. It is
|
||||||
|
convenient (and insecure) to store them in `git` repositories containing scripts
|
||||||
|
and configuration management code. Since it's likely you will use multiple
|
||||||
|
different services, it also means you will have a ton of different credentials
|
||||||
|
to guard.
|
||||||
|
|
||||||
|
#### An alternative
|
||||||
|
|
||||||
|
Instead, build your service to accept a public key that you store in the users
|
||||||
|
account. Only consumers that can correctly sign messages matching this public
|
||||||
|
key should be authorized. This mechanism is well-understood by anyone who has
|
||||||
|
ever uploaded their public SSH key to a server. You can use SSH keys, GPG keys,
|
||||||
|
or even get into Kerberos if that's appropriate. Best of all, if you and other
|
||||||
|
services use a standardized mechanism like GPG, a user might only need to keep
|
||||||
|
track of their single key-pair, even when they're using multiple services!
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
#### The problem
|
||||||
|
|
||||||
|
People have been building "[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)"
|
||||||
|
and "[REST](https://en.wikipedia.org/wiki/REST)"ful API's for years. The biggest
|
||||||
|
missing part that most of them don't provide is events. If users want to know
|
||||||
|
when a resource changes, they have to repeatedly poll the server, which is both
|
||||||
|
network intensive, and introduces latency. When services were simpler, this
|
||||||
|
wasn't as much of a consideration, but these days it matters. An embarrassingly
|
||||||
|
small number of major software vendors implement these correctly, if at all.
|
||||||
|
|
||||||
|
#### Why events?
|
||||||
|
|
||||||
|
The `mgmt` tool is different from most other static tools in that it allows
|
||||||
|
reading streams of incoming data, and stream of change events from resources we
|
||||||
|
are managing. If an event API is not available, we can still poll, but this is
|
||||||
|
not as desirable. An event-capable API doesn't prevent polling if that's
|
||||||
|
preferred, you can always repeat a read request periodically.
|
||||||
|
|
||||||
|
#### Variants
|
||||||
|
|
||||||
|
The two common mechanisms for receiving events are "callbacks" and
|
||||||
|
"long-polling". In the former, the service contacts the consumer when something
|
||||||
|
happens. In the latter, the consumer opens a connection, and the service either
|
||||||
|
closes the connection or sends the reply, when it's ready. Long-polling is often
|
||||||
|
preferred since it doesn't require an open firewall on the consumers side.
|
||||||
|
Callbacks are preferred because it's often cheaper for the service to implement
|
||||||
|
that. It's also less reliable since it's hard to know if the callback message
|
||||||
|
wasn't received because it was dropped, or if there just wasn't an event. And it
|
||||||
|
requires static timeouts when retrying a callback message, and so on. It's best
|
||||||
|
to implement long-polling or something equivalent at a minimum.
|
||||||
|
|
||||||
|
#### "Since" requests
|
||||||
|
|
||||||
|
When making an event request, some API's will let you tack on a "since" style
|
||||||
|
parameter that tells the endpoint that we're interested in all of the events
|
||||||
|
_since_ a particular timestamp, or _since_ a particular sequence ID. This can be
|
||||||
|
very useful if missing an intermediate event is a concern. Implement this if you
|
||||||
|
can, but it's better for all concerned if purely declarative facilities are all
|
||||||
|
that is required. It also forces the endpoint to maintain some state, which may
|
||||||
|
be undesirable for them.
|
||||||
|
|
||||||
|
#### Out of band
|
||||||
|
|
||||||
|
Some providers have the event system tacked on to a separate facility. If it's
|
||||||
|
not part of the core API, then it's not useful. You shouldn't have to configure
|
||||||
|
a separate system in order to start getting events.
|
||||||
|
|
||||||
|
### Batching
|
||||||
|
|
||||||
|
With so many resources, you might expect to have 1000's of long-polling
|
||||||
|
connections all sitting open and idle. That can't be efficient! It's not, which
|
||||||
|
is why good API's need a batching facility. This lets the consumer group
|
||||||
|
together many watches (all waiting on a long-poll) inside of a single call. That
|
||||||
|
way, a single connection might only be needed for a large amount of information.
|
||||||
|
|
||||||
|
### Don't auto-generate junk
|
||||||
|
|
||||||
|
Please build an elegant API. Many services auto-generate a "phone book" SDK of
|
||||||
|
junk. It might seem inevitable, so if you absolutely need to do this, then put
|
||||||
|
some extra effort into making it idiomatic. If I'm using an SDK generated for
|
||||||
|
`golang` and I see an internal `foo.String` wrapper, then chances are you have
|
||||||
|
designed your API and code to be easier to maintain for you, instead of
|
||||||
|
prioritizing your customers. Surely the total volume of all customer code is
|
||||||
|
more than your own, so why optimize for that instead of the putting the customer
|
||||||
|
first?
|
||||||
|
|
||||||
|
### Resources and functions
|
||||||
|
|
||||||
|
`Mgmt` has a concept of "resources" and "functions". Resources are used in an
|
||||||
|
idempotent model to express desired state and perform that work, and "functions"
|
||||||
|
are used to receive and pull data into the system. That separation has shown to
|
||||||
|
be an elegant one. Consider it when designing your API's. For example, if some
|
||||||
|
vital information can only be obtained after performing a modifying operation,
|
||||||
|
then it might signal that you're missing some sort of a lookup or event-log
|
||||||
|
system. Design your API's to be idempotent, this solves many distributed-system
|
||||||
|
problems involving receiving duplicate messages, and so on.
|
||||||
|
|
||||||
|
## Using mgmt as a library
|
||||||
|
|
||||||
|
Instead of building a new service from scratch, and re-inventing the typical
|
||||||
|
management and CLI layer, consider using `mgmt` as a library, and directly
|
||||||
|
benefiting from that work. This has not been done for a large production
|
||||||
|
service, but the author believes it would be quite efficient, particularly if
|
||||||
|
your application is written in golang. It's equivalently easy to do it for other
|
||||||
|
languages as well, you just end up with two binaries instead of one. (Or you can
|
||||||
|
embed the other binary into the new golang management tool.)
|
||||||
|
|
||||||
|
## Cloud API considerations
|
||||||
|
|
||||||
|
Many "cloud" companies have a lot of technical debt and a lot of customers. As a
|
||||||
|
result, it might be very hard for them to improve their API's, particularly
|
||||||
|
without breaking compatibility promises for their existing customers. As a
|
||||||
|
result, they should either add a versioned API, which lets newer consumers get
|
||||||
|
the benefit, or add new parallel services which offer the modern features. If
|
||||||
|
they don't, the only solution is for new competitors to build-in these better
|
||||||
|
efficiencies, eventually offering better value to cost ratios, which will then
|
||||||
|
make legacy products less lucrative and therefore unmaintainable as compared to
|
||||||
|
their competitors.
|
||||||
|
|
||||||
|
## Suggestions
|
||||||
|
|
||||||
|
If you have any ideas for suggestions or other improvements to this guide,
|
||||||
|
please let us know! I hope this was helpful. Please reach out if you are
|
||||||
|
building an API that you might like to have `mgmt` consume!
|
||||||
@@ -67,6 +67,37 @@ 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
|
default value be the `zero` value. For example, instead of `const NoDanger`, use
|
||||||
`const AllowDanger` so that the `false` value is the safe scenario.
|
`const AllowDanger` so that the `false` value is the safe scenario.
|
||||||
|
|
||||||
|
### Method receiver pointers
|
||||||
|
|
||||||
|
You almost always want any method receivers to be declared on the pointer to the
|
||||||
|
struct. There are only a few rare situations where this is not the case. This
|
||||||
|
makes it easier to merge future changes that mutate the state without wondering
|
||||||
|
why you now have two different copies of a struct. When you do need to copy a
|
||||||
|
a struct, you can add a `Copy()` method to it. It's true that in many situations
|
||||||
|
adding the pointer adds a small performance penalty, but we haven't found them
|
||||||
|
to be significant in practice. If you do have a performance sensitive patch
|
||||||
|
which benefits from skipping the pointer, please demonstrate this need with
|
||||||
|
data first.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type Foo struct {
|
||||||
|
Whatever string
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bar is implemented correctly as a pointer on Foo.
|
||||||
|
func (obj *Foo) Bar(baz string) int {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bar is implemented *incorrectly* without a pointer to Foo.
|
||||||
|
func (obj Foo) Bar(baz string) int {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Method receiver naming
|
### Method receiver naming
|
||||||
|
|
||||||
[Contrary](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)
|
[Contrary](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)
|
||||||
@@ -141,6 +172,24 @@ 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
|
code. For example, `x := MyFoo("blah", false)` is less clear than
|
||||||
`const useMagic = false; x := MyFoo("blah", useMagic)`.
|
`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
|
### Consistent ordering
|
||||||
|
|
||||||
In general we try to preserve a logical ordering in source files which usually
|
In general we try to preserve a logical ordering in source files which usually
|
||||||
|
|||||||
103
docs/util/metadata.go
Normal file
103
docs/util/metadata.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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 handles metadata for documentation generation.
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
registeredResourceMetadata = make(map[string]*Metadata) // must initialize
|
||||||
|
registeredFunctionMetadata = make(map[string]*Metadata) // must initialize
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterResource records the metadata for a resource of this kind.
|
||||||
|
func RegisterResource(kind string, metadata *Metadata) error {
|
||||||
|
if _, exists := registeredResourceMetadata[kind]; exists {
|
||||||
|
return fmt.Errorf("metadata kind %s is already registered", kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
registeredResourceMetadata[kind] = metadata
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupResource looks up the metadata for a resource of this kind.
|
||||||
|
func LookupResource(kind string) (*Metadata, error) {
|
||||||
|
metadata, exists := registeredResourceMetadata[kind]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("not found")
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFunction records the metadata for a function of this name.
|
||||||
|
func RegisterFunction(name string, metadata *Metadata) error {
|
||||||
|
if _, exists := registeredFunctionMetadata[name]; exists {
|
||||||
|
return fmt.Errorf("metadata named %s is already registered", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
registeredFunctionMetadata[name] = metadata
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupFunction looks up the metadata for a function of this name.
|
||||||
|
func LookupFunction(name string) (*Metadata, error) {
|
||||||
|
metadata, exists := registeredFunctionMetadata[name]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("not found")
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata stores some additional information about the function or resource.
|
||||||
|
// This is used to automatically generate documentation.
|
||||||
|
type Metadata struct {
|
||||||
|
// Filename is the filename (without any base dir path) that this is in.
|
||||||
|
Filename string
|
||||||
|
|
||||||
|
// Typename is the string name of the main resource struct or function.
|
||||||
|
Typename string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMetadata returns some metadata about the func. It can be called at any
|
||||||
|
// time. This must not be named the same as the struct it's on or using it as an
|
||||||
|
// anonymous embedded struct will stop us from being able to call this method.
|
||||||
|
func (obj *Metadata) GetMetadata() *Metadata {
|
||||||
|
//if obj == nil { // TODO: Do I need this?
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
return &Metadata{
|
||||||
|
Filename: obj.Filename,
|
||||||
|
Typename: obj.Typename,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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.
|
||||||
|
|
||||||
//go:build !root
|
//go:build !root
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
@@ -42,7 +54,8 @@ type GroupableRes interface {
|
|||||||
// grouping. This usually needs to be unique to your resource.
|
// grouping. This usually needs to be unique to your resource.
|
||||||
GroupCmp(res GroupableRes) error
|
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
|
GroupRes(res GroupableRes) error
|
||||||
|
|
||||||
// IsGrouped determines if we are grouped.
|
// IsGrouped determines if we are grouped.
|
||||||
@@ -54,8 +67,15 @@ type GroupableRes interface {
|
|||||||
// GetGroup returns everyone grouped inside me.
|
// GetGroup returns everyone grouped inside me.
|
||||||
GetGroup() []GroupableRes // return 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)
|
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.
|
// AutoGroupMeta provides some parameters specific to automatic grouping.
|
||||||
|
|||||||
148
engine/cmp.go
148
engine/cmp.go
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
@@ -21,6 +33,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResCmp compares two resources by checking multiple aspects. This is the main
|
// 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 {
|
for k := range ix {
|
||||||
// compare sub resources
|
// compare sub resources
|
||||||
if err := ResCmp(ix[k], jx[k]); err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,14 +135,29 @@ func ResCmp(r1, r2 Res) error {
|
|||||||
v1 := r1r.Recv()
|
v1 := r1r.Recv()
|
||||||
v2 := r2r.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
|
if (v1 == nil) != (v2 == nil) { // xor
|
||||||
return fmt.Errorf("recv params differ")
|
//return fmt.Errorf("recv params differ")
|
||||||
}
|
}
|
||||||
if v1 != nil && v2 != nil {
|
if v1 != nil && v2 != nil {
|
||||||
// TODO: until we hit this code path, don't allow
|
if len(v1) != len(v2) {
|
||||||
// comparing anything that has this set to non-zero
|
//return fmt.Errorf("recv param lengths differ")
|
||||||
if len(v1) != 0 || len(v2) != 0 {
|
}
|
||||||
return fmt.Errorf("recv params exist")
|
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,17 @@ func ResCmp(r1, r2 Res) error {
|
|||||||
s1 := r1s.Sent()
|
s1 := r1s.Sent()
|
||||||
s2 := r2s.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
|
if (s1 == nil) != (s2 == nil) { // xor
|
||||||
return fmt.Errorf("send params differ")
|
//return fmt.Errorf("send params differ")
|
||||||
}
|
}
|
||||||
if s1 != nil && s2 != nil {
|
if s1 != nil && s2 != nil {
|
||||||
// TODO: until we hit this code path, don't allow
|
// TODO: reflect.DeepEqual?
|
||||||
// adapting anything that has this set to non-nil
|
//return fmt.Errorf("send params exist")
|
||||||
return fmt.Errorf("send params exist")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,14 +294,29 @@ func AdaptCmp(r1, r2 CompatibleRes) error {
|
|||||||
v1 := r1r.Recv()
|
v1 := r1r.Recv()
|
||||||
v2 := r2r.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
|
if (v1 == nil) != (v2 == nil) { // xor
|
||||||
return fmt.Errorf("recv params differ")
|
//return fmt.Errorf("recv params differ")
|
||||||
}
|
}
|
||||||
if v1 != nil && v2 != nil {
|
if v1 != nil && v2 != nil {
|
||||||
// TODO: until we hit this code path, don't allow
|
if len(v1) != len(v2) {
|
||||||
// adapting anything that has this set to non-zero
|
//return fmt.Errorf("recv param lengths differ")
|
||||||
if len(v1) != 0 || len(v2) != 0 {
|
}
|
||||||
return fmt.Errorf("recv params exist")
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,13 +330,17 @@ func AdaptCmp(r1, r2 CompatibleRes) error {
|
|||||||
s1 := r1s.Sent()
|
s1 := r1s.Sent()
|
||||||
s2 := r2s.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
|
if (s1 == nil) != (s2 == nil) { // xor
|
||||||
return fmt.Errorf("send params differ")
|
//return fmt.Errorf("send params differ")
|
||||||
}
|
}
|
||||||
if s1 != nil && s2 != nil {
|
if s1 != nil && s2 != nil {
|
||||||
// TODO: until we hit this code path, don't allow
|
// TODO: reflect.DeepEqual?
|
||||||
// adapting anything that has this set to non-nil
|
//return fmt.Errorf("send params exist")
|
||||||
return fmt.Errorf("send params exist")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,9 +372,11 @@ func VertexCmpFn(v1, v2 pgraph.Vertex) (bool, error) {
|
|||||||
return false, fmt.Errorf("v2 is not a Res")
|
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
|
return false, nil
|
||||||
}
|
}
|
||||||
|
//fmt.Printf("ok Cmp: %p %+v <> %p %+v\n", r1, r1, r2, r2)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -341,3 +395,55 @@ func EdgeCmpFn(e1, e2 pgraph.Edge) (bool, error) {
|
|||||||
}
|
}
|
||||||
return edge1.Cmp(edge2) == nil, nil
|
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
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
@@ -81,6 +93,7 @@ func ResCopy(r CopyableRes) (CopyableRes, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("resource wasn't groupable")
|
return nil, fmt.Errorf("resource wasn't groupable")
|
||||||
}
|
}
|
||||||
|
g2.SetParent(dst) // store who my parent is
|
||||||
grouped = append(grouped, g2)
|
grouped = append(grouped, g2)
|
||||||
}
|
}
|
||||||
dst.SetGroup(grouped)
|
dst.SetGroup(grouped)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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 represents the implementation of the resource engine that runs
|
// 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
|
// the graph of resources in real-time. This package has the common imports that
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
|
|||||||
101
engine/fs.go
101
engine/fs.go
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
@@ -23,39 +35,64 @@ import (
|
|||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
// from the ioutil package:
|
// Fs is an interface that represents the file system API that we support.
|
||||||
// NopCloser(r io.Reader) io.ReadCloser // not implemented here
|
// TODO: rename this to FS for consistency with the io/fs.FS naming scheme
|
||||||
// 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.
|
|
||||||
type Fs interface {
|
type Fs interface {
|
||||||
//fmt.Stringer // TODO: add this method?
|
//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
|
URI() string // returns the URI for this file system
|
||||||
|
|
||||||
//DirExists(path string) (bool, error)
|
afero.Fs // TODO: why doesn't this interface exist in the os pkg?
|
||||||
//Exists(path string) (bool, error)
|
|
||||||
//FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error)
|
// FS is the read-only filesystem interface from the io/fs.FS package.
|
||||||
//FileContainsBytes(filename string, subslice []byte) (bool, error)
|
//fs.FS // io/fs.FS
|
||||||
//FullBaseFsPath(basePathFs *BasePathFs, relativePath string) string
|
|
||||||
//GetTempDir(subPath string) string
|
// ReadDir reads the named directory and returns a list of directory
|
||||||
//IsDir(path string) (bool, error)
|
// entries sorted by filename.
|
||||||
//IsEmpty(path string) (bool, error)
|
//
|
||||||
//NeuterAccents(s string) string
|
// This mimics the signature from io/fs.ReadDirFS and has the same docs.
|
||||||
//ReadAll(r io.Reader) ([]byte, error) // not needed, same as ioutil
|
//
|
||||||
|
// 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)
|
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)
|
// WriteableFS is our internal filesystem interface for filesystems we write to.
|
||||||
TempFile(dir, prefix string) (f afero.File, err error) // slightly different from upstream
|
// It can wrap whatever implementations we want.
|
||||||
//UnicodeSanitize(s string) string
|
type WriteableFS interface {
|
||||||
//Walk(root string, walkFn filepath.WalkFunc) error
|
Fs
|
||||||
WriteFile(filename string, data []byte, perm os.FileMode) error
|
|
||||||
//WriteReader(path string, r io.Reader) (err error)
|
// 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
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
@@ -41,14 +53,18 @@ func (obj *Engine) OKTimestamp(vertex pgraph.Vertex) bool {
|
|||||||
// be bad.
|
// be bad.
|
||||||
func (obj *Engine) BadTimestamps(vertex pgraph.Vertex) []pgraph.Vertex {
|
func (obj *Engine) BadTimestamps(vertex pgraph.Vertex) []pgraph.Vertex {
|
||||||
vs := []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
|
// these are all the vertices pointing TO vertex, eg: ??? -> vertex
|
||||||
for _, v := range obj.graph.IncomingGraphVertices(vertex) {
|
for _, v := range obj.graph.IncomingGraphVertices(vertex) {
|
||||||
// If the vertex has a greater timestamp than any prerequisite,
|
// If the vertex has a greater timestamp than any prerequisite,
|
||||||
// then we can't run right now. If they're equal (eg: initially
|
// 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
|
// with a value of 0) then we also can't run because we should
|
||||||
// let our pre-requisites go first.
|
// 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 {
|
if obj.Debug {
|
||||||
obj.Logf("OKTimestamp: %d >= %d (%s): !%t", ts, t, v.String(), ts >= t)
|
obj.Logf("OKTimestamp: %d >= %d (%s): !%t", ts, t, v.String(), ts >= t)
|
||||||
}
|
}
|
||||||
@@ -112,21 +128,41 @@ func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
|
|||||||
|
|
||||||
// sendrecv!
|
// sendrecv!
|
||||||
// connect any senders to receivers and detect if values changed
|
// 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 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")
|
return errwrap.Wrapf(err, "could not SendRecv")
|
||||||
} else if len(updated) > 0 {
|
} else if len(updated) > 0 {
|
||||||
for _, changed := range updated {
|
//for _, s := range graph.UpdatedStrings(updated) {
|
||||||
if changed { // at least one was updated
|
// obj.Logf("SendRecv: %s", s)
|
||||||
// invalidate cache, mark as dirty
|
//}
|
||||||
obj.state[vertex].tuid.StopTimer()
|
for r, m := range updated { // map[engine.RecvableRes]map[string]*engine.Send
|
||||||
obj.state[vertex].isStateOK = false
|
v, ok := r.(pgraph.Vertex)
|
||||||
break
|
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +184,7 @@ func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
|
|||||||
// Check cached state, to skip CheckApply, but can't skip if refreshing!
|
// Check cached state, to skip CheckApply, but can't skip if refreshing!
|
||||||
// If the resource doesn't implement refresh, skip the refresh test.
|
// If the resource doesn't implement refresh, skip the refresh test.
|
||||||
// FIXME: if desired, check that we pass through refresh notifications!
|
// 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
|
checkOK, err = true, nil
|
||||||
|
|
||||||
} else if noop && (refresh && isRefreshableRes) { // had a refresh to do w/ noop!
|
} else if noop && (refresh && isRefreshableRes) { // had a refresh to do w/ noop!
|
||||||
@@ -156,10 +192,14 @@ func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// run the CheckApply!
|
// run the CheckApply!
|
||||||
obj.Logf("%s: CheckApply(%t)", res, !noop)
|
if obj.Debug {
|
||||||
|
obj.Logf("%s: CheckApply(%t)", res, !noop)
|
||||||
|
}
|
||||||
// if this fails, don't UpdateTimestamp()
|
// if this fails, don't UpdateTimestamp()
|
||||||
checkOK, err = res.CheckApply(ctx, !noop)
|
checkOK, err = res.CheckApply(ctx, !noop)
|
||||||
obj.Logf("%s: CheckApply(%t): Return(%t, %s)", res, !noop, checkOK, engineUtil.CleanError(err))
|
if !checkOK && obj.Debug { // don't log on (checkOK == true)
|
||||||
|
obj.Logf("%s: CheckApply(%t): Return(%t, %s)", res, !noop, checkOK, engineUtil.CleanError(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkOK && err != nil { // should never return this way
|
if checkOK && err != nil { // should never return this way
|
||||||
@@ -176,7 +216,9 @@ func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
|
|||||||
// if CheckApply ran without noop and without error, state should be good
|
// if CheckApply ran without noop and without error, state should be good
|
||||||
if !noop && err == nil { // aka !noop || checkOK
|
if !noop && err == nil { // aka !noop || checkOK
|
||||||
obj.state[vertex].tuid.StartTimer()
|
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 {
|
if refresh {
|
||||||
obj.SetUpstreamRefresh(vertex, false) // refresh happened, clear the request
|
obj.SetUpstreamRefresh(vertex, false) // refresh happened, clear the request
|
||||||
if isRefreshableRes {
|
if isRefreshableRes {
|
||||||
@@ -213,7 +255,9 @@ func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
|
|||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
// update this timestamp *before* we poke or the poked
|
// update this timestamp *before* we poke or the poked
|
||||||
// nodes might fail due to having a too old timestamp!
|
// 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) {
|
for _, v := range obj.graph.OutgoingGraphVertices(vertex) {
|
||||||
if !obj.OKTimestamp(v) {
|
if !obj.OKTimestamp(v) {
|
||||||
// there is at least another one that will poke this...
|
// there is at least another one that will poke this...
|
||||||
@@ -338,9 +382,17 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
|
|||||||
obj.state[vertex].cuid.StopTimer() // clean up nicely
|
obj.state[vertex].cuid.StopTimer() // clean up nicely
|
||||||
} else {
|
} else {
|
||||||
obj.state[vertex].cuid.StartTimer()
|
obj.state[vertex].cuid.StartTimer()
|
||||||
obj.Logf("Watch(%s)", vertex)
|
if obj.Debug {
|
||||||
|
obj.Logf("%s: Watch...", vertex)
|
||||||
|
}
|
||||||
err = res.Watch(obj.state[vertex].doneCtx) // run the watch normally
|
err = res.Watch(obj.state[vertex].doneCtx) // run the watch normally
|
||||||
obj.Logf("Watch(%s): Exited(%s)", vertex, engineUtil.CleanError(err))
|
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
|
obj.state[vertex].cuid.StopTimer() // clean up nicely
|
||||||
}
|
}
|
||||||
if err == nil { // || err == engine.ErrClosed
|
if err == nil { // || err == engine.ErrClosed
|
||||||
@@ -430,7 +482,10 @@ Loop:
|
|||||||
|
|
||||||
// we are paused now, and waiting for resume or exit...
|
// we are paused now, and waiting for resume or exit...
|
||||||
select {
|
select {
|
||||||
case _, closed = <-obj.state[vertex].resumeSignal: // channel closes
|
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
|
||||||
|
if !ok {
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
// resumed!
|
// resumed!
|
||||||
// pass through to allow a Process to try to run
|
// pass through to allow a Process to try to run
|
||||||
// TODO: consider adding this fast pause here...
|
// TODO: consider adding this fast pause here...
|
||||||
@@ -496,7 +551,10 @@ Loop:
|
|||||||
break LimitWait
|
break LimitWait
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case _, closed = <-obj.state[vertex].resumeSignal: // channel closes
|
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
|
||||||
|
if !ok {
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
// resumed!
|
// resumed!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,7 +610,10 @@ Loop:
|
|||||||
break RetryWait
|
break RetryWait
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case _, closed = <-obj.state[vertex].resumeSignal: // channel closes
|
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
|
||||||
|
if !ok {
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
// resumed!
|
// resumed!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package autoedge
|
||||||
|
|
||||||
@@ -27,7 +39,7 @@ import (
|
|||||||
|
|
||||||
// AutoEdge adds the automatic edges to the graph.
|
// AutoEdge adds the automatic edges to the graph.
|
||||||
func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...interface{})) error {
|
func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...interface{})) error {
|
||||||
logf("adding autoedges...")
|
logf("building...")
|
||||||
|
|
||||||
// initially get all of the autoedges to seek out all possible errors
|
// initially get all of the autoedges to seek out all possible errors
|
||||||
var err error
|
var err error
|
||||||
@@ -51,7 +63,9 @@ func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...int
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if autoEdgeObj == nil {
|
if autoEdgeObj == nil {
|
||||||
logf("no auto edges were found for: %s", res)
|
if debug {
|
||||||
|
logf("no auto edges were found for: %s", res)
|
||||||
|
}
|
||||||
continue // next vertex
|
continue // next vertex
|
||||||
}
|
}
|
||||||
autoEdgeObjMap[res] = autoEdgeObj // save for next loop
|
autoEdgeObjMap[res] = autoEdgeObj // save for next loop
|
||||||
@@ -74,9 +88,9 @@ func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...int
|
|||||||
break // inner loop
|
break // inner loop
|
||||||
}
|
}
|
||||||
if debug {
|
if debug {
|
||||||
logf("autoedge: UIDS:")
|
logf("UIDS:")
|
||||||
for i, u := range uids {
|
for i, u := range uids {
|
||||||
logf("autoedge: UID%d: %v", i, u)
|
logf("UID%d: %v", i, u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +131,7 @@ func addEdgesByMatchingUIDS(res engine.EdgeableRes, uids []engine.ResUID, graph
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if debug {
|
if debug {
|
||||||
logf("autoedge: Match: %s with UID: %s", r, uid)
|
logf("match: %s with UID: %s", r, uid)
|
||||||
}
|
}
|
||||||
// we must match to an effective UID for the resource,
|
// we must match to an effective UID for the resource,
|
||||||
// that is to say, the name value of a res is a helpful
|
// that is to say, the name value of a res is a helpful
|
||||||
@@ -126,13 +140,13 @@ func addEdgesByMatchingUIDS(res engine.EdgeableRes, uids []engine.ResUID, graph
|
|||||||
if UIDExistsInUIDs(uid, r.UIDs()) {
|
if UIDExistsInUIDs(uid, r.UIDs()) {
|
||||||
// add edge from: r -> res
|
// add edge from: r -> res
|
||||||
if uid.IsReversed() {
|
if uid.IsReversed() {
|
||||||
txt := fmt.Sprintf("%s -> %s (autoedge)", r, res)
|
txt := fmt.Sprintf("%s -> %s", r, res)
|
||||||
logf("autoedge: adding: %s", txt)
|
logf("adding: %s", txt)
|
||||||
edge := &engine.Edge{Name: txt}
|
edge := &engine.Edge{Name: txt}
|
||||||
graph.AddEdge(r, res, edge)
|
graph.AddEdge(r, res, edge)
|
||||||
} else { // edges go the "normal" way, eg: pkg resource
|
} else { // edges go the "normal" way, eg: pkg resource
|
||||||
txt := fmt.Sprintf("%s -> %s (autoedge)", res, r)
|
txt := fmt.Sprintf("%s -> %s", res, r)
|
||||||
logf("autoedge: adding: %s", txt)
|
logf("adding: %s", txt)
|
||||||
edge := &engine.Edge{Name: txt}
|
edge := &engine.Edge{Name: txt}
|
||||||
graph.AddEdge(res, r, edge)
|
graph.AddEdge(res, r, edge)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
@@ -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
|
if err = r1.GroupRes(r2); err != nil { // GroupRes skips stupid groupings
|
||||||
return // return early on error
|
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
|
// merging two resources into one should yield the sum of their semas
|
||||||
if semas := r2.MetaParams().Sema; len(semas) > 0 {
|
if semas := r2.MetaParams().Sema; len(semas) > 0 {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package autogroup
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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.
|
||||||
|
|
||||||
//go:build !root
|
//go:build !root
|
||||||
|
|
||||||
@@ -190,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
|
if err := r1.GroupRes(r2); err != nil { // group them first
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
r2.SetParent(r1) // store who my parent is
|
||||||
|
|
||||||
// HACK: update the name so it matches full list of self+grouped
|
// HACK: update the name so it matches full list of self+grouped
|
||||||
res := v1.(engine.GroupableRes)
|
res := v1.(engine.GroupableRes)
|
||||||
names := strings.Split(res.Name(), ",") // load in stored names
|
names := strings.Split(res.Name(), ",") // load in stored names
|
||||||
@@ -741,17 +755,30 @@ func TestPgraphGrouping16(t *testing.T) {
|
|||||||
g1.AddEdge(b1, c1, e2)
|
g1.AddEdge(b1, c1, e2)
|
||||||
g1.AddEdge(a2, c1, e3)
|
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")
|
a := NewNoopResTest("a1,a2")
|
||||||
b1 := NewNoopResTest("b1")
|
b1 := NewNoopResTest("b1")
|
||||||
c1 := NewNoopResTest("c1")
|
c1 := NewNoopResTest("c1")
|
||||||
e1 := NE("e1,e3")
|
e1 := NE("e1")
|
||||||
e2 := NE("e2,e3") // e3 gets "merged through" to BOTH edges!
|
e2 := NE("e2")
|
||||||
g2.AddEdge(a, b1, e1)
|
e3 := NE("e3")
|
||||||
g2.AddEdge(b1, c1, e2)
|
g3.AddEdge(a, b1, e1)
|
||||||
|
g3.AddEdge(b1, c1, e2)
|
||||||
|
g3.AddEdge(a, c1, e3)
|
||||||
}
|
}
|
||||||
runGraphCmp(t, g1, g2)
|
runGraphCmp(t, g1, g3)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -792,11 +819,12 @@ func TestPgraphGrouping17(t *testing.T) {
|
|||||||
/*
|
/*
|
||||||
// re-attach 3 (double)
|
// re-attach 3 (double)
|
||||||
// similar to "re-attach 1", technically there is a second possibility for this
|
// similar to "re-attach 1", technically there is a second possibility for this
|
||||||
// a2 a1 b2 a1,a2
|
// TODO: verify this second possibility manually
|
||||||
// \ | / |
|
// a2 a1 b2 a1,a2 a1,a2
|
||||||
// \ b1 / >>> b1,b2 (arrows point downwards)
|
// \ | / | | \
|
||||||
// \ | / |
|
// \ b1 / >>> b1,b2 OR b1,b2 / (arrows point downwards)
|
||||||
// c1 c1
|
// \ | / | | /
|
||||||
|
// c1 c1 c1
|
||||||
*/
|
*/
|
||||||
func TestPgraphGrouping18(t *testing.T) {
|
func TestPgraphGrouping18(t *testing.T) {
|
||||||
g1, _ := pgraph.NewGraph("g1") // original graph
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
@@ -815,17 +843,30 @@ func TestPgraphGrouping18(t *testing.T) {
|
|||||||
g1.AddEdge(a2, c1, e3)
|
g1.AddEdge(a2, c1, e3)
|
||||||
g1.AddEdge(b2, c1, e4)
|
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")
|
a := NewNoopResTest("a1,a2")
|
||||||
b := NewNoopResTest("b1,b2")
|
b := NewNoopResTest("b1,b2")
|
||||||
c1 := NewNoopResTest("c1")
|
c1 := NewNoopResTest("c1")
|
||||||
e1 := NE("e1,e3")
|
e1 := NE("e1")
|
||||||
e2 := NE("e2,e3,e4") // e3 gets "merged through" to BOTH edges!
|
e2 := NE("e2,e4")
|
||||||
g2.AddEdge(a, b, e1)
|
e3 := NE("e3")
|
||||||
g2.AddEdge(b, c1, e2)
|
g3.AddEdge(a, b, e1)
|
||||||
|
g3.AddEdge(b, c1, e2)
|
||||||
|
g3.AddEdge(a, c1, e3)
|
||||||
}
|
}
|
||||||
runGraphCmp(t, g1, g2)
|
runGraphCmp(t, g1, g3)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package autogroup
|
||||||
|
|
||||||
@@ -43,8 +55,24 @@ func (ag *baseGrouper) Init(g *pgraph.Graph) error {
|
|||||||
if ag.graph != nil {
|
if ag.graph != nil {
|
||||||
return fmt.Errorf("the init method has already been called")
|
return fmt.Errorf("the init method has already been called")
|
||||||
}
|
}
|
||||||
ag.graph = g // pointer
|
ag.graph = g // pointer
|
||||||
ag.vertices = ag.graph.VerticesSorted() // cache in deterministic order!
|
|
||||||
|
// 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.i = 0
|
||||||
ag.j = 0
|
ag.j = 0
|
||||||
if len(ag.vertices) == 0 { // empty graph
|
if len(ag.vertices) == 0 { // empty graph
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package autogroup
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,12 +13,26 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package autogroup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/engine"
|
"github.com/purpleidea/mgmt/engine"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
@@ -136,3 +150,67 @@ func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgrap
|
|||||||
}
|
}
|
||||||
return nil // success
|
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
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
// 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 the graph of resources in real-time. This package has the algorithm
|
||||||
@@ -28,6 +40,7 @@ import (
|
|||||||
|
|
||||||
"github.com/purpleidea/mgmt/converger"
|
"github.com/purpleidea/mgmt/converger"
|
||||||
"github.com/purpleidea/mgmt/engine"
|
"github.com/purpleidea/mgmt/engine"
|
||||||
|
"github.com/purpleidea/mgmt/engine/local"
|
||||||
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
@@ -45,15 +58,16 @@ type Engine struct {
|
|||||||
Program string
|
Program string
|
||||||
Version string
|
Version string
|
||||||
Hostname 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
|
// Prefix is a unique directory prefix which can be used. It should be
|
||||||
// created if needed.
|
// created if needed.
|
||||||
Prefix string
|
Prefix string
|
||||||
Converger *converger.Coordinator
|
Debug bool
|
||||||
|
Logf func(format string, v ...interface{})
|
||||||
Debug bool
|
|
||||||
Logf func(format string, v ...interface{})
|
|
||||||
|
|
||||||
graph *pgraph.Graph
|
graph *pgraph.Graph
|
||||||
nextGraph *pgraph.Graph
|
nextGraph *pgraph.Graph
|
||||||
@@ -92,7 +106,8 @@ func (obj *Engine) Init() error {
|
|||||||
if obj.Prefix == "" || obj.Prefix == "/" {
|
if obj.Prefix == "" || obj.Prefix == "/" {
|
||||||
return fmt.Errorf("the prefix of `%s` is invalid", obj.Prefix)
|
return fmt.Errorf("the prefix of `%s` is invalid", obj.Prefix)
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(obj.Prefix, 0770); err != nil {
|
// 0775 since we want children to be able to read this!
|
||||||
|
if err := os.MkdirAll(obj.Prefix, 0775); err != nil {
|
||||||
return errwrap.Wrapf(err, "can't create prefix")
|
return errwrap.Wrapf(err, "can't create prefix")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +159,7 @@ func (obj *Engine) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := engine.Validate(res); err != nil {
|
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
|
return nil
|
||||||
@@ -210,7 +225,7 @@ func (obj *Engine) Commit() error {
|
|||||||
statePrefix := fmt.Sprintf("%s/", path.Join(obj.statePrefix(), pathUID))
|
statePrefix := fmt.Sprintf("%s/", path.Join(obj.statePrefix(), pathUID))
|
||||||
|
|
||||||
// don't create this unless it *will* be used
|
// don't create this unless it *will* be used
|
||||||
//if err := os.MkdirAll(statePrefix, 0770); err != nil {
|
//if err := os.MkdirAll(statePrefix, 0775); err != nil {
|
||||||
// return errwrap.Wrapf(err, "can't create state prefix")
|
// return errwrap.Wrapf(err, "can't create state prefix")
|
||||||
//}
|
//}
|
||||||
|
|
||||||
@@ -223,9 +238,10 @@ func (obj *Engine) Commit() error {
|
|||||||
Version: obj.Version,
|
Version: obj.Version,
|
||||||
Hostname: obj.Hostname,
|
Hostname: obj.Hostname,
|
||||||
|
|
||||||
|
//Converger: obj.Converger,
|
||||||
|
Local: obj.Local,
|
||||||
World: obj.World,
|
World: obj.World,
|
||||||
Prefix: statePrefix,
|
Prefix: statePrefix,
|
||||||
//Converger: obj.Converger,
|
|
||||||
|
|
||||||
Debug: obj.Debug,
|
Debug: obj.Debug,
|
||||||
Logf: func(format string, v ...interface{}) {
|
Logf: func(format string, v ...interface{}) {
|
||||||
@@ -253,10 +269,18 @@ func (obj *Engine) Commit() error {
|
|||||||
obj.wlock.Unlock()
|
obj.wlock.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
obj.Logf("Worker(%s)", v)
|
if obj.Debug {
|
||||||
|
obj.Logf("%s: Working...", v)
|
||||||
|
}
|
||||||
// contains the Watch and CheckApply loops
|
// contains the Watch and CheckApply loops
|
||||||
err := obj.Worker(v)
|
err := obj.Worker(v)
|
||||||
obj.Logf("Worker(%s): Exited(%s)", v, engineUtil.CleanError(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
|
obj.state[v].workerErr = err // store the error
|
||||||
// If the Rewatch metaparam is true, then this will get
|
// If the Rewatch metaparam is true, then this will get
|
||||||
// restarted if we do a graph cmp swap. This is why the
|
// restarted if we do a graph cmp swap. This is why the
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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.
|
||||||
|
|
||||||
//go:build !root
|
//go:build !root
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,13 +13,24 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -161,7 +172,7 @@ func (obj *Engine) ReversalList() (map[string]string, error) {
|
|||||||
result := make(map[string]string) // some key to contents
|
result := make(map[string]string) // some key to contents
|
||||||
|
|
||||||
dir := obj.statePrefix() // loop through this dir...
|
dir := obj.statePrefix() // loop through this dir...
|
||||||
files, err := ioutil.ReadDir(dir)
|
files, err := os.ReadDir(dir)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return nil, errwrap.Wrapf(err, "error reading list of state dirs")
|
return nil, errwrap.Wrapf(err, "error reading list of state dirs")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@@ -171,7 +182,7 @@ func (obj *Engine) ReversalList() (map[string]string, error) {
|
|||||||
for _, x := range files {
|
for _, x := range files {
|
||||||
key := x.Name() // some uid for the resource
|
key := x.Name() // some uid for the resource
|
||||||
file := path.Join(dir, key, ReverseFile)
|
file := path.Join(dir, key, ReverseFile)
|
||||||
content, err := ioutil.ReadFile(file)
|
content, err := os.ReadFile(file)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return nil, errwrap.Wrapf(err, "could not read reverse file: %s", file)
|
return nil, errwrap.Wrapf(err, "could not read reverse file: %s", file)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@@ -246,7 +257,7 @@ func (obj *State) ReversalCleanup() error {
|
|||||||
return nil // nothing to erase, we're not a reversal resource
|
return nil // nothing to erase, we're not a reversal resource
|
||||||
}
|
}
|
||||||
|
|
||||||
if !obj.isStateOK { // did we successfully reverse?
|
if !obj.isStateOK.Load() { // did we successfully reverse? (mutex RLock/RUnlock)
|
||||||
obj.Logf("did not complete reversal") // warn
|
obj.Logf("did not complete reversal") // warn
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -263,7 +274,7 @@ func (obj *State) ReversalWrite(str string, overwrite bool) error {
|
|||||||
}
|
}
|
||||||
file := path.Join(dir, ReverseFile) // return a unique file
|
file := path.Join(dir, ReverseFile) // return a unique file
|
||||||
|
|
||||||
content, err := ioutil.ReadFile(file)
|
content, err := os.ReadFile(file)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return errwrap.Wrapf(err, "could not read reverse file: %s", file)
|
return errwrap.Wrapf(err, "could not read reverse file: %s", file)
|
||||||
}
|
}
|
||||||
@@ -275,12 +286,12 @@ func (obj *State) ReversalWrite(str string, overwrite bool) error {
|
|||||||
if str != oldStr {
|
if str != oldStr {
|
||||||
obj.Logf("existing, pending, reversible resource exists")
|
obj.Logf("existing, pending, reversible resource exists")
|
||||||
//obj.Logf("diff:")
|
//obj.Logf("diff:")
|
||||||
//obj.Logf("") // TODO: print the diff w/o and secret values
|
//obj.Logf("") // TODO: print the diff w/o secret values
|
||||||
return fmt.Errorf("existing, pending, reversible resource exists")
|
return fmt.Errorf("existing, pending, reversible resource exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(file, []byte(str), ReversePerm)
|
return os.WriteFile(file, []byte(str), ReversePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReversalDelete removes the reversal state information for this resource.
|
// ReversalDelete removes the reversal state information for this resource.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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.
|
||||||
|
|
||||||
//go:build !root
|
//go:build !root
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,33 +13,114 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/engine"
|
"github.com/purpleidea/mgmt/engine"
|
||||||
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||||
|
"github.com/purpleidea/mgmt/lang/types"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"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
|
// 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.
|
// receiver and must be given as input the full resource struct to receive on.
|
||||||
// It applies the loaded values to the resource.
|
// It applies the loaded values to the resource. It is called recursively, as it
|
||||||
func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) {
|
// recurses into any grouped resources found within the first receiver. It
|
||||||
recv := res.Recv()
|
// returns a map of resource pointer, to resource field key, to changed boolean.
|
||||||
if obj.Debug {
|
func SendRecv(res engine.RecvableRes, fn RecvFn) (map[engine.RecvableRes]map[string]*engine.Send, error) {
|
||||||
// NOTE: this could expose private resource data like passwords
|
updated := make(map[engine.RecvableRes]map[string]*engine.Send) // list of updated keys
|
||||||
obj.Logf("%s: SendRecv: %+v", res, recv)
|
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
|
var err error
|
||||||
for k, v := range recv {
|
recv := res.Recv()
|
||||||
updated[k] = false // default
|
if fn != nil {
|
||||||
v.Changed = false // reset to the default
|
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
|
var st interface{} = v.Res // old style direct send/recv
|
||||||
if true { // new style send/recv API
|
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))
|
obj1 := reflect.Indirect(reflect.ValueOf(st))
|
||||||
type1 := obj1.Type()
|
//type1 := obj1.Type()
|
||||||
value1 := obj1.FieldByName(key1)
|
value1 := obj1.FieldByName(key1)
|
||||||
kind1 := value1.Kind()
|
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
|
obj2 := reflect.Indirect(reflect.ValueOf(res)) // pass in full struct
|
||||||
type2 := obj2.Type()
|
//type2 := obj2.Type()
|
||||||
value2 := obj2.FieldByName(key2)
|
value2 := obj2.FieldByName(key2)
|
||||||
kind2 := value2.Kind()
|
kind2 := value2.Kind()
|
||||||
|
|
||||||
if obj.Debug {
|
//orig := value1
|
||||||
obj.Logf("Send(%s) has %v: %v", type1, kind1, value1)
|
dest := value2 // save the o.g. because we need the real dest!
|
||||||
obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
|
|
||||||
|
// 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...
|
// This second block is identical, but it's just accidentally
|
||||||
if kind1 != kind2 {
|
// symmetrical. The types of input structs are different shapes.
|
||||||
e := fmt.Errorf("kind mismatch between %s: %s and %s: %s", v.Res, kind1, res, kind2)
|
// for kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
|
||||||
err = errwrap.Append(err, e) // list of errors
|
// if kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
|
||||||
continue
|
// 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
|
//if obj.Debug {
|
||||||
// FIXME: do we want to relax this for string -> *string ?
|
// obj.Logf("Send(%s) has %v: %v", type1, kind1, value1)
|
||||||
if e := TypeCmp(value1, value2); e != nil {
|
// obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
|
||||||
e := errwrap.Wrapf(e, "type mismatch between %s and %s", v.Res, res)
|
//}
|
||||||
err = errwrap.Append(err, e) // list of errors
|
|
||||||
continue
|
// 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 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)
|
e := fmt.Errorf("can't set %s.%s", res, k)
|
||||||
err = errwrap.Append(err, e) // list of errors
|
err = errwrap.Append(err, e) // list of errors
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we can't interface, we can't compare...
|
// 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)
|
e := fmt.Errorf("can't interface %s.%s", res, k)
|
||||||
err = errwrap.Append(err, e) // list of errors
|
err = errwrap.Append(err, e) // list of errors
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the values aren't equal, we're changing the receiver
|
// if the values aren't equal, we're changing the receiver
|
||||||
if !reflect.DeepEqual(value1.Interface(), value2.Interface()) {
|
if reflect.DeepEqual(value1.Interface(), value2.Interface()) {
|
||||||
// TODO: can we catch the panics here in case they happen?
|
continue // skip as they're the same, no error needed
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
return updated, err
|
||||||
}
|
}
|
||||||
@@ -150,3 +289,19 @@ func TypeCmp(a, b reflect.Value) error {
|
|||||||
|
|
||||||
return nil // identical Type()'s
|
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
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
@@ -21,10 +33,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/converger"
|
"github.com/purpleidea/mgmt/converger"
|
||||||
"github.com/purpleidea/mgmt/engine"
|
"github.com/purpleidea/mgmt/engine"
|
||||||
|
"github.com/purpleidea/mgmt/engine/local"
|
||||||
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
@@ -43,23 +57,27 @@ type State struct {
|
|||||||
Program string
|
Program string
|
||||||
Version string
|
Version string
|
||||||
Hostname 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
|
// Prefix is a unique directory prefix which can be used. It should be
|
||||||
// created if needed.
|
// created if needed.
|
||||||
Prefix string
|
Prefix string
|
||||||
|
|
||||||
//Converger *converger.Coordinator
|
|
||||||
|
|
||||||
// Debug turns on additional output and behaviours.
|
// Debug turns on additional output and behaviours.
|
||||||
Debug bool
|
Debug bool
|
||||||
|
|
||||||
// Logf is the logging function that should be used to display messages.
|
// Logf is the logging function that should be used to display messages.
|
||||||
Logf func(format string, v ...interface{})
|
Logf func(format string, v ...interface{})
|
||||||
|
|
||||||
timestamp int64 // last updated timestamp
|
timestamp int64 // last updated timestamp
|
||||||
isStateOK bool // is state OK or do we need to run CheckApply ?
|
isStateOK *atomic.Bool // is state OK or do we need to run CheckApply ?
|
||||||
workerErr error // did the Worker error?
|
workerErr error // did the Worker error?
|
||||||
|
|
||||||
|
mutex *sync.RWMutex // used for editing state properties
|
||||||
|
|
||||||
// doneCtx is cancelled when Watch should shut down. When any of the
|
// doneCtx is cancelled when Watch should shut down. When any of the
|
||||||
// following channels close, it causes this to close.
|
// following channels close, it causes this to close.
|
||||||
@@ -140,6 +158,9 @@ func (obj *State) Init() error {
|
|||||||
return fmt.Errorf("the Logf function is missing")
|
return fmt.Errorf("the Logf function is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj.isStateOK = &atomic.Bool{}
|
||||||
|
|
||||||
|
obj.mutex = &sync.RWMutex{}
|
||||||
obj.doneCtx, obj.doneCtxCancel = context.WithCancel(context.Background())
|
obj.doneCtx, obj.doneCtxCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
obj.processDone = make(chan struct{})
|
obj.processDone = make(chan struct{})
|
||||||
@@ -238,12 +259,13 @@ func (obj *State) Init() error {
|
|||||||
return graph, nil // we return in a func so it's fresh!
|
return graph, nil // we return in a func so it's fresh!
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Local: obj.Local,
|
||||||
World: obj.World,
|
World: obj.World,
|
||||||
VarDir: obj.varDir,
|
VarDir: obj.varDir,
|
||||||
|
|
||||||
Debug: obj.Debug,
|
Debug: obj.Debug,
|
||||||
Logf: func(format string, v ...interface{}) {
|
Logf: func(format string, v ...interface{}) {
|
||||||
obj.Logf("resource: "+format, v...)
|
obj.Logf(format, v...)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +405,9 @@ func (obj *State) event() {
|
|||||||
// CheckApply will have some work to do in order to converge it.
|
// CheckApply will have some work to do in order to converge it.
|
||||||
func (obj *State) setDirty() {
|
func (obj *State) setDirty() {
|
||||||
obj.tuid.StopTimer()
|
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.
|
// poll is a replacement for Watch when the Poll metaparameter is used.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package graph
|
||||||
|
|
||||||
@@ -42,7 +54,8 @@ func (obj *State) varDir(extra string) (string, error) {
|
|||||||
|
|
||||||
// an empty string at the end has no effect
|
// an empty string at the end has no effect
|
||||||
p := fmt.Sprintf("%s/", path.Join(obj.Prefix, extra))
|
p := fmt.Sprintf("%s/", path.Join(obj.Prefix, extra))
|
||||||
if err := os.MkdirAll(p, 0770); err != nil {
|
// 0775 since we want children to be able to read this!
|
||||||
|
if err := os.MkdirAll(p, 0775); err != nil {
|
||||||
return "", errwrap.Wrapf(err, "can't create prefix in: %s", p)
|
return "", errwrap.Wrapf(err, "can't create prefix in: %s", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
|
|||||||
594
engine/local/local.go
Normal file
594
engine/local/local.go
Normal file
@@ -0,0 +1,594 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"
|
||||||
|
"strconv"
|
||||||
|
"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 // TODO: Rename to ValueImpl?
|
||||||
|
|
||||||
|
// VarDirImpl is the implementation for the VarDir API's. The API's are
|
||||||
|
// the collection of public methods that exist on this struct.
|
||||||
|
*VarDirImpl
|
||||||
|
|
||||||
|
// PoolImpl is the implementation for the Pool API's. The API's are the
|
||||||
|
// collection of public methods that exist on this struct.
|
||||||
|
*PoolImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
})
|
||||||
|
|
||||||
|
obj.VarDirImpl = &VarDirImpl{}
|
||||||
|
obj.VarDirImpl.Init(&VarDirInit{
|
||||||
|
Prefix: obj.Prefix,
|
||||||
|
Debug: obj.Debug,
|
||||||
|
Logf: obj.Logf,
|
||||||
|
})
|
||||||
|
|
||||||
|
obj.PoolImpl = &PoolImpl{}
|
||||||
|
obj.PoolImpl.Init(&PoolInit{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarDirInit are the init values that the VarDir API needs to work correctly.
|
||||||
|
type VarDirInit struct {
|
||||||
|
Prefix string
|
||||||
|
Debug bool
|
||||||
|
Logf func(format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarDirImpl is the implementation for the VarDir API's. The API's are the
|
||||||
|
// collection of public methods that exist on this struct.
|
||||||
|
type VarDirImpl struct {
|
||||||
|
init *VarDirInit
|
||||||
|
mutex *sync.Mutex
|
||||||
|
prefix string
|
||||||
|
prefixExists bool // is it okay to use the prefix?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init runs some initialization code for the VarDir API.
|
||||||
|
func (obj *VarDirImpl) Init(init *VarDirInit) {
|
||||||
|
obj.init = init
|
||||||
|
obj.mutex = &sync.Mutex{}
|
||||||
|
obj.prefix = fmt.Sprintf("%s/", path.Join(obj.init.Prefix, "vardir"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarDir returns a directory rooted at the internal prefix.
|
||||||
|
func (obj *VarDirImpl) VarDir(ctx context.Context, reldir string) (string, error) {
|
||||||
|
if strings.HasPrefix(reldir, "/") {
|
||||||
|
return "", fmt.Errorf("path must be relative")
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(reldir, "/") {
|
||||||
|
return "", fmt.Errorf("path must be a dir")
|
||||||
|
}
|
||||||
|
// NOTE: The above checks ensure we don't get either "" or "/" as input!
|
||||||
|
|
||||||
|
prefix, err := obj.getPrefix()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := fmt.Sprintf("%s/", path.Join(prefix, reldir))
|
||||||
|
|
||||||
|
// TODO: Should we mkdir this?
|
||||||
|
obj.mutex.Lock()
|
||||||
|
defer obj.mutex.Unlock()
|
||||||
|
if err := os.MkdirAll(result, 0755); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, 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 *VarDirImpl) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolInit are the init values that the Pool API needs to work correctly.
|
||||||
|
type PoolInit struct {
|
||||||
|
Prefix string
|
||||||
|
Debug bool
|
||||||
|
Logf func(format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolConfig configures how the Pool operates.
|
||||||
|
// XXX: These are not implemented yet.
|
||||||
|
type PoolConfig struct {
|
||||||
|
// Expiry specifies that we expire old values that have not been read
|
||||||
|
// for this many seconds. Zero disables this and they never expire.
|
||||||
|
Expiry int64 // TODO: or time.Time ?
|
||||||
|
|
||||||
|
// Random lets you allocate a random integer instead of sequential ones.
|
||||||
|
Random bool
|
||||||
|
|
||||||
|
// Max specifies the maximum integer to allocate.
|
||||||
|
Max int
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolImpl is the implementation for the Pool API's. The API's are the
|
||||||
|
// collection of public methods that exist on this struct.
|
||||||
|
type PoolImpl struct {
|
||||||
|
init *PoolInit
|
||||||
|
mutex *sync.Mutex
|
||||||
|
prefix string
|
||||||
|
prefixExists bool // is it okay to use the prefix?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init runs some initialization code for the Pool API.
|
||||||
|
func (obj *PoolImpl) Init(init *PoolInit) {
|
||||||
|
obj.init = init
|
||||||
|
obj.mutex = &sync.Mutex{}
|
||||||
|
obj.prefix = fmt.Sprintf("%s/", path.Join(obj.init.Prefix, "pool"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool returns a unique integer from a pool of numbers. Within a given
|
||||||
|
// namespace, it returns the same integer for a given name. It is a simple
|
||||||
|
// mechanism to allocate numbers to different inputs when we don't have a
|
||||||
|
// hashing alternative. It does not allocate zero.
|
||||||
|
func (obj *PoolImpl) Pool(ctx context.Context, namespace, uid string, config *PoolConfig) (int, error) {
|
||||||
|
if namespace == "" {
|
||||||
|
return 0, fmt.Errorf("namespace is empty")
|
||||||
|
}
|
||||||
|
if strings.Contains(namespace, "/") {
|
||||||
|
return 0, fmt.Errorf("namespace contains slash")
|
||||||
|
}
|
||||||
|
if uid == "" {
|
||||||
|
return 0, fmt.Errorf("uid is empty")
|
||||||
|
}
|
||||||
|
if strings.Contains(uid, "/") {
|
||||||
|
return 0, fmt.Errorf("uid contains slash")
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix, err := obj.getPrefix()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := fmt.Sprintf("%s/", path.Join(prefix, namespace))
|
||||||
|
file := fmt.Sprintf("%s.uid", path.Join(dir, uid)) // file
|
||||||
|
|
||||||
|
// TODO: Run clean up funcs here to get rid of any stale/expired values.
|
||||||
|
// TODO: This will happen based on the future config options we build...
|
||||||
|
|
||||||
|
obj.mutex.Lock()
|
||||||
|
defer obj.mutex.Unlock()
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := func(p string) (int, error) {
|
||||||
|
b, err := os.ReadFile(p)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return 0, err // real error
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil // absent!
|
||||||
|
}
|
||||||
|
|
||||||
|
// File exists!
|
||||||
|
d, err := strconv.Atoi(strings.TrimSpace(string(b)))
|
||||||
|
if err != nil {
|
||||||
|
// Someone put corrupt data in a uid file.
|
||||||
|
return 0, err // real error
|
||||||
|
}
|
||||||
|
return d, nil // value already allocated!
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := fn(file)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err // real error
|
||||||
|
}
|
||||||
|
if d != 0 {
|
||||||
|
return d, nil // Value already allocated! We're done early.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found, so find the max. (0 without error means not found!)
|
||||||
|
|
||||||
|
files, err := os.ReadDir(dir) // ([]os.DirEntry, error)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err // real error
|
||||||
|
}
|
||||||
|
|
||||||
|
m := 0
|
||||||
|
for _, f := range files {
|
||||||
|
if f.IsDir() {
|
||||||
|
continue // unexpected!
|
||||||
|
}
|
||||||
|
d, err := fn(path.Join(dir, f.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err // real error
|
||||||
|
}
|
||||||
|
if d == 0 {
|
||||||
|
// Must be someone deleting files without our mutex!
|
||||||
|
return 0, fmt.Errorf("unexpected missing file")
|
||||||
|
}
|
||||||
|
|
||||||
|
m = max(m, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
m++ // increment
|
||||||
|
data := []byte(fmt.Sprintf("%d\n", m)) // it's polite to end with \n
|
||||||
|
if err := os.WriteFile(file, data, 0600); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, 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 *PoolImpl) 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
|
||||||
|
}
|
||||||
62
engine/local/local_test.go
Normal file
62
engine/local/local_test.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
@@ -40,6 +52,7 @@ var DefaultMetaParams = &MetaParams{
|
|||||||
//Sema: []string{},
|
//Sema: []string{},
|
||||||
Rewatch: false,
|
Rewatch: false,
|
||||||
Realize: false, // true would be more awesome, but unexpected for users
|
Realize: false, // true would be more awesome, but unexpected for users
|
||||||
|
Dollar: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetaRes is the interface a resource must implement to support meta params.
|
// MetaRes is the interface a resource must implement to support meta params.
|
||||||
@@ -120,6 +133,13 @@ type MetaParams struct {
|
|||||||
// the resource is blocked because of a failed pre-requisite resource.
|
// the resource is blocked because of a failed pre-requisite resource.
|
||||||
// XXX: Not implemented!
|
// XXX: Not implemented!
|
||||||
Realize bool `yaml:"realize"`
|
Realize bool `yaml:"realize"`
|
||||||
|
|
||||||
|
// Dollar allows you to name a resource to start with the dollar
|
||||||
|
// character. We don't allow this by default since it's probably not
|
||||||
|
// needed, and is more likely to be a typo where the user forgot to
|
||||||
|
// interpolate a variable name. In the rare case when it's needed, you
|
||||||
|
// can disable that check with this meta param.
|
||||||
|
Dollar bool `yaml:"dollar"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cmp compares two AutoGroupMeta structs and determines if they're equivalent.
|
// Cmp compares two AutoGroupMeta structs and determines if they're equivalent.
|
||||||
@@ -166,6 +186,9 @@ func (obj *MetaParams) Cmp(meta *MetaParams) error {
|
|||||||
if obj.Realize != meta.Realize {
|
if obj.Realize != meta.Realize {
|
||||||
return fmt.Errorf("values for Realize are different")
|
return fmt.Errorf("values for Realize are different")
|
||||||
}
|
}
|
||||||
|
if obj.Dollar != meta.Dollar {
|
||||||
|
return fmt.Errorf("values for Dollar are different")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -206,6 +229,7 @@ func (obj *MetaParams) Copy() *MetaParams {
|
|||||||
Sema: sema,
|
Sema: sema,
|
||||||
Rewatch: obj.Rewatch,
|
Rewatch: obj.Rewatch,
|
||||||
Realize: obj.Realize,
|
Realize: obj.Realize,
|
||||||
|
Dollar: obj.Dollar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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.
|
||||||
|
|
||||||
//go:build !root
|
//go:build !root
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package engine
|
||||||
|
|
||||||
@@ -21,13 +33,25 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
docsUtil "github.com/purpleidea/mgmt/docs/util"
|
||||||
|
"github.com/purpleidea/mgmt/engine/local"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ResourcesRelDir is the path where the resources are kept, relative to
|
||||||
|
// the main source code root.
|
||||||
|
ResourcesRelDir = "engine/resources/"
|
||||||
|
)
|
||||||
|
|
||||||
// TODO: should each resource be a sub-package?
|
// TODO: should each resource be a sub-package?
|
||||||
var registeredResources = map[string]func() Res{}
|
var registeredResources = map[string]func() Res{}
|
||||||
|
|
||||||
@@ -43,6 +67,23 @@ func RegisterResource(kind string, fn func() Res) {
|
|||||||
}
|
}
|
||||||
gob.Register(f)
|
gob.Register(f)
|
||||||
registeredResources[kind] = fn
|
registeredResources[kind] = fn
|
||||||
|
|
||||||
|
// Additional metadata for documentation generation!
|
||||||
|
_, filename, _, ok := runtime.Caller(1)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("could not locate resource filename for %s", kind))
|
||||||
|
}
|
||||||
|
sp := strings.Split(reflect.TypeOf(f).String(), ".")
|
||||||
|
if len(sp) != 2 {
|
||||||
|
panic(fmt.Sprintf("could not parse resource struct name for %s", kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := docsUtil.RegisterResource(kind, &docsUtil.Metadata{
|
||||||
|
Filename: filepath.Base(filename),
|
||||||
|
Typename: sp[1],
|
||||||
|
}); err != nil {
|
||||||
|
panic(fmt.Sprintf("could not register resource metadata for %s", kind))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisteredResourcesNames returns the kind of the registered resources.
|
// RegisteredResourcesNames returns the kind of the registered resources.
|
||||||
@@ -135,6 +176,11 @@ type Init struct {
|
|||||||
|
|
||||||
// TODO: GraphQuery offers an interface to query the resource graph.
|
// 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
|
// World provides a connection to the outside world. This is most often
|
||||||
// used for communicating with the distributed database.
|
// used for communicating with the distributed database.
|
||||||
World World
|
World World
|
||||||
@@ -254,6 +300,12 @@ func Validate(res Res) error {
|
|||||||
return errwrap.Wrapf(err, "the Res has an invalid meta param")
|
return errwrap.Wrapf(err, "the Res has an invalid meta param")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: pull dollar prefix from a constant
|
||||||
|
// This catches typos where the user meant to use ${var} interpolation.
|
||||||
|
if !res.MetaParams().Dollar && strings.HasPrefix(res.Name(), "$") {
|
||||||
|
return fmt.Errorf("the Res name starts with a $")
|
||||||
|
}
|
||||||
|
|
||||||
return res.Validate()
|
return res.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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.
|
||||||
|
|
||||||
//go:build !noaugeas
|
//go:build !noaugeas
|
||||||
|
|
||||||
@@ -27,8 +39,8 @@ import (
|
|||||||
|
|
||||||
"github.com/purpleidea/mgmt/engine"
|
"github.com/purpleidea/mgmt/engine"
|
||||||
"github.com/purpleidea/mgmt/engine/traits"
|
"github.com/purpleidea/mgmt/engine/traits"
|
||||||
"github.com/purpleidea/mgmt/recwatch"
|
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
|
"github.com/purpleidea/mgmt/util/recwatch"
|
||||||
|
|
||||||
"honnef.co/go/augeas"
|
"honnef.co/go/augeas"
|
||||||
)
|
)
|
||||||
@@ -194,7 +206,6 @@ func (obj *AugeasRes) checkApplySet(ctx context.Context, apply bool, ag *augeas.
|
|||||||
|
|
||||||
// CheckApply method for Augeas resource.
|
// CheckApply method for Augeas resource.
|
||||||
func (obj *AugeasRes) CheckApply(ctx context.Context, 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.
|
// By default we do not set any option to augeas, we use the defaults.
|
||||||
opts := augeas.None
|
opts := augeas.None
|
||||||
if obj.Lens != "" {
|
if obj.Lens != "" {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package resources
|
||||||
|
|
||||||
@@ -24,7 +36,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -626,7 +638,7 @@ func (obj *AwsEc2Res) snsWatch(ctx context.Context) error {
|
|||||||
|
|
||||||
// CheckApply method for AwsEc2 resource.
|
// CheckApply method for AwsEc2 resource.
|
||||||
func (obj *AwsEc2Res) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
func (obj *AwsEc2Res) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||||
obj.init.Logf("CheckApply(%t)", apply)
|
obj.init.Logf("CheckApply(%t)", apply) // XXX: replace with logf on change
|
||||||
|
|
||||||
// find the instance we need to check
|
// find the instance we need to check
|
||||||
instance, err := describeInstanceByName(obj.client, obj.prependName())
|
instance, err := describeInstanceByName(obj.client, obj.prependName())
|
||||||
@@ -980,7 +992,7 @@ func (obj *AwsEc2Res) snsGetCert(url string) (*x509.Certificate, error) {
|
|||||||
return nil, errwrap.Wrapf(err, "http get error")
|
return nil, errwrap.Wrapf(err, "http get error")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "error reading post body")
|
return nil, errwrap.Wrapf(err, "error reading post body")
|
||||||
}
|
}
|
||||||
|
|||||||
466
engine/resources/bmc_power.go
Normal file
466
engine/resources/bmc_power.go
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 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"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/purpleidea/mgmt/engine"
|
||||||
|
"github.com/purpleidea/mgmt/engine/traits"
|
||||||
|
|
||||||
|
bmclib "github.com/bmc-toolbox/bmclib/v2"
|
||||||
|
"github.com/bmc-toolbox/bmclib/v2/providers/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
engine.RegisterResource("bmc:power", func() engine.Res { return &BmcPowerRes{} })
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultBmcPowerPort is the default port we try to connect on.
|
||||||
|
DefaultBmcPowerPort = 443
|
||||||
|
|
||||||
|
// BmcDriverSecureSuffix is the magic char we append to a driver name to
|
||||||
|
// specify we want the SSL/TLS variant.
|
||||||
|
BmcDriverSecureSuffix = "s"
|
||||||
|
|
||||||
|
// BmcDriverRPC is the RPC driver.
|
||||||
|
BmcDriverRPC = "rpc"
|
||||||
|
|
||||||
|
// BmcDriverGofish is the gofish driver.
|
||||||
|
BmcDriverGofish = "gofish"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BmcPowerRes is a resource that manages power state of a BMC. This is usually
|
||||||
|
// used for turning computers on and off. The name value can be a big URL string
|
||||||
|
// in the form: `driver://user:pass@hostname:port` for example you may see:
|
||||||
|
// gofishs://ADMIN:hunter2@127.0.0.1:8800 to use the "https" variant of the
|
||||||
|
// gofish driver.
|
||||||
|
//
|
||||||
|
// NOTE: New drivers should either not end in "s" or at least not be identical
|
||||||
|
// to the name of another driver an "s" is added or removed to the end.
|
||||||
|
type BmcPowerRes struct {
|
||||||
|
traits.Base // add the base methods without re-implementation
|
||||||
|
|
||||||
|
init *engine.Init
|
||||||
|
|
||||||
|
// Hostname to connect to. If not specified, we parse this from the
|
||||||
|
// Name.
|
||||||
|
Hostname string `lang:"hostname" yaml:"hostname"`
|
||||||
|
|
||||||
|
// Port to connect to. If not specified, we parse this from the Name.
|
||||||
|
Port int `lang:"port" yaml:"port"`
|
||||||
|
|
||||||
|
// Username to use to connect. If not specified, we parse this from the
|
||||||
|
// Name.
|
||||||
|
// TODO: If the Username field is not set, should we parse from the
|
||||||
|
// Name? It's not really part of the BMC unique identifier so maybe we
|
||||||
|
// shouldn't use that.
|
||||||
|
Username string `lang:"username" yaml:"username"`
|
||||||
|
|
||||||
|
// Password to use to connect. We do NOT parse this from the Name unless
|
||||||
|
// you set InsecurePassword to true.
|
||||||
|
// XXX: Use mgmt magic credentials in the future.
|
||||||
|
Password string `lang:"password" yaml:"password"`
|
||||||
|
|
||||||
|
// InsecurePassword can be set to true to allow a password in the Name.
|
||||||
|
InsecurePassword bool `lang:"insecure_password" yaml:"insecure_password"`
|
||||||
|
|
||||||
|
// Driver to use, such as: "gofish" or "rpc". This is a different
|
||||||
|
// concept than the "bmclib" driver vs provider distinction. Here we
|
||||||
|
// just statically pick what we're using without any magic. If not
|
||||||
|
// specified, we parse this from the Name scheme. If this ends with an
|
||||||
|
// extra "s" then we use https instead of http.
|
||||||
|
Driver string `lang:"driver" yaml:"driver"`
|
||||||
|
|
||||||
|
// State of machine power. Can be "on" or "off".
|
||||||
|
State string `lang:"state" yaml:"state"`
|
||||||
|
|
||||||
|
driver string
|
||||||
|
scheme string
|
||||||
|
}
|
||||||
|
|
||||||
|
// validDriver determines if we are using a valid drive. This does not include
|
||||||
|
// the magic "s" bits. This function need to be expanded as we support more
|
||||||
|
// drivers.
|
||||||
|
func (obj *BmcPowerRes) validDriver(driver string) error {
|
||||||
|
if driver == BmcDriverRPC {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if driver == BmcDriverGofish {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unknown driver: %s", driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHostname returns the hostname that we want to connect to. If the Hostname
|
||||||
|
// field is set, we use that, otherwise we parse from the Name.
|
||||||
|
func (obj *BmcPowerRes) getHostname() string {
|
||||||
|
if obj.Hostname != "" {
|
||||||
|
return obj.Hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(obj.Name())
|
||||||
|
if err != nil || u == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitHostPort splits a network address of the form "host:port",
|
||||||
|
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
|
||||||
|
// host%zone and port.
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
return u.Host // must be a naked hostname or ip w/o port
|
||||||
|
}
|
||||||
|
_ = port
|
||||||
|
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPort returns the port that we want to connect to. If the Port field is
|
||||||
|
// set, we use that, otherwise we parse from the Name.
|
||||||
|
//
|
||||||
|
// NOTE: We return a string since all the bmclib things usually expect a string,
|
||||||
|
// but if that gets fixed we should return an int here instead.
|
||||||
|
func (obj *BmcPowerRes) getPort() string {
|
||||||
|
if obj.Port != 0 {
|
||||||
|
return strconv.Itoa(obj.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(obj.Name())
|
||||||
|
if err != nil || u == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitHostPort splits a network address of the form "host:port",
|
||||||
|
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
|
||||||
|
// host%zone and port.
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
return strconv.Itoa(DefaultBmcPowerPort) // default port
|
||||||
|
}
|
||||||
|
_ = host
|
||||||
|
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUsername returns the username that we want to connect with. If the
|
||||||
|
// Username field is set, we use that, otherwise we parse from the Name.
|
||||||
|
// TODO: If the Username field is not set, should we parse from the Name? It's
|
||||||
|
// not really part of the BMC unique identifier so maybe we shouldn't use that.
|
||||||
|
func (obj *BmcPowerRes) getUsername() string {
|
||||||
|
if obj.Username != "" {
|
||||||
|
return obj.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(obj.Name())
|
||||||
|
if err != nil || u == nil || u.User == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.User.Username()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPassword returns the password that we want to connect with.
|
||||||
|
// XXX: Use mgmt magic credentials in the future.
|
||||||
|
func (obj *BmcPowerRes) getPassword() string {
|
||||||
|
if obj.Password != "" || !obj.InsecurePassword {
|
||||||
|
return obj.Password
|
||||||
|
}
|
||||||
|
// NOTE: We don't look at any password string from the name unless the
|
||||||
|
// InsecurePassword field is true.
|
||||||
|
|
||||||
|
u, err := url.Parse(obj.Name())
|
||||||
|
if err != nil || u == nil || u.User == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
password, ok := u.User.Password()
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRawDriver returns the raw magic driver string. If the Driver field is set,
|
||||||
|
// we use that, otherwise we parse from the Name. This version may include the
|
||||||
|
// magic "s" at the end.
|
||||||
|
func (obj *BmcPowerRes) getRawDriver() string {
|
||||||
|
if obj.Driver != "" {
|
||||||
|
return obj.Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(obj.Name())
|
||||||
|
if err != nil || u == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDriverAndScheme figures out which driver and scheme we want to use.
|
||||||
|
func (obj *BmcPowerRes) getDriverAndScheme() (string, string, error) {
|
||||||
|
driver := obj.getRawDriver()
|
||||||
|
err := obj.validDriver(driver)
|
||||||
|
if err == nil {
|
||||||
|
return driver, "http", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
driver = strings.TrimSuffix(driver, BmcDriverSecureSuffix)
|
||||||
|
if err := obj.validDriver(driver); err == nil {
|
||||||
|
return driver, "https", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", err // return the first error
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDriver returns the actual driver that we want to connect with. If the
|
||||||
|
// Driver field is set, we use that, otherwise we parse from the Name. This
|
||||||
|
// version does NOT include the magic "s" at the end.
|
||||||
|
func (obj *BmcPowerRes) getDriver() string {
|
||||||
|
return obj.driver
|
||||||
|
}
|
||||||
|
|
||||||
|
// getScheme figures out which scheme we want to use.
|
||||||
|
func (obj *BmcPowerRes) getScheme() string {
|
||||||
|
return obj.scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default returns some sensible defaults for this resource.
|
||||||
|
func (obj *BmcPowerRes) Default() engine.Res {
|
||||||
|
return &BmcPowerRes{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate if the params passed in are valid data.
|
||||||
|
func (obj *BmcPowerRes) Validate() error {
|
||||||
|
// XXX: Force polling until we have real events...
|
||||||
|
if obj.MetaParams().Poll == 0 {
|
||||||
|
return fmt.Errorf("events are not yet supported, use polling")
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.getHostname() == "" {
|
||||||
|
return fmt.Errorf("need a Hostname")
|
||||||
|
}
|
||||||
|
//if obj.getUsername() == "" {
|
||||||
|
// return fmt.Errorf("need a Username")
|
||||||
|
//}
|
||||||
|
|
||||||
|
if obj.getRawDriver() == "" {
|
||||||
|
return fmt.Errorf("need a Driver")
|
||||||
|
}
|
||||||
|
if _, _, err := obj.getDriverAndScheme(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init runs some startup code for this resource.
|
||||||
|
func (obj *BmcPowerRes) Init(init *engine.Init) error {
|
||||||
|
obj.init = init // save for later
|
||||||
|
|
||||||
|
driver, scheme, err := obj.getDriverAndScheme()
|
||||||
|
if err != nil {
|
||||||
|
// programming error (we checked in Validate)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
obj.driver = driver
|
||||||
|
obj.scheme = scheme
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup is run by the engine to clean up after the resource is done.
|
||||||
|
func (obj *BmcPowerRes) Cleanup() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// client builds the bmclib client. The API to build it is complicated.
|
||||||
|
func (obj *BmcPowerRes) client() *bmclib.Client {
|
||||||
|
// NOTE: The bmclib API is weird, you can't put the port in this string!
|
||||||
|
u := fmt.Sprintf("%s://%s", obj.getScheme(), obj.getHostname())
|
||||||
|
|
||||||
|
uPort := u
|
||||||
|
if p := obj.getPort(); p != "" {
|
||||||
|
uPort = u + ":" + p
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []bmclib.Option{}
|
||||||
|
|
||||||
|
if obj.getDriver() == BmcDriverRPC {
|
||||||
|
opts = append(opts, bmclib.WithRPCOpt(rpc.Provider{
|
||||||
|
// NOTE: The main API cannot take a port, but here we do!
|
||||||
|
ConsumerURL: uPort,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p := obj.getPort(); p != "" {
|
||||||
|
switch obj.getDriver() {
|
||||||
|
case BmcDriverRPC:
|
||||||
|
// TODO: ???
|
||||||
|
|
||||||
|
case BmcDriverGofish:
|
||||||
|
// XXX: Why doesn't this accept an int?
|
||||||
|
opts = append(opts, bmclib.WithRedfishPort(p))
|
||||||
|
|
||||||
|
//case BmcDriverOpenbmc:
|
||||||
|
// // XXX: Why doesn't this accept an int?
|
||||||
|
// opts = append(opts, openbmc.WithPort(p))
|
||||||
|
|
||||||
|
default:
|
||||||
|
// TODO: error or pass through?
|
||||||
|
obj.init.Logf("unhandled driver: %s", obj.getDriver())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := bmclib.NewClient(u, obj.getUsername(), obj.Password, opts...)
|
||||||
|
|
||||||
|
if obj.getDriver() != "" && obj.getDriver() != BmcDriverRPC {
|
||||||
|
client = client.For(obj.getDriver()) // limit to this provider
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch is the primary listener for this resource and it outputs events.
|
||||||
|
func (obj *BmcPowerRes) Watch(ctx context.Context) error {
|
||||||
|
obj.init.Running() // when started, notify engine that we're running
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done(): // closed by the engine to signal shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
//obj.init.Event() // notify engine of an event (this can block)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckApply method for BmcPower resource. Does nothing, returns happy!
|
||||||
|
func (obj *BmcPowerRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||||
|
|
||||||
|
client := obj.client()
|
||||||
|
|
||||||
|
if err := client.Open(ctx); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer client.Close(ctx) // (err error)
|
||||||
|
|
||||||
|
if obj.init.Debug {
|
||||||
|
obj.init.Logf("connected ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := client.GetPowerState(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
state = strings.ToLower(state) // normalize
|
||||||
|
obj.init.Logf("get state: %s", state)
|
||||||
|
|
||||||
|
if !apply {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.State == state {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should this be "On" and "Off"? Does case matter?
|
||||||
|
ok, err := client.SetPowerState(ctx, obj.State)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// TODO: When is this ever false?
|
||||||
|
}
|
||||||
|
obj.init.Logf("set state: %s", obj.State)
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmp compares two resources and returns an error if they are not equivalent.
|
||||||
|
func (obj *BmcPowerRes) Cmp(r engine.Res) error {
|
||||||
|
// we can only compare BmcPowerRes to others of the same resource kind
|
||||||
|
res, ok := r.(*BmcPowerRes)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("not a %s", obj.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Hostname != res.Hostname {
|
||||||
|
return fmt.Errorf("the Hostname differs")
|
||||||
|
}
|
||||||
|
if obj.Port != res.Port {
|
||||||
|
return fmt.Errorf("the Port differs")
|
||||||
|
}
|
||||||
|
if obj.Username != res.Username {
|
||||||
|
return fmt.Errorf("the Username differs")
|
||||||
|
}
|
||||||
|
if obj.Password != res.Password {
|
||||||
|
return fmt.Errorf("the Password differs")
|
||||||
|
}
|
||||||
|
if obj.InsecurePassword != res.InsecurePassword {
|
||||||
|
return fmt.Errorf("the InsecurePassword differs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Driver != res.Driver {
|
||||||
|
return fmt.Errorf("the Driver differs")
|
||||||
|
}
|
||||||
|
if obj.State != res.State {
|
||||||
|
return fmt.Errorf("the State differs")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
|
||||||
|
// primarily useful for setting the defaults.
|
||||||
|
func (obj *BmcPowerRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
type rawRes BmcPowerRes // indirection to avoid infinite recursion
|
||||||
|
|
||||||
|
def := obj.Default() // get the default
|
||||||
|
res, ok := def.(*BmcPowerRes) // put in the right format
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("could not convert to BmcPowerRes")
|
||||||
|
}
|
||||||
|
raw := rawRes(*res) // convert; the defaults go here
|
||||||
|
|
||||||
|
if err := unmarshal(&raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*obj = BmcPowerRes(raw) // restore from indirection with type conversion!
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,19 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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
|
package resources
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mgmt
|
// Mgmt
|
||||||
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
// Copyright (C) James Shubin and the project contributors
|
||||||
// Written by James Shubin <james@shubin.ca> 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
|
// This program is free software: you can redistribute it and/or modify
|
||||||
@@ -13,7 +13,21 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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.
|
||||||
|
|
||||||
|
//go:build !noconsul
|
||||||
|
|
||||||
package resources
|
package resources
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user