Compare commits

...

544 Commits

Author SHA1 Message Date
Matt Hook
6a3eda4bce fix(doclinks): fix help link paths [EE-6861] (#11417) 2024-03-19 11:46:55 +13:00
Matt Hook
889c36f64a fix(docs): fix all remaining webhook app links [EE-6861] (#11392) 2024-03-18 16:28:43 +13:00
Matt Hook
c8fb3adda3 fix(kube): fix edit application webhook link [EE-6861] (#11390) 2024-03-18 10:21:20 +13:00
cmeng
f15be1d92a fix(stack): prepopulate when creating template from stack EE-6853 (#11379) 2024-03-18 09:36:04 +13:00
Oscar Zhou
d9ae249ffe chore(template/git): sync frontend code from ee (#11343) 2024-03-18 08:55:26 +13:00
Matt Hook
04de06c07f fix(docs): make all doc links versioned [EE-6861] (#11381) 2024-03-15 16:57:42 +13:00
Matt Hook
59d53940fe fix(stacks): update swagger stacks doc description [EE-6860] (#11383) 2024-03-15 16:47:05 +13:00
cmeng
db16888379 fix(container): make blank string as valid value EE-6852 (#11372) 2024-03-15 09:01:42 +13:00
Prabhat Khera
8880876bcd fix(auth): make createAccessToken api backward compatible [EE-6818] (#11327)
* fix(auth): make createAccessToken api backward compatible [EE-6818]

* fix(api): api error message [EE-6818]

* fix messages
2024-03-14 09:02:25 +13:00
Ali
bfe5a49263 fix(app): only show special message when limits change for existing app resource limit [EE-6837] (#11368)
Co-authored-by: testa113 <testa113>
2024-03-14 08:45:53 +13:00
cmeng
6e11c10bab fix(csrf): disable csrf secure cookie EE-6787 (#11299) 2024-03-13 11:22:18 +13:00
LP B
cb9ab3b375 fix(app): views not loading when quickly navigating in app (#11279) 2024-03-12 15:16:19 +01:00
Chaim Lev-Ari
b13dac0f6d fix(docker): apply private uac to edge admin [EE-6788] (#11284) 2024-03-12 09:59:39 +02:00
cmeng
0144a98b3b fix(edge-stack): deploy button is disabled EE-6819 (#11354) 2024-03-12 17:19:45 +13:00
Prabhat Khera
64a08c59e9 address review commets (#11361) 2024-03-12 11:32:03 +13:00
Ali
1090c82beb fix(app): on create don't mention previous values [EE-6837] (#11351)
Co-authored-by: testa113 <testa113>
2024-03-11 16:43:45 +13:00
Prabhat Khera
6094dc115b fix(container): autocomplete off for create container form [EE-6761] (#11337)
* autocomplete off doe create container form

* address review commets

* remove auto complete off from forms
2024-03-11 13:38:59 +13:00
Prabhat Khera
30513695b5 fix(kube): stackname in daemonsets and statefulsets app [EE-6670] (#11353) 2024-03-11 10:04:55 +13:00
Chaim Lev-Ari
dd2be9fb1e refactor(tests): wrap tests explicitly with provider [EE-6686] (#11276) 2024-03-10 14:22:05 +02:00
Chaim Lev-Ari
e265b8b67c fix(kube/config): validate change window start [EE-6830] (#11328) 2024-03-10 09:42:29 +02:00
Matt Hook
cc1ce9412a fix(exec): improve alignment of help icon [EE-6816] (#11340) 2024-03-08 14:03:01 +13:00
Prabhat Khera
8eb8df2b30 fix(kube-stacks): change wordings [EE-6670] (#11335) 2024-03-08 12:15:27 +13:00
Ali
c0bd2dfdaf fix(matomo): stop oauth link event [EE-6779] (#11333) 2024-03-08 10:17:26 +13:00
Matt Hook
bf65a38d5a fix(exec): fix alignment and text size and alignment [EE-6816] (#11324) 2024-03-07 12:57:53 +13:00
cmeng
0ea21f2317 fix(menu): edge compute menu not clickable EE-6804 (#11320) 2024-03-07 12:11:59 +13:00
Prabhat Khera
b5f839a920 fix(stacks): make stackName kube stack specific field [EE-6670] (#11316)
* fix(stacks): make stackName kube stack specific field [EE-6670]

* fix wordings
2024-03-07 11:31:28 +13:00
Prabhat Khera
29025e7dd4 fix(UI): axios progress bar loading issue [EE-6781] (#11290) 2024-03-07 11:30:23 +13:00
Ali
692981b615 fix(time window): show errors for component [EE-6800] (#11318)
Co-authored-by: testa113 <testa113>
2024-03-07 09:03:26 +13:00
Chaim Lev-Ari
d6545b6af5 fix(kube/setup): add a11y labels [EE-6747] (#11308) 2024-03-06 14:57:03 +02:00
Matt Hook
6bbf62fe64 fix(contexthelp): remove extra slash from contexthelp docs link [EE-6780] (#11312) 2024-03-06 16:38:19 +13:00
Matt Hook
6b3ddf11d4 fix(helm): remove helm insights from the stack datatable [EE-6803] (#11313) 2024-03-06 16:36:48 +13:00
Dakota Walsh
77c9124e8a fix(datatable): title size EE-6774 (#11273) 2024-03-06 08:01:45 +13:00
Chaim Lev-Ari
2c3dcdd14e fix(docker/images): export image [EE-6807] (#11305) 2024-03-05 19:30:45 +02:00
matias-portainer
ec913b45d6 fix(edge/templates): get correct default value for selectType env vars EE-6796 (#11293) 2024-03-04 10:35:19 -03:00
Matt Hook
51c672af21 fix(kube): update doc links to match new menu structure [EE-6759] (#11266) 2024-03-01 15:37:32 +13:00
Matt Hook
ff178641be fix(help): add versioned doc links to support LTS/STS docs [EE-6780] (#11282) 2024-03-01 15:36:19 +13:00
cmeng
a43454076b fix(edge-stacks): take not-found stack as removed EE-6758 (#11249) 2024-03-01 11:50:27 +13:00
cmeng
a7eaa0f3fa fix(container): get old container info correctly EE-6716 (#11215) 2024-03-01 09:14:26 +13:00
cmeng
8ad11fc88f fix(stack): more space for add button EE-6773 (#11258) 2024-03-01 09:11:46 +13:00
Chaim Lev-Ari
43a95874f4 fix(auth): prevent unauthorized redirect on page load [EE-6777] (#11265) 2024-02-29 09:41:29 +02:00
Chaim Lev-Ari
b4f4c3212a feat(kube): add a11y props for smoke tests [EE-6747] (#11262) 2024-02-29 09:26:10 +02:00
Chaim Lev-Ari
d44f57ed6f fix(ci): prevent tests from running twice [EE-6728] (#11196) 2024-02-29 08:11:46 +02:00
Chaim Lev-Ari
eba08cdca0 fix(docker): hide write buttons for non authorized [EE-6775] (#11261) 2024-02-27 12:36:47 +02:00
Prabhat Khera
de3a3f88a0 fix(ui): autocomplete on edge custom template and stacks [EE-6761] (#11269) 2024-02-27 20:15:56 +13:00
Matt Hook
f6b2c879bc fix(kube): make app autorefresh and show system settings stay [EE-6771] (#11256) 2024-02-27 11:18:28 +13:00
Prabhat Khera
f5fbcd4d9d fix(stack): auto complete dropdown in docker stacks [EE-6761] (#11254) 2024-02-26 11:43:18 +13:00
Ali
f8b68a809f fix(app): parse nan in validation check [EE-6714] (#11247) 2024-02-26 09:20:59 +13:00
Oscar Zhou
6258c02353 fix(edge/template): validate app template env vars [EE-6743] (#11234) 2024-02-26 09:00:03 +13:00
Chaim Lev-Ari
0fd20277c1 fix(docker): prevent non admins from passing security settings [EE-6765] (#11239) 2024-02-25 11:57:19 +02:00
cmeng
988064a542 fix(stack): make web editor readonly for git template EE-6706 (#11183) 2024-02-23 13:28:20 +13:00
Matt Hook
380b23a9f5 fix(dependancies): update compose and runc [EE-6744] (#11243) 2024-02-23 11:48:49 +13:00
Prabhat Khera
158b43194c fix(ui): turn autocomplete off for git deployment [EE-6761] (#11241) 2024-02-23 08:44:00 +13:00
Ali
1bbe98379a fix(app): NaN validation for autoscaling [EE-6714] (#11238) 2024-02-22 17:36:41 +13:00
Matt Hook
8f9b265f5a fix(helm) tighten up helm requests [EE-6722] (#11233) 2024-02-22 11:35:01 +13:00
Ali
1cdd3fdfe2 fix(input): allow clearing number inputs [EE-6714] (#11187) 2024-02-21 10:43:28 +13:00
Ali
4e95139909 fix(inputlist): update warning style [EE-6737] (#11222) 2024-02-21 08:29:14 +13:00
Matt Hook
704d75596d fix(libhttp): capitalize http error responses for better display [EE-6698] (#11109) 2024-02-21 07:51:29 +13:00
Chaim Lev-Ari
a8938779bf fix(ui): check for authorization [EE-6733] (#11207) 2024-02-20 11:06:05 +02:00
Chaim Lev-Ari
bb6f4e026a fix(kube/apps): move namespace selector in apps view [EE-6612] (#11069) 2024-02-20 10:14:11 +02:00
Ali
b64166ff25 fix(app): remove insight from helm [EE-6693] (#11214)
Co-authored-by: testa113 <testa113>
2024-02-20 17:25:22 +13:00
Ali
bac1c28fa9 fix(app): set values in react autoscaling form section [EE-6740] (#11220) 2024-02-20 09:35:32 +13:00
Prabhat Khera
a17da6d2cd fix(git): update stack name for git stacks [EE-6670] (#11218) 2024-02-20 09:23:50 +13:00
Chaim Lev-Ari
24c2baf6cc feat(a11y): add labels and roles [EE-6717] (#11209) 2024-02-19 16:37:21 +02:00
Oscar Zhou
22b4d029fd fix(edge/template): custom template git fields not pre-filled [EE-6695] (#11113) 2024-02-19 08:39:16 +13:00
Ali
b126472ec7 fix(app): update app type when changing data access policy [EE-6719] (#11210)
Co-authored-by: testa113 <testa113>
2024-02-19 08:08:17 +13:00
Ali
a46fa3b2c4 fix(app): avoid duplicate env requests [EE-6727] (#11193)
Co-authored-by: testa113 <testa113>
2024-02-16 14:02:02 +13:00
Prabhat Khera
a374157d6f fix(ui): update search placeholder [EE-6667] (#11191)
* update search placeholder

* remove box selector description
2024-02-16 12:34:10 +13:00
Matt Hook
861ed662e2 fix(namespace): fix default namespace quota [EE-6700] (#11184) 2024-02-16 08:17:10 +13:00
Chaim Lev-Ari
99b89a8ec5 chore(eslint): add rule to check imports [EE-6730] (#11200) 2024-02-15 17:45:54 +02:00
Chaim Lev-Ari
95750c2339 fix(auth): export hasAuthorizations [EE-6595] (#11198) 2024-02-15 14:05:45 +02:00
Chaim Lev-Ari
165d6165dc feat(ui): restrict views by role [EE-6595] (#11071) 2024-02-15 13:29:55 +02:00
Chaim Lev-Ari
fe6ed55cab feat(edge/stacks): add app templates to deploy types [EE-6632] (#11070) 2024-02-15 09:00:57 +02:00
Chaim Lev-Ari
edea9e3481 feat(auth): add useIsEdgeAdmin hook [EE-6627] (#11101) 2024-02-14 19:50:26 -03:00
Ali
c08b5af85a fix(insight): split insight from input [EE-6693] (#11177)
Co-authored-by: testa113 <testa113>
2024-02-15 10:46:02 +13:00
Prabhat Khera
ed861044a7 Revert "fix(logs): add NOCOLOR option for use when exporting to greylog etc […" (#11178)
This reverts commit aca6d33548.
2024-02-15 06:26:22 +13:00
Chaim Lev-Ari
a83321ebe6 feat(ui): write tests [EE-6685] (#11082) 2024-02-14 17:25:32 +02:00
Ali
513cd9c9b3 fix(configs): correct 'external' display in tables [EE-6649] (#11111)
Co-authored-by: testa113 <testa113>
2024-02-14 11:48:05 +13:00
Ali
dc94bf141e fix(stacks): add app form stacks input [EE-6693] (#11105) 2024-02-14 09:01:02 +13:00
Dakota Walsh
24471a9ae1 fix(restore): add S3 teaser [EE-6675] (#11096) 2024-02-14 08:40:34 +13:00
Matt Hook
aca6d33548 fix(logs): add NOCOLOR option for use when exporting to greylog etc [EE-6696] (#11107) 2024-02-14 07:54:47 +13:00
Ali
ca77b85c65 fix(kube-owner): owner labels from resources created via manifest [EE-6647] (#11103)
Co-authored-by: testa113 <testa113>
2024-02-12 15:30:59 +13:00
Prabhat Khera
1fd4291630 fix(ui): stackname auto fill on create from manifest screen [EE-6688] (#11100)
* fix(ui): stackname auto fill on create from manifest screen [EE-6688]

* address review comment
2024-02-12 10:54:24 +13:00
Ali
08dd7f6d2a fix(auth): isAdmin redirect for wizard [EE-6669] (#11075)
Co-authored-by: testa113 <testa113>
2024-02-12 08:04:44 +13:00
Prabhat Khera
ce4b0e759c fix(ui): scroll issue [EE-6667 (#11085)
* Fix scroll issue

* fix minorissue

* address review comments

* add comment
2024-02-09 15:35:38 +13:00
Steven Kang
538e7a823b fix: pre-release build only after merging (#11098) 2024-02-09 15:26:39 +13:00
Matt Hook
956e8d3c59 fix(docs): fix swagger docs for webhook params [EE-6668] (#11089) 2024-02-09 14:44:29 +13:00
Prabhat Khera
1c5458f0d4 fix(kube): ingress path duplication issue [EE-6649] (#11087) 2024-02-09 07:49:57 +13:00
Prabhat Khera
f6085ffad7 fix stack name update issue (#11065) 2024-02-08 13:51:06 +13:00
Matt Hook
490bda2eaf fix(kube-apps): add helm insights, remove namespace insights panel [EE-6671] (#11078) 2024-02-08 11:18:48 +13:00
Prabhat Khera
d601d8eb7b fix(UI): some minor fixes [EE-6667] (#11062)
* minor tweeks for kubernetes settings

* address review comments
2024-02-06 12:17:35 +13:00
Steven Kang
b0564b9238 Pre-release as part of the CI (#11067)
* feat: add pre-release
* feat: add extension
* feat: fix typo
2024-02-05 18:29:12 +13:00
Prabhat Khera
8922585a70 keep labels on edit ingress, configmaps and secrets (#11063) 2024-02-05 16:30:31 +13:00
Ali
d7cf2284dc fix(r2a): don't set errors to undefined [EE-6665] (#11060)
Co-authored-by: testa113 <testa113>
2024-02-05 14:24:15 +13:00
Dakota Walsh
5ee6efb145 fix(backup): restore over network share EE-6578 (#11044) 2024-02-01 11:41:32 +13:00
Matt Hook
a618ee78e4 fix(helm): minor helm screen page corrections [EE-6642] (#11045) 2024-02-01 11:34:33 +13:00
Ali
9a1604e775 fix(kubeclient): cache kubeclient by user token [EE-6610] (#11039) 2024-01-31 14:50:41 +13:00
Prabhat Khera
9615e678e6 chore(golang): version upgrade to 1.21.6 [EE-6634] (#11036) 2024-01-31 06:28:53 +13:00
Dakota Walsh
e39c19bcca fix(console): export LANG and LC_ALL for kube app console EE-6593 (#11037) 2024-01-30 15:19:53 +13:00
Matt Hook
16ae4f8681 fix(kube): change pod security policy teaser screen wording [EE-6629] (#11035) 2024-01-30 13:03:54 +13:00
Matt Hook
70deba50ba fix(kube): clear kube cache on login/logout [EE-6620] (#11026) 2024-01-30 10:39:12 +13:00
Dakota Walsh
89359dae8c ix(console): docker console UTF-8 EE-6593 (#11034) 2024-01-30 09:34:10 +13:00
Chaim Lev-Ari
97d227be2a fix(swarm/services): convert webhooks API filters to JSON on list request [EE-6621] (#11031)
Co-authored-by: matias-portainer <matias.spinarolli@portainer.io>
2024-01-29 18:08:25 +02:00
Matt Hook
8a98704111 fix(helm): increase default helm timeouts [EE-6617] 2024-01-29 13:03:11 +13:00
Prabhat Khera
46b2175729 fix(kubernetes): placement rules calculations [EE-6552] (#11013) 2024-01-29 08:00:15 +13:00
Chaim Lev-Ari
1561814fe5 feat(gitops): add autocomplete to ref selector [EE-6245] (#10935) 2024-01-28 15:55:10 +02:00
Chaim Lev-Ari
2826a4ce39 feat(custom-templates): filter templates by edge [EE-6565] (#10979) 2024-01-28 15:54:34 +02:00
Matt Hook
441a8bbbbf fix(helm): add clarifying text and new badge to helm user repo settings table [EE-6609] (#11018) 2024-01-26 12:37:13 +13:00
Ali
2248ce0173 fix(secret): update hide secret tooltip [EE-6568] (#11020)
Co-authored-by: testa113 <testa113>
2024-01-26 11:21:34 +13:00
Dakota Walsh
b640b58371 fix(console): use writeUtf8 instead of environment variables EE-6593 (#11019) 2024-01-26 11:21:00 +13:00
Ali
249b6bc628 fix(secrets): teaser wording updates [EE-6568] (#11017) 2024-01-26 10:28:57 +13:00
Chaim Lev-Ari
4a10c2bb07 feat(version): show git commit and env [EE-6021] (#10748) 2024-01-25 07:41:33 +02:00
Chaim Lev-Ari
52db4cba0e fix(storybook): fix msw stories [EE-6503] (#10985) 2024-01-24 10:06:38 +02:00
Chaim Lev-Ari
079bade139 refactor(kube/app): use structuredClone to copy object [EE-6581] (#11004) 2024-01-24 09:31:33 +02:00
Ali
26e52a0f00 fix(pods): don't add labels to old pod that has none [EE-6587] (#11009) 2024-01-24 14:44:15 +13:00
Ali
3ccc764d40 fix(images): update up to date teaser wording [EE-6537] (#11008)
Co-authored-by: testa113 <testa113>
2024-01-24 14:22:15 +13:00
Dakota Walsh
dd068473d2 fix(console): minor typo in tooltip EE-1976 (#11007) 2024-01-24 12:02:56 +13:00
Dakota Walsh
fe47318e26 fix(terminal): display os specific copy/paste tooltip EE-1976 (#10835) 2024-01-24 09:45:40 +13:00
Dakota Walsh
fc7d9ca2cd fix(secrets): add CE teaser EE-6568 (#11001) 2024-01-24 09:44:50 +13:00
Ali
7bf346bd2d fix(app): no summary for existing pvc on edit [EE-6569] (#11003) 2024-01-24 08:09:59 +13:00
Chaim Lev-Ari
8f0f9d7aaa fix(ui): stub unused modules [EE-6583] (#11006) 2024-01-23 15:22:56 +02:00
Chaim Lev-Ari
69c06bc756 feat(ci): replace jest with vitest [EE-6504] (#10997) 2024-01-23 08:42:52 +02:00
Ali
4a19871fcc fix(app): fix capitalisation typos and match EE codebase [EE-6480] (#11002)
Co-authored-by: testa113 <testa113>
2024-01-23 16:28:00 +13:00
Ali
d5080b6884 fix(r2a): fix layout shifting from errors showing as undefined [EE-6570] (#11000) 2024-01-23 14:16:34 +13:00
Prabhat Khera
f7840e0407 fix(ui): mark resources system correctly [EE-6558] (#10996)
* fix(ui): mark resources system correctly [EE-6558]

* address review comments
2024-01-23 13:49:25 +13:00
andres-portainer
85ae705833 fix(gitops): add singleflight behavior to RedeployWhenChanged calls EE-6377 (#10734) 2024-01-22 19:41:48 -03:00
Ali
77c38306b2 fix(app): get min resource limits [EE-6567] (#10994)
Co-authored-by: testa113 <testa113>
2024-01-23 11:20:24 +13:00
Ali
b81babe682 fix(app): no summary for existing pvc [EE-6569] (#10995) 2024-01-23 11:19:52 +13:00
Ali
4c0049edbe fix(app): allow editing pod services [EE-6480] (#10875)
* fix(app): allow editing pod services [EE-6480]
* address review comment

---------

Co-authored-by: testa113 <testa113>
Co-authored-by: prabhat khera <prabhat.khera@portainer.io>
2024-01-23 10:10:16 +13:00
Oscar Zhou
7cba02226e fix(container): duplicate/edit button causes empty container screen [EE-6566] (#10982) 2024-01-22 10:28:16 +13:00
Ali
a15b7cf39a fix(app): fix namespace validation message for admin [EE-6561] (#10992)
Co-authored-by: testa113 <testa113>
2024-01-22 09:30:11 +13:00
Dakota Walsh
36ab4dfb1a Revert "fix(docs): add APIKey Digest example EE-6199 (#10980)" (#10981)
This reverts commit 7b6e106606.
2024-01-19 14:02:19 +13:00
Dakota Walsh
7b6e106606 fix(docs): add APIKey Digest example EE-6199 (#10980) 2024-01-19 13:16:56 +13:00
Ali
5f040bf788 fix(app): namespace selector fixes [EE-6561] (#10977) 2024-01-19 12:20:44 +13:00
Prabhat Khera
a4739f1701 fix messaging for resourse over commit (#10974) 2024-01-19 12:14:47 +13:00
Ali
59f642ea56 fix(app): persisted volume fixes [EE-6554] (#10975)
Co-authored-by: testa113 <testa113>
2024-01-19 12:14:19 +13:00
Oscar Zhou
fa63432695 fix(stack/template): web editor error shows for changing between same mustache templates [EE-6563] (#10976) 2024-01-19 09:28:09 +13:00
Dakota Walsh
1676fefd97 fix(backup): calculate file size correctly EE-6439 (#10919) 2024-01-18 09:00:01 +13:00
Prabhat Khera
bf66b6c5f3 fix(ui): reset auto-scaling formvalues if needed [EE-6544] (#10969) 2024-01-18 07:59:00 +13:00
Chaim Lev-Ari
115b01cee3 fix(docker): include healthy containers in running [EE-6264] (#10746) 2024-01-17 22:30:12 +07:00
Chaim Lev-Ari
a305fe9e4c feat(stacks): hide redeploy for orphaned stacks [EE-5784] (#10841) 2024-01-17 21:45:08 +07:00
Ali
a58b4f479b fix(app): remove duplicate validation messages [EE-5933] (#10967) 2024-01-17 16:30:30 +13:00
Prabhat Khera
93593e1379 fix(ui): update button disabled when manisfest reverted to the orignial content [EE-6544] (#10968) 2024-01-17 13:56:10 +13:00
Prabhat Khera
51ae2198f6 fix typo in app name (#10965) 2024-01-17 12:15:58 +13:00
Prabhat Khera
ccc97e6f78 fix(ui): app summary [EE-6515] (#10966) 2024-01-17 12:15:22 +13:00
Dakota Walsh
3f28d56bfc fix(teams): show add user notification EE-4899 (#10873) 2024-01-17 12:14:05 +13:00
Matt Hook
3103d498cf fix(docs): fix minor swagger issue and upgrade swag [EE-6548] 2024-01-17 11:27:57 +13:00
Oscar Zhou
47f29002f0 fix(edgestack): repull image not work in git autoupdate [EE-6430] (#10952) 2024-01-17 10:20:59 +13:00
Ali
787c7ec4cc fix(app): remove canUndo function from environment variables [EE-6232] (#10961)
Co-authored-by: testa113 <testa113>
2024-01-17 10:13:53 +13:00
Ali
a8e53a4510 fix(app): hide placement form section [EE-6386] (#10964)
Co-authored-by: testa113 <testa113>
2024-01-17 09:34:29 +13:00
Ali
752be47fcc fix(app): get utilization percentage in payload [EE-6387] (#10962)
Co-authored-by: testa113 <testa113>
2024-01-17 08:33:40 +13:00
Ali
95474b7dc5 fix(app): various persisted folder fixes [EE-6235] (#10963)
Co-authored-by: testa113 <testa113>
2024-01-17 08:31:22 +13:00
Prabhat Khera
7a04d1d4ea fix input cursor moving to the end on edit (#10959) 2024-01-16 16:03:01 +13:00
Prabhat Khera
211fff5ed4 update metrics help text (#10960) 2024-01-16 16:02:26 +13:00
Prabhat Khera
2f2cfad722 fix high contrast theme colors (#10872) 2024-01-16 14:32:24 +13:00
Prabhat Khera
380c16c8dd increase the font size for search panel (#10838) 2024-01-16 14:31:39 +13:00
Prabhat Khera
bbf1900677 Disable update application button on load of edit screen (#10957) 2024-01-16 09:31:44 +13:00
Ali
fcc5736d61 fix(app): use isAdmin check on CE [EE-6231] (#10956)
Co-authored-by: testa113 <testa113>
2024-01-15 15:34:21 +13:00
Ali
ae6333bf7c fix(app): remove duplicate values for multinode cluster [EE-6386] (#10947) 2024-01-15 14:34:54 +13:00
Ali
3a959208a8 fix(app): autoscaling min validation [EE-6387] (#10945) 2024-01-15 14:34:16 +13:00
Prabhat Khera
b3b7cfa77f fix(kube): patching stateful service [EE-6523] (#10948) 2024-01-15 13:30:45 +13:00
Ali
6d71a28584 fix(app): improve resource quota error handling [EE-5933] (#10951) 2024-01-15 13:29:35 +13:00
Dakota Walsh
488fcc7cc5 fix(docs): convert APIKey to string EE-6199 (#10943) 2024-01-15 11:59:39 +13:00
Ali
d750389c67 fix(app): fix exhaused error message [EE-6231] (#10949)
Co-authored-by: testa113 <testa113>
2024-01-15 11:03:38 +13:00
Ali
cb7efd8601 fix(app): fix wording and 2 key validation [EE-6233] (#10944)
Co-authored-by: testa113 <testa113>
2024-01-15 11:01:48 +13:00
Ali
55f66f161e fix(app): fix env var state and validation [EE-6232] (#10941)
Co-authored-by: testa113 <testa113>
2024-01-15 10:56:53 +13:00
Prabhat Khera
067a7d148f update endpoint angular state (#10950) 2024-01-12 16:44:49 +13:00
Prabhat Khera
cf88570c39 update validation for storage classes (#10940) 2024-01-12 09:40:55 +13:00
Prabhat Khera
0e6a175bf6 fix metrics text wordings (#10939) 2024-01-12 08:31:29 +13:00
Oscar Zhou
bb680ef20a fix(git): incorrect git commit url for bitbucket [EE-6446] (#10855) 2024-01-12 08:22:50 +13:00
Oscar Zhou
c6505a6647 fix(docker/container): show exit code in status column if needs [EE-5760] (#10916) 2024-01-12 08:21:38 +13:00
Ali
4e7d1c7088 refactor(app): migrate remaining form sections [EE-6231] (#10938) 2024-01-11 15:13:28 +13:00
Prabhat Khera
0b9cebc685 fix(caching): integrate with axios cache interceptor [EE-6505] (#10922)
* integrate with axios-cache-interceptor
* remove extra headers as not needed
2024-01-11 11:12:53 +13:00
Prabhat Khera
d0b9e3a732 fix(UI): app summary on forvalues update [EE-6515] (#10932)
* app summary on forvalues update

* comment added
2024-01-11 10:14:23 +13:00
Prabhat Khera
b7635feff0 fix rbac message when not enabled (#10933) 2024-01-11 08:28:01 +13:00
Matt Hook
7528cabf5a deep upgrade dependencies, follow-redirects, @babel/traverse, postcss (#10931) 2024-01-10 15:40:05 +13:00
Matt Hook
39eb37d5e5 upgrade circl => v1.3.7 (#10925) 2024-01-10 13:08:26 +13:00
Matt Hook
dbd2e609d7 fix(api-key): add password requirement to generate api key [EE-6140] (#10617) 2024-01-09 11:14:24 +13:00
Chaim Lev-Ari
236e669332 refactor(templates): migrate edit view to react [EE-6412] (#10774) 2024-01-08 14:32:32 +07:00
Chaim Lev-Ari
e142939929 fix(ui): apply controlled input to field [EE-6411] (#10738) 2024-01-08 12:11:31 +07:00
Prabhat Khera
98157350b6 fix(UI): add resourse quota warning is consumed 100% [EE-6508] (#10914)
* add resourse quota warning is consumed 100%

* address review comments
2024-01-08 13:49:57 +13:00
Prabhat Khera
317eec2790 allow kube app to scale 0 (#10909) 2024-01-08 08:31:31 +13:00
Prabhat Khera
7a1893f864 fix showing env var values (#10908) 2024-01-08 08:26:20 +13:00
Chaim Lev-Ari
c7125266f6 fix(registries): retag image [EE-6456] (#10836) 2024-01-05 18:02:09 -03:00
matias-portainer
69271c9d59 fix(docker/images): check for empty tags EE-6256 (#10531) 2024-01-05 17:33:42 -03:00
andres-portainer
717f0978d9 fix(tls): set the correct scheme for Docker clients EE-6514 (#10917) 2024-01-05 15:24:29 -03:00
Ali
abf517de28 refactor(app): migrate app summary section [EE-6239] (#10910) 2024-01-05 15:42:36 +13:00
matias-portainer
7a4314032a fix(docker/console): avoid resizing console when inactive EE-5370 (#10292) 2024-01-04 13:01:52 -03:00
andres-portainer
791c21f643 fix(swarm): retrieve the node names for the image list EE-6401 (#10879) 2024-01-04 10:28:24 -03:00
Chaim Lev-Ari
eb5975a400 docs(dashboard): update link for swarm node [EE-6318] (#10833)
Co-authored-by: holysoles <holysoles97@gmail.com>
2024-01-04 17:02:36 +07:00
Chaim Lev-Ari
400a80c07d chore(deps): upgrade to msw v2 [EE-6489] (#10911) 2024-01-04 16:57:21 +07:00
Matt Hook
ecd603db8c fix(docker-networks): use Network icon for networks [EE-6507] (#10913) 2024-01-04 18:54:04 +13:00
Chaim Lev-Ari
95358c204b chore(deps): upgrade docker-types [EE-6491] (#10905) 2024-01-03 16:55:45 +07:00
Ali
9fc7187e24 refactor(app): placement form section [EE-6386] (#10818)
Co-authored-by: testa113 <testa113>
2024-01-03 11:00:50 +13:00
Ali
2d77e71085 refactor(app): migrate-autoscaling [EE-6387] (#10709)
* refactor(app): migrate-autoscaling [EE-6387]
2024-01-03 10:42:39 +13:00
Ali
6da71661d5 refactor(app): migrate replicas form section [EE-6238] (#10705)
Co-authored-by: testa113 <testa113>
2024-01-03 10:27:38 +13:00
Ali
58da51f767 refactor(app): migrate deployment type section [EE-6237] (#10704)
Co-authored-by: testa113 <testa113>
2024-01-03 10:04:08 +13:00
Ali
947ba4940b refactor(app): migrate resource reservations [EE-6236] (#10695)
* refactor(app): migrate resource reservations [EE-6236]
2024-01-03 10:03:33 +13:00
Ali
e07ee05ee7 refactor(app): persisted folders form section [EE-6235] (#10693)
* refactor(app): persisted folder section [EE-6235]
2024-01-03 09:46:26 +13:00
Ali
7a2412b1be refactor(app): migrate configmap and secret form sections [EE-6233] (#10528)
* refactor(app): migrate configmap and secret form sections [EE-6233]
2024-01-03 09:07:11 +13:00
Matt Hook
391b85da41 fix(lib): update binaries and modules for CVEs [EE-6457] 2024-01-03 08:58:13 +13:00
Prabhat Khera
e412958dcc chore(build): exclude draft PRs [EE-5872] (#9987)
* exclude draft PRs
2024-01-03 08:25:35 +13:00
Ali
488393007f refactor(app): migrate env var form section [EE-6232] (#10499)
* refactor(app): migrate env var form section [EE-6232]

* allow undoing delete in inputlist
2024-01-03 08:17:54 +13:00
matias-portainer
6228314e3c fix(oauth): show asterisks placeholder in secret key input field EE-5664 (#10761) 2024-01-02 12:19:15 -03:00
Chaim Lev-Ari
ba19aab8dc refactor(registries): migrate repos table to react [EE-6451] (#10830) 2024-01-02 14:04:15 +07:00
Chaim Lev-Ari
3ae430bdd8 chore(build): remove eslint plugin [EE-6432] (#10773) 2024-01-02 13:42:48 +07:00
Chaim Lev-Ari
faa7180536 docs(api): default to pascal case for property name [EE-6471] (#10860) 2024-01-02 13:30:02 +07:00
Chaim Lev-Ari
a1519ba737 chore(deps): upgrade axios [EE-6488] (#10885)
Co-authored-by: Matt Hook <hookenz@gmail.com>
2024-01-02 13:26:54 +07:00
Chaim Lev-Ari
4c226d7a17 fix(templates): separate template views filters [EE-6397] (#10711) 2024-01-02 13:25:26 +07:00
Chaim Lev-Ari
82951093b5 chore(ci): run lint and test on all pkgs [EE-6201] (#10481) 2024-01-02 10:59:49 +07:00
Matt Hook
2e15cad048 fix(postcss): update postcss to 8.4.32 [EE-6490] 2023-12-29 06:39:53 +13:00
Matt Hook
27e997fe0d update go-get and x/crypto (#10893) 2023-12-28 07:54:41 +13:00
Matt Hook
6a4cfc8d7c chore(libs): update go libs and hide passwords/keys [EE-6496] (#10889) 2023-12-28 05:23:25 +13:00
Matt Hook
ebac0b9da2 upgrade golang and other dependant binaries (#10888) 2023-12-27 10:42:35 +13:00
andres-portainer
e3c5cd063b fix(chisel): fix a nil pointer dereference EE-6481 (#10871) 2023-12-22 11:36:01 -03:00
Chaim Lev-Ari
2b73116284 fix(templates): add host file entry [EE-6461] (#10839) 2023-12-21 15:56:02 +07:00
Prabhat Khera
d2ccb10972 add border to tooltip and modal in high contrast theme (#10834) 2023-12-20 08:55:00 +13:00
Prabhat Khera
6ede9f8cc3 disable html5 validation (#10844) 2023-12-20 08:54:00 +13:00
Prabhat Khera
6b07c874fc revert #10765 (#10870) 2023-12-19 14:19:24 +13:00
Ali
e84dd27e88 feat(cache): default to off [EE-6293] (#10867)
Co-authored-by: testa113 <testa113>
2023-12-19 12:13:44 +13:00
Matt Hook
5f1f797281 remove deprecated random seed and other minor staticcheck errors (#10851) 2023-12-18 11:48:41 +13:00
Ali
52fe09d0b1 fix(stacks): remove deployed version column [EE-6346] (#10859)
Co-authored-by: testa113 <testa113>
2023-12-18 11:39:38 +13:00
Matt Hook
e687cee608 ignore, remove or comment out unused code. Enable unused linter (#10743) 2023-12-18 10:28:15 +13:00
Matt Hook
8396ff068d enable gosimple linter (#10744) 2023-12-18 10:27:24 +13:00
Ali
d98fc1238e fix(git): stacks deployed version [EE-6346] (#10852)
Co-authored-by: testa113 <testa113>
2023-12-15 16:55:39 +13:00
Dakota Walsh
0ddf84638f fix(kubernetes): deprecate old configurations api EE-5571 (#10837)
* fix(kubernetes): deprecate old configurations api EE-5571

* fix doc variable type
2023-12-15 09:04:08 +13:00
Matt Hook
0b9407f0a6 close db before restore. fix log (#10826) 2023-12-14 12:01:05 +13:00
Oscar Zhou
e4d71d858d fix(setting/ssl): cert files are optional to upload [EE-6139] (#10776) 2023-12-13 23:20:19 +13:00
Chaim Lev-Ari
25741e8c4c feat(edge): sort waiting room table [EE-6259] (#10577) 2023-12-13 11:10:29 +02:00
Prabhat Khera
32d8dc311b fix cpu parsing logic (#10808) 2023-12-12 15:35:36 +13:00
Dakota Walsh
6ff6fd7f75 fix(swagger): custom template create docs EE-6428 (#10807) 2023-12-11 10:04:23 +13:00
Matt Hook
41b73fe2ae close the db before backup for windows shares and better error handling (#10810) 2023-12-08 15:24:23 +13:00
Prabhat Khera
fb3b00de41 fix(UI): remember backup settings tab selection [EE-6347] (#10765)
* remember backup settings tab selection

* address review comments
2023-12-08 15:17:27 +13:00
Prabhat Khera
0f9b91a15f disable create access btn if there is no team or user (#10766) 2023-12-08 14:19:43 +13:00
Dakota Walsh
79f3e1b04b fix(backup ui): minor typo on backup page EE-6348 (#10716) 2023-12-08 13:22:41 +13:00
matias-portainer
56022ab7b1 fix(stacks): allow editing custom templates before stack deployment EE-6380 (#10712) 2023-12-07 09:42:18 -03:00
Ali
4e8b371fb7 fix(gitops): clean trailing slash [EE-6346] (#10777)
Co-authored-by: testa113 <testa113>
2023-12-07 13:43:01 +13:00
Ali
a2d6d6002c fix(app): update sliders when limits are known [EE-5933] (#10768)
Co-authored-by: testa113 <testa113>
2023-12-07 12:11:44 +13:00
Chaim Lev-Ari
dabcf4f7db feat(custom-templates): migrate create view to react [EE-6400] (#10715) 2023-12-06 14:11:02 +01:00
Prabhat Khera
bd5ba7b5d0 fix(kube): configmaps and secrets from envFrom in the app detail screen [EE-6282] (#10742)
* fix configmaps and secrets from envFrom

* adress review comments
2023-12-06 16:02:26 +13:00
James Carppe
1d279428a7 Update bug template for 2.19.4 (#10763) 2023-12-06 12:05:53 +13:00
Chaim Lev-Ari
8ee0c0cf27 fix(images): sort by tags [EE-6410] (#10740) 2023-12-04 08:47:28 +02:00
Chaim Lev-Ari
2a18c9f215 fix(edge/templates): fix issues with git templates [EE-6357] (#10679) 2023-12-04 08:46:44 +02:00
Ali
974378c9b5 fix(gitops): correct commit hash link [EE-6346] (#10723) 2023-12-04 11:18:15 +13:00
Matt Hook
eb23818f83 fix(rollback): reimplement rollback feature [EE-6367] (#10721) 2023-12-04 09:12:41 +13:00
Ali
8f4d6e7e27 fix(app): shift external to the top [EE-6392] (#10719)
Co-authored-by: testa113 <testa113>
2023-12-04 07:43:53 +13:00
Oscar Zhou
5c7f6aab66 fix(docker/image): swarm image list issue [EE-6374] (#10710) 2023-12-01 16:49:31 +13:00
Chaim Lev-Ari
3cf36b0e93 fix(app/templates): show default url in settings [EE-6393] (#10706) 2023-11-30 07:18:15 +02:00
Chaim Lev-Ari
7a9436dad7 fix(edge/stacks): clear templates values on change method [EE-6390] (#10707) 2023-11-30 07:13:01 +02:00
cmeng
5c59c53e91 fix(password): force change password EE-6382 (#10708) 2023-11-30 17:46:57 +13:00
Ali
e3a995d515 fix(pvc): show access modes [EE-5581] (#10554)
Co-authored-by: testa113 <testa113>
2023-11-30 09:48:55 +13:00
Ali
87b486b798 fix(PVC): access modes match storage class capability [EE-5580] (#10550) 2023-11-30 09:48:14 +13:00
cmeng
92c18843b2 fix(wizard): count swarm agent as local environment EE-6215 (#10684) 2023-11-30 08:53:56 +13:00
Ali
450c167461 fix(cache): exclude reqs that accept yaml [EE-6381] (#10696)
Co-authored-by: testa113 <testa113>
2023-11-29 11:45:10 +13:00
Ali
bdcb003a32 fix(app): dont validate stack name [EE-6379] (#10701)
Co-authored-by: testa113 <testa113>
2023-11-29 11:44:33 +13:00
Chaim Lev-Ari
c40931b31c fix(templates): show type selector [EE-6370] (#10694) 2023-11-28 15:40:22 +02:00
Matt Hook
db46dc553f fix(backups): fix rollback feature [EE-6367] (#10691) 2023-11-28 11:12:17 +13:00
Chaim Lev-Ari
76bcdfa2b8 fix(edge/templates): fix issues [EE-6328] (#10656) 2023-11-27 09:56:15 +02:00
cmeng
140ac5d17c fix(logout): clean user data when logout EE-6368 (#10690) 2023-11-27 17:21:55 +13:00
Ali
2fe965942a fix(kubeClient): get standard client [EE-6376] (#10692)
Co-authored-by: testa113 <testa113>
2023-11-27 16:48:47 +13:00
cmeng
dc574af734 fix(container): runtime and resources issues EE-6306 (#10611) 2023-11-27 11:56:44 +13:00
Ali
1bcbfb8213 fix(cache): set as true for a new admin [EE-6293] (#10689)
Co-authored-by: testa113 <testa113>
2023-11-27 10:19:08 +13:00
Oscar Zhou
6bec4cdecc fix(edgestack): set retry deployment (#10676) 2023-11-24 13:45:10 +13:00
Ali
04c1c7d8fb fix(cache): cache fixes [EE-6293] (#10681)
* fix(cache): default cache to on for new users [EE-6293]

* clear cache to transition terminating namespace

* add rq requests back to the namespace view

---------

Co-authored-by: testa113 <testa113>
2023-11-24 11:27:52 +13:00
Ali
2f91315ac7 fix(namespace): handle undefined registry options [EE-6366] (#10683)
Co-authored-by: testa113 <testa113>
2023-11-24 10:58:32 +13:00
andres-portainer
a4b17d2548 fix(gitops): change the condition that checks if the environment is online EE-6321 (#10665) 2023-11-23 11:54:50 -03:00
Chaim Lev-Ari
26953d0b15 fix(templates): change default url [EE-6363] (#10680) 2023-11-23 03:41:22 +02:00
cmeng
13d1fc63ff fix(stack): sync ee code to ce EE-5960 (#10642) 2023-11-23 09:17:12 +13:00
Ali
a4926e5237 fix(namespace): create page layout [EE-6385] (#10675) 2023-11-22 15:37:11 +13:00
James Carppe
936a71ee00 Update bug template for 2.19.3 (#10674) 2023-11-22 14:25:22 +13:00
Ali
4096bb562d feat(cache): introduce cache option [EE-6293] (#10672)
Co-authored-by: testa113 <testa113>
2023-11-22 14:21:07 +13:00
cmeng
57ed6ae6a6 fix(edge-stack): parse docker compose multi lines json output EE-6317 (#10627) 2023-11-20 22:54:28 +13:00
Chaim Lev-Ari
ad5a17ac34 feat(edge/updates): schedule time changes [EE-5975] (#10458) 2023-11-20 10:24:54 +02:00
Chaim Lev-Ari
436da01bce feat(auth): save jwt in cookie [EE-5864] (#10527) 2023-11-20 09:35:03 +02:00
Ali
ecce501cf3 Revert "feat(cache): introduce cache option [EE-6293] (#10641)" (#10658)
This reverts commit 2c032f1739.
2023-11-20 15:08:19 +13:00
Ali
2c032f1739 feat(cache): introduce cache option [EE-6293] (#10641) 2023-11-20 10:22:48 +13:00
cmeng
fffc7b364e fix(container): Unable to create container with webhook EE-6313 (#10619) 2023-11-17 14:35:47 +13:00
andres-portainer
0b5b8971b1 fix(gitops): handle the local environment in isEnvironmentOnline() EE-6321 (#10631) 2023-11-16 09:40:42 -03:00
cmeng
be09c5e346 fix(volumes): Volumes browse button spacing issue EE-6323 (#10633) 2023-11-16 16:25:17 +13:00
cmeng
d089dfbca0 fix(container): fix various creating container issues EE-6287 (#10595)
* fix(container): show placeholder for image field EE-6287

* fix(container): correct query params for search button field EE-6287

* fix(container): use btoa to encode registry credential EE-6287

* fix(container): allow creating non-existing option EE-6287

* fix(ui/forms): typeahead component

* fix(container): select the default registry EE-6287

* fix(container): always enable deploy button when always pull is off EE-6287

* fix(container): reset command fields outside current event to avoid validation on broken values EE-6287

* fix(container): query registry with endpoint ID param EE-6287

---------

Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2023-11-16 08:50:23 +13:00
Chaim Lev-Ari
e43d076269 feat(edge/templates): introduce edge specific settings [EE-6276] (#10609) 2023-11-15 14:43:18 +02:00
Chaim Lev-Ari
68950fbb24 feat(edge/templates): introduce custom templates [EE-6208] (#10561) 2023-11-15 10:45:07 +02:00
Chaim Lev-Ari
a0f583a17d fix(containers): align switches [EE-6314] (#10616) 2023-11-15 09:34:08 +02:00
Chaim Lev-Ari
51474262eb fix(access-control): show only environment users [EE-6315] (#10614) 2023-11-15 09:33:29 +02:00
Matt Hook
3525a1af77 fix(kube): change advanced deployment label [EE-6310] (#10626)
* change namespace label to deploy to

* fix var typo
2023-11-15 11:35:39 +13:00
Chaim Lev-Ari
e1e90c9c1d feat(edge/templates): introduce edge app templates [EE-6209] (#10480) 2023-11-14 14:54:44 +02:00
Chaim Lev-Ari
95d96e1164 fix(ui): parse slider value correctly [EE-6225] (#10484) 2023-11-14 13:17:25 +02:00
Chaim Lev-Ari
99b39da03d refactor(edge/groups): migrate view to react [EE-4683] (#10592) 2023-11-14 12:57:27 +02:00
Chaim Lev-Ari
1f2f4525e3 feat(ui/buttons): introduce Add and Delete buttons [EE-6296] (#10585) 2023-11-14 12:36:15 +02:00
James Carppe
66635ba6b1 Updated versions in bug report template (#10620)
LGTM
2023-11-13 07:07:44 +05:30
yi-portainer
3630aab820 * remove line break 2023-11-13 14:18:52 +13:00
Matt Hook
3c8c2118d4 update namespace section for helm (#10610) 2023-11-13 09:37:52 +13:00
Chaim Lev-Ari
d6ac29b498 fix(edge/stacks): remove parentheses [EE-6277] (#10560) 2023-11-09 09:55:54 +02:00
Prabhat Khera
e73b7fe0fd fix(kubernetes): clear user token from kube token cache on logout + update cluster rolebindings for user on change of team/user authorization [EE-6298] (#10598)
* clear user token from kube token cache on logoug + updates cluster role bindings for service accounts on change user/teams authorizations
2023-11-09 14:33:23 +13:00
Prabhat Khera
e761a00098 fix(kubernetes): URL form validation for advance deployment [EE-6280] (#10607) 2023-11-09 13:26:42 +13:00
Oscar Zhou
9041880bdb fix(container): assign container domain name (#10605) 2023-11-09 10:19:28 +13:00
Ali
e4ddd8048a fix(app): disable deploy when there are no namespaces [EE-6295] (#10606)
Co-authored-by: testa113 <testa113>
2023-11-08 03:22:41 +00:00
Oscar Zhou
e6ef913bb1 fix(docker/swarm): suppress no such container logs (#10604) 2023-11-08 11:43:42 +13:00
Matt Hook
3fd696d6b5 switch to filter after upgrading helm binary (#10596) 2023-11-06 13:32:14 +13:00
Ali
24c9959ca4 fix(app): hide services section when there are no namespaces [EE-6295] (#10588)
Co-authored-by: testa113 <testa113>
2023-11-05 17:37:48 +00:00
Chaim Lev-Ari
e72671e4ab fix(edge/updates): hide sidebar item when disabled [EE-6294] (#10582) 2023-11-05 12:45:56 +02:00
Prabhat Khera
47c9e498f9 fix validation fro custom template (#10587) 2023-11-03 11:39:44 +13:00
Ali
0c323b48e7 fix(nodes): restrict nodes details from standard user [EE-6125] (#10586)
Co-authored-by: testa113 <testa113>
2023-11-02 19:02:19 +00:00
Prabhat Khera
103d908e63 fix(users): hide admin users for non admins from user list API [EE-6290] (#10580)
* hide admin users for non admins from user list API

* address review comments
2023-11-02 16:08:17 +13:00
cmeng
2972022523 fix(version): show build info EE-6278 (#10578) 2023-11-02 11:04:11 +13:00
Oscar Zhou
4ffeefd267 feat(security): add docker scout pr github action flow (#10557) 2023-11-02 09:34:24 +13:00
Prabhat Khera
c8bdf21d07 fix(kubernetes): validation for advance deployments [EE-6280] (#10574) 2023-11-02 08:50:12 +13:00
Prabhat Khera
b6f9777bbf fix custom template id on select (#10573) 2023-11-02 08:46:50 +13:00
Ali
f6b78312f4 fix(nodes): allow standard users to get kube endpoints [EE-6125] (#10572)
Co-authored-by: testa113 <testa113>
2023-11-01 19:08:38 +00:00
Chaim Lev-Ari
948486df77 fix(services): update service [EE-6275] (#10559) 2023-11-01 12:27:38 +02:00
cmeng
600c8a3025 fix(volumes): fix broken volume browse button EE-6274 (#10566) 2023-11-01 14:15:54 +13:00
cmeng
8daf77c3b6 fix(log-viewer): unable to view container logs EE-6273 (#10555) 2023-11-01 12:00:05 +13:00
matias-portainer
8bb5129be0 feat(nomad): remove nomad from UI EE-6060 (#10509) 2023-10-31 15:27:20 -03:00
Ali
1140804fe9 fix(app): sync showSystem between stacks and apps tables [EE-6216] (#10532) 2023-10-30 19:41:41 +00:00
Prabhat Khera
7d868d1dc9 hide stacks tab if stack feature is disabled (#10551) 2023-10-30 14:13:36 +13:00
andres-portainer
247f358b94 fix(code): revert omitempty optimization EE-6269 (#10548) 2023-10-27 17:33:04 -03:00
matias-portainer
f10356641a fix(edge/aeec): make edge id generator field mandatory EE-6010 (#10545) 2023-10-27 10:35:10 -03:00
LP B
9e60723e4d fix(app/logout): always perform API logout + make API logout route public [EE-6198] (#10448)
* feat(api/logout): make logout route public

* feat(app/logout): always perform API logout on /logout redirect

* fix(app): send a logout event to AngularJS when axios hits a 401
2023-10-27 14:44:05 +02:00
Ali
47fa1626c6 fix(app): don't attach all ingresses to app [EE-5686] (#10537) 2023-10-27 16:59:45 +13:00
Prabhat Khera
26036c05f2 fix(kubernetes): remove unique check from kubernetes stacks [EE-6170] (#10542) 2023-10-27 15:41:02 +13:00
Matt Hook
8ee718f808 chore(binaries): upgrade binaries [EE-6253] (#10529) 2023-10-27 15:40:06 +13:00
cmeng
30e4b3e68c fix(edge-stack): remove text info for relative path EE-6228 (#10541) 2023-10-27 14:53:20 +13:00
cmeng
0d56504268 fix(settings): disable save button when revert changes EE-6263 (#10543) 2023-10-27 13:47:08 +13:00
Ali
6a5f44b5ba fix(errors): display kube confgimap and secret errors [EE-5558] (#10539) 2023-10-27 10:56:03 +13:00
cmeng
3964852fda fix(container): hide capabilities tab EE-6258 (#10540) 2023-10-26 15:44:31 +13:00
Ali
403fdf7ce3 fix(nodes): disable select [EE-4692] (#10538)
Co-authored-by: testa113 <testa113>
2023-10-25 23:59:01 +01:00
Ali
afa3e7477b fix(toggle): update cursor style and color transition speed [EE-6229] (#10534)
Co-authored-by: testa113 <testa113>
2023-10-25 23:17:18 +01:00
Ali
d9effb3597 fix(nodes): fix nodes datatable width [EE-4962] (#10533)
Co-authored-by: testa113 <testa113>
2023-10-25 20:42:36 +01:00
andres-portainer
ee80e3d252 fix(edge): fix frontend issues with omitempty EE-6260 (#10536) 2023-10-25 15:51:39 -03:00
matias-portainer
824706e4e9 fix(ui): label GPU functionality as NVIDIA only EE-6204 (#10522) 2023-10-25 14:48:22 -03:00
Chaim Lev-Ari
09f9c09706 chore(ci): separate tests from CI (#10524) 2023-10-25 07:52:04 +03:00
Prabhat Khera
577eef5de0 fix stack name placeholder and some css styling (#10523) 2023-10-25 08:59:10 +13:00
andres-portainer
ae1726cece feat(performance): performance optimizations EE-6042 (#10520) 2023-10-24 13:55:11 -03:00
andres-portainer
e4e66dac9c fix(gitops): only attempt to redeploy when the environment appears to be online EE-6182 (#10464) 2023-10-24 11:20:45 -03:00
Steven Kang
08fdebfbd9 feat(ci): introduce GH Actions for Portainer CE (#10419)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2023-10-24 13:30:33 +03:00
Matt Hook
860890046d fix(registry): remove k8s registry secrets when registries are removed [EE-5768] (#10369) 2023-10-24 09:24:09 +13:00
Ali
96ead31a8d fix(kubeapi): fix ts api error handling [EE-5558] (#10488)
* fix(kubeapi): fix ts api error handling [EE-5558]

* use portainer errors for mapped functions

* don't parse long patch responses

* allow nested kube error that's thrown to bubble up

---------

Co-authored-by: testa113 <testa113>
2023-10-23 20:52:40 +01:00
andres-portainer
6c55cac52a feat(code): equalize the code with EE EE-6218 (#10518) 2023-10-23 15:52:37 -03:00
Ali
e110856003 fix(namespace): remove duplicate 'no registry' text [EE-2226] (#10519)
Co-authored-by: testa113 <testa113>
2023-10-23 19:49:24 +01:00
Chaim Lev-Ari
10c3ed42f0 refactor(custom-templates): migrate list component to react [EE-6206] (#10440) 2023-10-23 20:00:50 +03:00
Chaim Lev-Ari
14129632a3 refactor(app-templates): convert list to react [EE-6205] (#10439) 2023-10-23 19:04:18 +03:00
Chaim Lev-Ari
1fa63f6ab7 refactor(docker/services): migrate service tasks to react [EE-4676] (#10328) 2023-10-23 13:52:49 +03:00
Chaim Lev-Ari
70455320be fix(docker/volumes): Add volume typo [EE-6226] (#10483) 2023-10-23 13:31:59 +03:00
Chaim Lev-Ari
b933bee95e feat(docker/networks): migrate networks datatable to React [EE-4670] (#10351)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2023-10-22 12:35:22 +03:00
Chaim Lev-Ari
0dc1805881 refactor(docker/services): convert services table to react [EE-4675] (#10289) 2023-10-22 12:32:05 +03:00
Chaim Lev-Ari
6b5c24faff refactor(custom-templates): migrate common-fields to react [EE-6207] (#10445) 2023-10-22 12:19:19 +03:00
Chaim Lev-Ari
1ad9488ca7 refactor(templates): migrate template item to react [EE-6203] (#10429) 2023-10-19 21:09:15 +02:00
Chaim Lev-Ari
d970f0e2bc refactor(containers): migrate create view to react [EE-2307] (#9175) 2023-10-19 13:45:50 +02:00
cmeng
bc0050a7b4 fix(user-token): prevent admin read tokens of other admins EE-5858 (#10489) 2023-10-19 16:23:14 +13:00
Prabhat Khera
03155685ab fix toggle colors (#10503) 2023-10-19 15:01:31 +13:00
Prabhat Khera
7e4d113fda fix libhelm error message (#10502) 2023-10-19 13:20:49 +13:00
Oscar Zhou
7c91780eb7 fix(edge): introduce pause and rollback status [EE-5992] (#10465) 2023-10-19 11:26:02 +13:00
Ali
877dc1e236 fix(namespace): update no registries text [EE-2226] (#10501)
Co-authored-by: testa113 <testa113>
2023-10-18 22:57:29 +01:00
Prabhat Khera
56f3bd8417 add name field for helm install in advance deployments (#10493) 2023-10-18 11:37:03 +13:00
Ali
776be2e022 fix(sidebar): high contrast styles, single option link [EE-5666] (#10485) 2023-10-16 21:23:23 +01:00
Ali
0e47f22c0a refactor(cluster): migrate nodes datatable to react [EE-4962] (#10459)
Co-authored-by: testa113 <testa113>
2023-10-16 21:19:08 +01:00
andres-portainer
b346fd7f39 fix(store): fix StoreIsUpdating() to properly set the state EE-6227 (#10486) 2023-10-16 16:32:30 -03:00
Prabhat Khera
35448c7f48 fix helm install (#10479) 2023-10-17 07:50:13 +13:00
Ali
07ec2ffe5e fix(namespace): create ns qa feedback [EE-2226] (#10474) 2023-10-16 19:15:44 +01:00
Prabhat Khera
bcb3f918d1 some minor UI fixes (#10475) 2023-10-16 14:08:55 +13:00
Prabhat Khera
7840e0bfe1 feature(kubernetes): stack name made optional & add toggle to disable stack in kubernetes [EE-6170] (#10436) 2023-10-16 14:08:06 +13:00
Chaim Lev-Ari
44d66cc633 fix(docker/secrets): allow navigating to secret item page [EE-6164] (#10382) 2023-10-15 09:33:27 +03:00
Matt Hook
148bd4d997 chore:(kubeclient): refactor kubeclient middleware and endpoints [EE-5028] (#10423) 2023-10-13 13:43:36 +13:00
Matt Hook
7c4c985247 upgrade some badge components to match EE (#10451) 2023-10-13 03:10:16 +13:00
Chaim Lev-Ari
57c45838d5 fix(edge/updates): allow group search [EE-6179] (#10408) 2023-10-12 08:30:23 +03:00
Ali
5a73605df2 fix(sidebar): consistent font weight [EE-5666] (#10461) 2023-10-12 01:59:46 +01:00
Prabhat Khera
ff5b311eee fix(helm): fix helm move to advance deployments issues [EE-5999] (#10453)
* fix helm move to adv deployments
2023-10-12 11:02:09 +13:00
Ali
7218eb0892 feat(namespace): migrate create ns to react [EE-2226] (#10377) 2023-10-11 20:32:02 +01:00
Prabhat Khera
31bcba96c6 feature(UI): toggle styling changes [EE-4602] (#10373) 2023-10-12 07:34:38 +13:00
Ali
6a5f5aa424 fix(sidebar): qa feedback [EE-5666] (#10452) 2023-10-11 19:32:52 +01:00
matias-portainer
da5a4d6714 fix(swarm/services): avoid sending credSpec object when empty EE-6178 (#10441) 2023-10-11 11:30:23 -03:00
Chaim Lev-Ari
35dfde70de refactor(ui/page-header): make docs url explicit [EE-5966] (#10411) 2023-10-11 10:38:57 +03:00
Chaim Lev-Ari
9e57530bde fix(build): handle warning about userId [EE-5612] (#10444) 2023-10-11 10:30:14 +03:00
Chaim Lev-Ari
5c37ed328f refactor(docker/volumes): migrate table to react [EE-4677] (#10312) 2023-10-11 10:27:42 +03:00
Chaim Lev-Ari
8e1417b4e9 refactor(docker/containers): remove EndpointProvider from container service [EE-6180] (#10392) 2023-10-11 10:26:44 +03:00
Chaim Lev-Ari
b80fcb0467 fix(docker/stacks): show orphaned stacks option [EE-6149] (#10346) 2023-10-11 10:24:35 +03:00
cmeng
66ca73f98b fix(edge-stack): sync CE code with EE EE-6163 (#10437) 2023-10-11 18:11:12 +13:00
Ali
a0dbabcc5f feat(sidebar): update menu structure [EE-5666] (#10418) 2023-10-09 19:23:12 +01:00
Prabhat Khera
b468070945 feature(helm): move helm charts inside advance deployments (create from manifest) [EE-5999] (#10395) 2023-10-09 11:20:44 +13:00
Oscar Zhou
9885694df6 fix(filesys): update stack version methods [EE-6190] (#10406) 2023-10-06 09:08:22 +13:00
Chaim Lev-Ari
95f3cf6e5b refactor(server): use httperror.NewError instead of struct [EE-6189] (#10398) 2023-10-05 11:26:24 +03:00
Chaim Lev-Ari
da346cba60 chore(deps): update ts and more deps [EE-5756] (#10409) 2023-10-05 11:25:35 +03:00
Chaim Lev-Ari
5f9687a361 fix(edge/waitingroom): hide sidebar when disabled [EE-6003] (#10343) 2023-10-05 10:31:08 +03:00
Chaim Lev-Ari
20823a7f27 chore(deps): upgrade golangci [EE-5685] (#10410) 2023-10-04 08:50:59 +03:00
Chaim Lev-Ari
9bf2957ea7 feat(docker/images): show used tag correctly [EE-5396] (#10305) 2023-10-03 15:55:23 +03:00
Ali
b895e88075 fix(teasers): add teaser message full stops [EE-6035] (#10401) 2023-10-02 21:23:00 +01:00
matias-portainer
671f74ce0d fix(edge/groups): include only user trusted endpoints in endpoint count EE-5964 (#10378) 2023-10-02 11:37:39 -03:00
cmeng
56ab19433a fix(websocket): abort websocket when logout EE-6058 (#10372) 2023-09-29 12:13:09 +13:00
Matt Hook
9440aa733d support proxy for helm repo validation (#10358) 2023-09-29 10:55:49 +13:00
LP B
ada6b31f69 fix(docker/container): container logs viewer error when logging is disabled (#10384)
* fix(docker/container-logs): invalid string breadcrumb

* fix(docker/container): let docker select the logging driver by default on container create

* fix(docker/container-logs): information panel in container logs when logging is disabled

* fix(docker/container): dont include HostConfig.LogConfig if no driver is selected
2023-09-28 15:53:52 +02:00
Ali
d678b155ba fix(teasers): updated muted styles from qa feedback [EE-6035] (#10390)
* fix(teasers): updated muted styles from qa feedback [EE-6035]
2023-09-28 11:32:58 +01:00
Prabhat Khera
99625cd35f fix team lead access to view user names (#10388) 2023-09-28 12:40:54 +13:00
Chaim Lev-Ari
95ca1d396b fix(docker/services): show cred spec configs [EE-5276] (#10083) 2023-09-27 07:57:47 +03:00
Chaim Lev-Ari
e28322459a fix(stacks): mark stack as start after autoupdate [EE-6165] (#10376) 2023-09-27 07:53:33 +03:00
Prabhat Khera
3ff2f64930 fix(authorization): disable user list api call if not authorised [EE-5825] (#10379)
* disable user list api call if not authorised

* fix tests

* fix lint issues
2023-09-27 10:12:30 +13:00
Ali
702391cf88 remove apostrophe from tooltip (#10386)
Co-authored-by: testa113 <testa113>
2023-09-26 21:25:08 +01:00
LP B
d437cde046 fix(docker/container): missing return statement when preparing container config (#10383) 2023-09-26 14:09:38 +02:00
Chaim Lev-Ari
7acde18930 feat(containers): migrate labels tab to react [EE-5212] (#10348) 2023-09-26 13:54:45 +03:00
cmeng
b4b44e6fa4 fix(edge-config): allow empty filter type EE-5962 (#10381) 2023-09-26 13:49:25 +13:00
Chaim Lev-Ari
2dfa4a7c45 refactor(containers): migrate restart policy tab to react [EE-5213] (#10347) 2023-09-25 20:40:26 +03:00
Chaim Lev-Ari
3d19c46326 style(kubernetes): disable autoFocus warning [EE-5752] (#10368) 2023-09-25 20:13:31 +03:00
Chaim Lev-Ari
57e04c3544 refactor(containers): migrate caps tab to react [EE-5215] (#10366) 2023-09-25 19:36:50 +03:00
Chaim Lev-Ari
9dde610da3 fix(docker/containers): create container with bridge network [EE-6160] (#10365) 2023-09-25 19:35:54 +03:00
LP B
26cb75def9 feat(app/home): tooltip aside edge agent version on mismatch with Portainer version (#10287)
* feat(app/home): tooltip aside edge agent version on mismatch with Portainer version

* fix(app/home): split agent and edge version display + display warning for agents before 2.15
2023-09-25 11:56:08 +02:00
Prabhat Khera
3c4660bbf3 fix(permissions): non admin access to view users [EE-5825] (#10352)
* fix non admin access to view users

* review comments and fix tests
2023-09-25 09:08:26 +13:00
Ali
13c48ab961 fix(be-teaser): mute styles [EE-6035] (#10349) 2023-09-24 19:56:09 +01:00
Chaim Lev-Ari
ffac83864d refactor(containers): migrate resources tab to react [EE-5214] (#10355) 2023-09-24 15:31:06 +03:00
Prabhat Khera
ec091efe3b fix deadlock situation (#10360) 2023-09-22 16:06:20 +12:00
cmeng
fb7a2fbbe6 fix(stack): fix edit git stack validation EE-5855 (#10339) 2023-09-22 10:09:24 +12:00
matias-portainer
dfce48cd5e fix(stacks): check properly if endpoint id is defined in the stacks object EE-6118 (#10302) 2023-09-21 10:12:43 -03:00
Chaim Lev-Ari
2b47b84e5e feat(docker/containers): migrate network tab to react [EE-5210] (#10344) 2023-09-21 14:02:02 +03:00
Chaim Lev-Ari
e92f067e42 refactor(containers): migrate volumes tab to react [EE-5209] (#10284) 2023-09-21 05:31:00 +03:00
Chaim Lev-Ari
16ccf5871e refactor(docker/containers): migrate env vars to react [EE-5211] (#10345) 2023-09-21 04:11:18 +03:00
cmeng
54112b56f2 feat(edge-config): support edge config for group EE-5962 (#10329) 2023-09-21 11:22:44 +12:00
LP B
a66942aa5a fix(app/stacks): swarm stacks incorrectly marked as orphaned (#10319) 2023-09-20 12:40:08 +02:00
Ali
c18504d6f1 fix(cluster): make angular refresh env [EE-5524] (#10315)
Co-authored-by: testa113 <testa113>
2023-09-20 19:33:43 +12:00
Chaim Lev-Ari
25d5e62f5c refactor(kube/apps): migrate stacks table to react [EE-4661] (#10091) 2023-09-20 09:04:26 +03:00
James Carppe
a5f60c64ef Added 2.19.1 to list of versions in bug report template (#10338) 2023-09-20 07:48:35 +05:30
Matt Hook
d6d532473e allow libhelm to use forward proxy (#10331) 2023-09-19 18:07:51 +12:00
Chaim Lev-Ari
af7834174a fix(api): restore deleted apis [EE-6090] (#10267) 2023-09-19 13:44:48 +12:00
Prabhat Khera
14853f6da0 fix(kubernetes): kube env permissions when down [EE-5427] (#10327) 2023-09-19 08:57:27 +12:00
Oscar Zhou
cc37ccfe4d fix(db/migration): avoid fatal error from being overwritten (#10316) 2023-09-18 14:33:04 +12:00
Matt Hook
e3a4b7ad17 improved user update validation (#10321) 2023-09-18 12:29:04 +12:00
Dakota Walsh
0a02f6b02e fix(kubernetes): add prefix only when needed EE-6068 (#3915) (#10310) 2023-09-15 09:25:56 +12:00
Chaim Lev-Ari
dcdf5e1837 fix(edge/jobs): clear logs [EE-5923] (#10291) 2023-09-13 22:11:42 +01:00
Chaim Lev-Ari
bf85a8861d refactor(docker/swarm): migrate nodes table to react [EE-4672] (#10184) 2023-09-13 10:51:33 +01:00
Chaim Lev-Ari
fbdbd277f7 fix(docker/container): pass empty command and entrypoint [EE-6106] (#10285) 2023-09-13 10:47:13 +01:00
cmeng
0a80f4dc51 fix(backup): add chisel key to backup EE-6105 (#10283) 2023-09-13 09:01:27 +12:00
andres-portainer
5a0cb4d0e8 fix(gitops): avoid cancelling the auto updates for any error EE-5604 (#10294) 2023-09-12 17:53:01 -03:00
Oscar Zhou
f17da30d31 fix(db/init): check server version and db schema version (#10300) 2023-09-12 15:55:09 +12:00
Matt Hook
291625959b update logic to purge the cache, update the message when the environment can't be reached (#10298) 2023-09-12 13:52:09 +12:00
Prabhat Khera
4c16594a25 fix(security): added restrictions to see user names [EE-5825] (#10296)
* fix(security): added restrictions to see user names [EE-5825]

* use pluralize method
2023-09-12 13:15:34 +12:00
Chaim Lev-Ari
60477ae287 refactor(docker/networks): migrate macvlan nodes selector to react [EE-4669] (#10183) 2023-09-11 15:27:04 +01:00
Chaim Lev-Ari
09aa1d35a8 refactor(ui): remove unused tables [EE-4698] (#10215) 2023-09-11 15:26:22 +01:00
cmeng
7669a3c8c6 fix(settings): misaligned poll frequency selector EE-6081 (#10286) 2023-09-11 15:35:44 +12:00
Ali
dde4b95426 fix(cluster): faster submitting load times [EE-5524] (#10280)
* faster submitting load times

* scroll to selected tz option

---------

Co-authored-by: testa113 <testa113>
2023-09-11 10:52:00 +12:00
LP B
dfd415c62e fix(app/stacks): stacks incorrectly marked as orphaned (#10273) 2023-09-08 22:22:26 +02:00
Matt Hook
b40b305e63 fix(styles): improve styling of form-section-title [EE-5366] (#10250) 2023-09-08 13:40:09 +12:00
Chaim Lev-Ari
c8a1f0fa77 refactor(docker/stacks): migrate table to react [EE-4705] (#9956) 2023-09-07 15:59:59 +01:00
Chaim Lev-Ari
c3d266931f refactor(docker/services): convert service tasks table to react [EE-4674] (#10188) 2023-09-07 15:19:03 +01:00
Chaim Lev-Ari
c47a804c97 refactor(docker/secrets): migrate table to react [EE-4673] (#10185) 2023-09-07 15:15:22 +01:00
Chaim Lev-Ari
b15812a74d refactor(docker/containers): migrate networks table to react [EE-4665] (#10069) 2023-09-07 15:14:03 +01:00
matias-portainer
776f6a62c3 fix(authentication): allow nested whitespaces on AD OU names EE-5206 (#10260) 2023-09-07 11:02:57 -03:00
Chaim Lev-Ari
ae3e612a24 feat(docker/stacks): fold env vars by default [EE-5575] (#9957) 2023-09-07 14:45:59 +01:00
Ali
6a8ff7c076 fix(yaml): remove create message on edit views [EE-5356] (#10254)
Co-authored-by: testa113 <testa113>
2023-09-07 09:29:25 +12:00
andres-portainer
4a39122415 fix(code): remove code that is no longer necessary EE-6078 (#10256) 2023-09-05 22:35:16 -03:00
andres-portainer
c748385879 feat(transactions): remove the feature flag EE-6080 (#10257) 2023-09-05 20:27:20 -03:00
Oscar Zhou
e83aa4d88d fix(gomod): update golang version (#10255) 2023-09-06 10:29:58 +12:00
Matt Hook
91d2132264 prevent regular users changing their username (#10247) 2023-09-06 09:17:04 +12:00
Matt Hook
e5f7641e46 non-admins must supply existing passwd when changing passwd (#10249) 2023-09-06 08:26:32 +12:00
Ali
515b02813b feat(k8sconfigure): migrate configure to react [EE-5524] (#10218) 2023-09-06 04:06:36 +12:00
Oscar Zhou
0f1e77a6d5 fix(security): update dependency and binary version [EE-5798] (#10192) 2023-09-05 17:23:12 +12:00
Prabhat Khera
a02f9f1f07 fix(kubernetes): run group permission when endpoint is up [EE-5427] (#10121)
* update group access when env is down

* fix tests
2023-09-05 11:03:43 +12:00
Dakota Walsh
d75a8027a5 fix(security): block user access policies for non admins EE-5826 (#10243) 2023-09-05 09:17:55 +12:00
Dakota Walsh
6a08bbe7e9 fix(security): block non-admins from user info listing EE-5825 (#10241) 2023-09-05 09:17:05 +12:00
Chaim Lev-Ari
e82b34b775 refactor(docker/services): migrate scale form to react [EE-6057] (#10208) 2023-09-04 16:24:41 -03:00
Chaim Lev-Ari
f7366d9788 refactor(docker/containers): migrate commands tab to react [EE-5208] (#10085) 2023-09-04 19:07:29 +01:00
Chaim Lev-Ari
46e73ee524 refactor(docker/containers): migrate processes table to react [EE-4666] (#10081) 2023-09-04 17:05:01 +01:00
Chaim Lev-Ari
e5880b3e34 fix(edge): add background to table icons [EE-6020] (#10187) 2023-09-04 16:52:51 +01:00
Chaim Lev-Ari
0e2eb17220 chore(deps): upgrade tailwind and prettier [EE-5218] (#10068) 2023-09-04 16:20:36 +01:00
Chaim Lev-Ari
cb7377ead6 refactor(ui/datatables): allow datatable to globally filter on object value [EE-5824] (#9955) 2023-09-04 10:33:07 +01:00
Oscar Zhou
440f4e8dda fix(edge): stack associated no dynamic group being deployed [EE-5531] (#10224) 2023-09-04 17:04:45 +12:00
James Carppe
490e4ec655 Add 2.19.0 to bug report template (#10239) 2023-09-04 10:20:55 +05:30
Dakota Walsh
7be8619ab7 fix(search): Add noindex meta tag EE-5371 (#10220) 2023-09-04 07:45:44 +12:00
Chaim Lev-Ari
4a6b7e2654 fix(ui/switch): reduce label size [EE-3803] (#10019) 2023-09-03 10:26:38 +01:00
andres-portainer
8cc5e0796c feat(libhttp): move into the Portainer repository EE-5475 (#10231) 2023-09-01 19:27:02 -03:00
andres-portainer
090fa4aeb3 feat(libcrypto): move into the Portainer repository EE-5476 (#10230) 2023-09-01 17:27:19 -03:00
andres-portainer
9a234204fa chore(go): move go.mod up one level to simplify dependencies EE-5726 (#10228) 2023-09-01 13:39:13 -03:00
Prabhat Khera
4560a53317 add tls options to the tls dropdown (#10221) 2023-09-01 10:42:22 +12:00
Chaim Lev-Ari
1b0fd60115 refactor(docker/configs): remove EndpointProvider [EE-5746] (#9198) 2023-08-31 22:11:57 +02:00
Ali
cd3c6e3089 fix(k8sconfigure): make ingress restrict be only [EE-6062] (#10216)
Co-authored-by: testa113 <testa113>
2023-09-01 06:11:48 +12:00
Oscar Zhou
4654978567 fix(api/system): support to display server edition via api (#10211) 2023-08-31 13:39:02 +12:00
Prabhat Khera
6d203033c1 fix showing default ns for ingresses on edi (#10197) 2023-08-29 15:12:49 +12:00
cmeng
4ca45e89c5 fix(relative-path): not deploy git stack via unpacker EE-6043 (#10195) 2023-08-29 11:49:00 +12:00
Prabhat Khera
a8c6bd8082 fix ECR registry token refresh (#10191) 2023-08-29 10:32:41 +12:00
Ali
841ca1ebd4 feat(app): migrate app parent view to react [EE-5361] (#10086)
Co-authored-by: testa113 <testa113>
2023-08-28 09:01:35 +12:00
Chaim Lev-Ari
531f88b947 chore(tests): clean tests output [EE-5758] (#9215) 2023-08-27 12:30:45 +02:00
Dakota Walsh
2953848b9a feat(gpu): remove GPU lightbubble EE-5254 (#10096) 2023-08-25 15:32:08 +12:00
Dakota Walsh
c0ba221021 fix(registry): ecr secret fix [EE-5673] (#10107) 2023-08-25 13:12:41 +12:00
andres-portainer
be85d34c4b fix(logging): enable colored logging EE-5512 (#10097) 2023-08-24 18:40:52 -03:00
cmeng
7125ef81f3 fix(stack): pass registries to unpacker to start stack EE-4797 (#10095) 2023-08-24 13:01:49 +12:00
cmeng
1aae2e27f4 chore(chisel): bump chisel to 1.9.0 EE-5976 (#10093) 2023-08-24 11:06:33 +12:00
cmeng
3237e1990c fix(waiting-room): search endpoints by dynamic edge group name EE-5965 (#10090) 2023-08-24 09:18:59 +12:00
Ali
1e61f7e305 fix(ingress): handle system resources [EE-4775] (#9972)
* fix(ingress): handle system resources [EE-4775]
2023-08-23 09:13:35 +12:00
Chaim Lev-Ari
5586910e9d fix(ui/datatables): sync page count with filtering [EE-5890] (#10010) 2023-08-22 09:36:31 +03:00
Prabhat Khera
bb646162d1 fix wrong error message for secrets (#10073) 2023-08-21 08:05:57 +12:00
Chaim Lev-Ari
cfe0d3092d feat(ui): add confirmation to delete actions [EE-4612] (#10003) 2023-08-19 19:19:02 +03:00
cmeng
6fde4195f8 fix(migrator): prevent duplicated migration EE-5777 (#10077) 2023-08-18 21:40:48 +12:00
Chaim Lev-Ari
36b8c849b3 feat(edge/stacks): reload edge stacks from server [EE-5970] (#10061) 2023-08-17 14:09:41 +03:00
Ali
0f6607e703 refactor(app): migrate the yaml inspector to react [EE-5356] (#10058)
Co-authored-by: testa113 <testa113>
2023-08-17 22:01:10 +12:00
Ali
23295d2736 feat(app): migrate app containers to react [EE-5353] (#9992) 2023-08-17 22:00:25 +12:00
cmeng
6290e9facc fix(waiting-room): search endpoints by edge group name EE-5965 (#10072) 2023-08-17 14:47:09 +12:00
cmeng
95424c322d fix(datatable): image page not loading image list EE-5978 (#10071) 2023-08-17 09:53:28 +12:00
Chaim Lev-Ari
a1e610a39a fix(edge/groups): filter selected environments [EE-5891] (#10050) 2023-08-16 12:24:37 +03:00
Chaim Lev-Ari
a27cc6c0e5 fix(edge/stacks): show pending envs [EE-5913] (#10052) 2023-08-16 10:22:41 +03:00
Ali
2b4cb1b7b4 fix(ingress): empty initial selection + fixes [EE-5852] (#10066)
Co-authored-by: testa113 <testa113>
2023-08-16 18:07:46 +12:00
Ali
26074437ca fix(environments): fix env table [EE-5971] (#10059)
Co-authored-by: testa113 <testa113>
2023-08-16 13:21:23 +12:00
Prabhat Khera
665a25e448 fix edit namespace resource quota issue (#10064) 2023-08-16 10:25:01 +12:00
Chaim Lev-Ari
4a91e947ed feat(edge/configs): add context help [EE-5963] (#10055) 2023-08-15 18:46:58 +03:00
Chaim Lev-Ari
d514eeec86 fix(edge/devices): search waiting room devices [EE-5895] (#10014) 2023-08-15 06:05:10 +03:00
matias-portainer
0ef4aad79a fix(authentication): allow whitespaces when loading AD OU name EE-5206 (#9977) 2023-08-14 12:18:07 -03:00
matias-portainer
8355d449c5 fix(edge/stacks): add pagination to environments list EE-5908 (#10042) 2023-08-14 12:17:00 -03:00
Chaim Lev-Ari
fd7e8a629e feat(edge/stacks): info for old agent status [EE-5792] (#10013) 2023-08-14 16:04:24 +03:00
Ali
7757bf7a84 fix(r2a): remove withUserProvider [EE-5355] (#10048)
Co-authored-by: testa113 <testa113>
2023-08-14 19:01:31 +12:00
Ali
5862aa5dd8 fix(app): use correct withCurrentUser wrapper [EE-5928] (#10040)
Co-authored-by: testa113 <testa113>
2023-08-14 16:53:28 +12:00
cmeng
925a0d0a9a fix(stack): fail to start swarm stack with private image EE-4797 (#10047) 2023-08-14 16:13:12 +12:00
Ali
2a7a96f498 fix(microk8s): PO ui fixes [EE-5900] (#10031)
Co-authored-by: testa113 <testa113>
2023-08-14 12:34:58 +12:00
Ali
c472fe9c18 refactor(app): app events datatable [EE-5355] (#10024) 2023-08-14 05:09:40 +12:00
andres-portainer
0eaf296e1b fix(unpacker): implement unpacker error parsing EE-5779 (#10005) 2023-08-10 10:25:59 -03:00
Oscar Zhou
598b8d0f28 fix(stagger): introduce stack version into DeploymentInfo struct (#10011) 2023-08-10 11:58:40 +12:00
matias-portainer
e1a3010bc7 fix(edge/stacks): fix UI issues EE-5844 (#10021) 2023-08-09 10:09:08 -03:00
cmeng
2de4863532 fix(edge-stack): detaching swarm stack from git repository EE-5812 (#9998) 2023-08-07 10:33:04 +12:00
Oscar Zhou
8cf54cd0df fix(react/datatable): override getColumnCanGlobalFilter method (#9990) 2023-08-07 10:30:38 +12:00
cmeng
1ef1953d7d fix(edge-stack): detaching from git repository EE-5812 (#9989) 2023-08-04 15:17:46 +12:00
cmeng
5b033abaa4 fix(registry): registry login failure for regular stack EE-5832 (#9986) 2023-08-04 15:16:55 +12:00
Ali
5865f1ca77 fix(app): update summary with ingresses [EE-5847] (#9973)
Co-authored-by: testa113 <testa113>
2023-08-04 13:48:21 +12:00
Chaim Lev-Ari
f59573f306 fix(home): empty default sort [EE-5822] (#9951) 2023-08-03 16:21:09 -03:00
Chaim Lev-Ari
1cecbd7177 fix(docker/images): show empty size cell [EE-5823] (#9954) 2023-08-03 16:19:58 -03:00
Ali
acf9203580 fix(ingress): ingress ui feedback [EE-5852] (#9982)
Co-authored-by: testa113 <testa113>
2023-08-03 23:03:09 +12:00
cmeng
9845518aa9 fix(edge-stack): unable to edit edge stack EE-5845 (#9981) 2023-08-03 17:21:01 +12:00
matias-portainer
d7e83aad26 fix(endpoints): fix nil pointer dereference EE-5843 (#9969) 2023-08-02 11:06:34 -03:00
Matt Hook
df47f3d8a8 show kube icon for custom template (#9968) 2023-08-02 09:43:54 +12:00
Ali
d0ecf6c16b fix(ingress): loading and ui fixes [EE-5132] (#9959) 2023-08-01 19:31:35 +12:00
Matt Hook
e400c4dfc6 bump compose to 2.20.2 (#9964) 2023-08-01 12:27:21 +12:00
Matt Hook
721457b71d bump version to 2.20 (#9963) 2023-08-01 09:20:51 +12:00
Ali
b19800681f fix(app): improve perceived ingress load time [EE-5805] (#9946)
Co-authored-by: testa113 <testa113>
2023-07-31 20:18:45 +12:00
cmeng
6a4e44ee0a fix(stack): update gitops updates tooltip EE-5827 (#9962) 2023-07-31 18:46:00 +12:00
Chaim Lev-Ari
37ece734f0 refactor(kube/apps): convert placement table to react [EE-4662] (#8938) 2023-07-29 17:08:41 +02:00
Prabhat Khera
bf79ef7d89 fix(security): upgrade helm binary to v3.12.2 [EE-5801] (#9263) 2023-07-28 15:08:45 +12:00
James Carppe
883ef2578f fix indentation in bug report template (#9944) 2023-07-28 13:05:43 +12:00
Matt Hook
a585f34106 workding change (#9266) 2023-07-28 07:53:33 +12:00
Ali
b128139b69 fix(UI): PO review tweaks [EE-5776] (#9245)
Co-authored-by: testa113 <testa113>
2023-07-28 07:50:53 +12:00
James Carppe
4c425a7af8 Discussions updates (#9730)
* Update bug template: versions to dropdown, add license types to editions, set render on command used

* Update docs URL in help template
2023-07-27 10:27:32 +05:30
Dakota Walsh
400d95c1a5 fix(metrics): node chart race condition EE-5447 (#9249) 2023-07-27 11:46:38 +12:00
Dakota Walsh
ca617e2ac9 fix(jwt): replace deprecated gorilla/securecookie [EE-5153] (#9247) 2023-07-27 09:34:16 +12:00
samdulam
4a90b8a3f7 Fix links in Discussions and Issues Templates (#9258)
* Fix Links

* Fix links for discussions
2023-07-26 12:34:15 +05:30
samdulam
43ad3face2 Fix Links (#9257) 2023-07-26 12:11:06 +05:30
samdulam
69e61be474 file type changes (#9256) 2023-07-26 12:07:06 +05:30
samdulam
a4ea7a3709 Changes to issues templates now that Discussions are enabled (#9255)
* Discussions Enabled and Templates

* Discussions - Ideas Template
2023-07-26 12:02:38 +05:30
samdulam
c5ecf8a66d Change Issues so we can move to discussions 2023-07-26 12:00:41 +05:30
samdulam
c2c0631495 Add Discussion Templates (#9254)
* Discussions Enabled and Templates

* Discussions - Ideas Template
2023-07-26 11:57:59 +05:30
samdulam
4ff3cee72e Add workflow_dispatch so we can run manually (#9253) 2023-07-26 09:33:54 +05:30
Matt Hook
c4e8251e52 post po review changes (#9244) 2023-07-26 11:36:02 +12:00
andres-portainer
21b00c267d fix(docker): use version negotiation for the Docker client EE-5797 (#9250) 2023-07-25 19:00:21 -03:00
samdulam
86ec058347 Change stabot action version as it stopped working (#9246) 2023-07-25 14:47:07 +05:30
2056 changed files with 53495 additions and 31499 deletions

View File

@@ -10,6 +10,7 @@ globals:
extends:
- 'eslint:recommended'
- 'plugin:storybook/recommended'
- 'plugin:import/typescript'
- prettier
plugins:
@@ -23,10 +24,13 @@ parserOptions:
modules: true
rules:
no-console: error
no-alert: error
no-control-regex: 'off'
no-empty: warn
no-empty-function: warn
no-useless-escape: 'off'
import/named: error
import/order:
[
'error',
@@ -41,6 +45,12 @@ rules:
pathGroupsExcludedImportTypes: ['internal'],
},
]
no-restricted-imports:
- error
- patterns:
- group:
- '@/react/test-utils/*'
message: 'These utils are just for test files'
settings:
'import/resolver':
@@ -49,6 +59,8 @@ settings:
- ['@@', './app/react/components']
- ['@', './app']
extensions: ['.js', '.ts', '.tsx']
typescript: true
node: true
overrides:
- files:
@@ -73,6 +85,7 @@ overrides:
settings:
react:
version: 'detect'
rules:
import/order:
[
@@ -86,8 +99,8 @@ overrides:
no-plusplus: off
func-style: [error, 'declaration']
import/prefer-default-export: off
no-use-before-define: "off"
'@typescript-eslint/no-use-before-define': ['error', { functions: false, "allowNamedExports": true }]
no-use-before-define: 'off'
'@typescript-eslint/no-use-before-define': ['error', { functions: false, 'allowNamedExports': true }]
no-shadow: 'off'
'@typescript-eslint/no-shadow': off
jsx-a11y/no-autofocus: warn
@@ -106,6 +119,12 @@ overrides:
'no-await-in-loop': 'off'
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }]
'regex/invalid': ['error', [{ 'regex': '<Icon icon="(.*)"', 'message': 'Please directly import the `lucide-react` icon instead of using the string' }]]
'@typescript-eslint/no-restricted-imports':
- error
- patterns:
- group:
- '@/react/test-utils/*'
message: 'These utils are just for test files'
overrides: # allow props spreading for hoc files
- files:
- app/**/with*.ts{,x}
@@ -114,13 +133,16 @@ overrides:
- files:
- app/**/*.test.*
extends:
- 'plugin:jest/recommended'
- 'plugin:jest/style'
- 'plugin:vitest/recommended'
env:
'jest/globals': true
'vitest/env': true
rules:
'react/jsx-no-constructed-context-values': off
'@typescript-eslint/no-restricted-imports': off
no-restricted-imports: off
- files:
- app/**/*.stories.*
rules:
'no-alert': off
'@typescript-eslint/no-restricted-imports': off
no-restricted-imports: off

11
.github/DISCUSSION_TEMPLATE/help.yaml vendored Normal file
View File

@@ -0,0 +1,11 @@
body:
- type: markdown
attributes:
value: |
Before asking a question, make sure it hasn't been already asked and answered. You can search our [discussions](https://github.com/orgs/portainer/discussions) and [bug reports](https://github.com/portainer/portainer/issues) in GitHub. Also, be sure to check our [knowledge base](https://portal.portainer.io/knowledge) and [documentation](https://docs.portainer.io/) first.
- type: textarea
attributes:
label: Ask a Question!
validations:
required: true

38
.github/DISCUSSION_TEMPLATE/ideas.yaml vendored Normal file
View File

@@ -0,0 +1,38 @@
body:
- type: markdown
attributes:
value: |
# Welcome!
Thanks for suggesting an idea for Portainer!
Before opening a new idea or feature request, make sure that we do not have any duplicates already open. You can ensure this by [searching this discussion cagetory](https://github.com/orgs/portainer/discussions/categories/ideas). If there is a duplicate, please add a comment to the existing idea instead.
Also, be sure to check our [knowledge base](https://portal.portainer.io/knowledge) and [documentation](https://docs.portainer.io) as they may point you toward a solution.
**DO NOT FILE DUPLICATE REQUESTS.**
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe
description: Short list of what the feature request aims to address.
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false

View File

@@ -1,54 +0,0 @@
---
name: Bug report
about: Create a bug report
title: ''
labels: bug/need-confirmation, kind/bug
assignees: ''
---
<!--
Thanks for reporting a bug for Portainer !
You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
Do you need help or have a question? Come chat with us on Slack https://portainer.io/slack/
Before opening a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
-->
**Bug description**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Portainer Logs**
Provide the logs of your Portainer container or Service.
You can see how [here](https://documentation.portainer.io/r/portainer-logs)
**Steps to reproduce the issue:**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Technical details:**
- Portainer version:
- Docker version (managed by Portainer):
- Kubernetes version (managed by Portainer):
- Platform (windows/linux):
- Command used to start Portainer (`docker run -p 9443:9443 portainer/portainer`):
- Browser:
- Use Case (delete as appropriate): Using Portainer at Home, Using Portainer in a Commercial setup.
- Have you reviewed our technical documentation and knowledge base? Yes/No
**Additional context**
Add any other context about the problem here.

167
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,167 @@
name: Bug Report
description: Create a report to help us improve.
labels: kind/bug,bug/need-confirmation
body:
- type: markdown
attributes:
value: |
# Welcome!
The issue tracker is for reporting bugs. If you have an [idea for a new feature](https://github.com/orgs/portainer/discussions/categories/ideas) or a [general question about Portainer](https://github.com/orgs/portainer/discussions/categories/help) please post in our [GitHub Discussions](https://github.com/orgs/portainer/discussions).
You can also ask for help in our [community Slack channel](https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA).
**DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS**.
- type: checkboxes
id: terms
attributes:
label: Before you start please confirm the following.
options:
- label: Yes, I've searched similar issues on [GitHub](https://github.com/portainer/portainer/issues).
required: true
- label: Yes, I've checked whether this issue is covered in the Portainer [documentation](https://docs.portainer.io) or [knowledge base](https://portal.portainer.io/knowledge).
required: true
- type: markdown
attributes:
value: |
# About your issue
Tell us a bit about the issue you're having.
How to write a good bug report:
- Respect the issue template as much as possible.
- Summarize the issue so that we understand what is going wrong.
- Describe what you would have expected to have happened, and what actually happened instead.
- Provide easy to follow steps to reproduce the issue.
- Remain clear and concise.
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown).
- type: textarea
attributes:
label: Problem Description
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: A clear and concise description of what actually happens.
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
description: Please be as detailed as possible when providing steps to reproduce.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
attributes:
label: Portainer logs or screenshots
description: Provide Portainer container logs or any screenshots related to the issue.
validations:
required: false
- type: markdown
attributes:
value: |
# About your environment
Tell us a bit about your Portainer environment.
- type: dropdown
attributes:
label: Portainer version
description: We only provide support for the most recent version of Portainer and the previous 3 versions. If you are on an older version of Portainer we recommend [upgrading first](https://docs.portainer.io/start/upgrade) in case your bug has already been fixed.
multiple: false
options:
- '2.19.4'
- '2.19.3'
- '2.19.2'
- '2.19.1'
- '2.19.0'
- '2.18.4'
- '2.18.3'
- '2.18.2'
- '2.18.1'
- '2.17.1'
- '2.17.0'
- '2.16.2'
- '2.16.1'
- '2.16.0'
validations:
required: true
- type: dropdown
attributes:
label: Portainer Edition
multiple: false
options:
- 'Business Edition (BE/EE) with 5NF / 3NF license'
- 'Business Edition (BE/EE) with Home & Student license'
- 'Business Edition (BE/EE) with Starter license'
- 'Business Edition (BE/EE) with Professional or Enterprise license'
- 'Community Edition (CE)'
validations:
required: true
- type: input
attributes:
label: Platform and Version
description: |
Enter your container management platform (Docker | Swarm | Kubernetes) along with the version.
Example: Docker 24.0.3 | Docker Swarm 24.0.3 | Kubernetes 1.26
You can find our supported platforms [in our documentation](https://docs.portainer.io/start/requirements-and-prerequisites).
validations:
required: true
- type: input
attributes:
label: OS and Architecture
description: |
Enter your Operating System, Version and Architecture. Example: Ubuntu 22.04, AMD64 | Raspbian OS, ARM64
validations:
required: true
- type: input
attributes:
label: Browser
description: |
Enter your browser and version. Example: Google Chrome 114.0
validations:
required: false
- type: textarea
attributes:
label: What command did you use to deploy Portainer?
description: |
Example: `docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest`
If you deployed Portainer using a compose file or manifest you can provide this here as well.
render: bash
validations:
required: false
- type: textarea
attributes:
label: Additional Information
description: Any additional information about your environment, the bug, or anything else you think might be helpful.
validations:
required: false

View File

@@ -1,5 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Portainer Business Edition - Get 3 nodes free
url: https://www.portainer.io/take-3
- name: Question
url: https://github.com/orgs/portainer/discussions/new?category=help
about: Ask us a question about Portainer usage or deployment.
- name: Idea or Feature Request
url: https://github.com/orgs/portainer/discussions/new?category=ideas
about: Suggest an idea or feature/enhancement that should be added in Portainer.
- name: Portainer Business Edition - Get 3 Nodes Free
url: https://www.portainer.io/take-3
about: Portainer Business Edition has more features, more support and you can now get 3 nodes free for as long as you want.

176
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,176 @@
name: ci
on:
workflow_dispatch:
push:
branches:
- 'develop'
- 'release/*'
pull_request:
branches:
- 'develop'
- 'release/*'
- 'feat/*'
- 'fix/*'
- 'refactor/*'
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
DOCKER_HUB_REPO: portainerci/portainer-ce
EXTENSION_HUB_REPO: portainerci/portainer-docker-extension
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
jobs:
build_images:
strategy:
matrix:
config:
- { platform: linux, arch: amd64, version: "" }
- { platform: linux, arch: arm64, version: "" }
- { platform: linux, arch: arm, version: "" }
- { platform: linux, arch: ppc64le, version: "" }
- { platform: linux, arch: s390x, version: "" }
- { platform: windows, arch: amd64, version: 1809 }
- { platform: windows, arch: amd64, version: ltsc2022 }
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: '[preparation] checkout the current branch'
uses: actions/checkout@v4.1.1
with:
ref: ${{ github.event.inputs.branch }}
- name: '[preparation] set up golang'
uses: actions/setup-go@v5.0.0
with:
go-version: ${{ env.GO_VERSION }}
- name: '[preparation] set up node.js'
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: '[preparation] set up qemu'
uses: docker/setup-qemu-action@v3.0.0
- name: '[preparation] set up docker context for buildx'
run: docker context create builders
- name: '[preparation] set up docker buildx'
uses: docker/setup-buildx-action@v3.0.0
with:
endpoint: builders
- name: '[preparation] docker login'
uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: '[preparation] set the container image tag'
run: |
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
# use the release branch name as the tag for release branches
# for instance, release/2.19 becomes 2.19
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -d "/" -f 2)
elif [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then
# use pr${{ github.event.number }} as the tag for pull requests
# for instance, pr123
CONTAINER_IMAGE_TAG="pr${{ github.event.number }}"
else
# replace / with - in the branch name
# for instance, feature/1.0.0 -> feature-1.0.0
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | sed 's/\//-/g')
fi
echo "CONTAINER_IMAGE_TAG=${CONTAINER_IMAGE_TAG}-${{ matrix.config.platform }}${{ matrix.config.version }}-${{ matrix.config.arch }}" >> $GITHUB_ENV
- name: '[execution] build linux & windows portainer binaries'
run: |
export YARN_VERSION=$(yarn --version)
export WEBPACK_VERSION=$(yarn list webpack --depth=0 | grep webpack | awk -F@ '{print $2}')
export BUILDNUMBER=${GITHUB_RUN_NUMBER}
GIT_COMMIT_HASH_LONG=${{ github.sha }}
export GIT_COMMIT_HASH_SHORT={GIT_COMMIT_HASH_LONG:0:7}
NODE_ENV="testing"
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
NODE_ENV="production"
fi
make build-all PLATFORM=${{ matrix.config.platform }} ARCH=${{ matrix.config.arch }} ENV=${NODE_ENV}
env:
CONTAINER_IMAGE_TAG: ${{ env.CONTAINER_IMAGE_TAG }}
- name: '[execution] build and push docker images'
run: |
if [ "${{ matrix.config.platform }}" == "windows" ]; then
mv dist/portainer dist/portainer.exe
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} --build-arg OSVERSION=${{ matrix.config.version }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
else
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" -f build/${{ matrix.config.platform }}/alpine.Dockerfile .
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" -f build/${{ matrix.config.platform }}/alpine.Dockerfile .
fi
fi
env:
CONTAINER_IMAGE_TAG: ${{ env.CONTAINER_IMAGE_TAG }}
build_manifests:
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
needs: [build_images]
steps:
- name: '[preparation] docker login'
uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: '[preparation] set up docker context for buildx'
run: docker version && docker context create builders
- name: '[preparation] set up docker buildx'
uses: docker/setup-buildx-action@v3.0.0
with:
endpoint: builders
- name: '[execution] build and push manifests'
run: |
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
# use the release branch name as the tag for release branches
# for instance, release/2.19 becomes 2.19
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -d "/" -f 2)
elif [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then
# use pr${{ github.event.number }} as the tag for pull requests
# for instance, pr123
CONTAINER_IMAGE_TAG="pr${{ github.event.number }}"
else
# replace / with - in the branch name
# for instance, feature/1.0.0 -> feature-1.0.0
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | sed 's/\//-/g')
fi
docker buildx imagetools create -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-ppc64le" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-s390x" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-windows1809-amd64" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-windowsltsc2022-amd64"
docker buildx imagetools create -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64-alpine" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64-alpine" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm-alpine"
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
docker buildx imagetools create -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-ppc64le" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-s390x"
docker buildx imagetools create -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64-alpine" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64-alpine" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm-alpine"
fi

View File

@@ -11,21 +11,31 @@ on:
- master
- develop
- release/*
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '18'
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- uses: actions/setup-go@v4
with:
go-version: 1.19.5
go-version: ${{ env.GO_VERSION }}
- run: yarn --frozen-lockfile
- name: Run linters
uses: wearerequired/lint-action@v1
@@ -41,6 +51,5 @@ jobs:
- name: GolangCI-Lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.52.2
working-directory: api
version: v1.55.2
args: --timeout=10m -c .golangci.yaml

View File

@@ -5,6 +5,9 @@ on:
- cron: '0 20 * * *'
workflow_dispatch:
env:
GO_VERSION: 1.21.6
jobs:
client-dependencies:
name: Client Dependency Check
@@ -25,7 +28,7 @@ jobs:
with:
json: true
- name: upload scan result as develop artifact
- name: upload scan result as develop artifact
uses: actions/upload-artifact@v3
with:
name: js-security-scan-develop-result
@@ -41,7 +44,7 @@ jobs:
name: html-js-result-${{github.run_id}}
path: js-result.html
- name: analyse vulnerabilities
- name: analyse vulnerabilities
id: set-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=snyk --path="/data/snyk.json" --output-type=matrix)
@@ -58,10 +61,10 @@ jobs:
- name: checkout repository
uses: actions/checkout@master
- name: install Go
- name: install Go
uses: actions/setup-go@v3
with:
go-version: '1.19.5'
go-version: ${{ env.GO_VERSION }}
- name: download Go modules
run: cd ./api && go get -t -v -d ./...
@@ -72,9 +75,9 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: |
yarn global add snyk
snyk test --file=./api/go.mod --json-file-output=snyk.json 2>/dev/null || :
snyk test --file=./go.mod --json-file-output=snyk.json 2>/dev/null || :
- name: upload scan result as develop artifact
- name: upload scan result as develop artifact
uses: actions/upload-artifact@v3
with:
name: go-security-scan-develop-result
@@ -102,35 +105,68 @@ jobs:
if: >-
github.ref == 'refs/heads/develop'
outputs:
image: ${{ steps.set-matrix.outputs.image_result }}
image-trivy: ${{ steps.set-trivy-matrix.outputs.image_trivy_result }}
image-docker-scout: ${{ steps.set-docker-scout-matrix.outputs.image_docker_scout_result }}
steps:
- name: scan vulnerabilities by Trivy
- name: scan vulnerabilities by Trivy
uses: docker://docker.io/aquasec/trivy:latest
continue-on-error: true
with:
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress portainerci/portainer:develop
- name: upload image security scan result as artifact
- name: upload Trivy image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-develop-result
path: image-trivy.json
- name: develop scan report export to html
- name: develop Trivy scan report export to html
run: |
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=table --export --export-filename="/data/image-result")
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=table --export --export-filename="/data/image-trivy-result")
- name: upload html file as artifact
- name: upload html file as Trivy artifact
uses: actions/upload-artifact@v3
with:
name: html-image-result-${{github.run_id}}
path: image-result.html
path: image-trivy-result.html
- name: analyse vulnerabilities
id: set-matrix
- name: analyse vulnerabilities from Trivy
id: set-trivy-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=matrix)
echo "image_result=${result}" >> $GITHUB_OUTPUT
echo "image_trivy_result=${result}" >> $GITHUB_OUTPUT
- name: scan vulnerabilities by Docker Scout
uses: docker/scout-action@v1
continue-on-error: true
with:
command: cves
image: portainerci/portainer:develop
sarif-file: image-docker-scout.json
dockerhub-user: ${{ secrets.DOCKER_HUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: upload Docker Scout image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-develop-result
path: image-docker-scout.json
- name: develop Docker Scout scan report export to html
run: |
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=docker-scout --path="/data/image-docker-scout.json" --output-type=table --export --export-filename="/data/image-docker-scout-result")
- name: upload html file as Docker Scout artifact
uses: actions/upload-artifact@v3
with:
name: html-image-result-${{github.run_id}}
path: image-docker-scout-result.html
- name: analyse vulnerabilities from Docker Scout
id: set-docker-scout-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=docker-scout --path="/data/image-docker-scout.json" --output-type=matrix)
echo "image_docker_scout_result=${result}" >> $GITHUB_OUTPUT
result-analysis:
name: Analyse Scan Results
@@ -142,22 +178,26 @@ jobs:
matrix:
js: ${{fromJson(needs.client-dependencies.outputs.js)}}
go: ${{fromJson(needs.server-dependencies.outputs.go)}}
image: ${{fromJson(needs.image-vulnerability.outputs.image)}}
image-trivy: ${{fromJson(needs.image-vulnerability.outputs.image-trivy)}}
image-docker-scout: ${{fromJson(needs.image-vulnerability.outputs.image-docker-scout)}}
steps:
- name: display the results of js, Go, and image scan
run: |
echo "${{ matrix.js.status }}"
echo "${{ matrix.go.status }}"
echo "${{ matrix.image.status }}"
echo "${{ matrix.image-trivy.status }}"
echo "${{ matrix.image-docker-scout.status }}"
echo "${{ matrix.js.summary }}"
echo "${{ matrix.go.summary }}"
echo "${{ matrix.image.summary }}"
echo "${{ matrix.image-trivy.summary }}"
echo "${{ matrix.image-docker-scout.summary }}"
- name: send message to Slack
if: >-
- name: send message to Slack
if: >-
matrix.js.status == 'failure' ||
matrix.go.status == 'failure' ||
matrix.image.status == 'failure'
matrix.image-trivy.status == 'failure' ||
matrix.image-docker-scout.status == 'failure'
uses: slackapi/slack-github-action@v1.23.0
with:
payload: |
@@ -193,7 +233,14 @@ jobs:
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Image vulnerability check*: *${{ matrix.image.status }}*\n${{ matrix.image.summary }}\n"
"text": "*Image Trivy vulnerability check*: *${{ matrix.image-trivy.status }}*\n${{ matrix.image-trivy.summary }}\n"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Image Docker Scout vulnerability check*: *${{ matrix.image-docker-scout.status }}*\n${{ matrix.image-docker-scout.summary }}\n"
}
}
]

View File

@@ -7,20 +7,24 @@ on:
- edited
paths:
- 'package.json'
- 'api/go.mod'
- 'gruntfile.js'
- 'go.mod'
- 'build/linux/Dockerfile'
- 'build/linux/alpine.Dockerfile'
- 'build/windows/Dockerfile'
- '.github/workflows/pr-security.yml'
env:
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
jobs:
client-dependencies:
name: Client Dependency Check
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
github.event.review.body == '/scan' &&
github.event.pull_request.draft == false
outputs:
jsdiff: ${{ steps.set-diff-matrix.outputs.js_diff_result }}
steps:
@@ -74,7 +78,8 @@ jobs:
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
github.event.review.body == '/scan' &&
github.event.pull_request.draft == false
outputs:
godiff: ${{ steps.set-diff-matrix.outputs.go_diff_result }}
steps:
@@ -84,7 +89,7 @@ jobs:
- name: install Go
uses: actions/setup-go@v3
with:
go-version: '1.19.5'
go-version: ${{ env.GO_VERSION }}
- name: download Go modules
run: cd ./api && go get -t -v -d ./...
@@ -95,7 +100,7 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: |
yarn global add snyk
snyk test --file=./api/go.mod --json-file-output=snyk.json 2>/dev/null || :
snyk test --file=./go.mod --json-file-output=snyk.json 2>/dev/null || :
- name: upload scan result as pull-request artifact
uses: actions/upload-artifact@v3
@@ -136,22 +141,24 @@ jobs:
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
github.event.review.body == '/scan' &&
github.event.pull_request.draft == false
outputs:
imagediff: ${{ steps.set-diff-matrix.outputs.image_diff_result }}
imagediff-trivy: ${{ steps.set-diff-trivy-matrix.outputs.image_diff_trivy_result }}
imagediff-docker-scout: ${{ steps.set-diff-docker-scout-matrix.outputs.image_diff_docker_scout_result }}
steps:
- name: checkout code
uses: actions/checkout@master
- name: install Go 1.19.5
- name: install Go
uses: actions/setup-go@v3
with:
go-version: '1.19.5'
go-version: ${{ env.GO_VERSION }}
- name: install Node.js 18.x
- name: install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: ${{ env.NODE_VERSION }}
- name: Install packages
run: yarn --frozen-lockfile
@@ -167,26 +174,26 @@ jobs:
with:
context: .
file: build/linux/Dockerfile
tags: trivy-portainer:${{ github.sha }}
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
tags: local-portainer:${{ github.sha }}
outputs: type=docker,dest=/tmp/local-portainer-image.tar
- name: load docker image
run: |
docker load --input /tmp/trivy-portainer-image.tar
docker load --input /tmp/local-portainer-image.tar
- name: scan vulnerabilities by Trivy
uses: docker://docker.io/aquasec/trivy:latest
continue-on-error: true
with:
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress local-portainer:${{ github.sha }}
- name: upload image security scan result as artifact
- name: upload Trivy image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-feature-result
path: image-trivy.json
- name: download artifacts from develop branch built by nightly scan
- name: download Trivy artifacts from develop branch built by nightly scan
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
@@ -198,21 +205,65 @@ jobs:
echo "null" > ./image-trivy-develop.json
fi
- name: pr vs develop scan report comparison export to html
- name: pr vs develop Trivy scan report comparison export to html
run: |
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=table --export --export-filename="/data/image-result")
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=table --export --export-filename="/data/image-trivy-result")
- name: upload html file as artifact
- name: upload html file as Trivy artifact
uses: actions/upload-artifact@v3
with:
name: html-image-result-compare-to-develop-${{github.run_id}}
path: image-result.html
path: image-trivy-result.html
- name: analyse different vulnerabilities against develop branch
id: set-diff-matrix
- name: analyse different vulnerabilities against develop branch by Trivy
id: set-diff-trivy-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=matrix)
echo "image_diff_result=${result}" >> $GITHUB_OUTPUT
echo "image_diff_trivy_result=${result}" >> $GITHUB_OUTPUT
- name: scan vulnerabilities by Docker Scout
uses: docker/scout-action@v1
continue-on-error: true
with:
command: cves
image: local-portainer:${{ github.sha }}
sarif-file: image-docker-scout.json
dockerhub-user: ${{ secrets.DOCKER_HUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: upload Docker Scout image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-feature-result
path: image-docker-scout.json
- name: download Docker Scout artifacts from develop branch built by nightly scan
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mv ./image-docker-scout.json ./image-docker-scout-feature.json
(gh run download -n image-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
if [[ -e ./image-docker-scout.json ]]; then
mv ./image-docker-scout.json ./image-docker-scout-develop.json
else
echo "null" > ./image-docker-scout-develop.json
fi
- name: pr vs develop Docker Scout scan report comparison export to html
run: |
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=docker-scout --path="/data/image-docker-scout-feature.json" --compare-to="/data/image-docker-scout-develop.json" --output-type=table --export --export-filename="/data/image-docker-scout-result")
- name: upload html file as Docker Scout artifact
uses: actions/upload-artifact@v3
with:
name: html-image-result-compare-to-develop-${{github.run_id}}
path: image-docker-scout-result.html
- name: analyse different vulnerabilities against develop branch by Docker Scout
id: set-diff-docker-scout-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=docker-scout --path="/data/image-docker-scout-feature.json" --compare-to="/data/image-docker-scout-develop.json" --output-type=matrix)
echo "image_diff_docker_scout_result=${result}" >> $GITHUB_OUTPUT
result-analysis:
name: Analyse Scan Result Against develop Branch
@@ -220,23 +271,28 @@ jobs:
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
github.event.review.body == '/scan' &&
github.event.pull_request.draft == false
strategy:
matrix:
jsdiff: ${{fromJson(needs.client-dependencies.outputs.jsdiff)}}
godiff: ${{fromJson(needs.server-dependencies.outputs.godiff)}}
imagediff: ${{fromJson(needs.image-vulnerability.outputs.imagediff)}}
imagediff-trivy: ${{fromJson(needs.image-vulnerability.outputs.imagediff-trivy)}}
imagediff-docker-scout: ${{fromJson(needs.image-vulnerability.outputs.imagediff-docker-scout)}}
steps:
- name: check job status of diff result
if: >-
matrix.jsdiff.status == 'failure' ||
matrix.godiff.status == 'failure' ||
matrix.imagediff.status == 'failure'
matrix.imagediff-trivy.status == 'failure' ||
matrix.imagediff-docker-scout.status == 'failure'
run: |
echo "${{ matrix.jsdiff.status }}"
echo "${{ matrix.godiff.status }}"
echo "${{ matrix.imagediff.status }}"
echo "${{ matrix.imagediff-trivy.status }}"
echo "${{ matrix.imagediff-docker-scout.status }}"
echo "${{ matrix.jsdiff.summary }}"
echo "${{ matrix.godiff.summary }}"
echo "${{ matrix.imagediff.summary }}"
echo "${{ matrix.imagediff-trivy.summary }}"
echo "${{ matrix.imagediff-docker-scout.summary }}"
exit 1

View File

@@ -1,7 +1,8 @@
name: Close Stale Issues
on:
schedule:
- cron: '0 12 * * *'
- cron: '0 12 * * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
@@ -9,7 +10,7 @@ jobs:
issues: write
steps:
- uses: actions/stale@v4.0.0
- uses: actions/stale@v8
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,25 +1,56 @@
name: Test
on: push
env:
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
on:
pull_request:
branches:
- master
- develop
- release/*
types:
- opened
- reopened
- synchronize
- ready_for_review
push:
branches:
- master
- develop
- release/*
jobs:
test-client:
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '18'
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- run: yarn --frozen-lockfile
- name: Run tests
run: yarn jest --maxWorkers=2
run: make test-client ARGS="--maxWorkers=2 --minWorkers=1"
test-server:
strategy:
matrix:
config:
- { platform: linux, arch: amd64 }
- { platform: linux, arch: arm64 }
- { platform: windows, arch: amd64, version: 1809 }
- { platform: windows, arch: amd64, version: ltsc2022 }
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: 1.19.5
go-version: ${{ env.GO_VERSION }}
- name: Run tests
run: make test-server

View File

@@ -6,22 +6,32 @@ on:
- master
- develop
- 'release/*'
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
jobs:
openapi-spec:
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '1.18'
go-version: ${{ env.GO_VERSION }}
- name: Download golang modules
run: cd ./api && go get -t -v -d ./...
- uses: actions/setup-node@v3
with:
node-version: '18'
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- run: yarn --frozen-lockfile

40
.golangci.yaml Normal file
View File

@@ -0,0 +1,40 @@
linters:
# Disable all linters, the defaults don't pass on our code yet
disable-all: true
# Enable these for now
enable:
- unused
- depguard
- gosimple
- govet
- errorlint
- exportloopref
linters-settings:
depguard:
rules:
main:
deny:
- pkg: 'encoding/json'
desc: 'use github.com/segmentio/encoding/json'
- pkg: 'github.com/sirupsen/logrus'
desc: 'logging is allowed only by github.com/rs/zerolog'
- pkg: 'golang.org/x/exp'
desc: 'exp is not allowed'
- pkg: 'github.com/portainer/libcrypto'
desc: 'use github.com/portainer/portainer/pkg/libcrypto'
- pkg: 'github.com/portainer/libhttp'
desc: 'use github.com/portainer/portainer/pkg/libhttp'
files:
- '!**/*_test.go'
- '!**/base.go'
- '!**/base_tx.go'
# errorlint is causing a typecheck error for some reason. The go compiler will report these
# anyway, so ignore them from the linter
issues:
exclude-rules:
- path: ./
linters:
- typecheck

View File

@@ -2,18 +2,24 @@
"printWidth": 180,
"singleQuote": true,
"htmlWhitespaceSensitivity": "strict",
"trailingComma": "es5",
"overrides": [
{
"files": ["*.html"],
"files": [
"*.html"
],
"options": {
"parser": "angular"
}
},
{
"files": ["*.{j,t}sx", "*.ts"],
"files": [
"*.{j,t}sx",
"*.ts"
],
"options": {
"printWidth": 80
}
}
]
}
}

View File

@@ -3,6 +3,7 @@ import { StorybookConfig } from '@storybook/react-webpack5';
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import { Configuration } from 'webpack';
import postcss from 'postcss';
const config: StorybookConfig = {
stories: ['../app/**/*.stories.@(ts|tsx)'],
addons: [
@@ -87,9 +88,6 @@ const config: StorybookConfig = {
name: '@storybook/react-webpack5',
options: {},
},
docs: {
autodocs: true,
},
};
export default config;

View File

@@ -1,23 +1,26 @@
import '../app/assets/css';
import React from 'react';
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
import { initialize as initMSW, mswDecorator } from 'msw-storybook-addon';
import { handlers } from '@/setup-tests/server-handlers';
import { initialize as initMSW, mswLoader } from 'msw-storybook-addon';
import { handlers } from '../app/setup-tests/server-handlers';
import { QueryClient, QueryClientProvider } from 'react-query';
// Initialize MSW
initMSW({
onUnhandledRequest: ({ method, url }) => {
if (url.pathname.startsWith('/api')) {
console.error(`Unhandled ${method} request to ${url}.
initMSW(
{
onUnhandledRequest: ({ method, url }) => {
console.log(method, url);
if (url.startsWith('/api')) {
console.error(`Unhandled ${method} request to ${url}.
This exception has been only logged in the console, however, it's strongly recommended to resolve this error as you don't want unmocked data in Storybook stories.
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses
`);
}
}
},
},
});
handlers
);
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
@@ -44,5 +47,6 @@ export const decorators = [
</UIRouter>
</QueryClientProvider>
),
mswDecorator,
];
export const loaders = [mswLoader];

View File

@@ -2,22 +2,22 @@
/* tslint:disable */
/**
* Mock Service Worker (0.36.3).
* Mock Service Worker (2.0.11).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929';
const bypassHeaderName = 'x-msw-bypass';
const INTEGRITY_CHECKSUM = 'c5f7f8e188b673ea4e677df7ea3c5a39';
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse');
const activeClientIds = new Set();
self.addEventListener('install', function () {
return self.skipWaiting();
self.skipWaiting();
});
self.addEventListener('activate', async function (event) {
return self.clients.claim();
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim());
});
self.addEventListener('message', async function (event) {
@@ -33,7 +33,9 @@ self.addEventListener('message', async function (event) {
return;
}
const allClients = await self.clients.matchAll();
const allClients = await self.clients.matchAll({
type: 'window',
});
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
@@ -83,165 +85,8 @@ self.addEventListener('message', async function (event) {
}
});
// Resolve the "main" client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId);
if (client.frameType === 'top-level') {
return client;
}
const allClients = await self.clients.matchAll();
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible';
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id);
});
}
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event);
const response = await getResponse(event, client, requestId);
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
(async function () {
const clonedResponse = response.clone();
sendToClient(client, {
type: 'RESPONSE',
payload: {
requestId,
type: clonedResponse.type,
ok: clonedResponse.ok,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
body: clonedResponse.body === null ? null : await clonedResponse.text(),
headers: serializeHeaders(clonedResponse.headers),
redirected: clonedResponse.redirected,
},
});
})();
}
return response;
}
async function getResponse(event, client, requestId) {
const { request } = event;
const requestClone = request.clone();
const getOriginalResponse = () => fetch(requestClone);
// Bypass mocking when the request client is not active.
if (!client) {
return getOriginalResponse();
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return await getOriginalResponse();
}
// Bypass requests with the explicit bypass header
if (requestClone.headers.get(bypassHeaderName) === 'true') {
const cleanRequestHeaders = serializeHeaders(requestClone.headers);
// Remove the bypass header to comply with the CORS preflight check.
delete cleanRequestHeaders[bypassHeaderName];
const originalRequest = new Request(requestClone, {
headers: new Headers(cleanRequestHeaders),
});
return fetch(originalRequest);
}
// Send the request to the client-side MSW.
const reqHeaders = serializeHeaders(request.headers);
const body = await request.text();
const clientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: reqHeaders,
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body,
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
},
});
switch (clientMessage.type) {
case 'MOCK_SUCCESS': {
return delayPromise(() => respondWithMock(clientMessage), clientMessage.payload.delay);
}
case 'MOCK_NOT_FOUND': {
return getOriginalResponse();
}
case 'NETWORK_ERROR': {
const { name, message } = clientMessage.payload;
const networkError = new Error(message);
networkError.name = name;
// Rejecting a request Promise emulates a network error.
throw networkError;
}
case 'INTERNAL_ERROR': {
const parsedBody = JSON.parse(clientMessage.payload.body);
console.error(
`\
[MSW] Uncaught exception in the request handler for "%s %s":
${parsedBody.location}
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
request.method,
request.url
);
return respondWithMock(clientMessage);
}
}
return getOriginalResponse();
}
self.addEventListener('fetch', function (event) {
const { request } = event;
const accept = request.headers.get('accept') || '';
// Bypass server-sent events.
if (accept.includes('text/event-stream')) {
return;
}
// Bypass navigation requests.
if (request.mode === 'navigate') {
@@ -261,36 +106,149 @@ self.addEventListener('fetch', function (event) {
return;
}
const requestId = uuidv4();
return event.respondWith(
handleRequest(event, requestId).catch((error) => {
if (error.name === 'NetworkError') {
console.warn('[MSW] Successfully emulated a network error for the "%s %s" request.', request.method, request.url);
return;
}
// At this point, any exception indicates an issue with the original request/response.
console.error(
`\
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
request.method,
request.url,
`${error.name}: ${error.message}`
);
})
);
// Generate unique request ID.
const requestId = crypto.randomUUID();
event.respondWith(handleRequest(event, requestId));
});
function serializeHeaders(headers) {
const reqHeaders = {};
headers.forEach((value, name) => {
reqHeaders[name] = reqHeaders[name] ? [].concat(reqHeaders[name]).concat(value) : value;
});
return reqHeaders;
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event);
const response = await getResponse(event, client, requestId);
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
(async function () {
const responseClone = response.clone();
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body]
);
})();
}
return response;
}
function sendToClient(client, message) {
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId);
if (client?.frameType === 'top-level') {
return client;
}
const allClients = await self.clients.matchAll({
type: 'window',
});
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible';
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id);
});
}
async function getResponse(event, client, requestId) {
const { request } = event;
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone();
function passthrough() {
const headers = Object.fromEntries(requestClone.headers.entries());
// Remove internal MSW request header so the passthrough request
// complies with any potential CORS preflight checks on the server.
// Some servers forbid unknown request headers.
delete headers['x-msw-intention'];
return fetch(requestClone, { headers });
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough();
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough();
}
// Bypass requests with the explicit bypass header.
// Such requests can be issued by "ctx.fetch()".
const mswIntention = request.headers.get('x-msw-intention');
if (['bypass', 'passthrough'].includes(mswIntention)) {
return passthrough();
}
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer();
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer]
);
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data);
}
case 'MOCK_NOT_FOUND': {
return passthrough();
}
}
return passthrough();
}
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel();
@@ -302,27 +260,25 @@ function sendToClient(client, message) {
resolve(event.data);
};
client.postMessage(JSON.stringify(message), [channel.port2]);
client.postMessage(message, [channel.port2].concat(transferrables.filter(Boolean)));
});
}
function delayPromise(cb, duration) {
return new Promise((resolve) => {
setTimeout(() => resolve(cb()), duration);
});
}
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error();
}
function respondWithMock(clientMessage) {
return new Response(clientMessage.payload.body, {
...clientMessage.payload,
headers: clientMessage.payload.headers,
});
}
const mockedResponse = new Response(response.body, response);
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
const v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
});
return mockedResponse;
}

View File

@@ -7,9 +7,9 @@ ARCH=$(shell go env GOARCH)
# build target, can be one of "production", "testing", "development"
ENV=development
WEBPACK_CONFIG=webpack/webpack.$(ENV).js
TAG=latest
TAG=local
SWAG=go run github.com/swaggo/swag/cmd/swag@v1.8.11
SWAG=go run github.com/swaggo/swag/cmd/swag@v1.16.2
GOTESTSUM=go run gotest.tools/gotestsum@latest
# Don't change anything below this line unless you know what you're doing
@@ -65,10 +65,10 @@ clean: ## Remove all build and download artifacts
test: test-server test-client ## Run all tests
test-client: ## Run client tests
yarn test
yarn test $(ARGS)
test-server: ## Run server tests
cd api && $(GOTESTSUM) --format pkgname-and-test-fails --format-hide-empty-pkg --hide-summary skipped -- -cover ./...
$(GOTESTSUM) --format pkgname-and-test-fails --format-hide-empty-pkg --hide-summary skipped -- -cover ./...
##@ Dev
.PHONY: dev dev-client dev-server
@@ -92,7 +92,7 @@ format-client: ## Format client code
yarn format
format-server: ## Format server code
cd api && go fmt ./...
go fmt ./...
##@ Lint
.PHONY: lint lint-client lint-server
@@ -102,7 +102,7 @@ lint-client: ## Lint client code
yarn lint
lint-server: ## Lint server code
cd api && go vet ./...
golangci-lint run --timeout=10m -c .golangci.yaml
##@ Extension
@@ -114,7 +114,7 @@ dev-extension: build-server build-client ## Run the extension in development mod
##@ Docs
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
docs-build: init-dist ## Build docs
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 --markdownFiles ./
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 -p pascalcase --markdownFiles ./
docs-validate: docs-build ## Validate docs
yarn swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml

View File

@@ -1,31 +0,0 @@
linters:
# Disable all linters, the defaults don't pass on our code yet
disable-all: true
# Enable these for now
enable:
- depguard
- govet
- errorlint
- exportloopref
linters-settings:
depguard:
list-type: denylist
include-go-root: true
packages:
- github.com/sirupsen/logrus
- golang.org/x/exp
packages-with-error-message:
- github.com/sirupsen/logrus: 'logging is allowed only by github.com/rs/zerolog'
ignore-file-rules:
- '**/*_test.go'
- '**/base.go'
- '**/base_tx.go'
# errorlint is causing a typecheck error for some reason. The go compiler will report these
# anyway, so ignore them from the linter
issues:
exclude-rules:
- path: ./
linters:
- typecheck

View File

@@ -7,9 +7,9 @@ import (
"sync"
"time"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/rs/zerolog/log"
)

View File

@@ -1,30 +1,17 @@
package apikey
import (
"crypto/rand"
"io"
portainer "github.com/portainer/portainer/api"
)
// APIKeyService represents a service for managing API keys.
type APIKeyService interface {
HashRaw(rawKey string) []byte
HashRaw(rawKey string) string
GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error)
GetAPIKey(apiKeyID portainer.APIKeyID) (*portainer.APIKey, error)
GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey, error)
GetDigestUserAndKey(digest []byte) (portainer.User, portainer.APIKey, error)
GetDigestUserAndKey(digest string) (portainer.User, portainer.APIKey, error)
UpdateAPIKey(apiKey *portainer.APIKey) error
DeleteAPIKey(apiKeyID portainer.APIKeyID) error
InvalidateUserKeyCache(userId portainer.UserID) bool
}
// generateRandomKey generates a random key of specified length
// source: https://github.com/gorilla/securecookie/blob/master/securecookie.go#L515
func generateRandomKey(length int) []byte {
k := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, k); err != nil {
return nil
}
return k
}

View File

@@ -3,6 +3,7 @@ package apikey
import (
"testing"
"github.com/portainer/portainer/api/internal/securecookie"
"github.com/stretchr/testify/assert"
)
@@ -33,7 +34,7 @@ func Test_generateRandomKey(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := generateRandomKey(tt.wantLenth)
got := securecookie.GenerateRandomKey(tt.wantLenth)
is.Equal(tt.wantLenth, len(got))
})
}
@@ -41,7 +42,7 @@ func Test_generateRandomKey(t *testing.T) {
t.Run("Generated keys are unique", func(t *testing.T) {
keys := make(map[string]bool)
for i := 0; i < 100; i++ {
key := generateRandomKey(8)
key := securecookie.GenerateRandomKey(8)
_, ok := keys[string(key)]
is.False(ok)
keys[string(key)] = true

View File

@@ -33,8 +33,8 @@ func NewAPIKeyCache(cacheSize int) *apiKeyCache {
// Get returns the user/key associated to an api-key's digest
// This is required because HTTP requests will contain the digest of the API key in header,
// the digest value must be mapped to a portainer user.
func (c *apiKeyCache) Get(digest []byte) (portainer.User, portainer.APIKey, bool) {
val, ok := c.cache.Get(string(digest))
func (c *apiKeyCache) Get(digest string) (portainer.User, portainer.APIKey, bool) {
val, ok := c.cache.Get(digest)
if !ok {
return portainer.User{}, portainer.APIKey{}, false
}
@@ -44,23 +44,23 @@ func (c *apiKeyCache) Get(digest []byte) (portainer.User, portainer.APIKey, bool
}
// Set persists a user/key entry to the cache
func (c *apiKeyCache) Set(digest []byte, user portainer.User, apiKey portainer.APIKey) {
c.cache.Add(string(digest), entry{
func (c *apiKeyCache) Set(digest string, user portainer.User, apiKey portainer.APIKey) {
c.cache.Add(digest, entry{
user: user,
apiKey: apiKey,
})
}
// Delete evicts a digest's user/key entry key from the cache
func (c *apiKeyCache) Delete(digest []byte) {
c.cache.Remove(string(digest))
func (c *apiKeyCache) Delete(digest string) {
c.cache.Remove(digest)
}
// InvalidateUserKeyCache loops through all the api-keys associated to a user and removes them from the cache
func (c *apiKeyCache) InvalidateUserKeyCache(userId portainer.UserID) bool {
present := false
for _, k := range c.cache.Keys() {
user, _, _ := c.Get([]byte(k.(string)))
user, _, _ := c.Get(k.(string))
if user.ID == userId {
present = c.cache.Remove(k)
}

View File

@@ -17,19 +17,19 @@ func Test_apiKeyCacheGet(t *testing.T) {
keyCache.cache.Add(string(""), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
tests := []struct {
digest []byte
digest string
found bool
}{
{
digest: []byte("foo"),
digest: "foo",
found: true,
},
{
digest: []byte(""),
digest: "",
found: true,
},
{
digest: []byte("bar"),
digest: "bar",
found: false,
},
}
@@ -48,11 +48,11 @@ func Test_apiKeyCacheSet(t *testing.T) {
keyCache := NewAPIKeyCache(10)
// pre-populate cache
keyCache.Set([]byte("bar"), portainer.User{ID: 2}, portainer.APIKey{})
keyCache.Set([]byte("foo"), portainer.User{ID: 1}, portainer.APIKey{})
keyCache.Set("bar", portainer.User{ID: 2}, portainer.APIKey{})
keyCache.Set("foo", portainer.User{ID: 1}, portainer.APIKey{})
// overwrite existing entry
keyCache.Set([]byte("foo"), portainer.User{ID: 3}, portainer.APIKey{})
keyCache.Set("foo", portainer.User{ID: 3}, portainer.APIKey{})
val, ok := keyCache.cache.Get(string("bar"))
is.True(ok)
@@ -74,14 +74,14 @@ func Test_apiKeyCacheDelete(t *testing.T) {
t.Run("Delete an existing entry", func(t *testing.T) {
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.Delete([]byte("foo"))
keyCache.Delete("foo")
_, ok := keyCache.cache.Get(string("foo"))
is.False(ok)
})
t.Run("Delete a non-existing entry", func(t *testing.T) {
nonPanicFunc := func() { keyCache.Delete([]byte("non-existent-key")) }
nonPanicFunc := func() { keyCache.Delete("non-existent-key") }
is.NotPanics(nonPanicFunc)
})
}
@@ -131,16 +131,16 @@ func Test_apiKeyCacheLRU(t *testing.T) {
keyCache := NewAPIKeyCache(test.cacheLen)
for _, key := range test.key {
keyCache.Set([]byte(key), portainer.User{ID: 1}, portainer.APIKey{})
keyCache.Set(key, portainer.User{ID: 1}, portainer.APIKey{})
}
for _, key := range test.foundKeys {
_, _, found := keyCache.Get([]byte(key))
_, _, found := keyCache.Get(key)
is.True(found, "Key %s not found", key)
}
for _, key := range test.evictedKeys {
_, _, found := keyCache.Get([]byte(key))
_, _, found := keyCache.Get(key)
is.False(found, "key %s should have been evicted", key)
}
})

View File

@@ -8,6 +8,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/securecookie"
"github.com/pkg/errors"
)
@@ -31,15 +32,15 @@ func NewAPIKeyService(apiKeyRepository dataservices.APIKeyRepository, userReposi
}
// HashRaw computes a hash digest of provided raw API key.
func (a *apiKeyService) HashRaw(rawKey string) []byte {
func (a *apiKeyService) HashRaw(rawKey string) string {
hashDigest := sha256.Sum256([]byte(rawKey))
return hashDigest[:]
return base64.StdEncoding.EncodeToString(hashDigest[:])
}
// GenerateApiKey generates a raw API key for a user (for one-time display).
// The generated API key is stored in the cache and database.
func (a *apiKeyService) GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error) {
randKey := generateRandomKey(32)
randKey := securecookie.GenerateRandomKey(32)
encodedRawAPIKey := base64.StdEncoding.EncodeToString(randKey)
prefixedAPIKey := portainerAPIKeyPrefix + encodedRawAPIKey
@@ -76,7 +77,7 @@ func (a *apiKeyService) GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey,
// GetDigestUserAndKey returns the user and api-key associated to a specified hash digest.
// A cache lookup is performed first; if the user/api-key is not found in the cache, respective database lookups are performed.
func (a *apiKeyService) GetDigestUserAndKey(digest []byte) (portainer.User, portainer.APIKey, error) {
func (a *apiKeyService) GetDigestUserAndKey(digest string) (portainer.User, portainer.APIKey, error) {
// get api key from cache if possible
cachedUser, cachedKey, ok := a.cache.Get(digest)
if ok {

View File

@@ -2,6 +2,7 @@ package apikey
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
"testing"
@@ -68,7 +69,7 @@ func Test_GenerateApiKey(t *testing.T) {
generatedDigest := sha256.Sum256([]byte(rawKey))
is.Equal(apiKey.Digest, generatedDigest[:])
is.Equal(apiKey.Digest, base64.StdEncoding.EncodeToString(generatedDigest[:]))
})
}

View File

@@ -48,18 +48,6 @@ func TarGzDir(absolutePath string) (string, error) {
}
func addToArchive(tarWriter *tar.Writer, pathInArchive string, path string, info os.FileInfo) error {
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
header.Name = pathInArchive // use relative paths in archive
err = tarWriter.WriteHeader(header)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
@@ -68,6 +56,26 @@ func addToArchive(tarWriter *tar.Writer, pathInArchive string, path string, info
if err != nil {
return err
}
stat, err := file.Stat()
if err != nil {
return err
}
header, err := tar.FileInfoHeader(stat, stat.Name())
if err != nil {
return err
}
header.Name = pathInArchive // use relative paths in archive
err = tarWriter.WriteHeader(header)
if err != nil {
return err
}
if stat.IsDir() {
return nil
}
_, err = io.Copy(tarWriter, file)
return err
}
@@ -98,7 +106,7 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
// skip, dir will be created with a file
case tar.TypeReg:
p := filepath.Clean(filepath.Join(outputDirPath, header.Name))
if err := os.MkdirAll(filepath.Dir(p), 0744); err != nil {
if err := os.MkdirAll(filepath.Dir(p), 0o744); err != nil {
return fmt.Errorf("Failed to extract dir %s", filepath.Dir(p))
}
outFile, err := os.Create(p)

View File

@@ -17,7 +17,7 @@ import (
"github.com/rs/zerolog/log"
)
const rwxr__r__ os.FileMode = 0744
const rwxr__r__ os.FileMode = 0o744
var filesToBackup = []string{
"certs",
@@ -30,6 +30,7 @@ var filesToBackup = []string{
"portainer.key",
"portainer.pub",
"tls",
"chisel",
}
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
@@ -81,14 +82,8 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
}
func backupDb(backupDirPath string, datastore dataservices.DataStore) error {
backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
if err != nil {
return err
}
if err = datastore.BackupTo(backupWriter); err != nil {
return err
}
return backupWriter.Close()
_, err := datastore.Backup(filepath.Join(backupDirPath, "portainer.db"))
return err
}
func encrypt(path string, passphrase string) (string, error) {

View File

@@ -1,9 +1,12 @@
package build
import "runtime"
// Variables to be set during the build time
var BuildNumber string
var ImageTag string
var NodejsVersion string
var YarnVersion string
var WebpackVersion string
var GoVersion string
var GoVersion string = runtime.Version()
var GitCommit string

View File

@@ -21,6 +21,7 @@ const (
tunnelCleanupInterval = 10 * time.Second
requiredTimeout = 15 * time.Second
activeTimeout = 4*time.Minute + 30*time.Second
pingTimeout = 3 * time.Second
)
// Service represents a service to manage the state of multiple reverse tunnels.
@@ -59,14 +60,18 @@ func (service *Service) pingAgent(endpointID portainer.EndpointID) error {
}
httpClient := &http.Client{
Timeout: 3 * time.Second,
Timeout: pingTimeout,
}
resp, err := httpClient.Do(req)
if err != nil {
return err
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
return err
return nil
}
// KeepTunnelAlive keeps the tunnel of the given environment for maxAlive duration, or until ctx is done
@@ -75,10 +80,11 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("max_alive_minutes", maxAlive.Minutes()).
Msg("start")
Msg("KeepTunnelAlive: start")
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
pingTicker := time.NewTicker(tunnelCleanupInterval)
defer pingTicker.Stop()
@@ -91,13 +97,13 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("ping agent")
Msg("KeepTunnelAlive: ping agent")
}
case <-maxAliveTicker.C:
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("timeout_minutes", maxAlive.Minutes()).
Msg("tunnel keep alive timeout")
Msg("KeepTunnelAlive: tunnel keep alive timeout")
return
case <-ctx.Done():
@@ -105,7 +111,7 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("tunnel stop")
Msg("KeepTunnelAlive: tunnel stop")
return
}
@@ -126,8 +132,8 @@ func (service *Service) StartTunnelServer(addr, port string, snapshotService por
}
config := &chserver.Config{
Reverse: true,
PrivateKeyFile: privateKeyFile,
Reverse: true,
KeyFile: privateKeyFile,
}
chiselServer, err := chserver.NewServer(config)

View File

@@ -0,0 +1,39 @@
package chisel
import (
"net"
"net/http"
"testing"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/require"
)
func TestPingAgentPanic(t *testing.T) {
endpointID := portainer.EndpointID(1)
s := NewService(nil, nil, nil)
defer func() {
require.Nil(t, recover())
}()
mux := http.NewServeMux()
mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(pingTimeout + 1*time.Second)
})
ln, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
require.NoError(t, err)
go func() {
require.NoError(t, http.Serve(ln, mux))
}()
s.getTunnelDetails(endpointID)
s.tunnelDetailsMap[endpointID].Port = ln.Addr().(*net.TCPAddr).Port
require.Error(t, s.pingAgent(endpointID))
}

View File

@@ -8,9 +8,9 @@ import (
"strings"
"time"
"github.com/portainer/libcrypto"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/portainer/portainer/pkg/libcrypto"
"github.com/dchest/uniuri"
)

View File

@@ -49,7 +49,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
Rollback: kingpin.Flag("rollback", "Rollback the database to the previous backup").Bool(),
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").String(),
AdminPassword: kingpin.Flag("admin-password", "Set admin password with provided hash").String(),
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),

View File

@@ -9,7 +9,7 @@ import (
// Confirm starts a rollback db cli application
func Confirm(message string) (bool, error) {
fmt.Printf("%s [y/N]", message)
fmt.Printf("%s [y/N] ", message)
reader := bufio.NewReader(os.Stdin)

View File

@@ -39,9 +39,9 @@ func setLoggingMode(mode string) {
case "PRETTY":
log.Logger = log.Output(zerolog.ConsoleWriter{
Out: os.Stderr,
NoColor: true,
TimeFormat: "2006/01/02 03:04PM",
FormatMessage: formatMessage})
FormatMessage: formatMessage,
})
case "JSON":
log.Logger = log.Output(os.Stderr)
}
@@ -51,5 +51,6 @@ func formatMessage(i interface{}) string {
if i == nil {
return ""
}
return fmt.Sprintf("%s |", i)
}

View File

@@ -3,11 +3,9 @@ package main
import (
"context"
"crypto/sha256"
"math/rand"
"os"
"path"
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
@@ -20,6 +18,7 @@ import (
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/datastore/migrator"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/docker"
dockerclient "github.com/portainer/portainer/api/docker/client"
@@ -42,6 +41,7 @@ import (
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/pendingactions"
"github.com/portainer/portainer/api/scheduler"
"github.com/portainer/portainer/api/stacks/deployments"
"github.com/portainer/portainer/pkg/featureflags"
@@ -119,11 +119,15 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
log.Fatal().Err(err).Msg("failed generating instance id")
}
migratorInstance := migrator.NewMigrator(&migrator.MigratorParameters{})
migratorCount := migratorInstance.GetMigratorCountOfCurrentAPIVersion()
// from MigrateData
v := models.Version{
SchemaVersion: portainer.APIVersion,
Edition: int(portainer.PortainerCE),
InstanceID: instanceId.String(),
MigratorCount: migratorCount,
}
store.VersionService.UpdateVersion(&v)
@@ -152,6 +156,16 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
return store
}
// checkDBSchemaServerVersionMatch checks if the server version matches the db scehma version
func checkDBSchemaServerVersionMatch(dbStore dataservices.DataStore, serverVersion string, serverEdition int) bool {
v, err := dbStore.Version().Version()
if err != nil {
return false
}
return v.SchemaVersion == serverVersion && v.Edition == serverEdition
}
func initComposeStackManager(composeDeployer libstack.Deployer, proxyManager *proxy.Manager) portainer.ComposeStackManager {
composeWrapper, err := exec.NewComposeStackManager(composeDeployer, proxyManager)
if err != nil {
@@ -184,7 +198,7 @@ func initAPIKeyService(datastore dataservices.DataStore) apikey.APIKeyService {
return apikey.NewAPIKeyService(datastore.APIKeyRepository(), datastore.User())
}
func initJWTService(userSessionTimeout string, dataStore dataservices.DataStore) (dataservices.JWTService, error) {
func initJWTService(userSessionTimeout string, dataStore dataservices.DataStore) (portainer.JWTService, error) {
if userSessionTimeout == "" {
userSessionTimeout = portainer.DefaultUserSessionTimeout
}
@@ -248,11 +262,12 @@ func initSnapshotService(
dockerClientFactory *dockerclient.ClientFactory,
kubernetesClientFactory *kubecli.ClientFactory,
shutdownCtx context.Context,
pendingActionsService *pendingactions.PendingActionsService,
) (portainer.SnapshotService, error) {
dockerSnapshotter := docker.NewSnapshotter(dockerClientFactory)
kubernetesSnapshotter := kubernetes.NewSnapshotter(kubernetesClientFactory)
snapshotService, err := snapshot.NewService(snapshotIntervalFromFlag, dataStore, dockerSnapshotter, kubernetesSnapshotter, shutdownCtx)
snapshotService, err := snapshot.NewService(snapshotIntervalFromFlag, dataStore, dockerSnapshotter, kubernetesSnapshotter, shutdownCtx, pendingActionsService)
if err != nil {
return nil, err
}
@@ -383,6 +398,11 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatal().Err(err).Msg("")
}
// check if the db schema version matches with server version
if !checkDBSchemaServerVersionMatch(dataStore, portainer.APIVersion, int(portainer.Edition)) {
log.Fatal().Msg("The database schema version does not align with the server version. Please consider reverting to the previous server version or addressing the database migration issue.")
}
instanceID, err := dataStore.Version().InstanceID()
if err != nil {
log.Fatal().Err(err).Msg("failed getting instance id")
@@ -434,15 +454,17 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
authorizationService := authorization.NewService(dataStore)
authorizationService.K8sClientFactory = kubernetesClientFactory
pendingActionsService := pendingactions.NewService(dataStore, kubernetesClientFactory, authorizationService, shutdownCtx)
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx, pendingActionsService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing snapshot service")
}
snapshotService.Start()
authorizationService := authorization.NewService(dataStore)
authorizationService.K8sClientFactory = kubernetesClientFactory
kubernetesTokenCacheManager := kubeproxy.NewTokenCacheManager()
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService(*flags.BaseURL, *flags.AddrHTTPS, sslSettings.CertPath)
@@ -602,12 +624,11 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
DemoService: demoService,
UpgradeService: upgradeService,
AdminCreationDone: adminCreationDone,
PendingActionsService: pendingActionsService,
}
}
func main() {
rand.Seed(time.Now().UnixNano())
configureLogger()
setLoggingMode("PRETTY")

View File

@@ -8,7 +8,7 @@ import (
"encoding/base64"
"encoding/hex"
"github.com/portainer/libcrypto"
"github.com/portainer/portainer/pkg/libcrypto"
)
const (

View File

@@ -144,6 +144,8 @@ func (connection *DbConnection) Open() error {
// Close closes the BoltDB database.
// Safe to being called multiple times.
func (connection *DbConnection) Close() error {
log.Info().Msg("closing PortainerDB")
if connection.DB != nil {
return connection.DB.Close()
}
@@ -255,7 +257,7 @@ func (connection *DbConnection) UpdateObjectFunc(bucketName string, key []byte,
return fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
}
err := connection.UnmarshalObjectWithJsoniter(data, object)
err := connection.UnmarshalObject(data, object)
if err != nil {
return err
}

View File

@@ -1,10 +1,10 @@
package boltdb
import (
"encoding/json"
"time"
"github.com/rs/zerolog/log"
"github.com/segmentio/encoding/json"
bolt "go.etcd.io/bbolt"
)

View File

@@ -1,34 +1,41 @@
package boltdb
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"fmt"
"io"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/segmentio/encoding/json"
)
var errEncryptedStringTooShort = fmt.Errorf("encrypted string too short")
// MarshalObject encodes an object to binary format
func (connection *DbConnection) MarshalObject(object interface{}) (data []byte, err error) {
func (connection *DbConnection) MarshalObject(object interface{}) ([]byte, error) {
buf := &bytes.Buffer{}
// Special case for the VERSION bucket. Here we're not using json
if v, ok := object.(string); ok {
data = []byte(v)
buf.WriteString(v)
} else {
data, err = json.Marshal(object)
if err != nil {
return data, err
enc := json.NewEncoder(buf)
enc.SetSortMapKeys(false)
enc.SetAppendNewline(false)
if err := enc.Encode(object); err != nil {
return nil, err
}
}
if connection.getEncryptionKey() == nil {
return data, nil
return buf.Bytes(), nil
}
return encrypt(data, connection.getEncryptionKey())
return encrypt(buf.Bytes(), connection.getEncryptionKey())
}
// UnmarshalObject decodes an object from binary data
@@ -54,31 +61,6 @@ func (connection *DbConnection) UnmarshalObject(data []byte, object interface{})
return err
}
// UnmarshalObjectWithJsoniter decodes an object from binary data
// using the jsoniter library. It is mainly used to accelerate environment(endpoint)
// decoding at the moment.
func (connection *DbConnection) UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
if connection.getEncryptionKey() != nil {
var err error
data, err = decrypt(data, connection.getEncryptionKey())
if err != nil {
return err
}
}
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary
err := jsoni.Unmarshal(data, &object)
if err != nil {
if s, ok := object.(*string); ok {
*s = string(data)
return nil
}
return err
}
return nil
}
// mmm, don't have a KMS .... aes GCM seems the most likely from
// https://gist.github.com/atoponce/07d8d4c833873be2f68c34f9afc5a78a#symmetric-encryption

View File

@@ -28,7 +28,7 @@ func (tx *DbTransaction) GetObject(bucketName string, key []byte, object interfa
return fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
}
return tx.conn.UnmarshalObjectWithJsoniter(value, object)
return tx.conn.UnmarshalObject(value, object)
}
func (tx *DbTransaction) UpdateObject(bucketName string, key []byte, object interface{}) error {
@@ -134,7 +134,7 @@ func (tx *DbTransaction) GetAllWithJsoniter(bucketName string, obj interface{},
bucket := tx.tx.Bucket([]byte(bucketName))
return bucket.ForEach(func(k []byte, v []byte) error {
err := tx.conn.UnmarshalObjectWithJsoniter(v, obj)
err := tx.conn.UnmarshalObject(v, obj)
if err == nil {
obj, err = appendFn(obj)
}
@@ -147,7 +147,7 @@ func (tx *DbTransaction) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte
cursor := tx.tx.Bucket([]byte(bucketName)).Cursor()
for k, v := cursor.Seek(keyPrefix); k != nil && bytes.HasPrefix(k, keyPrefix); k, v = cursor.Next() {
err := tx.conn.UnmarshalObjectWithJsoniter(v, obj)
err := tx.conn.UnmarshalObject(v, obj)
if err != nil {
return err
}

View File

@@ -1,7 +1,6 @@
package apikeyrepository
import (
"bytes"
"errors"
"fmt"
@@ -37,7 +36,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
// GetAPIKeysByUserID returns a slice containing all the APIKeys a user has access to.
func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error) {
var result = make([]portainer.APIKey, 0)
result := make([]portainer.APIKey, 0)
err := service.Connection.GetAll(
BucketName,
@@ -61,7 +60,7 @@ func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer
// GetAPIKeyByDigest returns the API key for the associated digest.
// Note: there is a 1-to-1 mapping of api-key and digest
func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error) {
func (service *Service) GetAPIKeyByDigest(digest string) (*portainer.APIKey, error) {
var k *portainer.APIKey
stop := fmt.Errorf("ok")
err := service.Connection.GetAll(
@@ -73,7 +72,7 @@ func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, err
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
return nil, fmt.Errorf("failed to convert to APIKey object: %s", obj)
}
if bytes.Equal(key.Digest, digest) {
if key.Digest == digest {
k = key
return nil, stop
}

View File

@@ -5,6 +5,7 @@ import (
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
@@ -144,6 +145,23 @@ func (service *Service) Create(endpoint *portainer.Endpoint) error {
})
}
func (service *Service) EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.Endpoint, error) {
var endpoints = make([]portainer.Endpoint, 0)
return endpoints, service.connection.GetAll(
BucketName,
&portainer.Endpoint{},
dataservices.FilterFn(&endpoints, func(e portainer.Endpoint) bool {
for t := range e.TeamAccessPolicies {
if t == teamID {
return true
}
}
return false
}),
)
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service *Service) GetNextIdentifier() int {
var identifier int

View File

@@ -122,6 +122,23 @@ func (service ServiceTx) Create(endpoint *portainer.Endpoint) error {
return nil
}
func (service ServiceTx) EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.Endpoint, error) {
var endpoints = make([]portainer.Endpoint, 0)
return endpoints, service.tx.GetAll(
BucketName,
&portainer.Endpoint{},
dataservices.FilterFn(&endpoints, func(e portainer.Endpoint) bool {
for t := range e.TeamAccessPolicies {
if t == teamID {
return true
}
}
return false
}),
)
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service ServiceTx) GetNextIdentifier() int {
return service.tx.GetNextIdentifier(BucketName)

View File

@@ -5,10 +5,7 @@ import (
"github.com/portainer/portainer/api/dataservices"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "endpoint_groups"
)
const BucketName = "endpoint_groups"
// Service represents a service for managing environment(endpoint) data.
type Service struct {

View File

@@ -1,9 +1,6 @@
package dataservices
import (
"io"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
)
@@ -35,6 +32,7 @@ type (
User() UserService
Version() VersionService
Webhook() WebhookService
PendingActions() PendingActionsService
}
DataStore interface {
@@ -46,7 +44,7 @@ type (
MigrateData() error
Rollback(force bool) error
CheckCurrentEdition() error
BackupTo(w io.Writer) error
Backup(path string) (string, error)
Export(filename string) (err error)
DataStoreTx
@@ -72,6 +70,11 @@ type (
GetNextIdentifier() int
}
PendingActionsService interface {
BaseCRUD[portainer.PendingActions, portainer.PendingActionsID]
GetNextIdentifier() int
}
// EdgeStackService represents a service to manage Edge stacks
EdgeStackService interface {
EdgeStacks() ([]portainer.EdgeStack, error)
@@ -89,6 +92,7 @@ type (
EndpointService interface {
Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error)
EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool)
EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.Endpoint, error)
Heartbeat(endpointID portainer.EndpointID) (int64, bool)
UpdateHeartbeat(endpointID portainer.EndpointID)
Endpoints() ([]portainer.Endpoint, error)
@@ -126,15 +130,6 @@ type (
HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error)
}
// JWTService represents a service for managing JWT tokens
JWTService interface {
GenerateToken(data *portainer.TokenData) (string, error)
GenerateTokenForOAuth(data *portainer.TokenData, expiryTime *time.Time) (string, error)
GenerateTokenForKubeconfig(data *portainer.TokenData) (string, error)
ParseAndVerifyToken(token string) (*portainer.TokenData, error)
SetUserSessionDuration(userSessionDuration time.Duration)
}
// RegistryService represents a service for managing registry data
RegistryService interface {
BaseCRUD[portainer.Registry, portainer.RegistryID]
@@ -155,7 +150,7 @@ type (
APIKeyRepository interface {
BaseCRUD[portainer.APIKey, portainer.APIKeyID]
GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error)
GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error)
GetAPIKeyByDigest(digest string) (*portainer.APIKey, error)
}
// SettingsService represents a service for managing application settings

View File

@@ -0,0 +1,74 @@
package pendingactions
import (
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
const (
BucketName = "pending_actions"
)
type Service struct {
dataservices.BaseDataService[portainer.PendingActions, portainer.PendingActionsID]
}
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.PendingActions, portainer.PendingActionsID]
}
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.PendingActions, portainer.PendingActionsID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
func (s Service) Create(config *portainer.PendingActions) error {
return s.Connection.UpdateTx(func(tx portainer.Transaction) error {
return s.Tx(tx).Create(config)
})
}
func (s Service) Update(ID portainer.PendingActionsID, config *portainer.PendingActions) error {
return s.Connection.UpdateTx(func(tx portainer.Transaction) error {
return s.Tx(tx).Update(ID, config)
})
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.PendingActions, portainer.PendingActionsID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
}
}
func (s ServiceTx) Create(config *portainer.PendingActions) error {
return s.Tx.CreateObject(BucketName, func(id uint64) (int, interface{}) {
config.ID = portainer.PendingActionsID(id)
config.CreatedAt = time.Now().Unix()
return int(config.ID), config
})
}
func (s ServiceTx) Update(ID portainer.PendingActionsID, config *portainer.PendingActions) error {
return s.BaseDataServiceTx.Update(ID, config)
}
// GetNextIdentifier returns the next identifier for a custom template.
func (service *Service) GetNextIdentifier() int {
return service.Connection.GetNextIdentifier(BucketName)
}

View File

@@ -5,9 +5,7 @@ import (
"github.com/portainer/portainer/api/dataservices"
)
const (
BucketName = "snapshots"
)
const BucketName = "snapshots"
type Service struct {
dataservices.BaseDataService[portainer.Snapshot, portainer.EndpointID]

View File

@@ -106,7 +106,6 @@ func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) {
}
return nil, err
}
// RefreshableStacks returns stacks that are configured for a periodic update

View File

@@ -5,10 +5,8 @@ import (
"github.com/portainer/portainer/api/dataservices"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "tags"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "tags"
// Service represents a service for managing environment(endpoint) data.
type Service struct {

View File

@@ -22,7 +22,7 @@ func (service ServiceTx) Create(tag *portainer.Tag) error {
)
}
// UpdateTagFunc is a no-op inside a transaction
// UpdateTagFunc is a no-op inside a transaction.
func (service ServiceTx) UpdateTagFunc(ID portainer.TagID, updateFunc func(tag *portainer.Tag)) error {
return errors.New("cannot be called inside a transaction")
}

View File

@@ -73,6 +73,10 @@ func (service *Service) IsUpdating() (bool, error) {
// StoreIsUpdating store the database updating status.
func (service *Service) StoreIsUpdating(isUpdating bool) error {
if isUpdating {
return service.connection.UpdateObject(BucketName, []byte(updatingKey), isUpdating)
}
return service.connection.DeleteObject(BucketName, []byte(updatingKey))
}

View File

@@ -4,186 +4,89 @@ import (
"fmt"
"os"
"path"
"time"
"github.com/portainer/portainer/api/database/models"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
var backupDefaults = struct {
backupDir string
commonDir string
}{
"backups",
"common",
// Backup takes an optional output path and creates a backup of the database.
// The database connection is stopped before running the backup to avoid any
// corruption and if a path is not given a default is used.
// The path or an error are returned.
func (store *Store) Backup(path string) (string, error) {
if err := store.createBackupPath(); err != nil {
return "", err
}
backupFilename := store.backupFilename()
if path != "" {
backupFilename = path
}
log.Info().Str("from", store.connection.GetDatabaseFilePath()).Str("to", backupFilename).Msgf("Backing up database")
// Close the store before backing up
err := store.Close()
if err != nil {
return "", fmt.Errorf("failed to close store before backup: %w", err)
}
err = store.fileService.Copy(store.connection.GetDatabaseFilePath(), backupFilename, true)
if err != nil {
return "", fmt.Errorf("failed to create backup file: %w", err)
}
// reopen the store
_, err = store.Open()
if err != nil {
return "", fmt.Errorf("failed to reopen store after backup: %w", err)
}
return backupFilename, nil
}
//
// Backup Helpers
//
func (store *Store) Restore() error {
backupFilename := store.backupFilename()
return store.RestoreFromFile(backupFilename)
}
// createBackupFolders create initial folders for backups
func (store *Store) createBackupFolders() {
// create common dir
commonDir := store.commonBackupDir()
if exists, _ := store.fileService.FileExists(commonDir); !exists {
if err := os.MkdirAll(commonDir, 0700); err != nil {
log.Error().Err(err).Msg("error while creating common backup folder")
func (store *Store) RestoreFromFile(backupFilename string) error {
store.Close()
if err := store.fileService.Copy(backupFilename, store.connection.GetDatabaseFilePath(), true); err != nil {
return fmt.Errorf("unable to restore backup file %q. err: %w", backupFilename, err)
}
log.Info().Str("from", backupFilename).Str("to", store.connection.GetDatabaseFilePath()).Msgf("database restored")
_, err := store.Open()
if err != nil {
return fmt.Errorf("unable to determine version of restored portainer backup file: %w", err)
}
// determine the db version
version, err := store.VersionService.Version()
if err != nil {
return fmt.Errorf("unable to determine restored database version. err: %w", err)
}
editionLabel := portainer.SoftwareEdition(version.Edition).GetEditionLabel()
log.Info().Msgf("Restored database version: Portainer %s %s", editionLabel, version.SchemaVersion)
return nil
}
func (store *Store) createBackupPath() error {
backupDir := path.Join(store.connection.GetStorePath(), "backups")
if exists, _ := store.fileService.FileExists(backupDir); !exists {
if err := os.MkdirAll(backupDir, 0o700); err != nil {
return fmt.Errorf("unable to create backup folder: %w", err)
}
}
return nil
}
func (store *Store) backupFilename() string {
return path.Join(store.connection.GetStorePath(), "backups", store.connection.GetDatabaseFileName()+".bak")
}
func (store *Store) databasePath() string {
return store.connection.GetDatabaseFilePath()
}
func (store *Store) commonBackupDir() string {
return path.Join(store.connection.GetStorePath(), backupDefaults.backupDir, backupDefaults.commonDir)
}
func (store *Store) copyDBFile(from string, to string) error {
log.Info().Str("from", from).Str("to", to).Msg("copying DB file")
err := store.fileService.Copy(from, to, true)
if err != nil {
log.Error().Err(err).Msg("failed")
}
return err
}
// BackupOptions provide a helper to inject backup options
type BackupOptions struct {
Version string
BackupDir string
BackupFileName string
BackupPath string
}
// getBackupRestoreOptions returns options to store db at common backup dir location; used by:
// - db backup prior to version upgrade
// - db rollback
func getBackupRestoreOptions(backupDir string) *BackupOptions {
return &BackupOptions{
BackupDir: backupDir, //connection.commonBackupDir(),
BackupFileName: beforePortainerVersionUpgradeBackup,
}
}
// Backup current database with default options
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 == "" {
v, err := store.VersionService.Version()
if err != nil {
options.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(), options.Version, time.Now().Format("20060102150405"))
}
if options.BackupPath == "" {
options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
}
return options
}
// BackupWithOptions backup current database with options
func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
log.Info().Msg("creating DB backup")
store.createBackupFolders()
options = store.setupOptions(options)
dbPath := store.databasePath()
if err := store.Close(); err != nil {
return options.BackupPath, fmt.Errorf(
"error closing datastore before creating backup: %w",
err,
)
}
if err := store.copyDBFile(dbPath, options.BackupPath); err != nil {
return options.BackupPath, err
}
if _, err := store.Open(); err != nil {
return options.BackupPath, fmt.Errorf(
"error opening datastore after creating backup: %w",
err,
)
}
return options.BackupPath, nil
}
// RestoreWithOptions previously saved backup for the current Edition with options
// Restore strategies:
// - default: restore latest from current edition
// - restore a specific
func (store *Store) restoreWithOptions(options *BackupOptions) error {
options = store.setupOptions(options)
// Check if backup file exist before restoring
_, err := os.Stat(options.BackupPath)
if os.IsNotExist(err) {
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to restore does not exist %s")
return err
}
err = store.Close()
if err != nil {
log.Error().Err(err).Msg("error while closing store before restore")
return err
}
log.Info().Msg("restoring DB backup")
err = store.copyDBFile(options.BackupPath, store.databasePath())
if err != nil {
return err
}
_, err = store.Open()
return err
}
// RemoveWithOptions removes backup database based on supplied options
func (store *Store) removeWithOptions(options *BackupOptions) error {
log.Info().Msg("removing DB backup")
options = store.setupOptions(options)
_, err := os.Stat(options.BackupPath)
if os.IsNotExist(err) {
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to remove does not exist")
return err
}
log.Info().Str("path", options.BackupPath).Msg("removing DB file")
err = os.Remove(options.BackupPath)
if err != nil {
log.Error().Err(err).Msg("failed")
return err
}
return nil
}

View File

@@ -2,106 +2,79 @@ package datastore
import (
"fmt"
"os"
"path"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/rs/zerolog/log"
)
func TestCreateBackupFolders(t *testing.T) {
_, store := MustNewTestStore(t, true, true)
connection := store.GetConnection()
backupPath := path.Join(connection.GetStorePath(), backupDefaults.backupDir)
if isFileExist(backupPath) {
t.Error("Expect backups folder to not exist")
}
store.createBackupFolders()
if !isFileExist(backupPath) {
t.Error("Expect backups folder to exist")
}
}
func TestStoreCreation(t *testing.T) {
_, store := MustNewTestStore(t, true, true)
if store == nil {
t.Error("Expect to create a store")
t.Fatal("Expect to create a store")
}
if store.CheckCurrentEdition() != nil {
v, err := store.VersionService.Version()
if err != nil {
log.Fatal().Err(err).Msg("")
}
if portainer.SoftwareEdition(v.Edition) != portainer.PortainerCE {
t.Error("Expect to get CE Edition")
}
if v.SchemaVersion != portainer.APIVersion {
t.Error("Expect to get APIVersion")
}
}
func TestBackup(t *testing.T) {
_, store := MustNewTestStore(t, true, true)
connection := store.GetConnection()
t.Run("Backup should create default db backup", func(t *testing.T) {
backupFileName := store.backupFilename()
t.Run(fmt.Sprintf("Backup should create %s", backupFileName), func(t *testing.T) {
v := models.Version{
Edition: int(portainer.PortainerCE),
SchemaVersion: portainer.APIVersion,
}
store.VersionService.UpdateVersion(&v)
store.backupWithOptions(nil)
store.Backup("")
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)
}
})
t.Run("BackupWithOption should create a name specific backup at common path", func(t *testing.T) {
store.backupWithOptions(&BackupOptions{
BackupFileName: beforePortainerVersionUpgradeBackup,
BackupDir: store.commonBackupDir(),
})
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", beforePortainerVersionUpgradeBackup)
if !isFileExist(backupFileName) {
t.Errorf("Expect backup file to be created %s", backupFileName)
}
})
}
func TestRemoveWithOptions(t *testing.T) {
_, store := MustNewTestStore(t, true, true)
func TestRestore(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
t.Run("successfully removes file if existent", func(t *testing.T) {
store.createBackupFolders()
options := &BackupOptions{
BackupDir: store.commonBackupDir(),
BackupFileName: "test.txt",
}
t.Run("Basic Restore", func(t *testing.T) {
// override and set initial db version and edition
updateEdition(store, portainer.PortainerCE)
updateVersion(store, "2.4")
filePath := path.Join(options.BackupDir, options.BackupFileName)
f, err := os.Create(filePath)
if err != nil {
t.Fatalf("file should be created; err=%s", err)
}
f.Close()
store.Backup("")
updateVersion(store, "2.16")
testVersion(store, "2.16", t)
store.Restore()
err = store.removeWithOptions(options)
if err != nil {
t.Errorf("RemoveWithOptions should successfully remove file; err=%v", err)
}
if isFileExist(f.Name()) {
t.Errorf("RemoveWithOptions should successfully remove file; file=%s", f.Name())
}
// check if the restore is successful and the version is correct
testVersion(store, "2.4", t)
})
t.Run("fails to removes file if non-existent", func(t *testing.T) {
options := &BackupOptions{
BackupDir: store.commonBackupDir(),
BackupFileName: "test.txt",
}
t.Run("Basic Restore After Multiple Backups", func(t *testing.T) {
// override and set initial db version and edition
updateEdition(store, portainer.PortainerCE)
updateVersion(store, "2.4")
store.Backup("")
updateVersion(store, "2.14")
updateVersion(store, "2.16")
testVersion(store, "2.16", t)
store.Restore()
err := store.removeWithOptions(options)
if err == nil {
t.Error("RemoveWithOptions should fail for non-existent file")
}
// check if the restore is successful and the version is correct
testVersion(store, "2.4", t)
})
}

View File

@@ -31,8 +31,14 @@ func (store *Store) Open() (newStore bool, err error) {
}
if encryptionReq {
backupFilename, err := store.Backup("")
if err != nil {
return false, fmt.Errorf("failed to backup database prior to encrypting: %w", err)
}
err = store.encryptDB()
if err != nil {
store.RestoreFromFile(backupFilename) // restore from backup if encryption fails
return false, err
}
}

View File

@@ -0,0 +1,58 @@
package datastore
import (
"path/filepath"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
// isFileExist is helper function to check for file existence
func isFileExist(path string) bool {
matches, err := filepath.Glob(path)
if err != nil {
return false
}
return len(matches) > 0
}
func updateVersion(store *Store, v string) {
version, err := store.VersionService.Version()
if err != nil {
log.Fatal().Err(err).Msg("")
}
version.SchemaVersion = v
err = store.VersionService.UpdateVersion(version)
if err != nil {
log.Fatal().Err(err).Msg("")
}
}
func updateEdition(store *Store, edition portainer.SoftwareEdition) {
version, err := store.VersionService.Version()
if err != nil {
log.Fatal().Err(err).Msg("")
}
version.Edition = int(edition)
err = store.VersionService.UpdateVersion(version)
if err != nil {
log.Fatal().Err(err).Msg("")
}
}
// testVersion is a helper which tests current store version against wanted version
func testVersion(store *Store, versionWant string, t *testing.T) {
v, err := store.VersionService.Version()
if err != nil {
log.Fatal().Err(err).Msg("")
}
if v.SchemaVersion != versionWant {
t.Errorf("Expect store version to be %s but was %s", versionWant, v.SchemaVersion)
}
}

View File

@@ -53,7 +53,7 @@ func (store *Store) checkOrCreateDefaultSettings() error {
},
SnapshotInterval: portainer.DefaultSnapshotInterval,
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
TemplatesURL: portainer.DefaultTemplatesURL,
TemplatesURL: "",
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
KubeconfigExpiry: portainer.DefaultKubeconfigExpiry,

View File

@@ -2,6 +2,7 @@ package datastore
import (
"fmt"
"os"
"runtime/debug"
portainer "github.com/portainer/portainer/api"
@@ -15,8 +16,6 @@ import (
"github.com/rs/zerolog/log"
)
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
func (store *Store) MigrateData() error {
updating, err := store.VersionService.IsUpdating()
if err != nil {
@@ -41,7 +40,7 @@ func (store *Store) MigrateData() error {
}
// before we alter anything in the DB, create a backup
backupPath, err := store.Backup(version)
_, err = store.Backup("")
if err != nil {
return errors.Wrap(err, "while backing up database")
}
@@ -50,10 +49,10 @@ func (store *Store) MigrateData() error {
if err != nil {
err = errors.Wrap(err, "failed to migrate database")
log.Warn().Msg("migration failed, restoring database to previous version")
err = store.restoreWithOptions(&BackupOptions{BackupPath: backupPath})
if err != nil {
return errors.Wrap(err, "failed to restore database")
log.Warn().Err(err).Msg("migration failed, restoring database to previous version")
restoreErr := store.Restore()
if restoreErr != nil {
return errors.Wrap(restoreErr, "failed to restore database")
}
log.Info().Msg("database restored to previous version")
@@ -117,6 +116,11 @@ func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version *models
return err
}
// Special test code to simulate a failure (used by migrate_data_test.go). Do not remove...
if os.Getenv("PORTAINER_TEST_MIGRATE_FAIL") == "FAIL" {
panic("test migration failure")
}
err = store.VersionService.StoreIsUpdating(false)
if err != nil {
return errors.Wrap(err, "failed to update the store")
@@ -127,7 +131,6 @@ func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version *models
// Rollback to a pre-upgrade backup copy/snapshot of portainer.db
func (store *Store) connectionRollback(force bool) error {
if !force {
confirmed, err := cli.Confirm("Are you sure you want to rollback your database to the previous backup?")
if err != nil || !confirmed {
@@ -135,9 +138,7 @@ func (store *Store) connectionRollback(force bool) error {
}
}
options := getBackupRestoreOptions(store.commonBackupDir())
err := store.restoreWithOptions(options)
err := store.Restore()
if err != nil {
return err
}

View File

@@ -7,29 +7,20 @@ import (
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/Masterminds/semver"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/boltdb"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/datastore/migrator"
"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 string, t *testing.T) {
v, err := store.VersionService.Version()
if err != nil {
t.Errorf("Expect store version to be %s but was %s with error: %s", versionWant, v.SchemaVersion, err)
}
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 {
tests := []struct {
testName string
srcPath string
wantPath string
@@ -42,7 +33,7 @@ func TestMigrateData(t *testing.T) {
overrideInstanceId: true,
},
}
for _, test := range snapshotTests {
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
err := migrateDBTestHelper(t, test.srcPath, test.wantPath, test.overrideInstanceId)
if err != nil {
@@ -55,147 +46,133 @@ func TestMigrateData(t *testing.T) {
})
}
// t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
// newStore, store, teardown := MustNewTestStore(t, true, false)
// defer teardown()
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
newStore, store := MustNewTestStore(t, true, false)
if !newStore {
t.Error("Expect a new DB")
}
// if !newStore {
// t.Error("Expect a new DB")
// }
testVersion(store, portainer.APIVersion, t)
store.Close()
// testVersion(store, portainer.APIVersion, 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")
// }
// })
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
store.VersionService.UpdateVersion(&models.Version{SchemaVersion: "1.0", Edition: int(portainer.PortainerCE)})
store.MigrateData()
// 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()
backupfilename := store.backupFilename()
if exists, _ := store.fileService.FileExists(backupfilename); !exists {
t.Errorf("Expect backup file to be created %s", backupfilename)
}
})
// // Setup data
// v := models.Version{SchemaVersion: tc.version}
// store.VersionService.UpdateVersion(&v)
t.Run("MigrateData should recover and restore backup during migration critical failure", func(t *testing.T) {
os.Setenv("PORTAINER_TEST_MIGRATE_FAIL", "FAIL")
// // 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})
version := "2.15"
_, store := MustNewTestStore(t, true, false)
store.VersionService.UpdateVersion(&models.Version{SchemaVersion: version, Edition: int(portainer.PortainerCE)})
store.MigrateData()
// t.Run(fmt.Sprintf("MigrateData for version %s", tc.version), func(t *testing.T) {
// store.MigrateData()
// testVersion(store, tc.expectedVersion, t)
// })
store.Open()
testVersion(store, version, 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("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
store.VersionService.StoreIsUpdating(true)
store.MigrateData()
// t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
// If the backup file is not blank, then it means a backup was created. We don't want that because we
// only create a backup when the version changes.
backupfilename := store.backupFilename()
if exists, _ := store.fileService.FileExists(backupfilename); exists {
t.Errorf("Backup file should not exist for dirty database")
}
})
// v := models.Version{SchemaVersion: "1.24.1"}
// store.VersionService.UpdateVersion(&v)
t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
// store.MigrateData()
// Set migrator the count to match our migrations array (simulate no changes).
// Should not create a backup
v, err := store.VersionService.Version()
if err != nil {
t.Errorf("Unable to read version from db: %s", err)
t.FailNow()
}
// testVersion(store, v.SchemaVersion, t)
// })
migratorParams := store.newMigratorParameters(v)
m := migrator.NewMigrator(migratorParams)
latestMigrations := m.LatestMigrations()
// t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
if latestMigrations.Version.Equal(semver.MustParse(portainer.APIVersion)) {
v.MigratorCount = len(latestMigrations.MigrationFuncs)
store.VersionService.UpdateVersion(v)
}
// v := models.Version{SchemaVersion: "0.0.0"}
// store.VersionService.UpdateVersion(&v)
store.MigrateData()
// store.MigrateData()
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
// If the backup file is not blank, then it means a backup was created. We don't want that because we
// only create a backup when the version changes.
backupfilename := store.backupFilename()
if exists, _ := store.fileService.FileExists(backupfilename); exists {
t.Errorf("Backup file should not exist for dirty database")
}
})
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
t.Run("MigrateData should create backup on startup if portainer version matches db and migrationFuncs counts differ", func(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
// if !isFileExist(options.BackupPath) {
// t.Errorf("Backup file should exist; file=%s", options.BackupPath)
// }
// })
// Set migrator count very large to simulate changes
// Should not create a backup
v, err := store.VersionService.Version()
if err != nil {
t.Errorf("Unable to read version from db: %s", err)
t.FailNow()
}
// 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()
v.MigratorCount = 1000
store.VersionService.UpdateVersion(v)
store.MigrateData()
// store.VersionService.StoreIsUpdating(true)
// store.MigrateData()
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// if isFileExist(options.BackupPath) {
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
// }
// })
// t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
// store.MigrateData()
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// if isFileExist(options.BackupPath) {
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
// }
// })
}
func Test_getBackupRestoreOptions(t *testing.T) {
_, store := MustNewTestStore(t, false, true)
options := getBackupRestoreOptions(store.commonBackupDir())
wantDir := store.commonBackupDir()
if !strings.HasSuffix(options.BackupDir, wantDir) {
log.Fatal().Str("got", options.BackupDir).Str("want", wantDir).Msg("incorrect backup dir")
}
wantFilename := "portainer.db.bak"
if options.BackupFileName != wantFilename {
log.Fatal().Str("got", options.BackupFileName).Str("want", wantFilename).Msg("incorrect backup file")
}
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
// If the backup file is not blank, then it means a backup was created. We don't want that because we
// only create a backup when the version changes.
backupfilename := store.backupFilename()
if exists, _ := store.fileService.FileExists(backupfilename); !exists {
t.Errorf("DB backup should exist and there should be no error")
}
})
}
func TestRollback(t *testing.T) {
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
version := models.Version{SchemaVersion: "2.4.0"}
_, store := MustNewTestStore(t, true, false)
version := "2.11"
err := store.VersionService.UpdateVersion(&version)
if err != nil {
t.Errorf("Failed updating version: %v", err)
v := models.Version{
SchemaVersion: version,
}
_, err = store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
_, store := MustNewTestStore(t, false, false)
store.VersionService.UpdateVersion(&v)
_, err := store.Backup("")
if err != nil {
log.Fatal().Err(err).Msg("")
}
// Change the current version
version2 := models.Version{SchemaVersion: "2.6.0"}
err = store.VersionService.UpdateVersion(&version2)
v.SchemaVersion = "2.14"
// Change the current edition
err = store.VersionService.UpdateVersion(&v)
if err != nil {
log.Fatal().Err(err).Msg("")
}
@@ -207,26 +184,45 @@ func TestRollback(t *testing.T) {
return
}
_, err = store.Open()
store.Open()
testVersion(store, version, t)
})
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
version := "2.15"
v := models.Version{
SchemaVersion: version,
Edition: int(portainer.PortainerCE),
}
_, store := MustNewTestStore(t, true, false)
store.VersionService.UpdateVersion(&v)
_, err := store.Backup("")
if err != nil {
t.Logf("Open failed: %s", err)
log.Fatal().Err(err).Msg("")
}
v.SchemaVersion = "2.14"
// Change the current edition
err = store.VersionService.UpdateVersion(&v)
if err != nil {
log.Fatal().Err(err).Msg("")
}
err = store.Rollback(true)
if err != nil {
t.Logf("Rollback failed: %s", err)
t.Fail()
return
}
testVersion(store, version.SchemaVersion, t)
store.Open()
testVersion(store, version, t)
})
}
// isFileExist is helper function to check for file existence
func isFileExist(path string) bool {
matches, err := filepath.Glob(path)
if err != nil {
return false
}
return len(matches) > 0
}
// 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.
@@ -309,7 +305,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
os.WriteFile(
gotPath,
gotJSON,
0600,
0o600,
)
t.Errorf(
"migrate data from %s to %s failed\nwrote migrated input to %s\nmismatch (-want +got):\n%s",

View File

@@ -1,7 +1,7 @@
package datastore
import (
portaineree "github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices"
)
@@ -72,7 +72,7 @@ func dbVersionToSemanticVersion(dbVersion int) string {
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)
edition := int(portainer.PortainerCE)
instanceId := ""
// If we already have a version key, we don't need to migrate

View File

@@ -10,8 +10,8 @@ import (
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDockerDesktopExtentionSetting() error {
log.Info().Msg("updating docker desktop extention flag in settings")
func (m *Migrator) migrateDockerDesktopExtensionSetting() error {
log.Info().Msg("updating docker desktop extension flag in settings")
isDDExtension := false
if _, ok := os.LookupEnv("DOCKER_EXTENSION"); ok {
@@ -115,10 +115,16 @@ func (m *Migrator) updateEdgeStackStatusForDB100() error {
}
if environmentStatus.Details.Ok {
statusArray = append(statusArray, portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusRunning,
Time: time.Now().Unix(),
})
statusArray = append(statusArray,
portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusDeploymentReceived,
Time: time.Now().Unix(),
},
portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusRunning,
Time: time.Now().Unix(),
},
)
}
if environmentStatus.Details.ImagesPulled {

View File

@@ -0,0 +1,51 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
// updateAppTemplatesVersionForDB110 changes the templates URL to be empty if it was never changed
// from the default value (version 2.0 URL)
func (migrator *Migrator) updateAppTemplatesVersionForDB110() error {
log.Info().Msg("updating app templates url to v3.0")
version2URL := "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json"
settings, err := migrator.settingsService.Settings()
if err != nil {
return err
}
if settings.TemplatesURL == version2URL || settings.TemplatesURL == portainer.DefaultTemplatesURL {
settings.TemplatesURL = ""
}
return migrator.settingsService.UpdateSettings(settings)
}
// In PortainerCE the resource overcommit option should always be true across all endpoints
func (migrator *Migrator) updateResourceOverCommitToDB110() error {
log.Info().Msg("updating resource overcommit setting to true")
endpoints, err := migrator.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
if endpoint.Type == portainer.KubernetesLocalEnvironment ||
endpoint.Type == portainer.AgentOnKubernetesEnvironment ||
endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
endpoint.Kubernetes.Configuration.EnableResourceOverCommit = true
err = migrator.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -14,8 +14,10 @@ func (m *Migrator) updateSettingsToDB25() error {
return err
}
// to keep the same migration functionality as before 2.20.0, we need to set the templates URL to v2
version2URL := "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json"
if legacySettings.TemplatesURL == "" {
legacySettings.TemplatesURL = portainer.DefaultTemplatesURL
legacySettings.TemplatesURL = version2URL
}
legacySettings.UserSessionTimeout = portainer.DefaultUserSessionTimeout

View File

@@ -245,7 +245,7 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
return nil
}
func findResourcesToUpdateForDB32(dockerID string, volumesData volume.VolumeListOKBody, toUpdate map[portainer.ResourceControlID]string, volumeResourceControls map[string]*portainer.ResourceControl) {
func findResourcesToUpdateForDB32(dockerID string, volumesData volume.ListResponse, toUpdate map[portainer.ResourceControlID]string, volumeResourceControls map[string]*portainer.ResourceControl) {
volumes := volumesData.Volumes
for _, volume := range volumes {
volumeName := volume.Name

View File

@@ -148,6 +148,17 @@ func (m *Migrator) LatestMigrations() Migrations {
return m.migrations[len(m.migrations)-1]
}
func (m *Migrator) GetMigratorCountOfCurrentAPIVersion() int {
migratorCount := 0
latestMigrations := m.LatestMigrations()
if latestMigrations.Version.Equal(semver.MustParse(portainer.APIVersion)) {
migratorCount = len(latestMigrations.MigrationFuncs)
}
return migratorCount
}
// !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.
@@ -214,9 +225,13 @@ func (m *Migrator) initMigrations() {
m.addMigrations("2.18", m.migrateDBVersionToDB90)
m.addMigrations("2.19",
m.convertSeedToPrivateKeyForDB100,
m.migrateDockerDesktopExtentionSetting,
m.migrateDockerDesktopExtensionSetting,
m.updateEdgeStackStatusForDB100,
)
m.addMigrations("2.20",
m.updateAppTemplatesVersionForDB110,
m.updateResourceOverCommitToDB110,
)
// Add new migrations below...
// One function per migration, each versions migration funcs in the same file.

View File

@@ -1,7 +1,6 @@
package datastore
import (
"encoding/json"
"fmt"
"os"
@@ -20,6 +19,7 @@ import (
"github.com/portainer/portainer/api/dataservices/extension"
"github.com/portainer/portainer/api/dataservices/fdoprofile"
"github.com/portainer/portainer/api/dataservices/helmuserrepository"
"github.com/portainer/portainer/api/dataservices/pendingactions"
"github.com/portainer/portainer/api/dataservices/registry"
"github.com/portainer/portainer/api/dataservices/resourcecontrol"
"github.com/portainer/portainer/api/dataservices/role"
@@ -37,6 +37,7 @@ import (
"github.com/portainer/portainer/api/dataservices/webhook"
"github.com/rs/zerolog/log"
"github.com/segmentio/encoding/json"
)
// Store defines the implementation of portainer.DataStore using
@@ -72,6 +73,7 @@ type Store struct {
UserService *user.Service
VersionService *version.Service
WebhookService *webhook.Service
PendingActionsService *pendingactions.Service
}
func (store *Store) initServices() error {
@@ -238,9 +240,20 @@ func (store *Store) initServices() error {
}
store.ScheduleService = scheduleService
pendingActionsService, err := pendingactions.NewService(store.connection)
if err != nil {
return err
}
store.PendingActionsService = pendingActionsService
return nil
}
// PendingActions gives access to the PendingActions data management layer
func (store *Store) PendingActions() dataservices.PendingActionsService {
return store.PendingActionsService
}
// CustomTemplate gives access to the CustomTemplate data management layer
func (store *Store) CustomTemplate() dataservices.CustomTemplateService {
return store.CustomTemplateService

View File

@@ -16,6 +16,8 @@ func (tx *StoreTx) IsErrObjectNotFound(err error) bool {
func (tx *StoreTx) CustomTemplate() dataservices.CustomTemplateService { return nil }
func (tx *StoreTx) PendingActions() dataservices.PendingActionsService { return nil }
func (tx *StoreTx) EdgeGroup() dataservices.EdgeGroupService {
return tx.store.EdgeGroupService.Tx(tx.tx)
}

View File

@@ -46,12 +46,10 @@
},
"EdgeCheckinInterval": 0,
"EdgeKey": "",
"EnableGPUManagement": false,
"Gpus": [],
"GroupId": 1,
"Heartbeat": false,
"Id": 1,
"IsEdgeDevice": false,
"Kubernetes": {
"Configuration": {
"AllowNoneIngressClass": false,
@@ -101,8 +99,7 @@
"TeamAccessPolicies": {},
"Type": 1,
"URL": "unix:///var/run/docker.sock",
"UserAccessPolicies": {},
"UserTrusted": false
"UserAccessPolicies": {}
}
],
"registries": [
@@ -124,8 +121,7 @@
"Name": "canister.io",
"Password": "MjWbx8A6YK7cw7",
"Quay": {
"OrganisationName": "",
"UseOrganisation": false
"OrganisationName": ""
},
"RegistryAccesses": {
"1": {
@@ -584,11 +580,8 @@
"AllowHostNamespaceForRegularUsers": true,
"AllowPrivilegedModeForRegularUsers": true,
"AllowStackManagementForRegularUsers": true,
"AllowVolumeBrowserForRegularUsers": false,
"AuthenticationMethod": 1,
"BlackListedLabels": [],
"DisplayDonationHeader": false,
"DisplayExternalContributors": false,
"Edge": {
"AsyncMode": false,
"CommandInterval": 0,
@@ -598,15 +591,16 @@
"EdgeAgentCheckinInterval": 5,
"EdgePortainerUrl": "",
"EnableEdgeComputeFeatures": false,
"EnableHostManagementFeatures": false,
"EnableTelemetry": true,
"EnforceEdgeID": false,
"FeatureFlagSettings": null,
"GlobalDeploymentOptions": {
"hideStacksFunctionality": false
},
"HelmRepositoryURL": "https://charts.bitnami.com/bitnami",
"InternalAuthSettings": {
"RequiredPasswordLength": 12
},
"IsDockerDesktopExtension": false,
"KubeconfigExpiry": "0",
"KubectlShellImage": "portainer/kubectl-shell",
"LDAPSettings": {
@@ -651,7 +645,7 @@
},
"ShowKomposeBuildOption": false,
"SnapshotInterval": "5m",
"TemplatesURL": "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json",
"TemplatesURL": "",
"TrustOnFirstConnect": false,
"UserSessionTimeout": "8h",
"fdoConfiguration": {
@@ -675,6 +669,7 @@
"snapshots": [
{
"Docker": {
"ContainerCount": 0,
"DockerSnapshotRaw": {
"Containers": null,
"Images": null,
@@ -909,7 +904,7 @@
"color": ""
},
"TokenIssueAt": 0,
"UserTheme": "",
"UseCache": false,
"Username": "admin"
},
{
@@ -939,11 +934,11 @@
"color": ""
},
"TokenIssueAt": 0,
"UserTheme": "",
"UseCache": false,
"Username": "prabhat"
}
],
"version": {
"VERSION": "{\"SchemaVersion\":\"2.19.0\",\"MigratorCount\":3,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
"VERSION": "{\"SchemaVersion\":\"2.20.0\",\"MigratorCount\":2,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
}
}

View File

@@ -1,18 +1,24 @@
package client
import (
"bytes"
"errors"
"fmt"
"io"
"maps"
"net/http"
"strings"
"time"
"github.com/docker/docker/client"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/segmentio/encoding/json"
)
var errUnsupportedEnvironmentType = errors.New("Environment not supported")
var errUnsupportedEnvironmentType = errors.New("environment not supported")
const (
defaultDockerRequestTimeout = 60 * time.Second
@@ -42,9 +48,16 @@ func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint, nodeNam
case portainer.AzureEnvironment:
return nil, errUnsupportedEnvironmentType
case portainer.AgentOnDockerEnvironment:
return createAgentClient(endpoint, factory.signatureService, nodeName, timeout)
return createAgentClient(endpoint, endpoint.URL, factory.signatureService, nodeName, timeout)
case portainer.EdgeAgentOnDockerEnvironment:
return createEdgeClient(endpoint, factory.signatureService, factory.reverseTunnelService, nodeName, timeout)
tunnel, err := factory.reverseTunnelService.GetActiveTunnel(endpoint)
if err != nil {
return nil, err
}
endpointURL := fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
return createAgentClient(endpoint, endpointURL, factory.signatureService, nodeName, timeout)
}
if strings.HasPrefix(endpoint.URL, "unix://") || strings.HasPrefix(endpoint.URL, "npipe://") {
@@ -57,20 +70,20 @@ func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint, nodeNam
func createLocalClient(endpoint *portainer.Endpoint) (*client.Client, error) {
return client.NewClientWithOpts(
client.WithHost(endpoint.URL),
client.WithVersion(dockerClientVersion),
client.WithAPIVersionNegotiation(),
)
}
func CreateClientFromEnv() (*client.Client, error) {
return client.NewClientWithOpts(
client.FromEnv,
client.WithVersion(dockerClientVersion),
client.WithAPIVersionNegotiation(),
)
}
func CreateSimpleClient() (*client.Client, error) {
return client.NewClientWithOpts(
client.WithVersion(dockerClientVersion),
client.WithAPIVersionNegotiation(),
)
}
@@ -82,12 +95,12 @@ func createTCPClient(endpoint *portainer.Endpoint, timeout *time.Duration) (*cli
return client.NewClientWithOpts(
client.WithHost(endpoint.URL),
client.WithVersion(dockerClientVersion),
client.WithAPIVersionNegotiation(),
client.WithHTTPClient(httpCli),
)
}
func createEdgeClient(endpoint *portainer.Endpoint, signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, nodeName string, timeout *time.Duration) (*client.Client, error) {
func createAgentClient(endpoint *portainer.Endpoint, endpointURL string, signatureService portainer.DigitalSignatureService, nodeName string, timeout *time.Duration) (*client.Client, error) {
httpCli, err := httpClient(endpoint, timeout)
if err != nil {
return nil, err
@@ -107,51 +120,73 @@ func createEdgeClient(endpoint *portainer.Endpoint, signatureService portainer.D
headers[portainer.PortainerAgentTargetHeader] = nodeName
}
tunnel, err := reverseTunnelService.GetActiveTunnel(endpoint)
if err != nil {
return nil, err
}
endpointURL := fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
return client.NewClientWithOpts(
opts := []client.Opt{
client.WithHost(endpointURL),
client.WithVersion(dockerClientVersion),
client.WithAPIVersionNegotiation(),
client.WithHTTPClient(httpCli),
client.WithHTTPHeaders(headers),
)
}
if nnTransport, ok := httpCli.Transport.(*NodeNameTransport); ok && nnTransport.TLSClientConfig != nil {
opts = append(opts, client.WithScheme("https"))
}
return client.NewClientWithOpts(opts...)
}
func createAgentClient(endpoint *portainer.Endpoint, signatureService portainer.DigitalSignatureService, nodeName string, timeout *time.Duration) (*client.Client, error) {
httpCli, err := httpClient(endpoint, timeout)
type NodeNameTransport struct {
*http.Transport
nodeNames map[string]string
}
func (t *NodeNameTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := t.Transport.RoundTrip(req)
if err != nil ||
resp.StatusCode != http.StatusOK ||
resp.ContentLength == 0 ||
!strings.HasSuffix(req.URL.Path, "/images/json") {
return resp, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
resp.Body.Close()
return resp, err
}
signature, err := signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
if err != nil {
return nil, err
resp.Body.Close()
resp.Body = io.NopCloser(bytes.NewReader(body))
var rs []struct {
types.ImageSummary
Portainer struct {
Agent struct {
NodeName string
}
}
}
headers := map[string]string{
portainer.PortainerAgentPublicKeyHeader: signatureService.EncodedPublicKey(),
portainer.PortainerAgentSignatureHeader: signature,
if err = json.Unmarshal(body, &rs); err != nil {
return resp, nil
}
if nodeName != "" {
headers[portainer.PortainerAgentTargetHeader] = nodeName
t.nodeNames = make(map[string]string)
for _, r := range rs {
t.nodeNames[r.ID] = r.Portainer.Agent.NodeName
}
return client.NewClientWithOpts(
client.WithHost(endpoint.URL),
client.WithVersion(dockerClientVersion),
client.WithHTTPClient(httpCli),
client.WithHTTPHeaders(headers),
)
return resp, err
}
func (t *NodeNameTransport) NodeNames() map[string]string {
return maps.Clone(t.nodeNames)
}
func httpClient(endpoint *portainer.Endpoint, timeout *time.Duration) (*http.Client, error) {
transport := &http.Transport{}
transport := &NodeNameTransport{
Transport: &http.Transport{},
}
if endpoint.TLSConfig.TLS {
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)

View File

@@ -2,6 +2,7 @@ package images
import (
"context"
"slices"
"strings"
"time"
@@ -9,7 +10,6 @@ import (
"github.com/docker/docker/api/types/filters"
portainer "github.com/portainer/portainer/api"
consts "github.com/portainer/portainer/api/docker/consts"
"github.com/portainer/portainer/api/internal/slices"
"github.com/opencontainers/go-digest"
"github.com/patrickmn/go-cache"

View File

@@ -7,7 +7,7 @@ import (
"github.com/docker/docker/api/types"
_container "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
portainer "github.com/portainer/portainer/api"
dockerclient "github.com/portainer/portainer/api/docker/client"
@@ -174,7 +174,12 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
if !snapshot.Swarm {
return err
} else {
log.Info().Str("container", container.ID).Err(err).Msg("unable to inspect container in other Swarm nodes")
if !strings.Contains(err.Error(), "No such container") {
return err
}
// It is common to have containers running on different Swarm nodes,
// so we just log the error in the debug level
log.Debug().Str("container", container.ID).Err(err).Msg("unable to inspect container in other Swarm nodes")
}
} else {
var gpuOptions *_container.DeviceRequest = nil
@@ -196,9 +201,12 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
}
}
if strings.Contains(container.Status, "(healthy)") {
if container.State == "healthy" {
runningContainers++
healthyContainers++
} else if strings.Contains(container.Status, "(unhealthy)") {
}
if container.State == "unhealthy" {
unhealthyContainers++
}
@@ -217,6 +225,7 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
snapshot.GpuUseAll = gpuUseAll
snapshot.GpuUseList = gpuUseList
snapshot.ContainerCount = len(containers)
snapshot.RunningContainerCount = runningContainers
snapshot.StoppedContainerCount = stoppedContainers
snapshot.HealthyContainerCount = healthyContainers
@@ -240,7 +249,7 @@ func snapshotImages(snapshot *portainer.DockerSnapshot, cli *client.Client) erro
}
func snapshotVolumes(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
volumes, err := cli.VolumeList(context.Background(), filters.Args{})
volumes, err := cli.VolumeList(context.Background(), volume.ListOptions{})
if err != nil {
return err
}

View File

@@ -51,6 +51,10 @@ type (
// Used only for EE
// EnvVars is a list of environment variables to inject into the stack
EnvVars []portainer.Pair
// Used only for EE async edge agent
// ReadyRePullImage is a flag to indicate whether the auto update is trigger to re-pull image
ReadyRePullImage bool
}
// RegistryCredentials holds the credentials for a Docker registry.

View File

@@ -3,48 +3,3 @@ package exec
import "regexp"
var stackNameNormalizeRegex = regexp.MustCompile("[^-_a-z0-9]+")
type StringSet map[string]bool
func NewStringSet() StringSet {
return make(StringSet)
}
func (s StringSet) Add(x string) {
s[x] = true
}
func (s StringSet) Remove(x string) {
if s.Contains(x) {
delete(s, x)
}
}
func (s StringSet) Contains(x string) bool {
_, ok := s[x]
return ok
}
func (s StringSet) Len() int {
return len(s)
}
func (s StringSet) List() []string {
list := make([]string, s.Len())
i := 0
for k := range s {
list[i] = k
i++
}
return list
}
func (s StringSet) Union(x StringSet) {
if x.Len() != 0 {
for k := range x {
s.Add(k)
}
}
}

View File

@@ -10,8 +10,8 @@ import (
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/pkg/libstack/compose"
"github.com/portainer/portainer/pkg/testhelpers"
"github.com/rs/zerolog/log"
)

View File

@@ -2,7 +2,6 @@ package exec
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
@@ -15,6 +14,9 @@ import (
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/registryutils"
"github.com/portainer/portainer/api/stacks/stackutils"
"github.com/rs/zerolog/log"
"github.com/segmentio/encoding/json"
)
// SwarmStackManager represents a service for managing stacks.
@@ -64,16 +66,35 @@ func (manager *SwarmStackManager) Login(registries []portainer.Registry, endpoin
if registry.Authentication {
err = registryutils.EnsureRegTokenValid(manager.dataStore, &registry)
if err != nil {
return err
log.
Warn().
Err(err).
Str("RegistryName", registry.Name).
Msg("Failed to validate registry token. Skip logging with this registry.")
continue
}
username, password, err := registryutils.GetRegEffectiveCredential(&registry)
if err != nil {
return err
log.
Warn().
Err(err).
Str("RegistryName", registry.Name).
Msg("Failed to get effective credential. Skip logging with this registry.")
continue
}
registryArgs := append(args, "login", "--username", username, "--password", password, registry.URL)
runCommandAndCaptureStdErr(command, registryArgs, nil, "")
err = runCommandAndCaptureStdErr(command, registryArgs, nil, "")
if err != nil {
log.
Warn().
Err(err).
Str("RegistryName", registry.Name).
Msg("Failed to login.")
}
}
}

View File

@@ -2,7 +2,6 @@ package filesystem
import (
"bytes"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
@@ -15,6 +14,7 @@ import (
"github.com/gofrs/uuid"
"github.com/rs/zerolog/log"
"github.com/segmentio/encoding/json"
)
const (
@@ -173,7 +173,7 @@ func (service *Service) GetStackProjectPathByVersion(stackIdentifier string, ver
}
if commitHash != "" {
versionStr = fmt.Sprintf("%s", commitHash)
versionStr = commitHash
}
return JoinPaths(service.wrapFileStore(ComposeStorePath), stackIdentifier, versionStr)
}
@@ -302,6 +302,38 @@ func (service *Service) UpdateStoreStackFileFromBytes(stackIdentifier, fileName
return service.wrapFileStore(stackStorePath), nil
}
// UpdateStoreStackFileFromBytesByVersion makes stack file backup and updates a new file from bytes.
// It returns the path to the folder where the file is stored.
func (service *Service) UpdateStoreStackFileFromBytesByVersion(stackIdentifier, fileName string, version int, commitHash string, data []byte) (string, error) {
stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier)
versionStr := ""
if version != 0 {
versionStr = fmt.Sprintf("v%d", version)
}
if commitHash != "" {
versionStr = commitHash
}
if versionStr != "" {
stackStorePath = JoinPaths(stackStorePath, versionStr)
}
composeFilePath := JoinPaths(stackStorePath, fileName)
err := service.createBackupFileInStore(composeFilePath)
if err != nil {
return "", err
}
r := bytes.NewReader(data)
err = service.createFileInStore(composeFilePath, r)
if err != nil {
return "", err
}
return service.wrapFileStore(stackStorePath), nil
}
// RemoveStackFileBackup removes the stack file backup in the ComposeStorePath.
func (service *Service) RemoveStackFileBackup(stackIdentifier, fileName string) error {
stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier)

View File

@@ -49,8 +49,8 @@ func FilterDirForEntryFile(dirEntries []DirEntry, entryFile string) []DirEntry {
return filteredDirEntries
}
// FilterDirForCompatibility returns the content of the entry file if agent version is less than 2.19.0
func FilterDirForCompatibility(dirEntries []DirEntry, entryFilePath, agentVersion string) (string, error) {
if semver.Compare(fmt.Sprintf("v%s", agentVersion), "v2.19.0") == -1 {
for _, dirEntry := range dirEntries {
if dirEntry.IsFile {

View File

@@ -9,6 +9,39 @@ import (
"github.com/portainer/portainer/api"
)
type MultiFilterArgs []struct {
FilterKey string
FilterType portainer.PerDevConfigsFilterType
}
// MultiFilterDirForPerDevConfigs filers the given dirEntries with multiple filter args, returns the merged entries for the given device
func MultiFilterDirForPerDevConfigs(dirEntries []DirEntry, configPath string, multiFilterArgs MultiFilterArgs) []DirEntry {
var filteredDirEntries []DirEntry
for _, multiFilterArg := range multiFilterArgs {
tmp := FilterDirForPerDevConfigs(dirEntries, multiFilterArg.FilterKey, configPath, multiFilterArg.FilterType)
filteredDirEntries = append(filteredDirEntries, tmp...)
}
return deduplicate(filteredDirEntries)
}
func deduplicate(dirEntries []DirEntry) []DirEntry {
var deduplicatedDirEntries []DirEntry
marks := make(map[string]struct{})
for _, dirEntry := range dirEntries {
_, ok := marks[dirEntry.Name]
if !ok {
marks[dirEntry.Name] = struct{}{}
deduplicatedDirEntries = append(deduplicatedDirEntries, dirEntry)
}
}
return deduplicatedDirEntries
}
// FilterDirForPerDevConfigs filers the given dirEntries, returns entries for the given device
// For given configPath A/B/C, return entries:
// 1. all entries outside of dir A
@@ -47,10 +80,14 @@ func shouldIncludeEntry(dirEntry DirEntry, deviceName, configPath string, filter
return shouldIncludeFile(dirEntry, deviceName, configPath)
}
// Include:
// dir entry A/B/C/<deviceName>
// all entries A/B/C/<deviceName>/*
return shouldIncludeDir(dirEntry, deviceName, configPath)
if filterType == portainer.PerDevConfigsTypeDir {
// Include:
// dir entry A/B/C/<deviceName>
// all entries A/B/C/<deviceName>/*
return shouldIncludeDir(dirEntry, deviceName, configPath)
}
return false
}
func isInConfigRootDir(dirEntry DirEntry, configPath string) bool {

View File

@@ -0,0 +1,91 @@
package filesystem
import (
portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/assert"
"testing"
)
func TestMultiFilterDirForPerDevConfigs(t *testing.T) {
type args struct {
dirEntries []DirEntry
configPath string
multiFilterArgs MultiFilterArgs
}
baseDirEntries := []DirEntry{
{".env", "", true, 420},
{"docker-compose.yaml", "", true, 420},
{"configs", "", false, 420},
{"configs/file1.conf", "", true, 420},
{"configs/file2.conf", "", true, 420},
{"configs/folder1", "", false, 420},
{"configs/folder1/config1", "", true, 420},
{"configs/folder2", "", false, 420},
{"configs/folder2/config2", "", true, 420},
}
tests := []struct {
name string
args args
want []DirEntry
}{
{
name: "filter file1",
args: args{
baseDirEntries,
"configs",
MultiFilterArgs{{"file1", portainer.PerDevConfigsTypeFile}},
},
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3]},
},
{
name: "filter folder1",
args: args{
baseDirEntries,
"configs",
MultiFilterArgs{{"folder1", portainer.PerDevConfigsTypeDir}},
},
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6]},
},
{
name: "filter file1 and folder1",
args: args{
baseDirEntries,
"configs",
MultiFilterArgs{{"folder1", portainer.PerDevConfigsTypeDir}},
},
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6]},
},
{
name: "filter file1 and file2",
args: args{
baseDirEntries,
"configs",
MultiFilterArgs{
{"file1", portainer.PerDevConfigsTypeFile},
{"file2", portainer.PerDevConfigsTypeFile},
},
},
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3], baseDirEntries[4]},
},
{
name: "filter folder1 and folder2",
args: args{
baseDirEntries,
"configs",
MultiFilterArgs{
{"folder1", portainer.PerDevConfigsTypeDir},
{"folder2", portainer.PerDevConfigsTypeDir},
},
},
want: []DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6], baseDirEntries[7], baseDirEntries[8]},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, MultiFilterDirForPerDevConfigs(tt.args.dirEntries, tt.args.configPath, tt.args.multiFilterArgs), "MultiFilterDirForPerDevConfigs(%v, %v, %v)", tt.args.dirEntries, tt.args.configPath, tt.args.multiFilterArgs)
})
}
}

View File

@@ -2,7 +2,6 @@ package git
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
@@ -11,12 +10,13 @@ import (
"strings"
"time"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/portainer/portainer/api/archive"
"github.com/portainer/portainer/api/crypto"
gittypes "github.com/portainer/portainer/api/git/types"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/pkg/errors"
"github.com/segmentio/encoding/json"
)
const (

View File

@@ -2,12 +2,13 @@ package openamt
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
portainer "github.com/portainer/portainer/api"
"github.com/segmentio/encoding/json"
)
type authenticationResponse struct {

View File

@@ -2,7 +2,6 @@ package openamt
import (
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
@@ -11,6 +10,8 @@ import (
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/segmentio/encoding/json"
)
type CIRAConfig struct {

View File

@@ -1,11 +1,12 @@
package openamt
import (
"encoding/json"
"fmt"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/segmentio/encoding/json"
)
type Device struct {

View File

@@ -1,11 +1,12 @@
package openamt
import (
"encoding/json"
"fmt"
"net/http"
portainer "github.com/portainer/portainer/api"
"github.com/segmentio/encoding/json"
)
type (

View File

@@ -1,11 +1,12 @@
package openamt
import (
"encoding/json"
"fmt"
"net/http"
portainer "github.com/portainer/portainer/api"
"github.com/segmentio/encoding/json"
)
type (

View File

@@ -1,12 +1,13 @@
package openamt
import (
"encoding/json"
"fmt"
"net/http"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/segmentio/encoding/json"
)
type ActionResponse struct {

View File

@@ -1,11 +1,12 @@
package openamt
import (
"encoding/json"
"fmt"
"net/http"
portainer "github.com/portainer/portainer/api"
"github.com/segmentio/encoding/json"
)
func (service *Service) enableDeviceFeatures(configuration portainer.OpenAMTConfiguration, deviceGUID string, features portainer.OpenAMTDeviceEnabledFeatures) error {

View File

@@ -2,7 +2,6 @@ package openamt
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
@@ -12,6 +11,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
"github.com/segmentio/encoding/json"
"golang.org/x/sync/errgroup"
)

View File

@@ -2,7 +2,6 @@ package client
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
@@ -14,6 +13,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/segmentio/encoding/json"
)
var errInvalidResponseStatus = errors.New("invalid response status (expecting 200)")

66
api/http/csrf/csrf.go Normal file
View File

@@ -0,0 +1,66 @@
package csrf
import (
"crypto/rand"
"fmt"
"net/http"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
gorillacsrf "github.com/gorilla/csrf"
"github.com/portainer/portainer/api/http/security"
"github.com/urfave/negroni"
)
func WithProtect(handler http.Handler) (http.Handler, error) {
handler = withSendCSRFToken(handler)
token := make([]byte, 32)
_, err := rand.Read(token)
if err != nil {
return nil, fmt.Errorf("failed to generate CSRF token: %w", err)
}
handler = gorillacsrf.Protect(
[]byte(token),
gorillacsrf.Path("/"),
gorillacsrf.Secure(false),
)(handler)
return withSkipCSRF(handler), nil
}
func withSendCSRFToken(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sw := negroni.NewResponseWriter(w)
sw.Before(func(sw negroni.ResponseWriter) {
statusCode := sw.Status()
if statusCode >= 200 && statusCode < 300 {
csrfToken := gorillacsrf.Token(r)
sw.Header().Set("X-CSRF-Token", csrfToken)
}
})
handler.ServeHTTP(sw, r)
})
}
func withSkipCSRF(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
skip, err := security.ShouldSkipCSRFCheck(r)
if err != nil {
httperror.WriteError(w, http.StatusForbidden, err.Error(), err)
return
}
if skip {
r = gorillacsrf.UnsafeSkipCheck(r)
}
handler.ServeHTTP(w, r)
})
}

View File

@@ -3,7 +3,7 @@ package errors
import (
"errors"
httperror "github.com/portainer/libhttp/error"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
)
func TxResponse(err error, validResponse func() *httperror.HandlerError) *httperror.HandlerError {

View File

@@ -4,12 +4,13 @@ import (
"net/http"
"strings"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/request"
"github.com/portainer/portainer/pkg/libhttp/response"
"github.com/asaskevich/govalidator"
"github.com/pkg/errors"
@@ -25,7 +26,7 @@ type authenticatePayload struct {
type authenticateResponse struct {
// JWT token used to authenticate against the API
JWT string `json:"jwt" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE"`
JWT string `json:"jwt" example:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzAB"`
}
func (payload *authenticatePayload) Validate(r *http.Request) error {
@@ -74,7 +75,7 @@ func (handler *Handler) authenticate(rw http.ResponseWriter, r *http.Request) *h
if settings.AuthenticationMethod == portainer.AuthenticationInternal ||
settings.AuthenticationMethod == portainer.AuthenticationOAuth ||
(settings.AuthenticationMethod == portainer.AuthenticationLDAP && !settings.LDAPSettings.AutoCreateUsers) {
return &httperror.HandlerError{StatusCode: http.StatusUnprocessableEntity, Message: "Invalid credentials", Err: httperrors.ErrUnauthorized}
return httperror.NewError(http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized)
}
}
@@ -83,14 +84,14 @@ func (handler *Handler) authenticate(rw http.ResponseWriter, r *http.Request) *h
}
if settings.AuthenticationMethod == portainer.AuthenticationOAuth {
return &httperror.HandlerError{StatusCode: http.StatusUnprocessableEntity, Message: "Only initial admin is allowed to login without oauth", Err: httperrors.ErrUnauthorized}
return httperror.NewError(http.StatusUnprocessableEntity, "Only initial admin is allowed to login without oauth", httperrors.ErrUnauthorized)
}
if settings.AuthenticationMethod == portainer.AuthenticationLDAP {
return handler.authenticateLDAP(rw, user, payload.Username, payload.Password, &settings.LDAPSettings)
}
return &httperror.HandlerError{StatusCode: http.StatusUnprocessableEntity, Message: "Login method is not supported", Err: httperrors.ErrUnauthorized}
return httperror.NewError(http.StatusUnprocessableEntity, "Login method is not supported", httperrors.ErrUnauthorized)
}
func isUserInitialAdmin(user *portainer.User) bool {
@@ -100,7 +101,7 @@ func isUserInitialAdmin(user *portainer.User) bool {
func (handler *Handler) authenticateInternal(w http.ResponseWriter, user *portainer.User, password string) *httperror.HandlerError {
err := handler.CryptoService.CompareHashAndData(user.Password, password)
if err != nil {
return &httperror.HandlerError{StatusCode: http.StatusUnprocessableEntity, Message: "Invalid credentials", Err: httperrors.ErrUnauthorized}
return httperror.NewError(http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized)
}
forceChangePassword := !handler.passwordStrengthChecker.Check(password)
@@ -142,12 +143,15 @@ func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User,
}
func (handler *Handler) persistAndWriteToken(w http.ResponseWriter, tokenData *portainer.TokenData) *httperror.HandlerError {
token, err := handler.JWTService.GenerateToken(tokenData)
token, expirationTime, err := handler.JWTService.GenerateToken(tokenData)
if err != nil {
return httperror.InternalServerError("Unable to generate JWT token", err)
}
security.AddAuthCookie(w, token, expirationTime)
return response.JSON(w, &authenticateResponse{JWT: token})
}
func (handler *Handler) syncUserTeamsWithLDAPGroups(user *portainer.User, settings *portainer.LDAPSettings) error {
@@ -196,7 +200,7 @@ func (handler *Handler) syncUserTeamsWithLDAPGroups(user *portainer.User, settin
func teamExists(teamName string, ldapGroups []string) bool {
for _, group := range ldapGroups {
if strings.ToLower(group) == strings.ToLower(teamName) {
if strings.EqualFold(group, teamName) {
return true
}
}

View File

@@ -4,10 +4,10 @@ import (
"errors"
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
portainer "github.com/portainer/portainer/api"
httperrors "github.com/portainer/portainer/api/http/errors"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/request"
"github.com/asaskevich/govalidator"
"github.com/rs/zerolog/log"

View File

@@ -3,12 +3,12 @@ package auth
import (
"net/http"
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"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/gorilla/mux"
)
@@ -18,12 +18,13 @@ type Handler struct {
*mux.Router
DataStore dataservices.DataStore
CryptoService portainer.CryptoService
JWTService dataservices.JWTService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
OAuthService portainer.OAuthService
ProxyManager *proxy.Manager
KubernetesTokenCacheManager *kubernetes.TokenCacheManager
passwordStrengthChecker security.PasswordStrengthChecker
bouncer security.BouncerService
}
// NewHandler creates a handler to manage authentication operations.
@@ -31,6 +32,7 @@ func NewHandler(bouncer security.BouncerService, rateLimiter *security.RateLimit
h := &Handler{
Router: mux.NewRouter(),
passwordStrengthChecker: passwordStrengthChecker,
bouncer: bouncer,
}
h.Handle("/auth/oauth/validate",
@@ -38,7 +40,6 @@ func NewHandler(bouncer security.BouncerService, rateLimiter *security.RateLimit
h.Handle("/auth",
rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.authenticate)))).Methods(http.MethodPost)
h.Handle("/auth/logout",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.logout))).Methods(http.MethodPost)
bouncer.PublicAccess(httperror.LoggerHandler(h.logout))).Methods(http.MethodPost)
return h
}

View File

@@ -3,14 +3,15 @@ package auth
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/logoutcontext"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/response"
)
// @id Logout
// @summary Logout
// @description **Access policy**: authenticated
// @description **Access policy**: public
// @security ApiKeyAuth
// @security jwt
// @tags auth
@@ -18,12 +19,14 @@ import (
// @failure 500 "Server error"
// @router /auth/logout [post]
func (handler *Handler) logout(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
tokenData, err := security.RetrieveTokenData(r)
if err != nil {
return httperror.InternalServerError("Unable to retrieve user details from authentication token", err)
tokenData, _ := handler.bouncer.CookieAuthLookup(r)
if tokenData != nil {
handler.KubernetesTokenCacheManager.RemoveUserFromCache(tokenData.ID)
logoutcontext.Cancel(tokenData.Token)
}
handler.KubernetesTokenCacheManager.RemoveUserFromCache(tokenData.ID)
security.RemoveAuthCookie(w)
return response.Empty(w)
}

View File

@@ -6,9 +6,9 @@ import (
"os"
"path/filepath"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
operations "github.com/portainer/portainer/api/backup"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/request"
)
type (

View File

@@ -4,13 +4,13 @@ import (
"context"
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/api/adminmonitor"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/middlewares"
"github.com/portainer/portainer/api/http/offlinegate"
"github.com/portainer/portainer/api/http/security"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/gorilla/mux"
)

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