Compare commits
619 Commits
feat/edge-
...
feat/fedex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eab395555c | ||
|
|
ab627107ad | ||
|
|
cdfa0f8f34 | ||
|
|
a304b9c7ef | ||
|
|
7a6ff10268 | ||
|
|
fd91de3571 | ||
|
|
ab3a6f402e | ||
|
|
d3edb7ebd5 | ||
|
|
c23b8b2816 | ||
|
|
724f1f63b7 | ||
|
|
c6ae8467c0 | ||
|
|
56087bcbb3 | ||
|
|
315c1c7e1e | ||
|
|
819dc4d561 | ||
|
|
380a64d546 | ||
|
|
6429546462 | ||
|
|
ebfb71da05 | ||
|
|
ae0b9b1e30 | ||
|
|
e9de484c3e | ||
|
|
83a1ce9d2a | ||
|
|
66fd039933 | ||
|
|
1722257d68 | ||
|
|
7d8b037761 | ||
|
|
cd52e04a5a | ||
|
|
a0fa64781a | ||
|
|
43e3cb476b | ||
|
|
a1a88eb5e4 | ||
|
|
cb79dc18f8 | ||
|
|
e9384a6987 | ||
|
|
90a0e6fe35 | ||
|
|
e5f8466fb9 | ||
|
|
c3110a85b2 | ||
|
|
89eda13eb3 | ||
|
|
c96551e410 | ||
|
|
9f7d5ac842 | ||
|
|
648c1db437 | ||
|
|
4e20d70a99 | ||
|
|
3b2f0ff9eb | ||
|
|
fcb76f570e | ||
|
|
c384d834f5 | ||
|
|
45e2ed3d86 | ||
|
|
6e0f83b99e | ||
|
|
4fe2a7c750 | ||
|
|
f8b8d549fd | ||
|
|
1b0db4971f | ||
|
|
6063f368ea | ||
|
|
8ef584e41c | ||
|
|
ceaee4e175 | ||
|
|
1e21961e6a | ||
|
|
5777c18297 | ||
|
|
ef1d648c07 | ||
|
|
393d1fc91d | ||
|
|
f9fe440401 | ||
|
|
fad376b415 | ||
|
|
d3f094cb18 | ||
|
|
1950c4ca2b | ||
|
|
5232427a5b | ||
|
|
0fac1f85f7 | ||
|
|
70ce4e70d9 | ||
|
|
47f2490059 | ||
|
|
4d123895ea | ||
|
|
36e7981ab7 | ||
|
|
53025178ef | ||
|
|
f71fe87ba7 | ||
|
|
6078234d07 | ||
|
|
fa162cafc1 | ||
|
|
9ef5636718 | ||
|
|
7accdf704c | ||
|
|
d570aee554 | ||
|
|
a7d458f0bd | ||
|
|
1a9d793f2f | ||
|
|
0242c8e4ef | ||
|
|
6c4c958bf0 | ||
|
|
dd1662c8b8 | ||
|
|
fdfebcf731 | ||
|
|
9ce3e7d20d | ||
|
|
bf8b9463d3 | ||
|
|
9375e577b0 | ||
|
|
d95a67a567 | ||
|
|
160e210ffe | ||
|
|
c9eaad6237 | ||
|
|
2edff939ef | ||
|
|
13338c46bb | ||
|
|
ea05814af4 | ||
|
|
0fe2ddf535 | ||
|
|
9af9395b73 | ||
|
|
d9cc7eda51 | ||
|
|
77c3f9131b | ||
|
|
2b2580fb61 | ||
|
|
f870619fb6 | ||
|
|
602e42739e | ||
|
|
326a8abdc7 | ||
|
|
c0f3d0193d | ||
|
|
f9427c8fb2 | ||
|
|
9b02f575ef | ||
|
|
5b4f6098d8 | ||
|
|
ccaf2bedb7 | ||
|
|
88757d2617 | ||
|
|
d79586cf6a | ||
|
|
a9b1a9c194 | ||
|
|
eb5036b96f | ||
|
|
2f0dbf2ae1 | ||
|
|
c79be58700 | ||
|
|
d24e5ff71e | ||
|
|
6536d36c24 | ||
|
|
6174940ac2 | ||
|
|
4c98fcd7db | ||
|
|
ad8054ac1f | ||
|
|
a54c54ef24 | ||
|
|
27095ede22 | ||
|
|
e2789ab354 | ||
|
|
d4f4bb532f | ||
|
|
c6ab5d5717 | ||
|
|
234627f278 | ||
|
|
87214d48be | ||
|
|
a2a35a1851 | ||
|
|
11f0574ad3 | ||
|
|
9fbc6177a6 | ||
|
|
b91e06a60a | ||
|
|
ad3f4ff711 | ||
|
|
7edcfd6eab | ||
|
|
735b2063ea | ||
|
|
bce4d02dd2 | ||
|
|
e84126ec13 | ||
|
|
3a324acb0e | ||
|
|
c6f7427283 | ||
|
|
ace01eac9d | ||
|
|
8d304b78cb | ||
|
|
c17baa36ef | ||
|
|
8cbff097e4 | ||
|
|
294738cb0d | ||
|
|
69bc815acd | ||
|
|
fb62edefbc | ||
|
|
5e35ff8b8a | ||
|
|
20053b1f07 | ||
|
|
cc6c5d45b7 | ||
|
|
f480e0ccf6 | ||
|
|
d85149e328 | ||
|
|
cee241e77c | ||
|
|
8ec9515225 | ||
|
|
d4ffaaef2f | ||
|
|
eda8347091 | ||
|
|
4c23513a41 | ||
|
|
81d1f35bdc | ||
|
|
36c93c7f57 | ||
|
|
b67f404d8d | ||
|
|
95fb5a4baa | ||
|
|
dd372637cb | ||
|
|
c1a4856e9d | ||
|
|
92b7e64689 | ||
|
|
a750259a2c | ||
|
|
87accfce5d | ||
|
|
29f0daa7ea | ||
|
|
a247db7e93 | ||
|
|
1fbaf5fcbf | ||
|
|
c981e6ff7b | ||
|
|
ee1ee633d7 | ||
|
|
a7ab0a5662 | ||
|
|
bed4257194 | ||
|
|
5ee570e075 | ||
|
|
9666c21b8a | ||
|
|
5cf789a8e4 | ||
|
|
6a4a353b92 | ||
|
|
02355acfa8 | ||
|
|
04eb718f88 | ||
|
|
36888b5ad4 | ||
|
|
7bd971f838 | ||
|
|
c3ce4d8b53 | ||
|
|
fb3a31a4fd | ||
|
|
b6852b5e30 | ||
|
|
34e2178752 | ||
|
|
83a17de1c0 | ||
|
|
e5b27d7a57 | ||
|
|
fb14a85483 | ||
|
|
8d4cb5e16b | ||
|
|
ad8b8399c4 | ||
|
|
8ff2fa66b6 | ||
|
|
539948b5a6 | ||
|
|
bfe1cace77 | ||
|
|
7b806cf586 | ||
|
|
69a824c25b | ||
|
|
8d733ccc8c | ||
|
|
2574f223b4 | ||
|
|
f2d93654f5 | ||
|
|
5e74b90780 | ||
|
|
78ce176268 | ||
|
|
dfb398d091 | ||
|
|
4e9b3a8940 | ||
|
|
0141e55936 | ||
|
|
46fba176f0 | ||
|
|
441e265c32 | ||
|
|
d28030abea | ||
|
|
aa0f1221de | ||
|
|
305a949692 | ||
|
|
31d3fd730c | ||
|
|
a46002502f | ||
|
|
56fcc91e30 | ||
|
|
8a8058e4eb | ||
|
|
20a66fb10f | ||
|
|
628f822025 | ||
|
|
d8db8718bd | ||
|
|
5b40c79ea3 | ||
|
|
ae9025c1fb | ||
|
|
0014e39b61 | ||
|
|
5d1ea8ceb2 | ||
|
|
079478f191 | ||
|
|
65c050dc87 | ||
|
|
21fbd37bfb | ||
|
|
b28f635fb2 | ||
|
|
0580d3833a | ||
|
|
bff9bb7800 | ||
|
|
fb3d333453 | ||
|
|
2c25e1d48e | ||
|
|
5469392ec7 | ||
|
|
e1c7079c81 | ||
|
|
75c1b485ab | ||
|
|
03590d46e6 | ||
|
|
9dc6aa81cb | ||
|
|
d0b88d7e2f | ||
|
|
5343b965aa | ||
|
|
104c82c54e | ||
|
|
c0569a0752 | ||
|
|
ad86b6b11f | ||
|
|
ff32e87b97 | ||
|
|
1e78234f04 | ||
|
|
d0a9c046b3 | ||
|
|
c54bb255ba | ||
|
|
8843b7b0e8 | ||
|
|
a95d734c34 | ||
|
|
8262487401 | ||
|
|
57e53d1a21 | ||
|
|
e28a1491d4 | ||
|
|
9342ba9792 | ||
|
|
2552eb5e25 | ||
|
|
ddaf9dc885 | ||
|
|
11c778cfeb | ||
|
|
11dffdee9a | ||
|
|
d4d80ed8f7 | ||
|
|
0ba10b44ec | ||
|
|
0f617f7f87 | ||
|
|
423dd5e394 | ||
|
|
44737029a9 | ||
|
|
ce22544c60 | ||
|
|
9106e74e61 | ||
|
|
6c57ddb563 | ||
|
|
a2e1570162 | ||
|
|
ea60740d48 | ||
|
|
762c664948 | ||
|
|
d574a71cb1 | ||
|
|
bb066cd58c | ||
|
|
e779939ae1 | ||
|
|
aa830a0e58 | ||
|
|
52ac54f15c | ||
|
|
cc0ab75aca | ||
|
|
7e3347da2b | ||
|
|
87e9d7f8d4 | ||
|
|
6d3a33635d | ||
|
|
090268d7b6 | ||
|
|
698a91596e | ||
|
|
bb447bb02a | ||
|
|
5ffcbe8677 | ||
|
|
ac6296b86d | ||
|
|
3239a61bda | ||
|
|
2a43285593 | ||
|
|
36071837cb | ||
|
|
1ef713d80b | ||
|
|
82b848af0c | ||
|
|
b059641c80 | ||
|
|
728e885b9d | ||
|
|
3acefba069 | ||
|
|
9205f67791 | ||
|
|
6d95643a68 | ||
|
|
149c414d08 | ||
|
|
f8b4663e0a | ||
|
|
7b774c702d | ||
|
|
8045a15a50 | ||
|
|
9a18dd8162 | ||
|
|
70a7eefa22 | ||
|
|
3356d1abe2 | ||
|
|
7ee8dac832 | ||
|
|
5b3f099f4e | ||
|
|
5f5cb36df1 | ||
|
|
3645ff7459 | ||
|
|
9a92b97b7e | ||
|
|
005c48b1ad | ||
|
|
4fb1880ddc | ||
|
|
54145ce949 | ||
|
|
b040aa1e78 | ||
|
|
985eef6987 | ||
|
|
a5c3116b0c | ||
|
|
df381b6a33 | ||
|
|
9223c0226a | ||
|
|
314fdc850e | ||
|
|
43bbeed141 | ||
|
|
e07253bcef | ||
|
|
23b9baa059 | ||
|
|
05357ecce5 | ||
|
|
1a8fe82821 | ||
|
|
95f4db4f48 | ||
|
|
43600083a7 | ||
|
|
e6477b0b97 | ||
|
|
6aa7fdb4f2 | ||
|
|
4997e9c7be | ||
|
|
f0456cbf5f | ||
|
|
a0d349e0b3 | ||
|
|
f5e774c89d | ||
|
|
552d3f8a3e | ||
|
|
39f9173956 | ||
|
|
e4fc41fc94 | ||
|
|
ce7d234cba | ||
|
|
35701f5899 | ||
|
|
3d4d2b50ae | ||
|
|
0da4e3ae63 | ||
|
|
ad7055ee01 | ||
|
|
8076455423 | ||
|
|
23eca3ce80 | ||
|
|
4cc672f902 | ||
|
|
82fb5f7ac1 | ||
|
|
de59ea030a | ||
|
|
d9be6d1724 | ||
|
|
958a8e97e9 | ||
|
|
5fd202d629 | ||
|
|
768f1aa663 | ||
|
|
69caa1179f | ||
|
|
9a2cdc4a93 | ||
|
|
14a8b1d897 | ||
|
|
712207e69f | ||
|
|
8d46692d66 | ||
|
|
3241738775 | ||
|
|
ce840997bf | ||
|
|
88c4a43a19 | ||
|
|
b4acbfc9e1 | ||
|
|
8bf1c91bc9 | ||
|
|
a66fd78dc1 | ||
|
|
b004b33935 | ||
|
|
d32793e84e | ||
|
|
fd4b515350 | ||
|
|
0cd2a4558b | ||
|
|
89359a21ce | ||
|
|
69baa279d4 | ||
|
|
33861a834b | ||
|
|
dd4d126934 | ||
|
|
7275d23e4b | ||
|
|
d7306fb22e | ||
|
|
ebc0a8c772 | ||
|
|
f26e1fa21b | ||
|
|
6b27ba9121 | ||
|
|
975dc9c1da | ||
|
|
6fe26a52dd | ||
|
|
cd66e32912 | ||
|
|
81f8b88541 | ||
|
|
882051cc30 | ||
|
|
ed8f9b5931 | ||
|
|
e5e57978af | ||
|
|
75fef397d3 | ||
|
|
624490716e | ||
|
|
8eff32ebc7 | ||
|
|
cd19eb036b | ||
|
|
95f706aabe | ||
|
|
1551b02fde | ||
|
|
557f4773cf | ||
|
|
b84e1c8550 | ||
|
|
46e1a01625 | ||
|
|
7238372d8d | ||
|
|
00126cd08a | ||
|
|
58c44ad1ea | ||
|
|
84611a90a1 | ||
|
|
f78a6568a6 | ||
|
|
825269c119 | ||
|
|
60cd7b5527 | ||
|
|
767fabe0ce | ||
|
|
f86ba7b176 | ||
|
|
912250732a | ||
|
|
ae731b5496 | ||
|
|
92eaa02156 | ||
|
|
18252ab854 | ||
|
|
212400c283 | ||
|
|
8ed41de815 | ||
|
|
97a880e6c1 | ||
|
|
f39775752d | ||
|
|
6d6c70a98b | ||
|
|
461fc91446 | ||
|
|
8059cae8e7 | ||
|
|
41107191c3 | ||
|
|
cb6a5fa41d | ||
|
|
66799a53f4 | ||
|
|
892fdbf60d | ||
|
|
b6309682ef | ||
|
|
be11dfc231 | ||
|
|
12527aa820 | ||
|
|
0d0f9499eb | ||
|
|
60eab3e263 | ||
|
|
eb547162e9 | ||
|
|
0864c371e8 | ||
|
|
b90b1701e9 | ||
|
|
eb4ff12744 | ||
|
|
0522032515 | ||
|
|
bca1c6b9cf | ||
|
|
4195d93a16 | ||
|
|
e8a8b71daa | ||
|
|
aea62723c0 | ||
|
|
9b58c2e466 | ||
|
|
c41f7f8270 | ||
|
|
ac096dda46 | ||
|
|
e686d64011 | ||
|
|
1ccdb64938 | ||
|
|
71c0e8e661 | ||
|
|
c162e180e0 | ||
|
|
e806f74652 | ||
|
|
d52417c14f | ||
|
|
75d854e6ad | ||
|
|
0b2217a916 | ||
|
|
ca30efeca7 | ||
|
|
dc98850489 | ||
|
|
01dc9066b7 | ||
|
|
3aacaa7caf | ||
|
|
b031a30f62 | ||
|
|
12cddbd896 | ||
|
|
3791b7a16f | ||
|
|
d754532ab1 | ||
|
|
9a48ceaec1 | ||
|
|
1132c9ce87 | ||
|
|
668d526604 | ||
|
|
0e257c200f | ||
|
|
df05914fac | ||
|
|
0ffb84aaa6 | ||
|
|
b01180bb29 | ||
|
|
16f8b737f1 | ||
|
|
d9d1d6bfaa | ||
|
|
45b300eaff | ||
|
|
ad7545f009 | ||
|
|
5df30b9eb0 | ||
|
|
2e0555dbca | ||
|
|
9650aa56c7 | ||
|
|
0beb0d95c1 | ||
|
|
3de585fe17 | ||
|
|
c732ca2d2f | ||
|
|
d4c2ad4a57 | ||
|
|
bf59ef50a3 | ||
|
|
840a3ce732 | ||
|
|
f7780cecb3 | ||
|
|
24c61034c1 | ||
|
|
95b3fff917 | ||
|
|
0f52188261 | ||
|
|
b1b0a76465 | ||
|
|
8a6024ce9b | ||
|
|
61a3bfe994 | ||
|
|
842044e759 | ||
|
|
b3e035d353 | ||
|
|
33f433ce45 | ||
|
|
abb79ccbeb | ||
|
|
c340b62f43 | ||
|
|
bbb096412d | ||
|
|
141a530e28 | ||
|
|
d08b498cb9 | ||
|
|
bebee78152 | ||
|
|
5b77edb76d | ||
|
|
bcec6a8915 | ||
|
|
3496d5f00b | ||
|
|
4ee5ae90e7 | ||
|
|
4180e41fa1 | ||
|
|
5289e4d66b | ||
|
|
ace162ec1c | ||
|
|
a9887d4a31 | ||
|
|
8ce3e7581b | ||
|
|
9de0704775 | ||
|
|
e20c34e12a | ||
|
|
e217ac7121 | ||
|
|
76d1b70644 | ||
|
|
360701e256 | ||
|
|
7efdae5eee | ||
|
|
da9ef7dfcf | ||
|
|
69c34cdf0c | ||
|
|
030b3d7c4d | ||
|
|
355674cf22 | ||
|
|
85a7b7e0fc | ||
|
|
328ce2f995 | ||
|
|
e4241207cb | ||
|
|
85ad4e334a | ||
|
|
9ebc963082 | ||
|
|
3178787bc1 | ||
|
|
b08e0b0235 | ||
|
|
aac2aca912 | ||
|
|
f707c90cd3 | ||
|
|
3eea3e88bc | ||
|
|
13faa75a2d | ||
|
|
287107e8da | ||
|
|
2535887984 | ||
|
|
f12c3968f1 | ||
|
|
6419e7740a | ||
|
|
298e3d263e | ||
|
|
9ffaf47741 | ||
|
|
dff74f0823 | ||
|
|
f9f937f844 | ||
|
|
77e48bfb74 | ||
|
|
f4ac6f8320 | ||
|
|
bf8b44834a | ||
|
|
3c98bf9a79 | ||
|
|
e1df46b92b | ||
|
|
7e28b3ca3f | ||
|
|
2059a9e064 | ||
|
|
167825ff3f | ||
|
|
f154e6e0f1 | ||
|
|
311129e746 | ||
|
|
f59459f936 | ||
|
|
ee90fffce1 | ||
|
|
4ddd6663f5 | ||
|
|
ec3d7026d4 | ||
|
|
fb7f24df9c | ||
|
|
8860d72f70 | ||
|
|
b846c8e6d2 | ||
|
|
379f9e2822 | ||
|
|
3579b11a8b | ||
|
|
4377aec72b | ||
|
|
c486130a9f | ||
|
|
cf7746082b | ||
|
|
1ab65a4b4f | ||
|
|
a66e863646 | ||
|
|
d962c300f9 | ||
|
|
9aeedf1bfa | ||
|
|
98d8cd99fb | ||
|
|
226ffdcd20 | ||
|
|
78150a738f | ||
|
|
ecf5e90783 | ||
|
|
f63b07bbb9 | ||
|
|
07294c19bb | ||
|
|
f8cbb54ba5 | ||
|
|
f8fd28bb61 | ||
|
|
78f7cd0d6c | ||
|
|
9a42d4c506 | ||
|
|
f2c48409e0 | ||
|
|
5188ead870 | ||
|
|
f1ea2b5c02 | ||
|
|
b7d18ef50f | ||
|
|
20405e9803 | ||
|
|
0f3c7b1424 | ||
|
|
c442d936d3 | ||
|
|
0cd164bada | ||
|
|
ee42e44246 | ||
|
|
6695d75468 | ||
|
|
eb6cdf1229 | ||
|
|
a3b1466b96 | ||
|
|
8b7dcf20bf | ||
|
|
14ed6ed2a3 | ||
|
|
9f4549212d | ||
|
|
37209918ad | ||
|
|
aefa34d6d2 | ||
|
|
eaffde39f6 | ||
|
|
d71d291895 | ||
|
|
a894e3182a | ||
|
|
ff7847aaa5 | ||
|
|
a89c3773dd | ||
|
|
5d75ca34ea | ||
|
|
d47a9d590e | ||
|
|
bd679ae806 | ||
|
|
5de7ecb5f0 | ||
|
|
b3cd9c69df | ||
|
|
73311b6f32 | ||
|
|
93ddcfecd9 | ||
|
|
2bffba7371 | ||
|
|
37ca62eb06 | ||
|
|
fa208c7f2a | ||
|
|
6fac3fa127 | ||
|
|
171392c5ca | ||
|
|
d48ff2921b | ||
|
|
3165d354b5 | ||
|
|
9c2dbac479 | ||
|
|
318844226c | ||
|
|
e96f63023e | ||
|
|
1765b99336 | ||
|
|
74a0d4c12e | ||
|
|
3372f78cbf | ||
|
|
fe082f762f | ||
|
|
a8d3cda3fa | ||
|
|
ad7f87122d | ||
|
|
6f6f78fbe5 | ||
|
|
1bb02eea59 | ||
|
|
cf459a2d28 | ||
|
|
7d91ab72e1 | ||
|
|
cb804e8813 | ||
|
|
0973808234 | ||
|
|
edd5193100 | ||
|
|
0ad66510a9 | ||
|
|
5a6cd2002d | ||
|
|
1fbf13e812 | ||
|
|
a9406764ee | ||
|
|
dfb0ba9efe | ||
|
|
df2269a2fe | ||
|
|
8b4a74f06e | ||
|
|
48f2e7316a | ||
|
|
b76bcf0ee7 | ||
|
|
24893573aa | ||
|
|
118809a9c0 | ||
|
|
61be10bb00 | ||
|
|
4bd3f61ce6 | ||
|
|
48c2f127f8 | ||
|
|
b588d901cf | ||
|
|
2c4c638f46 | ||
|
|
3ed92e5fee | ||
|
|
804fdd414e | ||
|
|
661f0aad49 | ||
|
|
58de8e175f | ||
|
|
1e21aeb7e8 | ||
|
|
a79aa221d3 | ||
|
|
50b2f789a3 | ||
|
|
bc70198102 | ||
|
|
1b1a50d6b5 | ||
|
|
34cc8ea96a | ||
|
|
59ec22f706 | ||
|
|
c47e840b37 | ||
|
|
edf048570b | ||
|
|
b71ca2afb0 | ||
|
|
9ff8f42a66 | ||
|
|
125d84cbd1 | ||
|
|
fa798665cd | ||
|
|
95fbf7500c | ||
|
|
584a46d9d4 | ||
|
|
085762a1f4 | ||
|
|
6c32edc5b5 |
@@ -1,3 +1,5 @@
|
||||
*
|
||||
!dist
|
||||
!build
|
||||
!metadata.json
|
||||
!docker-extension/build
|
||||
|
||||
1
.env.defaults
Normal file
1
.env.defaults
Normal file
@@ -0,0 +1 @@
|
||||
PORTAINER_EDITION=CE
|
||||
@@ -31,7 +31,12 @@ rules:
|
||||
[
|
||||
'error',
|
||||
{
|
||||
pathGroups: [{ pattern: '@/**', group: 'internal' }, { pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' }],
|
||||
pathGroups:
|
||||
[
|
||||
{ pattern: '@@/**', group: 'internal', position: 'after' },
|
||||
{ pattern: '@/**', group: 'internal' },
|
||||
{ pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' },
|
||||
],
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||
pathGroupsExcludedImportTypes: ['internal'],
|
||||
},
|
||||
@@ -41,6 +46,7 @@ settings:
|
||||
'import/resolver':
|
||||
alias:
|
||||
map:
|
||||
- ['@@', './app/react/components']
|
||||
- ['@', './app']
|
||||
extensions: ['.js', '.ts', '.tsx']
|
||||
|
||||
@@ -52,6 +58,7 @@ overrides:
|
||||
parser: '@typescript-eslint/parser'
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
- 'regex'
|
||||
extends:
|
||||
- airbnb
|
||||
- airbnb-typescript
|
||||
@@ -68,7 +75,14 @@ overrides:
|
||||
version: 'detect'
|
||||
rules:
|
||||
import/order:
|
||||
['error', { pathGroups: [{ pattern: '@/**', group: 'internal' }], groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 'newlines-between': 'always' }]
|
||||
[
|
||||
'error',
|
||||
{
|
||||
pathGroups: [{ pattern: '@@/**', group: 'internal', position: 'after' }, { pattern: '@/**', group: 'internal' }],
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||
'newlines-between': 'always',
|
||||
},
|
||||
]
|
||||
func-style: [error, 'declaration']
|
||||
import/prefer-default-export: off
|
||||
no-use-before-define: ['error', { functions: false }]
|
||||
@@ -90,6 +104,7 @@ overrides:
|
||||
'react/jsx-no-bind': off
|
||||
'no-await-in-loop': 'off'
|
||||
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }]
|
||||
'regex/invalid': ['error', [{ 'regex': 'data-feather="(.*)"', 'message': 'Please use `react-feather` package instead' }]]
|
||||
- files:
|
||||
- app/**/*.test.*
|
||||
extends:
|
||||
@@ -99,3 +114,7 @@ overrides:
|
||||
'jest/globals': true
|
||||
rules:
|
||||
'react/jsx-no-constructed-context-values': off
|
||||
- files:
|
||||
- app/**/*.stories.*
|
||||
rules:
|
||||
'no-alert': off
|
||||
|
||||
5
.git-blame-ignore-revs
Normal file
5
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,5 @@
|
||||
# prettier
|
||||
cf5056d9c03b62d91a25c3b9127caac838695f98
|
||||
|
||||
# prettier v2
|
||||
42e7db0ae7897d3cb72b0ea1ecf57ee2dd694169
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
Thanks for opening an issue on Portainer !
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.io/slack/ or gitter https://gitter.im/portainer/Lobby.
|
||||
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/ or gitter https://gitter.im/portainer/Lobby.
|
||||
|
||||
If you are reporting a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -12,7 +12,7 @@ Thanks for reporting a bug for Portainer !
|
||||
|
||||
You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
|
||||
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/
|
||||
|
||||
Before opening a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
@@ -47,7 +47,7 @@ You can see how [here](https://documentation.portainer.io/r/portainer-logs)
|
||||
- Platform (windows/linux):
|
||||
- Command used to start Portainer (`docker run -p 9443:9443 portainer/portainer`):
|
||||
- Browser:
|
||||
- Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commerical setup.
|
||||
- Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commercial setup.
|
||||
- Have you reviewed our technical documentation and knowledge base? Yes/No
|
||||
|
||||
**Additional context**
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/Custom.md
vendored
6
.github/ISSUE_TEMPLATE/Custom.md
vendored
@@ -4,11 +4,11 @@ about: Ask us a question about Portainer usage or deployment
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before you start, we need a little bit more information from you:
|
||||
|
||||
Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commerical setup.
|
||||
Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commercial setup.
|
||||
|
||||
Have you reviewed our technical documentation and knowledge base? Yes/No
|
||||
|
||||
@@ -16,7 +16,7 @@ Have you reviewed our technical documentation and knowledge base? Yes/No
|
||||
|
||||
You can find more information about Portainer support framework policy here: https://old.portainer.io/2019/04/portainer-support-policy/
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
|
||||
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/
|
||||
|
||||
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
|
||||
-->
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -4,14 +4,13 @@ about: Suggest a feature/enhancement that should be added in Portainer
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Thanks for opening a feature request for Portainer !
|
||||
|
||||
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
|
||||
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/
|
||||
|
||||
Before opening a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
closes #0 <!-- Github issue number (remove if unknown) -->
|
||||
closes [CE-0] <!-- Jira link number (remove if unknown). Please also add the same [CE-XXX] at the back of the PR title -->
|
||||
|
||||
### Changes:
|
||||
38
.github/workflows/lint.yml
vendored
Normal file
38
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- release/*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- release/*
|
||||
|
||||
jobs:
|
||||
run-linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
cache: 'yarn'
|
||||
- run: yarn --frozen-lockfile
|
||||
|
||||
- name: Run linters
|
||||
uses: wearerequired/lint-action@v1
|
||||
with:
|
||||
eslint: true
|
||||
eslint_extensions: ts,tsx,js,jsx
|
||||
prettier: true
|
||||
prettier_dir: app/
|
||||
gofmt: true
|
||||
gofmt_dir: api/
|
||||
- name: Typecheck
|
||||
uses: icrawl/action-tsc@v1
|
||||
230
.github/workflows/nightly-security-scan.yml
vendored
Normal file
230
.github/workflows/nightly-security-scan.yml
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
name: Nightly Code Security Scan
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
client-dependencies:
|
||||
name: Client dependency check
|
||||
runs-on: ubuntu-latest
|
||||
if: >- # only run for develop branch
|
||||
github.ref == 'refs/heads/develop'
|
||||
outputs:
|
||||
js: ${{ steps.set-matrix.outputs.js_result }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
uses: snyk/actions/node@master
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
json: true
|
||||
|
||||
- name: Upload js security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: js-security-scan-develop-result
|
||||
path: snyk.json
|
||||
|
||||
- name: Export scan result to html file
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=table -export -export-filename="/data/js-result")
|
||||
|
||||
- name: Upload js result html file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-js-result-${{github.run_id}}
|
||||
path: js-result.html
|
||||
|
||||
- name: Analyse the js result
|
||||
id: set-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=matrix)
|
||||
echo "::set-output name=js_result::${result}"
|
||||
|
||||
server-dependencies:
|
||||
name: Server dependency check
|
||||
runs-on: ubuntu-latest
|
||||
if: >- # only run for develop branch
|
||||
github.ref == 'refs/heads/develop'
|
||||
outputs:
|
||||
go: ${{ steps.set-matrix.outputs.go_result }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Download go modules
|
||||
run: cd ./api && go get -t -v -d ./...
|
||||
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
uses: snyk/actions/golang@master
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
args: --file=./api/go.mod
|
||||
json: true
|
||||
|
||||
- name: Upload go security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: go-security-scan-develop-result
|
||||
path: snyk.json
|
||||
|
||||
- name: Export scan result to html file
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=table -export -export-filename="/data/go-result")
|
||||
|
||||
- name: Upload go result html file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-go-result-${{github.run_id}}
|
||||
path: go-result.html
|
||||
|
||||
- name: Analyse the go result
|
||||
id: set-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=matrix)
|
||||
echo "::set-output name=go_result::${result}"
|
||||
|
||||
image-vulnerability:
|
||||
name: Build docker image and Image vulnerability check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.ref == 'refs/heads/develop'
|
||||
outputs:
|
||||
image: ${{ steps.set-matrix.outputs.image_result }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Use golang 1.18
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- name: Install packages and build
|
||||
run: yarn install && yarn build
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: build/linux/Dockerfile
|
||||
tags: trivy-portainer:${{ github.sha }}
|
||||
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
|
||||
|
||||
- name: Load docker image
|
||||
run: |
|
||||
docker load --input /tmp/trivy-portainer-image.tar
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: docker://docker.io/aquasec/trivy:latest
|
||||
continue-on-error: true
|
||||
with:
|
||||
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
|
||||
|
||||
- name: Upload image security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image-security-scan-develop-result
|
||||
path: image-trivy.json
|
||||
|
||||
- name: Export scan result to html file
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=trivy -path="/data/image-trivy.json" -output-type=table -export -export-filename="/data/image-result")
|
||||
|
||||
- name: Upload go result html file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-image-result-${{github.run_id}}
|
||||
path: image-result.html
|
||||
|
||||
- name: Analyse the trivy result
|
||||
id: set-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=trivy -path="/data/image-trivy.json" -output-type=matrix)
|
||||
echo "::set-output name=image_result::${result}"
|
||||
|
||||
result-analysis:
|
||||
name: Analyse scan result
|
||||
needs: [client-dependencies, server-dependencies, image-vulnerability]
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.ref == 'refs/heads/develop'
|
||||
strategy:
|
||||
matrix:
|
||||
js: ${{fromJson(needs.client-dependencies.outputs.js)}}
|
||||
go: ${{fromJson(needs.server-dependencies.outputs.go)}}
|
||||
image: ${{fromJson(needs.image-vulnerability.outputs.image)}}
|
||||
steps:
|
||||
- name: Display the results of js, go and image
|
||||
run: |
|
||||
echo ${{ matrix.js.status }}
|
||||
echo ${{ matrix.go.status }}
|
||||
echo ${{ matrix.image.status }}
|
||||
echo ${{ matrix.js.summary }}
|
||||
echo ${{ matrix.go.summary }}
|
||||
echo ${{ matrix.image.summary }}
|
||||
|
||||
- name: Send Slack message
|
||||
if: >-
|
||||
matrix.js.status == 'failure' ||
|
||||
matrix.go.status == 'failure' ||
|
||||
matrix.image.status == 'failure'
|
||||
uses: slackapi/slack-github-action@v1.18.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "Code Scanning Result (*${{ github.repository }}*)\n*<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|GitHub Actions Workflow URL>*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"attachments": [
|
||||
{
|
||||
"color": "#FF0000",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*JS dependency check*: *${{ matrix.js.status }}*\n${{ matrix.js.summary }}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Go dependency check*: *${{ matrix.go.status }}*\n${{ matrix.go.summary }}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Image vulnerability check*: *${{ matrix.image.status }}*\n${{ matrix.image.summary }}\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SECURITY_SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
233
.github/workflows/pr-security.yml
vendored
Normal file
233
.github/workflows/pr-security.yml
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
name: PR Code Security Scan
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types:
|
||||
- submitted
|
||||
- edited
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'api/go.mod'
|
||||
- 'gruntfile.js'
|
||||
- 'build/linux/Dockerfile'
|
||||
- 'build/linux/alpine.Dockerfile'
|
||||
- 'build/windows/Dockerfile'
|
||||
|
||||
jobs:
|
||||
client-dependencies:
|
||||
name: Client dependency check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
github.event.review.body == '/scan'
|
||||
outputs:
|
||||
jsdiff: ${{ steps.set-diff-matrix.outputs.js_diff_result }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
uses: snyk/actions/node@master
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
json: true
|
||||
|
||||
- name: Upload js security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: js-security-scan-feat-result
|
||||
path: snyk.json
|
||||
|
||||
- name: Download artifacts from develop branch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mv ./snyk.json ./js-snyk-feature.json
|
||||
(gh run download -n js-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
|
||||
if [[ -e ./snyk.json ]]; then
|
||||
mv ./snyk.json ./js-snyk-develop.json
|
||||
else
|
||||
echo "null" > ./js-snyk-develop.json
|
||||
fi
|
||||
|
||||
- name: Export scan result to html file
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/js-snyk-feature.json" -compare-to="/data/js-snyk-develop.json" -output-type=table -export -export-filename="/data/js-result")
|
||||
|
||||
- name: Upload js result html file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-js-result-compare-to-develop-${{github.run_id}}
|
||||
path: js-result.html
|
||||
|
||||
- name: Analyse the js diff result
|
||||
id: set-diff-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/js-snyk-feature.json" -compare-to="./data/js-snyk-develop.json" -output-type=matrix)
|
||||
echo "::set-output name=js_diff_result::${result}"
|
||||
|
||||
server-dependencies:
|
||||
name: Server dependency check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
github.event.review.body == '/scan'
|
||||
outputs:
|
||||
godiff: ${{ steps.set-diff-matrix.outputs.go_diff_result }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Download go modules
|
||||
run: cd ./api && go get -t -v -d ./...
|
||||
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
uses: snyk/actions/golang@master
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
args: --file=./api/go.mod
|
||||
json: true
|
||||
|
||||
- name: Upload go security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: go-security-scan-feature-result
|
||||
path: snyk.json
|
||||
|
||||
- name: Download artifacts from develop branch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mv ./snyk.json ./go-snyk-feature.json
|
||||
(gh run download -n go-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
|
||||
if [[ -e ./snyk.json ]]; then
|
||||
mv ./snyk.json ./go-snyk-develop.json
|
||||
else
|
||||
echo "null" > ./go-snyk-develop.json
|
||||
fi
|
||||
|
||||
- name: Export scan result to html file
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/go-snyk-feature.json" -compare-to="/data/go-snyk-develop.json" -output-type=table -export -export-filename="/data/go-result")
|
||||
|
||||
- name: Upload go result html file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-go-result-compare-to-develop-${{github.run_id}}
|
||||
path: go-result.html
|
||||
|
||||
- name: Analyse the go diff result
|
||||
id: set-diff-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/go-snyk-feature.json" -compare-to="/data/go-snyk-develop.json" -output-type=matrix)
|
||||
echo "::set-output name=go_diff_result::${result}"
|
||||
|
||||
image-vulnerability:
|
||||
name: Build docker image and Image vulnerability check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
github.event.review.body == '/scan'
|
||||
outputs:
|
||||
imagediff: ${{ steps.set-diff-matrix.outputs.image_diff_result }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Use golang 1.18
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- name: Install packages and build
|
||||
run: yarn install && yarn build
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: build/linux/Dockerfile
|
||||
tags: trivy-portainer:${{ github.sha }}
|
||||
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
|
||||
|
||||
- name: Load docker image
|
||||
run: |
|
||||
docker load --input /tmp/trivy-portainer-image.tar
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: docker://docker.io/aquasec/trivy:latest
|
||||
continue-on-error: true
|
||||
with:
|
||||
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
|
||||
|
||||
- name: Upload image security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image-security-scan-feature-result
|
||||
path: image-trivy.json
|
||||
|
||||
- name: Download artifacts from develop branch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mv ./image-trivy.json ./image-trivy-feature.json
|
||||
(gh run download -n image-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
|
||||
if [[ -e ./image-trivy.json ]]; then
|
||||
mv ./image-trivy.json ./image-trivy-develop.json
|
||||
else
|
||||
echo "null" > ./image-trivy-develop.json
|
||||
fi
|
||||
|
||||
- name: Export scan result to html file
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=trivy -path="/data/image-trivy-feature.json" -compare-to="/data/image-trivy-develop.json" -output-type=table -export -export-filename="/data/image-result")
|
||||
|
||||
- name: Upload image result html file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-image-result-compare-to-develop-${{github.run_id}}
|
||||
path: image-result.html
|
||||
|
||||
- name: Analyse the image diff result
|
||||
id: set-diff-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=trivy -path="/data/image-trivy-feature.json" -compare-to="./data/image-trivy-develop.json" -output-type=matrix)
|
||||
echo "::set-output name=image_diff_result::${result}"
|
||||
|
||||
result-analysis:
|
||||
name: Analyse scan result compared to develop
|
||||
needs: [client-dependencies, server-dependencies, image-vulnerability]
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
github.event.review.body == '/scan'
|
||||
strategy:
|
||||
matrix:
|
||||
jsdiff: ${{fromJson(needs.client-dependencies.outputs.jsdiff)}}
|
||||
godiff: ${{fromJson(needs.server-dependencies.outputs.godiff)}}
|
||||
imagediff: ${{fromJson(needs.image-vulnerability.outputs.imagediff)}}
|
||||
steps:
|
||||
|
||||
- name: Check job status of diff result
|
||||
if: >-
|
||||
matrix.jsdiff.status == 'failure' ||
|
||||
matrix.godiff.status == 'failure' ||
|
||||
matrix.imagediff.status == 'failure'
|
||||
run: |
|
||||
echo ${{ matrix.jsdiff.status }}
|
||||
echo ${{ matrix.godiff.status }}
|
||||
echo ${{ matrix.imagediff.status }}
|
||||
echo ${{ matrix.jsdiff.summary }}
|
||||
echo ${{ matrix.godiff.summary }}
|
||||
echo ${{ matrix.imagediff.summary }}
|
||||
exit 1
|
||||
29
.github/workflows/test.yaml
vendored
Normal file
29
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Test
|
||||
on: push
|
||||
jobs:
|
||||
test-client:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
cache: 'yarn'
|
||||
- run: yarn --frozen-lockfile
|
||||
|
||||
- name: Run tests
|
||||
run: yarn test:client
|
||||
# test-server:
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# GOPRIVATE: "github.com/portainer"
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - uses: actions/setup-go@v3
|
||||
# with:
|
||||
# go-version: '1.18'
|
||||
# - name: Run tests
|
||||
# run: |
|
||||
# cd api
|
||||
# go test ./...
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ storybook-static
|
||||
.tmp
|
||||
**/.vscode/settings.json
|
||||
**/.vscode/tasks.json
|
||||
.vscode
|
||||
*.DS_Store
|
||||
|
||||
.eslintcache
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
dist
|
||||
dist
|
||||
api/datastore/test_data
|
||||
11
.prettierrc
11
.prettierrc
@@ -4,21 +4,16 @@
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"files": ["*.html"],
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.{j,t}sx",
|
||||
"*.ts"
|
||||
],
|
||||
"files": ["*.{j,t}sx", "*.ts"],
|
||||
"options": {
|
||||
"printWidth": 80
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ module.exports = {
|
||||
exportLocalsConvention: 'camelCaseOnly',
|
||||
},
|
||||
},
|
||||
postcssLoaderOptions: {
|
||||
implementation: require('postcss'),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -3,6 +3,7 @@ import '../app/assets/css';
|
||||
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
|
||||
import { initialize as initMSW, mswDecorator } from 'msw-storybook-addon';
|
||||
import { handlers } from '@/setup-tests/server-handlers';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
|
||||
// Initialize MSW
|
||||
initMSW({
|
||||
@@ -31,11 +32,17 @@ export const parameters = {
|
||||
},
|
||||
};
|
||||
|
||||
const testQueryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
|
||||
export const decorators = [
|
||||
(Story) => (
|
||||
<UIRouter plugins={[pushStateLocationPlugin]}>
|
||||
<Story />
|
||||
</UIRouter>
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<UIRouter plugins={[pushStateLocationPlugin]}>
|
||||
<Story />
|
||||
</UIRouter>
|
||||
</QueryClientProvider>
|
||||
),
|
||||
mswDecorator,
|
||||
];
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceRoot}/api/cmd/portainer/main.go",
|
||||
"program": "${workspaceRoot}/api/cmd/portainer",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {},
|
||||
"showLog": true,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintFlags": ["--fast", "-E", "exportloopref"]
|
||||
"go.lintFlags": ["--fast", "-E", "exportloopref"],
|
||||
"gopls": {
|
||||
"build.expandWorkspaceToModule": false
|
||||
},
|
||||
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"]
|
||||
}
|
||||
|
||||
14
README.md
14
README.md
@@ -12,21 +12,15 @@ Portainer consists of a single container that can run on any cluster. It can be
|
||||
- [Take5 – get 5 free nodes of Portainer Business for as long as you want them](https://portainer.io/pricing/take5)
|
||||
- [Portainer BE install guide](https://install.portainer.io)
|
||||
|
||||
## Demo
|
||||
|
||||
You can try out the public demo instance: http://demo.portainer.io/ (login with the username **admin** and the password **tryportainer**).
|
||||
|
||||
Please note that the public demo cluster is **reset every 15min**.
|
||||
|
||||
## Latest Version
|
||||
|
||||
Portainer CE is updated regularly. We aim to do an update release every couple of months.
|
||||
|
||||
**The latest version of Portainer is 2.9.x**. Portainer is on version 2, the second number denotes the month of release.
|
||||
[](https://github.com/portainer/portainer/releases/latest)
|
||||
|
||||
## Getting started
|
||||
|
||||
- [Deploy Portainer](https://docs.portainer.io/v/ce-2.9/start/install)
|
||||
- [Deploy Portainer](https://docs.portainer.io/start/install)
|
||||
- [Documentation](https://documentation.portainer.io)
|
||||
- [Contribute to the project](https://documentation.portainer.io/contributing/instructions/)
|
||||
|
||||
@@ -42,10 +36,10 @@ View [this](https://www.portainer.io/products) table to see all of the Portainer
|
||||
|
||||
Portainer CE is an open source project and is supported by the community. You can buy a supported version of Portainer at portainer.io
|
||||
|
||||
Learn more about Portainers community support channels [here.](https://www.portainer.io/community_help)
|
||||
Learn more about Portainer's community support channels [here.](https://www.portainer.io/community_help)
|
||||
|
||||
- Issues: https://github.com/portainer/portainer/issues
|
||||
- Slack (chat): [https://portainer.slack.com/](https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA)
|
||||
- Slack (chat): [https://portainer.io/slack](https://portainer.io/slack)
|
||||
|
||||
You can join the Portainer Community by visiting community.portainer.io. This will give you advance notice of events, content and other related Portainer content.
|
||||
|
||||
|
||||
@@ -2,57 +2,83 @@ package adminmonitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var logFatalf = log.Fatalf
|
||||
const RedirectReasonAdminInitTimeout string = "AdminInitTimeout"
|
||||
|
||||
type Monitor struct {
|
||||
timeout time.Duration
|
||||
datastore dataservices.DataStore
|
||||
shutdownCtx context.Context
|
||||
cancellationFunc context.CancelFunc
|
||||
timeout time.Duration
|
||||
datastore dataservices.DataStore
|
||||
shutdownCtx context.Context
|
||||
cancellationFunc context.CancelFunc
|
||||
mu sync.Mutex
|
||||
adminInitDisabled bool
|
||||
}
|
||||
|
||||
// New creates a monitor that when started will wait for the timeout duration and then shutdown the application unless it has been initialized.
|
||||
func New(timeout time.Duration, datastore dataservices.DataStore, shutdownCtx context.Context) *Monitor {
|
||||
return &Monitor{
|
||||
timeout: timeout,
|
||||
datastore: datastore,
|
||||
shutdownCtx: shutdownCtx,
|
||||
timeout: timeout,
|
||||
datastore: datastore,
|
||||
shutdownCtx: shutdownCtx,
|
||||
adminInitDisabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Starts starts the monitor. Active monitor could be stopped or shuttted down by cancelling the shutdown context.
|
||||
func (m *Monitor) Start() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.cancellationFunc != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cancellationCtx, cancellationFunc := context.WithCancel(context.Background())
|
||||
m.cancellationFunc = cancellationFunc
|
||||
|
||||
go func() {
|
||||
log.Println("[DEBUG] [internal,init] [message: start initialization monitor ]")
|
||||
log.Debug().Msg("start initialization monitor")
|
||||
|
||||
select {
|
||||
case <-time.After(m.timeout):
|
||||
initialized, err := m.WasInitialized()
|
||||
if err != nil {
|
||||
logFatalf("%s", err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
if !initialized {
|
||||
logFatalf("[FATAL] [internal,init] No administrator account was created in %f mins. Shutting down the Portainer instance for security reasons", m.timeout.Minutes())
|
||||
log.Info().Msg("the Portainer instance timed out for security purposes, to re-enable your Portainer instance, you will need to restart Portainer")
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.adminInitDisabled = true
|
||||
return
|
||||
}
|
||||
case <-cancellationCtx.Done():
|
||||
log.Println("[DEBUG] [internal,init] [message: canceling initialization monitor]")
|
||||
log.Debug().Msg("canceling initialization monitor")
|
||||
case <-m.shutdownCtx.Done():
|
||||
log.Println("[DEBUG] [internal,init] [message: shutting down initialization monitor]")
|
||||
log.Debug().Msg("shutting down initialization monitor")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops monitor. Safe to call even if monitor wasn't started.
|
||||
func (m *Monitor) Stop() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.cancellationFunc == nil {
|
||||
return
|
||||
}
|
||||
@@ -68,3 +94,23 @@ func (m *Monitor) WasInitialized() (bool, error) {
|
||||
}
|
||||
return len(users) > 0, nil
|
||||
}
|
||||
|
||||
func (m *Monitor) WasInstanceDisabled() bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.adminInitDisabled
|
||||
}
|
||||
|
||||
// WithRedirect checks whether administrator initialisation timeout. If so, it will return the error with redirect reason.
|
||||
// Otherwise, it will pass through the request to next
|
||||
func (m *Monitor) WithRedirect(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if m.WasInstanceDisabled() && strings.HasPrefix(r.RequestURI, "/api") && r.RequestURI != "/api/status" && r.RequestURI != "/api/settings/public" {
|
||||
w.Header().Set("redirect-reason", RedirectReasonAdminInitTimeout)
|
||||
httperror.WriteError(w, http.StatusSeeOther, "Administrator initialization timeout", nil)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,6 +21,18 @@ func Test_stopCouldBeCalledMultipleTimes(t *testing.T) {
|
||||
monitor.Stop()
|
||||
}
|
||||
|
||||
func Test_startOrStopCouldBeCalledMultipleTimesConcurrently(t *testing.T) {
|
||||
monitor := New(1*time.Minute, nil, context.Background())
|
||||
|
||||
go monitor.Start()
|
||||
monitor.Start()
|
||||
|
||||
go monitor.Stop()
|
||||
monitor.Stop()
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
func Test_canStopStartedMonitor(t *testing.T) {
|
||||
monitor := New(1*time.Minute, nil, context.Background())
|
||||
monitor.Start()
|
||||
@@ -30,21 +42,13 @@ func Test_canStopStartedMonitor(t *testing.T) {
|
||||
assert.Nil(t, monitor.cancellationFunc, "cancellation function should absent in stopped monitor")
|
||||
}
|
||||
|
||||
func Test_start_shouldFatalAfterTimeout_ifNotInitialized(t *testing.T) {
|
||||
func Test_start_shouldDisableInstanceAfterTimeout_ifNotInitialized(t *testing.T) {
|
||||
timeout := 10 * time.Millisecond
|
||||
|
||||
datastore := i.NewDatastore(i.WithUsers([]portainer.User{}))
|
||||
|
||||
var fataled bool
|
||||
origLogFatalf := logFatalf
|
||||
logFatalf = func(s string, v ...interface{}) { fataled = true }
|
||||
defer func() {
|
||||
logFatalf = origLogFatalf
|
||||
}()
|
||||
|
||||
monitor := New(timeout, datastore, context.Background())
|
||||
monitor.Start()
|
||||
<-time.After(2 * timeout)
|
||||
|
||||
assert.True(t, fataled, "monitor should been timeout and fatal")
|
||||
<-time.After(20 * timeout)
|
||||
assert.True(t, monitor.WasInstanceDisabled(), "monitor should have been timeout and instance is disabled")
|
||||
}
|
||||
|
||||
71
api/agent/version.go
Normal file
71
api/agent/version.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/url"
|
||||
)
|
||||
|
||||
// GetAgentVersionAndPlatform returns the agent version and platform
|
||||
//
|
||||
// it sends a ping to the agent and parses the version and platform from the headers
|
||||
func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (portainer.AgentPlatform, string, error) {
|
||||
httpCli := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
if tlsConfig != nil {
|
||||
httpCli.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
parsedURL, err := url.ParseURL(endpointUrl + "/ping")
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
parsedURL.Scheme = "https"
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, parsedURL.String(), nil)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
resp, err := httpCli.Do(req)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return 0, "", fmt.Errorf("Failed request with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
version := resp.Header.Get(portainer.PortainerAgentHeader)
|
||||
if version == "" {
|
||||
return 0, "", errors.New("Version Header is missing")
|
||||
}
|
||||
|
||||
agentPlatformHeader := resp.Header.Get(portainer.HTTPResponseAgentPlatform)
|
||||
if agentPlatformHeader == "" {
|
||||
return 0, "", errors.New("Agent Platform Header is missing")
|
||||
}
|
||||
|
||||
agentPlatformNumber, err := strconv.Atoi(agentPlatformHeader)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
if agentPlatformNumber == 0 {
|
||||
return 0, "", errors.New("Agent platform is invalid")
|
||||
}
|
||||
|
||||
return portainer.AgentPlatform(agentPlatformNumber), version, nil
|
||||
}
|
||||
@@ -50,4 +50,15 @@ Instead, it acts as a reverse-proxy to the Docker HTTP API. This means that you
|
||||
|
||||
To do so, you can use the `/endpoints/{id}/docker` Portainer API environment(endpoint) (which is not documented below due to Swagger limitations). This environment(endpoint) has a restricted access policy so you still need to be authenticated to be able to query this environment(endpoint). Any query on this environment(endpoint) will be proxied to the Docker API of the associated environment(endpoint) (requests and responses objects are the same as documented in the Docker API).
|
||||
|
||||
# Private Registry
|
||||
|
||||
Using private registry, you will need to pass a based64 encoded JSON string ‘{"registryId":\<registryID value\>}’ inside the Request Header. The parameter name is "X-Registry-Auth".
|
||||
\<registryID value\> - The registry ID where the repository was created.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
eyJyZWdpc3RyeUlkIjoxfQ==
|
||||
```
|
||||
|
||||
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://documentation.portainer.io/api/api-examples/).
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const portainerAPIKeyPrefix = "ptr_"
|
||||
|
||||
@@ -2,7 +2,7 @@ package apikey
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"log"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
|
||||
@@ -20,7 +22,7 @@ func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
|
||||
func Test_GenerateApiKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -74,7 +76,7 @@ func Test_GenerateApiKey(t *testing.T) {
|
||||
func Test_GetAPIKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -94,7 +96,7 @@ func Test_GetAPIKey(t *testing.T) {
|
||||
func Test_GetAPIKeys(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -115,7 +117,7 @@ func Test_GetAPIKeys(t *testing.T) {
|
||||
func Test_GetDigestUserAndKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -151,7 +153,7 @@ func Test_GetDigestUserAndKey(t *testing.T) {
|
||||
func Test_UpdateAPIKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -169,11 +171,9 @@ func Test_UpdateAPIKey(t *testing.T) {
|
||||
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
|
||||
is.NoError(err)
|
||||
|
||||
log.Println(apiKey)
|
||||
log.Println(apiKeyGot)
|
||||
log.Debug().Str("wanted", fmt.Sprintf("%+v", apiKey)).Str("got", fmt.Sprintf("%+v", apiKeyGot)).Msg("")
|
||||
|
||||
is.Equal(apiKey.LastUsed, apiKeyGot.LastUsed)
|
||||
|
||||
})
|
||||
|
||||
t.Run("Successfully updates api-key in cache upon api-key update", func(t *testing.T) {
|
||||
@@ -199,7 +199,7 @@ func Test_UpdateAPIKey(t *testing.T) {
|
||||
func Test_DeleteAPIKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -240,7 +240,7 @@ func Test_DeleteAPIKey(t *testing.T) {
|
||||
func Test_InvalidateUserKeyCache(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
|
||||
@@ -34,3 +34,45 @@ func TarFileInBuffer(fileContent []byte, fileName string, mode int64) ([]byte, e
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// tarFileInBuffer represents a tar archive buffer.
|
||||
type tarFileInBuffer struct {
|
||||
b *bytes.Buffer
|
||||
w *tar.Writer
|
||||
}
|
||||
|
||||
func NewTarFileInBuffer() *tarFileInBuffer {
|
||||
var b bytes.Buffer
|
||||
return &tarFileInBuffer{
|
||||
b: &b,
|
||||
w: tar.NewWriter(&b),
|
||||
}
|
||||
}
|
||||
|
||||
// Put puts a single file to tar archive buffer.
|
||||
func (t *tarFileInBuffer) Put(fileContent []byte, fileName string, mode int64) error {
|
||||
hdr := &tar.Header{
|
||||
Name: fileName,
|
||||
Mode: mode,
|
||||
Size: int64(len(fileContent)),
|
||||
}
|
||||
|
||||
if err := t.w.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := t.w.Write(fileContent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bytes returns the archive as a byte array.
|
||||
func (t *tarFileInBuffer) Bytes() []byte {
|
||||
return t.b.Bytes()
|
||||
}
|
||||
|
||||
func (t *tarFileInBuffer) Close() error {
|
||||
return t.w.Close()
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -27,9 +26,7 @@ func listFiles(dir string) []string {
|
||||
}
|
||||
|
||||
func Test_shouldCreateArhive(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||
@@ -40,9 +37,7 @@ func Test_shouldCreateArhive(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||
|
||||
extractionDir, _ := ioutils.TempDir("", "extract")
|
||||
defer os.RemoveAll(extractionDir)
|
||||
|
||||
extractionDir := t.TempDir()
|
||||
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
@@ -63,9 +58,7 @@ func Test_shouldCreateArhive(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||
@@ -76,9 +69,7 @@ func Test_shouldCreateArhiveXXXXX(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||
|
||||
extractionDir, _ := ioutils.TempDir("", "extract")
|
||||
defer os.RemoveAll(extractionDir)
|
||||
|
||||
extractionDir := t.TempDir()
|
||||
r, _ := os.Open(gzPath)
|
||||
ExtractTarGz(r, extractionDir)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,16 +2,12 @@ package archive
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnzipFile(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "unzip-test-")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
dir := t.TempDir()
|
||||
/*
|
||||
Archive structure.
|
||||
├── 0
|
||||
@@ -21,7 +17,7 @@ func TestUnzipFile(t *testing.T) {
|
||||
└── 0.txt
|
||||
*/
|
||||
|
||||
err = UnzipFile("./testdata/sample_archive.zip", dir)
|
||||
err := UnzipFile("./testdata/sample_archive.zip", dir)
|
||||
|
||||
assert.NoError(t, err)
|
||||
archiveDir := dir + "/sample_archive"
|
||||
|
||||
@@ -1,109 +1,110 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
// import (
|
||||
// "fmt"
|
||||
// "os"
|
||||
// "path"
|
||||
// "path/filepath"
|
||||
// "time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/archive"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/http/offlinegate"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
// "github.com/portainer/portainer/api/archive"
|
||||
// "github.com/portainer/portainer/api/crypto"
|
||||
// "github.com/portainer/portainer/api/dataservices"
|
||||
// "github.com/portainer/portainer/api/filesystem"
|
||||
// "github.com/portainer/portainer/api/http/offlinegate"
|
||||
|
||||
const rwxr__r__ os.FileMode = 0744
|
||||
// "github.com/pkg/errors"
|
||||
// "github.com/rs/zerolog/log"
|
||||
// )
|
||||
|
||||
var filesToBackup = []string{
|
||||
"certs",
|
||||
"compose",
|
||||
"config.json",
|
||||
"custom_templates",
|
||||
"edge_jobs",
|
||||
"edge_stacks",
|
||||
"extensions",
|
||||
"portainer.key",
|
||||
"portainer.pub",
|
||||
"tls",
|
||||
}
|
||||
// const rwxr__r__ os.FileMode = 0744
|
||||
|
||||
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
|
||||
func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
|
||||
unlock := gate.Lock()
|
||||
defer unlock()
|
||||
// var filesToBackup = []string{
|
||||
// "certs",
|
||||
// "compose",
|
||||
// "config.json",
|
||||
// "custom_templates",
|
||||
// "edge_jobs",
|
||||
// "edge_stacks",
|
||||
// "extensions",
|
||||
// "portainer.key",
|
||||
// "portainer.pub",
|
||||
// "tls",
|
||||
// }
|
||||
|
||||
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
|
||||
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to create backup dir")
|
||||
}
|
||||
// // Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
|
||||
// func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
|
||||
// unlock := gate.Lock()
|
||||
// defer unlock()
|
||||
|
||||
{
|
||||
// new export
|
||||
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
||||
// backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
|
||||
// if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
|
||||
// return "", errors.Wrap(err, "Failed to create backup dir")
|
||||
// }
|
||||
|
||||
err := datastore.Export(exportFilename)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Debugf("failed to export to %s", exportFilename)
|
||||
} else {
|
||||
logrus.Debugf("exported to %s", exportFilename)
|
||||
}
|
||||
}
|
||||
// {
|
||||
// // new export
|
||||
// exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
||||
|
||||
if err := backupDb(backupDirPath, datastore); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to backup database")
|
||||
}
|
||||
// err := datastore.Export(exportFilename)
|
||||
// if err != nil {
|
||||
// log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
|
||||
// } else {
|
||||
// log.Debug().Str("filename", exportFilename).Msg("file exported")
|
||||
// }
|
||||
// }
|
||||
|
||||
for _, filename := range filesToBackup {
|
||||
err := filesystem.CopyPath(filepath.Join(filestorePath, filename), backupDirPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to create backup file")
|
||||
}
|
||||
}
|
||||
// if err := backupDb(backupDirPath, datastore); err != nil {
|
||||
// return "", errors.Wrap(err, "Failed to backup database")
|
||||
// }
|
||||
|
||||
archivePath, err := archive.TarGzDir(backupDirPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to make an archive")
|
||||
}
|
||||
// for _, filename := range filesToBackup {
|
||||
// err := filesystem.CopyPath(filepath.Join(filestorePath, filename), backupDirPath)
|
||||
// if err != nil {
|
||||
// return "", errors.Wrap(err, "Failed to create backup file")
|
||||
// }
|
||||
// }
|
||||
|
||||
if password != "" {
|
||||
archivePath, err = encrypt(archivePath, password)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to encrypt backup with the password")
|
||||
}
|
||||
}
|
||||
// archivePath, err := archive.TarGzDir(backupDirPath)
|
||||
// if err != nil {
|
||||
// return "", errors.Wrap(err, "Failed to make an archive")
|
||||
// }
|
||||
|
||||
return archivePath, nil
|
||||
}
|
||||
// if password != "" {
|
||||
// archivePath, err = encrypt(archivePath, password)
|
||||
// if err != nil {
|
||||
// return "", errors.Wrap(err, "Failed to encrypt backup with the password")
|
||||
// }
|
||||
// }
|
||||
|
||||
func backupDb(backupDirPath string, datastore dataservices.DataStore) error {
|
||||
backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = datastore.BackupTo(backupWriter); err != nil {
|
||||
return err
|
||||
}
|
||||
return backupWriter.Close()
|
||||
}
|
||||
// return archivePath, nil
|
||||
// }
|
||||
|
||||
func encrypt(path string, passphrase string) (string, error) {
|
||||
in, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer in.Close()
|
||||
// func backupDb(backupDirPath string, datastore dataservices.DataStore) error {
|
||||
// backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err = datastore.BackupTo(backupWriter); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return backupWriter.Close()
|
||||
// }
|
||||
|
||||
outFileName := fmt.Sprintf("%s.encrypted", path)
|
||||
out, err := os.Create(outFileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// func encrypt(path string, passphrase string) (string, error) {
|
||||
// in, err := os.Open(path)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// defer in.Close()
|
||||
|
||||
err = crypto.AesEncrypt(in, out, []byte(passphrase))
|
||||
// outFileName := fmt.Sprintf("%s.encrypted", path)
|
||||
// out, err := os.Create(outFileName)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
return outFileName, err
|
||||
}
|
||||
// err = crypto.AesEncrypt(in, out, []byte(passphrase))
|
||||
|
||||
// return outFileName, err
|
||||
// }
|
||||
|
||||
@@ -1,69 +1,85 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
// import (
|
||||
// "context"
|
||||
// "io"
|
||||
// "os"
|
||||
// "path/filepath"
|
||||
// "time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/archive"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/http/offlinegate"
|
||||
)
|
||||
// "github.com/pkg/errors"
|
||||
// "github.com/portainer/portainer/api/archive"
|
||||
// "github.com/portainer/portainer/api/crypto"
|
||||
// "github.com/portainer/portainer/api/dataservices"
|
||||
// "github.com/portainer/portainer/api/filesystem"
|
||||
// "github.com/portainer/portainer/api/http/offlinegate"
|
||||
// )
|
||||
|
||||
var filesToRestore = append(filesToBackup, "portainer.db")
|
||||
// var filesToRestore = append(filesToBackup, "portainer.db")
|
||||
|
||||
// Restores system state from backup archive, will trigger system shutdown, when finished.
|
||||
func RestoreArchive(archive io.Reader, password string, filestorePath string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, shutdownTrigger context.CancelFunc) error {
|
||||
var err error
|
||||
if password != "" {
|
||||
archive, err = decrypt(archive, password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to decrypt the archive")
|
||||
}
|
||||
}
|
||||
// // Restores system state from backup archive, will trigger system shutdown, when finished.
|
||||
// func RestoreArchive(archive io.Reader, password string, filestorePath string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, shutdownTrigger context.CancelFunc) error {
|
||||
// var err error
|
||||
// if password != "" {
|
||||
// archive, err = decrypt(archive, password)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "failed to decrypt the archive")
|
||||
// }
|
||||
// }
|
||||
|
||||
restorePath := filepath.Join(filestorePath, "restore", time.Now().Format("20060102150405"))
|
||||
defer os.RemoveAll(filepath.Dir(restorePath))
|
||||
// restorePath := filepath.Join(filestorePath, "restore", time.Now().Format("20060102150405"))
|
||||
// defer os.RemoveAll(filepath.Dir(restorePath))
|
||||
|
||||
err = extractArchive(archive, restorePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot extract files from the archive. Please ensure the password is correct and try again")
|
||||
}
|
||||
// err = extractArchive(archive, restorePath)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "cannot extract files from the archive. Please ensure the password is correct and try again")
|
||||
// }
|
||||
|
||||
unlock := gate.Lock()
|
||||
defer unlock()
|
||||
// unlock := gate.Lock()
|
||||
// defer unlock()
|
||||
|
||||
if err = datastore.Close(); err != nil {
|
||||
return errors.Wrap(err, "Failed to stop db")
|
||||
}
|
||||
// if err = datastore.Close(); err != nil {
|
||||
// return errors.Wrap(err, "Failed to stop db")
|
||||
// }
|
||||
|
||||
if err = restoreFiles(restorePath, filestorePath); err != nil {
|
||||
return errors.Wrap(err, "failed to restore the system state")
|
||||
}
|
||||
// if err = restoreFiles(restorePath, filestorePath); err != nil {
|
||||
// return errors.Wrap(err, "failed to restore the system state")
|
||||
// }
|
||||
|
||||
shutdownTrigger()
|
||||
return nil
|
||||
}
|
||||
// shutdownTrigger()
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func decrypt(r io.Reader, password string) (io.Reader, error) {
|
||||
return crypto.AesDecrypt(r, []byte(password))
|
||||
}
|
||||
// func decrypt(r io.Reader, password string) (io.Reader, error) {
|
||||
// return crypto.AesDecrypt(r, []byte(password))
|
||||
// }
|
||||
|
||||
func extractArchive(r io.Reader, destinationDirPath string) error {
|
||||
return archive.ExtractTarGz(r, destinationDirPath)
|
||||
}
|
||||
// func extractArchive(r io.Reader, destinationDirPath string) error {
|
||||
// return archive.ExtractTarGz(r, destinationDirPath)
|
||||
// }
|
||||
|
||||
func restoreFiles(srcDir string, destinationDir string) error {
|
||||
for _, filename := range filesToRestore {
|
||||
err := filesystem.CopyPath(filepath.Join(srcDir, filename), destinationDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func restoreFiles(srcDir string, destinationDir string) error {
|
||||
// for _, filename := range filesToRestore {
|
||||
// err := filesystem.CopyPath(filepath.Join(srcDir, filename), destinationDir)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// // TODO: This is very boltdb module specific once again due to the filename. Move to bolt module? Refactor for another day
|
||||
|
||||
// // Prevent the possibility of having both databases. Remove any default new instance
|
||||
// os.Remove(filepath.Join(destinationDir, boltdb.DatabaseFileName))
|
||||
// os.Remove(filepath.Join(destinationDir, boltdb.EncryptedDatabaseFileName))
|
||||
|
||||
// // Now copy the database. It'll be either portainer.db or portainer.edb
|
||||
|
||||
// // Note: CopyPath does not return an error if the source file doesn't exist
|
||||
// err := filesystem.CopyPath(filepath.Join(srcDir, boltdb.EncryptedDatabaseFileName), destinationDir)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // return filesystem.CopyPath(filepath.Join(srcDir, boltdb.DatabaseFileName), destinationDir)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
9
api/build/variables.go
Normal file
9
api/build/variables.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package build
|
||||
|
||||
// Variables to be set during the build time
|
||||
var BuildNumber string
|
||||
var ImageTag string
|
||||
var NodejsVersion string
|
||||
var YarnVersion string
|
||||
var WebpackVersion string
|
||||
var GoVersion string
|
||||
@@ -1,14 +1,13 @@
|
||||
package chisel
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
|
||||
func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *portainer.EdgeJob) {
|
||||
tunnel := service.GetTunnelDetails(endpointID)
|
||||
service.mu.Lock()
|
||||
tunnel := service.getTunnelDetails(endpointID)
|
||||
|
||||
existingJobIndex := -1
|
||||
for idx, existingJob := range tunnel.Jobs {
|
||||
@@ -24,24 +23,25 @@ func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *por
|
||||
tunnel.Jobs[existingJobIndex] = *edgeJob
|
||||
}
|
||||
|
||||
key := strconv.Itoa(int(endpointID))
|
||||
service.tunnelDetailsMap.Set(key, tunnel)
|
||||
service.mu.Unlock()
|
||||
}
|
||||
|
||||
// RemoveEdgeJob will remove the specified Edge job from each tunnel it was registered with.
|
||||
func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
|
||||
for item := range service.tunnelDetailsMap.IterBuffered() {
|
||||
tunnelDetails := item.Val.(*portainer.TunnelDetails)
|
||||
service.mu.Lock()
|
||||
|
||||
updatedJobs := make([]portainer.EdgeJob, 0)
|
||||
for _, edgeJob := range tunnelDetails.Jobs {
|
||||
if edgeJob.ID == edgeJobID {
|
||||
continue
|
||||
for _, tunnel := range service.tunnelDetailsMap {
|
||||
// Filter in-place
|
||||
n := 0
|
||||
for _, edgeJob := range tunnel.Jobs {
|
||||
if edgeJob.ID != edgeJobID {
|
||||
tunnel.Jobs[n] = edgeJob
|
||||
n++
|
||||
}
|
||||
updatedJobs = append(updatedJobs, edgeJob)
|
||||
}
|
||||
|
||||
tunnelDetails.Jobs = updatedJobs
|
||||
service.tunnelDetailsMap.Set(item.Key, tunnelDetails)
|
||||
tunnel.Jobs = tunnel.Jobs[:n]
|
||||
}
|
||||
|
||||
service.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ package chisel
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
chserver "github.com/jpillora/chisel/server"
|
||||
cmap "github.com/orcaman/concurrent-map"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,18 +28,19 @@ const (
|
||||
type Service struct {
|
||||
serverFingerprint string
|
||||
serverPort string
|
||||
tunnelDetailsMap cmap.ConcurrentMap
|
||||
tunnelDetailsMap map[portainer.EndpointID]*portainer.TunnelDetails
|
||||
dataStore dataservices.DataStore
|
||||
snapshotService portainer.SnapshotService
|
||||
chiselServer *chserver.Server
|
||||
shutdownCtx context.Context
|
||||
ProxyManager *proxy.Manager
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewService returns a pointer to a new instance of Service
|
||||
func NewService(dataStore dataservices.DataStore, shutdownCtx context.Context) *Service {
|
||||
return &Service{
|
||||
tunnelDetailsMap: cmap.New(),
|
||||
tunnelDetailsMap: make(map[portainer.EndpointID]*portainer.TunnelDetails),
|
||||
dataStore: dataStore,
|
||||
shutdownCtx: shutdownCtx,
|
||||
}
|
||||
@@ -58,17 +59,17 @@ func (service *Service) pingAgent(endpointID portainer.EndpointID) error {
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
_, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// KeepTunnelAlive keeps the tunnel of the given environment for maxAlive duration, or until ctx is done
|
||||
func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx context.Context, maxAlive time.Duration) {
|
||||
go func() {
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: start for %.0f minutes]\n", endpointID, maxAlive.Minutes())
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Float64("max_alive_minutes", maxAlive.Minutes()).
|
||||
Msg("start")
|
||||
|
||||
maxAliveTicker := time.NewTicker(maxAlive)
|
||||
defer maxAliveTicker.Stop()
|
||||
pingTicker := time.NewTicker(tunnelCleanupInterval)
|
||||
@@ -80,14 +81,25 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
|
||||
service.SetTunnelStatusToActive(endpointID)
|
||||
err := service.pingAgent(endpointID)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [warning: ping agent err=%s]\n", endpointID, err)
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Err(err).
|
||||
Msg("ping agent")
|
||||
}
|
||||
case <-maxAliveTicker.C:
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as %.0f minutes timeout]\n", endpointID, maxAlive.Minutes())
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Float64("timeout_minutes", maxAlive.Minutes()).
|
||||
Msg("tunnel keep alive timeout")
|
||||
|
||||
return
|
||||
case <-ctx.Done():
|
||||
err := ctx.Err()
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as err=%s]\n", endpointID, err)
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Err(err).
|
||||
Msg("tunnel stop")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -166,7 +178,10 @@ func (service *Service) retrievePrivateKeySeed() (string, error) {
|
||||
}
|
||||
|
||||
func (service *Service) startTunnelVerificationLoop() {
|
||||
log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
|
||||
log.Debug().
|
||||
Float64("check_interval_seconds", tunnelCleanupInterval.Seconds()).
|
||||
Msg("starting tunnel management process")
|
||||
|
||||
ticker := time.NewTicker(tunnelCleanupInterval)
|
||||
|
||||
for {
|
||||
@@ -174,10 +189,12 @@ func (service *Service) startTunnelVerificationLoop() {
|
||||
case <-ticker.C:
|
||||
service.checkTunnels()
|
||||
case <-service.shutdownCtx.Done():
|
||||
log.Println("[DEBUG] Shutting down tunnel service")
|
||||
log.Debug().Msg("shutting down tunnel service")
|
||||
|
||||
if err := service.StopTunnelServer(); err != nil {
|
||||
log.Printf("Stopped tunnel service: %s", err)
|
||||
log.Debug().Err(err).Msg("stopped tunnel service")
|
||||
}
|
||||
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
@@ -185,42 +202,54 @@ func (service *Service) startTunnelVerificationLoop() {
|
||||
}
|
||||
|
||||
func (service *Service) checkTunnels() {
|
||||
for item := range service.tunnelDetailsMap.IterBuffered() {
|
||||
tunnel := item.Val.(*portainer.TunnelDetails)
|
||||
tunnels := make(map[portainer.EndpointID]portainer.TunnelDetails)
|
||||
|
||||
service.mu.Lock()
|
||||
for key, tunnel := range service.tunnelDetailsMap {
|
||||
tunnels[key] = *tunnel
|
||||
}
|
||||
service.mu.Unlock()
|
||||
|
||||
for endpointID, tunnel := range tunnels {
|
||||
if tunnel.LastActivity.IsZero() || tunnel.Status == portainer.EdgeAgentIdle {
|
||||
continue
|
||||
}
|
||||
|
||||
elapsed := time.Since(tunnel.LastActivity)
|
||||
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %s] [status: %s] [status_time_seconds: %f] [message: environment tunnel monitoring]", item.Key, tunnel.Status, elapsed.Seconds())
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Str("status", tunnel.Status).
|
||||
Float64("status_time_seconds", elapsed.Seconds()).
|
||||
Msg("environment tunnel monitoring")
|
||||
|
||||
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() < requiredTimeout.Seconds() {
|
||||
continue
|
||||
} else if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() > requiredTimeout.Seconds() {
|
||||
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %s] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: REQUIRED state timeout exceeded]", item.Key, tunnel.Status, elapsed.Seconds(), requiredTimeout.Seconds())
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Str("status", tunnel.Status).
|
||||
Float64("status_time_seconds", elapsed.Seconds()).
|
||||
Float64("timeout_seconds", requiredTimeout.Seconds()).
|
||||
Msg("REQUIRED state timeout exceeded")
|
||||
}
|
||||
|
||||
if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() < activeTimeout.Seconds() {
|
||||
continue
|
||||
} else if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() > activeTimeout.Seconds() {
|
||||
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %s] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: ACTIVE state timeout exceeded]", item.Key, tunnel.Status, elapsed.Seconds(), activeTimeout.Seconds())
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Str("status", tunnel.Status).
|
||||
Float64("status_time_seconds", elapsed.Seconds()).
|
||||
Float64("timeout_seconds", activeTimeout.Seconds()).
|
||||
Msg("ACTIVE state timeout exceeded")
|
||||
|
||||
endpointID, err := strconv.Atoi(item.Key)
|
||||
err := service.snapshotEnvironment(endpointID, tunnel.Port)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] [chisel,snapshot,conversion] Invalid environment identifier (id: %s): %s", item.Key, err)
|
||||
log.Error().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Err(err).
|
||||
Msg("unable to snapshot Edge environment")
|
||||
}
|
||||
|
||||
err = service.snapshotEnvironment(portainer.EndpointID(endpointID), tunnel.Port)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] [snapshot] Unable to snapshot Edge environment (id: %s): %s", item.Key, err)
|
||||
}
|
||||
}
|
||||
|
||||
endpointID, err := strconv.Atoi(item.Key)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] [chisel,conversion] Invalid environment identifier (id: %s): %s", item.Key, err)
|
||||
continue
|
||||
}
|
||||
|
||||
service.SetTunnelStatusToIdle(portainer.EndpointID(endpointID))
|
||||
|
||||
@@ -4,13 +4,11 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/libcrypto"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
"github.com/portainer/libcrypto"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
@@ -19,13 +17,13 @@ const (
|
||||
maxAvailablePort = 65535
|
||||
)
|
||||
|
||||
// NOTE: it needs to be called with the lock acquired
|
||||
// getUnusedPort is used to generate an unused random port in the dynamic port range.
|
||||
// Dynamic ports (also called private ports) are 49152 to 65535.
|
||||
func (service *Service) getUnusedPort() int {
|
||||
port := randomInt(minAvailablePort, maxAvailablePort)
|
||||
|
||||
for item := range service.tunnelDetailsMap.IterBuffered() {
|
||||
tunnel := item.Val.(*portainer.TunnelDetails)
|
||||
for _, tunnel := range service.tunnelDetailsMap {
|
||||
if tunnel.Port == port {
|
||||
return service.getUnusedPort()
|
||||
}
|
||||
@@ -38,26 +36,32 @@ func randomInt(min, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
// NOTE: it needs to be called with the lock acquired
|
||||
func (service *Service) getTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
|
||||
|
||||
if tunnel, ok := service.tunnelDetailsMap[endpointID]; ok {
|
||||
return tunnel
|
||||
}
|
||||
|
||||
tunnel := &portainer.TunnelDetails{
|
||||
Status: portainer.EdgeAgentIdle,
|
||||
}
|
||||
|
||||
service.tunnelDetailsMap[endpointID] = tunnel
|
||||
|
||||
return tunnel
|
||||
}
|
||||
|
||||
// GetTunnelDetails returns information about the tunnel associated to an environment(endpoint).
|
||||
func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
|
||||
key := strconv.Itoa(int(endpointID))
|
||||
func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) portainer.TunnelDetails {
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
if item, ok := service.tunnelDetailsMap.Get(key); ok {
|
||||
tunnelDetails := item.(*portainer.TunnelDetails)
|
||||
return tunnelDetails
|
||||
}
|
||||
|
||||
jobs := make([]portainer.EdgeJob, 0)
|
||||
return &portainer.TunnelDetails{
|
||||
Status: portainer.EdgeAgentIdle,
|
||||
Port: 0,
|
||||
Jobs: jobs,
|
||||
Credentials: "",
|
||||
}
|
||||
return *service.getTunnelDetails(endpointID)
|
||||
}
|
||||
|
||||
// GetActiveTunnel retrieves an active tunnel which allows communicating with edge agent
|
||||
func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (*portainer.TunnelDetails, error) {
|
||||
func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (portainer.TunnelDetails, error) {
|
||||
tunnel := service.GetTunnelDetails(endpoint.ID)
|
||||
|
||||
if tunnel.Status == portainer.EdgeAgentActive {
|
||||
@@ -68,13 +72,13 @@ func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (*portaine
|
||||
if tunnel.Status == portainer.EdgeAgentIdle || tunnel.Status == portainer.EdgeAgentManagementRequired {
|
||||
err := service.SetTunnelStatusToRequired(endpoint.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed opening tunnel to endpoint: %w", err)
|
||||
return portainer.TunnelDetails{}, fmt.Errorf("failed opening tunnel to endpoint: %w", err)
|
||||
}
|
||||
|
||||
if endpoint.EdgeCheckinInterval == 0 {
|
||||
settings, err := service.dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed fetching settings from db: %w", err)
|
||||
return portainer.TunnelDetails{}, fmt.Errorf("failed fetching settings from db: %w", err)
|
||||
}
|
||||
|
||||
endpoint.EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||
@@ -83,29 +87,27 @@ func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (*portaine
|
||||
time.Sleep(2 * time.Duration(endpoint.EdgeCheckinInterval) * time.Second)
|
||||
}
|
||||
|
||||
tunnel = service.GetTunnelDetails(endpoint.ID)
|
||||
|
||||
return tunnel, nil
|
||||
return service.GetTunnelDetails(endpoint.ID), nil
|
||||
}
|
||||
|
||||
// SetTunnelStatusToActive update the status of the tunnel associated to the specified environment(endpoint).
|
||||
// It sets the status to ACTIVE.
|
||||
func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID) {
|
||||
tunnel := service.GetTunnelDetails(endpointID)
|
||||
service.mu.Lock()
|
||||
tunnel := service.getTunnelDetails(endpointID)
|
||||
tunnel.Status = portainer.EdgeAgentActive
|
||||
tunnel.Credentials = ""
|
||||
tunnel.LastActivity = time.Now()
|
||||
|
||||
key := strconv.Itoa(int(endpointID))
|
||||
service.tunnelDetailsMap.Set(key, tunnel)
|
||||
service.mu.Unlock()
|
||||
}
|
||||
|
||||
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified environment(endpoint).
|
||||
// It sets the status to IDLE.
|
||||
// It removes any existing credentials associated to the tunnel.
|
||||
func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
|
||||
tunnel := service.GetTunnelDetails(endpointID)
|
||||
service.mu.Lock()
|
||||
|
||||
tunnel := service.getTunnelDetails(endpointID)
|
||||
tunnel.Status = portainer.EdgeAgentIdle
|
||||
tunnel.Port = 0
|
||||
tunnel.LastActivity = time.Now()
|
||||
@@ -116,10 +118,9 @@ func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
|
||||
service.chiselServer.DeleteUser(strings.Split(credentials, ":")[0])
|
||||
}
|
||||
|
||||
key := strconv.Itoa(int(endpointID))
|
||||
service.tunnelDetailsMap.Set(key, tunnel)
|
||||
|
||||
service.ProxyManager.DeleteEndpointProxy(endpointID)
|
||||
|
||||
service.mu.Unlock()
|
||||
}
|
||||
|
||||
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified environment(endpoint).
|
||||
@@ -128,7 +129,10 @@ func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
|
||||
// and generate temporary credentials that can be used to establish a reverse tunnel on that port.
|
||||
// Credentials are encrypted using the Edge ID associated to the environment(endpoint).
|
||||
func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
|
||||
tunnel := service.GetTunnelDetails(endpointID)
|
||||
tunnel := service.getTunnelDetails(endpointID)
|
||||
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
if tunnel.Port == 0 {
|
||||
endpoint, err := service.dataStore.Endpoint().Endpoint(endpointID)
|
||||
@@ -152,9 +156,6 @@ func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointI
|
||||
return err
|
||||
}
|
||||
tunnel.Credentials = credentials
|
||||
|
||||
key := strconv.Itoa(int(endpointID))
|
||||
service.tunnelDetailsMap.Set(key, tunnel)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,15 +2,14 @@ package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
@@ -35,6 +34,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
|
||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||
DemoEnvironment: kingpin.Flag("demo", "Demo environment").Bool(),
|
||||
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
|
||||
FeatureFlags: BoolPairs(kingpin.Flag("feat", "List of feature flags").Hidden()),
|
||||
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
|
||||
@@ -50,13 +50,18 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
|
||||
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
|
||||
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
|
||||
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").Default(defaultSnapshotInterval).String(),
|
||||
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
|
||||
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").String(),
|
||||
AdminPassword: kingpin.Flag("admin-password", "Set admin password with provided hash").String(),
|
||||
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
|
||||
Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')),
|
||||
Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(),
|
||||
Templates: kingpin.Flag("templates", "URL to the templates definitions.").Short('t').String(),
|
||||
BaseURL: kingpin.Flag("base-url", "Base URL parameter such as portainer if running portainer as http://yourdomain.com/portainer/.").Short('b').Default(defaultBaseURL).String(),
|
||||
InitialMmapSize: kingpin.Flag("initial-mmap-size", "Initial mmap size of the database in bytes").Int(),
|
||||
MaxBatchSize: kingpin.Flag("max-batch-size", "Maximum size of a batch").Int(),
|
||||
MaxBatchDelay: kingpin.Flag("max-batch-delay", "Maximum delay before a batch starts").Duration(),
|
||||
SecretKeyName: kingpin.Flag("secret-key-name", "Secret key name for encryption and will be used as /run/secrets/<secret-key-name>.").Default(defaultSecretKeyName).String(),
|
||||
LogLevel: kingpin.Flag("log-level", "Set the minimum logging level to show").Default("INFO").Enum("DEBUG", "INFO", "WARN", "ERROR"),
|
||||
}
|
||||
|
||||
kingpin.Parse()
|
||||
@@ -96,11 +101,11 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||
|
||||
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
|
||||
if *flags.NoAnalytics {
|
||||
log.Println("Warning: The --no-analytics flag has been kept to allow migration of instances running a previous version of Portainer with this flag enabled, to version 2.0 where enabling this flag will have no effect.")
|
||||
log.Warn().Msg("the --no-analytics flag has been kept to allow migration of instances running a previous version of Portainer with this flag enabled, to version 2.0 where enabling this flag will have no effect")
|
||||
}
|
||||
|
||||
if *flags.SSL {
|
||||
log.Println("Warning: SSL is enabled by default and there is no need for the --ssl flag. It has been kept to allow migration of instances running a previous version of Portainer with this flag enabled")
|
||||
log.Warn().Msg("SSL is enabled by default and there is no need for the --ssl flag, it has been kept to allow migration of instances running a previous version of Portainer with this flag enabled")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +130,7 @@ func validateEndpointURL(endpointURL string) error {
|
||||
}
|
||||
|
||||
func validateSnapshotInterval(snapshotInterval string) error {
|
||||
if snapshotInterval != defaultSnapshotInterval {
|
||||
if snapshotInterval != "" {
|
||||
_, err := time.ParseDuration(snapshotInterval)
|
||||
if err != nil {
|
||||
return errInvalidSnapshotInterval
|
||||
|
||||
@@ -18,8 +18,6 @@ const (
|
||||
defaultHTTPDisabled = "false"
|
||||
defaultHTTPEnabled = "false"
|
||||
defaultSSL = "false"
|
||||
defaultSSLCertPath = "/certs/portainer.crt"
|
||||
defaultSSLKeyPath = "/certs/portainer.key"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultBaseURL = "/"
|
||||
defaultSecretKeyName = "portainer"
|
||||
)
|
||||
|
||||
@@ -15,8 +15,7 @@ const (
|
||||
defaultHTTPDisabled = "false"
|
||||
defaultHTTPEnabled = "false"
|
||||
defaultSSL = "false"
|
||||
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
||||
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultBaseURL = "/"
|
||||
defaultSecretKeyName = "portainer"
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"strings"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func importFromJson(fileService portainer.FileService, store *datastore.Store) {
|
||||
// EXPERIMENTAL - if used with an incomplete json file, it will fail, as we don't have a way to default the model values
|
||||
importFile := "/data/import.json"
|
||||
if exists, _ := fileService.FileExists(importFile); exists {
|
||||
if err := store.Import(importFile); err != nil {
|
||||
logrus.WithError(err).Debugf("import %s failed", importFile)
|
||||
|
||||
// TODO: should really rollback on failure, but then we have nothing.
|
||||
} else {
|
||||
logrus.Printf("Successfully imported %s to new portainer database", importFile)
|
||||
}
|
||||
// TODO: this is bad - its to ensure that any defaults that were broken in import, or migrations get set back to what we want
|
||||
// I also suspect that everything from "Init to Init" is potentially a migration
|
||||
err := store.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing data store: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
stdlog "log"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/pkgerrors"
|
||||
)
|
||||
|
||||
func configureLogger() {
|
||||
logger := logrus.New() // logger is to implicitly substitute stdlib's log
|
||||
log.SetOutput(logger.Writer())
|
||||
zerolog.ErrorStackFieldName = "stack_trace"
|
||||
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
|
||||
formatter := &logrus.TextFormatter{DisableTimestamp: true, DisableLevelTruncation: true}
|
||||
logger.SetFormatter(formatter)
|
||||
logrus.SetFormatter(formatter)
|
||||
stdlog.SetFlags(0)
|
||||
stdlog.SetOutput(log.Logger)
|
||||
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
log.Logger = log.Logger.With().Caller().Stack().Logger()
|
||||
}
|
||||
|
||||
func setLoggingLevel(level string) {
|
||||
switch level {
|
||||
case "ERROR":
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
case "WARN":
|
||||
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||
case "INFO":
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
case "DEBUG":
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,25 +2,24 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/portainer/libhelm"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/apikey"
|
||||
"github.com/portainer/portainer/api/build"
|
||||
"github.com/portainer/portainer/api/chisel"
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/database"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/demo"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
"github.com/portainer/portainer/api/exec"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
@@ -40,82 +39,90 @@ import (
|
||||
"github.com/portainer/portainer/api/ldap"
|
||||
"github.com/portainer/portainer/api/oauth"
|
||||
"github.com/portainer/portainer/api/scheduler"
|
||||
"github.com/portainer/portainer/api/stacks"
|
||||
"github.com/portainer/portainer/api/stacks/deployments"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func initCLI() *portainer.CLIFlags {
|
||||
var cliService portainer.CLIService = &cli.Service{}
|
||||
flags, err := cliService.ParseFlags(portainer.APIVersion)
|
||||
if err != nil {
|
||||
log.Fatalf("failed parsing flags: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed parsing flags")
|
||||
}
|
||||
|
||||
err = cliService.ValidateFlags(flags)
|
||||
if err != nil {
|
||||
log.Fatalf("failed validating flags:%v", err)
|
||||
log.Fatal().Err(err).Msg("failed validating flags")
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func initFileService(dataStorePath string) portainer.FileService {
|
||||
fileService, err := filesystem.NewService(dataStorePath, "")
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating file service: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed creating file service")
|
||||
}
|
||||
|
||||
return fileService
|
||||
}
|
||||
|
||||
func initDataStore(flags *portainer.CLIFlags, fileService portainer.FileService, shutdownCtx context.Context) dataservices.DataStore {
|
||||
connection, err := database.NewDatabase("boltdb", *flags.Data)
|
||||
func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService portainer.FileService, shutdownCtx context.Context) dataservices.DataStore {
|
||||
connection, err := database.NewDatabase("sqlite", *flags.Data, secretKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatal().Err(err).Msg("failed creating database connection")
|
||||
}
|
||||
|
||||
store := datastore.NewStore(*flags.Data, fileService, connection)
|
||||
isNew, err := store.Open()
|
||||
if err != nil {
|
||||
log.Fatalf("failed opening store: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed opening store")
|
||||
}
|
||||
|
||||
if *flags.Rollback {
|
||||
err := store.Rollback(false)
|
||||
if err != nil {
|
||||
log.Fatalf("failed rolling back: %s", err)
|
||||
}
|
||||
// if *flags.Rollback {
|
||||
// err := store.Rollback(false)
|
||||
// if err != nil {
|
||||
// log.Fatal().Err(err).Msg("failed rolling back")
|
||||
// }
|
||||
|
||||
log.Println("Exiting rollback")
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
// log.Info().Msg("exiting rollback")
|
||||
// os.Exit(0)
|
||||
|
||||
// Init sets some defaults - its basically a migration
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// Init sets some defaults - it's basically a migration
|
||||
err = store.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing data store: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing data store")
|
||||
}
|
||||
|
||||
if isNew {
|
||||
// from MigrateData
|
||||
store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
|
||||
// Disabled for now. Can't use feature flags due to the way that works
|
||||
// EXPERIMENTAL, will only activate if `/data/import.json` exists
|
||||
//importFromJson(fileService, store)
|
||||
|
||||
err := updateSettingsFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatalf("failed updating settings from flags: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed updating settings from flags")
|
||||
}
|
||||
} else {
|
||||
// storedVersion, err := store.VersionService.DBVersion()
|
||||
// if err != nil {
|
||||
// log.Fatal().Err(err).Msg("failure during creation of new database")
|
||||
// }
|
||||
|
||||
// if storedVersion != portainer.DBVersion {
|
||||
// err = store.MigrateData()
|
||||
// if err != nil {
|
||||
// log.Fatal().Err(err).Msg("failed migration")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
storedVersion, err := store.VersionService.DBVersion()
|
||||
err = updateSettingsFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatalf("Something failed during creation of new database: %v", err)
|
||||
}
|
||||
if storedVersion != portainer.DBVersion {
|
||||
err = store.MigrateData()
|
||||
if err != nil {
|
||||
log.Fatalf("failed migration: %v", err)
|
||||
}
|
||||
log.Fatal().Err(err).Msg("failed updating settings from flags")
|
||||
}
|
||||
|
||||
// this is for the db restore functionality - needs more tests.
|
||||
@@ -123,23 +130,23 @@ func initDataStore(flags *portainer.CLIFlags, fileService portainer.FileService,
|
||||
<-shutdownCtx.Done()
|
||||
defer connection.Close()
|
||||
|
||||
exportFilename := path.Join(*flags.Data, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
||||
// exportFilename := path.Join(*flags.Data, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
||||
|
||||
err := store.Export(exportFilename)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Debugf("failed to export to %s", exportFilename)
|
||||
} else {
|
||||
logrus.Debugf("exported to %s", exportFilename)
|
||||
}
|
||||
connection.Close()
|
||||
// err := store.Export(exportFilename)
|
||||
// if err != nil {
|
||||
// log.Error().Str("filename", exportFilename).Err(err).Msg("failed to export")
|
||||
// } else {
|
||||
// log.Debug().Str("filename", exportFilename).Msg("exported")
|
||||
// }
|
||||
}()
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
func initComposeStackManager(assetsPath string, configPath string, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
|
||||
composeWrapper, err := exec.NewComposeStackManager(assetsPath, configPath, proxyManager)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating compose manager: %s", err)
|
||||
log.Fatal().Err(err).Msg("failed creating compose manager")
|
||||
}
|
||||
|
||||
return composeWrapper
|
||||
@@ -169,10 +176,15 @@ func initAPIKeyService(datastore dataservices.DataStore) apikey.APIKeyService {
|
||||
}
|
||||
|
||||
func initJWTService(userSessionTimeout string, dataStore dataservices.DataStore) (dataservices.JWTService, error) {
|
||||
if userSessionTimeout == "" {
|
||||
userSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||
}
|
||||
|
||||
jwtService, err := jwt.NewService(userSessionTimeout, dataStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jwtService, nil
|
||||
}
|
||||
|
||||
@@ -192,11 +204,11 @@ func initOAuthService() portainer.OAuthService {
|
||||
return oauth.NewService()
|
||||
}
|
||||
|
||||
func initGitService() portainer.GitService {
|
||||
return git.NewService()
|
||||
func initGitService(ctx context.Context) portainer.GitService {
|
||||
return git.NewService(ctx)
|
||||
}
|
||||
|
||||
func initSSLService(addr, dataPath, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
|
||||
func initSSLService(addr, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
|
||||
slices := strings.Split(addr, ":")
|
||||
host := slices[0]
|
||||
if host == "" {
|
||||
@@ -221,11 +233,17 @@ func initKubernetesClientFactory(signatureService portainer.DigitalSignatureServ
|
||||
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID, dataStore)
|
||||
}
|
||||
|
||||
func initSnapshotService(snapshotInterval string, dataStore dataservices.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory, shutdownCtx context.Context) (portainer.SnapshotService, error) {
|
||||
func initSnapshotService(
|
||||
snapshotIntervalFromFlag string,
|
||||
dataStore dataservices.DataStore,
|
||||
dockerClientFactory *docker.ClientFactory,
|
||||
kubernetesClientFactory *kubecli.ClientFactory,
|
||||
shutdownCtx context.Context,
|
||||
) (portainer.SnapshotService, error) {
|
||||
dockerSnapshotter := docker.NewSnapshotter(dockerClientFactory)
|
||||
kubernetesSnapshotter := kubernetes.NewSnapshotter(kubernetesClientFactory)
|
||||
|
||||
snapshotService, err := snapshot.NewService(snapshotInterval, dataStore, dockerSnapshotter, kubernetesSnapshotter, shutdownCtx)
|
||||
snapshotService, err := snapshot.NewService(snapshotIntervalFromFlag, dataStore, dockerSnapshotter, kubernetesSnapshotter, shutdownCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -246,11 +264,17 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
|
||||
return err
|
||||
}
|
||||
|
||||
settings.LogoURL = *flags.Logo
|
||||
settings.SnapshotInterval = *flags.SnapshotInterval
|
||||
settings.EnableEdgeComputeFeatures = *flags.EnableEdgeComputeFeatures
|
||||
settings.EnableTelemetry = true
|
||||
settings.OAuthSettings.SSO = true
|
||||
if *flags.SnapshotInterval != "" {
|
||||
settings.SnapshotInterval = *flags.SnapshotInterval
|
||||
}
|
||||
|
||||
if *flags.Logo != "" {
|
||||
settings.LogoURL = *flags.Logo
|
||||
}
|
||||
|
||||
if *flags.EnableEdgeComputeFeatures {
|
||||
settings.EnableEdgeComputeFeatures = *flags.EnableEdgeComputeFeatures
|
||||
}
|
||||
|
||||
if *flags.Templates != "" {
|
||||
settings.TemplatesURL = *flags.Templates
|
||||
@@ -260,6 +284,12 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
|
||||
settings.BlackListedLabels = *flags.Labels
|
||||
}
|
||||
|
||||
if agentKey, ok := os.LookupEnv("AGENT_SECRET"); ok {
|
||||
settings.AgentSecret = agentKey
|
||||
} else {
|
||||
settings.AgentSecret = ""
|
||||
}
|
||||
|
||||
err = dataStore.Settings().UpdateSettings(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -272,8 +302,8 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
|
||||
|
||||
if *flags.HTTPDisabled {
|
||||
sslSettings.HTTPEnabled = false
|
||||
} else {
|
||||
sslSettings.HTTPEnabled = *flags.HTTPEnabled || sslSettings.HTTPEnabled
|
||||
} else if *flags.HTTPEnabled {
|
||||
sslSettings.HTTPEnabled = true
|
||||
}
|
||||
|
||||
err = dataStore.SSLSettings().UpdateSettings(sslSettings)
|
||||
@@ -315,11 +345,7 @@ func enableFeaturesFromFlags(dataStore dataservices.DataStore, flags *portainer.
|
||||
return fmt.Errorf("feature flag's '%s' value should be true or false", feat.Name)
|
||||
}
|
||||
|
||||
if featureState {
|
||||
log.Printf("Feature %v : on", *correspondingFeature)
|
||||
} else {
|
||||
log.Printf("Feature %v : off", *correspondingFeature)
|
||||
}
|
||||
log.Info().Str("feature", string(*correspondingFeature)).Bool("state", featureState).Msg("")
|
||||
|
||||
settings.FeatureFlagSettings[*correspondingFeature] = featureState
|
||||
}
|
||||
@@ -347,7 +373,7 @@ func generateAndStoreKeyPair(fileService portainer.FileService, signatureService
|
||||
func initKeyPair(fileService portainer.FileService, signatureService portainer.DigitalSignatureService) error {
|
||||
existingKeyPair, err := fileService.KeyPairFilesExist()
|
||||
if err != nil {
|
||||
log.Fatalf("failed checking for existing key pair: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed checking for existing key pair")
|
||||
}
|
||||
|
||||
if existingKeyPair {
|
||||
@@ -380,7 +406,6 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.
|
||||
TLSConfig: tlsConfiguration,
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
TagIDs: []portainer.TagID{},
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.DockerSnapshot{},
|
||||
@@ -418,7 +443,11 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.
|
||||
|
||||
err := snapshotService.SnapshotEndpoint(endpoint)
|
||||
if err != nil {
|
||||
log.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
|
||||
log.Error().
|
||||
Str("endpoint", endpoint.Name).
|
||||
Str("URL", endpoint.URL).
|
||||
Err(err).
|
||||
Msg("environment snapshot error")
|
||||
}
|
||||
|
||||
return dataStore.Endpoint().Create(endpoint)
|
||||
@@ -442,7 +471,6 @@ func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStor
|
||||
TLSConfig: portainer.TLSConfiguration{},
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
TagIDs: []portainer.TagID{},
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.DockerSnapshot{},
|
||||
@@ -464,7 +492,10 @@ func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStor
|
||||
|
||||
err := snapshotService.SnapshotEndpoint(endpoint)
|
||||
if err != nil {
|
||||
log.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
|
||||
log.Error().
|
||||
Str("endpoint", endpoint.Name).
|
||||
Str("URL", endpoint.URL).Err(err).
|
||||
Msg("environment snapshot error")
|
||||
}
|
||||
|
||||
return dataStore.Endpoint().Create(endpoint)
|
||||
@@ -481,7 +512,8 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, s
|
||||
}
|
||||
|
||||
if len(endpoints) > 0 {
|
||||
log.Println("Instance already has defined environments. Skipping the environment defined via CLI.")
|
||||
log.Info().Msg("instance already has defined environments, skipping the environment defined via CLI")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -491,59 +523,85 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, s
|
||||
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
|
||||
}
|
||||
|
||||
func loadEncryptionSecretKey(keyfilename string) []byte {
|
||||
content, err := os.ReadFile(path.Join("/run/secrets", keyfilename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Info().Str("filename", keyfilename).Msg("encryption key file not present")
|
||||
} else {
|
||||
log.Info().Err(err).Msg("error reading encryption key file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// return a 32 byte hash of the secret (required for AES)
|
||||
hash := sha256.Sum256(content)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
shutdownCtx, shutdownTrigger := context.WithCancel(context.Background())
|
||||
|
||||
fileService := initFileService(*flags.Data)
|
||||
encryptionKey := loadEncryptionSecretKey(*flags.SecretKeyName)
|
||||
if encryptionKey == nil {
|
||||
log.Info().Msg("proceeding without encryption key")
|
||||
}
|
||||
|
||||
dataStore := initDataStore(flags, fileService, shutdownCtx)
|
||||
dataStore := initDataStore(flags, encryptionKey, fileService, shutdownCtx)
|
||||
|
||||
if err := dataStore.CheckCurrentEdition(); err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
instanceID, err := dataStore.Version().InstanceID()
|
||||
if err != nil {
|
||||
log.Fatalf("failed getting instance id: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed getting instance id")
|
||||
}
|
||||
|
||||
apiKeyService := initAPIKeyService(dataStore)
|
||||
|
||||
settings, err := dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
jwtService, err := initJWTService(settings.UserSessionTimeout, dataStore)
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing JWT service: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing JWT service")
|
||||
}
|
||||
|
||||
err = enableFeaturesFromFlags(dataStore, flags)
|
||||
if err != nil {
|
||||
log.Fatalf("failed enabling feature flag: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed enabling feature flag")
|
||||
}
|
||||
|
||||
ldapService := initLDAPService()
|
||||
oauthService := initOAuthService()
|
||||
gitService := initGitService()
|
||||
|
||||
openAMTService := openamt.NewService(dataStore)
|
||||
oauthService := initOAuthService()
|
||||
|
||||
gitService := initGitService(shutdownCtx)
|
||||
|
||||
openAMTService := openamt.NewService()
|
||||
|
||||
cryptoService := initCryptoService()
|
||||
|
||||
digitalSignatureService := initDigitalSignatureService()
|
||||
|
||||
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.Data, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
|
||||
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
sslSettings, err := sslService.GetSSLSettings()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get ssl settings: %s", err)
|
||||
log.Fatal().Err(err).Msg("failed to get SSL settings")
|
||||
}
|
||||
|
||||
err = initKeyPair(fileService, digitalSignatureService)
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing key pair: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing key pair")
|
||||
}
|
||||
|
||||
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
|
||||
@@ -553,7 +611,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing snapshot service: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing snapshot service")
|
||||
}
|
||||
snapshotService.Start()
|
||||
|
||||
@@ -562,9 +620,9 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
kubernetesTokenCacheManager := kubeproxy.NewTokenCacheManager()
|
||||
|
||||
kubeConfigService := kubernetes.NewKubeConfigCAService(*flags.AddrHTTPS, sslSettings.CertPath)
|
||||
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService(*flags.BaseURL, *flags.AddrHTTPS, sslSettings.CertPath)
|
||||
|
||||
proxyManager := proxy.NewManager(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager)
|
||||
proxyManager := proxy.NewManager(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService)
|
||||
|
||||
reverseTunnelService.ProxyManager = proxyManager
|
||||
|
||||
@@ -574,37 +632,46 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing swarm stack manager: %s", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing swarm stack manager")
|
||||
}
|
||||
|
||||
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
|
||||
|
||||
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing helm package manager: %s", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing helm package manager")
|
||||
}
|
||||
|
||||
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
|
||||
if err != nil {
|
||||
log.Fatalf("failed loading edge jobs from database: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed loading edge jobs from database")
|
||||
}
|
||||
|
||||
applicationStatus := initStatus(instanceID)
|
||||
|
||||
demoService := demo.NewService()
|
||||
if *flags.DemoEnvironment {
|
||||
err := demoService.Init(dataStore, cryptoService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing demo environment")
|
||||
}
|
||||
}
|
||||
|
||||
err = initEndpoint(flags, dataStore, snapshotService)
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing environment: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing environment")
|
||||
}
|
||||
|
||||
adminPasswordHash := ""
|
||||
if *flags.AdminPasswordFile != "" {
|
||||
content, err := fileService.GetFileContent(*flags.AdminPasswordFile, "")
|
||||
if err != nil {
|
||||
log.Fatalf("failed getting admin password file: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed getting admin password file")
|
||||
}
|
||||
|
||||
adminPasswordHash, err = cryptoService.Hash(strings.TrimSuffix(string(content), "\n"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed hashing admin password: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed hashing admin password")
|
||||
}
|
||||
} else if *flags.AdminPassword != "" {
|
||||
adminPasswordHash = *flags.AdminPassword
|
||||
@@ -613,38 +680,39 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
if adminPasswordHash != "" {
|
||||
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
|
||||
if err != nil {
|
||||
log.Fatalf("failed getting admin user: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed getting admin user")
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
log.Println("Created admin user with the given password.")
|
||||
log.Info().Msg("created admin user with the given password.")
|
||||
user := &portainer.User{
|
||||
Username: "admin",
|
||||
Role: portainer.AdministratorRole,
|
||||
Password: adminPasswordHash,
|
||||
}
|
||||
|
||||
err := dataStore.User().Create(user)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating admin user: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed creating admin user")
|
||||
}
|
||||
} else {
|
||||
log.Println("Instance already has an administrator user defined. Skipping admin password related flags.")
|
||||
log.Info().Msg("instance already has an administrator user defined, skipping admin password related flags.")
|
||||
}
|
||||
}
|
||||
|
||||
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
|
||||
if err != nil {
|
||||
log.Fatalf("failed starting tunnel server: %s", err)
|
||||
}
|
||||
|
||||
sslDBSettings, err := dataStore.SSLSettings().Settings()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to fetch ssl settings from DB")
|
||||
log.Fatal().Err(err).Msg("failed starting tunnel server")
|
||||
}
|
||||
|
||||
scheduler := scheduler.NewScheduler(shutdownCtx)
|
||||
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
|
||||
stacks.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
|
||||
stackDeployer := deployments.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
|
||||
deployments.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
|
||||
|
||||
sslDBSettings, err := dataStore.SSLSettings().Settings()
|
||||
if err != nil {
|
||||
log.Fatal().Msg("failed to fetch SSL settings from DB")
|
||||
}
|
||||
|
||||
return &http.Server{
|
||||
AuthorizationService: authorizationService,
|
||||
@@ -659,8 +727,8 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
ComposeStackManager: composeStackManager,
|
||||
KubernetesDeployer: kubernetesDeployer,
|
||||
HelmPackageManager: helmPackageManager,
|
||||
CryptoService: cryptoService,
|
||||
APIKeyService: apiKeyService,
|
||||
CryptoService: cryptoService,
|
||||
JWTService: jwtService,
|
||||
FileService: fileService,
|
||||
LDAPService: ldapService,
|
||||
@@ -669,7 +737,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
OpenAMTService: openAMTService,
|
||||
ProxyManager: proxyManager,
|
||||
KubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
||||
KubeConfigService: kubeConfigService,
|
||||
KubeClusterAccessService: kubeClusterAccessService,
|
||||
SignatureService: digitalSignatureService,
|
||||
SnapshotService: snapshotService,
|
||||
SSLService: sslService,
|
||||
@@ -679,19 +747,30 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
ShutdownCtx: shutdownCtx,
|
||||
ShutdownTrigger: shutdownTrigger,
|
||||
StackDeployer: stackDeployer,
|
||||
BaseURL: *flags.BaseURL,
|
||||
DemoService: demoService,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
configureLogger()
|
||||
|
||||
flags := initCLI()
|
||||
|
||||
configureLogger()
|
||||
setLoggingLevel(*flags.LogLevel)
|
||||
|
||||
for {
|
||||
server := buildServer(flags)
|
||||
log.Printf("[INFO] [cmd,main] Starting Portainer version %s\n", portainer.APIVersion)
|
||||
log.Info().
|
||||
Str("version", portainer.APIVersion).
|
||||
Str("build_number", build.BuildNumber).
|
||||
Str("image_tag", build.ImageTag).
|
||||
Str("nodejs_version", build.NodejsVersion).
|
||||
Str("yarn_version", build.YarnVersion).
|
||||
Str("webpack_version", build.WebpackVersion).
|
||||
Str("go_version", build.GoVersion).
|
||||
Msg("starting Portainer")
|
||||
|
||||
err := server.Start()
|
||||
log.Printf("[INFO] [cmd,main] Http server exited: %s\n", err)
|
||||
log.Info().Err(err).Msg("HTTP server exited")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
// import (
|
||||
// "fmt"
|
||||
// "testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
// portainer "github.com/portainer/portainer/api"
|
||||
// "github.com/portainer/portainer/api/cli"
|
||||
// "github.com/portainer/portainer/api/dataservices"
|
||||
// "github.com/portainer/portainer/api/datastore"
|
||||
|
||||
type mockKingpinSetting string
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// "gopkg.in/alecthomas/kingpin.v2"
|
||||
// )
|
||||
|
||||
func (m mockKingpinSetting) SetValue(value kingpin.Value) {
|
||||
value.Set(string(m))
|
||||
}
|
||||
// type mockKingpinSetting string
|
||||
|
||||
func Test_enableFeaturesFromFlags(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
// func (m mockKingpinSetting) SetValue(value kingpin.Value) {
|
||||
// value.Set(string(m))
|
||||
// }
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
defer teardown()
|
||||
// func Test_enableFeaturesFromFlags(t *testing.T) {
|
||||
// is := assert.New(t)
|
||||
|
||||
tests := []struct {
|
||||
featureFlag string
|
||||
isSupported bool
|
||||
}{
|
||||
{"test", false},
|
||||
{"openamt", false},
|
||||
{"open-amt", true},
|
||||
{"oPeN-amT", true},
|
||||
{"fdo", true},
|
||||
{"FDO", true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%s succeeds:%v", test.featureFlag, test.isSupported), func(t *testing.T) {
|
||||
mockKingpinSetting := mockKingpinSetting(test.featureFlag)
|
||||
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||
err := enableFeaturesFromFlags(store, flags)
|
||||
if test.isSupported {
|
||||
is.NoError(err)
|
||||
} else {
|
||||
is.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
// _, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
// defer teardown()
|
||||
|
||||
t.Run("passes for all supported feature flags", func(t *testing.T) {
|
||||
for _, flag := range portainer.SupportedFeatureFlags {
|
||||
mockKingpinSetting := mockKingpinSetting(flag)
|
||||
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||
err := enableFeaturesFromFlags(store, flags)
|
||||
is.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
// tests := []struct {
|
||||
// featureFlag string
|
||||
// isSupported bool
|
||||
// }{
|
||||
// {"test", false},
|
||||
// }
|
||||
// for _, test := range tests {
|
||||
// t.Run(fmt.Sprintf("%s succeeds:%v", test.featureFlag, test.isSupported), func(t *testing.T) {
|
||||
// mockKingpinSetting := mockKingpinSetting(test.featureFlag)
|
||||
// flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||
// err := enableFeaturesFromFlags(store, flags)
|
||||
// if test.isSupported {
|
||||
// is.NoError(err)
|
||||
// } else {
|
||||
// is.Error(err)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
const FeatTest portainer.Feature = "optional-test"
|
||||
// t.Run("passes for all supported feature flags", func(t *testing.T) {
|
||||
// for _, flag := range portainer.SupportedFeatureFlags {
|
||||
// mockKingpinSetting := mockKingpinSetting(flag)
|
||||
// flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||
// err := enableFeaturesFromFlags(store, flags)
|
||||
// is.NoError(err)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
func optionalFunc(dataStore dataservices.DataStore) string {
|
||||
// const FeatTest portainer.Feature = "optional-test"
|
||||
|
||||
// TODO: this is a code smell - finding out if a feature flag is enabled should not require having access to the store, and asking for a settings obj.
|
||||
// ideally, the `if` should look more like:
|
||||
// if featureflags.FlagEnabled(FeatTest) {}
|
||||
settings, err := dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
// func optionalFunc(dataStore dataservices.DataStore) string {
|
||||
|
||||
if settings.FeatureFlagSettings[FeatTest] {
|
||||
return "enabled"
|
||||
}
|
||||
return "disabled"
|
||||
}
|
||||
// // TODO: this is a code smell - finding out if a feature flag is enabled should not require having access to the store, and asking for a settings obj.
|
||||
// // ideally, the `if` should look more like:
|
||||
// // if featureflags.FlagEnabled(FeatTest) {}
|
||||
// settings, err := dataStore.Settings().Settings()
|
||||
// if err != nil {
|
||||
// return err.Error()
|
||||
// }
|
||||
|
||||
func Test_optionalFeature(t *testing.T) {
|
||||
portainer.SupportedFeatureFlags = append(portainer.SupportedFeatureFlags, FeatTest)
|
||||
// if settings.FeatureFlagSettings[FeatTest] {
|
||||
// return "enabled"
|
||||
// }
|
||||
// return "disabled"
|
||||
// }
|
||||
|
||||
is := assert.New(t)
|
||||
// func Test_optionalFeature(t *testing.T) {
|
||||
// portainer.SupportedFeatureFlags = append(portainer.SupportedFeatureFlags, FeatTest)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
defer teardown()
|
||||
// is := assert.New(t)
|
||||
|
||||
// Enable the test feature
|
||||
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||
mockKingpinSetting := mockKingpinSetting(FeatTest)
|
||||
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||
err := enableFeaturesFromFlags(store, flags)
|
||||
is.NoError(err)
|
||||
is.Equal("enabled", optionalFunc(store))
|
||||
})
|
||||
// _, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
// defer teardown()
|
||||
|
||||
// Same store, so the feature flag should still be enabled
|
||||
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||
is.Equal("enabled", optionalFunc(store))
|
||||
})
|
||||
// // Enable the test feature
|
||||
// t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||
// mockKingpinSetting := mockKingpinSetting(FeatTest)
|
||||
// flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||
// err := enableFeaturesFromFlags(store, flags)
|
||||
// is.NoError(err)
|
||||
// is.Equal("enabled", optionalFunc(store))
|
||||
// })
|
||||
|
||||
// disable the test feature
|
||||
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||
mockKingpinSetting := mockKingpinSetting(FeatTest + "=false")
|
||||
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||
err := enableFeaturesFromFlags(store, flags)
|
||||
is.NoError(err)
|
||||
is.Equal("disabled", optionalFunc(store))
|
||||
})
|
||||
// // Same store, so the feature flag should still be enabled
|
||||
// t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||
// is.Equal("enabled", optionalFunc(store))
|
||||
// })
|
||||
|
||||
// Same store, so feature flag should still be disabled
|
||||
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||
is.Equal("disabled", optionalFunc(store))
|
||||
})
|
||||
// // disable the test feature
|
||||
// t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||
// mockKingpinSetting := mockKingpinSetting(FeatTest + "=false")
|
||||
// flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||
// err := enableFeaturesFromFlags(store, flags)
|
||||
// is.NoError(err)
|
||||
// is.Equal("disabled", optionalFunc(store))
|
||||
// })
|
||||
|
||||
}
|
||||
// // Same store, so feature flag should still be disabled
|
||||
// t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||
// is.Equal("disabled", optionalFunc(store))
|
||||
// })
|
||||
|
||||
// }
|
||||
|
||||
@@ -1,34 +1,20 @@
|
||||
package portainer
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Connection interface {
|
||||
Open() error
|
||||
Close() error
|
||||
|
||||
// write the db contents to filename as json (the schema needs defining)
|
||||
ExportRaw(filename string) error
|
||||
Init() error
|
||||
|
||||
//Rollback(force bool) error
|
||||
//MigrateData(migratorParams *database.MigratorParameters, force bool) error
|
||||
GetDB() *gorm.DB
|
||||
|
||||
// TODO: this one is very database specific atm
|
||||
BackupTo(w io.Writer) error
|
||||
GetDatabaseFilename() string
|
||||
GetDatabaseFileName() string
|
||||
GetDatabaseFilePath() string
|
||||
GetStorePath() string
|
||||
|
||||
SetServiceName(bucketName string) error
|
||||
GetObject(bucketName string, key []byte, object interface{}) error
|
||||
UpdateObject(bucketName string, key []byte, object interface{}) error
|
||||
DeleteObject(bucketName string, key []byte) error
|
||||
DeleteAllObjects(bucketName string, matching func(o interface{}) (id int, ok bool)) error
|
||||
GetNextIdentifier(bucketName string) int
|
||||
CreateObject(bucketName string, fn func(uint64) (int, interface{})) error
|
||||
CreateObjectWithId(bucketName string, id int, obj interface{}) error
|
||||
CreateObjectWithSetSequence(bucketName string, id int, obj interface{}) error
|
||||
GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
ConvertToKey(v int) []byte
|
||||
SetEncrypted(encrypted bool)
|
||||
IsEncryptedStore() bool
|
||||
NeedsEncryptionMigration() (bool, error)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,11 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "encrypt")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
var (
|
||||
originFilePath = filepath.Join(tmpdir, "origin")
|
||||
@@ -52,8 +50,7 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "encrypt")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
var (
|
||||
originFilePath = filepath.Join(tmpdir, "origin")
|
||||
@@ -92,8 +89,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "encrypt")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
var (
|
||||
originFilePath = filepath.Join(tmpdir, "origin")
|
||||
|
||||
@@ -9,11 +9,11 @@ type Service struct{}
|
||||
|
||||
// Hash hashes a string using the bcrypt algorithm
|
||||
func (*Service) Hash(data string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(data), bcrypt.DefaultCost)
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(data), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
return "", err
|
||||
}
|
||||
return string(hash), nil
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
// CompareHashAndData compares a hash to clear data and returns an error if the comparison fails.
|
||||
|
||||
53
api/crypto/hash_test.go
Normal file
53
api/crypto/hash_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestService_Hash(t *testing.T) {
|
||||
var s = &Service{}
|
||||
|
||||
type args struct {
|
||||
hash string
|
||||
data string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
name: "Empty",
|
||||
args: args{
|
||||
hash: "",
|
||||
data: "",
|
||||
},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "Matching",
|
||||
args: args{
|
||||
hash: "$2a$10$6BFGd94oYx8k0bFNO6f33uPUpcpAJyg8UVX.akLe9EthF/ZBTXqcy",
|
||||
data: "Passw0rd!",
|
||||
},
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "Not matching",
|
||||
args: args{
|
||||
hash: "$2a$10$ltKrUZ7492xyutHOb0/XweevU4jyw7QO66rP32jTVOMb3EX3JxA/a",
|
||||
data: "Passw0rd!",
|
||||
},
|
||||
expect: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
err := s.CompareHashAndData(tt.args.hash, tt.args.data)
|
||||
if (err != nil) == tt.expect {
|
||||
t.Errorf("Service.CompareHashAndData() = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
package boltdb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
DatabaseFileName = "portainer.db"
|
||||
)
|
||||
|
||||
type DbConnection struct {
|
||||
Path string
|
||||
|
||||
*bolt.DB
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetDatabaseFilename() string {
|
||||
return DatabaseFileName
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetStorePath() string {
|
||||
return connection.Path
|
||||
}
|
||||
|
||||
// Open opens and initializes the BoltDB database.
|
||||
func (connection *DbConnection) Open() error {
|
||||
|
||||
// Disabled for now. Can't use feature flags due to the way that works
|
||||
// databaseExportPath := path.Join(connection.Path, fmt.Sprintf("raw-%s-%d.json", DatabaseFileName, time.Now().Unix()))
|
||||
// if err := connection.ExportRaw(databaseExportPath); err != nil {
|
||||
// log.Printf("raw export to %s error: %s", databaseExportPath, err)
|
||||
// } else {
|
||||
// log.Printf("raw export to %s success", databaseExportPath)
|
||||
// }
|
||||
|
||||
databasePath := path.Join(connection.Path, DatabaseFileName)
|
||||
|
||||
db, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
connection.DB = db
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the BoltDB database.
|
||||
// Safe to being called multiple times.
|
||||
func (connection *DbConnection) Close() error {
|
||||
if connection.DB != nil {
|
||||
return connection.DB.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BackupTo backs up db to a provided writer.
|
||||
// It does hot backup and doesn't block other database reads and writes
|
||||
func (connection *DbConnection) BackupTo(w io.Writer) error {
|
||||
return connection.View(func(tx *bolt.Tx) error {
|
||||
_, err := tx.WriteTo(w)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (connection *DbConnection) ExportRaw(filename string) error {
|
||||
databasePath := path.Join(connection.Path, DatabaseFileName)
|
||||
if _, err := os.Stat(databasePath); err != nil {
|
||||
return fmt.Errorf("stat on %s failed: %s", databasePath, err)
|
||||
}
|
||||
|
||||
b, err := exportJson(databasePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filename, b, 0600)
|
||||
}
|
||||
|
||||
// ConvertToKey returns an 8-byte big endian representation of v.
|
||||
// This function is typically used for encoding integer IDs to byte slices
|
||||
// so that they can be used as BoltDB keys.
|
||||
func (connection *DbConnection) ConvertToKey(v int) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(v))
|
||||
return b
|
||||
}
|
||||
|
||||
// CreateBucket is a generic function used to create a bucket inside a database database.
|
||||
func (connection *DbConnection) SetServiceName(bucketName string) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetObject is a generic function used to retrieve an unmarshalled object from a database database.
|
||||
func (connection *DbConnection) GetObject(bucketName string, key []byte, object interface{}) error {
|
||||
var data []byte
|
||||
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
value := bucket.Get(key)
|
||||
if value == nil {
|
||||
return errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return UnmarshalObject(data, object)
|
||||
}
|
||||
|
||||
// UpdateObject is a generic function used to update an object inside a database database.
|
||||
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object interface{}) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
data, err := MarshalObject(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = bucket.Put(key, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteObject is a generic function used to delete an object inside a database database.
|
||||
func (connection *DbConnection) DeleteObject(bucketName string, key []byte) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
return bucket.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAllObjects delete all objects where matching() returns (id, ok).
|
||||
// TODO: think about how to return the error inside (maybe change ok to type err, and use "notfound"?
|
||||
func (connection *DbConnection) DeleteAllObjects(bucketName string, matching func(o interface{}) (id int, ok bool)) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var obj interface{}
|
||||
err := UnmarshalObject(v, &obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if id, ok := matching(obj); ok {
|
||||
err := bucket.Delete(connection.ConvertToKey(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetNextIdentifier is a generic function that returns the specified bucket identifier incremented by 1.
|
||||
func (connection *DbConnection) GetNextIdentifier(bucketName string) int {
|
||||
var identifier int
|
||||
|
||||
connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
id, err := bucket.NextSequence()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
identifier = int(id)
|
||||
return nil
|
||||
})
|
||||
|
||||
return identifier
|
||||
}
|
||||
|
||||
// CreateObject creates a new object in the bucket, using the next bucket sequence id
|
||||
func (connection *DbConnection) CreateObject(bucketName string, fn func(uint64) (int, interface{})) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
seqId, _ := bucket.NextSequence()
|
||||
id, obj := fn(seqId)
|
||||
|
||||
data, err := MarshalObject(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(connection.ConvertToKey(int(id)), data)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateObjectWithId creates a new object in the bucket, using the specified id
|
||||
func (connection *DbConnection) CreateObjectWithId(bucketName string, id int, obj interface{}) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
data, err := MarshalObject(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(connection.ConvertToKey(id), data)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateObjectWithSetSequence creates a new object in the bucket, using the specified id, and sets the bucket sequence
|
||||
// avoid this :)
|
||||
func (connection *DbConnection) CreateObjectWithSetSequence(bucketName string, id int, obj interface{}) error {
|
||||
return connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
// We manually manage sequences for schedules
|
||||
err := bucket.SetSequence(uint64(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := MarshalObject(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(connection.ConvertToKey(id), data)
|
||||
})
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
err := UnmarshalObject(v, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj, err = append(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: decide which Unmarshal to use, and use one...
|
||||
func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
err := UnmarshalObjectWithJsoniter(v, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj, err = append(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package boltdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// inspired by github.com/konoui/boltdb-exporter (which has no license)
|
||||
// but very much simplified, based on how we use boltdb
|
||||
|
||||
func exportJson(databasePath string) ([]byte, error) {
|
||||
connection, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second, ReadOnly: true})
|
||||
if err != nil {
|
||||
return []byte("{}"), err
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
backup := make(map[string]interface{})
|
||||
|
||||
err = connection.View(func(tx *bolt.Tx) error {
|
||||
err = tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
bucketName := string(name)
|
||||
var list []interface{}
|
||||
version := make(map[string]string)
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
var obj interface{}
|
||||
err := UnmarshalObject(v, &obj)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("Failed to unmarshal (bucket %s): %v", bucketName, string(v))
|
||||
obj = v
|
||||
}
|
||||
if bucketName == "version" {
|
||||
version[string(k)] = string(v)
|
||||
} else {
|
||||
list = append(list, obj)
|
||||
}
|
||||
}
|
||||
if bucketName == "version" {
|
||||
backup[bucketName] = version
|
||||
}
|
||||
if len(list) > 0 {
|
||||
if bucketName == "ssl" ||
|
||||
bucketName == "settings" ||
|
||||
bucketName == "tunnel_server" {
|
||||
backup[bucketName] = list[0]
|
||||
return nil
|
||||
}
|
||||
backup[bucketName] = list
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return []byte("{}"), err
|
||||
}
|
||||
|
||||
return json.MarshalIndent(backup, "", " ")
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package boltdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// MarshalObject encodes an object to binary format
|
||||
func MarshalObject(object interface{}) ([]byte, error) {
|
||||
// Special case for the VERSION bucket. Here we're not using json
|
||||
if v, ok := object.(string); ok {
|
||||
return []byte(v), nil
|
||||
}
|
||||
|
||||
return json.Marshal(object)
|
||||
}
|
||||
|
||||
// UnmarshalObject decodes an object from binary data
|
||||
func UnmarshalObject(data []byte, object interface{}) error {
|
||||
// Special case for the VERSION bucket. Here we're not using json
|
||||
// So we need to return it as a string
|
||||
err := json.Unmarshal(data, object)
|
||||
if err != nil {
|
||||
if s, ok := object.(*string); ok {
|
||||
*s = string(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalObjectWithJsoniter decodes an object from binary data
|
||||
// using the jsoniter library. It is mainly used to accelerate environment(endpoint)
|
||||
// decoding at the moment.
|
||||
func UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
|
||||
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
return jsoni.Unmarshal(data, &object)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package boltdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const jsonobject = `{"LogoURL":"","BlackListedLabels":[],"AuthenticationMethod":1,"LDAPSettings":{"AnonymousMode":true,"ReaderDN":"","URL":"","TLSConfig":{"TLS":false,"TLSSkipVerify":false},"StartTLS":false,"SearchSettings":[{"BaseDN":"","Filter":"","UserNameAttribute":""}],"GroupSearchSettings":[{"GroupBaseDN":"","GroupFilter":"","GroupAttribute":""}],"AutoCreateUsers":true},"OAuthSettings":{"ClientID":"","AccessTokenURI":"","AuthorizationURI":"","ResourceURI":"","RedirectURI":"","UserIdentifier":"","Scopes":"","OAuthAutoCreateUsers":false,"DefaultTeamID":0,"SSO":true,"LogoutURI":"","KubeSecretKey":"j0zLVtY/lAWBk62ByyF0uP80SOXaitsABP0TTJX8MhI="},"OpenAMTConfiguration":{"Enabled":false,"MPSServer":"","Credentials":{"MPSUser":"","MPSPassword":"","MPSToken":""},"DomainConfiguration":{"CertFileText":"","CertPassword":"","DomainName":""},"WirelessConfiguration":null},"FeatureFlagSettings":{},"SnapshotInterval":"5m","TemplatesURL":"https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json","EdgeAgentCheckinInterval":5,"EnableEdgeComputeFeatures":false,"UserSessionTimeout":"8h","KubeconfigExpiry":"0","EnableTelemetry":true,"HelmRepositoryURL":"https://charts.bitnami.com/bitnami","KubectlShellImage":"portainer/kubectl-shell","DisplayDonationHeader":false,"DisplayExternalContributors":false,"EnableHostManagementFeatures":false,"AllowVolumeBrowserForRegularUsers":false,"AllowBindMountsForRegularUsers":false,"AllowPrivilegedModeForRegularUsers":false,"AllowHostNamespaceForRegularUsers":false,"AllowStackManagementForRegularUsers":false,"AllowDeviceMappingForRegularUsers":false,"AllowContainerCapabilitiesForRegularUsers":false}`
|
||||
|
||||
func Test_MarshalObject(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
uuid := uuid.Must(uuid.NewV4())
|
||||
|
||||
tests := []struct {
|
||||
object interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
object: nil,
|
||||
expected: `null`,
|
||||
},
|
||||
{
|
||||
object: true,
|
||||
expected: `true`,
|
||||
},
|
||||
{
|
||||
object: false,
|
||||
expected: `false`,
|
||||
},
|
||||
{
|
||||
object: 123,
|
||||
expected: `123`,
|
||||
},
|
||||
{
|
||||
object: "456",
|
||||
expected: "456",
|
||||
},
|
||||
{
|
||||
object: uuid,
|
||||
expected: "\"" + uuid.String() + "\"",
|
||||
},
|
||||
{
|
||||
object: uuid.String(),
|
||||
expected: uuid.String(),
|
||||
},
|
||||
{
|
||||
object: map[string]interface{}{"key": "value"},
|
||||
expected: `{"key":"value"}`,
|
||||
},
|
||||
{
|
||||
object: []bool{true, false},
|
||||
expected: `[true,false]`,
|
||||
},
|
||||
{
|
||||
object: []int{1, 2, 3},
|
||||
expected: `[1,2,3]`,
|
||||
},
|
||||
{
|
||||
object: []string{"1", "2", "3"},
|
||||
expected: `["1","2","3"]`,
|
||||
},
|
||||
{
|
||||
object: []map[string]interface{}{{"key1": "value1"}, {"key2": "value2"}},
|
||||
expected: `[{"key1":"value1"},{"key2":"value2"}]`,
|
||||
},
|
||||
{
|
||||
object: []interface{}{1, "2", false, map[string]interface{}{"key1": "value1"}},
|
||||
expected: `[1,"2",false,{"key1":"value1"}]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%s -> %s", test.object, test.expected), func(t *testing.T) {
|
||||
data, err := MarshalObject(test.object)
|
||||
is.NoError(err)
|
||||
is.Equal(test.expected, string(data))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UnMarshalObject(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
// Based on actual data entering and what we expect out of the function
|
||||
|
||||
tests := []struct {
|
||||
object []byte
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
object: []byte(""),
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
object: []byte("35"),
|
||||
expected: "35",
|
||||
},
|
||||
{
|
||||
// An unmarshalled byte string should return the same without error
|
||||
object: []byte("9ca4a1dd-a439-4593-b386-a7dfdc2e9fc6"),
|
||||
expected: "9ca4a1dd-a439-4593-b386-a7dfdc2e9fc6",
|
||||
},
|
||||
{
|
||||
// An unmarshalled json object string should return the same as a string without error also
|
||||
object: []byte(jsonobject),
|
||||
expected: jsonobject,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%s -> %s", test.object, test.expected), func(t *testing.T) {
|
||||
var object string
|
||||
err := UnmarshalObject(test.object, &object)
|
||||
is.NoError(err)
|
||||
is.Equal(test.expected, string(object))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,20 @@ package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/database/sqlite"
|
||||
)
|
||||
|
||||
// NewDatabase should use config options to return a connection to the requested database
|
||||
func NewDatabase(storeType, storePath string) (connection portainer.Connection, err error) {
|
||||
func NewDatabase(storeType, storePath string, encryptionKey []byte) (connection portainer.Connection, err error) {
|
||||
switch storeType {
|
||||
case "boltdb":
|
||||
return &boltdb.DbConnection{Path: storePath}, nil
|
||||
case "sqlite":
|
||||
return &sqlite.DbConnection{
|
||||
Path: storePath,
|
||||
EncryptionKey: encryptionKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unknown storage database: %s", storeType)
|
||||
}
|
||||
|
||||
21
api/database/models/version.go
Normal file
21
api/database/models/version.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
VersionKey string = "DB_VERSION"
|
||||
InstanceKey string = "INSTANCE_ID"
|
||||
EditionKey string = "EDITION"
|
||||
UpdatingKey string = "DB_UPDATING"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
Key string `json:"Key" gorm:"unique,primaryKey"`
|
||||
Value string `json:"Value"`
|
||||
}
|
||||
|
||||
func (r *Version) Validate(request *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
169
api/database/sqlite/db.go
Normal file
169
api/database/sqlite/db.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
DatabaseFileName = "portainer.db"
|
||||
EncryptedDatabaseFileName = "portainer.edb"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrHaveEncryptedAndUnencrypted = errors.New("Portainer has detected both an encrypted and un-encrypted database and cannot start. Only one database should exist")
|
||||
ErrHaveEncryptedWithNoKey = errors.New("The portainer database is encrypted, but no secret was loaded")
|
||||
)
|
||||
|
||||
type DbConnection struct {
|
||||
Path string
|
||||
EncryptionKey []byte
|
||||
isEncrypted bool
|
||||
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetDB() *gorm.DB {
|
||||
if connection.DB == nil {
|
||||
err := connection.Open()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return connection.DB
|
||||
}
|
||||
|
||||
// GetDatabaseFileName get the database filename
|
||||
func (connection *DbConnection) GetDatabaseFileName() string {
|
||||
if connection.IsEncryptedStore() {
|
||||
return EncryptedDatabaseFileName
|
||||
}
|
||||
|
||||
return DatabaseFileName
|
||||
}
|
||||
|
||||
// GetDataseFilePath get the path + filename for the database file
|
||||
func (connection *DbConnection) GetDatabaseFilePath() string {
|
||||
if connection.IsEncryptedStore() {
|
||||
return path.Join(connection.Path, EncryptedDatabaseFileName)
|
||||
}
|
||||
|
||||
return path.Join(connection.Path, DatabaseFileName)
|
||||
}
|
||||
|
||||
// GetStorePath get the filename and path for the database file
|
||||
func (connection *DbConnection) GetStorePath() string {
|
||||
return connection.Path
|
||||
}
|
||||
|
||||
func (connection *DbConnection) SetEncrypted(flag bool) {
|
||||
connection.isEncrypted = flag
|
||||
}
|
||||
|
||||
// Return true if the database is encrypted
|
||||
func (connection *DbConnection) IsEncryptedStore() bool {
|
||||
return connection.getEncryptionKey() != nil
|
||||
}
|
||||
|
||||
// NeedsEncryptionMigration returns true if database encryption is enabled and
|
||||
// we have an un-encrypted DB that requires migration to an encrypted DB
|
||||
func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
|
||||
|
||||
// Cases: Note, we need to check both portainer.db and portainer.edb
|
||||
// to determine if it's a new store. We only need to differentiate between cases 2,3 and 5
|
||||
|
||||
// 1) portainer.edb + key => False
|
||||
// 2) portainer.edb + no key => ERROR Fatal!
|
||||
// 3) portainer.db + key => True (needs migration)
|
||||
// 4) portainer.db + no key => False
|
||||
// 5) NoDB (new) + key => False
|
||||
// 6) NoDB (new) + no key => False
|
||||
// 7) portainer.db & portainer.edb => ERROR Fatal!
|
||||
|
||||
// If we have a loaded encryption key, always set encrypted
|
||||
if connection.EncryptionKey != nil {
|
||||
connection.SetEncrypted(true)
|
||||
}
|
||||
|
||||
// Check for portainer.db
|
||||
dbFile := path.Join(connection.Path, DatabaseFileName)
|
||||
_, err := os.Stat(dbFile)
|
||||
haveDbFile := err == nil
|
||||
|
||||
// Check for portainer.edb
|
||||
edbFile := path.Join(connection.Path, EncryptedDatabaseFileName)
|
||||
_, err = os.Stat(edbFile)
|
||||
haveEdbFile := err == nil
|
||||
|
||||
if haveDbFile && haveEdbFile {
|
||||
// 7 - encrypted and unencrypted db?
|
||||
return false, ErrHaveEncryptedAndUnencrypted
|
||||
}
|
||||
|
||||
if haveDbFile && connection.EncryptionKey != nil {
|
||||
// 3 - needs migration
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if haveEdbFile && connection.EncryptionKey == nil {
|
||||
// 2 - encrypted db, but no key?
|
||||
return false, ErrHaveEncryptedWithNoKey
|
||||
}
|
||||
|
||||
// 1, 4, 5, 6
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Open opens and initializes the BoltDB database.
|
||||
func (connection *DbConnection) Open() error {
|
||||
|
||||
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
|
||||
|
||||
// Now we open the db
|
||||
databasePath := connection.GetDatabaseFilePath()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sqlDB.SetMaxOpenConns(5)
|
||||
sqlDB.SetMaxOpenConns(10)
|
||||
connection.DB = db
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (connection *DbConnection) Close() error {
|
||||
sqlDB, err := connection.DB.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
connection.DB = nil
|
||||
return sqlDB.Close()
|
||||
}
|
||||
|
||||
func (connection *DbConnection) getEncryptionKey() []byte {
|
||||
if !connection.isEncrypted {
|
||||
return nil
|
||||
}
|
||||
|
||||
return connection.EncryptionKey
|
||||
}
|
||||
|
||||
func (connection *DbConnection) Init() error {
|
||||
connection.DB.AutoMigrate(&models.Version{})
|
||||
connection.DB.AutoMigrate(&portainer.Settings{})
|
||||
return nil
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
package apikeyrepository
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,10 +16,10 @@ type Service struct {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -35,85 +30,93 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error) {
|
||||
var result = make([]portainer.APIKey, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.APIKey{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
record, ok := obj.(*portainer.APIKey)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to APIKey object")
|
||||
return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
|
||||
}
|
||||
if record.UserID == userID {
|
||||
result = append(result, *record)
|
||||
}
|
||||
return &portainer.APIKey{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.APIKey{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// record, ok := obj.(*portainer.APIKey)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
|
||||
// return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
|
||||
// }
|
||||
|
||||
return result, err
|
||||
// if record.UserID == userID {
|
||||
// result = append(result, *record)
|
||||
// }
|
||||
|
||||
// return &portainer.APIKey{}, nil
|
||||
// })
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetAPIKeyByDigest returns the API key for the associated digest.
|
||||
// Note: there is a 1-to-1 mapping of api-key and digest
|
||||
func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error) {
|
||||
var k *portainer.APIKey
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.APIKey{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
key, ok := obj.(*portainer.APIKey)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to APIKey object")
|
||||
return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
|
||||
}
|
||||
if bytes.Equal(key.Digest, digest) {
|
||||
k = key
|
||||
return nil, stop
|
||||
}
|
||||
return &portainer.APIKey{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
return k, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
// var k *portainer.APIKey
|
||||
// stop := fmt.Errorf("ok")
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.APIKey{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// key, ok := obj.(*portainer.APIKey)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
|
||||
// return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
|
||||
// }
|
||||
// if bytes.Equal(key.Digest, digest) {
|
||||
// k = key
|
||||
// return nil, stop
|
||||
// }
|
||||
|
||||
return nil, err
|
||||
// return &portainer.APIKey{}, nil
|
||||
// })
|
||||
|
||||
// if err == stop {
|
||||
// return k, nil
|
||||
// }
|
||||
|
||||
// if err == nil {
|
||||
// return nil, errors.ErrObjectNotFound
|
||||
// }
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CreateAPIKey creates a new APIKey object.
|
||||
func (service *Service) CreateAPIKey(record *portainer.APIKey) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
record.ID = portainer.APIKeyID(id)
|
||||
// return service.connection.CreateObject(
|
||||
// BucketName,
|
||||
// func(id uint64) (int, interface{}) {
|
||||
// record.ID = portainer.APIKeyID(id)
|
||||
|
||||
return int(record.ID), record
|
||||
},
|
||||
)
|
||||
// return int(record.ID), record
|
||||
// },
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAPIKey retrieves an existing APIKey object by api key ID.
|
||||
func (service *Service) GetAPIKey(keyID portainer.APIKeyID) (*portainer.APIKey, error) {
|
||||
var key portainer.APIKey
|
||||
identifier := service.connection.ConvertToKey(int(keyID))
|
||||
// identifier := service.connection.ConvertToKey(int(keyID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &key)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &key, nil
|
||||
}
|
||||
|
||||
func (service *Service) UpdateAPIKey(key *portainer.APIKey) error {
|
||||
identifier := service.connection.ConvertToKey(int(key.ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, key)
|
||||
// identifier := service.connection.ConvertToKey(int(key.ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) DeleteAPIKey(ID portainer.APIKeyID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package customtemplate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -37,55 +34,60 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
|
||||
var customTemplates = make([]portainer.CustomTemplate, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.CustomTemplate{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
//var tag portainer.Tag
|
||||
customTemplate, ok := obj.(*portainer.CustomTemplate)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to CustomTemplate object")
|
||||
return nil, fmt.Errorf("Failed to convert to CustomTemplate object: %s", obj)
|
||||
}
|
||||
customTemplates = append(customTemplates, *customTemplate)
|
||||
return &portainer.CustomTemplate{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.CustomTemplate{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// //var tag portainer.Tag
|
||||
// customTemplate, ok := obj.(*portainer.CustomTemplate)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to CustomTemplate object")
|
||||
// return nil, fmt.Errorf("Failed to convert to CustomTemplate object: %s", obj)
|
||||
// }
|
||||
// customTemplates = append(customTemplates, *customTemplate)
|
||||
|
||||
return customTemplates, err
|
||||
// return &portainer.CustomTemplate{}, nil
|
||||
// })
|
||||
|
||||
return customTemplates, nil
|
||||
}
|
||||
|
||||
// CustomTemplate returns an custom template by ID.
|
||||
func (service *Service) CustomTemplate(ID portainer.CustomTemplateID) (*portainer.CustomTemplate, error) {
|
||||
var customTemplate portainer.CustomTemplate
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &customTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &customTemplate)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &customTemplate, nil
|
||||
}
|
||||
|
||||
// UpdateCustomTemplate updates an custom template.
|
||||
func (service *Service) UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, customTemplate)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, customTemplate)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCustomTemplate deletes an custom template.
|
||||
func (service *Service) DeleteCustomTemplate(ID portainer.CustomTemplateID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCustomTemplate uses the existing id and saves it.
|
||||
// TODO: where does the ID come from, and is it safe?
|
||||
func (service *Service) Create(customTemplate *portainer.CustomTemplate) error {
|
||||
return service.connection.CreateObjectWithId(BucketName, int(customTemplate.ID), customTemplate)
|
||||
// return service.connection.CreateObjectWithId(BucketName, int(customTemplate.ID), customTemplate)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a custom template.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return service.connection.GetNextIdentifier(BucketName)
|
||||
// return service.connection.GetNextIdentifier(BucketName)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -35,15 +35,16 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) DockerHub() (*portainer.DockerHub, error) {
|
||||
var dockerhub portainer.DockerHub
|
||||
|
||||
err := service.connection.GetObject(BucketName, []byte(dockerHubKey), &dockerhub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, []byte(dockerHubKey), &dockerhub)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &dockerhub, nil
|
||||
}
|
||||
|
||||
// UpdateDockerHub updates a DockerHub object.
|
||||
func (service *Service) UpdateDockerHub(dockerhub *portainer.DockerHub) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(dockerHubKey), dockerhub)
|
||||
// return service.connection.UpdateObject(BucketName, []byte(dockerHubKey), dockerhub)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package edgegroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -37,54 +34,58 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
|
||||
var groups = make([]portainer.EdgeGroup, 0)
|
||||
|
||||
err := service.connection.GetAllWithJsoniter(
|
||||
BucketName,
|
||||
&portainer.EdgeGroup{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
group, ok := obj.(*portainer.EdgeGroup)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeGroup object")
|
||||
return nil, fmt.Errorf("Failed to convert to EdgeGroup object: %s", obj)
|
||||
}
|
||||
groups = append(groups, *group)
|
||||
return &portainer.EdgeGroup{}, nil
|
||||
})
|
||||
// err := service.connection.GetAllWithJsoniter(
|
||||
// BucketName,
|
||||
// &portainer.EdgeGroup{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// group, ok := obj.(*portainer.EdgeGroup)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeGroup object")
|
||||
// return nil, fmt.Errorf("Failed to convert to EdgeGroup object: %s", obj)
|
||||
// }
|
||||
// groups = append(groups, *group)
|
||||
|
||||
return groups, err
|
||||
// return &portainer.EdgeGroup{}, nil
|
||||
// })
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// EdgeGroup returns an Edge group by ID.
|
||||
func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error) {
|
||||
var group portainer.EdgeGroup
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &group)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &group, nil
|
||||
}
|
||||
|
||||
// UpdateEdgeGroup updates an Edge group.
|
||||
func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, group)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, group)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEdgeGroup deletes an Edge group.
|
||||
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateEdgeGroup assign an ID to a new Edge group and saves it.
|
||||
func (service *Service) Create(group *portainer.EdgeGroup) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
group.ID = portainer.EdgeGroupID(id)
|
||||
return int(group.ID), group
|
||||
},
|
||||
)
|
||||
// return service.connection.CreateObject(
|
||||
// BucketName,
|
||||
// func(id uint64) (int, interface{}) {
|
||||
// group.ID = portainer.EdgeGroupID(id)
|
||||
// return int(group.ID), group
|
||||
// },
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package edgejob
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -37,60 +34,65 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
|
||||
var edgeJobs = make([]portainer.EdgeJob, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.EdgeJob{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
//var tag portainer.Tag
|
||||
job, ok := obj.(*portainer.EdgeJob)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeJob object")
|
||||
return nil, fmt.Errorf("Failed to convert to EdgeJob object: %s", obj)
|
||||
}
|
||||
edgeJobs = append(edgeJobs, *job)
|
||||
return &portainer.EdgeJob{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.EdgeJob{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// job, ok := obj.(*portainer.EdgeJob)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object")
|
||||
// return nil, fmt.Errorf("Failed to convert to EdgeJob object: %s", obj)
|
||||
// }
|
||||
|
||||
return edgeJobs, err
|
||||
// edgeJobs = append(edgeJobs, *job)
|
||||
|
||||
// return &portainer.EdgeJob{}, nil
|
||||
// })
|
||||
|
||||
return edgeJobs, nil
|
||||
}
|
||||
|
||||
// EdgeJob returns an Edge job by ID
|
||||
func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
|
||||
var edgeJob portainer.EdgeJob
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &edgeJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &edgeJob)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &edgeJob, nil
|
||||
}
|
||||
|
||||
// CreateEdgeJob creates a new Edge job
|
||||
func (service *Service) Create(edgeJob *portainer.EdgeJob) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
edgeJob.ID = portainer.EdgeJobID(id)
|
||||
return int(edgeJob.ID), edgeJob
|
||||
},
|
||||
)
|
||||
// Create creates a new EdgeJob
|
||||
func (service *Service) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||
// edgeJob.ID = ID
|
||||
|
||||
// return service.connection.CreateObjectWithId(
|
||||
// BucketName,
|
||||
// int(edgeJob.ID),
|
||||
// edgeJob,
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateEdgeJob updates an Edge job by ID
|
||||
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, edgeJob)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, edgeJob)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEdgeJob deletes an Edge job
|
||||
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return service.connection.GetNextIdentifier(BucketName)
|
||||
// return service.connection.GetNextIdentifier(BucketName)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package edgestack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -37,32 +34,34 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
|
||||
var stacks = make([]portainer.EdgeStack, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.EdgeStack{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
//var tag portainer.Tag
|
||||
stack, ok := obj.(*portainer.EdgeStack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeStack object")
|
||||
return nil, fmt.Errorf("Failed to convert to EdgeStack object: %s", obj)
|
||||
}
|
||||
stacks = append(stacks, *stack)
|
||||
return &portainer.EdgeStack{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.EdgeStack{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// //var tag portainer.Tag
|
||||
// stack, ok := obj.(*portainer.EdgeStack)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object")
|
||||
// return nil, fmt.Errorf("Failed to convert to EdgeStack object: %s", obj)
|
||||
// }
|
||||
|
||||
return stacks, err
|
||||
// stacks = append(stacks, *stack)
|
||||
|
||||
// return &portainer.EdgeStack{}, nil
|
||||
// })
|
||||
|
||||
return stacks, nil
|
||||
}
|
||||
|
||||
// EdgeStack returns an Edge stack by ID.
|
||||
func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error) {
|
||||
var stack portainer.EdgeStack
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &stack)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &stack, nil
|
||||
}
|
||||
@@ -70,28 +69,32 @@ func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStac
|
||||
// CreateEdgeStack saves an Edge stack object to db.
|
||||
func (service *Service) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||
|
||||
edgeStack.ID = id
|
||||
// edgeStack.ID = id
|
||||
|
||||
return service.connection.CreateObjectWithId(
|
||||
BucketName,
|
||||
int(edgeStack.ID),
|
||||
edgeStack,
|
||||
)
|
||||
// return service.connection.CreateObjectWithId(
|
||||
// BucketName,
|
||||
// int(edgeStack.ID),
|
||||
// edgeStack,
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateEdgeStack updates an Edge stack.
|
||||
func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, edgeStack)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, edgeStack)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEdgeStack deletes an Edge stack.
|
||||
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return service.connection.GetNextIdentifier(BucketName)
|
||||
// return service.connection.GetNextIdentifier(BucketName)
|
||||
return 0
|
||||
}
|
||||
|
||||
185
api/dataservices/edgeupdateschedule/edgeupdateschedule.go
Normal file
185
api/dataservices/edgeupdateschedule/edgeupdateschedule.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package edgeupdateschedule
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/edgetypes"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "edge_update_schedule"
|
||||
)
|
||||
|
||||
// Service represents a service for managing Edge Update Schedule data.
|
||||
type Service struct {
|
||||
connection portainer.Connection
|
||||
|
||||
mu sync.Mutex
|
||||
idxActiveSchedules map[portainer.EndpointID]*edgetypes.EndpointUpdateScheduleRelation
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
service := &Service{
|
||||
connection: connection,
|
||||
}
|
||||
|
||||
service.idxActiveSchedules = map[portainer.EndpointID]*edgetypes.EndpointUpdateScheduleRelation{}
|
||||
|
||||
schedules, err := service.List()
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "Unable to list schedules")
|
||||
}
|
||||
|
||||
for _, schedule := range schedules {
|
||||
service.setRelation(&schedule)
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (service *Service) ActiveSchedule(environmentID portainer.EndpointID) *edgetypes.EndpointUpdateScheduleRelation {
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
return service.idxActiveSchedules[environmentID]
|
||||
}
|
||||
|
||||
func (service *Service) ActiveSchedules(environmentsIDs []portainer.EndpointID) []edgetypes.EndpointUpdateScheduleRelation {
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
schedules := []edgetypes.EndpointUpdateScheduleRelation{}
|
||||
|
||||
for _, environmentID := range environmentsIDs {
|
||||
if s, ok := service.idxActiveSchedules[environmentID]; ok {
|
||||
schedules = append(schedules, *s)
|
||||
}
|
||||
}
|
||||
|
||||
return schedules
|
||||
}
|
||||
|
||||
// List return an array containing all the items in the bucket.
|
||||
func (service *Service) List() ([]edgetypes.UpdateSchedule, error) {
|
||||
var list = make([]edgetypes.UpdateSchedule, 0)
|
||||
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &edgetypes.UpdateSchedule{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// item, ok := obj.(*edgetypes.UpdateSchedule)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeUpdateSchedule object")
|
||||
// return nil, fmt.Errorf("Failed to convert to EdgeUpdateSchedule object: %s", obj)
|
||||
// }
|
||||
// list = append(list, *item)
|
||||
// return &edgetypes.UpdateSchedule{}, nil
|
||||
// })
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Item returns a item by ID.
|
||||
func (service *Service) Item(ID edgetypes.UpdateScheduleID) (*edgetypes.UpdateSchedule, error) {
|
||||
var item edgetypes.UpdateSchedule
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
// err := service.connection.GetObject(BucketName, identifier, &item)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
// Create assign an ID to a new object and saves it.
|
||||
func (service *Service) Create(item *edgetypes.UpdateSchedule) error {
|
||||
// err := service.connection.CreateObject(
|
||||
// BucketName,
|
||||
// func(id uint64) (int, interface{}) {
|
||||
// item.ID = edgetypes.UpdateScheduleID(id)
|
||||
// return int(item.ID), item
|
||||
// },
|
||||
// )
|
||||
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return service.setRelation(item)
|
||||
}
|
||||
|
||||
// Update updates an item.
|
||||
func (service *Service) Update(id edgetypes.UpdateScheduleID, item *edgetypes.UpdateSchedule) error {
|
||||
// identifier := service.connection.ConvertToKey(int(id))
|
||||
// err := service.connection.UpdateObject(BucketName, identifier, item)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
service.cleanRelation(id)
|
||||
|
||||
return service.setRelation(item)
|
||||
}
|
||||
|
||||
// Delete deletes an item.
|
||||
func (service *Service) Delete(id edgetypes.UpdateScheduleID) error {
|
||||
|
||||
service.cleanRelation(id)
|
||||
|
||||
// identifier := service.connection.ConvertToKey(int(id))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) cleanRelation(id edgetypes.UpdateScheduleID) {
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
for _, schedule := range service.idxActiveSchedules {
|
||||
if schedule != nil && schedule.ScheduleID == id {
|
||||
delete(service.idxActiveSchedules, schedule.EnvironmentID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (service *Service) setRelation(schedule *edgetypes.UpdateSchedule) error {
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
for environmentID, environmentStatus := range schedule.Status {
|
||||
if environmentStatus.Status != edgetypes.UpdateScheduleStatusPending {
|
||||
continue
|
||||
}
|
||||
|
||||
// this should never happen
|
||||
if service.idxActiveSchedules[environmentID] != nil && service.idxActiveSchedules[environmentID].ScheduleID != schedule.ID {
|
||||
return errors.New("Multiple schedules are pending for the same environment")
|
||||
}
|
||||
|
||||
service.idxActiveSchedules[environmentID] = &edgetypes.EndpointUpdateScheduleRelation{
|
||||
EnvironmentID: environmentID,
|
||||
ScheduleID: schedule.ID,
|
||||
TargetVersion: environmentStatus.TargetVersion,
|
||||
Status: environmentStatus.Status,
|
||||
Error: environmentStatus.Error,
|
||||
Type: schedule.Type,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package endpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -36,54 +33,60 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// Endpoint returns an environment(endpoint) by ID.
|
||||
func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
|
||||
var endpoint portainer.Endpoint
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &endpoint)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &endpoint, nil
|
||||
}
|
||||
|
||||
// UpdateEndpoint updates an environment(endpoint).
|
||||
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, endpoint)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, endpoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEndpoint deletes an environment(endpoint).
|
||||
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Endpoints return an array containing all the environments(endpoints).
|
||||
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
||||
var endpoints = make([]portainer.Endpoint, 0)
|
||||
|
||||
err := service.connection.GetAllWithJsoniter(
|
||||
BucketName,
|
||||
&portainer.Endpoint{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
endpoint, ok := obj.(*portainer.Endpoint)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Endpoint object")
|
||||
return nil, fmt.Errorf("Failed to convert to Endpoint object: %s", obj)
|
||||
}
|
||||
endpoints = append(endpoints, *endpoint)
|
||||
return &portainer.Endpoint{}, nil
|
||||
})
|
||||
// err := service.connection.GetAllWithJsoniter(
|
||||
// BucketName,
|
||||
// &portainer.Endpoint{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// endpoint, ok := obj.(*portainer.Endpoint)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Endpoint object")
|
||||
// return nil, fmt.Errorf("failed to convert to Endpoint object: %s", obj)
|
||||
// }
|
||||
|
||||
return endpoints, err
|
||||
// endpoints = append(endpoints, *endpoint)
|
||||
|
||||
// return &portainer.Endpoint{}, nil
|
||||
// })
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
|
||||
func (service *Service) Create(endpoint *portainer.Endpoint) error {
|
||||
return service.connection.CreateObjectWithSetSequence(BucketName, int(endpoint.ID), endpoint)
|
||||
// return service.connection.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return service.connection.GetNextIdentifier(BucketName)
|
||||
// return service.connection.GetNextIdentifier(BucketName)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package endpointgroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -36,56 +33,60 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// EndpointGroup returns an environment(endpoint) group by ID.
|
||||
func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
|
||||
var endpointGroup portainer.EndpointGroup
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &endpointGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &endpointGroup)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &endpointGroup, nil
|
||||
}
|
||||
|
||||
// UpdateEndpointGroup updates an environment(endpoint) group.
|
||||
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, endpointGroup)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, endpointGroup)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEndpointGroup deletes an environment(endpoint) group.
|
||||
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EndpointGroups return an array containing all the environment(endpoint) groups.
|
||||
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||
var endpointGroups = make([]portainer.EndpointGroup, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.EndpointGroup{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
//var tag portainer.Tag
|
||||
endpointGroup, ok := obj.(*portainer.EndpointGroup)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EndpointGroup object")
|
||||
return nil, fmt.Errorf("Failed to convert to EndpointGroup object: %s", obj)
|
||||
}
|
||||
endpointGroups = append(endpointGroups, *endpointGroup)
|
||||
return &portainer.EndpointGroup{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.EndpointGroup{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// endpointGroup, ok := obj.(*portainer.EndpointGroup)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointGroup object")
|
||||
// return nil, fmt.Errorf("Failed to convert to EndpointGroup object: %s", obj)
|
||||
// }
|
||||
|
||||
return endpointGroups, err
|
||||
// endpointGroups = append(endpointGroups, *endpointGroup)
|
||||
|
||||
// return &portainer.EndpointGroup{}, nil
|
||||
// })
|
||||
|
||||
return endpointGroups, nil
|
||||
}
|
||||
|
||||
// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it.
|
||||
func (service *Service) Create(endpointGroup *portainer.EndpointGroup) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
endpointGroup.ID = portainer.EndpointGroupID(id)
|
||||
return int(endpointGroup.ID), endpointGroup
|
||||
},
|
||||
)
|
||||
// return service.connection.CreateObject(
|
||||
// BucketName,
|
||||
// func(id uint64) (int, interface{}) {
|
||||
// endpointGroup.ID = portainer.EndpointGroupID(id)
|
||||
// return int(endpointGroup.ID), endpointGroup
|
||||
// },
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package endpointrelation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,62 +20,67 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//EndpointRelations returns an array of all EndpointRelations
|
||||
// EndpointRelations returns an array of all EndpointRelations
|
||||
func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error) {
|
||||
var all = make([]portainer.EndpointRelation, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.EndpointRelation{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
r, ok := obj.(*portainer.EndpointRelation)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EndpointRelation object")
|
||||
return nil, fmt.Errorf("Failed to convert to EndpointRelation object: %s", obj)
|
||||
}
|
||||
all = append(all, *r)
|
||||
return &portainer.EndpointRelation{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.EndpointRelation{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// r, ok := obj.(*portainer.EndpointRelation)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointRelation object")
|
||||
// return nil, fmt.Errorf("Failed to convert to EndpointRelation object: %s", obj)
|
||||
// }
|
||||
|
||||
return all, err
|
||||
// all = append(all, *r)
|
||||
|
||||
// return &portainer.EndpointRelation{}, nil
|
||||
// })
|
||||
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// EndpointRelation returns a Environment(Endpoint) relation object by EndpointID
|
||||
func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*portainer.EndpointRelation, error) {
|
||||
var endpointRelation portainer.EndpointRelation
|
||||
identifier := service.connection.ConvertToKey(int(endpointID))
|
||||
// identifier := service.connection.ConvertToKey(int(endpointID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &endpointRelation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &endpointRelation)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &endpointRelation, nil
|
||||
}
|
||||
|
||||
// CreateEndpointRelation saves endpointRelation
|
||||
func (service *Service) Create(endpointRelation *portainer.EndpointRelation) error {
|
||||
return service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
|
||||
// return service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateEndpointRelation updates an Environment(Endpoint) relation object
|
||||
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
|
||||
identifier := service.connection.ConvertToKey(int(EndpointID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, endpointRelation)
|
||||
// identifier := service.connection.ConvertToKey(int(EndpointID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, endpointRelation)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEndpointRelation deletes an Environment(Endpoint) relation object
|
||||
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
|
||||
identifier := service.connection.ConvertToKey(int(EndpointID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(EndpointID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import "errors"
|
||||
|
||||
var (
|
||||
// TODO: i'm pretty sure this needs wrapping at several levels
|
||||
ErrObjectNotFound = errors.New("Object not found inside the database")
|
||||
ErrWrongDBEdition = errors.New("The Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
|
||||
ErrObjectNotFound = errors.New("object not found inside the database")
|
||||
ErrWrongDBEdition = errors.New("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
|
||||
ErrDBImportFailed = errors.New("importing backup failed")
|
||||
)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -36,12 +33,12 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// Extension returns a extension by ID
|
||||
func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extension, error) {
|
||||
var extension portainer.Extension
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &extension)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &extension, nil
|
||||
}
|
||||
@@ -50,29 +47,33 @@ func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extensio
|
||||
func (service *Service) Extensions() ([]portainer.Extension, error) {
|
||||
var extensions = make([]portainer.Extension, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Extension{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
extension, ok := obj.(*portainer.Extension)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Extension object")
|
||||
return nil, fmt.Errorf("Failed to convert to Extension object: %s", obj)
|
||||
}
|
||||
extensions = append(extensions, *extension)
|
||||
return &portainer.Extension{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.Extension{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// extension, ok := obj.(*portainer.Extension)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Extension object")
|
||||
// return nil, fmt.Errorf("Failed to convert to Extension object: %s", obj)
|
||||
// }
|
||||
|
||||
return extensions, err
|
||||
// extensions = append(extensions, *extension)
|
||||
|
||||
// return &portainer.Extension{}, nil
|
||||
// })
|
||||
|
||||
return extensions, nil
|
||||
}
|
||||
|
||||
// Persist persists a extension inside the database.
|
||||
func (service *Service) Persist(extension *portainer.Extension) error {
|
||||
return service.connection.CreateObjectWithId(BucketName, int(extension.ID), extension)
|
||||
// return service.connection.CreateObjectWithId(BucketName, int(extension.ID), extension)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteExtension deletes a Extension.
|
||||
func (service *Service) DeleteExtension(ID portainer.ExtensionID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
95
api/dataservices/fdoprofile/fdoprofile.go
Normal file
95
api/dataservices/fdoprofile/fdoprofile.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package fdoprofile
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "fdo_profiles"
|
||||
)
|
||||
|
||||
// Service represents a service for managingFDO Profiles data.
|
||||
type Service struct {
|
||||
connection portainer.Connection
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FDOProfiles return an array containing all the FDO Profiles.
|
||||
func (service *Service) FDOProfiles() ([]portainer.FDOProfile, error) {
|
||||
var fdoProfiles = make([]portainer.FDOProfile, 0)
|
||||
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.FDOProfile{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// fdoProfile, ok := obj.(*portainer.FDOProfile)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to FDOProfile object")
|
||||
|
||||
// return nil, fmt.Errorf("Failed to convert to FDOProfile object: %s", obj)
|
||||
// }
|
||||
// fdoProfiles = append(fdoProfiles, *fdoProfile)
|
||||
// return &portainer.FDOProfile{}, nil
|
||||
// })
|
||||
|
||||
return fdoProfiles, nil
|
||||
}
|
||||
|
||||
// FDOProfile returns an FDO Profile by ID.
|
||||
func (service *Service) FDOProfile(ID portainer.FDOProfileID) (*portainer.FDOProfile, error) {
|
||||
var FDOProfile portainer.FDOProfile
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
// err := service.connection.GetObject(BucketName, identifier, &FDOProfile)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &FDOProfile, nil
|
||||
}
|
||||
|
||||
// Create assign an ID to a new FDO Profile and saves it.
|
||||
func (service *Service) Create(FDOProfile *portainer.FDOProfile) error {
|
||||
// return service.connection.CreateObjectWithId(
|
||||
// BucketName,
|
||||
// int(FDOProfile.ID),
|
||||
// FDOProfile,
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates an FDO Profile.
|
||||
func (service *Service) Update(ID portainer.FDOProfileID, FDOProfile *portainer.FDOProfile) error {
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, FDOProfile)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes an FDO Profile.
|
||||
func (service *Service) Delete(ID portainer.FDOProfileID) error {
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a FDO Profile.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
// return service.connection.GetNextIdentifier(BucketName)
|
||||
return 0
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package helmuserrepository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,77 +20,84 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//HelmUserRepository returns an array of all HelmUserRepository
|
||||
func (service *Service) HelmUserRepositorys() ([]portainer.HelmUserRepository, error) {
|
||||
// HelmUserRepository returns an array of all HelmUserRepository
|
||||
func (service *Service) HelmUserRepositories() ([]portainer.HelmUserRepository, error) {
|
||||
var repos = make([]portainer.HelmUserRepository, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.HelmUserRepository{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
r, ok := obj.(*portainer.HelmUserRepository)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to HelmUserRepository object")
|
||||
return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
|
||||
}
|
||||
repos = append(repos, *r)
|
||||
return &portainer.HelmUserRepository{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.HelmUserRepository{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// r, ok := obj.(*portainer.HelmUserRepository)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
|
||||
// return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
|
||||
// }
|
||||
|
||||
return repos, err
|
||||
// repos = append(repos, *r)
|
||||
|
||||
// return &portainer.HelmUserRepository{}, nil
|
||||
// })
|
||||
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// HelmUserRepositoryByUserID return an array containing all the HelmUserRepository objects where the specified userID is present.
|
||||
func (service *Service) HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error) {
|
||||
var result = make([]portainer.HelmUserRepository, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.HelmUserRepository{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
record, ok := obj.(*portainer.HelmUserRepository)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to HelmUserRepository object")
|
||||
return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
|
||||
}
|
||||
if record.UserID == userID {
|
||||
result = append(result, *record)
|
||||
}
|
||||
return &portainer.HelmUserRepository{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.HelmUserRepository{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// record, ok := obj.(*portainer.HelmUserRepository)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
|
||||
// return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
|
||||
// }
|
||||
|
||||
return result, err
|
||||
// if record.UserID == userID {
|
||||
// result = append(result, *record)
|
||||
// }
|
||||
|
||||
// return &portainer.HelmUserRepository{}, nil
|
||||
// })
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateHelmUserRepository creates a new HelmUserRepository object.
|
||||
func (service *Service) Create(record *portainer.HelmUserRepository) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
record.ID = portainer.HelmUserRepositoryID(id)
|
||||
return int(record.ID), record
|
||||
},
|
||||
)
|
||||
// return service.connection.CreateObject(
|
||||
// BucketName,
|
||||
// func(id uint64) (int, interface{}) {
|
||||
// record.ID = portainer.HelmUserRepositoryID(id)
|
||||
// return int(record.ID), record
|
||||
// },
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateHelmUserRepostory updates an registry.
|
||||
func (service *Service) UpdateHelmUserRepository(ID portainer.HelmUserRepositoryID, registry *portainer.HelmUserRepository) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, registry)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, registry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteHelmUserRepository deletes an registry.
|
||||
func (service *Service) DeleteHelmUserRepository(ID portainer.HelmUserRepositoryID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package dataservices
|
||||
// "github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/edgetypes"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
@@ -17,26 +17,25 @@ type (
|
||||
Open() (newStore bool, err error)
|
||||
Init() error
|
||||
Close() error
|
||||
MigrateData() error
|
||||
Rollback(force bool) error
|
||||
CheckCurrentEdition() error
|
||||
BackupTo(w io.Writer) error
|
||||
Export(filename string) (err error)
|
||||
IsErrObjectNotFound(err error) bool
|
||||
|
||||
CheckCurrentEdition() error
|
||||
IsErrObjectNotFound(err error) bool
|
||||
CustomTemplate() CustomTemplateService
|
||||
EdgeGroup() EdgeGroupService
|
||||
EdgeJob() EdgeJobService
|
||||
EdgeStack() EdgeStackService
|
||||
EdgeUpdateSchedule() EdgeUpdateScheduleService
|
||||
Endpoint() EndpointService
|
||||
EndpointGroup() EndpointGroupService
|
||||
EndpointRelation() EndpointRelationService
|
||||
FDOProfile() FDOProfileService
|
||||
HelmUserRepository() HelmUserRepositoryService
|
||||
Registry() RegistryService
|
||||
ResourceControl() ResourceControlService
|
||||
Role() RoleService
|
||||
APIKeyRepository() APIKeyRepository
|
||||
Settings() SettingsService
|
||||
Snapshot() SnapshotService
|
||||
SSLSettings() SSLSettingsService
|
||||
Stack() StackService
|
||||
Tag() TagService
|
||||
@@ -56,7 +55,6 @@ type (
|
||||
Create(customTemplate *portainer.CustomTemplate) error
|
||||
UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error
|
||||
DeleteCustomTemplate(ID portainer.CustomTemplateID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// EdgeGroupService represents a service to manage Edge groups
|
||||
@@ -66,18 +64,26 @@ type (
|
||||
Create(group *portainer.EdgeGroup) error
|
||||
UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error
|
||||
DeleteEdgeGroup(ID portainer.EdgeGroupID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// EdgeJobService represents a service to manage Edge jobs
|
||||
EdgeJobService interface {
|
||||
EdgeJobs() ([]portainer.EdgeJob, error)
|
||||
EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error)
|
||||
Create(edgeJob *portainer.EdgeJob) error
|
||||
Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
|
||||
UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
|
||||
DeleteEdgeJob(ID portainer.EdgeJobID) error
|
||||
GetNextIdentifier() int
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
EdgeUpdateScheduleService interface {
|
||||
ActiveSchedule(environmentID portainer.EndpointID) *edgetypes.EndpointUpdateScheduleRelation
|
||||
ActiveSchedules(environmentIDs []portainer.EndpointID) []edgetypes.EndpointUpdateScheduleRelation
|
||||
List() ([]edgetypes.UpdateSchedule, error)
|
||||
Item(ID edgetypes.UpdateScheduleID) (*edgetypes.UpdateSchedule, error)
|
||||
Create(edgeUpdateSchedule *edgetypes.UpdateSchedule) error
|
||||
Update(ID edgetypes.UpdateScheduleID, edgeUpdateSchedule *edgetypes.UpdateSchedule) error
|
||||
Delete(ID edgetypes.UpdateScheduleID) error
|
||||
}
|
||||
|
||||
// EdgeStackService represents a service to manage Edge stacks
|
||||
@@ -88,7 +94,6 @@ type (
|
||||
UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error
|
||||
DeleteEdgeStack(ID portainer.EdgeStackID) error
|
||||
GetNextIdentifier() int
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// EndpointService represents a service for managing environment(endpoint) data
|
||||
@@ -99,7 +104,6 @@ type (
|
||||
UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error
|
||||
DeleteEndpoint(ID portainer.EndpointID) error
|
||||
GetNextIdentifier() int
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// EndpointGroupService represents a service for managing environment(endpoint) group data
|
||||
@@ -109,7 +113,6 @@ type (
|
||||
Create(group *portainer.EndpointGroup) error
|
||||
UpdateEndpointGroup(ID portainer.EndpointGroupID, group *portainer.EndpointGroup) error
|
||||
DeleteEndpointGroup(ID portainer.EndpointGroupID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// EndpointRelationService represents a service for managing environment(endpoint) relations data
|
||||
@@ -119,17 +122,25 @@ type (
|
||||
Create(endpointRelation *portainer.EndpointRelation) error
|
||||
UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error
|
||||
DeleteEndpointRelation(EndpointID portainer.EndpointID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// FDOProfileService represents a service to manage FDO Profiles
|
||||
FDOProfileService interface {
|
||||
FDOProfiles() ([]portainer.FDOProfile, error)
|
||||
FDOProfile(ID portainer.FDOProfileID) (*portainer.FDOProfile, error)
|
||||
Create(FDOProfile *portainer.FDOProfile) error
|
||||
Update(ID portainer.FDOProfileID, FDOProfile *portainer.FDOProfile) error
|
||||
Delete(ID portainer.FDOProfileID) error
|
||||
GetNextIdentifier() int
|
||||
}
|
||||
|
||||
// HelmUserRepositoryService represents a service to manage HelmUserRepositories
|
||||
HelmUserRepositoryService interface {
|
||||
HelmUserRepositorys() ([]portainer.HelmUserRepository, error)
|
||||
HelmUserRepositories() ([]portainer.HelmUserRepository, error)
|
||||
HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error)
|
||||
Create(record *portainer.HelmUserRepository) error
|
||||
UpdateHelmUserRepository(ID portainer.HelmUserRepositoryID, repository *portainer.HelmUserRepository) error
|
||||
DeleteHelmUserRepository(ID portainer.HelmUserRepositoryID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// JWTService represents a service for managing JWT tokens
|
||||
@@ -148,7 +159,6 @@ type (
|
||||
Create(registry *portainer.Registry) error
|
||||
UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error
|
||||
DeleteRegistry(ID portainer.RegistryID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// ResourceControlService represents a service for managing resource control data
|
||||
@@ -159,7 +169,6 @@ type (
|
||||
Create(rc *portainer.ResourceControl) error
|
||||
UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error
|
||||
DeleteResourceControl(ID portainer.ResourceControlID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// RoleService represents a service for managing user roles
|
||||
@@ -168,7 +177,6 @@ type (
|
||||
Roles() ([]portainer.Role, error)
|
||||
Create(role *portainer.Role) error
|
||||
UpdateRole(ID portainer.RoleID, role *portainer.Role) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// APIKeyRepositoryService
|
||||
@@ -186,14 +194,20 @@ type (
|
||||
Settings() (*portainer.Settings, error)
|
||||
UpdateSettings(settings *portainer.Settings) error
|
||||
IsFeatureFlagEnabled(feature portainer.Feature) bool
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
SnapshotService interface {
|
||||
Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error)
|
||||
Snapshots() ([]portainer.Snapshot, error)
|
||||
UpdateSnapshot(snapshot *portainer.Snapshot) error
|
||||
DeleteSnapshot(endpointID portainer.EndpointID) error
|
||||
Create(snapshot *portainer.Snapshot) error
|
||||
}
|
||||
|
||||
// SSLSettingsService represents a service for managing application settings
|
||||
SSLSettingsService interface {
|
||||
Settings() (*portainer.SSLSettings, error)
|
||||
UpdateSettings(settings *portainer.SSLSettings) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// StackService represents a service for managing stack data
|
||||
@@ -208,7 +222,6 @@ type (
|
||||
GetNextIdentifier() int
|
||||
StackByWebhookID(ID string) (*portainer.Stack, error)
|
||||
RefreshableStacks() ([]portainer.Stack, error)
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// TagService represents a service for managing tag data
|
||||
@@ -218,7 +231,6 @@ type (
|
||||
Create(tag *portainer.Tag) error
|
||||
UpdateTag(ID portainer.TagID, tag *portainer.Tag) error
|
||||
DeleteTag(ID portainer.TagID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// TeamService represents a service for managing user data
|
||||
@@ -229,7 +241,6 @@ type (
|
||||
Create(team *portainer.Team) error
|
||||
UpdateTeam(ID portainer.TeamID, team *portainer.Team) error
|
||||
DeleteTeam(ID portainer.TeamID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// TeamMembershipService represents a service for managing team membership data
|
||||
@@ -243,14 +254,12 @@ type (
|
||||
DeleteTeamMembership(ID portainer.TeamMembershipID) error
|
||||
DeleteTeamMembershipByUserID(userID portainer.UserID) error
|
||||
DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// TunnelServerService represents a service for managing data associated to the tunnel server
|
||||
TunnelServerService interface {
|
||||
Info() (*portainer.TunnelServerInfo, error)
|
||||
UpdateInfo(info *portainer.TunnelServerInfo) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// UserService represents a service for managing user data
|
||||
@@ -262,7 +271,6 @@ type (
|
||||
Create(user *portainer.User) error
|
||||
UpdateUser(ID portainer.UserID, user *portainer.User) error
|
||||
DeleteUser(ID portainer.UserID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// VersionService represents a service for managing version data
|
||||
@@ -272,7 +280,6 @@ type (
|
||||
InstanceID() (string, error)
|
||||
StoreDBVersion(version int) error
|
||||
StoreInstanceID(ID string) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// WebhookService represents a service for managing webhook data.
|
||||
@@ -284,7 +291,6 @@ type (
|
||||
WebhookByResourceID(resourceID string) (*portainer.Webhook, error)
|
||||
WebhookByToken(token string) (*portainer.Webhook, error)
|
||||
DeleteWebhook(ID portainer.WebhookID) error
|
||||
BucketName() string
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -36,12 +33,12 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// Registry returns an registry by ID.
|
||||
func (service *Service) Registry(ID portainer.RegistryID) (*portainer.Registry, error) {
|
||||
var registry portainer.Registry
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, ®istry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, ®istry)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return ®istry, nil
|
||||
}
|
||||
@@ -50,41 +47,46 @@ func (service *Service) Registry(ID portainer.RegistryID) (*portainer.Registry,
|
||||
func (service *Service) Registries() ([]portainer.Registry, error) {
|
||||
var registries = make([]portainer.Registry, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Registry{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
registry, ok := obj.(*portainer.Registry)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Registry object")
|
||||
return nil, fmt.Errorf("Failed to convert to Registry object: %s", obj)
|
||||
}
|
||||
registries = append(registries, *registry)
|
||||
return &portainer.Registry{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.Registry{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// registry, ok := obj.(*portainer.Registry)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Registry object")
|
||||
// return nil, fmt.Errorf("Failed to convert to Registry object: %s", obj)
|
||||
// }
|
||||
|
||||
return registries, err
|
||||
// registries = append(registries, *registry)
|
||||
|
||||
// return &portainer.Registry{}, nil
|
||||
// })
|
||||
|
||||
return registries, nil
|
||||
}
|
||||
|
||||
// CreateRegistry creates a new registry.
|
||||
func (service *Service) Create(registry *portainer.Registry) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
registry.ID = portainer.RegistryID(id)
|
||||
return int(registry.ID), registry
|
||||
},
|
||||
)
|
||||
// return service.connection.CreateObject(
|
||||
// BucketName,
|
||||
// func(id uint64) (int, interface{}) {
|
||||
// registry.ID = portainer.RegistryID(id)
|
||||
// return int(registry.ID), registry
|
||||
// },
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRegistry updates an registry.
|
||||
func (service *Service) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, registry)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, registry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRegistry deletes an registry.
|
||||
func (service *Service) DeleteRegistry(ID portainer.RegistryID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package resourcecontrol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -24,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -37,12 +33,12 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// ResourceControl returns a ResourceControl object by ID
|
||||
func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error) {
|
||||
var resourceControl portainer.ResourceControl
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &resourceControl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &resourceControl)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &resourceControl, nil
|
||||
}
|
||||
@@ -51,80 +47,83 @@ func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portai
|
||||
// to the main ResourceID or in SubResourceIDs. It also performs a check on the resource type. Return nil
|
||||
// if no ResourceControl was found.
|
||||
func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
||||
var resourceControl *portainer.ResourceControl
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.ResourceControl{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
rc, ok := obj.(*portainer.ResourceControl)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
|
||||
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
|
||||
}
|
||||
// var resourceControl *portainer.ResourceControl
|
||||
// stop := fmt.Errorf("ok")
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.ResourceControl{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// rc, ok := obj.(*portainer.ResourceControl)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
|
||||
// return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
|
||||
// }
|
||||
|
||||
if rc.ResourceID == resourceID && rc.Type == resourceType {
|
||||
resourceControl = rc
|
||||
return nil, stop
|
||||
}
|
||||
// if rc.ResourceID == resourceID && rc.Type == resourceType {
|
||||
// resourceControl = rc
|
||||
// return nil, stop
|
||||
// }
|
||||
|
||||
for _, subResourceID := range rc.SubResourceIDs {
|
||||
if subResourceID == resourceID {
|
||||
resourceControl = rc
|
||||
return nil, stop
|
||||
}
|
||||
}
|
||||
return &portainer.ResourceControl{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
return resourceControl, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
// for _, subResourceID := range rc.SubResourceIDs {
|
||||
// if subResourceID == resourceID {
|
||||
// resourceControl = rc
|
||||
// return nil, stop
|
||||
// }
|
||||
// }
|
||||
|
||||
return nil, err
|
||||
// return &portainer.ResourceControl{}, nil
|
||||
// })
|
||||
// if err == stop {
|
||||
// return resourceControl, nil
|
||||
// }
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ResourceControls returns all the ResourceControl objects
|
||||
func (service *Service) ResourceControls() ([]portainer.ResourceControl, error) {
|
||||
var rcs = make([]portainer.ResourceControl, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.ResourceControl{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
rc, ok := obj.(*portainer.ResourceControl)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
|
||||
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
|
||||
}
|
||||
rcs = append(rcs, *rc)
|
||||
return &portainer.ResourceControl{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.ResourceControl{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// rc, ok := obj.(*portainer.ResourceControl)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
|
||||
// return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
|
||||
// }
|
||||
|
||||
return rcs, err
|
||||
// rcs = append(rcs, *rc)
|
||||
|
||||
// return &portainer.ResourceControl{}, nil
|
||||
// })
|
||||
|
||||
return rcs, nil
|
||||
}
|
||||
|
||||
// CreateResourceControl creates a new ResourceControl object
|
||||
func (service *Service) Create(resourceControl *portainer.ResourceControl) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
resourceControl.ID = portainer.ResourceControlID(id)
|
||||
return int(resourceControl.ID), resourceControl
|
||||
},
|
||||
)
|
||||
// return service.connection.CreateObject(
|
||||
// BucketName,
|
||||
// func(id uint64) (int, interface{}) {
|
||||
// resourceControl.ID = portainer.ResourceControlID(id)
|
||||
// return int(resourceControl.ID), resourceControl
|
||||
// },
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateResourceControl saves a ResourceControl object.
|
||||
func (service *Service) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, resourceControl)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, resourceControl)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteResourceControl deletes a ResourceControl object by ID
|
||||
func (service *Service) DeleteResourceControl(ID portainer.ResourceControlID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -36,12 +33,12 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// Role returns a Role by ID
|
||||
func (service *Service) Role(ID portainer.RoleID) (*portainer.Role, error) {
|
||||
var set portainer.Role
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &set)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &set)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &set, nil
|
||||
}
|
||||
@@ -50,35 +47,39 @@ func (service *Service) Role(ID portainer.RoleID) (*portainer.Role, error) {
|
||||
func (service *Service) Roles() ([]portainer.Role, error) {
|
||||
var sets = make([]portainer.Role, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Role{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
set, ok := obj.(*portainer.Role)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Role object")
|
||||
return nil, fmt.Errorf("Failed to convert to Role object: %s", obj)
|
||||
}
|
||||
sets = append(sets, *set)
|
||||
return &portainer.Role{}, nil
|
||||
})
|
||||
// err := service.connection.GetAll(
|
||||
// BucketName,
|
||||
// &portainer.Role{},
|
||||
// func(obj interface{}) (interface{}, error) {
|
||||
// set, ok := obj.(*portainer.Role)
|
||||
// if !ok {
|
||||
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Role object")
|
||||
// return nil, fmt.Errorf("Failed to convert to Role object: %s", obj)
|
||||
// }
|
||||
|
||||
return sets, err
|
||||
// sets = append(sets, *set)
|
||||
|
||||
// return &portainer.Role{}, nil
|
||||
// })
|
||||
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
// CreateRole creates a new Role.
|
||||
func (service *Service) Create(role *portainer.Role) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
role.ID = portainer.RoleID(id)
|
||||
return int(role.ID), role
|
||||
},
|
||||
)
|
||||
// return service.connection.CreateObject(
|
||||
// BucketName,
|
||||
// func(id uint64) (int, interface{}) {
|
||||
// role.ID = portainer.RoleID(id)
|
||||
// return int(role.ID), role
|
||||
// },
|
||||
// )
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRole updates a role.
|
||||
func (service *Service) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, role)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, role)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package schedule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,10 +20,10 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -36,46 +33,33 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// Schedule returns a schedule by ID.
|
||||
func (service *Service) Schedule(ID portainer.ScheduleID) (*portainer.Schedule, error) {
|
||||
var schedule portainer.Schedule
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &schedule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err := service.connection.GetObject(BucketName, identifier, &schedule)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &schedule, nil
|
||||
}
|
||||
|
||||
// UpdateSchedule updates a schedule.
|
||||
func (service *Service) UpdateSchedule(ID portainer.ScheduleID, schedule *portainer.Schedule) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, schedule)
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, schedule)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSchedule deletes a schedule.
|
||||
func (service *Service) DeleteSchedule(ID portainer.ScheduleID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Schedules return a array containing all the schedules.
|
||||
func (service *Service) Schedules() ([]portainer.Schedule, error) {
|
||||
var schedules = make([]portainer.Schedule, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Schedule{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
schedule, ok := obj.(*portainer.Schedule)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Schedule object")
|
||||
return nil, fmt.Errorf("Failed to convert to Schedule object: %s", obj)
|
||||
}
|
||||
schedules = append(schedules, *schedule)
|
||||
return &portainer.Schedule{}, nil
|
||||
})
|
||||
|
||||
return schedules, err
|
||||
return schedules, nil
|
||||
}
|
||||
|
||||
// SchedulesByJobType return a array containing all the schedules
|
||||
@@ -83,30 +67,15 @@ func (service *Service) Schedules() ([]portainer.Schedule, error) {
|
||||
func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portainer.Schedule, error) {
|
||||
var schedules = make([]portainer.Schedule, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Schedule{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
schedule, ok := obj.(*portainer.Schedule)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Schedule object")
|
||||
return nil, fmt.Errorf("Failed to convert to Schedule object: %s", obj)
|
||||
}
|
||||
if schedule.JobType == jobType {
|
||||
schedules = append(schedules, *schedule)
|
||||
}
|
||||
return &portainer.Schedule{}, nil
|
||||
})
|
||||
|
||||
return schedules, err
|
||||
return schedules, nil
|
||||
}
|
||||
|
||||
// Create assign an ID to a new schedule and saves it.
|
||||
func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
|
||||
return service.connection.CreateObjectWithSetSequence(BucketName, int(schedule.ID), schedule)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a schedule.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return service.connection.GetNextIdentifier(BucketName)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -21,11 +21,6 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
@@ -35,29 +30,14 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) Settings() (*portainer.Settings, error) {
|
||||
var settings portainer.Settings
|
||||
|
||||
err := service.connection.GetObject(BucketName, []byte(settingsKey), &settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &settings, nil
|
||||
}
|
||||
|
||||
// UpdateSettings persists a Settings object.
|
||||
func (service *Service) UpdateSettings(settings *portainer.Settings) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(settingsKey), settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) IsFeatureFlagEnabled(feature portainer.Feature) bool {
|
||||
settings, err := service.Settings()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
featureFlagSetting, ok := settings.FeatureFlagSettings[feature]
|
||||
if ok {
|
||||
return featureFlagSetting
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
48
api/dataservices/snapshot/snapshot.go
Normal file
48
api/dataservices/snapshot/snapshot.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
const (
|
||||
BucketName = "snapshots"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
connection portainer.Connection
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (service *Service) Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error) {
|
||||
var snapshot portainer.Snapshot
|
||||
|
||||
return &snapshot, nil
|
||||
}
|
||||
|
||||
func (service *Service) Snapshots() ([]portainer.Snapshot, error) {
|
||||
var snapshots = make([]portainer.Snapshot, 0)
|
||||
|
||||
return snapshots, nil
|
||||
}
|
||||
|
||||
func (service *Service) UpdateSnapshot(snapshot *portainer.Snapshot) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) DeleteSnapshot(endpointID portainer.EndpointID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) Create(snapshot *portainer.Snapshot) error {
|
||||
return nil
|
||||
}
|
||||
@@ -21,11 +21,6 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
@@ -35,15 +30,10 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) Settings() (*portainer.SSLSettings, error) {
|
||||
var settings portainer.SSLSettings
|
||||
|
||||
err := service.connection.GetObject(BucketName, []byte(key), &settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &settings, nil
|
||||
}
|
||||
|
||||
// UpdateSettings persists a SSLSettings object.
|
||||
func (service *Service) UpdateSettings(settings *portainer.SSLSettings) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(key), settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,10 +20,6 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
@@ -39,161 +29,56 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// Stack returns a stack object by ID.
|
||||
func (service *Service) Stack(ID portainer.StackID) (*portainer.Stack, error) {
|
||||
var stack portainer.Stack
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stack, nil
|
||||
}
|
||||
|
||||
// StackByName returns a stack object by name.
|
||||
func (service *Service) StackByName(name string) (*portainer.Stack, error) {
|
||||
var s *portainer.Stack
|
||||
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Stack{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(*portainer.Stack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
|
||||
}
|
||||
if stack.Name == name {
|
||||
s = stack
|
||||
return nil, stop
|
||||
}
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
return s, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Stacks returns an array containing all the stacks with same name
|
||||
func (service *Service) StacksByName(name string) ([]portainer.Stack, error) {
|
||||
var stacks = make([]portainer.Stack, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Stack{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(portainer.Stack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
|
||||
}
|
||||
if stack.Name == name {
|
||||
stacks = append(stacks, stack)
|
||||
}
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
|
||||
return stacks, err
|
||||
return stacks, nil
|
||||
}
|
||||
|
||||
// Stacks returns an array containing all the stacks.
|
||||
func (service *Service) Stacks() ([]portainer.Stack, error) {
|
||||
var stacks = make([]portainer.Stack, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Stack{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(*portainer.Stack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
|
||||
}
|
||||
stacks = append(stacks, *stack)
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
|
||||
return stacks, err
|
||||
return stacks, nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a stack.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return service.connection.GetNextIdentifier(BucketName)
|
||||
return 0
|
||||
}
|
||||
|
||||
// CreateStack creates a new stack.
|
||||
func (service *Service) Create(stack *portainer.Stack) error {
|
||||
return service.connection.CreateObjectWithSetSequence(BucketName, int(stack.ID), stack)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStack updates a stack.
|
||||
func (service *Service) UpdateStack(ID portainer.StackID, stack *portainer.Stack) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, stack)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteStack deletes a stack.
|
||||
func (service *Service) DeleteStack(ID portainer.StackID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StackByWebhookID returns a pointer to a stack object by webhook ID.
|
||||
// It returns nil, errors.ErrObjectNotFound if there's no stack associated with the webhook ID.
|
||||
func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) {
|
||||
var s *portainer.Stack
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Stack{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
var ok bool
|
||||
s, ok = obj.(*portainer.Stack)
|
||||
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
return &portainer.Stack{}, nil
|
||||
}
|
||||
|
||||
if s.AutoUpdate != nil && strings.EqualFold(s.AutoUpdate.Webhook, id) {
|
||||
return nil, stop
|
||||
}
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
return s, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
|
||||
// RefreshableStacks returns stacks that are configured for a periodic update
|
||||
func (service *Service) RefreshableStacks() ([]portainer.Stack, error) {
|
||||
stacks := make([]portainer.Stack, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Stack{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(*portainer.Stack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
|
||||
}
|
||||
if stack.AutoUpdate != nil && stack.AutoUpdate.Interval != "" {
|
||||
stacks = append(stacks, *stack)
|
||||
}
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
|
||||
return stacks, err
|
||||
return stacks, nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestService_StackByWebhookID(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
|
||||
}
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
b := stackBuilder{t: t, store: store}
|
||||
@@ -87,7 +87,7 @@ func Test_RefreshableStacks(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
|
||||
}
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
staticStack := portainer.Stack{ID: 1}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,11 +20,6 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
@@ -36,55 +28,26 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// Tags return an array containing all the tags.
|
||||
func (service *Service) Tags() ([]portainer.Tag, error) {
|
||||
var tags = make([]portainer.Tag, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Tag{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
tag, ok := obj.(*portainer.Tag)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Tag object")
|
||||
return nil, fmt.Errorf("Failed to convert to Tag object: %s", obj)
|
||||
}
|
||||
tags = append(tags, *tag)
|
||||
return &portainer.Tag{}, nil
|
||||
})
|
||||
|
||||
return tags, err
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// Tag returns a tag by ID.
|
||||
func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) {
|
||||
var tag portainer.Tag
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// CreateTag creates a new tag.
|
||||
func (service *Service) Create(tag *portainer.Tag) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
tag.ID = portainer.TagID(id)
|
||||
return int(tag.ID), tag
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTag updates a tag.
|
||||
func (service *Service) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTag deletes a tag.
|
||||
func (service *Service) DeleteTag(ID portainer.TagID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
@@ -26,11 +20,6 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
@@ -39,85 +28,31 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// Team returns a Team by ID
|
||||
func (service *Service) Team(ID portainer.TeamID) (*portainer.Team, error) {
|
||||
var team portainer.Team
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &team)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &team, nil
|
||||
}
|
||||
|
||||
// TeamByName returns a team by name.
|
||||
func (service *Service) TeamByName(name string) (*portainer.Team, error) {
|
||||
var t *portainer.Team
|
||||
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Team{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
team, ok := obj.(*portainer.Team)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Team object")
|
||||
return nil, fmt.Errorf("Failed to convert to Team object: %s", obj)
|
||||
}
|
||||
if strings.EqualFold(team.Name, name) {
|
||||
t = team
|
||||
return nil, stop
|
||||
}
|
||||
return &portainer.Team{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
return t, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Teams return an array containing all the teams.
|
||||
func (service *Service) Teams() ([]portainer.Team, error) {
|
||||
var teams = make([]portainer.Team, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Team{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
team, ok := obj.(*portainer.Team)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Team object")
|
||||
return nil, fmt.Errorf("Failed to convert to Team object: %s", obj)
|
||||
}
|
||||
teams = append(teams, *team)
|
||||
return &portainer.Team{}, nil
|
||||
})
|
||||
|
||||
return teams, err
|
||||
return teams, nil
|
||||
}
|
||||
|
||||
// UpdateTeam saves a Team.
|
||||
func (service *Service) UpdateTeam(ID portainer.TeamID, team *portainer.Team) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, team)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTeam creates a new Team.
|
||||
func (service *Service) Create(team *portainer.Team) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
team.ID = portainer.TeamID(id)
|
||||
return int(team.ID), team
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTeam deletes a Team.
|
||||
func (service *Service) DeleteTeam(ID portainer.TeamID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func Test_teamByName(t *testing.T) {
|
||||
t.Run("When store is empty should return ErrObjectNotFound", func(t *testing.T) {
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
_, err := store.Team().TeamByName("name")
|
||||
@@ -19,7 +19,7 @@ func Test_teamByName(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When there is no object with the same name should return ErrObjectNotFound", func(t *testing.T) {
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
teamBuilder := teamBuilder{
|
||||
@@ -35,7 +35,7 @@ func Test_teamByName(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When there is an object with the same name should return the object", func(t *testing.T) {
|
||||
_, store, teardown := datastore.MustNewTestStore(true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
teamBuilder := teamBuilder{
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package teammembership
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,11 +20,6 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
@@ -36,135 +28,48 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// TeamMembership returns a TeamMembership object by ID
|
||||
func (service *Service) TeamMembership(ID portainer.TeamMembershipID) (*portainer.TeamMembership, error) {
|
||||
var membership portainer.TeamMembership
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &membership)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &membership, nil
|
||||
}
|
||||
|
||||
// TeamMemberships return an array containing all the TeamMembership objects.
|
||||
func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
|
||||
var memberships = make([]portainer.TeamMembership, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
}
|
||||
memberships = append(memberships, *membership)
|
||||
return &portainer.TeamMembership{}, nil
|
||||
})
|
||||
|
||||
return memberships, err
|
||||
return memberships, nil
|
||||
}
|
||||
|
||||
// TeamMembershipsByUserID return an array containing all the TeamMembership objects where the specified userID is present.
|
||||
func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error) {
|
||||
var memberships = make([]portainer.TeamMembership, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
}
|
||||
if membership.UserID == userID {
|
||||
memberships = append(memberships, *membership)
|
||||
}
|
||||
return &portainer.TeamMembership{}, nil
|
||||
})
|
||||
|
||||
return memberships, err
|
||||
return memberships, nil
|
||||
}
|
||||
|
||||
// TeamMembershipsByTeamID return an array containing all the TeamMembership objects where the specified teamID is present.
|
||||
func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error) {
|
||||
var memberships = make([]portainer.TeamMembership, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
}
|
||||
if membership.TeamID == teamID {
|
||||
memberships = append(memberships, *membership)
|
||||
}
|
||||
return &portainer.TeamMembership{}, nil
|
||||
})
|
||||
|
||||
return memberships, err
|
||||
return memberships, nil
|
||||
}
|
||||
|
||||
// UpdateTeamMembership saves a TeamMembership object.
|
||||
func (service *Service) UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, membership)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTeamMembership creates a new TeamMembership object.
|
||||
func (service *Service) Create(membership *portainer.TeamMembership) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
membership.ID = portainer.TeamMembershipID(id)
|
||||
return int(membership.ID), membership
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTeamMembership deletes a TeamMembership object.
|
||||
func (service *Service) DeleteTeamMembership(ID portainer.TeamMembershipID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTeamMembershipByUserID deletes all the TeamMembership object associated to a UserID.
|
||||
func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) error {
|
||||
return service.connection.DeleteAllObjects(
|
||||
BucketName,
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
return -1, false
|
||||
}
|
||||
if membership.UserID == userID {
|
||||
return int(membership.ID), true
|
||||
}
|
||||
return -1, false
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTeamMembershipByTeamID deletes all the TeamMembership object associated to a TeamID.
|
||||
func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error {
|
||||
return service.connection.DeleteAllObjects(
|
||||
BucketName,
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
return -1, false
|
||||
}
|
||||
if membership.TeamID == teamID {
|
||||
return int(membership.ID), true
|
||||
}
|
||||
return -1, false
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,11 +21,6 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
@@ -35,15 +30,10 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
func (service *Service) Info() (*portainer.TunnelServerInfo, error) {
|
||||
var info portainer.TunnelServerInfo
|
||||
|
||||
err := service.connection.GetObject(BucketName, []byte(infoKey), &info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// UpdateInfo persists a TunnelServerInfo object.
|
||||
func (service *Service) UpdateInfo(settings *portainer.TunnelServerInfo) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(infoKey), settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
@@ -26,11 +20,6 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
@@ -39,109 +28,38 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
// User returns a user by ID
|
||||
func (service *Service) User(ID portainer.UserID) (*portainer.User, error) {
|
||||
var user portainer.User
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UserByUsername returns a user by username.
|
||||
func (service *Service) UserByUsername(username string) (*portainer.User, error) {
|
||||
var u *portainer.User
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.User{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
user, ok := obj.(*portainer.User)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
|
||||
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
|
||||
}
|
||||
if strings.EqualFold(user.Username, username) {
|
||||
u = user
|
||||
return nil, stop
|
||||
}
|
||||
return &portainer.User{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
return u, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Users return an array containing all the users.
|
||||
func (service *Service) Users() ([]portainer.User, error) {
|
||||
var users = make([]portainer.User, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.User{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
user, ok := obj.(*portainer.User)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
|
||||
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
|
||||
}
|
||||
users = append(users, *user)
|
||||
return &portainer.User{}, nil
|
||||
})
|
||||
|
||||
return users, err
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// UsersByRole return an array containing all the users with the specified role.
|
||||
func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
|
||||
var users = make([]portainer.User, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.User{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
user, ok := obj.(*portainer.User)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
|
||||
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
|
||||
}
|
||||
if user.Role == role {
|
||||
users = append(users, *user)
|
||||
}
|
||||
return &portainer.User{}, nil
|
||||
})
|
||||
|
||||
return users, err
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// UpdateUser saves a user.
|
||||
func (service *Service) UpdateUser(ID portainer.UserID, user *portainer.User) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
user.Username = strings.ToLower(user.Username)
|
||||
return service.connection.UpdateObject(BucketName, identifier, user)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateUser creates a new user.
|
||||
func (service *Service) Create(user *portainer.User) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
user.ID = portainer.UserID(id)
|
||||
user.Username = strings.ToLower(user.Username)
|
||||
|
||||
return int(user.ID), user
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUser deletes a user.
|
||||
func (service *Service) DeleteUser(ID portainer.UserID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,15 +4,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "version"
|
||||
versionKey = "DB_VERSION"
|
||||
instanceKey = "INSTANCE_ID"
|
||||
editionKey = "EDITION"
|
||||
updatingKey = "DB_UPDATING"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
)
|
||||
|
||||
// Service represents a service to manage stored versions.
|
||||
@@ -20,17 +12,8 @@ type Service struct {
|
||||
connection portainer.Connection
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
@@ -38,22 +21,24 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
|
||||
// DBVersion retrieves the stored database version.
|
||||
func (service *Service) DBVersion() (int, error) {
|
||||
var version string
|
||||
err := service.connection.GetObject(BucketName, []byte(versionKey), &version)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
db := service.connection.GetDB()
|
||||
var version models.Version
|
||||
tx := db.First(&version, `key = ?`, models.VersionKey)
|
||||
if tx.Error != nil {
|
||||
return 0, tx.Error
|
||||
}
|
||||
return strconv.Atoi(version)
|
||||
return strconv.Atoi(version.Value)
|
||||
}
|
||||
|
||||
// Edition retrieves the stored portainer edition.
|
||||
func (service *Service) Edition() (portainer.SoftwareEdition, error) {
|
||||
var edition string
|
||||
err := service.connection.GetObject(BucketName, []byte(editionKey), &edition)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
db := service.connection.GetDB()
|
||||
var version models.Version
|
||||
tx := db.First(&version, `key = ?`, models.EditionKey)
|
||||
if tx.Error != nil {
|
||||
return 0, tx.Error
|
||||
}
|
||||
e, err := strconv.Atoi(edition)
|
||||
e, err := strconv.Atoi(version.Value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -61,31 +46,54 @@ func (service *Service) Edition() (portainer.SoftwareEdition, error) {
|
||||
}
|
||||
|
||||
// StoreDBVersion store the database version.
|
||||
func (service *Service) StoreDBVersion(version int) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(versionKey), strconv.Itoa(version))
|
||||
func (service *Service) StoreDBVersion(v int) error {
|
||||
db := service.connection.GetDB()
|
||||
tx := db.Model(&models.Version{}).Where("key = ?", models.VersionKey).Update("value", strconv.FormatInt(int64(v), 10)).Limit(1)
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsUpdating retrieves the database updating status.
|
||||
func (service *Service) IsUpdating() (bool, error) {
|
||||
var isUpdating bool
|
||||
err := service.connection.GetObject(BucketName, []byte(updatingKey), &isUpdating)
|
||||
return isUpdating, err
|
||||
db := service.connection.GetDB()
|
||||
var version models.Version
|
||||
tx := db.First(&version, `key = ?`, models.UpdatingKey)
|
||||
if tx.Error != nil {
|
||||
return false, tx.Error
|
||||
}
|
||||
return version.Value == "true", nil
|
||||
}
|
||||
|
||||
// StoreIsUpdating store the database updating status.
|
||||
func (service *Service) StoreIsUpdating(isUpdating bool) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(updatingKey), isUpdating)
|
||||
db := service.connection.GetDB()
|
||||
tx := db.Model(&models.Version{}).Where("key = ?", models.UpdatingKey).Update("value", strconv.FormatBool(isUpdating)).Limit(1)
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstanceID retrieves the stored instance ID.
|
||||
func (service *Service) InstanceID() (string, error) {
|
||||
var id string
|
||||
err := service.connection.GetObject(BucketName, []byte(instanceKey), &id)
|
||||
return id, err
|
||||
db := service.connection.GetDB()
|
||||
var version models.Version
|
||||
tx := db.First(&version, `key = ?`, models.InstanceKey)
|
||||
if tx.Error != nil {
|
||||
return "", tx.Error
|
||||
}
|
||||
return version.Value, nil
|
||||
}
|
||||
|
||||
// StoreInstanceID store the instance ID.
|
||||
func (service *Service) StoreInstanceID(ID string) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(instanceKey), ID)
|
||||
db := service.connection.GetDB()
|
||||
tx := db.FirstOrCreate(&models.Version{Key: models.InstanceKey, Value: ID})
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -24,126 +20,44 @@ func (service *Service) BucketName() string {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//Webhooks returns an array of all webhooks
|
||||
// Webhooks returns an array of all webhooks
|
||||
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
|
||||
var webhooks = make([]portainer.Webhook, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Webhook{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
webhook, ok := obj.(*portainer.Webhook)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
|
||||
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
|
||||
}
|
||||
webhooks = append(webhooks, *webhook)
|
||||
return &portainer.Webhook{}, nil
|
||||
})
|
||||
|
||||
return webhooks, err
|
||||
return webhooks, nil
|
||||
}
|
||||
|
||||
// Webhook returns a webhook by ID.
|
||||
func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, error) {
|
||||
var webhook portainer.Webhook
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &webhook, nil
|
||||
}
|
||||
|
||||
// WebhookByResourceID returns a webhook by the ResourceID it is associated with.
|
||||
func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) {
|
||||
var w *portainer.Webhook
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Webhook{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
webhook, ok := obj.(*portainer.Webhook)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
|
||||
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
|
||||
}
|
||||
if webhook.ResourceID == ID {
|
||||
w = webhook
|
||||
return nil, stop
|
||||
}
|
||||
return &portainer.Webhook{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
return w, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// WebhookByToken returns a webhook by the random token it is associated with.
|
||||
func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) {
|
||||
var w *portainer.Webhook
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.Webhook{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
webhook, ok := obj.(*portainer.Webhook)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
|
||||
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
|
||||
}
|
||||
if webhook.Token == token {
|
||||
w = webhook
|
||||
return nil, stop
|
||||
}
|
||||
return &portainer.Webhook{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
return w, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeleteWebhook deletes a webhook.
|
||||
func (service *Service) DeleteWebhook(ID portainer.WebhookID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWebhook assign an ID to a new webhook and saves it.
|
||||
func (service *Service) Create(webhook *portainer.Webhook) error {
|
||||
return service.connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, interface{}) {
|
||||
webhook.ID = portainer.WebhookID(id)
|
||||
return int(webhook.ID), webhook
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateWebhook update a webhook.
|
||||
func (service *Service) UpdateWebhook(ID portainer.WebhookID, webhook *portainer.Webhook) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, webhook)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
plog "github.com/portainer/portainer/api/datastore/log"
|
||||
)
|
||||
|
||||
var backupDefaults = struct {
|
||||
backupDir string
|
||||
commonDir string
|
||||
}{
|
||||
"backups",
|
||||
"common",
|
||||
}
|
||||
|
||||
var backupLog = plog.NewScopedLog("database, backup")
|
||||
|
||||
//
|
||||
// Backup Helpers
|
||||
//
|
||||
|
||||
// createBackupFolders create initial folders for backups
|
||||
func (store *Store) createBackupFolders() {
|
||||
// create common dir
|
||||
commonDir := store.commonBackupDir()
|
||||
if exists, _ := store.fileService.FileExists(commonDir); !exists {
|
||||
if err := os.MkdirAll(commonDir, 0700); err != nil {
|
||||
backupLog.Error("Error while creating common backup folder", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (store *Store) databasePath() string {
|
||||
return path.Join(store.connection.GetStorePath(), store.connection.GetDatabaseFilename())
|
||||
}
|
||||
|
||||
func (store *Store) commonBackupDir() string {
|
||||
return path.Join(store.connection.GetStorePath(), backupDefaults.backupDir, backupDefaults.commonDir)
|
||||
}
|
||||
|
||||
func (store *Store) copyDBFile(from string, to string) error {
|
||||
backupLog.Info(fmt.Sprintf("Copying db file from %s to %s", from, to))
|
||||
err := store.fileService.Copy(from, to, true)
|
||||
if err != nil {
|
||||
backupLog.Error("Failed", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// BackupOptions provide a helper to inject backup options
|
||||
type BackupOptions struct {
|
||||
Version int // I can't find this used for anything other than a filename
|
||||
BackupDir string
|
||||
BackupFileName string
|
||||
BackupPath string
|
||||
}
|
||||
|
||||
// getBackupRestoreOptions returns options to store db at common backup dir location; used by:
|
||||
// - db backup prior to version upgrade
|
||||
// - db rollback
|
||||
func getBackupRestoreOptions(backupDir string) *BackupOptions {
|
||||
return &BackupOptions{
|
||||
BackupDir: backupDir, //connection.commonBackupDir(),
|
||||
BackupFileName: beforePortainerVersionUpgradeBackup,
|
||||
}
|
||||
}
|
||||
|
||||
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
|
||||
if options == nil {
|
||||
options = &BackupOptions{}
|
||||
}
|
||||
if options.Version == 0 {
|
||||
version, err := store.version()
|
||||
if err != nil {
|
||||
version = 0
|
||||
}
|
||||
options.Version = version
|
||||
}
|
||||
if options.BackupDir == "" {
|
||||
options.BackupDir = store.commonBackupDir()
|
||||
}
|
||||
if options.BackupFileName == "" {
|
||||
options.BackupFileName = fmt.Sprintf("%s.%s.%s", store.connection.GetDatabaseFilename(), fmt.Sprintf("%03d", options.Version), time.Now().Format("20060102150405"))
|
||||
}
|
||||
if options.BackupPath == "" {
|
||||
options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// BackupWithOptions backup current database with options
|
||||
func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
||||
backupLog.Info("creating db backup")
|
||||
store.createBackupFolders()
|
||||
|
||||
options = store.setupOptions(options)
|
||||
|
||||
return options.BackupPath, store.copyDBFile(store.databasePath(), options.BackupPath)
|
||||
}
|
||||
|
||||
// RestoreWithOptions previously saved backup for the current Edition with options
|
||||
// Restore strategies:
|
||||
// - default: restore latest from current edition
|
||||
// - restore a specific
|
||||
func (store *Store) restoreWithOptions(options *BackupOptions) error {
|
||||
options = store.setupOptions(options)
|
||||
|
||||
// Check if backup file exist before restoring
|
||||
_, err := os.Stat(options.BackupPath)
|
||||
if os.IsNotExist(err) {
|
||||
backupLog.Error(fmt.Sprintf("Backup file to restore does not exist %s", options.BackupPath), err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
backupLog.Error("Error while closing store before restore", err)
|
||||
return err
|
||||
}
|
||||
|
||||
backupLog.Info("Restoring db backup")
|
||||
err = store.copyDBFile(options.BackupPath, store.databasePath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = store.Open()
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveWithOptions removes backup database based on supplied options
|
||||
func (store *Store) removeWithOptions(options *BackupOptions) error {
|
||||
backupLog.Info("Removing db backup")
|
||||
|
||||
options = store.setupOptions(options)
|
||||
_, err := os.Stat(options.BackupPath)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
backupLog.Error(fmt.Sprintf("Backup file to remove does not exist %s", options.BackupPath), err)
|
||||
return err
|
||||
}
|
||||
|
||||
backupLog.Info(fmt.Sprintf("Removing db file at %s", options.BackupPath))
|
||||
err = os.Remove(options.BackupPath)
|
||||
if err != nil {
|
||||
backupLog.Error("Failed", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func TestCreateBackupFolders(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
connection := store.GetConnection()
|
||||
backupPath := path.Join(connection.GetStorePath(), backupDefaults.backupDir)
|
||||
|
||||
if isFileExist(backupPath) {
|
||||
t.Error("Expect backups folder to not exist")
|
||||
}
|
||||
|
||||
store.createBackupFolders()
|
||||
if !isFileExist(backupPath) {
|
||||
t.Error("Expect backups folder to exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreCreation(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
if store == nil {
|
||||
t.Error("Expect to create a store")
|
||||
}
|
||||
|
||||
if store.CheckCurrentEdition() != nil {
|
||||
t.Error("Expect to get CE Edition")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackup(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(true)
|
||||
connection := store.GetConnection()
|
||||
defer teardown()
|
||||
|
||||
t.Run("Backup should create default db backup", func(t *testing.T) {
|
||||
store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
store.backupWithOptions(nil)
|
||||
|
||||
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", fmt.Sprintf("portainer.db.%03d.*", portainer.DBVersion))
|
||||
if !isFileExist(backupFileName) {
|
||||
t.Errorf("Expect backup file to be created %s", backupFileName)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BackupWithOption should create a name specific backup at common path", func(t *testing.T) {
|
||||
store.backupWithOptions(&BackupOptions{
|
||||
BackupFileName: beforePortainerVersionUpgradeBackup,
|
||||
BackupDir: store.commonBackupDir(),
|
||||
})
|
||||
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", beforePortainerVersionUpgradeBackup)
|
||||
if !isFileExist(backupFileName) {
|
||||
t.Errorf("Expect backup file to be created %s", backupFileName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveWithOptions(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
t.Run("successfully removes file if existent", func(t *testing.T) {
|
||||
store.createBackupFolders()
|
||||
options := &BackupOptions{
|
||||
BackupDir: store.commonBackupDir(),
|
||||
BackupFileName: "test.txt",
|
||||
}
|
||||
|
||||
filePath := path.Join(options.BackupDir, options.BackupFileName)
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("file should be created; err=%s", err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
err = store.removeWithOptions(options)
|
||||
if err != nil {
|
||||
t.Errorf("RemoveWithOptions should successfully remove file; err=%w", err)
|
||||
}
|
||||
|
||||
if isFileExist(f.Name()) {
|
||||
t.Errorf("RemoveWithOptions should successfully remove file; file=%s", f.Name())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails to removes file if non-existent", func(t *testing.T) {
|
||||
options := &BackupOptions{
|
||||
BackupDir: store.commonBackupDir(),
|
||||
BackupFileName: "test.txt",
|
||||
}
|
||||
|
||||
err := store.removeWithOptions(options)
|
||||
if err == nil {
|
||||
t.Error("RemoveWithOptions should fail for non-existent file")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
portainerErrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (store *Store) version() (int, error) {
|
||||
@@ -34,6 +33,7 @@ func NewStore(storePath string, fileService portainer.FileService, connection po
|
||||
// Open opens and initializes the BoltDB database.
|
||||
func (store *Store) Open() (newStore bool, err error) {
|
||||
newStore = true
|
||||
|
||||
err = store.connection.Open()
|
||||
if err != nil {
|
||||
return newStore, err
|
||||
@@ -45,9 +45,20 @@ func (store *Store) Open() (newStore bool, err error) {
|
||||
}
|
||||
|
||||
// if we have DBVersion in the database then ensure we flag this as NOT a new store
|
||||
if _, err := store.VersionService.DBVersion(); err == nil {
|
||||
newStore = false
|
||||
}
|
||||
// version, err := store.VersionService.DBVersion()
|
||||
// if err != nil {
|
||||
// if store.IsErrObjectNotFound(err) {
|
||||
// return newStore, nil
|
||||
// }
|
||||
|
||||
// return newStore, err
|
||||
// }
|
||||
|
||||
// if version > 0 {
|
||||
// log.Debug().Int("version", version).Msg("opened existing store")
|
||||
|
||||
// return false, nil
|
||||
// }
|
||||
|
||||
return newStore, nil
|
||||
}
|
||||
@@ -56,25 +67,15 @@ func (store *Store) Close() error {
|
||||
return store.connection.Close()
|
||||
}
|
||||
|
||||
// BackupTo backs up db to a provided writer.
|
||||
// It does hot backup and doesn't block other database reads and writes
|
||||
func (store *Store) BackupTo(w io.Writer) error {
|
||||
return store.connection.BackupTo(w)
|
||||
}
|
||||
|
||||
// CheckCurrentEdition checks if current edition is community edition
|
||||
func (store *Store) CheckCurrentEdition() error {
|
||||
if store.edition() != portainer.PortainerCE {
|
||||
return errors.ErrWrongDBEdition
|
||||
return portainerErrors.ErrWrongDBEdition
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: move the use of this to dataservices.IsErrObjectNotFound()?
|
||||
func (store *Store) IsErrObjectNotFound(e error) bool {
|
||||
return e == errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
func (store *Store) Rollback(force bool) error {
|
||||
return store.connectionRollback(force)
|
||||
return e == gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
@@ -1,418 +0,0 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/chisel"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
adminUsername = "admin"
|
||||
adminPassword = "password"
|
||||
standardUsername = "standard"
|
||||
standardPassword = "password"
|
||||
agentOnDockerEnvironmentUrl = "tcp://192.168.167.207:30775"
|
||||
edgeAgentOnKubernetesEnvironmentUrl = "tcp://192.168.167.207"
|
||||
kubernetesLocalEnvironmentUrl = "https://kubernetes.default.svc"
|
||||
)
|
||||
|
||||
// TestStoreFull an eventually comprehensive set of tests for the Store.
|
||||
// The idea is what we write to the store, we should read back.
|
||||
func TestStoreFull(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
testCases := map[string]func(t *testing.T){
|
||||
"User Accounts": func(t *testing.T) {
|
||||
store.testUserAccounts(t)
|
||||
},
|
||||
"Environments": func(t *testing.T) {
|
||||
store.testEnvironments(t)
|
||||
},
|
||||
"Settings": func(t *testing.T) {
|
||||
store.testSettings(t)
|
||||
},
|
||||
"SSL Settings": func(t *testing.T) {
|
||||
store.testSSLSettings(t)
|
||||
},
|
||||
"Tunnel Server": func(t *testing.T) {
|
||||
store.testTunnelServer(t)
|
||||
},
|
||||
"Custom Templates": func(t *testing.T) {
|
||||
store.testCustomTemplates(t)
|
||||
},
|
||||
"Registries": func(t *testing.T) {
|
||||
store.testRegistries(t)
|
||||
},
|
||||
"Resource Control": func(t *testing.T) {
|
||||
store.testResourceControl(t)
|
||||
},
|
||||
"Schedules": func(t *testing.T) {
|
||||
store.testSchedules(t)
|
||||
},
|
||||
"Tags": func(t *testing.T) {
|
||||
store.testTags(t)
|
||||
},
|
||||
|
||||
// "Test Title": func(t *testing.T) {
|
||||
// },
|
||||
}
|
||||
|
||||
for name, test := range testCases {
|
||||
t.Run(name, test)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (store *Store) testEnvironments(t *testing.T) {
|
||||
id := store.CreateEndpoint(t, "local", portainer.KubernetesLocalEnvironment, "", true)
|
||||
store.CreateEndpointRelation(id)
|
||||
|
||||
id = store.CreateEndpoint(t, "agent", portainer.AgentOnDockerEnvironment, agentOnDockerEnvironmentUrl, true)
|
||||
store.CreateEndpointRelation(id)
|
||||
|
||||
id = store.CreateEndpoint(t, "edge", portainer.EdgeAgentOnKubernetesEnvironment, edgeAgentOnKubernetesEnvironmentUrl, true)
|
||||
store.CreateEndpointRelation(id)
|
||||
}
|
||||
|
||||
func newEndpoint(endpointType portainer.EndpointType, id portainer.EndpointID, name, URL string, TLS bool) *portainer.Endpoint {
|
||||
endpoint := &portainer.Endpoint{
|
||||
ID: id,
|
||||
Name: name,
|
||||
URL: URL,
|
||||
Type: endpointType,
|
||||
GroupID: portainer.EndpointGroupID(1),
|
||||
PublicURL: "",
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
TagIDs: []portainer.TagID{},
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.DockerSnapshot{},
|
||||
Kubernetes: portainer.KubernetesDefault(),
|
||||
}
|
||||
|
||||
if TLS {
|
||||
endpoint.TLSConfig = portainer.TLSConfiguration{
|
||||
TLS: true,
|
||||
TLSSkipVerify: true,
|
||||
}
|
||||
}
|
||||
|
||||
return endpoint
|
||||
}
|
||||
|
||||
func setEndpointAuthorizations(endpoint *portainer.Endpoint) {
|
||||
endpoint.SecuritySettings = portainer.EndpointSecuritySettings{
|
||||
AllowVolumeBrowserForRegularUsers: false,
|
||||
EnableHostManagementFeatures: false,
|
||||
|
||||
AllowSysctlSettingForRegularUsers: true,
|
||||
AllowBindMountsForRegularUsers: true,
|
||||
AllowPrivilegedModeForRegularUsers: true,
|
||||
AllowHostNamespaceForRegularUsers: true,
|
||||
AllowContainerCapabilitiesForRegularUsers: true,
|
||||
AllowDeviceMappingForRegularUsers: true,
|
||||
AllowStackManagementForRegularUsers: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (store *Store) CreateEndpoint(t *testing.T, name string, endpointType portainer.EndpointType, URL string, tls bool) portainer.EndpointID {
|
||||
is := assert.New(t)
|
||||
|
||||
var expectedEndpoint *portainer.Endpoint
|
||||
id := portainer.EndpointID(store.Endpoint().GetNextIdentifier())
|
||||
|
||||
switch endpointType {
|
||||
case portainer.DockerEnvironment:
|
||||
if URL == "" {
|
||||
URL = "unix:///var/run/docker.sock"
|
||||
if runtime.GOOS == "windows" {
|
||||
URL = "npipe:////./pipe/docker_engine"
|
||||
}
|
||||
}
|
||||
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
|
||||
|
||||
case portainer.AgentOnDockerEnvironment:
|
||||
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
|
||||
|
||||
case portainer.AgentOnKubernetesEnvironment:
|
||||
URL = strings.TrimPrefix(URL, "tcp://")
|
||||
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
|
||||
|
||||
case portainer.EdgeAgentOnKubernetesEnvironment:
|
||||
cs := chisel.NewService(store, nil)
|
||||
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
|
||||
edgeKey := cs.GenerateEdgeKey(URL, "", int(id))
|
||||
expectedEndpoint.EdgeKey = edgeKey
|
||||
store.testTunnelServer(t)
|
||||
|
||||
case portainer.KubernetesLocalEnvironment:
|
||||
if URL == "" {
|
||||
URL = kubernetesLocalEnvironmentUrl
|
||||
}
|
||||
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
|
||||
}
|
||||
|
||||
setEndpointAuthorizations(expectedEndpoint)
|
||||
store.Endpoint().Create(expectedEndpoint)
|
||||
|
||||
endpoint, err := store.Endpoint().Endpoint(id)
|
||||
is.NoError(err, "Endpoint() should not return an error")
|
||||
is.Equal(expectedEndpoint, endpoint, "endpoint should be the same")
|
||||
|
||||
return endpoint.ID
|
||||
}
|
||||
|
||||
func (store *Store) CreateEndpointRelation(id portainer.EndpointID) {
|
||||
relation := &portainer.EndpointRelation{
|
||||
EndpointID: id,
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
}
|
||||
|
||||
store.EndpointRelation().Create(relation)
|
||||
}
|
||||
|
||||
func (store *Store) testSSLSettings(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
ssl := &portainer.SSLSettings{
|
||||
CertPath: "/data/certs/cert.pem",
|
||||
HTTPEnabled: true,
|
||||
KeyPath: "/data/certs/key.pem",
|
||||
SelfSigned: true,
|
||||
}
|
||||
|
||||
store.SSLSettings().UpdateSettings(ssl)
|
||||
|
||||
settings, err := store.SSLSettings().Settings()
|
||||
is.NoError(err, "Get sslsettings should succeed")
|
||||
is.Equal(ssl, settings, "Stored SSLSettings should be the same as what is read out")
|
||||
}
|
||||
|
||||
func (store *Store) testTunnelServer(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
expectPrivateKeySeed := uniuri.NewLen(16)
|
||||
|
||||
err := store.TunnelServer().UpdateInfo(&portainer.TunnelServerInfo{PrivateKeySeed: expectPrivateKeySeed})
|
||||
is.NoError(err, "UpdateInfo should have succeeded")
|
||||
|
||||
serverInfo, err := store.TunnelServer().Info()
|
||||
is.NoError(err, "Info should have succeeded")
|
||||
|
||||
is.Equal(expectPrivateKeySeed, serverInfo.PrivateKeySeed, "hashed passwords should not differ")
|
||||
}
|
||||
|
||||
// add users, read them back and check the details are unchanged
|
||||
func (store *Store) testUserAccounts(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
err := store.createAccount(adminUsername, adminPassword, portainer.AdministratorRole)
|
||||
is.NoError(err, "CreateAccount should succeed")
|
||||
store.checkAccount(adminUsername, adminPassword, portainer.AdministratorRole)
|
||||
is.NoError(err, "Account failure")
|
||||
|
||||
err = store.createAccount(standardUsername, standardPassword, portainer.StandardUserRole)
|
||||
is.NoError(err, "CreateAccount should succeed")
|
||||
store.checkAccount(standardUsername, standardPassword, portainer.StandardUserRole)
|
||||
is.NoError(err, "Account failure")
|
||||
}
|
||||
|
||||
// create an account with the provided details
|
||||
func (store *Store) createAccount(username, password string, role portainer.UserRole) error {
|
||||
var err error
|
||||
user := &portainer.User{Username: username, Role: role}
|
||||
|
||||
// encrypt the password
|
||||
cs := &crypto.Service{}
|
||||
user.Password, err = cs.Hash(password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.User().Create(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) checkAccount(username, expectPassword string, expectRole portainer.UserRole) error {
|
||||
// Read the account for username. Check password and role is what we expect
|
||||
|
||||
user, err := store.User().UserByUsername(username)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to find user")
|
||||
}
|
||||
|
||||
if user.Username != username || user.Role != expectRole {
|
||||
return fmt.Errorf("%s user details do not match", user.Username)
|
||||
}
|
||||
|
||||
// Check the password
|
||||
cs := &crypto.Service{}
|
||||
expectPasswordHash, err := cs.Hash(expectPassword)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "hash failed")
|
||||
}
|
||||
|
||||
if user.Password != expectPasswordHash {
|
||||
return fmt.Errorf("%s user password hash failure", user.Username)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) testSettings(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
// since many settings are default and basically nil, I'm going to update some and read them back
|
||||
expectedSettings, err := store.Settings().Settings()
|
||||
is.NoError(err, "Settings() should not return an error")
|
||||
expectedSettings.TemplatesURL = "http://portainer.io/application-templates"
|
||||
expectedSettings.HelmRepositoryURL = "http://portainer.io/helm-repository"
|
||||
expectedSettings.EdgeAgentCheckinInterval = 60
|
||||
expectedSettings.AuthenticationMethod = portainer.AuthenticationLDAP
|
||||
expectedSettings.LDAPSettings = portainer.LDAPSettings{
|
||||
AnonymousMode: true,
|
||||
StartTLS: true,
|
||||
AutoCreateUsers: true,
|
||||
Password: "random",
|
||||
}
|
||||
expectedSettings.SnapshotInterval = "10m"
|
||||
|
||||
err = store.Settings().UpdateSettings(expectedSettings)
|
||||
is.NoError(err, "UpdateSettings() should succeed")
|
||||
|
||||
settings, err := store.Settings().Settings()
|
||||
is.NoError(err, "Settings() should not return an error")
|
||||
is.Equal(expectedSettings, settings, "stored settings should match")
|
||||
}
|
||||
|
||||
func (store *Store) testCustomTemplates(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
customTemplate := store.CustomTemplate()
|
||||
is.NotNil(customTemplate, "customTemplate Service shouldn't be nil")
|
||||
|
||||
expectedTemplate := &portainer.CustomTemplate{
|
||||
ID: portainer.CustomTemplateID(customTemplate.GetNextIdentifier()),
|
||||
Title: "Custom Title",
|
||||
Description: "Custom Template Description",
|
||||
ProjectPath: "/data/custom_template/1",
|
||||
Note: "A note about this custom template",
|
||||
EntryPoint: "docker-compose.yaml",
|
||||
CreatedByUserID: 10,
|
||||
}
|
||||
|
||||
customTemplate.Create(expectedTemplate)
|
||||
|
||||
actualTemplate, err := customTemplate.CustomTemplate(expectedTemplate.ID)
|
||||
is.NoError(err, "CustomTemplate should not return an error")
|
||||
is.Equal(expectedTemplate, actualTemplate, "expected and actual template do not match")
|
||||
}
|
||||
|
||||
func (store *Store) testRegistries(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
regService := store.RegistryService
|
||||
is.NotNil(regService, "RegistryService shouldn't be nil")
|
||||
|
||||
reg1 := &portainer.Registry{
|
||||
ID: 1,
|
||||
Type: portainer.DockerHubRegistry,
|
||||
Name: "Dockerhub Registry Test",
|
||||
}
|
||||
|
||||
reg2 := &portainer.Registry{
|
||||
ID: 2,
|
||||
Type: portainer.GitlabRegistry,
|
||||
Name: "Gitlab Registry Test",
|
||||
Gitlab: portainer.GitlabRegistryData{
|
||||
ProjectID: 12345,
|
||||
InstanceURL: "http://gitlab.com/12345",
|
||||
ProjectPath: "mytestproject",
|
||||
},
|
||||
}
|
||||
|
||||
err := regService.Create(reg1)
|
||||
is.NoError(err)
|
||||
|
||||
err = regService.Create(reg2)
|
||||
is.NoError(err)
|
||||
|
||||
actualReg1, err := regService.Registry(reg1.ID)
|
||||
is.NoError(err)
|
||||
is.Equal(reg1, actualReg1, "registries differ")
|
||||
|
||||
actualReg2, err := regService.Registry(reg2.ID)
|
||||
is.NoError(err)
|
||||
is.Equal(reg2, actualReg2, "registries differ")
|
||||
}
|
||||
|
||||
func (store *Store) testResourceControl(t *testing.T) {
|
||||
// is := assert.New(t)
|
||||
// resControl := store.ResourceControl()
|
||||
// ctrl := &portainer.ResourceControl{
|
||||
// }
|
||||
// resControl().Create()
|
||||
}
|
||||
|
||||
func (store *Store) testSchedules(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
schedule := store.ScheduleService
|
||||
s := &portainer.Schedule{
|
||||
ID: portainer.ScheduleID(schedule.GetNextIdentifier()),
|
||||
Name: "My Custom Schedule 1",
|
||||
CronExpression: "*/5 * * * * portainer /bin/sh -c echo 'hello world'",
|
||||
}
|
||||
|
||||
err := schedule.CreateSchedule(s)
|
||||
is.NoError(err, "CreateSchedule should succeed")
|
||||
|
||||
actual, err := schedule.Schedule(s.ID)
|
||||
is.NoError(err, "schedule should be found")
|
||||
is.Equal(s, actual, "schedules differ")
|
||||
}
|
||||
|
||||
func (store *Store) testTags(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
tags := store.TagService
|
||||
|
||||
tag1 := &portainer.Tag{
|
||||
ID: 1,
|
||||
Name: "Tag 1",
|
||||
}
|
||||
|
||||
tag2 := &portainer.Tag{
|
||||
ID: 2,
|
||||
Name: "Tag 1",
|
||||
}
|
||||
|
||||
err := tags.Create(tag1)
|
||||
is.NoError(err, "Tags.Create should succeed")
|
||||
|
||||
err = tags.Create(tag2)
|
||||
is.NoError(err, "Tags.Create should succeed")
|
||||
|
||||
actual, err := tags.Tag(tag1.ID)
|
||||
is.NoError(err, "tag1 should be found")
|
||||
is.Equal(tag1, actual, "tags differ")
|
||||
|
||||
actual, err = tags.Tag(tag2.ID)
|
||||
is.NoError(err, "tag2 should be found")
|
||||
is.Equal(tag2, actual, "tags differ")
|
||||
}
|
||||
@@ -1,47 +1,108 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
)
|
||||
|
||||
// Init creates the default data set.
|
||||
func (store *Store) Init() error {
|
||||
instanceID, err := store.VersionService.InstanceID()
|
||||
conn := store.GetConnection()
|
||||
err := conn.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.initVersions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// err = store.checkOrCreateDefaultSettings()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// err = store.checkOrCreateDefaultSSLSettings()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return store.checkOrCreateDefaultData()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) initVersions() error {
|
||||
_, err := store.VersionService.InstanceID()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
uid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instanceID = uid.String()
|
||||
err = store.VersionService.StoreInstanceID(instanceID)
|
||||
if err != nil {
|
||||
return err
|
||||
instanceID := uid.String()
|
||||
|
||||
db := store.connection.GetDB()
|
||||
|
||||
fmt.Println("updating instance id")
|
||||
tx := db.Create(&models.Version{Key: models.InstanceKey, Value: instanceID})
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
} else if err != nil {
|
||||
|
||||
fmt.Printf("updating version id %s\n", models.VersionKey)
|
||||
tx = db.Create(&models.Version{Key: models.VersionKey, Value: fmt.Sprint(portainer.DBVersion)})
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
fmt.Println("updating updating id")
|
||||
tx = db.Create(&models.Version{Key: models.UpdatingKey, Value: "false"})
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
fmt.Println("updating edition id")
|
||||
tx = db.Create(&models.Version{Key: models.EditionKey, Value: fmt.Sprint(portainer.PortainerCE)})
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) checkOrCreateDefaultSettings() error {
|
||||
// TODO: these need to also be applied when importing
|
||||
settings, err := store.SettingsService.Settings()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
defaultSettings := &portainer.Settings{
|
||||
EnableTelemetry: false,
|
||||
AuthenticationMethod: portainer.AuthenticationInternal,
|
||||
BlackListedLabels: make([]portainer.Pair, 0),
|
||||
InternalAuthSettings: portainer.InternalAuthSettings{
|
||||
RequiredPasswordLength: 12,
|
||||
},
|
||||
LDAPSettings: portainer.LDAPSettings{
|
||||
AnonymousMode: true,
|
||||
AutoCreateUsers: true,
|
||||
TLSConfig: portainer.TLSConfiguration{},
|
||||
SearchSettings: []portainer.LDAPSearchSettings{
|
||||
portainer.LDAPSearchSettings{},
|
||||
{},
|
||||
},
|
||||
GroupSearchSettings: []portainer.LDAPGroupSearchSettings{
|
||||
portainer.LDAPGroupSearchSettings{},
|
||||
{},
|
||||
},
|
||||
},
|
||||
OAuthSettings: portainer.OAuthSettings{},
|
||||
|
||||
OAuthSettings: portainer.OAuthSettings{
|
||||
SSO: true,
|
||||
},
|
||||
SnapshotInterval: portainer.DefaultSnapshotInterval,
|
||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||
TemplatesURL: portainer.DefaultTemplatesURL,
|
||||
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
|
||||
@@ -50,35 +111,33 @@ func (store *Store) Init() error {
|
||||
KubectlShellImage: portainer.DefaultKubectlShellImage,
|
||||
}
|
||||
|
||||
err = store.SettingsService.UpdateSettings(defaultSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return store.SettingsService.UpdateSettings(defaultSettings)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
} else if err == nil {
|
||||
if settings.UserSessionTimeout == "" {
|
||||
settings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||
store.Settings().UpdateSettings(settings)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = store.SSLSettings().Settings()
|
||||
if err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if settings.UserSessionTimeout == "" {
|
||||
settings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||
return store.Settings().UpdateSettings(settings)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) checkOrCreateDefaultSSLSettings() error {
|
||||
_, err := store.SSLSettings().Settings()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
defaultSSLSettings := &portainer.SSLSettings{
|
||||
HTTPEnabled: true,
|
||||
}
|
||||
|
||||
err = store.SSLSettings().UpdateSettings(defaultSSLSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return store.SSLSettings().UpdateSettings(defaultSSLSettings)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *Store) checkOrCreateDefaultData() error {
|
||||
groups, err := store.EndpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -99,6 +158,5 @@ func (store *Store) Init() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
INFO = "INFO"
|
||||
ERROR = "ERROR"
|
||||
DEBUG = "DEBUG"
|
||||
FATAL = "FATAL"
|
||||
)
|
||||
|
||||
type ScopedLog struct {
|
||||
scope string
|
||||
}
|
||||
|
||||
func NewScopedLog(scope string) *ScopedLog {
|
||||
return &ScopedLog{scope: scope}
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) print(kind string, message string) {
|
||||
log.Printf("[%s] [%s] %s", kind, slog.scope, message)
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) Debug(message string) {
|
||||
slog.print(DEBUG, fmt.Sprintf("[message: %s]", message))
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) Info(message string) {
|
||||
slog.print(INFO, fmt.Sprintf("[message: %s]", message))
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) Error(message string, err error) {
|
||||
slog.print(ERROR, fmt.Sprintf("[message: %s] [error: %s]", message, err))
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) NotImplemented(method string) {
|
||||
log.Fatalf("[%s] [%s] [%s]", FATAL, slog.scope, fmt.Sprintf("%s is not yet implemented", method))
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
plog "github.com/portainer/portainer/api/datastore/log"
|
||||
"github.com/portainer/portainer/api/datastore/migrator"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
|
||||
werrors "github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
|
||||
|
||||
var migrateLog = plog.NewScopedLog("database, migrate")
|
||||
|
||||
func (store *Store) MigrateData() error {
|
||||
version, err := store.version()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migratorParams := &migrator.MigratorParameters{
|
||||
DatabaseVersion: version,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointRelationService: store.EndpointRelationService,
|
||||
ExtensionService: store.ExtensionService,
|
||||
RegistryService: store.RegistryService,
|
||||
ResourceControlService: store.ResourceControlService,
|
||||
RoleService: store.RoleService,
|
||||
ScheduleService: store.ScheduleService,
|
||||
SettingsService: store.SettingsService,
|
||||
StackService: store.StackService,
|
||||
TagService: store.TagService,
|
||||
TeamMembershipService: store.TeamMembershipService,
|
||||
UserService: store.UserService,
|
||||
VersionService: store.VersionService,
|
||||
FileService: store.fileService,
|
||||
DockerhubService: store.DockerHubService,
|
||||
AuthorizationService: authorization.NewService(store),
|
||||
}
|
||||
|
||||
return store.connectionMigrateData(migratorParams)
|
||||
}
|
||||
|
||||
// FailSafeMigrate backup and restore DB if migration fail
|
||||
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
store.Rollback(true)
|
||||
// return error with cause and stacktrace (recover() doesn't include a stacktrace)
|
||||
err = fmt.Errorf("%v %s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
// !Important: we must use a named return value in the function definition and not a local
|
||||
// !variable referenced from the closure or else the return value will be incorrectly set
|
||||
return migrator.Migrate()
|
||||
}
|
||||
|
||||
// MigrateData automatically migrate the data based on the DBVersion.
|
||||
// This process is only triggered on an existing database, not if the database was just created.
|
||||
// if force is true, then migrate regardless.
|
||||
func (store *Store) connectionMigrateData(migratorParams *migrator.MigratorParameters) error {
|
||||
migrator := migrator.NewMigrator(migratorParams)
|
||||
|
||||
// backup db file before upgrading DB to support rollback
|
||||
isUpdating, err := migratorParams.VersionService.IsUpdating()
|
||||
if err != nil && err != errors.ErrObjectNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isUpdating && migrator.Version() != portainer.DBVersion {
|
||||
err = store.backupVersion(migrator)
|
||||
if err != nil {
|
||||
return werrors.Wrapf(err, "failed to backup database")
|
||||
}
|
||||
}
|
||||
|
||||
if migrator.Version() < portainer.DBVersion {
|
||||
migrateLog.Info(fmt.Sprintf("Migrating database from version %v to %v.\n", migrator.Version(), portainer.DBVersion))
|
||||
err = store.FailSafeMigrate(migrator)
|
||||
if err != nil {
|
||||
migrateLog.Error("An error occurred during database migration", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// backupVersion will backup the database or panic if any errors occur
|
||||
func (store *Store) backupVersion(migrator *migrator.Migrator) error {
|
||||
migrateLog.Info("Backing up database prior to version upgrade...")
|
||||
|
||||
options := getBackupRestoreOptions(store.commonBackupDir())
|
||||
|
||||
_, err := store.backupWithOptions(options)
|
||||
if err != nil {
|
||||
migrateLog.Error("An error occurred during database backup", err)
|
||||
removalErr := store.removeWithOptions(options)
|
||||
if removalErr != nil {
|
||||
migrateLog.Error("An error occurred during store removal prior to backup", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rollback to a pre-upgrade backup copy/snapshot of portainer.db
|
||||
func (store *Store) connectionRollback(force bool) error {
|
||||
|
||||
if !force {
|
||||
confirmed, err := cli.Confirm("Are you sure you want to rollback your database to the previous backup?")
|
||||
if err != nil || !confirmed {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
options := getBackupRestoreOptions(store.commonBackupDir())
|
||||
|
||||
err := store.restoreWithOptions(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.connection.Close()
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// testVersion is a helper which tests current store version against wanted version
|
||||
func testVersion(store *Store, versionWant int, t *testing.T) {
|
||||
v, err := store.VersionService.DBVersion()
|
||||
if err != nil {
|
||||
t.Errorf("Expect store version to be %d but was %d with error: %s", versionWant, v, err)
|
||||
}
|
||||
if v != versionWant {
|
||||
t.Errorf("Expect store version to be %d but was %d", versionWant, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateData(t *testing.T) {
|
||||
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
|
||||
newStore, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
if !newStore {
|
||||
t.Error("Expect a new DB")
|
||||
}
|
||||
|
||||
// not called for new stores
|
||||
//store.MigrateData()
|
||||
|
||||
testVersion(store, portainer.DBVersion, t)
|
||||
store.Close()
|
||||
|
||||
newStore, _ = store.Open()
|
||||
if newStore {
|
||||
t.Error("Expect store to NOT be new DB")
|
||||
}
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
version int
|
||||
expectedVersion int
|
||||
}{
|
||||
{version: 17, expectedVersion: portainer.DBVersion},
|
||||
{version: 21, expectedVersion: portainer.DBVersion},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
_, store, teardown := MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
// Setup data
|
||||
store.VersionService.StoreDBVersion(tc.version)
|
||||
|
||||
// Required roles by migrations 22.2
|
||||
store.RoleService.Create(&portainer.Role{ID: 1})
|
||||
store.RoleService.Create(&portainer.Role{ID: 2})
|
||||
store.RoleService.Create(&portainer.Role{ID: 3})
|
||||
store.RoleService.Create(&portainer.Role{ID: 4})
|
||||
|
||||
t.Run(fmt.Sprintf("MigrateData for version %d", tc.version), func(t *testing.T) {
|
||||
store.MigrateData()
|
||||
testVersion(store, tc.expectedVersion, t)
|
||||
})
|
||||
|
||||
t.Run(fmt.Sprintf("Restoring DB after migrateData for version %d", tc.version), func(t *testing.T) {
|
||||
store.Rollback(true)
|
||||
store.Open()
|
||||
testVersion(store, tc.version, t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
version := 17
|
||||
store.VersionService.StoreDBVersion(version)
|
||||
|
||||
store.MigrateData()
|
||||
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
|
||||
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
store.VersionService.StoreDBVersion(0)
|
||||
|
||||
store.MigrateData()
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
if !isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should exist; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
store.VersionService.StoreIsUpdating(true)
|
||||
|
||||
store.MigrateData()
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
if isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
store.MigrateData()
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
if isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_getBackupRestoreOptions(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
options := getBackupRestoreOptions(store.commonBackupDir())
|
||||
|
||||
wantDir := store.commonBackupDir()
|
||||
if !strings.HasSuffix(options.BackupDir, wantDir) {
|
||||
log.Fatalf("incorrect backup dir; got=%s, want=%s", options.BackupDir, wantDir)
|
||||
}
|
||||
|
||||
wantFilename := "portainer.db.bak"
|
||||
if options.BackupFileName != wantFilename {
|
||||
log.Fatalf("incorrect backup file; got=%s, want=%s", options.BackupFileName, wantFilename)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollback(t *testing.T) {
|
||||
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
|
||||
version := 21
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
store.VersionService.StoreDBVersion(version)
|
||||
|
||||
_, err := store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Change the current edition
|
||||
err = store.VersionService.StoreDBVersion(version + 10)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = store.Rollback(true)
|
||||
if err != nil {
|
||||
t.Logf("Rollback failed: %s", err)
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
store.Open()
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
}
|
||||
|
||||
// isFileExist is helper function to check for file existence
|
||||
func isFileExist(path string) bool {
|
||||
matches, err := filepath.Glob(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(matches) > 0
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrator"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
const dummyLogoURL = "example.com"
|
||||
|
||||
// initTestingDBConn creates a settings service with raw database DB connection
|
||||
// for unit testing usage only since using NewStore will cause cycle import inside migrator pkg
|
||||
func initTestingSettingsService(dbConn portainer.Connection, preSetObj map[string]interface{}) error {
|
||||
//insert a obj
|
||||
if err := dbConn.UpdateObject("settings", []byte("SETTINGS"), preSetObj); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setup(store *Store) error {
|
||||
var err error
|
||||
dummySettingsObj := map[string]interface{}{
|
||||
"LogoURL": dummyLogoURL,
|
||||
}
|
||||
err = initTestingSettingsService(store.connection, dummySettingsObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMigrateSettings(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
err := setup(store)
|
||||
if err != nil {
|
||||
t.Errorf("failed to complete testing setups, err: %v", err)
|
||||
}
|
||||
|
||||
updatedSettings, err := store.SettingsService.Settings()
|
||||
// SO -basically, this test _isn't_ testing migration, its testing golang type defaults.
|
||||
if updatedSettings.LogoURL != dummyLogoURL { // ensure a pre-migrate setting isn't unset
|
||||
t.Errorf("unexpected value changes in the updated settings, want LogoURL value: %s, got LogoURL value: %s", dummyLogoURL, updatedSettings.LogoURL)
|
||||
}
|
||||
if updatedSettings.OAuthSettings.SSO != false { // I recon golang defaulting will make this false
|
||||
t.Errorf("unexpected default OAuth SSO setting, want: false, got: %t", updatedSettings.OAuthSettings.SSO)
|
||||
}
|
||||
if updatedSettings.OAuthSettings.LogoutURI != "" {
|
||||
t.Errorf("unexpected default OAuth HideInternalAuth setting, want:, got: %s", updatedSettings.OAuthSettings.LogoutURI)
|
||||
}
|
||||
|
||||
m := migrator.NewMigrator(&migrator.MigratorParameters{
|
||||
DatabaseVersion: 29,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointRelationService: store.EndpointRelationService,
|
||||
ExtensionService: store.ExtensionService,
|
||||
RegistryService: store.RegistryService,
|
||||
ResourceControlService: store.ResourceControlService,
|
||||
RoleService: store.RoleService,
|
||||
ScheduleService: store.ScheduleService,
|
||||
SettingsService: store.SettingsService,
|
||||
StackService: store.StackService,
|
||||
TagService: store.TagService,
|
||||
TeamMembershipService: store.TeamMembershipService,
|
||||
UserService: store.UserService,
|
||||
VersionService: store.VersionService,
|
||||
FileService: store.fileService,
|
||||
DockerhubService: store.DockerHubService,
|
||||
AuthorizationService: authorization.NewService(store),
|
||||
})
|
||||
if err := m.MigrateSettingsToDB30(); err != nil {
|
||||
t.Errorf("failed to update settings: %v", err)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("failed to retrieve the updated settings: %v", err)
|
||||
}
|
||||
if updatedSettings.LogoURL != dummyLogoURL {
|
||||
t.Errorf("unexpected value changes in the updated settings, want LogoURL value: %s, got LogoURL value: %s", dummyLogoURL, updatedSettings.LogoURL)
|
||||
}
|
||||
if updatedSettings.OAuthSettings.SSO != false {
|
||||
t.Errorf("unexpected default OAuth SSO setting, want: false, got: %t", updatedSettings.OAuthSettings.SSO)
|
||||
}
|
||||
if updatedSettings.OAuthSettings.LogoutURI != "" {
|
||||
t.Errorf("unexpected default OAuth HideInternalAuth setting, want:, got: %s", updatedSettings.OAuthSettings.LogoutURI)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore/migrator"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrateStackEntryPoint(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
stackService := store.Stack()
|
||||
|
||||
stacks := []*portainer.Stack{
|
||||
{
|
||||
ID: 1,
|
||||
EntryPoint: "dir/sub/compose.yml",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
EntryPoint: "dir/sub/compose.yml",
|
||||
GitConfig: &gittypes.RepoConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range stacks {
|
||||
err := stackService.Create(s)
|
||||
assert.NoError(t, err, "failed to create stack")
|
||||
}
|
||||
|
||||
s, err := stackService.Stack(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, s.GitConfig, "first stack should not have git config")
|
||||
|
||||
s, err = stackService.Stack(2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", s.GitConfig.ConfigFilePath, "not migrated yet migrated")
|
||||
|
||||
err = migrator.MigrateStackEntryPoint(stackService)
|
||||
assert.NoError(t, err, "failed to migrate entry point to Git ConfigFilePath")
|
||||
|
||||
s, err = stackService.Stack(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, s.GitConfig, "first stack should not have git config")
|
||||
|
||||
s, err = stackService.Stack(2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "dir/sub/compose.yml", s.GitConfig.ConfigFilePath, "second stack should have config file path migrated")
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
werrors "github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func migrationError(err error, context string) error {
|
||||
return werrors.Wrap(err, "failed in "+context)
|
||||
}
|
||||
|
||||
// Migrate checks the database version and migrate the existing data to the most recent data model.
|
||||
func (m *Migrator) Migrate() error {
|
||||
// set DB to updating status
|
||||
err := m.versionService.StoreIsUpdating(true)
|
||||
if err != nil {
|
||||
return migrationError(err, "StoreIsUpdating")
|
||||
}
|
||||
|
||||
if m.currentDBVersion < 17 {
|
||||
return migrationError(err, "migrating from less than Portainer 1.21.0 is not supported, please contact Portainer support.")
|
||||
}
|
||||
|
||||
// Portainer 1.21.0
|
||||
if m.currentDBVersion < 18 {
|
||||
err := m.updateUsersToDBVersion18()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateUsersToDBVersion18")
|
||||
}
|
||||
|
||||
err = m.updateEndpointsToDBVersion18()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToDBVersion18")
|
||||
}
|
||||
|
||||
err = m.updateEndpointGroupsToDBVersion18()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointGroupsToDBVersion18")
|
||||
}
|
||||
|
||||
err = m.updateRegistriesToDBVersion18()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateRegistriesToDBVersion18")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.22.0
|
||||
if m.currentDBVersion < 19 {
|
||||
err := m.updateSettingsToDBVersion19()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDBVersion19")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.22.1
|
||||
if m.currentDBVersion < 20 {
|
||||
err := m.updateUsersToDBVersion20()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateUsersToDBVersion20")
|
||||
}
|
||||
|
||||
err = m.updateSettingsToDBVersion20()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDBVersion20")
|
||||
}
|
||||
|
||||
err = m.updateSchedulesToDBVersion20()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSchedulesToDBVersion20")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.23.0
|
||||
// DBVersion 21 is missing as it was shipped as via hotfix 1.22.2
|
||||
if m.currentDBVersion < 22 {
|
||||
err := m.updateResourceControlsToDBVersion22()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateResourceControlsToDBVersion22")
|
||||
}
|
||||
|
||||
err = m.updateUsersAndRolesToDBVersion22()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateUsersAndRolesToDBVersion22")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.0
|
||||
if m.currentDBVersion < 23 {
|
||||
migrateLog.Info("Migrating to DB 23")
|
||||
err := m.updateTagsToDBVersion23()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateTagsToDBVersion23")
|
||||
}
|
||||
|
||||
err = m.updateEndpointsAndEndpointGroupsToDBVersion23()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsAndEndpointGroupsToDBVersion23")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.1
|
||||
if m.currentDBVersion < 24 {
|
||||
migrateLog.Info("Migrating to DB 24")
|
||||
err := m.updateSettingsToDB24()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDB24")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.0.0
|
||||
if m.currentDBVersion < 25 {
|
||||
migrateLog.Info("Migrating to DB 25")
|
||||
err := m.updateSettingsToDB25()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDB25")
|
||||
}
|
||||
|
||||
err = m.updateStacksToDB24()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateStacksToDB24")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.1.0
|
||||
if m.currentDBVersion < 26 {
|
||||
migrateLog.Info("Migrating to DB 26")
|
||||
err := m.updateEndpointSettingsToDB25()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointSettingsToDB25")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.2.0
|
||||
if m.currentDBVersion < 27 {
|
||||
migrateLog.Info("Migrating to DB 27")
|
||||
err := m.updateStackResourceControlToDB27()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateStackResourceControlToDB27")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.6.0
|
||||
if m.currentDBVersion < 30 {
|
||||
migrateLog.Info("Migrating to DB 30")
|
||||
err := m.migrateDBVersionToDB30()
|
||||
if err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB30")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.9.0
|
||||
if m.currentDBVersion < 32 {
|
||||
err := m.migrateDBVersionToDB32()
|
||||
if err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB32")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.9.1, 2.9.2
|
||||
if m.currentDBVersion < 33 {
|
||||
migrateLog.Info("Migrating to DB 33")
|
||||
err := m.migrateDBVersionToDB33()
|
||||
if err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB33")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.10
|
||||
if m.currentDBVersion < 34 {
|
||||
migrateLog.Info("Migrating to DB 34")
|
||||
if err := m.migrateDBVersionToDB34(); err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB34")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.9.3 (yep out of order, but 2.10 is EE only)
|
||||
if m.currentDBVersion < 35 {
|
||||
migrateLog.Info("Migrating to DB 35")
|
||||
if err := m.migrateDBVersionToDB35(); err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB35")
|
||||
}
|
||||
}
|
||||
|
||||
err = m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
if err != nil {
|
||||
return migrationError(err, "StoreDBVersion")
|
||||
}
|
||||
migrateLog.Info(fmt.Sprintf("Updated DB version to %d", portainer.DBVersion))
|
||||
|
||||
// reset DB updating status
|
||||
return m.versionService.StoreIsUpdating(false)
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateUsersToDBVersion18() error {
|
||||
legacyUsers, err := m.userService.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range legacyUsers {
|
||||
user.PortainerAuthorizations = map[portainer.Authorization]bool{
|
||||
portainer.OperationPortainerDockerHubInspect: true,
|
||||
portainer.OperationPortainerEndpointGroupList: true,
|
||||
portainer.OperationPortainerEndpointList: true,
|
||||
portainer.OperationPortainerEndpointInspect: true,
|
||||
portainer.OperationPortainerEndpointExtensionAdd: true,
|
||||
portainer.OperationPortainerEndpointExtensionRemove: true,
|
||||
portainer.OperationPortainerExtensionList: true,
|
||||
portainer.OperationPortainerMOTD: true,
|
||||
portainer.OperationPortainerRegistryList: true,
|
||||
portainer.OperationPortainerRegistryInspect: true,
|
||||
portainer.OperationPortainerTeamList: true,
|
||||
portainer.OperationPortainerTemplateList: true,
|
||||
portainer.OperationPortainerTemplateInspect: true,
|
||||
portainer.OperationPortainerUserList: true,
|
||||
portainer.OperationPortainerUserMemberships: true,
|
||||
}
|
||||
|
||||
err = m.userService.UpdateUser(user.ID, &user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointsToDBVersion18() error {
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range endpoint.AuthorizedUsers {
|
||||
endpoint.UserAccessPolicies[userID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range endpoint.AuthorizedTeams {
|
||||
endpoint.TeamAccessPolicies[teamID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
|
||||
legacyEndpointGroups, err := m.endpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpointGroup := range legacyEndpointGroups {
|
||||
endpointGroup.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range endpointGroup.AuthorizedUsers {
|
||||
endpointGroup.UserAccessPolicies[userID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
endpointGroup.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range endpointGroup.AuthorizedTeams {
|
||||
endpointGroup.TeamAccessPolicies[teamID] = portainer.AccessPolicy{
|
||||
RoleID: 4,
|
||||
}
|
||||
}
|
||||
|
||||
err = m.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateRegistriesToDBVersion18() error {
|
||||
legacyRegistries, err := m.registryService.Registries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, registry := range legacyRegistries {
|
||||
registry.UserAccessPolicies = make(portainer.UserAccessPolicies)
|
||||
for _, userID := range registry.AuthorizedUsers {
|
||||
registry.UserAccessPolicies[userID] = portainer.AccessPolicy{}
|
||||
}
|
||||
|
||||
registry.TeamAccessPolicies = make(portainer.TeamAccessPolicies)
|
||||
for _, teamID := range registry.AuthorizedTeams {
|
||||
registry.TeamAccessPolicies[teamID] = portainer.AccessPolicy{}
|
||||
}
|
||||
|
||||
err = m.registryService.UpdateRegistry(registry.ID, ®istry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion19() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if legacySettings.EdgeAgentCheckinInterval == 0 {
|
||||
legacySettings.EdgeAgentCheckinInterval = portainer.DefaultEdgeAgentCheckinIntervalInSeconds
|
||||
}
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user