Compare commits

...

222 Commits

Author SHA1 Message Date
Ali
04156af50f fix(texttip): fix texttip in edge groups [EE-4990] (#8442)
Some checks failed
Test / test-client (push) Has been cancelled
Co-authored-by: testa113 <testa113>
2023-02-03 13:39:48 +13:00
Chaim Lev-Ari
ea6ec14451 fix(home): show tooltip for disabled envs [EE-4859] (#8284) 2023-02-02 18:24:11 -03:00
LP B
7d52427c5b fix(app/edge): updater/rollback calendar visual issues (#8385) 2023-02-02 12:30:27 -03:00
LP B
fadd71e7b9 fix(api/edgestack): update deployments count when env relations are updated (#8434) 2023-02-02 12:05:13 -03:00
Oscar Zhou
194f65107a fix(schedule): update date picker after removing edge group [EE-4963] (#8419) 2023-02-02 11:15:10 +13:00
cmeng
a491d89789 fix(update): prevent formik reinitialize [EE-4962] (#8425) 2023-02-02 09:59:23 +13:00
Ali
6bfbc3c338 fix(texttip): fix texttip placement [EE-4990] (#8428)
Co-authored-by: testa113 <testa113>
2023-02-01 20:34:08 +13:00
Chaim Lev-Ari
76072d11b6 fix(home): reduce update text for small screens [EE-4936] (#8421) 2023-02-01 12:14:56 +05:30
andres-portainer
1000d57cd2 fix(edgestacks): fix edge stacks cache invalidation EE-4909 (#8400) 2023-02-01 02:15:31 -03:00
cmeng
2216d2cdd2 fix(edge/stack): not clear stack status if version not updated [EE-4970] (#8409) 2023-02-01 09:18:12 +13:00
matias-portainer
70c9172112 fix(doc): update endpoint creation swagger documentation EE-4925 (#8416) 2023-01-31 11:06:19 -03:00
Ali
043ec008fe apply changes from EE (#8406)
Co-authored-by: testa113 <testa113>
2023-01-31 10:03:30 +13:00
Dakota Walsh
a3b8b0d58e fix(kube): 30 second delay to storage detection EE-4822 (#8361) 2023-01-31 09:58:50 +13:00
cmeng
fc09e5574f fix(edge/job): init endpoints if it is null [EE-4972] (#8410) 2023-01-27 22:08:07 +13:00
cmeng
1dcdce9feb fix(settings): EE-4959 Cannot turn on Edge Compute Features on CE (#8397) 2023-01-27 17:04:48 +13:00
Chaim Lev-Ari
12ea9fa404 fix(system/update): submit license form [EE-4743] (#8382) 2023-01-26 20:35:02 +05:30
cmeng
38f09fc8ad fix(UI): EE-4937 low resolution hides add container button (#8402) 2023-01-26 17:40:30 +13:00
Ali
c1c4dd190d chore(kompose): remove from settings EE-4741 (#8376) 2023-01-26 16:03:32 +13:00
Chaim Lev-Ari
674a98e432 refactor(auth): cache user data [EE-4935] (#8379) 2023-01-26 07:40:07 +05:30
matias-portainer
da7351cec7 fix(endpoints): check environment type before start metrics detection EE-4944 (#8392) 2023-01-25 10:29:16 -03:00
Chaim Lev-Ari
c9562d1252 fix(edge/update): remove schedule date for old envs [EE-3023] (#8314) 2023-01-24 12:20:59 +05:30
Prabhat Khera
cae257ddfc fix(kubernetes): detect metrics API for kubernetes endspoints EE-4865 (#8352) 2023-01-24 09:05:22 +13:00
LP B
7a063cb2fa chore(dependencies): upgrade msw to fix xmldom CVE (#8363)
* chore(dependencies): upgrade msw to fix xmldom CVE

* refactor(msw): rename msw DefaultRequestBody to DefaultBodyType
2023-01-23 14:03:02 +01:00
Chaim Lev-Ari
d2c967282e fix(edge/group): show tag selector when no tags [EE-4924] (#8367) 2023-01-23 11:05:25 +05:30
Ali
a7cad6fd09 fix(namespace): move warning [EE-4885] (#8371)
Co-authored-by: testa113 <testa113>
2023-01-23 10:41:43 +13:00
Prabhat Khera
d11ae40822 fix(ui): tooltip link color EE-4914 (#8364) 2023-01-23 10:15:41 +13:00
Chaim Lev-Ari
503ef6b415 fix(settings): save only the existing values [EE-4903] (#8325) 2023-01-23 02:34:41 +05:30
cmeng
b8b69ce116 fix(home): EE-4874 homepage ui issues (#8320) 2023-01-23 09:14:13 +13:00
LP B
54fb7242c3 fix(home): dont display disconnected status similar to disabled (#8312) 2023-01-20 17:34:09 +01:00
Chaim Lev-Ari
2887c11c93 feat(system/upgrade): add get license dialog [EE-4743] (#8278) 2023-01-19 15:31:51 +02:00
Chaim Lev-Ari
00b64beed7 fix(home): dark mode for edit buttons [EE-4828] (#8270) 2023-01-19 10:16:30 +02:00
cmeng
1199fe19ec fix(home): EE-4906 home page tiles for edge devices have the wrong url (#8354) 2023-01-19 09:00:52 +13:00
Ali
6d0f473f76 fix(ingress): update label [EE-4902] (#8331)
Co-authored-by: testa113 <testa113>
2023-01-18 13:30:04 +13:00
Ali
6b804152fe fix(app): update redeploy wording [EE-4889] (#8318)
Co-authored-by: testa113 <testa113>
2023-01-18 08:30:00 +13:00
matias-portainer
9e2849dd10 fix(edgejobs): fix edge jobs log collection EE-4893 (#8329) 2023-01-17 14:21:03 -03:00
matias-portainer
eeff16a300 fix(edgejobs): remove endpoint from edge job mapping on endpoint deletion EE-4764 (#8279) 2023-01-17 09:47:14 -03:00
Chaim Lev-Ari
de05fa869d fix(ldap): sync user teams when needed [EE-4802] (#8269) 2023-01-16 10:41:41 +02:00
Chaim Lev-Ari
81ae01d3a2 fix(home): enable kubeconfig button [EE-4833] (#8277) 2023-01-15 11:32:42 +02:00
cmeng
4e0d264bc4 fix(UI) EE-4883 stack repository method console error (#8305)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2023-01-13 16:08:12 +13:00
Chaim Lev-Ari
3f2220e340 fix(home): link to correct env route [EE-4877] (#8296) 2023-01-12 15:02:40 +02:00
Chaim Lev-Ari
b67c28f128 fix(home): allow non-admins [EE-4873] (#8298) 2023-01-12 15:01:54 +02:00
Chaim Lev-Ari
3a3ec0c50c feat(home): remove margins from env list [EE-4868] (#8286) 2023-01-11 10:59:58 +02:00
Chaim Lev-Ari
26f975f1d5 fix(sidebar): put behind modal [EE-4866] (#8281)
* fix(sidebar): put behind modal

* fix(system/upgrade): validate on open dialog
2023-01-11 08:31:57 +13:00
Chaim Lev-Ari
00f72a80f2 feat(environments): add edge device [EE-4840] (#8271)
* feat(environments): add edge device [EE-4840]

fix [EE-4840]

* fix(home): fix tests
2023-01-11 08:30:45 +13:00
Dakota Walsh
fc7d226f38 fix(padding): fix margin-right on DatatableFooter pagination EE-4831 (#8274) 2023-01-10 10:14:56 +13:00
Matt Hook
995579fda7 fix(rolling-restart): wording and icon changes [EE-4834] (#8275)
* grammar, wording and icon changes

* edit icon

* Rename be-only-button to be-teaser-button
2023-01-10 10:01:57 +13:00
Ali
0fd59eb6a8 fix(rbac): fix false negative rbac result in github microk8s environments [EE-4829] 2023-01-09 17:55:38 +13:00
andres-portainer
dd0d1737b0 fix(performance): optimize performance for edge EE-3311 (#8040) 2023-01-06 16:25:41 -03:00
andres-portainer
3d28a6f877 chore(edgejobs): clean up EE-4850 (#8259) 2023-01-03 13:12:12 -03:00
andres-portainer
2fc518f221 chore(edgestacks): clean up EE-4851 (#8260) 2023-01-03 10:49:29 -03:00
andres-portainer
137ce37096 chore(nil): remove unnecessary nil checks EE-4847 (#8254) 2022-12-30 15:26:46 -03:00
andres-portainer
e529327851 chore(utils): remove dead code EE-4846 (#8253) 2022-12-30 14:52:58 -03:00
andres-portainer
3625ab6faa fix(rand): seed the RNG EE-4845 (#8252) 2022-12-30 14:52:18 -03:00
Chaim Lev-Ari
afb024d2a4 chore(docs): fix api reference [EE-4835] (#8242) 2022-12-25 10:47:21 +02:00
andres-portainer
b2bc4b92d6 fix(ui): remove extra margins from Dockerfile details section EE-4790 (#8213) 2022-12-21 19:09:40 -03:00
Chaim Lev-Ari
e5fd0c9595 fix(system): ignore failure to connect to docker [EE-4825] (#8231) 2022-12-21 18:08:18 +02:00
Chaim Lev-Ari
649c1c9cee feat(system): check BE image existence before upgrade [EE-4071] (#8230) 2022-12-21 17:17:51 +02:00
cmeng
919a854d93 feat(edge): EE-4570 allow pre-pull images with edge stack deployment (#8210)
Co-authored-by: Matt Hook <hookenz@gmail.com>
2022-12-21 13:18:51 +13:00
Chaim Lev-Ari
7fe0712b61 feat(home): move edge device to view [EE-4559] (#8189)
Co-authored-by: matias.spinarolli <matias.spinarolli@portainer.io>
2022-12-21 10:07:34 +13:00
Chaim Lev-Ari
b4a6f6911c fix(home): wrap items on small screens [EE-4798] (#8220) 2022-12-20 10:57:59 +02:00
Chaim Lev-Ari
59d35d26d8 feat(home): open env on click [EE-4792] (#8219) 2022-12-20 10:57:25 +02:00
Dakota Walsh
95558ed4ad fix(EE-4782): add portainer internal label to created ingress rules (#8196) 2022-12-20 16:46:51 +13:00
matias-portainer
e1b474d04f feat(edgejobs): support edge groups when using edge jobs EE-3873 (#8099) 2022-12-19 18:54:51 -03:00
Matt Hook
9732d1b5d8 remove kubeshell pod constraint (#8185) 2022-12-20 10:32:49 +13:00
Chaim Lev-Ari
701410d259 feat(app): clear env when log out [EE-4791] (#8218) 2022-12-19 08:56:39 +02:00
Chaim Lev-Ari
123754cee7 feat(system): make ui fixes to upgrade dialog [EE-4071] (#8199)
* feat(system): make ui fixes to upgrade dialog

* fix(system): hide on non-enabled platforms
2022-12-18 19:57:41 +02:00
Chaim Lev-Ari
d75d2ba9ce fix(docker): fix links to docs [EE-4486] (#8166) 2022-12-18 10:13:18 +02:00
Dakota Walsh
046738c967 feat(kubernetes): cluster setup reasonable defaults EE-4518 (#8082) 2022-12-16 16:03:40 +13:00
Matt Hook
0436be7bc4 update pull latest image wording (#8191) 2022-12-16 14:57:07 +13:00
cmeng
94d64997cc fix(compose): EE-4777 Toast Error displays insufficient details when creating stack in Standalone (#8208) 2022-12-16 14:43:24 +13:00
Chaim Lev-Ari
294d1668d4 feat(edge/updates): sync code changes from EE [EE-3023] (#8186)
* feat(edge/updates): add info about each field

* feat(edge/update): add beta feature

* fix(edge/update): add info
2022-12-16 14:20:22 +13:00
Chaim Lev-Ari
4bd6618fb9 chore(deps): upgrade docker binary [EE-4545] (#8146)
closes [EE-4545]
upgrades docker to v20.10.21

Co-authored-by: Yi Chen <69284638+yi-portainer@users.noreply.github.com>
2022-12-16 14:07:21 +13:00
Chaim Lev-Ari
62197a67f7 chore(deps): upgrade docker compose [EE-4642] (#8147)
closes [EE-4642]
upgrades docker compose to v2.13.0
2022-12-16 14:05:14 +13:00
Matt Hook
c1dc1b49d1 fix(pagination): vert center pagination control on all screens and add consistent spacing [EE-4006] (#8192) 2022-12-16 10:55:54 +13:00
andres-portainer
b917e12b62 fix(fileservice): add missing interface EE-3458 (#8207) 2022-12-15 17:21:58 -03:00
andres-portainer
a8ccd2b153 feat(filestore): add function to save mTLS certificates (#8206) 2022-12-15 16:16:29 -03:00
LP B
68975620c5 fix(app): cursor jumps to the end of inputs [EE-4359] (#8163)
* feat(app/react): introduce controlled inputs HOC to avoid creating uncontrolled React components

* fix(app/env): jumping inputs when adding gpus to existing environment
2022-12-15 17:49:36 +01:00
LP B
67d3abcc9d fix(app/analytics): exclude login page in analytics (#8172) 2022-12-15 17:48:59 +01:00
matias-portainer
90b0cb84f4 fix(wording): change wording on CE teaser EE-4590 (#8136) 2022-12-15 11:25:13 -03:00
Oscar Zhou
b22cdb3559 fix(access/team): team member cannot change ownership to its own team [EE-4423] (#8052) 2022-12-15 21:27:18 +13:00
andres-portainer
37896661d6 fix(edgestacks): avoid a data race in edge stack status update endpoint EE-4737 (#8168) 2022-12-14 10:41:45 -03:00
Chaim Lev-Ari
f38b8234d9 fix(edge/groups): show tags for environments [EE-4422] (#7940) 2022-12-14 09:17:49 +02:00
Chaim Lev-Ari
52e150fa29 fix(notifications): break lines for long message [EE-4521] (#8110) 2022-12-14 09:17:12 +02:00
Matt Hook
929749c0da incorrect variable name used (#8198) 2022-12-14 17:49:20 +13:00
Ali
09bf5d03f4 give docker specific tooltip link (#8197)
Co-authored-by: testa113 <testa113>
2022-12-14 17:26:19 +13:00
Ali
ac6f52ab76 fix(gitapp): set manifest in git settings [EE-4734] (#8190) 2022-12-14 14:30:15 +13:00
Dakota Walsh
0ddcad66f3 fix(auth): invalidate session when permissions change EE-3320 (#8103) 2022-12-14 10:12:00 +13:00
Chaim Lev-Ari
930d9e5628 feat(edge/stacks): use namespace in manifest [EE-4507] (#8145) 2022-12-13 22:56:47 +02:00
Chaim Lev-Ari
8936ae9b7a feat(home): add connect and browse buttons [EE-4182] (#8175) 2022-12-13 22:56:09 +02:00
Chaim Lev-Ari
db9d87c918 feat(system): path to upgrade swarm to BE [EE-4624] (#8124) 2022-12-13 22:52:06 +02:00
matias-portainer
b59a0ba823 fix(settings): fix settings save/update EE-3604 (#8180) 2022-12-12 15:11:12 -03:00
Matt Hook
2188005b48 feat(kubernetes): add rolling restart button teaser [EE-4510] (#8126)
* rolling restart teaser button

* add be only rolling restart

* move position of button
2022-12-12 14:30:05 +13:00
Prabhat Khera
a1528475ba feat(UI): tooltip html message support and width fix [EE-3445] (#8165)
Co-authored-by: testa113 <testa113>
2022-12-12 14:03:50 +13:00
Chaim Lev-Ari
5cbf52377d feat(system): path to upgrade standalone to BE [EE-4071] (#8095) 2022-12-11 08:58:22 +02:00
andres-portainer
756ac034ec fix(go): add Go workspace EE-4763 (#8182) 2022-12-09 13:23:01 -03:00
Oscar Zhou
1008afd1fe fix(session): reset the environment session storage after deleting it (#8119) 2022-12-09 11:11:24 +13:00
Ali
563ead85cc fix(gitoptions): git app edit ui tweaks [EE-4584] (#8159) 2022-12-09 10:41:11 +13:00
Chaim Lev-Ari
eba5879ec8 feat(home): change layout of env tile [EE-4479] (#8061) 2022-12-07 16:51:20 +02:00
matias-portainer
b48aa1274d fix(host): fix host info request EE-4641 (#8169) 2022-12-07 10:48:42 -03:00
Matt Hook
3e485c3152 feat(api): remove golang experimental packages [EE-3648] (#8081)
* remove golang experimental packages

* rebase and fix references
2022-12-07 17:15:52 +13:00
Oscar Zhou
dffd45c5f9 refactor(ui): extract TagButton from TagSelector component [EE-4194] (#8127) 2022-12-07 16:15:09 +13:00
Ali
c1cc8bad77 feat(rbac): detect if rbac is enabled [EE-4308] (#8139) 2022-12-07 15:53:06 +13:00
matias-portainer
8dcc5e4adb fix(edgestacks): fix repository edge stack creation EE-4732 (#8160) 2022-12-06 10:26:18 -03:00
Matt Hook
4558ce84cf fix(header): removed red dot from help menu [EE-4586] (#8133)
* remove red dot from help menu

* fix cursor, add menu-icon class back
2022-12-06 10:24:38 +13:00
Ali
adc87b8f8e feat(deployment): PO feedback [EE-4416] (#8143) 2022-12-06 08:49:26 +13:00
Chaim Lev-Ari
ce8455953e chore(deps): upgrade k8s client [EE-4543] (#8019) 2022-12-05 09:58:55 +02:00
Prabhat Khera
a61b18dd93 feat(kubernetes): fix wordings for ingresses toggle EE-2647 (#8151) 2022-12-05 10:47:56 +13:00
Matt Hook
d6a3fe23e9 feat(libhelm) update missed package paths [EE-4650] (#8134)
* add missing pkg paths

* fix go tests

* fixed pkg paths
2022-12-05 10:38:16 +13:00
Prabhat Khera
cbaba43842 fix(ui): tooltip stays open on hover [EE-3445] (#8051) 2022-12-05 09:47:43 +13:00
Matt Hook
c173888b64 remove old digest library and update path (#8141) 2022-12-02 10:46:59 +13:00
Chaim Lev-Ari
82e9e2a895 refactor(edge/updates): sync changes from EE [EE-4288] (#7726) 2022-12-01 08:40:52 +02:00
Matt Hook
4fee359247 feat(libhelm) import libhelm into CE pkg directory [EE-4650] (#8138)
* import libhelm and update some paths

* remove file

* update readme
2022-12-01 14:27:49 +13:00
Ali
9cdc0da615 feat(kompose): hide kompose [EE-4562] (#8084) 2022-12-01 13:46:23 +13:00
Matt Hook
8fd0efa34f add third_party digest lib to top level (#8140) 2022-12-01 13:26:39 +13:00
Matt Hook
79bfd8f6fe fix package import for docker/distribution (#8132) 2022-12-01 09:43:51 +13:00
Ali
2114c15f55 refactor(data-cy): duplicate data cy [EE-4656] (#8131) 2022-11-30 16:54:28 +13:00
Matt Hook
d2f6d1e415 import libhelm into portainer (#8128) 2022-11-30 14:25:47 +13:00
Matt Hook
241440a474 fix(fdo): import deleted digest library [EE-4654] (#8129)
* import digest lib

* update references

* fix lint errors
2022-11-30 09:11:49 +13:00
LP B
2e19f4ea6d fix(docker/container): auto select private access when enabling UAC on public container edit (#8032) [EE-44-64] 2022-11-29 11:24:13 +01:00
Matt Hook
95bc508462 fix(migrator): fix setting version struct fields after migration [EE-4613] (#8090)
* fix setting version struct fields

* fix go tests

* remove versionUpdateRequired

* remove old code that was originally for debugging purposes
2022-11-28 19:28:10 +13:00
Ali
d78b762f7b refactor(icons): replace fa icons [EE-4459] (#7907)
refactor(icons): remove fontawesome EE-4459

refactor(icon) replace feather with lucide EE-4472
2022-11-28 15:00:28 +13:00
andres-portainer
9dfac98a26 fix(tags): add missing error handling EE-4622 (#8102) 2022-11-24 14:16:04 -03:00
andres-portainer
e26a607d28 fix(edgegroups): avoid a last-write-wins situation when updating edge groups concurrently EE-3732 (#8101) 2022-11-23 21:36:17 -03:00
Dakota Walsh
6dc1841c14 fix(kube): disable namespace resource quota by default EE-4421 (#8080) 2022-11-23 15:01:41 +13:00
andres-portainer
c28be7aced fix(token-cache-manager): refactor to avoid data races EE-4438 (#8094) 2022-11-22 18:31:14 -03:00
Prabhat Khera
dd01165224 show secrets in kube applications datatable (#8065) 2022-11-23 08:48:36 +13:00
Chaim Lev-Ari
d484a0eb64 fix(docker): remove word break in details [EE-4481] (#7996) 2022-11-22 15:00:55 +02:00
Chaim Lev-Ari
fe8e834dbf refactor(ui/datatables): migrate views to use datatable component [EE-4064] (#7609) 2022-11-22 14:16:34 +02:00
Prabhat Khera
0f0513c684 feat(kubernetes): edit yaml support EE-2855 (#8016) 2022-11-22 09:40:44 +13:00
Chaim Lev-Ari
7006c17ce4 fix(wizard): debounce name state [EE-4177] (#8042)
move debouncing to the component (from the validation).
debounce returns undefined when it's not calling the debounced function,
and undefined is considered a validation error.
2022-11-21 19:33:08 +02:00
Chaim Lev-Ari
253a3a2b40 fix(ui): remove loading of missing interceptors [EE-4604] (#8086) 2022-11-21 17:25:09 +02:00
Chaim Lev-Ari
1e4c4e2616 refactor(edge): move edge codebase to react (#7781) 2022-11-21 09:51:55 +02:00
Chaim Lev-Ari
75f40fe485 refactor(portainer): remove offline mode [EE-4348] (#7761)
Co-authored-by: deviantony <anthony.lapenna@portainer.io>
2022-11-21 09:50:47 +02:00
Ali
61e8e68c31 fix(svg): fix footer height [EE-4547] (#8071) 2022-11-21 09:22:41 +13:00
Matt Hook
583346321e feat(version): migrate version to semver [EE-3756] (#7693)
redisigned version bucket and migration code
2022-11-18 13:18:09 +13:00
cmeng
4cfa584c7c fix(git): EE-4577 Git Repository Fields are Missing in Edge Stacks (#8057) 2022-11-18 08:59:58 +13:00
Ali
d012a4efc4 feat(deployment): enforce deployment options EE-4416 (#7974) 2022-11-17 22:00:34 +13:00
Oscar Zhou
e0f3a8c0a2 fix(access/viwer): update the viwer filter key to user.id (#8055) 2022-11-17 10:38:34 +13:00
Yi Chen
bb48ab00cb * remove empty examples (#7952)
* fix missing type
2022-11-17 06:55:08 +13:00
Chaim Lev-Ari
eccc8131dd feat(system/upgrade): add upgrade banner [EE-4564] (#8046) 2022-11-16 18:38:39 +02:00
matias-portainer
c21921a08d fix(edgestacks): return 400 instead of 500 on edge stack create when an invalid payload is provided EE-4429 (#7880) 2022-11-15 17:40:56 -03:00
Prabhat Khera
573e05d1c7 feat(kubernetes): BE teasure added EE-2647 (#7927) 2022-11-15 20:57:01 +13:00
Prabhat Khera
246e351817 remove wrong checked-in files from parent dir (#8048) 2022-11-15 09:01:28 +13:00
Prabhat Khera
6775c7b6ec clean database models directory (#8026) 2022-11-14 08:33:57 +13:00
Chaim Lev-Ari
881e99df53 refactor(nomad): sync frontend with EE [EE-3353] (#7758) 2022-11-13 12:29:25 +02:00
Chaim Lev-Ari
78dcba614d refactor(portainer): move to react [EE-3350] (#7915) 2022-11-13 10:10:18 +02:00
matias-portainer
30e23ea5b4 fix(ui): tidy up Edge Devices configuration EE-3604 (#7797) 2022-11-10 16:13:20 -03:00
Oscar Zhou
e1e81731b9 fix(setting/auth): allow to save all auth method if custom ldap server is empty string (#7990) 2022-11-10 08:28:09 +13:00
Oscar Zhou
16377221f9 fix(stack): check if endpoint exists before checking the user permission on the endpoint (#7967) 2022-11-09 12:20:19 +13:00
Chaim Lev-Ari
a0237852ef fix: fix occurred typo [EE-4536] (#7994) 2022-11-08 07:30:37 +02:00
andres-portainer
193e0c7d6f fix(snapshots): remove snapshots when removing endpoints EE-4527 (#7973)
* fix(snapshots): remove snapshots when removing endpoints EE-4527

* Fix nil pointer dereference.

Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2022-11-07 20:28:18 -03:00
Chaim Lev-Ari
77c29ff87e refactor(kubernetes): move react codebase [EE-3349] (#7953) 2022-11-07 08:03:11 +02:00
Chaim Lev-Ari
2868da296a fix(stack): validate original containers names [EE-4520] (#7978) 2022-11-06 10:40:33 +02:00
Chaim Lev-Ari
ff10588383 fix(edge): show select all checkbox [EE-4129] (#7948) 2022-11-06 09:33:22 +02:00
LP B
6b02d9a1e3 fix(app/logs): change pattern to detect double serialized JSON logs [EE-4525] (#7962)
* fix(app/logs): change pattern to detect double serialized JSON logs

* fix(app/logs): fallback to raw display when parsing fails + include timestamp for Zerolog logs
2022-11-04 13:58:18 +01:00
Ali
9f3d5185b0 fix(slider): use and update react slider EE-4522 (#7987) 2022-11-04 14:12:53 +13:00
congs
f94147b07b fix(registry): EE-4526 Registry Manage access link broken (#7975) 2022-11-04 12:10:42 +13:00
andres-portainer
49d02e0386 fix(db): remove a bucket overwrite EE-4424 (#7867) 2022-11-03 18:51:15 -03:00
Ali
e82d0cfbdb fix(apps): fix edit error EE-4529 (#7963) 2022-11-03 13:04:56 +13:00
Oscar Zhou
c8051b68d4 fix(auth/ldap): allow to save internal authentication if custom ldap server is not set [EE-3155] (#7959) 2022-11-03 10:06:56 +13:00
Chaim Lev-Ari
37d4a80769 refactor(environments): remove endpoints cache [DTD-100] (#6408) 2022-11-02 13:29:26 +02:00
Chaim Lev-Ari
9ef2e27aae fix(environments): debounce name validation [EE-4177] (#7889) 2022-11-02 12:44:31 +02:00
Oscar Zhou
9e1f80cf37 chore(ui/ldap): add texttip for group search configuration (#7944) 2022-11-02 11:31:44 +13:00
Dakota Walsh
459c95169a fix(ingresses): migrate to new allow/disallow format EE-4465 (#7893) 2022-11-02 11:17:32 +13:00
fhanportainer
5048f08b5f fix(box-selector): fixed incorrect wording for Private Box selector under UAC. (#7949) 2022-11-01 11:06:11 +13:00
fhanportainer
e785d1572e fix(web-editor): fixed web editor scroll bar. (#7941) 2022-10-31 11:03:50 +13:00
Rex Wang
95a4f83466 fix(docker): docker template UI bug fix [EE-4034] (#7912)
* EE-4034 fix(docker): docker template UI bug fix

* EE-4034 fix(docker): fix ui
2022-10-30 14:56:23 +08:00
Dmitry Salakhov
4edf232e41 fix: document edge endoint url requirement (#7735) [EE-3425] 2022-10-28 13:00:12 +13:00
Dmitry Salakhov
903cf284e7 fix(image): build image from file (#7929) [EE-4501] 2022-10-27 23:31:31 +13:00
Prabhat Khera
a550bfaedb fix showing namespaces for standard user (#7917) 2022-10-27 16:14:54 +13:00
Hao
446febb0f6 fix(image): hide button issues [EE-4166] (#7845)
* fix(image): hide button issues [EE-4166]
2022-10-25 15:02:59 +08:00
Oscar Zhou
cb9fe2606c fix(team): disable team leader setting when external auth sync is enabled [EE-3579] (#7852) 2022-10-25 14:39:24 +13:00
Dakota Walsh
55211ef00e fix(ingress): allow none controller type EE-4420 (#7883)
Co-authored-by: testA113 <alex.harris@portainer.io>
2022-10-25 09:41:30 +13:00
Chaim Lev-Ari
e48ceb15e9 refactor(environments): move environments ts code to react [EE-3443] (#7747) 2022-10-23 09:53:25 +03:00
Rex Wang
1b12cc9f31 EE-4376 fix(docker): fix malformed struct of template (#7803) 2022-10-21 16:29:18 +08:00
Hao
0365ed8e70 fix(docker): comfirm modal for removing secrets/networks/configs [EE-4211] (#7882)
* fix(ui): comfirm modal for removing secrets/networks/configs [EE-4211]

* fix(ui): comfirm modal for removing secrets/networks/configs [EE-4211]

* fix(ui): comfirm modal for removing secrets/networks/configs [EE-4211]
2022-10-21 16:03:41 +08:00
Chaim Lev-Ari
7624ff10ee chore(edge): add aria-label for edge-group selector [EE-4466] (#7896)
* chore(edge): add aria-label for edge-group selector

* style(edge): remove comment
2022-10-21 08:22:49 +03:00
andres-portainer
535a26412f fix(logging): default to pretty logging [EE-4371] (#7847)
* fix(logging): default to pretty logging EE-4371

* feat(app/logs): prettify stack traces in JSON logs

* feat(nomad/logs): prettify JSON logs in log viewer

* feat(kubernetes/logs): prettigy JSON logs in log viewers

* feat(app/logs): format and color zerolog prettified logs

* fix(app/logs): pre-parse logs when they are double serialized

Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2022-10-20 16:33:54 +02:00
Chaim Lev-Ari
ee5600b6af chore(build): incremental ts build [EE-4204] (#7888) 2022-10-20 17:23:56 +03:00
Rex Wang
3f51d077ac fix(docker): create custom template [EE-4114] (#7812)
* EE-4114 fix(docker): create custom template

* Update customtemplate_create.go

remove white space
2022-10-20 16:42:49 +08:00
Rex Wang
0219d41ba7 fix(docker): Show stopped container on dashboard [EE-4327] (#7833)
* EE-4327 fix(docker): Show stopped container on dashboard

* Update ContainerStatus.tsx

remove comment

* EE-4327 fix(docker): show stopped container on dashboard
2022-10-20 15:10:39 +08:00
Prabhat Khera
f3e2ccd487 fix volume claims with k8s app (#7899) 2022-10-20 15:43:19 +13:00
Ali
368e6b2a44 fix(helm): helm charts view bad icon aspect ratio EE-4451 (#7875) 2022-10-20 14:14:55 +13:00
Dmitry Salakhov
1100a2bd28 feat: move jwt lib to v4 (#7773) 2022-10-20 10:26:11 +13:00
itsconquest
16dc66f173 fix(UAC): put team into resource control when editing as team lead [EE-4457] (#7886)
* fix(UAC): put team into resource control when editing as team lead [EE-4457]

* populate form values & payload correctly
2022-10-20 10:18:56 +13:00
itsconquest
c1f94be9b2 fix(notifications): sort by newest first by default [EE-4467] (#7891) 2022-10-19 15:25:20 +13:00
Matt Hook
58947fee69 fix(libhelm): new libhelm with relaxed validation when adding chart repo [EE-4440] (#7874)
update helm to fix adding some chart repos
2022-10-19 12:43:33 +13:00
Dakota Walsh
0c995ae1c8 fix(kubernetes): create proxied kubeclient EE-4326 (#7850) 2022-10-18 10:46:27 +13:00
itsconquest
f6d6be90e4 fix(UAC): provide required UI context [EE-4415] (#7854) 2022-10-18 09:45:47 +13:00
andres-portainer
5488389278 fix(code): replace calls to ioutil EE-4425 (#7878) 2022-10-17 15:29:12 -03:00
andres-portainer
69f498c431 fix(tests): add missing context cancel EE-4433 (#7879) 2022-10-17 13:57:41 -03:00
Prabhat Khera
669327da7c fix reloading page when ing class disallowed (#7830) 2022-10-17 10:44:17 +13:00
andres-portainer
191f8e17ee fix(code): remove unused code EE-4431 (#7866) 2022-10-14 19:42:31 -03:00
andres-portainer
ae2bec4bd9 fix(code): clean up EE-4432 (#7865) 2022-10-14 18:09:07 -03:00
andres-portainer
367f3dd6d4 fix(tags): remove a data race EE-4310 (#7862) 2022-10-13 11:12:12 -03:00
matias-portainer
8f1ac38963 fix(tags): get tags when loading associated endpoints selector EE-4140 (#7857) 2022-10-13 11:03:19 -03:00
Ali
7a6ff10268 fix(ing): nodeport validate and show errors [EE-4373] (#7801) 2022-10-12 10:06:57 +13:00
andres-portainer
fd91de3571 fix(logging): remove remaining traces of logrus EE-4414 (#7848) 2022-10-11 16:53:27 -03:00
andres-portainer
ab3a6f402e fix(build): add -trimpath EE-4406 (#7836) 2022-10-11 13:00:50 -03:00
andres-portainer
d3edb7ebd5 fix(logging): convert missing cases to Zerolog EE-4400 (#7817) 2022-10-11 12:59:00 -03:00
Oscar Zhou
c23b8b2816 fix(gitops): update the git ref cache key from url to url and pat (#7841) 2022-10-11 18:31:21 +13:00
itsconquest
724f1f63b7 fix(notifications): cleanup notifications code [EE-4274] (#7790)
* fix(notifications): cleanup notifications code [EE-4274]

* break long words
2022-10-11 14:05:53 +13:00
Ali
c6ae8467c0 fix(ingress): update ingress tls after deletion EE-4387 (#7804)
* fix(ing): update tls value EE-4387
2022-10-10 09:32:30 +13:00
Ali
56087bcbb3 fix(clustersetup): dont show modal when loading (#7810) 2022-10-08 17:48:36 +13:00
Ali
315c1c7e1e fix(application): edit cluster ip services EE-4328 (#7775) 2022-10-07 16:55:11 +13:00
congs
819dc4d561 fix(UI): EE-4381 environment ID is shown instead of its name when deleting an environment (#7808) 2022-10-07 16:36:19 +13:00
congs
380a64d546 fix(wizard): EE-4350 Environment creating script should only showed for relevant type of environment (#7786) 2022-10-07 15:43:06 +13:00
congs
6429546462 fix(help): EE-4335 context sensitive help improvement (#7754) 2022-10-07 14:25:26 +13:00
matias-portainer
ebfb71da05 fix(edge): fix docker proxy EE-4380 (#7799) 2022-10-06 11:12:39 -03:00
Ali
ae0b9b1e30 fix(ingress): ingress indicate missing services EE-4358 (#7794) 2022-10-06 15:24:59 +13:00
Oscar Zhou
e9de484c3e refactor(stack): stack build process backend only [EE-4342] (#7750) 2022-10-05 22:33:59 +13:00
Prabhat Khera
83a1ce9d2a bug(ingress): fix ingress class disallowed to not found issue EE-4311 (#7731)
Some checks failed
Test / test-client (push) Has been cancelled
2022-10-05 15:17:53 +13:00
Rex Wang
66fd039933 EE-2681 fix(docker): fix message format (#7784) 2022-10-05 09:20:00 +08:00
Ali
1722257d68 fix(cluster): fix cluster setup no ingress develop EE-4352 (#7776)
* fix(cluster) update cluster wo controllers EE-4352

* fix(ing): stop errors in ns EE-4352
2022-10-04 12:13:56 +13:00
Ali
7d8b037761 fix(deploy): update option text EE-4362 (#7783) 2022-10-04 10:20:16 +13:00
Ali
cd52e04a5a fix(customtemplate) fix custom var payload EE-4340 (#7752) 2022-10-03 09:49:28 +13:00
Xuing
a0fa64781a fix(readme) update deploy portainer url (#7760) 2022-09-30 14:47:47 +13:00
Ali
43e3cb476b fix(clustersetup): set a default access mode (#7745) 2022-09-29 10:26:25 +13:00
Ali
a1a88eb5e4 fix(secrets): allow edit sa token, refactor (#7732) 2022-09-29 09:57:39 +13:00
andres-portainer
cb79dc18f8 chore(code): reduce divergence with EE EE-4344 (#7748) 2022-09-28 14:56:32 -03:00
andres-portainer
e9384a6987 fix(database): fix a data race around the bucket sequences EE-4332 (#7738) 2022-09-27 16:14:00 -03:00
Dmitry Salakhov
90a0e6fe35 bump release version (#7733) 2022-09-27 13:55:20 +13:00
1609 changed files with 26369 additions and 16817 deletions

View File

@@ -83,6 +83,7 @@ overrides:
'newlines-between': 'always',
},
]
no-plusplus: off
func-style: [error, 'declaration']
import/prefer-default-export: off
no-use-before-define: ['error', { functions: false }]
@@ -99,12 +100,17 @@ overrides:
'@typescript-eslint/explicit-module-boundary-types': off
'@typescript-eslint/no-unused-vars': 'error'
'@typescript-eslint/no-explicit-any': 'error'
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either' }]
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either', controlComponents: ['Input', 'Checkbox'] }]
'react/function-component-definition': ['error', { 'namedComponents': 'function-declaration' }]
'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' }]]
'regex/invalid': ['error', [{ 'regex': '<Icon icon="(.*)"', 'message': 'Please directly import the `lucide-react` icon instead of using the string' }]]
overrides: # allow props spreading for hoc files
- files:
- app/**/with*.ts{,x}
rules:
'react/jsx-props-no-spreading': off
- files:
- app/**/*.test.*
extends:

View File

@@ -20,7 +20,7 @@ Portainer CE is updated regularly. We aim to do an update release every couple o
## 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/)

View File

@@ -21,11 +21,11 @@ type Monitor struct {
datastore dataservices.DataStore
shutdownCtx context.Context
cancellationFunc context.CancelFunc
mu sync.Mutex
mu sync.RWMutex
adminInitDisabled bool
}
// New creates a monitor that when started will wait for the timeout duration and then sends the timeout signal to disable the application
// 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,
@@ -82,6 +82,7 @@ func (m *Monitor) Stop() {
if m.cancellationFunc == nil {
return
}
m.cancellationFunc()
m.cancellationFunc = nil
}
@@ -92,12 +93,14 @@ func (m *Monitor) WasInitialized() (bool, error) {
if err != nil {
return false, err
}
return len(users) > 0, nil
}
func (m *Monitor) WasInstanceDisabled() bool {
m.mu.Lock()
defer m.mu.Unlock()
m.mu.RLock()
defer m.mu.RUnlock()
return m.adminInitDisabled
}
@@ -105,12 +108,10 @@ func (m *Monitor) WasInstanceDisabled() bool {
// 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() {
if 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
}
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)

View File

@@ -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_"

View File

@@ -2,6 +2,7 @@ package apikey
import (
"crypto/sha256"
"fmt"
"strings"
"testing"
"time"
@@ -170,11 +171,9 @@ func Test_UpdateAPIKey(t *testing.T) {
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
is.NoError(err)
log.Debug().Msgf("%+v", apiKey)
log.Debug().Msgf("%+v", 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) {

View File

@@ -2,7 +2,6 @@ package archive
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
@@ -28,10 +27,10 @@ func listFiles(dir string) []string {
func Test_shouldCreateArhive(t *testing.T) {
tmpdir := t.TempDir()
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
os.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
os.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
gzPath, err := TarGzDir(tmpdir)
assert.Nil(t, err)
@@ -48,7 +47,7 @@ func Test_shouldCreateArhive(t *testing.T) {
wasExtracted := func(p string) {
fullpath := path.Join(extractionDir, p)
assert.Contains(t, extractedFiles, fullpath)
copyContent, _ := ioutil.ReadFile(fullpath)
copyContent, _ := os.ReadFile(fullpath)
assert.Equal(t, content, copyContent)
}
@@ -60,10 +59,10 @@ func Test_shouldCreateArhive(t *testing.T) {
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
tmpdir := t.TempDir()
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
os.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
os.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
gzPath, err := TarGzDir(tmpdir)
assert.Nil(t, err)
@@ -80,7 +79,7 @@ func Test_shouldCreateArhiveXXXXX(t *testing.T) {
wasExtracted := func(p string) {
fullpath := path.Join(extractionDir, p)
assert.Contains(t, extractedFiles, fullpath)
copyContent, _ := ioutil.ReadFile(fullpath)
copyContent, _ := os.ReadFile(fullpath)
assert.Equal(t, content, copyContent)
}

View File

@@ -4,12 +4,12 @@ import (
"archive/zip"
"bytes"
"fmt"
"github.com/pkg/errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
// UnzipArchive will unzip an archive from bytes into the dest destination folder on disk
@@ -36,7 +36,7 @@ func extractFileFromArchive(file *zip.File, dest string) error {
}
defer f.Close()
data, err := ioutil.ReadAll(f)
data, err := io.ReadAll(f)
if err != nil {
return err
}

View File

@@ -1,9 +1,10 @@
package archive
import (
"github.com/stretchr/testify/assert"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnzipFile(t *testing.T) {

View File

@@ -2,6 +2,7 @@ package chisel
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
)
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
@@ -23,6 +24,8 @@ func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *por
tunnel.Jobs[existingJobIndex] = *edgeJob
}
cache.Del(endpointID)
service.mu.Unlock()
}
@@ -30,8 +33,7 @@ func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *por
func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
service.mu.Lock()
for _, tunnel := range service.tunnelDetailsMap {
// Filter in-place
for endpointID, tunnel := range service.tunnelDetailsMap {
n := 0
for _, edgeJob := range tunnel.Jobs {
if edgeJob.ID != edgeJobID {
@@ -41,7 +43,28 @@ func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
}
tunnel.Jobs = tunnel.Jobs[:n]
cache.Del(endpointID)
}
service.mu.Unlock()
}
func (service *Service) RemoveEdgeJobFromEndpoint(endpointID portainer.EndpointID, edgeJobID portainer.EdgeJobID) {
service.mu.Lock()
tunnel := service.getTunnelDetails(endpointID)
n := 0
for _, edgeJob := range tunnel.Jobs {
if edgeJob.ID != edgeJobID {
tunnel.Jobs[n] = edgeJob
n++
}
}
tunnel.Jobs = tunnel.Jobs[:n]
cache.Del(endpointID)
service.mu.Unlock()
}

View File

@@ -206,15 +206,23 @@ func (service *Service) checkTunnels() {
service.mu.Lock()
for key, tunnel := range service.tunnelDetailsMap {
if tunnel.LastActivity.IsZero() || tunnel.Status == portainer.EdgeAgentIdle {
continue
}
if tunnel.Status == portainer.EdgeAgentManagementRequired && time.Since(tunnel.LastActivity) < requiredTimeout {
continue
}
if tunnel.Status == portainer.EdgeAgentActive && time.Since(tunnel.LastActivity) < activeTimeout {
continue
}
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.Debug().
Int("endpoint_id", int(endpointID)).
@@ -222,9 +230,7 @@ func (service *Service) checkTunnels() {
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() {
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed > requiredTimeout {
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
@@ -233,9 +239,7 @@ func (service *Service) checkTunnels() {
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() {
if tunnel.Status == portainer.EdgeAgentActive && elapsed > activeTimeout {
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
@@ -246,9 +250,8 @@ func (service *Service) checkTunnels() {
err := service.snapshotEnvironment(endpointID, tunnel.Port)
if err != nil {
log.Error().
Int("endpoint_id", int(endpointID)).Err(
err).
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("unable to snapshot Edge environment")
}
}

View File

@@ -7,9 +7,11 @@ import (
"strings"
"time"
"github.com/dchest/uniuri"
"github.com/portainer/libcrypto"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/dchest/uniuri"
)
const (
@@ -49,6 +51,8 @@ func (service *Service) getTunnelDetails(endpointID portainer.EndpointID) *porta
service.tunnelDetailsMap[endpointID] = tunnel
cache.Del(endpointID)
return tunnel
}
@@ -99,6 +103,8 @@ func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID)
tunnel.Credentials = ""
tunnel.LastActivity = time.Now()
service.mu.Unlock()
cache.Del(endpointID)
}
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified environment(endpoint).
@@ -121,6 +127,8 @@ func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
service.ProxyManager.DeleteEndpointProxy(endpointID)
service.mu.Unlock()
cache.Del(endpointID)
}
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified environment(endpoint).
@@ -129,6 +137,8 @@ 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 {
defer cache.Del(endpointID)
tunnel := service.getTunnelDetails(endpointID)
service.mu.Lock()

View File

@@ -62,6 +62,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
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"),
LogMode: kingpin.Flag("log-mode", "Set the logging output mode").Default("PRETTY").Enum("PRETTY", "JSON"),
}
kingpin.Parse()

View File

@@ -2,14 +2,14 @@ package cli
import (
"bufio"
"log"
"fmt"
"os"
"strings"
)
// Confirm starts a rollback db cli application
func Confirm(message string) (bool, error) {
log.Printf("%s [y/N]", message)
fmt.Printf("%s [y/N]", message)
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')

View File

@@ -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"
)

View File

@@ -1,28 +0,0 @@
package main
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/rs/zerolog/log"
)
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 {
log.Error().Str("filename", importFile).Err(err).Msg("import failed")
// TODO: should really rollback on failure, but then we have nothing.
} else {
log.Info().Str("filename", importFile).Msg("successfully imported the file to a new portainer database")
}
// 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.Fatal().Err(err).Msg("failed initializing data store")
}
}
}

View File

@@ -1,7 +1,9 @@
package main
import (
"fmt"
stdlog "log"
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@@ -31,3 +33,23 @@ func setLoggingLevel(level string) {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
}
func setLoggingMode(mode string) {
switch mode {
case "PRETTY":
log.Logger = log.Output(zerolog.ConsoleWriter{
Out: os.Stderr,
NoColor: true,
TimeFormat: "2006/01/02 03:04PM",
FormatMessage: formatMessage})
case "JSON":
log.Logger = log.Output(os.Stderr)
}
}
func formatMessage(i interface{}) string {
if i == nil {
return ""
}
return fmt.Sprintf("%s |", i)
}

View File

@@ -4,13 +4,15 @@ import (
"context"
"crypto/sha256"
"fmt"
"math/rand"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/portainer/libhelm"
libstack "github.com/portainer/docker-compose-wrapper"
"github.com/portainer/docker-compose-wrapper/compose"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/build"
@@ -19,6 +21,7 @@ import (
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/database"
"github.com/portainer/portainer/api/database/boltdb"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/demo"
@@ -33,16 +36,20 @@ import (
kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/internal/edge"
"github.com/portainer/portainer/api/internal/edge/edgestacks"
"github.com/portainer/portainer/api/internal/snapshot"
"github.com/portainer/portainer/api/internal/ssl"
"github.com/portainer/portainer/api/internal/upgrade"
"github.com/portainer/portainer/api/jwt"
"github.com/portainer/portainer/api/kubernetes"
kubecli "github.com/portainer/portainer/api/kubernetes/cli"
"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/portainer/portainer/pkg/libhelm"
"github.com/gofrs/uuid"
"github.com/rs/zerolog/log"
)
@@ -98,8 +105,6 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
log.Info().Msg("exiting rollback")
os.Exit(0)
return nil
}
// Init sets some defaults - it's basically a migration
@@ -109,24 +114,27 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
}
if isNew {
// from MigrateData
store.VersionService.StoreDBVersion(portainer.DBVersion)
instanceId, err := uuid.NewV4()
if err != nil {
log.Fatal().Err(err).Msg("failed generating instance id")
}
err := updateSettingsFromFlags(store, flags)
// from MigrateData
v := models.Version{
SchemaVersion: portainer.APIVersion,
Edition: int(portainer.PortainerCE),
InstanceID: instanceId.String(),
}
store.VersionService.UpdateVersion(&v)
err = updateSettingsFromFlags(store, flags)
if err != nil {
log.Fatal().Err(err).Msg("failed updating settings from flags")
}
} else {
storedVersion, err := store.VersionService.DBVersion()
err = store.MigrateData()
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")
}
log.Fatal().Err(err).Msg("failed migration")
}
}
@@ -139,22 +147,13 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
go func() {
<-shutdownCtx.Done()
defer connection.Close()
exportFilename := path.Join(*flags.Data, fmt.Sprintf("export-%d.json", time.Now().Unix()))
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)
func initComposeStackManager(composeDeployer libstack.Deployer, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
composeWrapper, err := exec.NewComposeStackManager(composeDeployer, proxyManager)
if err != nil {
log.Fatal().Err(err).Msg("failed creating compose manager")
}
@@ -186,6 +185,10 @@ 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
@@ -235,11 +238,17 @@ func initDockerClientFactory(signatureService portainer.DigitalSignatureService,
return docker.NewClientFactory(signatureService, reverseTunnelService)
}
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string, dataStore dataservices.DataStore) *kubecli.ClientFactory {
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID, dataStore)
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*kubecli.ClientFactory, error) {
return kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, addrHTTPS, userSessionTimeout)
}
func initSnapshotService(snapshotIntervalFromFlag 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)
@@ -306,12 +315,7 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
sslSettings.HTTPEnabled = true
}
err = dataStore.SSLSettings().UpdateSettings(sslSettings)
if err != nil {
return err
}
return nil
return dataStore.SSLSettings().UpdateSettings(sslSettings)
}
// enableFeaturesFromFlags turns on or off feature flags
@@ -580,6 +584,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
ldapService := initLDAPService()
oauthService := initOAuthService()
gitService := initGitService(shutdownCtx)
openAMTService := openamt.NewService()
@@ -588,6 +593,8 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
digitalSignatureService := initDigitalSignatureService()
edgeStacksService := edgestacks.NewService(dataStore)
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
if err != nil {
log.Fatal().Err(err).Msg("")
@@ -606,7 +613,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID, dataStore)
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
if err != nil {
@@ -627,7 +634,12 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
dockerConfigPath := fileService.GetDockerConfigPath()
composeStackManager := initComposeStackManager(*flags.Assets, dockerConfigPath, reverseTunnelService, proxyManager)
composeDeployer, err := compose.NewComposeDeployer(*flags.Assets, dockerConfigPath)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing compose deployer")
}
composeStackManager := initComposeStackManager(composeDeployer, reverseTunnelService, proxyManager)
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
if err != nil {
@@ -704,14 +716,36 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatal().Err(err).Msg("failed starting tunnel server")
}
scheduler := scheduler.NewScheduler(shutdownCtx)
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")
}
scheduler := scheduler.NewScheduler(shutdownCtx)
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
stacks.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
upgradeService, err := upgrade.NewService(*flags.Assets, composeDeployer)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing upgrade service")
}
// FIXME: In 2.16 we changed the way ingress controller permissions are
// stored. Instead of being stored as annotation on an ingress rule, we keep
// them in our database. However, in order to run the migration we need an
// admin kube client to run lookup the old ingress rules and compare them
// with the current existing ingress classes.
//
// Unfortunately, our migrations run as part of the database initialization
// and our kubeclients require an initialized database. So it is not
// possible to do this migration as part of our normal flow. We DO have a
// migration which toggles a boolean in kubernetes configuration that
// indicated that this "post init" migration should be run. If/when this is
// resolved we can remove this function.
err = kubernetesClientFactory.PostInitMigrateIngresses()
if err != nil {
log.Fatal().Err(err).Msg("failure during creation of new database")
}
return &http.Server{
AuthorizationService: authorizationService,
@@ -722,12 +756,13 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
HTTPEnabled: sslDBSettings.HTTPEnabled,
AssetsPath: *flags.Assets,
DataStore: dataStore,
EdgeStacksService: edgeStacksService,
SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager,
KubernetesDeployer: kubernetesDeployer,
HelmPackageManager: helmPackageManager,
CryptoService: cryptoService,
APIKeyService: apiKeyService,
CryptoService: cryptoService,
JWTService: jwtService,
FileService: fileService,
LDAPService: ldapService,
@@ -747,15 +782,20 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
ShutdownTrigger: shutdownTrigger,
StackDeployer: stackDeployer,
DemoService: demoService,
UpgradeService: upgradeService,
}
}
func main() {
rand.Seed(time.Now().UnixNano())
configureLogger()
setLoggingMode("PRETTY")
flags := initCLI()
setLoggingLevel(*flags.LogLevel)
setLoggingMode(*flags.LogMode)
for {
server := buildServer(flags)

View File

@@ -8,6 +8,7 @@ import (
"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"
)

View File

@@ -24,13 +24,13 @@ type Connection interface {
SetServiceName(bucketName string) error
GetObject(bucketName string, key []byte, object interface{}) error
UpdateObject(bucketName string, key []byte, object interface{}) error
UpdateObjectFunc(bucketName string, key []byte, object any, updateFn func()) error
DeleteObject(bucketName string, key []byte) error
DeleteAllObjects(bucketName string, matching func(o interface{}) (id int, ok bool)) error
DeleteAllObjects(bucketName string, obj interface{}, 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
CreateObjectWithStringId(bucketName string, id []byte, 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

View File

@@ -13,7 +13,7 @@ import (
// Person with better knowledge is welcomed to improve it.
// sourced from https://golang.org/src/crypto/cipher/example_test.go
var emptySalt []byte = make([]byte, 0, 0)
var emptySalt []byte = make([]byte, 0)
// AesEncrypt reads from input, encrypts with AES-256 and writes to the output.
// passphrase is used to generate an encryption key.

View File

@@ -2,7 +2,6 @@ package crypto
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -20,7 +19,7 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
)
content := []byte("content")
ioutil.WriteFile(originFilePath, content, 0600)
os.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
@@ -30,7 +29,7 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
assert.Nil(t, err, "Failed to encrypt a file")
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
encryptedContent, err := os.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
@@ -45,7 +44,7 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
}
@@ -59,7 +58,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
)
content := []byte("content")
ioutil.WriteFile(originFilePath, content, 0600)
os.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
@@ -69,7 +68,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
err := AesEncrypt(originFile, encryptedFileWriter, []byte(""))
assert.Nil(t, err, "Failed to encrypt a file")
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
encryptedContent, err := os.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
@@ -84,7 +83,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
}
@@ -98,7 +97,7 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
)
content := []byte("content")
ioutil.WriteFile(originFilePath, content, 0600)
os.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
@@ -108,7 +107,7 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
assert.Nil(t, err, "Failed to encrypt a file")
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
encryptedContent, err := os.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
@@ -123,6 +122,6 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
assert.NotEqual(t, content, decryptedContent, "Original and decrypted content should NOT match")
}

View File

@@ -3,7 +3,7 @@ package crypto
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"os"
)
// CreateServerTLSConfiguration creates a basic tls.Config to be used by servers with recommended TLS settings
@@ -63,7 +63,7 @@ func CreateTLSConfigurationFromDisk(caCertPath, certPath, keyPath string, skipSe
}
if !skipServerVerification && caCertPath != "" {
caCert, err := ioutil.ReadFile(caCertPath)
caCert, err := os.ReadFile(caCertPath)
if err != nil {
return nil, err
}

View File

@@ -1,11 +1,11 @@
package boltdb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"time"
@@ -162,11 +162,11 @@ func (connection *DbConnection) ExportRaw(filename string) error {
return fmt.Errorf("stat on %s failed: %s", databasePath, err)
}
b, err := connection.ExportJson(databasePath, true)
b, err := connection.ExportJSON(databasePath, true)
if err != nil {
return err
}
return ioutil.WriteFile(filename, b, 0600)
return os.WriteFile(filename, b, 0600)
}
// ConvertToKey returns an 8-byte big endian representation of v.
@@ -178,7 +178,7 @@ func (connection *DbConnection) ConvertToKey(v int) []byte {
return b
}
// CreateBucket is a generic function used to create a bucket inside a database database.
// CreateBucket is a generic function used to create a bucket inside a database.
func (connection *DbConnection) SetServiceName(bucketName string) error {
return connection.Batch(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
@@ -186,7 +186,7 @@ func (connection *DbConnection) SetServiceName(bucketName string) error {
})
}
// GetObject is a generic function used to retrieve an unmarshalled object from a database database.
// GetObject is a generic function used to retrieve an unmarshalled object from a database.
func (connection *DbConnection) GetObject(bucketName string, key []byte, object interface{}) error {
var data []byte
@@ -218,7 +218,7 @@ func (connection *DbConnection) getEncryptionKey() []byte {
return connection.EncryptionKey
}
// UpdateObject is a generic function used to update an object inside a database database.
// UpdateObject is a generic function used to update an object inside a database.
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object interface{}) error {
data, err := connection.MarshalObject(object)
if err != nil {
@@ -231,7 +231,33 @@ func (connection *DbConnection) UpdateObject(bucketName string, key []byte, obje
})
}
// DeleteObject is a generic function used to delete an object inside a database database.
// UpdateObjectFunc is a generic function used to update an object safely without race conditions.
func (connection *DbConnection) UpdateObjectFunc(bucketName string, key []byte, object any, updateFn func()) error {
return connection.Batch(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(bucketName))
data := bucket.Get(key)
if data == nil {
return dserrors.ErrObjectNotFound
}
err := connection.UnmarshalObjectWithJsoniter(data, object)
if err != nil {
return err
}
updateFn()
data, err = connection.MarshalObject(object)
if err != nil {
return err
}
return bucket.Put(key, data)
})
}
// DeleteObject is a generic function used to delete an object inside a database.
func (connection *DbConnection) DeleteObject(bucketName string, key []byte) error {
return connection.Batch(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(bucketName))
@@ -241,13 +267,12 @@ func (connection *DbConnection) DeleteObject(bucketName string, key []byte) erro
// 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 {
func (connection *DbConnection) DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error {
return connection.Batch(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 := connection.UnmarshalObject(v, &obj)
if err != nil {
return err
@@ -325,27 +350,6 @@ func (connection *DbConnection) CreateObjectWithStringId(bucketName string, id [
})
}
// 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.Batch(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 := connection.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))
@@ -390,13 +394,33 @@ func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interf
return err
}
func (connection *DbConnection) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error {
return connection.View(func(tx *bolt.Tx) error {
cursor := tx.Bucket([]byte(bucketName)).Cursor()
for k, v := cursor.Seek(keyPrefix); k != nil && bytes.HasPrefix(k, keyPrefix); k, v = cursor.Next() {
err := connection.UnmarshalObjectWithJsoniter(v, obj)
if err != nil {
return err
}
obj, err = append(obj)
if err != nil {
return err
}
}
return nil
})
}
// BackupMetadata will return a copy of the boltdb sequence numbers for all buckets.
func (connection *DbConnection) BackupMetadata() (map[string]interface{}, error) {
buckets := map[string]interface{}{}
err := connection.View(func(tx *bolt.Tx) error {
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
bucketName := string(name)
bucket = tx.Bucket([]byte(bucketName))
seqId := bucket.Sequence()
buckets[bucketName] = int(seqId)
return nil
@@ -408,6 +432,7 @@ func (connection *DbConnection) BackupMetadata() (map[string]interface{}, error)
return buckets, err
}
// RestoreMetadata will restore the boltdb sequence numbers for all buckets.
func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error {
var err error

View File

@@ -14,7 +14,6 @@ func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) {
err := connection.View(func(tx *bolt.Tx) error {
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
bucketName := string(name)
bucket = tx.Bucket([]byte(bucketName))
seqId := bucket.Sequence()
buckets[bucketName] = int(seqId)
return nil
@@ -31,7 +30,7 @@ func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) {
// using this function.
// inspired by github.com/konoui/boltdb-exporter (which has no license)
// but very much simplified, based on how we use boltdb
func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, error) {
func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, error) {
log.Debug().Str("databasePath", databasePath).Msg("exportJson")
connection, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second, ReadOnly: true})

View File

@@ -16,5 +16,6 @@ func NewDatabase(storeType, storePath string, encryptionKey []byte) (connection
EncryptionKey: encryptionKey,
}, nil
}
return nil, fmt.Errorf("unknown storage database: %s", storeType)
return nil, fmt.Errorf("Unknown storage database: %s", storeType)
}

View File

@@ -0,0 +1,8 @@
package models
type Version struct {
SchemaVersion string
MigratorCount int
Edition int
InstanceID string
}

View File

@@ -8,10 +8,8 @@ import (
"github.com/rs/zerolog/log"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "edgegroups"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "edgegroups"
// Service represents a service for managing Edge group data.
type Service struct {
@@ -68,12 +66,22 @@ func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGrou
return &group, nil
}
// UpdateEdgeGroup updates an Edge group.
// Deprecated: Use UpdateEdgeGroupFunc instead.
func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, group)
}
// UpdateEdgeGroupFunc updates an edge group inside a transaction avoiding data races.
func (service *Service) UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFunc func(edgeGroup *portainer.EdgeGroup)) error {
id := service.connection.ConvertToKey(int(ID))
edgeGroup := &portainer.EdgeGroup{}
return service.connection.UpdateObjectFunc(BucketName, id, edgeGroup, func() {
updateFunc(edgeGroup)
})
}
// DeleteEdgeGroup deletes an Edge group.
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
identifier := service.connection.ConvertToKey(int(ID))

View File

@@ -2,6 +2,7 @@ package edgestack
import (
"fmt"
"sync"
portainer "github.com/portainer/portainer/api"
@@ -15,7 +16,10 @@ const (
// Service represents a service for managing Edge stack data.
type Service struct {
connection portainer.Connection
connection portainer.Connection
idxVersion map[portainer.EdgeStackID]int
mu sync.RWMutex
cacheInvalidationFn func(portainer.EdgeStackID)
}
func (service *Service) BucketName() string {
@@ -23,15 +27,32 @@ func (service *Service) BucketName() string {
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
func NewService(connection portainer.Connection, cacheInvalidationFn func(portainer.EdgeStackID)) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
}, nil
s := &Service{
connection: connection,
idxVersion: make(map[portainer.EdgeStackID]int),
cacheInvalidationFn: cacheInvalidationFn,
}
if s.cacheInvalidationFn == nil {
s.cacheInvalidationFn = func(portainer.EdgeStackID) {}
}
es, err := s.EdgeStacks()
if err != nil {
return nil, err
}
for _, e := range es {
s.idxVersion[e.ID] = e.Version
}
return s, nil
}
// EdgeStacks returns an array containing all edge stacks
@@ -42,7 +63,6 @@ func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
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")
@@ -70,28 +90,87 @@ func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStac
return &stack, nil
}
// EdgeStackVersion returns the version of the given edge stack ID directly from an in-memory index
func (service *Service) EdgeStackVersion(ID portainer.EdgeStackID) (int, bool) {
service.mu.RLock()
v, ok := service.idxVersion[ID]
service.mu.RUnlock()
return v, ok
}
// CreateEdgeStack saves an Edge stack object to db.
func (service *Service) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
edgeStack.ID = id
return service.connection.CreateObjectWithId(
err := service.connection.CreateObjectWithId(
BucketName,
int(edgeStack.ID),
edgeStack,
)
if err != nil {
return err
}
service.mu.Lock()
service.idxVersion[id] = edgeStack.Version
service.cacheInvalidationFn(id)
service.mu.Unlock()
return nil
}
// UpdateEdgeStack updates an Edge stack.
// Deprecated: Use UpdateEdgeStackFunc instead.
func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
service.mu.Lock()
defer service.mu.Unlock()
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, edgeStack)
err := service.connection.UpdateObject(BucketName, identifier, edgeStack)
if err != nil {
return err
}
service.idxVersion[ID] = edgeStack.Version
service.cacheInvalidationFn(ID)
return nil
}
// UpdateEdgeStackFunc updates an Edge stack inside a transaction avoiding data races.
func (service *Service) UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error {
id := service.connection.ConvertToKey(int(ID))
edgeStack := &portainer.EdgeStack{}
service.mu.Lock()
defer service.mu.Unlock()
return service.connection.UpdateObjectFunc(BucketName, id, edgeStack, func() {
updateFunc(edgeStack)
service.idxVersion[ID] = edgeStack.Version
service.cacheInvalidationFn(ID)
})
}
// DeleteEdgeStack deletes an Edge stack.
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
service.mu.Lock()
defer service.mu.Unlock()
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
err := service.connection.DeleteObject(BucketName, identifier)
if err != nil {
return err
}
delete(service.idxVersion, ID)
service.cacheInvalidationFn(ID)
return nil
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).

View File

@@ -1,185 +0,0 @@
package edgeupdateschedule
import (
"fmt"
"sync"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/edgetypes"
"github.com/sirupsen/logrus"
)
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 {
logrus.WithField("obj", obj).Errorf("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, err
}
// 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)
}
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
}

View File

@@ -2,8 +2,11 @@ package endpoint
import (
"fmt"
"sync"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
)
@@ -16,6 +19,9 @@ const (
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection portainer.Connection
mu sync.RWMutex
idxEdgeID map[string]portainer.EndpointID
heartbeats sync.Map
}
func (service *Service) BucketName() string {
@@ -29,9 +35,25 @@ func NewService(connection portainer.Connection) (*Service, error) {
return nil, err
}
return &Service{
s := &Service{
connection: connection,
}, nil
idxEdgeID: make(map[string]portainer.EndpointID),
}
es, err := s.Endpoints()
if err != nil {
return nil, err
}
for _, e := range es {
if len(e.EdgeID) > 0 {
s.idxEdgeID[e.EdgeID] = e.ID
}
s.heartbeats.Store(e.ID, e.LastCheckInDate)
}
return s, nil
}
// Endpoint returns an environment(endpoint) by ID.
@@ -44,19 +66,54 @@ func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
return nil, err
}
endpoint.LastCheckInDate, _ = service.Heartbeat(ID)
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)
err := service.connection.UpdateObject(BucketName, identifier, endpoint)
if err != nil {
return err
}
service.mu.Lock()
if len(endpoint.EdgeID) > 0 {
service.idxEdgeID[endpoint.EdgeID] = ID
}
service.heartbeats.Store(ID, endpoint.LastCheckInDate)
service.mu.Unlock()
cache.Del(endpoint.ID)
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)
err := service.connection.DeleteObject(BucketName, identifier)
if err != nil {
return err
}
service.mu.Lock()
for edgeID, endpointID := range service.idxEdgeID {
if endpointID == ID {
delete(service.idxEdgeID, edgeID)
break
}
}
service.heartbeats.Delete(ID)
service.mu.Unlock()
cache.Del(ID)
return nil
}
// Endpoints return an array containing all the environments(endpoints).
@@ -78,12 +135,54 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
return &portainer.Endpoint{}, nil
})
return endpoints, err
if err != nil {
return endpoints, err
}
for i, e := range endpoints {
t, _ := service.Heartbeat(e.ID)
endpoints[i].LastCheckInDate = t
}
return endpoints, nil
}
// EndpointIDByEdgeID returns the EndpointID from the given EdgeID using an in-memory index
func (service *Service) EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool) {
service.mu.RLock()
endpointID, ok := service.idxEdgeID[edgeID]
service.mu.RUnlock()
return endpointID, ok
}
func (service *Service) Heartbeat(endpointID portainer.EndpointID) (int64, bool) {
if t, ok := service.heartbeats.Load(endpointID); ok {
return t.(int64), true
}
return 0, false
}
func (service *Service) UpdateHeartbeat(endpointID portainer.EndpointID) {
service.heartbeats.Store(endpointID, time.Now().Unix())
}
// 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)
err := service.connection.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint)
if err != nil {
return err
}
service.mu.Lock()
if len(endpoint.EdgeID) > 0 {
service.idxEdgeID[endpoint.EdgeID] = endpoint.ID
}
service.heartbeats.Store(endpoint.ID, endpoint.LastCheckInDate)
service.mu.Unlock()
return nil
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).

View File

@@ -4,6 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
)
@@ -15,13 +16,18 @@ const (
// Service represents a service for managing environment(endpoint) relation data.
type Service struct {
connection portainer.Connection
connection portainer.Connection
updateStackFn func(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
}
func (service *Service) BucketName() string {
return BucketName
}
func (service *Service) RegisterUpdateStackFunction(updateFunc func(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error) {
service.updateStackFn = updateFunc
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
@@ -71,17 +77,128 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
// CreateEndpointRelation saves endpointRelation
func (service *Service) Create(endpointRelation *portainer.EndpointRelation) error {
return service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
err := service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
cache.Del(endpointRelation.EndpointID)
return err
}
// 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)
// Deprecated: Use UpdateEndpointRelationFunc instead.
func (service *Service) UpdateEndpointRelation(endpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
previousRelationState, _ := service.EndpointRelation(endpointID)
identifier := service.connection.ConvertToKey(int(endpointID))
err := service.connection.UpdateObject(BucketName, identifier, endpointRelation)
cache.Del(endpointID)
if err != nil {
return err
}
updatedRelationState, _ := service.EndpointRelation(endpointID)
service.updateEdgeStacksAfterRelationChange(previousRelationState, updatedRelationState)
return nil
}
// UpdateEndpointRelationFunc updates an Environment(Endpoint) relation object
func (service *Service) UpdateEndpointRelationFunc(endpointID portainer.EndpointID, updateFunc func(endpointRelation *portainer.EndpointRelation)) error {
previousRelationState, _ := service.EndpointRelation(endpointID)
id := service.connection.ConvertToKey(int(endpointID))
endpointRelation := &portainer.EndpointRelation{}
err := service.connection.UpdateObjectFunc(BucketName, id, endpointRelation, func() {
updateFunc(endpointRelation)
cache.Del(endpointID)
})
if err != nil {
return err
}
updatedRelationState, _ := service.EndpointRelation(endpointID)
service.updateEdgeStacksAfterRelationChange(previousRelationState, updatedRelationState)
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)
func (service *Service) DeleteEndpointRelation(endpointID portainer.EndpointID) error {
deletedRelation, _ := service.EndpointRelation(endpointID)
identifier := service.connection.ConvertToKey(int(endpointID))
err := service.connection.DeleteObject(BucketName, identifier)
cache.Del(endpointID)
if err != nil {
return err
}
service.updateEdgeStacksAfterRelationChange(deletedRelation, nil)
return nil
}
func (service *Service) InvalidateEdgeCacheForEdgeStack(edgeStackID portainer.EdgeStackID) {
rels, err := service.EndpointRelations()
if err != nil {
log.Error().Err(err).Msg("cannot retrieve endpoint relations")
return
}
for _, rel := range rels {
for id := range rel.EdgeStacks {
if edgeStackID == id {
cache.Del(rel.EndpointID)
}
}
}
}
func (service *Service) updateEdgeStacksAfterRelationChange(previousRelationState *portainer.EndpointRelation, updatedRelationState *portainer.EndpointRelation) {
relations, _ := service.EndpointRelations()
stacksToUpdate := map[portainer.EdgeStackID]bool{}
if previousRelationState != nil {
for stackId, enabled := range previousRelationState.EdgeStacks {
// flag stack for update if stack is not in the updated relation state
// = stack has been removed for this relation
// or this relation has been deleted
if enabled && (updatedRelationState == nil || !updatedRelationState.EdgeStacks[stackId]) {
stacksToUpdate[stackId] = true
}
}
}
if updatedRelationState != nil {
for stackId, enabled := range updatedRelationState.EdgeStacks {
// flag stack for update if stack is not in the previous relation state
// = stack has been added for this relation
if enabled && (previousRelationState == nil || !previousRelationState.EdgeStacks[stackId]) {
stacksToUpdate[stackId] = true
}
}
}
// for each stack referenced by the updated relation
// list how many time this stack is referenced in all relations
// in order to update the stack deployments count
for refStackId, refStackEnabled := range stacksToUpdate {
if refStackEnabled {
numDeployments := 0
for _, r := range relations {
for sId, enabled := range r.EdgeStacks {
if enabled && sId == refStackId {
numDeployments += 1
}
}
}
service.updateStackFn(refStackId, func(edgeStack *portainer.EdgeStack) {
edgeStack.NumDeployments = numDeployments
})
}
}
}

View File

@@ -4,7 +4,8 @@ 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/")
ErrDBImportFailed = errors.New("importing backup failed")
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")
ErrDatabaseIsUpdating = errors.New("database is currently in updating state. Failed prior upgrade. Please restore from backup or delete the database and restart Portainer")
)

View File

@@ -6,8 +6,8 @@ import (
"io"
"time"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/edgetypes"
portainer "github.com/portainer/portainer/api"
)
@@ -24,12 +24,10 @@ type (
BackupTo(w io.Writer) error
Export(filename string) (err error)
IsErrObjectNotFound(err error) bool
CustomTemplate() CustomTemplateService
EdgeGroup() EdgeGroupService
EdgeJob() EdgeJobService
EdgeStack() EdgeStackService
EdgeUpdateSchedule() EdgeUpdateScheduleService
Endpoint() EndpointService
EndpointGroup() EndpointGroupService
EndpointRelation() EndpointRelationService
@@ -69,6 +67,7 @@ type (
EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error)
Create(group *portainer.EdgeGroup) error
UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error
UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFunc func(group *portainer.EdgeGroup)) error
DeleteEdgeGroup(ID portainer.EdgeGroupID) error
BucketName() string
}
@@ -84,23 +83,14 @@ type (
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
BucketName() string
}
// EdgeStackService represents a service to manage Edge stacks
EdgeStackService interface {
EdgeStacks() ([]portainer.EdgeStack, error)
EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error)
EdgeStackVersion(ID portainer.EdgeStackID) (int, bool)
Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error
UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error
UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
DeleteEdgeStack(ID portainer.EdgeStackID) error
GetNextIdentifier() int
BucketName() string
@@ -109,6 +99,9 @@ type (
// EndpointService represents a service for managing environment(endpoint) data
EndpointService interface {
Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error)
EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool)
Heartbeat(endpointID portainer.EndpointID) (int64, bool)
UpdateHeartbeat(endpointID portainer.EndpointID)
Endpoints() ([]portainer.Endpoint, error)
Create(endpoint *portainer.Endpoint) error
UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error
@@ -133,6 +126,7 @@ type (
EndpointRelation(EndpointID portainer.EndpointID) (*portainer.EndpointRelation, error)
Create(endpointRelation *portainer.EndpointRelation) error
UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error
UpdateEndpointRelationFunc(EndpointID portainer.EndpointID, updateFunc func(*portainer.EndpointRelation)) error
DeleteEndpointRelation(EndpointID portainer.EndpointID) error
BucketName() string
}
@@ -252,6 +246,7 @@ type (
Tag(ID portainer.TagID) (*portainer.Tag, error)
Create(tag *portainer.Tag) error
UpdateTag(ID portainer.TagID, tag *portainer.Tag) error
UpdateTagFunc(ID portainer.TagID, updateFunc func(tag *portainer.Tag)) error
DeleteTag(ID portainer.TagID) error
BucketName() string
}
@@ -279,6 +274,7 @@ type (
DeleteTeamMembershipByUserID(userID portainer.UserID) error
DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error
BucketName() string
DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.TeamID, userID portainer.UserID) error
}
// TunnelServerService represents a service for managing data associated to the tunnel server
@@ -302,12 +298,11 @@ type (
// VersionService represents a service for managing version data
VersionService interface {
DBVersion() (int, error)
Edition() (portainer.SoftwareEdition, error)
InstanceID() (string, error)
StoreDBVersion(version int) error
StoreInstanceID(ID string) error
BucketName() string
UpdateInstanceID(ID string) error
Version() (*models.Version, error)
UpdateVersion(*models.Version) error
}
// WebhookService represents a service for managing webhook data.

View File

@@ -108,7 +108,7 @@ func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portain
// 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 service.connection.CreateObjectWithId(BucketName, int(schedule.ID), schedule)
}
// GetNextIdentifier returns the next identifier for a schedule.

View File

@@ -4,7 +4,8 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/sirupsen/logrus"
"github.com/rs/zerolog/log"
)
const (
@@ -51,7 +52,7 @@ func (service *Service) Snapshots() ([]portainer.Snapshot, error) {
func(obj interface{}) (interface{}, error) {
snapshot, ok := obj.(*portainer.Snapshot)
if !ok {
logrus.WithField("obj", obj).Errorf("Failed to convert to Snapshot object")
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Snapshot object")
return nil, fmt.Errorf("failed to convert to Snapshot object: %s", obj)
}
snapshots = append(snapshots, *snapshot)

View File

@@ -134,7 +134,7 @@ func (service *Service) GetNextIdentifier() int {
// CreateStack creates a new stack.
func (service *Service) Create(stack *portainer.Stack) error {
return service.connection.CreateObjectWithSetSequence(BucketName, int(stack.ID), stack)
return service.connection.CreateObjectWithId(BucketName, int(stack.ID), stack)
}
// UpdateStack updates a stack.

View File

@@ -80,12 +80,22 @@ func (service *Service) Create(tag *portainer.Tag) error {
)
}
// UpdateTag updates a tag.
// Deprecated: Use UpdateTagFunc instead.
func (service *Service) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, tag)
}
// UpdateTagFunc updates a tag inside a transaction avoiding data races.
func (service *Service) UpdateTagFunc(ID portainer.TagID, updateFunc func(tag *portainer.Tag)) error {
id := service.connection.ConvertToKey(int(ID))
tag := &portainer.Tag{}
return service.connection.UpdateObjectFunc(BucketName, id, tag, func() {
updateFunc(tag)
})
}
// DeleteTag deletes a tag.
func (service *Service) DeleteTag(ID portainer.TagID) error {
identifier := service.connection.ConvertToKey(int(ID))

View File

@@ -144,6 +144,7 @@ func (service *Service) DeleteTeamMembership(ID portainer.TeamMembershipID) erro
func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) error {
return service.connection.DeleteAllObjects(
BucketName,
&portainer.TeamMembership{},
func(obj interface{}) (id int, ok bool) {
membership, ok := obj.(portainer.TeamMembership)
if !ok {
@@ -164,6 +165,7 @@ func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) er
func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error {
return service.connection.DeleteAllObjects(
BucketName,
&portainer.TeamMembership{},
func(obj interface{}) (id int, ok bool) {
membership, ok := obj.(portainer.TeamMembership)
if !ok {
@@ -179,3 +181,23 @@ func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) er
return -1, false
})
}
func (service *Service) DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.TeamID, userID portainer.UserID) error {
return service.connection.DeleteAllObjects(
BucketName,
&portainer.TeamMembership{},
func(obj interface{}) (id int, ok bool) {
membership, ok := obj.(portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
return -1, false
}
if membership.TeamID == teamID && membership.UserID == userID {
return int(membership.ID), true
}
return -1, false
})
}

View File

@@ -1,17 +1,18 @@
package version
import (
"strconv"
"errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "version"
versionKey = "DB_VERSION"
instanceKey = "INSTANCE_ID"
editionKey = "EDITION"
versionKey = "VERSION"
updatingKey = "DB_UPDATING"
)
@@ -20,10 +21,6 @@ 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)
@@ -36,56 +33,87 @@ func NewService(connection portainer.Connection) (*Service, error) {
}, nil
}
// DBVersion retrieves the stored database version.
func (service *Service) DBVersion() (int, error) {
var version string
err := service.connection.GetObject(BucketName, []byte(versionKey), &version)
func (service *Service) SchemaVersion() (string, error) {
v, err := service.Version()
if err != nil {
return 0, err
return "", err
}
return strconv.Atoi(version)
return v.SchemaVersion, nil
}
func (service *Service) UpdateSchemaVersion(version string) error {
v, err := service.Version()
if err != nil {
return err
}
v.SchemaVersion = version
return service.UpdateVersion(v)
}
// Edition retrieves the stored portainer edition.
func (service *Service) Edition() (portainer.SoftwareEdition, error) {
var edition string
err := service.connection.GetObject(BucketName, []byte(editionKey), &edition)
v, err := service.Version()
if err != nil {
return 0, err
}
e, err := strconv.Atoi(edition)
if err != nil {
return 0, err
}
return portainer.SoftwareEdition(e), nil
}
// StoreDBVersion store the database version.
func (service *Service) StoreDBVersion(version int) error {
return service.connection.UpdateObject(BucketName, []byte(versionKey), strconv.Itoa(version))
return portainer.SoftwareEdition(v.Edition), nil
}
// IsUpdating retrieves the database updating status.
func (service *Service) IsUpdating() (bool, error) {
var isUpdating bool
err := service.connection.GetObject(BucketName, []byte(updatingKey), &isUpdating)
if err != nil && errors.Is(err, dserrors.ErrObjectNotFound) {
return false, nil
}
return isUpdating, err
}
// StoreIsUpdating store the database updating status.
func (service *Service) StoreIsUpdating(isUpdating bool) error {
return service.connection.UpdateObject(BucketName, []byte(updatingKey), isUpdating)
return service.connection.DeleteObject(BucketName, []byte(updatingKey))
}
// 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
v, err := service.Version()
if err != nil {
return "", err
}
return v.InstanceID, nil
}
// StoreInstanceID store the instance ID.
func (service *Service) StoreInstanceID(ID string) error {
return service.connection.UpdateObject(BucketName, []byte(instanceKey), ID)
func (service *Service) UpdateInstanceID(id string) error {
v, err := service.Version()
if err != nil {
if !dataservices.IsErrObjectNotFound(err) {
return err
}
v = &models.Version{}
}
v.InstanceID = id
return service.UpdateVersion(v)
}
// Version retrieve the version object.
func (service *Service) Version() (*models.Version, error) {
var v models.Version
err := service.connection.GetObject(BucketName, []byte(versionKey), &v)
if err != nil {
return nil, err
}
return &v, nil
}
// UpdateVersion persists a Version object.
func (service *Service) UpdateVersion(version *models.Version) error {
return service.connection.UpdateObject(BucketName, []byte(versionKey), version)
}

View File

@@ -6,6 +6,7 @@ import (
"path"
"time"
"github.com/portainer/portainer/api/database/models"
"github.com/rs/zerolog/log"
)
@@ -53,7 +54,7 @@ func (store *Store) copyDBFile(from string, to string) error {
// 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
Version string
BackupDir string
BackupFileName string
BackupPath string
@@ -70,26 +71,32 @@ func getBackupRestoreOptions(backupDir string) *BackupOptions {
}
// Backup current database with default options
func (store *Store) Backup() (string, error) {
return store.backupWithOptions(nil)
func (store *Store) Backup(version *models.Version) (string, error) {
if version == nil {
return store.backupWithOptions(nil)
}
return store.backupWithOptions(&BackupOptions{
Version: version.SchemaVersion,
})
}
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
if options == nil {
options = &BackupOptions{}
}
if options.Version == 0 {
version, err := store.version()
if options.Version == "" {
v, err := store.VersionService.Version()
if err != nil {
version = 0
options.Version = ""
}
options.Version = version
options.Version = v.SchemaVersion
}
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"))
options.BackupFileName = fmt.Sprintf("%s.%s.%s", store.connection.GetDatabaseFileName(), options.Version, time.Now().Format("20060102150405"))
}
if options.BackupPath == "" {
options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
@@ -168,7 +175,6 @@ func (store *Store) removeWithOptions(options *BackupOptions) error {
if os.IsNotExist(err) {
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to remove does not exist")
return err
}
@@ -176,7 +182,6 @@ func (store *Store) removeWithOptions(options *BackupOptions) error {
err = os.Remove(options.BackupPath)
if err != nil {
log.Error().Err(err).Msg("failed")
return err
}

View File

@@ -7,10 +7,11 @@ import (
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
)
func TestCreateBackupFolders(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(t, true, true)
defer teardown()
connection := store.GetConnection()
@@ -45,10 +46,13 @@ func TestBackup(t *testing.T) {
defer teardown()
t.Run("Backup should create default db backup", func(t *testing.T) {
store.VersionService.StoreDBVersion(portainer.DBVersion)
v := models.Version{
SchemaVersion: portainer.APIVersion,
}
store.VersionService.UpdateVersion(&v)
store.backupWithOptions(nil)
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", fmt.Sprintf("portainer.edb.%03d.*", portainer.DBVersion))
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", fmt.Sprintf("portainer.edb.%s.*", portainer.APIVersion))
if !isFileExist(backupFileName) {
t.Errorf("Expect backup file to be created %s", backupFileName)
}

View File

@@ -13,22 +13,6 @@ import (
"github.com/rs/zerolog/log"
)
func (store *Store) version() (int, error) {
version, err := store.VersionService.DBVersion()
if store.IsErrObjectNotFound(err) {
version = 0
}
return version, err
}
func (store *Store) edition() portainer.SoftwareEdition {
edition, err := store.VersionService.Edition()
if store.IsErrObjectNotFound(err) {
edition = portainer.PortainerCE
}
return edition
}
// NewStore initializes a new Store and the associated services
func NewStore(storePath string, fileService portainer.FileService, connection portainer.Connection) *Store {
return &Store{
@@ -39,8 +23,6 @@ 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
encryptionReq, err := store.connection.NeedsEncryptionMigration()
if err != nil {
return false, err
@@ -55,31 +37,24 @@ func (store *Store) Open() (newStore bool, err error) {
err = store.connection.Open()
if err != nil {
return newStore, err
return false, err
}
err = store.initServices()
if err != nil {
return newStore, err
return false, err
}
// if we have DBVersion in the database then ensure we flag this as NOT a new store
version, err := store.VersionService.DBVersion()
// If no settings object exists then assume we have a new store
_, err = store.SettingsService.Settings()
if err != nil {
if store.IsErrObjectNotFound(err) {
return newStore, nil
return true, nil
}
return newStore, err
return false, err
}
if version > 0 {
log.Debug().Int("version", version).Msg("opened existing store")
return false, nil
}
return newStore, nil
return false, nil
}
func (store *Store) Close() error {
@@ -94,17 +69,29 @@ func (store *Store) BackupTo(w io.Writer) error {
// CheckCurrentEdition checks if current edition is community edition
func (store *Store) CheckCurrentEdition() error {
if store.edition() != portainer.PortainerCE {
if store.edition() != portainer.Edition {
return portainerErrors.ErrWrongDBEdition
}
return nil
}
func (store *Store) edition() portainer.SoftwareEdition {
edition, err := store.VersionService.Edition()
if store.IsErrObjectNotFound(err) {
edition = portainer.PortainerCE
}
return edition
}
// TODO: move the use of this to dataservices.IsErrObjectNotFound()?
func (store *Store) IsErrObjectNotFound(e error) bool {
return e == portainerErrors.ErrObjectNotFound
}
func (store *Store) Connection() portainer.Connection {
return store.connection
}
func (store *Store) Rollback(force bool) error {
return store.connectionRollback(force)
}
@@ -156,6 +143,7 @@ func (store *Store) encryptDB() error {
if err != nil {
// Remove the new encrypted file that we failed to import
os.Remove(store.connection.GetDatabaseFilePath())
log.Fatal().Err(portainerErrors.ErrDBImportFailed).Msg("")
}

View File

@@ -1,18 +1,12 @@
package datastore
import (
"github.com/gofrs/uuid"
portainer "github.com/portainer/portainer/api"
)
// Init creates the default data set.
func (store *Store) Init() error {
err := store.checkOrCreateInstanceID()
if err != nil {
return err
}
err = store.checkOrCreateDefaultSettings()
err := store.checkOrCreateDefaultSettings()
if err != nil {
return err
}
@@ -25,20 +19,6 @@ func (store *Store) Init() error {
return store.checkOrCreateDefaultData()
}
func (store *Store) checkOrCreateInstanceID() error {
_, err := store.VersionService.InstanceID()
if store.IsErrObjectNotFound(err) {
uid, err := uuid.NewV4()
if err != nil {
return err
}
instanceID := uid.String()
return store.VersionService.StoreInstanceID(instanceID)
}
return err
}
func (store *Store) checkOrCreateDefaultSettings() error {
// TODO: these need to also be applied when importing
settings, err := store.SettingsService.Settings()
@@ -88,7 +68,6 @@ func (store *Store) checkOrCreateDefaultSettings() error {
func (store *Store) checkOrCreateDefaultSSLSettings() error {
_, err := store.SSLSettings().Settings()
if store.IsErrObjectNotFound(err) {
defaultSSLSettings := &portainer.SSLSettings{
HTTPEnabled: true,
@@ -96,6 +75,7 @@ func (store *Store) checkOrCreateDefaultSSLSettings() error {
return store.SSLSettings().UpdateSettings(defaultSSLSettings)
}
return err
}

View File

@@ -4,37 +4,69 @@ import (
"fmt"
"runtime/debug"
portainer "github.com/portainer/portainer/api"
portaineree "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/database/models"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/datastore/migrator"
"github.com/portainer/portainer/api/internal/authorization"
werrors "github.com/pkg/errors"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
func (store *Store) MigrateData() error {
version, err := store.version()
updating, err := store.VersionService.IsUpdating()
if err != nil {
return err
return errors.Wrap(err, "while checking if the store is updating")
}
// Backup Database
backupPath, err := store.Backup()
if err != nil {
return werrors.Wrap(err, "while backing up db before migration")
if updating {
return dserrors.ErrDatabaseIsUpdating
}
migratorParams := &migrator.MigratorParameters{
DatabaseVersion: version,
// migrate new version bucket if required (doesn't write anything to db yet)
version, err := store.getOrMigrateLegacyVersion()
if err != nil {
return errors.Wrap(err, "while migrating legacy version")
}
migratorParams := store.newMigratorParameters(version)
migrator := migrator.NewMigrator(migratorParams)
if !migrator.NeedsMigration() {
return nil
}
// before we alter anything in the DB, create a backup
backupPath, err := store.Backup(version)
if err != nil {
return errors.Wrap(err, "while backing up database")
}
err = store.FailSafeMigrate(migrator, version)
if err != nil {
err = store.restoreWithOptions(&BackupOptions{BackupPath: backupPath})
if err != nil {
return errors.Wrap(err, "failed to restore database")
}
log.Info().Msg("database restored to previous version")
return errors.Wrap(err, "failed to migrate database")
}
return nil
}
func (store *Store) newMigratorParameters(version *models.Version) *migrator.MigratorParameters {
return &migrator.MigratorParameters{
CurrentDBVersion: version,
EndpointGroupService: store.EndpointGroupService,
EndpointService: store.EndpointService,
EndpointRelationService: store.EndpointRelationService,
ExtensionService: store.ExtensionService,
FDOProfilesService: store.FDOProfilesService,
RegistryService: store.RegistryService,
ResourceControlService: store.ResourceControlService,
RoleService: store.RoleService,
@@ -49,99 +81,42 @@ func (store *Store) MigrateData() error {
FileService: store.fileService,
DockerhubService: store.DockerHubService,
AuthorizationService: authorization.NewService(store),
EdgeStackService: store.EdgeStackService,
}
// restore on error
err = store.connectionMigrateData(migratorParams)
if err != nil {
log.Error().Err(err).Msg("while DB migration, restoring DB")
// Restore options
options := BackupOptions{
BackupPath: backupPath,
}
err := store.restoreWithOptions(&options)
if err != nil {
log.Fatal().
Str("database_file", store.databasePath()).
Str("backup", options.BackupPath).Err(err).
Msg("failed restoring the backup, Portainer database file needs to restored manually by replacing the database file with a recent backup")
}
}
return err
}
// FailSafeMigrate backup and restore DB if migration fail
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator) (err error) {
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version *models.Version) (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 {
log.Info().
Int("migrator_version", migrator.Version()).
Int("db_version", portainer.DBVersion).
Msg("migrating database")
err = store.FailSafeMigrate(migrator)
if err != nil {
log.Error().Err(err).Msg("an error occurred during database migration")
return err
}
}
return nil
}
// backupVersion will backup the database or panic if any errors occur
func (store *Store) backupVersion(migrator *migrator.Migrator) error {
log.Info().Msg("backing up database prior to version upgrade")
options := getBackupRestoreOptions(store.commonBackupDir())
_, err := store.backupWithOptions(options)
err = store.VersionService.StoreIsUpdating(true)
if err != nil {
log.Error().Err(err).Msg("an error occurred during database backup")
return errors.Wrap(err, "while updating the store")
}
removalErr := store.removeWithOptions(options)
if removalErr != nil {
log.Error().Err(err).Msg("an error occurred during store removal prior to backup")
}
// now update the version to the new struct (if required)
err = store.finishMigrateLegacyVersion(version)
if err != nil {
return errors.Wrap(err, "while updating version")
}
log.Info().Msg("migrating database from version " + version.SchemaVersion + " to " + portaineree.APIVersion)
err = migrator.Migrate()
if err != nil {
return err
}
err = store.VersionService.StoreIsUpdating(false)
if err != nil {
return errors.Wrap(err, "failed to update the store")
}
return nil
}

View File

@@ -10,39 +10,41 @@ import (
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/boltdb"
"github.com/google/go-cmp/cmp"
"github.com/portainer/portainer/api/database/models"
"github.com/rs/zerolog/log"
)
// 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()
func testVersion(store *Store, versionWant string, t *testing.T) {
v, err := store.VersionService.Version()
if err != nil {
t.Errorf("Expect store version to be %d but was %d with error: %s", versionWant, v, err)
t.Errorf("Expect store version to be %s but was %s with error: %s", versionWant, v.SchemaVersion, err)
}
if v != versionWant {
t.Errorf("Expect store version to be %d but was %d", versionWant, v)
if v.SchemaVersion != versionWant {
t.Errorf("Expect store version to be %s but was %s", versionWant, v.SchemaVersion)
}
}
func TestMigrateData(t *testing.T) {
snapshotTests := []struct {
testName string
srcPath string
wantPath string
testName string
srcPath string
wantPath string
overrideInstanceId bool
}{
{
testName: "migrate version 24 to latest",
srcPath: "test_data/input_24.json",
wantPath: "test_data/output_24_to_latest.json",
testName: "migrate version 24 to latest",
srcPath: "test_data/input_24.json",
wantPath: "test_data/output_24_to_latest.json",
overrideInstanceId: true,
},
}
for _, test := range snapshotTests {
t.Run(test.testName, func(t *testing.T) {
err := migrateDBTestHelper(t, test.srcPath, test.wantPath)
err := migrateDBTestHelper(t, test.srcPath, test.wantPath, test.overrideInstanceId)
if err != nil {
t.Errorf(
"Failed migrating mock database %v: %v",
@@ -53,111 +55,111 @@ func TestMigrateData(t *testing.T) {
})
}
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
newStore, store, teardown := MustNewTestStore(t, false, true)
defer teardown()
// t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
// newStore, store, teardown := MustNewTestStore(t, true, false)
// defer teardown()
if !newStore {
t.Error("Expect a new DB")
}
// if !newStore {
// t.Error("Expect a new DB")
// }
// not called for new stores
//store.MigrateData()
// testVersion(store, portainer.APIVersion, t)
// store.Close()
testVersion(store, portainer.DBVersion, t)
store.Close()
// newStore, _ = store.Open()
// if newStore {
// t.Error("Expect store to NOT be new DB")
// }
// })
newStore, _ = store.Open()
if newStore {
t.Error("Expect store to NOT be new DB")
}
})
// tests := []struct {
// version string
// expectedVersion string
// }{
// {version: "1.24.1", expectedVersion: portainer.APIVersion},
// {version: "2.0.0", expectedVersion: portainer.APIVersion},
// }
// for _, tc := range tests {
// _, store, teardown := MustNewTestStore(t, true, true)
// defer teardown()
tests := []struct {
version int
expectedVersion int
}{
{version: 17, expectedVersion: portainer.DBVersion},
{version: 21, expectedVersion: portainer.DBVersion},
}
for _, tc := range tests {
_, store, teardown := MustNewTestStore(t, true, true)
defer teardown()
// // Setup data
// v := models.Version{SchemaVersion: tc.version}
// store.VersionService.UpdateVersion(&v)
// 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})
// 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 %s", tc.version), func(t *testing.T) {
// store.MigrateData()
// testVersion(store, tc.expectedVersion, t)
// })
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 %s", tc.version), func(t *testing.T) {
// store.Rollback(true)
// store.Open()
// testVersion(store, tc.version, 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(t, false, true)
// defer teardown()
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
defer teardown()
// v := models.Version{SchemaVersion: "1.24.1"}
// store.VersionService.UpdateVersion(&v)
version := 17
store.VersionService.StoreDBVersion(version)
// store.MigrateData()
store.MigrateData()
// testVersion(store, v.SchemaVersion, t)
// })
testVersion(store, version, t)
})
// t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
defer teardown()
store.VersionService.StoreDBVersion(0)
// v := models.Version{SchemaVersion: "0.0.0"}
// store.VersionService.UpdateVersion(&v)
store.MigrateData()
// store.MigrateData()
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
if !isFileExist(options.BackupPath) {
t.Errorf("Backup file should exist; file=%s", options.BackupPath)
}
})
// 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(t, false, true)
defer teardown()
// t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
store.VersionService.StoreIsUpdating(true)
// store.VersionService.StoreIsUpdating(true)
store.MigrateData()
// store.MigrateData()
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
if isFileExist(options.BackupPath) {
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
}
})
// 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(t, false, true)
defer teardown()
// t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
store.MigrateData()
// store.MigrateData()
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
if isFileExist(options.BackupPath) {
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
}
})
// if isFileExist(options.BackupPath) {
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
// }
// })
}
func Test_getBackupRestoreOptions(t *testing.T) {
@@ -179,18 +181,23 @@ func Test_getBackupRestoreOptions(t *testing.T) {
func TestRollback(t *testing.T) {
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
version := 21
_, store, teardown := MustNewTestStore(t, false, true)
version := models.Version{SchemaVersion: "2.4.0"}
_, store, teardown := MustNewTestStore(t, true, false)
defer teardown()
store.VersionService.StoreDBVersion(version)
_, err := store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
err := store.VersionService.UpdateVersion(&version)
if err != nil {
t.Errorf("Failed updating version: %v", err)
}
_, err = store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
if err != nil {
log.Fatal().Err(err).Msg("")
}
// Change the current edition
err = store.VersionService.StoreDBVersion(version + 10)
// Change the current version
version2 := models.Version{SchemaVersion: "2.6.0"}
err = store.VersionService.UpdateVersion(&version2)
if err != nil {
log.Fatal().Err(err).Msg("")
}
@@ -199,12 +206,17 @@ func TestRollback(t *testing.T) {
if err != nil {
t.Logf("Rollback failed: %s", err)
t.Fail()
return
}
store.Open()
testVersion(store, version, t)
_, err = store.Open()
if err != nil {
t.Logf("Open failed: %s", err)
t.Fail()
return
}
testVersion(store, version.SchemaVersion, t)
})
}
@@ -220,15 +232,20 @@ func isFileExist(path string) bool {
// migrateDBTestHelper loads a json representation of a bolt database from srcPath,
// parses it into a database, runs a migration on that database, and then
// compares it with an expected output database.
func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanceId bool) error {
srcJSON, err := os.ReadFile(srcPath)
if err != nil {
t.Fatalf("failed loading source JSON file %v: %v", srcPath, err)
}
// Parse source json to db.
_, store, teardown := MustNewTestStore(t, true, false)
defer teardown()
// When we create a new test store, it sets its version field automatically to latest.
_, store, _ := MustNewTestStore(t, true, false)
fmt.Println("store.path=", store.GetConnection().GetDatabaseFilePath())
store.connection.DeleteObject("version", []byte("VERSION"))
// defer teardown()
err = importJSON(t, bytes.NewReader(srcJSON), store)
if err != nil {
return err
@@ -240,6 +257,21 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
return err
}
if overrideInstanceId {
// old versions of portainer did not have instance-id. Because this gets generated
// we need to override the expected output to match the expected value to pass the test
v, err := store.VersionService.Version()
if err != nil {
return err
}
v.InstanceID = "463d5c47-0ea5-4aca-85b1-405ceefee254"
err = store.VersionService.UpdateVersion(v)
if err != nil {
return err
}
}
// Assert that our database connection is using bolt so we can call
// exportJson rather than ExportRaw. The exportJson function allows us to
// strip out the metadata which we don't want for our tests.
@@ -259,7 +291,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
return fmt.Errorf("stat on %s failed: %s", databasePath, err)
}
gotJSON, err := con.ExportJson(databasePath, false)
gotJSON, err := con.ExportJSON(databasePath, false)
if err != nil {
t.Logf(
"failed re-exporting database %s to JSON: %v",
@@ -275,7 +307,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
// Compare the result we got with the one we wanted.
if diff := cmp.Diff(wantJSON, gotJSON); diff != "" {
gotPath := filepath.Join(t.TempDir(), "portainer-migrator-test-fail.json")
gotPath := filepath.Join(os.TempDir(), "portainer-migrator-test-fail.json")
os.WriteFile(
gotPath,
gotJSON,
@@ -316,42 +348,65 @@ func importJSON(t *testing.T, r io.Reader, store *Store) error {
t.Logf("failed casting %s to map[string]interface{}", k)
}
// New format db
version, ok := versions["VERSION"]
if ok {
err := con.CreateObjectWithStringId(
k,
[]byte("VERSION"),
version,
)
if err != nil {
t.Logf("failed writing VERSION in %s: %v", k, err)
}
}
// old format db
dbVersion, ok := versions["DB_VERSION"]
if !ok {
t.Logf("failed getting DB_VERSION from %s", k)
}
if ok {
numDBVersion, ok := dbVersion.(json.Number)
if !ok {
t.Logf("failed parsing DB_VERSION as json number from %s", k)
}
numDBVersion, ok := dbVersion.(json.Number)
if !ok {
t.Logf("failed parsing DB_VERSION as json number from %s", k)
}
intDBVersion, err := numDBVersion.Int64()
if err != nil {
t.Logf("failed casting %v to int: %v", numDBVersion, intDBVersion)
}
intDBVersion, err := numDBVersion.Int64()
if err != nil {
t.Logf("failed casting %v to int: %v", numDBVersion, intDBVersion)
}
err = con.CreateObjectWithStringId(
k,
[]byte("DB_VERSION"),
int(intDBVersion),
)
if err != nil {
t.Logf("failed writing DB_VERSION in %s: %v", k, err)
err = con.CreateObjectWithStringId(
k,
[]byte("DB_VERSION"),
int(intDBVersion),
)
if err != nil {
t.Logf("failed writing DB_VERSION in %s: %v", k, err)
}
}
instanceID, ok := versions["INSTANCE_ID"]
if !ok {
t.Logf("failed getting INSTANCE_ID from %s", k)
if ok {
err = con.CreateObjectWithStringId(
k,
[]byte("INSTANCE_ID"),
instanceID,
)
if err != nil {
t.Logf("failed writing INSTANCE_ID in %s: %v", k, err)
}
}
err = con.CreateObjectWithStringId(
k,
[]byte("INSTANCE_ID"),
instanceID,
)
if err != nil {
t.Logf("failed writing INSTANCE_ID in %s: %v", k, err)
edition, ok := versions["EDITION"]
if ok {
err = con.CreateObjectWithStringId(
k,
[]byte("EDITION"),
edition,
)
if err != nil {
t.Logf("failed writing EDITION in %s: %v", k, err)
}
}
case "dockerhub":

View File

@@ -54,7 +54,6 @@ func TestMigrateSettings(t *testing.T) {
}
m := migrator.NewMigrator(&migrator.MigratorParameters{
DatabaseVersion: 29,
EndpointGroupService: store.EndpointGroupService,
EndpointService: store.EndpointService,
EndpointRelationService: store.EndpointRelationService,

View File

@@ -0,0 +1,115 @@
package datastore
import (
portaineree "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices"
)
const (
bucketName = "version"
legacyDBVersionKey = "DB_VERSION"
legacyInstanceKey = "INSTANCE_ID"
legacyEditionKey = "EDITION"
)
var dbVerToSemVerMap = map[int]string{
18: "1.21",
19: "1.22",
20: "1.22.1",
21: "1.22.2",
22: "1.23",
23: "1.24",
24: "1.24.1",
25: "2.0",
26: "2.1",
27: "2.2",
28: "2.4",
29: "2.4",
30: "2.6",
31: "2.7",
32: "2.9",
33: "2.9.1",
34: "2.10",
35: "2.9.3",
36: "2.11",
40: "2.13",
50: "2.14",
51: "2.14.1",
52: "2.14.2",
60: "2.15",
61: "2.15.1",
70: "2.16",
80: "2.17",
}
func dbVersionToSemanticVersion(dbVersion int) string {
if dbVersion < 18 {
return "1.0.0"
}
ver, ok := dbVerToSemVerMap[dbVersion]
if ok {
return ver
}
// We should always return something sensible
switch {
case dbVersion < 40:
return "2.11"
case dbVersion < 50:
return "2.13"
case dbVersion < 60:
return "2.14.2"
case dbVersion < 70:
return "2.15.1"
}
return "2.16.0"
}
// getOrMigrateLegacyVersion to new Version struct
func (store *Store) getOrMigrateLegacyVersion() (*models.Version, error) {
// Very old versions of portainer did not have a version bucket, lets set some defaults
dbVersion := 24
edition := int(portaineree.PortainerCE)
instanceId := ""
// If we already have a version key, we don't need to migrate
v, err := store.VersionService.Version()
if err == nil || !dataservices.IsErrObjectNotFound(err) {
return v, err
}
err = store.connection.GetObject(bucketName, []byte(legacyDBVersionKey), &dbVersion)
if err != nil && !dataservices.IsErrObjectNotFound(err) {
return nil, err
}
err = store.connection.GetObject(bucketName, []byte(legacyEditionKey), &edition)
if err != nil && !dataservices.IsErrObjectNotFound(err) {
return nil, err
}
err = store.connection.GetObject(bucketName, []byte(legacyInstanceKey), &instanceId)
if err != nil && !dataservices.IsErrObjectNotFound(err) {
return nil, err
}
return &models.Version{
SchemaVersion: dbVersionToSemanticVersion(dbVersion),
Edition: edition,
InstanceID: string(instanceId),
}, nil
}
// finishMigrateLegacyVersion writes the new version to the DB and removes the old version keys from the DB
func (store *Store) finishMigrateLegacyVersion(versionToWrite *models.Version) error {
err := store.VersionService.UpdateVersion(versionToWrite)
// Remove legacy keys if present
store.connection.DeleteObject(bucketName, []byte(legacyDBVersionKey))
store.connection.DeleteObject(bucketName, []byte(legacyEditionKey))
store.connection.DeleteObject(bucketName, []byte(legacyInstanceKey))
return err
}

View File

@@ -1,34 +1,18 @@
package migrator
import (
"errors"
"reflect"
"runtime"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
werrors "github.com/pkg/errors"
"github.com/Masterminds/semver"
"github.com/rs/zerolog/log"
)
type migration struct {
dbversion int
migrate func() error
}
func migrationError(err error, context string) error {
return werrors.Wrap(err, "failed in "+context)
}
func newMigration(dbversion int, migrate func() error) migration {
return migration{
dbversion: dbversion,
migrate: migrate,
}
}
func dbTooOldError() error {
return errors.New("migrating from less than Portainer 1.21.0 is not supported, please contact Portainer support.")
return errors.Wrap(err, "failed in "+context)
}
func GetFunctionName(i interface{}) string {
@@ -37,108 +21,105 @@ func GetFunctionName(i interface{}) string {
// 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)
version, err := m.versionService.Version()
if err != nil {
return migrationError(err, "StoreIsUpdating")
return migrationError(err, "get version service")
}
migrations := []migration{
// Portainer < 1.21.0
newMigration(17, dbTooOldError),
// Portainer 1.21.0
newMigration(18, m.updateUsersToDBVersion18),
newMigration(18, m.updateEndpointsToDBVersion18),
newMigration(18, m.updateEndpointGroupsToDBVersion18),
newMigration(18, m.updateRegistriesToDBVersion18),
// 1.22.0
newMigration(19, m.updateSettingsToDBVersion19),
// 1.22.1
newMigration(20, m.updateUsersToDBVersion20),
newMigration(20, m.updateSettingsToDBVersion20),
newMigration(20, m.updateSchedulesToDBVersion20),
// Portainer 1.23.0
// DBVersion 21 is missing as it was shipped as via hotfix 1.22.2
newMigration(22, m.updateResourceControlsToDBVersion22),
newMigration(22, m.updateUsersAndRolesToDBVersion22),
// Portainer 1.24.0
newMigration(23, m.updateTagsToDBVersion23),
newMigration(23, m.updateEndpointsAndEndpointGroupsToDBVersion23),
// Portainer 1.24.1
newMigration(24, m.updateSettingsToDB24),
// Portainer 2.0.0
newMigration(25, m.updateSettingsToDB25),
newMigration(25, m.updateStacksToDB24), // yes this looks odd. Don't be tempted to move it
// Portainer 2.1.0
newMigration(26, m.updateEndpointSettingsToDB25),
// Portainer 2.2.0
newMigration(27, m.updateStackResourceControlToDB27),
// Portainer 2.6.0
newMigration(30, m.migrateDBVersionToDB30),
// Portainer 2.9.0
newMigration(32, m.migrateDBVersionToDB32),
// Portainer 2.9.1, 2.9.2
newMigration(33, m.migrateDBVersionToDB33),
// Portainer 2.10
newMigration(34, m.migrateDBVersionToDB34),
// Portainer 2.9.3 (yep out of order, but 2.10 is EE only)
newMigration(35, m.migrateDBVersionToDB35),
newMigration(36, m.migrateDBVersionToDB36),
// Portainer 2.13
newMigration(40, m.migrateDBVersionToDB40),
// Portainer 2.14
newMigration(50, m.migrateDBVersionToDB50),
// Portainer 2.15
newMigration(60, m.migrateDBVersionToDB60),
// Portainer 2.16
newMigration(70, m.migrateDBVersionToDB70),
schemaVersion, err := semver.NewVersion(version.SchemaVersion)
if err != nil {
return migrationError(err, "invalid db schema version")
}
var lastDbVersion int
for _, migration := range migrations {
if m.currentDBVersion < migration.dbversion {
newMigratorCount := 0
apiVersion := semver.MustParse(portainer.APIVersion)
if schemaVersion.Equal(apiVersion) {
// detect and run migrations when the versions are the same.
// e.g. development builds
latestMigrations := m.LatestMigrations()
if latestMigrations.Version.Equal(schemaVersion) &&
version.MigratorCount != len(latestMigrations.MigrationFuncs) {
err := runMigrations(latestMigrations.MigrationFuncs)
if err != nil {
return err
}
newMigratorCount = len(latestMigrations.MigrationFuncs)
}
} else {
// regular path when major/minor/patch versions differ
for _, migration := range m.migrations {
if schemaVersion.LessThan(migration.Version) {
// Print the next line only when the version changes
if migration.dbversion > lastDbVersion {
log.Info().Int("to_version", migration.dbversion).Msg("migrating DB")
log.Info().Msgf("migrating data to %s", migration.Version.String())
err := runMigrations(migration.MigrationFuncs)
if err != nil {
return err
}
}
err := migration.migrate()
if err != nil {
return migrationError(err, GetFunctionName(migration.migrate))
if apiVersion.Equal(migration.Version) {
newMigratorCount = len(migration.MigrationFuncs)
}
}
lastDbVersion = migration.dbversion
}
log.Info().Int("version", portainer.DBVersion).Msg("setting DB version")
err = m.Always()
if err != nil {
return migrationError(err, "Always migrations returned error")
}
err = m.versionService.StoreDBVersion(portainer.DBVersion)
version.SchemaVersion = portainer.APIVersion
version.MigratorCount = newMigratorCount
err = m.versionService.UpdateVersion(version)
if err != nil {
return migrationError(err, "StoreDBVersion")
}
log.Info().Int("version", portainer.DBVersion).Msg("updated DB version")
log.Info().Msgf("db migrated to %s", portainer.APIVersion)
// reset DB updating status
return m.versionService.StoreIsUpdating(false)
return nil
}
func runMigrations(migrationFuncs []func() error) error {
for _, migrationFunc := range migrationFuncs {
err := migrationFunc()
if err != nil {
return migrationError(err, GetFunctionName(migrationFunc))
}
}
return nil
}
func (m *Migrator) NeedsMigration() bool {
// we need to migrate if anything changes with the version in the DB vs what our software version is.
// If the version matches, then it's all down to the number of migration funcs we have for the current version
// i.e. the MigratorCount
// In this particular instance we should log a fatal error
if m.CurrentDBEdition() != portainer.PortainerCE {
log.Fatal().Msg("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/")
return false
}
if m.CurrentSemanticDBVersion().LessThan(semver.MustParse(portainer.APIVersion)) {
return true
}
// Check if we have any migrations for the current version
latestMigrations := m.LatestMigrations()
if latestMigrations.Version.Equal(semver.MustParse(portainer.APIVersion)) {
if m.currentDBVersion.MigratorCount != len(latestMigrations.MigrationFuncs) {
return true
}
} else {
// One remaining possibility if we get here. If our migrator count > 0 and we have no migration funcs
// for the current version (i.e. they were deleted during development). Then we we need to migrate.
// This is to reset the migrator count back to 0
if m.currentDBVersion.MigratorCount > 0 {
return true
}
}
return false
}

View File

@@ -15,6 +15,7 @@ func (m *Migrator) updateUsersToDBVersion20() error {
}
func (m *Migrator) updateSettingsToDBVersion20() error {
log.Info().Msg("updating settings")
legacySettings, err := m.settingsService.Settings()
if err != nil {
return err

View File

@@ -22,6 +22,7 @@ func (m *Migrator) updateTagsToDBVersion23() error {
return err
}
}
return nil
}

View File

@@ -3,7 +3,7 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/internal/stackutils"
"github.com/portainer/portainer/api/stacks/stackutils"
"github.com/rs/zerolog/log"
)

View File

@@ -19,5 +19,6 @@ func (m *Migrator) MigrateSettingsToDB30() error {
legacySettings.OAuthSettings.SSO = false
legacySettings.OAuthSettings.LogoutURI = ""
return m.settingsService.UpdateSettings(legacySettings)
}

View File

@@ -13,6 +13,7 @@ func (m *Migrator) migrateDBVersionToDB33() error {
}
func (m *Migrator) migrateSettingsToDB33() error {
log.Info().Msg("setting default kubctl shell")
settings, err := m.settingsService.Settings()
if err != nil {
return err

View File

@@ -12,7 +12,7 @@ func (m *Migrator) migrateDBVersionToDB34() error {
return MigrateStackEntryPoint(m.stackService)
}
// MigrateStackEntryPoint exported for testing (blah.)
// MigrateStackEntryPoint exported for testing
func MigrateStackEntryPoint(stackService dataservices.StackService) error {
stacks, err := stackService.Stacks()
if err != nil {

View File

@@ -1,12 +1,7 @@
package migrator
import "github.com/rs/zerolog/log"
func (m *Migrator) migrateDBVersionToDB35() error {
// These should have been migrated already, but due to an earlier bug and a bunch of duplicates,
// calling it again will now fix the issue as the function has been repaired.
log.Info().Msg("updating dockerhub registries")
return m.updateDockerhubToDB32()
}

View File

@@ -1,94 +0,0 @@
package migrator
const (
db35TestFile = "portainer-mig-35.db"
username = "portainer"
password = "password"
)
// TODO: this is exactly the kind of reaching into the internals of the store we should not do
// func setupDB35Test(t *testing.T) *Migrator {
// is := assert.New(t)
// dbConn, err := bolt.Open(path.Join(t.TempDir(), db35TestFile), 0600, &bolt.Options{Timeout: 1 * time.Second})
// is.NoError(err, "failed to init testing DB connection")
// // Create an old style dockerhub authenticated account
// dockerhubService, err := dockerhub.NewService(&database.DbConnection{DB: dbConn})
// is.NoError(err, "failed to init testing registry service")
// err = dockerhubService.UpdateDockerHub(&portainer.DockerHub{true, username, password})
// is.NoError(err, "failed to create dockerhub account")
// registryService, err := registry.NewService(&database.DbConnection{DB: dbConn})
// is.NoError(err, "failed to init testing registry service")
// endpointService, err := endpoint.NewService(&database.DbConnection{DB: dbConn})
// is.NoError(err, "failed to init endpoint service")
// m := &Migrator{
// db: dbConn,
// dockerhubService: dockerhubService,
// registryService: registryService,
// endpointService: endpointService,
// }
// return m
// }
// // TestUpdateDockerhubToDB32 tests a normal upgrade
// func TestUpdateDockerhubToDB32(t *testing.T) {
// is := assert.New(t)
// m := setupDB35Test(t)
// defer m.db.Close()
// defer os.Remove(db35TestFile)
// if err := m.updateDockerhubToDB32(); err != nil {
// t.Errorf("failed to update settings: %v", err)
// }
// // Verify we have a single registry were created
// registries, err := m.registryService.Registries()
// is.NoError(err, "failed to read registries from the RegistryService")
// is.Equal(len(registries), 1, "only one migrated registry expected")
// }
// // TestUpdateDockerhubToDB32_with_duplicate_migrations tests an upgrade where in earlier versions a broken migration
// // created a large number of duplicate "dockerhub migrated" registry entries.
// func TestUpdateDockerhubToDB32_with_duplicate_migrations(t *testing.T) {
// is := assert.New(t)
// m := setupDB35Test(t)
// defer m.db.Close()
// defer os.Remove(db35TestFile)
// // Create lots of duplicate entries...
// registry := &portainer.Registry{
// Type: portainer.DockerHubRegistry,
// Name: "Dockerhub (authenticated - migrated)",
// URL: "docker.io",
// Authentication: true,
// Username: "portainer",
// Password: "password",
// RegistryAccesses: portainer.RegistryAccesses{},
// }
// for i := 1; i < 150; i++ {
// err := m.registryService.CreateRegistry(registry)
// assert.NoError(t, err, "create registry failed")
// }
// // Verify they were created
// registries, err := m.registryService.Registries()
// is.NoError(err, "failed to read registries from the RegistryService")
// is.Condition(func() bool {
// return len(registries) > 1
// }, "expected multiple duplicate registry entries")
// // Now run the migrator
// if err := m.updateDockerhubToDB32(); err != nil {
// t.Errorf("failed to update settings: %v", err)
// }
// // Verify we have a single registry were created
// registries, err = m.registryService.Registries()
// is.NoError(err, "failed to read registries from the RegistryService")
// is.Equal(len(registries), 1, "only one migrated registry expected")
// }

View File

@@ -14,6 +14,8 @@ func (m *Migrator) migrateDBVersionToDB36() error {
}
func (m *Migrator) migrateUsersToDB36() error {
log.Info().Msg("updating user authorizations")
users, err := m.userService.Users()
if err != nil {
return err

View File

@@ -19,12 +19,13 @@ func (m *Migrator) addGpuInputFieldDB60() error {
}
for _, endpoint := range endpoints {
endpoint.Gpus = []portainer.Pair{}
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
if endpoint.Gpus == nil {
endpoint.Gpus = []portainer.Pair{}
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
}
return nil

View File

@@ -2,12 +2,13 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB70() error {
log.Info().Msg("- add IngressAvailabilityPerNamespace field")
if err := m.addIngressAvailabilityPerNamespaceFieldDB70(); err != nil {
log.Info().Msg("add IngressAvailabilityPerNamespace field")
if err := m.updateIngressFieldsForEnvDB70(); err != nil {
return err
}
@@ -50,7 +51,7 @@ func (m *Migrator) migrateDBVersionToDB70() error {
return nil
}
func (m *Migrator) addIngressAvailabilityPerNamespaceFieldDB70() error {
func (m *Migrator) updateIngressFieldsForEnvDB70() error {
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
@@ -58,6 +59,8 @@ func (m *Migrator) addIngressAvailabilityPerNamespaceFieldDB70() error {
for _, endpoint := range endpoints {
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = true
endpoint.Kubernetes.Configuration.AllowNoneIngressClass = false
endpoint.PostInitMigrations.MigrateIngresses = true
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {

View File

@@ -0,0 +1,36 @@
package migrator
import (
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB71() error {
log.Info().Msg("removing orphaned snapshots")
snapshots, err := m.snapshotService.Snapshots()
if err != nil {
return err
}
for _, s := range snapshots {
_, err := m.endpointService.Endpoint(s.EndpointID)
if err == nil {
log.Debug().Int("endpoint_id", int(s.EndpointID)).Msg("keeping snapshot")
continue
} else if err != errors.ErrObjectNotFound {
log.Debug().Int("endpoint_id", int(s.EndpointID)).Err(err).Msg("database error")
return err
}
log.Debug().Int("endpoint_id", int(s.EndpointID)).Msg("removing snapshot")
err = m.snapshotService.DeleteSnapshot(s.EndpointID)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,102 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB80() error {
if err := m.updateEdgeStackStatusForDB80(); err != nil {
return err
}
if err := m.updateExistingEndpointsToNotDetectMetricsAPIForDB80(); err != nil {
return err
}
if err := m.updateExistingEndpointsToNotDetectStorageAPIForDB80(); err != nil {
return err
}
return nil
}
func (m *Migrator) updateExistingEndpointsToNotDetectMetricsAPIForDB80() error {
log.Info().Msg("updating existing endpoints to not detect metrics API for existing endpoints (k8s)")
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
if endpointutils.IsKubernetesEndpoint(&endpoint) {
endpoint.Kubernetes.Flags.IsServerMetricsDetected = true
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
}
return nil
}
func (m *Migrator) updateExistingEndpointsToNotDetectStorageAPIForDB80() error {
log.Info().Msg("updating existing endpoints to not detect metrics API for existing endpoints (k8s)")
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
if endpointutils.IsKubernetesEndpoint(&endpoint) {
endpoint.Kubernetes.Flags.IsServerStorageDetected = true
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
}
return nil
}
func (m *Migrator) updateEdgeStackStatusForDB80() error {
log.Info().Msg("transfer type field to details field for edge stack status")
edgeStacks, err := m.edgeStackService.EdgeStacks()
if err != nil {
return err
}
for _, edgeStack := range edgeStacks {
for endpointId, status := range edgeStack.Status {
switch status.Type {
case portainer.EdgeStackStatusPending:
status.Details.Pending = true
case portainer.EdgeStackStatusOk:
status.Details.Ok = true
case portainer.EdgeStackStatusError:
status.Details.Error = true
case portainer.EdgeStackStatusAcknowledged:
status.Details.Acknowledged = true
case portainer.EdgeStackStatusRemove:
status.Details.Remove = true
case portainer.EdgeStackStatusRemoteUpdateSuccess:
status.Details.RemoteUpdateSuccess = true
}
edgeStack.Status[endpointId] = status
}
err = m.edgeStackService.UpdateEdgeStack(edgeStack.ID, &edgeStack)
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,7 +1,15 @@
package migrator
import (
"errors"
"github.com/portainer/portainer/api/dataservices/edgestack"
"github.com/Masterminds/semver"
"github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices/dockerhub"
"github.com/portainer/portainer/api/dataservices/endpoint"
"github.com/portainer/portainer/api/dataservices/endpointgroup"
@@ -25,7 +33,9 @@ import (
type (
// Migrator defines a service to migrate data after a Portainer version update.
Migrator struct {
currentDBVersion int
currentDBVersion *models.Version
migrations []Migrations
endpointGroupService *endpointgroup.Service
endpointService *endpoint.Service
endpointRelationService *endpointrelation.Service
@@ -45,11 +55,12 @@ type (
fileService portainer.FileService
authorizationService *authorization.Service
dockerhubService *dockerhub.Service
edgeStackService *edgestack.Service
}
// MigratorParameters represents the required parameters to create a new Migrator instance.
MigratorParameters struct {
DatabaseVersion int
CurrentDBVersion *models.Version
EndpointGroupService *endpointgroup.Service
EndpointService *endpoint.Service
EndpointRelationService *endpointrelation.Service
@@ -69,13 +80,14 @@ type (
FileService portainer.FileService
AuthorizationService *authorization.Service
DockerhubService *dockerhub.Service
EdgeStackService *edgestack.Service
}
)
// NewMigrator creates a new Migrator.
func NewMigrator(parameters *MigratorParameters) *Migrator {
return &Migrator{
currentDBVersion: parameters.DatabaseVersion,
migrator := &Migrator{
currentDBVersion: parameters.CurrentDBVersion,
endpointGroupService: parameters.EndpointGroupService,
endpointService: parameters.EndpointService,
endpointRelationService: parameters.EndpointRelationService,
@@ -95,10 +107,115 @@ func NewMigrator(parameters *MigratorParameters) *Migrator {
fileService: parameters.FileService,
authorizationService: parameters.AuthorizationService,
dockerhubService: parameters.DockerhubService,
edgeStackService: parameters.EdgeStackService,
}
migrator.initMigrations()
return migrator
}
// Version exposes version of database
func (migrator *Migrator) Version() int {
return migrator.currentDBVersion
func (m *Migrator) CurrentDBVersion() string {
return m.currentDBVersion.SchemaVersion
}
func (m *Migrator) CurrentDBEdition() portainer.SoftwareEdition {
return portainer.SoftwareEdition(m.currentDBVersion.Edition)
}
func (m *Migrator) CurrentSemanticDBVersion() *semver.Version {
v, err := semver.NewVersion(m.currentDBVersion.SchemaVersion)
if err != nil {
log.Fatal().Stack().Err(err).Msg("failed to parse current version")
}
return v
}
func (m *Migrator) addMigrations(v string, funcs ...func() error) {
m.migrations = append(m.migrations, Migrations{
Version: semver.MustParse(v),
MigrationFuncs: funcs,
})
}
func (m *Migrator) LatestMigrations() Migrations {
return m.migrations[len(m.migrations)-1]
}
// !NOTE: Migration funtions should ideally be idempotent.
// ! Which simply means the function can run over the same data many times but only transform it once.
// ! In practice this really just means an extra check or two to ensure we're not destroying valid data.
// ! This is not a hard rule though. Understand the limitations. A migration function may only run over
// ! the data more than once if a new migration function is added and the version of your database schema is
// ! the same. e.g. two developers working on the same version add two different functions for different things.
// ! This increases the migration funcs count and so they all run again.
type Migrations struct {
Version *semver.Version
MigrationFuncs MigrationFuncs
}
type MigrationFuncs []func() error
func (m *Migrator) initMigrations() {
// !IMPORTANT: Do not be tempted to alter the order of these migrations.
// ! Even though one of them looks out of order. Caused by history related
// ! to maintaining two versions and releasing at different times
m.addMigrations("1.0.0", dbTooOldError) // default version found after migration
m.addMigrations("1.21",
m.updateUsersToDBVersion18,
m.updateEndpointsToDBVersion18,
m.updateEndpointGroupsToDBVersion18,
m.updateRegistriesToDBVersion18)
m.addMigrations("1.22", m.updateSettingsToDBVersion19)
m.addMigrations("1.22.1",
m.updateUsersToDBVersion20,
m.updateSettingsToDBVersion20,
m.updateSchedulesToDBVersion20)
m.addMigrations("1.23",
m.updateResourceControlsToDBVersion22,
m.updateUsersAndRolesToDBVersion22)
m.addMigrations("1.24",
m.updateTagsToDBVersion23,
m.updateEndpointsAndEndpointGroupsToDBVersion23)
m.addMigrations("1.24.1", m.updateSettingsToDB24)
m.addMigrations("2.0",
m.updateSettingsToDB25,
m.updateStacksToDB24)
m.addMigrations("2.1", m.updateEndpointSettingsToDB25)
m.addMigrations("2.2", m.updateStackResourceControlToDB27)
m.addMigrations("2.6", m.migrateDBVersionToDB30)
m.addMigrations("2.9", m.migrateDBVersionToDB32)
m.addMigrations("2.9.2", m.migrateDBVersionToDB33)
m.addMigrations("2.10.0", m.migrateDBVersionToDB34)
m.addMigrations("2.9.3", m.migrateDBVersionToDB35)
m.addMigrations("2.12", m.migrateDBVersionToDB36)
m.addMigrations("2.13", m.migrateDBVersionToDB40)
m.addMigrations("2.14", m.migrateDBVersionToDB50)
m.addMigrations("2.15", m.migrateDBVersionToDB60)
m.addMigrations("2.16", m.migrateDBVersionToDB70)
m.addMigrations("2.16.1", m.migrateDBVersionToDB71)
m.addMigrations("2.17", m.migrateDBVersionToDB80)
// Add new migrations below...
// One function per migration, each versions migration funcs in the same file.
}
// Always is always run at the end of migrations
func (m *Migrator) Always() error {
// currently nothing to be done in CE... yet
return nil
}
func dbTooOldError() error {
return errors.New("migrating from less than Portainer 1.21.0 is not supported, please contact Portainer support")
}

View File

@@ -3,10 +3,10 @@ package datastore
import (
"encoding/json"
"fmt"
"io/ioutil"
"strconv"
"os"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/dataservices/apikeyrepository"
"github.com/portainer/portainer/api/dataservices/customtemplate"
@@ -14,7 +14,6 @@ import (
"github.com/portainer/portainer/api/dataservices/edgegroup"
"github.com/portainer/portainer/api/dataservices/edgejob"
"github.com/portainer/portainer/api/dataservices/edgestack"
"github.com/portainer/portainer/api/dataservices/edgeupdateschedule"
"github.com/portainer/portainer/api/dataservices/endpoint"
"github.com/portainer/portainer/api/dataservices/endpointgroup"
"github.com/portainer/portainer/api/dataservices/endpointrelation"
@@ -50,7 +49,6 @@ type Store struct {
DockerHubService *dockerhub.Service
EdgeGroupService *edgegroup.Service
EdgeJobService *edgejob.Service
EdgeUpdateScheduleService *edgeupdateschedule.Service
EdgeStackService *edgestack.Service
EndpointGroupService *endpointgroup.Service
EndpointService *endpoint.Service
@@ -95,17 +93,18 @@ func (store *Store) initServices() error {
}
store.DockerHubService = dockerhubService
edgeUpdateScheduleService, err := edgeupdateschedule.NewService(store.connection)
endpointRelationService, err := endpointrelation.NewService(store.connection)
if err != nil {
return err
}
store.EdgeUpdateScheduleService = edgeUpdateScheduleService
store.EndpointRelationService = endpointRelationService
edgeStackService, err := edgestack.NewService(store.connection)
edgeStackService, err := edgestack.NewService(store.connection, endpointRelationService.InvalidateEdgeCacheForEdgeStack)
if err != nil {
return err
}
store.EdgeStackService = edgeStackService
endpointRelationService.RegisterUpdateStackFunction(edgeStackService.UpdateEdgeStackFunc)
edgeGroupService, err := edgegroup.NewService(store.connection)
if err != nil {
@@ -131,12 +130,6 @@ func (store *Store) initServices() error {
}
store.EndpointService = endpointService
endpointRelationService, err := endpointrelation.NewService(store.connection)
if err != nil {
return err
}
store.EndpointRelationService = endpointRelationService
extensionService, err := extension.NewService(store.connection)
if err != nil {
return err
@@ -263,11 +256,6 @@ func (store *Store) EdgeJob() dataservices.EdgeJobService {
return store.EdgeJobService
}
// EdgeUpdateSchedule gives access to the EdgeUpdateSchedule data management layer
func (store *Store) EdgeUpdateSchedule() dataservices.EdgeUpdateScheduleService {
return store.EdgeUpdateScheduleService
}
// EdgeStack gives access to the EdgeStack data management layer
func (store *Store) EdgeStack() dataservices.EdgeStackService {
return store.EdgeStackService
@@ -395,7 +383,7 @@ type storeExport struct {
Team []portainer.Team `json:"teams,omitempty"`
TunnelServer portainer.TunnelServerInfo `json:"tunnel_server,omitempty"`
User []portainer.User `json:"users,omitempty"`
Version map[string]string `json:"version,omitempty"`
Version models.Version `json:"version,omitempty"`
Webhook []portainer.Webhook `json:"webhooks,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
@@ -518,7 +506,7 @@ func (store *Store) Export(filename string) (err error) {
if snapshot, err := store.Snapshot().Snapshots(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Err(err).Msg("Exporting Snapshots")
log.Error().Err(err).Msg("exporting Snapshots")
}
} else {
backup.Snapshot = snapshot
@@ -588,14 +576,12 @@ func (store *Store) Export(filename string) (err error) {
backup.Webhook = webhooks
}
v, err := store.Version().DBVersion()
if err != nil && !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting DB version")
}
instance, _ := store.Version().InstanceID()
backup.Version = map[string]string{
"DB_VERSION": strconv.Itoa(v),
"INSTANCE_ID": instance,
if version, err := store.Version().Version(); err != nil {
if !store.IsErrObjectNotFound(err) {
log.Error().Err(err).Msg("exporting Version")
}
} else {
backup.Version = *version
}
backup.Metadata, err = store.connection.BackupMetadata()
@@ -607,13 +593,13 @@ func (store *Store) Export(filename string) (err error) {
if err != nil {
return err
}
return ioutil.WriteFile(filename, b, 0600)
return os.WriteFile(filename, b, 0600)
}
func (store *Store) Import(filename string) (err error) {
backup := storeExport{}
s, err := ioutil.ReadFile(filename)
s, err := os.ReadFile(filename)
if err != nil {
return err
}
@@ -622,19 +608,7 @@ func (store *Store) Import(filename string) (err error) {
return err
}
// TODO: yup, this is bad, and should be in a version struct...
if dbversion, ok := backup.Version["DB_VERSION"]; ok {
if v, err := strconv.Atoi(dbversion); err == nil {
if err := store.Version().StoreDBVersion(v); err != nil {
log.Error().Err(err).Msg("DB_VERSION import issue")
}
}
}
if instanceID, ok := backup.Version["INSTANCE_ID"]; ok {
if err := store.Version().StoreInstanceID(instanceID); err != nil {
log.Error().Err(err).Msg("INSTANCE_ID import issue")
}
}
store.Version().UpdateVersion(&backup.Version)
for _, v := range backup.CustomTemplate {
store.CustomTemplate().UpdateCustomTemplate(v.ID, &v)
@@ -707,7 +681,7 @@ func (store *Store) Import(filename string) (err error) {
for _, user := range backup.User {
if err := store.User().UpdateUser(user.ID, &user); err != nil {
log.Debug().Str("user", fmt.Sprintf("%+v", user)).Err(err).Msg("user: failed to Update Database")
log.Debug().Str("user", fmt.Sprintf("%+v", user)).Err(err).Msg("failed to update the user in the database")
}
}

View File

@@ -52,6 +52,7 @@
"IsEdgeDevice": false,
"Kubernetes": {
"Configuration": {
"AllowNoneIngressClass": false,
"EnableResourceOverCommit": false,
"IngressAvailabilityPerNamespace": true,
"IngressClasses": null,
@@ -61,10 +62,17 @@
"UseLoadBalancer": false,
"UseServerMetrics": false
},
"Flags": {
"IsServerMetricsDetected": false,
"IsServerStorageDetected": false
},
"Snapshots": []
},
"LastCheckInDate": 0,
"Name": "local",
"PostInitMigrations": {
"MigrateIngresses": true
},
"PublicURL": "",
"QueryDate": 0,
"SecuritySettings": {
@@ -636,6 +644,7 @@
"Scopes": "",
"UserIdentifier": ""
},
"ShowKomposeBuildOption": false,
"SnapshotInterval": "5m",
"TemplatesURL": "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json",
"TrustOnFirstConnect": false,
@@ -926,8 +935,6 @@
}
],
"version": {
"DB_UPDATING": "false",
"DB_VERSION": "70",
"INSTANCE_ID": "null"
"VERSION": "{\"SchemaVersion\":\"2.17.0\",\"MigratorCount\":1,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
}
}

View File

@@ -5,34 +5,29 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/filesystem"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
var errTempDir = errors.New("can't create a temp dir")
func (store *Store) GetConnection() portainer.Connection {
return store.connection
}
func MustNewTestStore(t *testing.T, init, secure bool) (bool, *Store, func()) {
func MustNewTestStore(t testing.TB, init, secure bool) (bool, *Store, func()) {
newStore, store, teardown, err := NewTestStore(t, init, secure)
if err != nil {
if !errors.Is(err, errTempDir) {
teardown()
}
log.Fatal().Err(err).Msg("")
}
return newStore, store, teardown
}
func NewTestStore(t *testing.T, init, secure bool) (bool, *Store, func(), error) {
func NewTestStore(t testing.TB, init, secure bool) (bool, *Store, func(), error) {
// Creates unique temp directory in a concurrency friendly manner.
storePath := t.TempDir()
fileService, err := filesystem.NewService(storePath, "")
if err != nil {
return false, nil, nil, err
@@ -54,8 +49,6 @@ func NewTestStore(t *testing.T, init, secure bool) (bool, *Store, func(), error)
return newStore, nil, nil, err
}
log.Debug().Msg("opened")
if init {
err = store.Init()
if err != nil {
@@ -63,26 +56,24 @@ func NewTestStore(t *testing.T, init, secure bool) (bool, *Store, func(), error)
}
}
log.Debug().Msg("initialised")
if newStore {
// from MigrateData
store.VersionService.StoreDBVersion(portainer.DBVersion)
v := models.Version{
SchemaVersion: portainer.APIVersion,
Edition: int(portainer.PortainerCE),
}
err = store.VersionService.UpdateVersion(&v)
if err != nil {
return newStore, nil, nil, err
}
}
teardown := func() {
teardown(store)
err := store.Close()
if err != nil {
log.Fatal().Err(err).Msg("")
}
}
return newStore, store, teardown, nil
}
func teardown(store *Store) {
err := store.Close()
if err != nil {
log.Fatal().Err(err).Msg("")
}
}

6
api/docker/labels.go Normal file
View File

@@ -0,0 +1,6 @@
package docker
const (
ComposeStackNameLabel = "com.docker.compose.project"
SwarmStackNameLabel = "com.docker.stack.namespace"
)

View File

@@ -159,7 +159,7 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
gpuUseSet := make(map[string]struct{})
gpuUseAll := false
for _, container := range containers {
if container.State == "exited" {
if container.State == "exited" || container.State == "stopped" {
stoppedContainers++
} else if container.State == "running" {
runningContainers++
@@ -194,7 +194,7 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
}
for k, v := range container.Labels {
if k == "com.docker.compose.project" {
if k == ComposeStackNameLabel {
stacks[v] = struct{}{}
}
}

View File

@@ -1,102 +0,0 @@
package edgetypes
import portainer "github.com/portainer/portainer/api"
const (
// PortainerAgentUpdateScheduleIDHeader represents the name of the header containing the update schedule id
PortainerAgentUpdateScheduleIDHeader = "X-Portainer-Update-Schedule-ID"
// PortainerAgentUpdateStatusHeader is the name of the header that will have the update status
PortainerAgentUpdateStatusHeader = "X-Portainer-Update-Status"
// PortainerAgentUpdateErrorHeader is the name of the header that will have the update error
PortainerAgentUpdateErrorHeader = "X-Portainer-Update-Error"
)
type (
// UpdateScheduleID represents an Edge schedule identifier
UpdateScheduleID int
// UpdateSchedule represents a schedule for update/rollback of edge devices
UpdateSchedule struct {
// EdgeUpdateSchedule Identifier
ID UpdateScheduleID `json:"id" example:"1"`
// Name of the schedule
Name string `json:"name" example:"Update Schedule"`
// Type of the schedule
Time int64 `json:"time" example:"1564897200"`
// EdgeGroups to be updated
GroupIDs []portainer.EdgeGroupID `json:"groupIds" example:"1"`
// Type of the update (1 - update, 2 - rollback)
Type UpdateScheduleType `json:"type" example:"1" enums:"1,2"`
// Status of the schedule, grouped by environment id
Status map[portainer.EndpointID]UpdateScheduleStatus `json:"status"`
// Created timestamp
Created int64 `json:"created" example:"1564897200"`
// Created by user id
CreatedBy portainer.UserID `json:"createdBy" example:"1"`
}
// UpdateScheduleType represents type of an Edge update schedule
UpdateScheduleType int
// UpdateScheduleStatus represents status of an Edge update schedule
UpdateScheduleStatus struct {
// Status of the schedule (0 - pending, 1 - failed, 2 - success)
Status UpdateScheduleStatusType `json:"status" example:"1" enums:"1,2,3"`
// Error message if status is failed
Error string `json:"error" example:""`
// Target version of the edge agent
TargetVersion string `json:"targetVersion" example:"1"`
// Current version of the edge agent
CurrentVersion string `json:"currentVersion" example:"1"`
}
// UpdateScheduleStatusType represents status type of an Edge update schedule
UpdateScheduleStatusType int
VersionUpdateRequest struct {
// Target version
Version string
// Scheduled time
ScheduledTime int64
// If need to update
Active bool
// Update schedule ID
ScheduleID UpdateScheduleID
}
// VersionUpdateStatus represents the status of an agent version update
VersionUpdateStatus struct {
Status UpdateScheduleStatusType
ScheduleID UpdateScheduleID
Error string
}
// EndpointUpdateScheduleRelation represents the relation between an environment(endpoint) and an update schedule
EndpointUpdateScheduleRelation struct {
EnvironmentID portainer.EndpointID `json:"environmentId"`
ScheduleID UpdateScheduleID `json:"scheduleId"`
TargetVersion string `json:"targetVersion"`
Status UpdateScheduleStatusType `json:"status"`
Error string `json:"error"`
Type UpdateScheduleType `json:"type"`
ScheduledTime int64 `json:"scheduledTime"`
}
)
const (
_ UpdateScheduleType = iota
// UpdateScheduleUpdate represents an edge device scheduled for an update
UpdateScheduleUpdate
// UpdateScheduleRollback represents an edge device scheduled for a rollback
UpdateScheduleRollback
)
const (
// UpdateScheduleStatusPending represents a pending edge update schedule
UpdateScheduleStatusPending UpdateScheduleStatusType = iota
// UpdateScheduleStatusError represents a failed edge update schedule
UpdateScheduleStatusError
// UpdateScheduleStatusSuccess represents a successful edge update schedule
UpdateScheduleStatusSuccess
)

View File

@@ -8,15 +8,13 @@ import (
"path"
"strings"
"github.com/pkg/errors"
libstack "github.com/portainer/docker-compose-wrapper"
"github.com/portainer/docker-compose-wrapper/compose"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/proxy/factory"
"github.com/portainer/portainer/api/internal/stackutils"
"github.com/portainer/portainer/api/stacks/stackutils"
"github.com/pkg/errors"
)
// ComposeStackManager is a wrapper for docker-compose binary
@@ -26,11 +24,7 @@ type ComposeStackManager struct {
}
// NewComposeStackManager returns a docker-compose wrapper if corresponding binary present, otherwise nil
func NewComposeStackManager(binaryPath string, configPath string, proxyManager *proxy.Manager) (*ComposeStackManager, error) {
deployer, err := compose.NewComposeDeployer(binaryPath, configPath)
if err != nil {
return nil, err
}
func NewComposeStackManager(deployer libstack.Deployer, proxyManager *proxy.Manager) (*ComposeStackManager, error) {
return &ComposeStackManager{
deployer: deployer,
@@ -44,7 +38,7 @@ func (manager *ComposeStackManager) ComposeSyntaxMaxVersion() string {
}
// Up builds, (re)creates and starts containers in the background. Wraps `docker-compose up -d` command
func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint, forceRereate bool) error {
func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint, forceRecreate bool) error {
url, proxy, err := manager.fetchEndpointProxy(endpoint)
if err != nil {
return errors.Wrap(err, "failed to fetch environment proxy")
@@ -54,13 +48,21 @@ func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Sta
defer proxy.Close()
}
envFile, err := createEnvFile(stack)
envFilePath, err := createEnvFile(stack)
if err != nil {
return errors.Wrap(err, "failed to create env file")
}
filePaths := stackutils.GetStackFilePaths(stack)
err = manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile, forceRereate)
filePaths := stackutils.GetStackFilePaths(stack, false)
err = manager.deployer.Deploy(ctx, filePaths, libstack.DeployOptions{
Options: libstack.Options{
WorkingDir: stack.ProjectPath,
EnvFilePath: envFilePath,
Host: url,
ProjectName: stack.Name,
},
ForceRecreate: forceRecreate,
})
return errors.Wrap(err, "failed to deploy a stack")
}
@@ -74,14 +76,19 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
defer proxy.Close()
}
envFile, err := createEnvFile(stack)
envFilePath, err := createEnvFile(stack)
if err != nil {
return errors.Wrap(err, "failed to create env file")
}
filePaths := stackutils.GetStackFilePaths(stack)
filePaths := stackutils.GetStackFilePaths(stack, false)
err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile)
err = manager.deployer.Remove(ctx, filePaths, libstack.Options{
WorkingDir: stack.ProjectPath,
EnvFilePath: envFilePath,
Host: url,
ProjectName: stack.Name,
})
return errors.Wrap(err, "failed to remove a stack")
}
@@ -96,13 +103,18 @@ func (manager *ComposeStackManager) Pull(ctx context.Context, stack *portainer.S
defer proxy.Close()
}
envFile, err := createEnvFile(stack)
envFilePath, err := createEnvFile(stack)
if err != nil {
return errors.Wrap(err, "failed to create env file")
}
filePaths := stackutils.GetStackFilePaths(stack)
err = manager.deployer.Pull(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile)
filePaths := stackutils.GetStackFilePaths(stack, false)
err = manager.deployer.Pull(ctx, filePaths, libstack.Options{
WorkingDir: stack.ProjectPath,
EnvFilePath: envFilePath,
Host: url,
ProjectName: stack.Name,
})
return errors.Wrap(err, "failed to pull images of the stack")
}
@@ -127,7 +139,7 @@ func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpo
// createEnvFile creates a file that would hold both "in-place" and default environment variables.
// It will return the name of the file if the stack has "in-place" env vars, otherwise empty string.
func createEnvFile(stack *portainer.Stack) (string, error) {
if stack.Env == nil || len(stack.Env) == 0 {
if len(stack.Env) == 0 {
return "", nil
}

View File

@@ -9,6 +9,7 @@ import (
"strings"
"testing"
"github.com/portainer/docker-compose-wrapper/compose"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/testhelpers"
@@ -47,7 +48,12 @@ func Test_UpAndDown(t *testing.T) {
stack, endpoint := setup(t)
w, err := NewComposeStackManager("", "", nil)
deployer, err := compose.NewComposeDeployer("", "")
if err != nil {
t.Fatal(err)
}
w, err := NewComposeStackManager(deployer, nil)
if err != nil {
t.Fatalf("Failed creating manager: %s", err)
}

View File

@@ -1,7 +1,7 @@
package exec
import (
"io/ioutil"
"io"
"os"
"path"
"testing"
@@ -55,7 +55,7 @@ func Test_createEnvFile(t *testing.T) {
assert.Equal(t, "stack.env", result)
f, _ := os.Open(path.Join(dir, "stack.env"))
content, _ := ioutil.ReadAll(f)
content, _ := io.ReadAll(f)
assert.Equal(t, tt.expected, string(content))
} else {
@@ -80,7 +80,7 @@ func Test_createEnvFile_mergesDefultAndInplaceEnvVars(t *testing.T) {
assert.NoError(t, err)
assert.FileExists(t, path.Join(dir, "stack.env"))
f, _ := os.Open(path.Join(dir, "stack.env"))
content, _ := ioutil.ReadAll(f)
content, _ := io.ReadAll(f)
assert.Equal(t, []byte("VAR1=VAL1\nVAR2=VAL2\n\nVAR1=NEW_VAL1\nVAR3=VAL3\n"), content)
}

View File

@@ -6,6 +6,7 @@ import (
type kubernetesMockDeployer struct{}
// NewKubernetesDeployer creates a mock kubernetes deployer
func NewKubernetesDeployer() portainer.KubernetesDeployer {
return &kubernetesMockDeployer{}
}

View File

@@ -9,14 +9,14 @@ import (
"runtime"
"strings"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/proxy/factory"
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/kubernetes/cli"
portainer "github.com/portainer/portainer/api"
"github.com/pkg/errors"
)
// KubernetesDeployer represents a service to deploy resources inside a Kubernetes environment(endpoint).
@@ -49,7 +49,7 @@ func (deployer *KubernetesDeployer) getToken(userID portainer.UserID, endpoint *
return "", err
}
tokenCache := deployer.kubernetesTokenCacheManager.GetOrCreateTokenCache(int(endpoint.ID))
tokenCache := deployer.kubernetesTokenCacheManager.GetOrCreateTokenCache(endpoint.ID)
tokenManager, err := kubernetes.NewTokenManager(kubeCLI, deployer.dataStore, tokenCache, setLocalAdminToken)
if err != nil {
@@ -73,6 +73,7 @@ func (deployer *KubernetesDeployer) getToken(userID portainer.UserID, endpoint *
if token == "" {
return "", fmt.Errorf("can not get a valid user service account token")
}
return token, nil
}

View File

@@ -14,7 +14,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/registryutils"
"github.com/portainer/portainer/api/internal/stackutils"
"github.com/portainer/portainer/api/stacks/stackutils"
)
// SwarmStackManager represents a service for managing stacks.
@@ -90,7 +90,7 @@ func (manager *SwarmStackManager) Logout(endpoint *portainer.Endpoint) error {
// Deploy executes the docker stack deploy command.
func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, pullImage bool, endpoint *portainer.Endpoint) error {
filePaths := stackutils.GetStackFilePaths(stack)
filePaths := stackutils.GetStackFilePaths(stack, false)
command, args, err := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
if err != nil {
return err
@@ -203,12 +203,7 @@ func (manager *SwarmStackManager) updateDockerCLIConfiguration(configPath string
headersObject["X-PortainerAgent-Signature"] = signature
headersObject["X-PortainerAgent-PublicKey"] = manager.signatureService.EncodedPublicKey()
err = manager.fileService.WriteJSONToFile(configFilePath, config)
if err != nil {
return err
}
return nil
return manager.fileService.WriteJSONToFile(configFilePath, config)
}
func (manager *SwarmStackManager) retrieveConfigurationFromDisk(path string) (map[string]interface{}, error) {

View File

@@ -1,7 +1,6 @@
package filesystem
import (
"io/ioutil"
"os"
"path"
"path/filepath"
@@ -19,12 +18,12 @@ func Test_copyFile_returnsError_whenSourceDoesNotExist(t *testing.T) {
func Test_copyFile_shouldMakeAbackup(t *testing.T) {
tmpdir := t.TempDir()
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
os.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
err := copyFile(path.Join(tmpdir, "origin"), path.Join(tmpdir, "copy"))
assert.NoError(t, err)
copyContent, _ := ioutil.ReadFile(path.Join(tmpdir, "copy"))
copyContent, _ := os.ReadFile(path.Join(tmpdir, "copy"))
assert.Equal(t, content, copyContent)
}
@@ -59,13 +58,13 @@ func Test_CopyPath_shouldSkipWhenNotExist(t *testing.T) {
func Test_CopyPath_shouldCopyFile(t *testing.T) {
tmpdir := t.TempDir()
content := []byte("content")
ioutil.WriteFile(path.Join(tmpdir, "file"), content, 0600)
os.WriteFile(path.Join(tmpdir, "file"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "backup"), 0700)
err := CopyPath(path.Join(tmpdir, "file"), path.Join(tmpdir, "backup"))
assert.NoError(t, err)
copyContent, err := ioutil.ReadFile(path.Join(tmpdir, "backup", "file"))
copyContent, err := os.ReadFile(path.Join(tmpdir, "backup", "file"))
assert.NoError(t, err)
assert.Equal(t, content, copyContent)
}

View File

@@ -62,6 +62,10 @@ const (
SSLKeyFilename = "key.pem"
// SSLCACertFilename represents the CA ssl certificate file name for mTLS
SSLCACertFilename = "ca-cert.pem"
MTLSCertFilename = "mtls-cert.pem"
MTLSCACertFilename = "mtls-ca-cert.pem"
MTLSKeyFilename = "mtls-key.pem"
)
// ErrUndefinedTLSFileType represents an error returned on undefined TLS file type
@@ -163,7 +167,7 @@ func (service *Service) Copy(fromFilePath string, toFilePath string, deleteIfExi
}
if !exists {
return errors.New(fmt.Sprintf("File (%s) doesn't exist", fromFilePath))
return fmt.Errorf("File (%s) doesn't exist", fromFilePath)
}
finput, err := os.Open(fromFilePath)
@@ -489,14 +493,7 @@ func (service *Service) createDirectoryInStore(name string) error {
func (service *Service) createFileInStore(filePath string, r io.Reader) error {
path := service.wrapFileStore(filePath)
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, r)
return err
return CreateFile(path, r)
}
// createBackupFileInStore makes a copy in the file store.
@@ -670,6 +667,14 @@ func (service *Service) GetDefaultSSLCertsPath() (string, string) {
return service.wrapFileStore(certPath), service.wrapFileStore(keyPath)
}
func defaultMTLSCertPathUnderFileStore() (string, string, string) {
certPath := JoinPaths(SSLCertPath, MTLSCertFilename)
caCertPath := JoinPaths(SSLCertPath, MTLSCACertFilename)
keyPath := JoinPaths(SSLCertPath, MTLSKeyFilename)
return certPath, caCertPath, keyPath
}
// StoreSSLCertPair stores a ssl certificate pair
func (service *Service) StoreSSLCertPair(cert, key []byte) (string, string, error) {
certPath, keyPath := defaultCertPathUnderFileStore()
@@ -762,3 +767,39 @@ func (service *Service) StoreFDOProfileFileFromBytes(fdoProfileIdentifier string
return service.wrapFileStore(filePath), nil
}
func CreateFile(path string, r io.Reader) error {
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, r)
return err
}
func (service *Service) StoreMTLSCertificates(cert, caCert, key []byte) (string, string, string, error) {
certPath, caCertPath, keyPath := defaultMTLSCertPathUnderFileStore()
r := bytes.NewReader(cert)
err := service.createFileInStore(certPath, r)
if err != nil {
return "", "", "", err
}
r = bytes.NewReader(caCert)
err = service.createFileInStore(caCertPath, r)
if err != nil {
return "", "", "", err
}
r = bytes.NewReader(key)
err = service.createFileInStore(keyPath, r)
if err != nil {
return "", "", "", err
}
return service.wrapFileStore(certPath), service.wrapFileStore(caCertPath), service.wrapFileStore(keyPath), nil
}

View File

@@ -1,7 +1,7 @@
package filesystem
import (
"io/ioutil"
"os"
"path"
"testing"
@@ -16,7 +16,7 @@ func Test_WriteFile_CanStoreContentInANewFile(t *testing.T) {
err := WriteToFile(tmpFilePath, content)
assert.NoError(t, err)
fileContent, _ := ioutil.ReadFile(tmpFilePath)
fileContent, _ := os.ReadFile(tmpFilePath)
assert.Equal(t, content, fileContent)
}
@@ -31,7 +31,7 @@ func Test_WriteFile_CanOverwriteExistingFile(t *testing.T) {
err = WriteToFile(tmpFilePath, content)
assert.NoError(t, err)
fileContent, _ := ioutil.ReadFile(tmpFilePath)
fileContent, _ := os.ReadFile(tmpFilePath)
assert.Equal(t, content, fileContent)
}
@@ -43,6 +43,6 @@ func Test_WriteFile_CanWriteANestedPath(t *testing.T) {
err := WriteToFile(tmpFilePath, content)
assert.NoError(t, err)
fileContent, _ := ioutil.ReadFile(tmpFilePath)
fileContent, _ := os.ReadFile(tmpFilePath)
assert.Equal(t, content, fileContent)
}

View File

@@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
@@ -17,6 +16,7 @@ import (
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/pkg/errors"
"github.com/portainer/portainer/api/archive"
gittypes "github.com/portainer/portainer/api/git/types"
)
const (
@@ -99,7 +99,7 @@ func (a *azureClient) downloadZipFromAzureDevOps(ctx context.Context, opt cloneO
if err != nil {
return "", errors.WithMessage(err, "failed to build download url")
}
zipFile, err := ioutil.TempFile("", "azure-git-repo-*.zip")
zipFile, err := os.CreateTemp("", "azure-git-repo-*.zip")
if err != nil {
return "", errors.WithMessage(err, "failed to create temp file")
}
@@ -483,9 +483,9 @@ func (a *azureClient) listFiles(ctx context.Context, opt fetchOption) ([]string,
func checkAzureStatusCode(err error, code int) error {
if code == http.StatusNotFound {
return ErrIncorrectRepositoryURL
return gittypes.ErrIncorrectRepositoryURL
} else if code == http.StatusUnauthorized || code == http.StatusNonAuthoritativeInfo {
return ErrAuthenticationFailure
return gittypes.ErrAuthenticationFailure
}
return err
}

View File

@@ -9,6 +9,7 @@ import (
"time"
_ "github.com/joho/godotenv/autoload"
gittypes "github.com/portainer/portainer/api/git/types"
"github.com/stretchr/testify/assert"
)
@@ -145,7 +146,7 @@ func TestService_ListFiles_Azure(t *testing.T) {
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -161,7 +162,7 @@ func TestService_ListFiles_Azure(t *testing.T) {
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -240,7 +241,7 @@ func TestService_ListFiles_Azure(t *testing.T) {
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrIncorrectRepositoryURL,
err: gittypes.ErrIncorrectRepositoryURL,
},
},
}

View File

@@ -7,6 +7,7 @@ import (
"net/url"
"testing"
gittypes "github.com/portainer/portainer/api/git/types"
"github.com/stretchr/testify/assert"
)
@@ -466,7 +467,7 @@ func Test_listRefs_azure(t *testing.T) {
password: "test-token",
},
expect: expectResult{
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -477,7 +478,7 @@ func Test_listRefs_azure(t *testing.T) {
password: "",
},
expect: expectResult{
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -488,7 +489,7 @@ func Test_listRefs_azure(t *testing.T) {
password: accessToken,
},
expect: expectResult{
err: ErrIncorrectRepositoryURL,
err: gittypes.ErrIncorrectRepositoryURL,
},
},
}
@@ -540,7 +541,7 @@ func Test_listFiles_azure(t *testing.T) {
},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -555,7 +556,7 @@ func Test_listFiles_azure(t *testing.T) {
},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -599,7 +600,7 @@ func Test_listFiles_azure(t *testing.T) {
},
expect: expectResult{
shouldFail: true,
err: ErrIncorrectRepositoryURL,
err: gittypes.ErrIncorrectRepositoryURL,
},
},
}

View File

@@ -6,14 +6,14 @@ import (
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/pkg/errors"
gittypes "github.com/portainer/portainer/api/git/types"
)
type gitClient struct {
@@ -41,7 +41,7 @@ func (c *gitClient) download(ctx context.Context, dst string, opt cloneOption) e
if err != nil {
if err.Error() == "authentication required" {
return ErrAuthenticationFailure
return gittypes.ErrAuthenticationFailure
}
return errors.Wrap(err, "failed to clone git repository")
}
@@ -66,7 +66,7 @@ func (c *gitClient) latestCommitID(ctx context.Context, opt fetchOption) (string
refs, err := remote.List(listOptions)
if err != nil {
if err.Error() == "authentication required" {
return "", ErrAuthenticationFailure
return "", gittypes.ErrAuthenticationFailure
}
return "", errors.Wrap(err, "failed to list repository refs")
}
@@ -172,9 +172,9 @@ func (c *gitClient) listFiles(ctx context.Context, opt fetchOption) ([]string, e
func checkGitError(err error) error {
errMsg := err.Error()
if errMsg == "repository not found" {
return ErrIncorrectRepositoryURL
return gittypes.ErrIncorrectRepositoryURL
} else if errMsg == "authentication required" {
return ErrAuthenticationFailure
return gittypes.ErrAuthenticationFailure
}
return err
}

View File

@@ -6,6 +6,7 @@ import (
"testing"
"time"
gittypes "github.com/portainer/portainer/api/git/types"
"github.com/stretchr/testify/assert"
)
@@ -99,7 +100,7 @@ func TestService_ListFiles_GitHub(t *testing.T) {
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -115,7 +116,7 @@ func TestService_ListFiles_GitHub(t *testing.T) {
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -194,7 +195,7 @@ func TestService_ListFiles_GitHub(t *testing.T) {
extensions: []string{},
expect: expectResult{
shouldFail: true,
err: ErrIncorrectRepositoryURL,
err: gittypes.ErrIncorrectRepositoryURL,
},
},
}
@@ -273,7 +274,8 @@ func TestService_purgeCacheByTTL_Github(t *testing.T) {
func TestService_canStopCacheCleanTimer_whenContextDone(t *testing.T) {
timeout := 10 * time.Millisecond
deadlineCtx, _ := context.WithDeadline(context.TODO(), time.Now().Add(10*timeout))
deadlineCtx, cancel := context.WithDeadline(context.TODO(), time.Now().Add(10*timeout))
defer cancel()
service := NewService(deadlineCtx)
assert.False(t, service.timerHasStopped(), "timer should not be stopped")
@@ -296,9 +298,9 @@ func TestService_HardRefresh_ListRefs_GitHub(t *testing.T) {
assert.GreaterOrEqual(t, len(refs), 1)
assert.Equal(t, 1, service.repoRefCache.Len())
refs, err = service.ListRefs(repositoryUrl, username, "fake-token", true)
refs, err = service.ListRefs(repositoryUrl, username, "fake-token", false)
assert.Error(t, err)
assert.Equal(t, 0, service.repoRefCache.Len())
assert.Equal(t, 1, service.repoRefCache.Len())
}
func TestService_HardRefresh_ListRefs_And_RemoveAllCaches_GitHub(t *testing.T) {
@@ -324,10 +326,13 @@ func TestService_HardRefresh_ListRefs_And_RemoveAllCaches_GitHub(t *testing.T) {
assert.GreaterOrEqual(t, len(files), 1)
assert.Equal(t, 2, service.repoFileCache.Len())
refs, err = service.ListRefs(repositoryUrl, username, "fake-token", false)
assert.Error(t, err)
assert.Equal(t, 1, service.repoRefCache.Len())
refs, err = service.ListRefs(repositoryUrl, username, "fake-token", true)
assert.Error(t, err)
assert.Equal(t, 0, service.repoRefCache.Len())
assert.Equal(t, 1, service.repoRefCache.Len())
// The relevant file caches should be removed too
assert.Equal(t, 0, service.repoFileCache.Len())
}

View File

@@ -6,11 +6,11 @@ import (
"path/filepath"
"testing"
"github.com/portainer/portainer/api/archive"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/pkg/errors"
"github.com/portainer/portainer/api/archive"
gittypes "github.com/portainer/portainer/api/git/types"
"github.com/stretchr/testify/assert"
)
@@ -148,7 +148,7 @@ func Test_listRefsPrivateRepository(t *testing.T) {
password: "test-token",
},
expect: expectResult{
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -159,7 +159,7 @@ func Test_listRefsPrivateRepository(t *testing.T) {
password: "",
},
expect: expectResult{
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -170,7 +170,7 @@ func Test_listRefsPrivateRepository(t *testing.T) {
password: accessToken,
},
expect: expectResult{
err: ErrIncorrectRepositoryURL,
err: gittypes.ErrIncorrectRepositoryURL,
},
},
}
@@ -222,7 +222,7 @@ func Test_listFilesPrivateRepository(t *testing.T) {
},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -237,7 +237,7 @@ func Test_listFilesPrivateRepository(t *testing.T) {
},
expect: expectResult{
shouldFail: true,
err: ErrAuthenticationFailure,
err: gittypes.ErrAuthenticationFailure,
},
},
{
@@ -281,7 +281,7 @@ func Test_listFilesPrivateRepository(t *testing.T) {
},
expect: expectResult{
shouldFail: true,
err: ErrIncorrectRepositoryURL,
err: gittypes.ErrIncorrectRepositoryURL,
},
},
}

View File

@@ -2,19 +2,15 @@ package git
import (
"context"
"errors"
"log"
"strings"
"sync"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/rs/zerolog/log"
)
var (
ErrIncorrectRepositoryURL = errors.New("Git repository could not be found, please ensure that the URL is correct.")
ErrAuthenticationFailure = errors.New("Authentication failed, please ensure that the git credentials are correct.")
REPOSITORY_CACHE_SIZE = 4
REPOSITORY_CACHE_TTL = 5 * time.Minute
)
@@ -78,12 +74,12 @@ func newService(ctx context.Context, cacheSize int, cacheTTL time.Duration) *Ser
var err error
service.repoRefCache, err = lru.New(cacheSize)
if err != nil {
log.Printf("[DEBUG] [git] [message: failed to create ref cache: %v\n", err)
log.Debug().Err(err).Msg("failed to create ref cache")
}
service.repoFileCache, err = lru.New(cacheSize)
if err != nil {
log.Printf("[DEBUG] [git] [message: failed to create file cache: %v\n", err)
log.Debug().Err(err).Msg("failed to create file cache")
}
if cacheTTL > 0 {
@@ -167,9 +163,10 @@ func (service *Service) LatestCommitID(repositoryURL, referenceName, username, p
// ListRefs will list target repository's references without cloning the repository
func (service *Service) ListRefs(repositoryURL, username, password string, hardRefresh bool) ([]string, error) {
refCacheKey := generateCacheKey(repositoryURL, password)
if service.cacheEnabled && hardRefresh {
// Should remove the cache explicitly, so that the following normal list can show the correct result
service.repoRefCache.Remove(repositoryURL)
service.repoRefCache.Remove(refCacheKey)
// Remove file caches pointed to the same repository
for _, fileCacheKey := range service.repoFileCache.Keys() {
key, ok := fileCacheKey.(string)
@@ -183,7 +180,7 @@ func (service *Service) ListRefs(repositoryURL, username, password string, hardR
if service.repoRefCache != nil {
// Lookup the refs cache first
cache, ok := service.repoRefCache.Get(repositoryURL)
cache, ok := service.repoRefCache.Get(refCacheKey)
if ok {
refs, success := cache.([]string)
if success {
@@ -215,7 +212,7 @@ func (service *Service) ListRefs(repositoryURL, username, password string, hardR
}
if service.cacheEnabled && service.repoRefCache != nil {
service.repoRefCache.Add(options.repositoryUrl, refs)
service.repoRefCache.Add(refCacheKey, refs)
}
return refs, nil
}

View File

@@ -1,5 +1,12 @@
package gittypes
import "errors"
var (
ErrIncorrectRepositoryURL = errors.New("Git repository could not be found, please ensure that the URL is correct.")
ErrAuthenticationFailure = errors.New("Authentication failed, please ensure that the git credentials are correct.")
)
// RepoConfig represents a configuration for a repo
type RepoConfig struct {
// The repo url

View File

@@ -3,11 +3,14 @@ module github.com/portainer/portainer/api
go 1.18
require (
github.com/Masterminds/semver v1.5.0
github.com/Microsoft/go-winio v0.5.1
github.com/VictoriaMetrics/fastcache v1.12.0
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/aws/aws-sdk-go-v2 v1.11.1
github.com/aws/aws-sdk-go-v2/credentials v1.6.2
github.com/aws/aws-sdk-go-v2/service/ecr v1.10.1
github.com/cbroglie/mustache v1.4.0
github.com/coreos/go-semver v0.3.0
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
github.com/docker/cli v20.10.9+incompatible
@@ -19,7 +22,7 @@ require (
github.com/go-ldap/ldap/v3 v3.1.8
github.com/go-playground/validator/v10 v10.10.1
github.com/gofrs/uuid v4.0.0+incompatible
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v4 v4.2.0
github.com/google/go-cmp v0.5.8
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.7.3
@@ -31,31 +34,32 @@ require (
github.com/json-iterator/go v1.1.12
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021
github.com/portainer/docker-compose-wrapper v0.0.0-20221215210951-2c30d1b17a27
github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108
github.com/portainer/libhttp v0.0.0-20220916153711-5d61e12f4b0a
github.com/rkl-/digest v0.0.0-20180419075440-8316caa4a777
github.com/portainer/libhttp v0.0.0-20221121135534-76f46e09c9a9
github.com/portainer/portainer/pkg/libhelm v0.0.0-20221201012749-4fee35924724
github.com/portainer/portainer/third_party/digest v0.0.0-20221201002639-8fd0efa34f73
github.com/robfig/cron/v3 v3.0.1
github.com/rs/zerolog v1.28.0
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.8.1
github.com/viney-shih/go-lock v1.1.1
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/api v0.22.5
k8s.io/apimachinery v0.22.5
k8s.io/client-go v0.22.5
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.25.3
k8s.io/apimachinery v0.25.3
k8s.io/client-go v0.25.3
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
)
require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect
@@ -63,33 +67,41 @@ require (
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.1 // indirect
github.com/aws/smithy-go v1.9.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.0+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/evanphx/json-patch v4.11.0+incompatible // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.3.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.1.0 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/jpillora/ansi v1.0.2 // indirect
github.com/jpillora/requestlog v1.0.0 // indirect
github.com/jpillora/sizestr v1.0.0 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
@@ -99,12 +111,12 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.15.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
github.com/x448/float16 v0.8.4 // indirect
@@ -112,20 +124,21 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gotest.tools/v3 v3.0.3 // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c // indirect
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
k8s.io/klog/v2 v2.70.1 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

View File

@@ -33,35 +33,34 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE=
github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZOs7ygH5BgQp4N+aYrZ2DNpWZ1KG3VOSOM=
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/aws/aws-sdk-go-v2 v1.11.1 h1:GzvOVAdTbWxhEMRK4FfiblkGverOkAT0UodDxC1jHQM=
@@ -80,7 +79,11 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.6.1/go.mod h1:/73aFBwUl60wKBKhdth2pE
github.com/aws/aws-sdk-go-v2/service/sts v1.10.1/go.mod h1:+BmlPeQ1Y+PuIho93MMKDby12PoUnt1SZXQdEHCzSlw=
github.com/aws/smithy-go v1.9.0 h1:c7FUdEqrQA1/UVKKCNDFQPNKGp4FQg3YW4Ck5SLTG58=
github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/cbroglie/mustache v1.4.0 h1:Azg0dVhxTml5me+7PsZ7WPrQq1Gkf3WApcHMjMprYoU=
github.com/cbroglie/mustache v1.4.0/go.mod h1:SS1FTIghy0sjse4DUVGV1k/40B1qE1XkD9DtDsHo9iM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -98,8 +101,8 @@ github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfD
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/docker/cli v20.10.9+incompatible h1:OJ7YkwQA+k2Oi51lmCojpjiygKpi76P7bg91b2eJxYU=
github.com/docker/cli v20.10.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY=
github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.16+incompatible h1:2Db6ZR/+FUR3hqPMwnogOPHFn405crbpxvWzKovETOQ=
github.com/docker/docker v20.10.16+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -108,23 +111,20 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw=
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo=
@@ -152,34 +152,36 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-ldap/ldap/v3 v3.1.8 h1:5vU/2jOh9HqprwXp8aF915s9p6Z8wmbSEVF7/gdTFhM=
github.com/go-ldap/ldap/v3 v3.1.8/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig=
github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -203,9 +205,12 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -219,7 +224,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -234,13 +238,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
@@ -251,14 +250,11 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -270,6 +266,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669/go.mod h1:kOeLNvjNBGSV3uYtFjvb72+fnZCMFJF1XDvRIjdom0g=
github.com/jpillora/ansi v1.0.2 h1:+Ei5HCAH0xsrQRCT2PDr4mq9r4Gm4tg+arNdXRkB22s=
github.com/jpillora/ansi v1.0.2/go.mod h1:D2tT+6uzJvN1nBVQILYWkIdq7zG+b5gcFN5WI/VyjMY=
@@ -282,8 +280,6 @@ github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVd
github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9/go.mod h1:1ffp+CRe0eAwwRb0/BownUAjMBsmTLwgAvRbfj9dRwE=
github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw=
github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -308,6 +304,8 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
@@ -323,53 +321,40 @@ github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6U
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021 h1:GFTn2e5AyIoBuK6hXbdVNkuV2m450DQnYmgQDZRU3x8=
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021/go.mod h1:WxDlJWZxCnicdLCPnLNEv7/gRhjeIVuCGmsv+iOPH3c=
github.com/portainer/docker-compose-wrapper v0.0.0-20221215210951-2c30d1b17a27 h1:PceCpp86SDYb3lZHT4KpuBCkmcJMW5x1qrdFNEfAdUo=
github.com/portainer/docker-compose-wrapper v0.0.0-20221215210951-2c30d1b17a27/go.mod h1:03UmPLyjiPUexGJuW20mQXvmsoSpeErvMlItJGtq/Ww=
github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a h1:B0z3skIMT+OwVNJPQhKp52X+9OWW6A9n5UWig3lHBJk=
github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a/go.mod h1:n54EEIq+MM0NNtqLeCby8ljL+l275VpolXO0ibHegLE=
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108 h1:5e8KAnDa2G3cEHK7aV/ue8lOaoQwBZUzoALslwWkR04=
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108/go.mod h1:YvYAk7krKTzB+rFwDr0jQ3sQu2BtiXK1AR0sZH7nhJA=
github.com/portainer/libhttp v0.0.0-20220916153711-5d61e12f4b0a h1:BJ5V4EDNhg3ImYbmXnGS8vrMhq6rzsEneIXyJh0g4dc=
github.com/portainer/libhttp v0.0.0-20220916153711-5d61e12f4b0a/go.mod h1:ckuHnoLA5kLuE5WkvPBXmrw63LUMdSH4aX71QRi9y10=
github.com/portainer/libhttp v0.0.0-20221121135534-76f46e09c9a9 h1:L7o0L+1qq+LzKjzgRB6bDIh5ZrZ5A1oSS+WgWzDgJIo=
github.com/portainer/libhttp v0.0.0-20221121135534-76f46e09c9a9/go.mod h1:H49JLiywwLt2rrJVroafEWy8fIs0i7mThAThK40sbb8=
github.com/portainer/portainer/pkg/libhelm v0.0.0-20221201012749-4fee35924724 h1:FZrRVMpxXdUV+p5VSCAy9Uz7RzAeEJr2ytlctvMrsHY=
github.com/portainer/portainer/pkg/libhelm v0.0.0-20221201012749-4fee35924724/go.mod h1:WUdwNVH9GMffP4qf4U2ea2qCYfti2V7S+IhGpO8Sxv0=
github.com/portainer/portainer/third_party/digest v0.0.0-20221201002639-8fd0efa34f73 h1:7bPOnwucE0nor0so1BQJxQKCL5t+vCWO4nAz/S0lci0=
github.com/portainer/portainer/third_party/digest v0.0.0-20221201002639-8fd0efa34f73/go.mod h1:E2w/A6qsKuG2VyiUubPdXpDyPykWfQqxuCs0YNS0MhM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rkl-/digest v0.0.0-20180419075440-8316caa4a777 h1:rDj3WeO+TiWyxfcydUnKegWAZoR5kQsnW0wzhggdOrw=
github.com/rkl-/digest v0.0.0-20180419075440-8316caa4a777/go.mod h1:xRVvTK+cS/dJSvrOufGUQFWfgvE7yXExeng96n8377o=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -386,20 +371,24 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
github.com/viney-shih/go-lock v1.1.1 h1:SwzDPPAiHpcwGCr5k8xD15d2gQSo8d4roRYd7TDV2eI=
@@ -432,11 +421,11 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -447,8 +436,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -471,7 +458,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -494,7 +480,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
@@ -502,18 +487,16 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -527,7 +510,6 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cO
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -537,12 +519,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -556,13 +535,11 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -572,26 +549,27 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -634,7 +612,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -718,8 +695,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -729,14 +706,10 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -745,8 +718,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
@@ -757,28 +731,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.22.5 h1:xk7C+rMjF/EGELiD560jdmwzrB788mfcHiNbMQLIVI8=
k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs=
k8s.io/apimachinery v0.22.5 h1:cIPwldOYm1Slq9VLBRPtEYpyhjIm1C6aAMAoENuvN9s=
k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U=
k8s.io/client-go v0.22.5 h1:I8Zn/UqIdi2r02aZmhaJ1hqMxcpfJ3t5VqvHtctHYFo=
k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ=
k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI=
k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc=
k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
k8s.io/client-go v0.25.3 h1:oB4Dyl8d6UbfDHD8Bv8evKylzs3BXzzufLiO27xuPs0=
k8s.io/client-go v0.25.3/go.mod h1:t39LPczAIMwycjcXkVc+CB+PZV69jQuNx4um5ORDjQA=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw=
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c h1:jvamsI1tn9V0S8jicyX82qaFC0H/NKxv2e5mbqsgR80=
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs=
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA=
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU=
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4=

View File

@@ -10,7 +10,7 @@ import (
"strings"
"time"
"github.com/rkl-/digest"
"github.com/portainer/portainer/third_party/digest"
)
type FDOOwnerClient struct {

View File

@@ -4,7 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
portainer "github.com/portainer/portainer/api"
@@ -33,7 +33,7 @@ func (service *Service) Authorization(configuration portainer.OpenAMTConfigurati
if err != nil {
return "", err
}
responseBody, readErr := ioutil.ReadAll(response.Body)
responseBody, readErr := io.ReadAll(response.Body)
if readErr != nil {
return "", readErr
}

View File

@@ -21,9 +21,6 @@ func (service *Service) enableDeviceFeatures(configuration portainer.OpenAMTConf
jsonValue, _ := json.Marshal(payload)
_, err := service.executeSaveRequest(http.MethodPost, url, configuration.MPSToken, jsonValue)
if err != nil {
return err
}
return nil
return err
}

View File

@@ -6,7 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"time"
@@ -83,11 +83,8 @@ func (service *Service) Configure(configuration portainer.OpenAMTConfiguration)
}
_, err = service.createOrUpdateDomain(configuration)
if err != nil {
return err
}
return nil
return err
}
func (service *Service) executeSaveRequest(method string, url string, token string, payload []byte) ([]byte, error) {
@@ -102,7 +99,7 @@ func (service *Service) executeSaveRequest(method string, url string, token stri
if err != nil {
return nil, err
}
responseBody, readErr := ioutil.ReadAll(response.Body)
responseBody, readErr := io.ReadAll(response.Body)
if readErr != nil {
return nil, readErr
}
@@ -131,7 +128,7 @@ func (service *Service) executeGetRequest(url string, token string) ([]byte, err
if err != nil {
return nil, err
}
responseBody, readErr := ioutil.ReadAll(response.Body)
responseBody, readErr := io.ReadAll(response.Body)
if readErr != nil {
return nil, readErr
}
@@ -229,12 +226,7 @@ func (service *Service) ExecuteDeviceAction(configuration portainer.OpenAMTConfi
}
configuration.MPSToken = token
err = service.executeDeviceAction(configuration, deviceGUID, int(parsedAction))
if err != nil {
return err
}
return nil
return service.executeDeviceAction(configuration, deviceGUID, int(parsedAction))
}
func (service *Service) EnableDeviceFeatures(configuration portainer.OpenAMTConfiguration, deviceGUID string, features portainer.OpenAMTDeviceEnabledFeatures) (string, error) {

View File

@@ -5,7 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"strings"
@@ -96,7 +96,7 @@ func Get(url string, timeout int) ([]byte, error) {
return nil, errInvalidResponseStatus
}
body, err := ioutil.ReadAll(response.Body)
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}

View File

@@ -1,7 +1,6 @@
package auth
import (
"errors"
"net/http"
"strings"
@@ -13,6 +12,7 @@ import (
"github.com/portainer/portainer/api/internal/authorization"
"github.com/asaskevich/govalidator"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
@@ -127,9 +127,9 @@ func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.
}
}
err = handler.addUserIntoTeams(user, ldapSettings)
err = handler.syncUserTeamsWithLDAPGroups(user, ldapSettings)
if err != nil {
log.Warn().Err(err).Msg("unable to automatically add user into teams")
log.Warn().Err(err).Msg("unable to automatically sync user teams with ldap")
}
return handler.writeToken(w, user, false)
@@ -150,7 +150,12 @@ func (handler *Handler) persistAndWriteToken(w http.ResponseWriter, tokenData *p
return response.JSON(w, &authenticateResponse{JWT: token})
}
func (handler *Handler) addUserIntoTeams(user *portainer.User, settings *portainer.LDAPSettings) error {
func (handler *Handler) syncUserTeamsWithLDAPGroups(user *portainer.User, settings *portainer.LDAPSettings) error {
// only sync if there is a group base DN
if len(settings.GroupSearchSettings) == 0 || len(settings.GroupSearchSettings[0].GroupBaseDN) == 0 {
return nil
}
teams, err := handler.DataStore.Team().Teams()
if err != nil {
return err

View File

@@ -22,6 +22,7 @@ func (payload *oauthPayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Code) {
return errors.New("Invalid OAuth authorization code")
}
return nil
}

View File

@@ -3,13 +3,14 @@ package auth
import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/http/security"
"github.com/gorilla/mux"
)
// Handler is the HTTP handler used to handle authentication operations.

View File

@@ -23,7 +23,7 @@ func (handler *Handler) logout(w http.ResponseWriter, r *http.Request) *httperro
return httperror.InternalServerError("Unable to retrieve user details from authentication token", err)
}
handler.KubernetesTokenCacheManager.RemoveUserFromCache(int(tokenData.ID))
handler.KubernetesTokenCacheManager.RemoveUserFromCache(tokenData.ID)
return response.Empty(w)
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"context"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
@@ -38,7 +37,7 @@ func listFiles(dir string) []string {
func contains(t *testing.T, list []string, path string) {
assert.Contains(t, list, path)
copyContent, _ := ioutil.ReadFile(path)
copyContent, _ := os.ReadFile(path)
assert.Equal(t, "content\n", string(copyContent))
}
@@ -56,8 +55,9 @@ func Test_backupHandlerWithoutPassword_shouldCreateATarballArchive(t *testing.T)
body, _ := io.ReadAll(response.Body)
tmpdir := t.TempDir()
archivePath := filepath.Join(tmpdir, "archive.tar.gz")
err := ioutil.WriteFile(archivePath, body, 0600)
err := os.WriteFile(archivePath, body, 0600)
if err != nil {
t.Fatal("Failed to save downloaded .tar.gz archive: ", err)
}
@@ -91,6 +91,7 @@ func Test_backupHandlerWithPassword_shouldCreateEncryptedATarballArchive(t *test
body, _ := io.ReadAll(response.Body)
tmpdir := t.TempDir()
dr, err := crypto.AesDecrypt(bytes.NewReader(body), []byte("secret"))
if err != nil {
t.Fatal("Failed to decrypt archive")

View File

@@ -6,7 +6,6 @@ import (
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/adminmonitor"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/demo"
@@ -71,11 +70,3 @@ func adminAccess(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
})
}
func systemWasInitialized(dataStore dataservices.DataStore) (bool, error) {
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
if err != nil {
return false, err
}
return len(users) > 0, nil
}

View File

@@ -3,20 +3,21 @@ package customtemplates
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"regexp"
"strconv"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
gittypes "github.com/portainer/portainer/api/git/types"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/asaskevich/govalidator"
"github.com/rs/zerolog/log"
)
@@ -141,12 +142,7 @@ func (payload *customTemplateFromFileContentPayload) Validate(r *http.Request) e
return errors.New("Invalid note. <img> tag is not supported")
}
err := validateVariablesDefinitions(payload.Variables)
if err != nil {
return err
}
return nil
return validateVariablesDefinitions(payload.Variables)
}
func isValidNote(note string) bool {
@@ -250,12 +246,7 @@ func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request)
return errors.New("Invalid note. <img> tag is not supported")
}
err := validateVariablesDefinitions(payload.Variables)
if err != nil {
return err
}
return nil
return validateVariablesDefinitions(payload.Variables)
}
func (handler *Handler) createCustomTemplateFromGitRepository(r *http.Request) (*portainer.CustomTemplate, error) {
@@ -290,6 +281,9 @@ func (handler *Handler) createCustomTemplateFromGitRepository(r *http.Request) (
err = handler.GitService.CloneRepository(projectPath, payload.RepositoryURL, payload.RepositoryReferenceName, repositoryUsername, repositoryPassword)
if err != nil {
if err == gittypes.ErrAuthenticationFailure {
return nil, fmt.Errorf("invalid git credential")
}
return nil, err
}
@@ -386,16 +380,13 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er
payload.FileContent = composeFileContent
varsString, _ := request.RetrieveMultiPartFormValue(r, "Variables", true)
err = json.Unmarshal([]byte(varsString), &payload.Variables)
if err != nil {
return errors.New("Invalid variables. Ensure that the variables are valid JSON")
if varsString != "" {
err = json.Unmarshal([]byte(varsString), &payload.Variables)
if err != nil {
return errors.New("Invalid variables. Ensure that the variables are valid JSON")
}
return validateVariablesDefinitions(payload.Variables)
}
err = validateVariablesDefinitions(payload.Variables)
if err != nil {
return err
}
return nil
}

View File

@@ -55,12 +55,7 @@ func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error {
return errors.New("Invalid note. <img> tag is not supported")
}
err := validateVariablesDefinitions(payload.Variables)
if err != nil {
return err
}
return nil
return validateVariablesDefinitions(payload.Variables)
}
// @id CustomTemplateUpdate

Some files were not shown because too many files have changed in this diff Show More