Compare commits

...

1308 Commits

Author SHA1 Message Date
Malcolm Lockyer
cc7f7008cb chore: bump version to 2.27.9 (#852) 2025-07-02 15:21:45 +12:00
andres-portainer
1e1998e269 feat(csrf): add trusted origins cli flags [BE-11972] (#839)
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2025-07-01 21:38:02 -03:00
Steven Kang
973c99dcf4 bump version to 2.27.8 (#824) 2025-06-25 09:43:58 +12:00
Cara Ryan
7fd5b96130 fix(kubernetes): Namespace access permission changes role bindings not created [R8S-366] (#807)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: Malcolm Lockyer <segfault88@users.noreply.github.com>
2025-06-25 09:24:18 +12:00
Steven Kang
ee6d33365e bump version to 2.27.7 (#804) 2025-06-17 09:43:15 +12:00
Steven Kang
e115055a1b security: cve-2025-22874 & cve-2025-22871 bump go to 1.23.10 (#799) 2025-06-12 17:30:49 +12:00
Devon Steenberg
384cb53c64 fix(proxy): whitelist headers for proxy to forward [BE-11819] (#760) 2025-05-30 11:49:41 +12:00
Oscar Zhou
4240cbf029 fix(csrf): skip trustedorigin for http request and check x-forwarded-proto for reverse proxy [BE-11832] (#713) 2025-05-09 13:45:33 +12:00
Steven Kang
eb28dd4f4e chore: bump version to 2.27.6 (#720) 2025-05-09 09:57:27 +12:00
Steven Kang
78127f8f3d chore: bump version to 2.27.5 (#704) 2025-05-02 10:08:56 +12:00
Oscar Zhou
c474322889 fix(dependencies): downgrade gorilla/csrf to v1.7.2 [BE-11832] (#689) 2025-04-24 12:14:18 +12:00
Oscar Zhou
83527da1a8 fix: cve-2025-22871 [BE-11825] (#677) 2025-04-22 21:29:18 +12:00
Oscar Zhou
7c8bef84b1 feat(docker): backport --pull-limit-check-disabled cli flag [BE-11820] (#658) 2025-04-16 19:28:43 +12:00
Steven Kang
5b3dba130b chore: bump version to 2.27.4 (#645) 2025-04-15 10:24:20 +12:00
Steven Kang
4039c3a693 security: cve-2025-30204 and other low ones - release 2.27.4 [BE-11781] (#642) 2025-04-15 09:59:01 +12:00
James Player
b1dceb15e4 Bump version to v2.27.3 (#571) 2025-03-25 12:32:10 +13:00
James Player
2feaacddb9 fix(kubernetes): 2.27 Cluster reservation CPU not showing R8S-268 (#570) 2025-03-25 11:01:31 +13:00
Oscar Zhou
65e0344975 fix(libstack): data loss for stack with relative path [FR-437] (#559) 2025-03-21 09:19:31 +13:00
Steven Kang
915beecce3 chore: bump 2.27.2 (#535) 2025-03-19 13:01:47 +13:00
andres-portainer
fbabeb098f fix(users): optimize the /users/me API endpoint BE-11688 (#527)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
Co-authored-by: JamesPlayer <james.player@portainer.io>
2025-03-18 17:55:42 -03:00
James Player
d5981a4be9 fix(app): datatable global checkbox doesn't reflect the selected state (#520) 2025-03-17 15:35:51 +13:00
Steven Kang
b0de6d41b7 fix: cve-2025-22869 release 2.27.2 (#512) 2025-03-17 12:24:41 +13:00
James Player
3898b9e09e fix: display unscheduled applications (#509)
Co-authored-by: Steven Kang <skan070@gmail.com>
2025-03-14 14:13:36 +13:00
Ali
c0a4a9ab5c fix(namespaces): only show namespaces with access [r8s-251] (#502) 2025-03-14 07:57:01 +13:00
Steven Kang
b9a68e9f31 chore: bump 2.27.1 - rel 227 (#469) 2025-02-27 11:00:58 +13:00
Oscar Zhou
52afa6cf67 fix(libstack): miss to read default .env file [BE-11638] (#460) 2025-02-26 13:00:36 +13:00
Steven Kang
1abb77aea5 fix: cve-2024-50338 - release 2.27 (#462) 2025-02-25 12:55:52 +13:00
Steven Kang
ab824da5d7 chore: bump version to 2.27.0 - release 2.27 (#446) 2025-02-20 09:42:54 +13:00
Viktor Pettersson
ded33a33a0 fix(edge): configure persisted mTLS certificates on start-up [BE-11622] (#440)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
Co-authored-by: Oscar Zhou <100548325+oscarzhou-portainer@users.noreply.github.com>
2025-02-19 14:46:44 +13:00
Steven Kang
4bd9569e63 version: bump version to 2.27.0-rc3 - release 2.27 (#427) 2025-02-14 08:39:05 +13:00
LP B
9e04145875 fix(swarm): fix the Host field when listing images (#369)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2025-02-12 00:47:50 +01:00
Oscar Zhou
3c6f61134e fix(platform): remove error log when local env is not found [BE-11353] (#375) 2025-02-12 09:24:08 +13:00
Steven Kang
9ac8641f7e workaround: leave the globally set helm repo to empty and add disclaimer - release 2.27 (#410) 2025-02-11 15:36:33 +13:00
Oscar Zhou
0fddedc1a9 fix(podman): missing filter in homepage [BE-11502] (#405) 2025-02-10 21:08:41 +13:00
Oscar Zhou
2e6a3a42be fix(setting): failed to persist edge computer setting [BE-11403] (#396) 2025-02-10 21:05:20 +13:00
Steven Kang
a245e93902 remove deprecated api endpoints - release 2.27 [BE-11510] (#400) 2025-02-10 10:46:48 +13:00
Steven Kang
d1f48ce043 feat: improve diagnostics stability - release 2.27 (#398) 2025-02-10 10:45:43 +13:00
Steven Kang
2c1156da75 version: bump version to 2.27.0-rc2 - release 2.27 (#403) 2025-02-07 14:47:54 +13:00
Steven Kang
5ed95ce714 chore: bump go version to 1.23.5 release 2.27 (#393) 2025-02-07 08:48:22 +13:00
viktigpetterr
3e5ec79b21 fix(endpoints): use the post method for batch delete API operations [BE-11573] (#397) 2025-02-06 18:17:13 +01:00
Steven Kang
157c83deee security: cve-2025-21613 release 227 (#391) 2025-02-05 15:56:35 +13:00
Oscar Zhou
2865fd6b84 fix(edge): check all endpoint_relation db query logic [BE-11602] (#379) 2025-02-05 15:20:27 +13:00
Steven Kang
96285817ab security: cve-2024-45338 release 2.27 (#387) 2025-02-05 15:03:42 +13:00
Oscar Zhou
c2c1ac70f8 fix(libstack): cannot open std edge stack log page [BE-11603] (#385) 2025-02-05 12:17:26 +13:00
James Player
b73f846397 fix(datatables): "Select all" should select only elements of the current page (#377) 2025-02-04 15:51:11 +13:00
Oscar Zhou
a43bb23bef fix(edgegroup): failed to associate env to static edge group [BE-11599] (#374) 2025-02-04 09:41:19 +13:00
LP B
c93b2fedb4 fix(app/edge): edge stacks webhooks cannot be disabled once created (#373) 2025-02-03 20:50:31 +01:00
LP B
156b223287 fix(api/edge): backend panic on edge stack removal (#370) 2025-02-03 20:25:31 +01:00
Steven Kang
9ea41f68bc version: bump version to 2.27.0-rc1 (#363)
Co-authored-by: steven <steven@stevens-Mini.hub>
2025-02-03 11:38:38 +13:00
James Player
e943aa8f03 feat(documentation): change docs to use LTS/STS instead of version number (#357) 2025-02-03 11:17:36 +13:00
James Player
17a4750d8e fix(kubernetes): Resource reservation wasn't displaying properly in business edition and remove leader status (#362) 2025-02-03 11:02:23 +13:00
Malcolm Lockyer
7d18c22aa1 fix(ui): bring back k8s applications page row expand published urls [r8s-145] (#356) 2025-01-31 13:16:18 +13:00
Ali
c80cc6e268 chore(automation): give unique selectors [r8s-168] (#345)
Co-authored-by: JamesPlayer <james.player@portainer.io>
2025-01-30 15:42:32 +13:00
andres-portainer
b30a1b5250 fix(edgestacks): avoid repeated statuses BE-11561 (#351) 2025-01-27 16:00:05 -03:00
LP B
b753371700 fix(app/edge-stack): edge stack create form validation (#343) 2025-01-24 17:02:52 +01:00
andres-portainer
3ca5ab180f fix(system): optimize the memory usage when counting nodes BE-11575 (#342) 2025-01-23 20:41:09 -03:00
Ali
4971f5510c fix(app): edit app with configmap [r8s-95] (#341) 2025-01-24 11:35:47 +13:00
andres-portainer
20fa7e508d fix(edgestacks): decouple the EdgeStackStatusUpdateCoordinator so it can be used by other packages BE-11572 (#340) 2025-01-23 17:10:46 -03:00
James Player
ebffc340d9 fix(k8s): Changed 'Deploy from file' button text to 'Deploy from code' (#338) 2025-01-23 16:47:52 +13:00
andres-portainer
9a86737caa fix(edgestacks): add a status update coordinator to increase performance BE-11572 (#337) 2025-01-22 20:24:54 -03:00
Steven Kang
d35d8a7307 feat(oauth): fix mapping (#330) 2025-01-23 09:03:51 +13:00
andres-portainer
701ff5d6bc refactor(edgestacks): move handlerDBErr() out of the handler BE-11572 (#336) 2025-01-22 16:35:06 -03:00
LP B
9044b25a23 fix(app): remove passwords from registries list response (#334) 2025-01-22 17:40:21 +01:00
Ali
7f089fab86 fix(apps): use replicas from application spec [r8s-142] (#335) 2025-01-22 12:31:27 +13:00
James Carppe
a259c28678 Update bug report template for 2.26.1 (#329) 2025-01-21 16:19:03 +13:00
LP B
db48da185a fix(app/editor): reduce editor slowness by debouncing onChange calls (#326) 2025-01-17 22:41:06 +01:00
LP B
cab667c23b fix(app/edge-stack): UI notification on creation error (#325) 2025-01-17 20:33:01 +01:00
andres-portainer
154ca9f1b1 fix(edge): return proper error from context BE-11564 (#323) 2025-01-16 20:18:51 -03:00
Oscar Zhou
2abe40b786 fix(edgestack): remove project folder after deleting edgestack [BE-11559] (#320) 2025-01-16 09:16:09 +13:00
James Carppe
6be2420b32 Update bug report template for 2.26.0 (#319) 2025-01-15 14:38:59 +13:00
Ali
9405cc0e04 chore(portainer): bump version to 2.26.0 (#302) 2025-01-14 07:20:11 +13:00
Yajith Dayarathna
55c98912ed feat(omni): support for omni [R8S-75] (#105)
Co-authored-by: stevensbkang <skan070@gmail.com>
Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Malcolm Lockyer <segfault88@users.noreply.github.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2025-01-13 17:06:10 +13:00
Ali
45bd7984b0 fit(jobs): remove redundant checkboxes in executions datatable [r8s-182] (#295) 2025-01-12 18:24:22 +13:00
andres-portainer
1ed9a0106e feat(edge): optimize Edge Stack retrieval BE-11555 (#294) 2025-01-10 16:44:19 -03:00
LP B
f8b2ee8c0d fix(app/edge-stack): local filesystem path is not retained (#292) 2025-01-10 18:20:44 +01:00
Steven Kang
d32b0f8b7e feat(kubernetes): support for jobs and cron jobs - r8s-182 (#260)
Co-authored-by: James Carppe <85850129+jamescarppe@users.noreply.github.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: andres-portainer <91705312+andres-portainer@users.noreply.github.com>
Co-authored-by: Oscar Zhou <100548325+oscarzhou-portainer@users.noreply.github.com>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2025-01-10 13:21:27 +13:00
andres-portainer
24fdb1f600 fix(libstack): redirect the Docker and Compose logging to zerolog BE-11518 (#289) 2025-01-08 16:26:04 -03:00
Oscar Zhou
4010174f66 fix(docker/volume): failed to list volume before snapshot is created [BE-11544] (#286) 2025-01-08 09:45:13 +13:00
andres-portainer
e2b812a611 fix(edgestacks): check the version of the edge stack before updating the status BE-11488 (#287) 2025-01-07 17:31:57 -03:00
andres-portainer
d72b3a9ba2 feat(edgestacks): optimize the Edge Stack status update endpoint BE-11539 (#279) 2025-01-06 15:39:24 -03:00
LP B
85f52d2574 feat(app/stack): ability to prune volumes on stack/edge stack delete (#232)
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
2025-01-01 10:44:49 +13:00
andres-portainer
33ea22c0a9 feat(ssl): improve caching behavior BE-11527 (#273) 2024-12-30 11:10:13 -03:00
andres-portainer
0d52f9dd0e feat(async): avoid sending CSRF token for async edge polling requests BE-1152 (#272) 2024-12-30 10:58:44 -03:00
andres-portainer
3caffe1e85 feat(async): filter out Docker snapshot diffs without meaningful changes BE-11527 (#265) 2024-12-26 18:45:20 -03:00
Oscar Zhou
87b8dd61c3 fix: replace strings.ToLower with strings.EqualFold [BE-11524] (#263) 2024-12-24 11:15:16 +13:00
andres-portainer
ad77cd195c fix(docker): fix a data race in the Docker transport BE-10873 (#255) 2024-12-23 09:54:11 -03:00
James Carppe
eb2a754580 Update bug report template for 2.21.5 / 2.25.1 (#261) 2024-12-20 14:39:33 +13:00
Steven Kang
9258db58db feat(auth): add 30m session timeout - r8s-178 (#259) 2024-12-20 10:49:13 +13:00
andres-portainer
8d1c90f912 fix(platform): fix a data race in GetPlatform() BE-11522 (#253) 2024-12-19 09:37:50 -03:00
Steven Kang
1c62bd6ca5 fix: security - CVE-2024-45337 - portainer-suite develop (#247) 2024-12-19 10:55:34 +13:00
andres-portainer
13317ec43c feat(stacks): simplify WaitForStatus() BE-11505 (#241) 2024-12-17 16:25:49 -03:00
James Carppe
35dcb5ca46 Update bug report template for 2.25.0 (#245) 2024-12-16 13:53:15 +13:00
AndrewHucklesby
4454b6b890 bump version to 2.25.0 (#240) 2024-12-12 16:42:55 +13:00
Ali
117e3500ae fix(edge-stack): revert useEffect, to call matchRegistry less often [BE-11501] (#239) 2024-12-12 15:22:19 +13:00
andres-portainer
94fda6a720 fix(offlinegate): avoid leaking an RLock when the handler panics BE-11495 (#234) 2024-12-11 16:38:03 -03:00
Ali
e1388eff84 fix(annotations): parse annotation keys in angular forms [r8s-170] (#233) 2024-12-11 17:50:08 +13:00
Ali
94d2e32b49 fix(apps): simplify helm status [r8s-155] (#230) 2024-12-11 13:18:34 +13:00
Ali
069f22afa4 fix(services): separate table state [BE-11401] (#152) 2024-12-11 11:58:43 +13:00
LP B
52c90d4d0a feat(app/edge-stack): ability to prune containers on edge stack update (#216) 2024-12-10 22:54:02 +01:00
Ali
ce7e0d8d60 refactor(namespace): migrate namespace edit to react [r8s-125] (#38) 2024-12-11 10:15:46 +13:00
Oscar Zhou
40c7742e46 fix(edgestack): validate edge stack name for api [BE-11365] (#222) 2024-12-11 08:21:46 +13:00
Malcolm Lockyer
05e872337a feat(support): add db and activity db file size to support bundle [r8s-169] (#221) 2024-12-10 09:35:30 +13:00
Ali
aac9d001f7 feat(askai): hide askAI for CE [BE-11409] (#220) 2024-12-10 09:11:51 +13:00
andres-portainer
d295968948 feat(libstack): update Compose to v2.31.0 BE-11416 (#223) 2024-12-09 16:36:57 -03:00
Ali
97e7a3c5e2 fix(edge-stacks): various custom template issues [BE-11414] (#189) 2024-12-09 17:48:34 +13:00
Ali
16a1825990 feat(version): remove brackets for sts/lts [BE-11409] (#215) 2024-12-06 22:52:47 +13:00
Ali
441afead10 feat(ask-ai): integrate kapa-ai page [BE-11409] (#214) 2024-12-06 18:41:32 +13:00
Malcolm Lockyer
783ab253af feat(support): collect system info bundle to assist support troubleshooting [r8s-157] (#154) 2024-12-06 15:38:10 +13:00
Yajith Dayarathna
17648d12fe codecov integration with portainer-suite [PLA-119] (#210) 2024-12-06 12:09:09 +13:00
andres-portainer
2f4f1be99c feat(performance): increase HTTP compression performance BE-11417 (#211) 2024-12-05 19:10:56 -03:00
Ali
5d4d3888b8 fix(rbac): use team ids to get namespace access [r8s-154] (#209) 2024-12-05 17:29:45 +13:00
andres-portainer
473084e915 fix(edgestacks): remove edge stacks even after a system crash or power-off BE-10822 (#208) 2024-12-04 19:52:53 -03:00
Anthony Lapenna
a8147b9713 build: tidy up packages by removing unused scripts and files (#207) 2024-12-05 11:18:49 +13:00
Yajith Dayarathna
3c3dc547b2 fix(app/edge-stack): hide non-working BE fields from CE (#205)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2024-12-04 19:00:40 +01:00
James Carppe
c5accd0f16 Update bug report template for 2.24.1 (#191) 2024-12-04 08:34:59 +13:00
Oscar Zhou
cb949e443e fix(volume): unable to inspect and browse volume [BE-11216] (#186) 2024-12-03 09:10:10 +13:00
Anthony Lapenna
bb6815f681 build: introduce central Makefile and live-reload for Go (#184) 2024-12-03 08:49:03 +13:00
Anthony Lapenna
a261f60764 version: display dependencies versions (#188)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2024-12-03 08:45:44 +13:00
LP B
d393529026 fix(app): passing an initial table state overrides the default global filter state (#180) 2024-11-29 21:06:11 +01:00
Oscar Zhou
219c9593e0 fix(container): binding ip disappear after duplicate container [BE-11413] (#177) 2024-11-29 08:56:44 +13:00
andres-portainer
faa6b2b790 fix(libstack): add the build step for Compose BE-11448 (#173) 2024-11-27 18:43:25 -03:00
Oscar Zhou
4046bf7b31 feat(image): build image with file [BE-11372] (#171) 2024-11-27 18:33:35 -03:00
Ali
4f708309af fix(activity logs): decode base64 [BE-11418] (#172) 2024-11-28 08:54:32 +13:00
andres-portainer
f2e7680bf3 fix(compose): fix path resolution for env files BE-11428 (#167) 2024-11-26 22:09:58 -03:00
andres-portainer
5d2689b139 fix(compose): avoid creating a default network unnecessarily BE-11427 (#169) 2024-11-26 19:48:49 -03:00
andres-portainer
145ffeea40 fix(libstack): resolve env vars correctly in Compose BE-11420 (#166) 2024-11-26 18:09:12 -03:00
andres-portainer
13143bc7ea fix(libstack): fix environment variable handling in compose BE- (#165) 2024-11-26 17:37:22 -03:00
Oscar Zhou
ee0dbf2d22 feat(init): allow to customize kubectl-shell image by cli flag [BE-11419] (#162) 2024-11-26 10:17:46 +13:00
andres-portainer
4265ae4dae feat(offlinegate): improve error message BE-11402 (#163) 2024-11-25 17:40:17 -03:00
andres-portainer
821c1fdbef feat(swarm): do not prevent server startup when Swarm config.json file is invalid BE-11402 (#160) 2024-11-25 17:40:10 -03:00
andres-portainer
fe29d6aee3 feat(backup): reduce the locking time of the offline gate BE-11402 (#157) 2024-11-25 10:10:11 -03:00
Ali
c0c7144539 fix(app templates): load app template for deployment [BE-11382] (#141) 2024-11-25 17:41:09 +13:00
Anthony Lapenna
20e3d3a15b fix: review snapshot and post init migration logic (#158) 2024-11-25 11:03:12 +13:00
James Carppe
07d1eedae3 Update template to include lifecycle policy link (#156) 2024-11-21 17:11:20 +13:00
James Carppe
4ad3d70739 Update bug report template for 2.24.0 (#153) 2024-11-20 13:15:56 +13:00
andres-portainer
e6a1c29655 fix(compose): fix support for ECR BE-11392 (#151) 2024-11-18 16:42:53 -03:00
Yajith Dayarathna
333dfe1ebf refactor(edge/update): choose images from registry [BE-10964] (#6)
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
2024-11-18 14:11:26 +13:00
andres-portainer
c59872553a fix(stacks): pass the registry credentials to Compose stacks BE-11388 (#147)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
2024-11-18 08:39:13 +13:00
andres-portainer
1a39370f5b fix(libstack): add missing private registry credentials BE-11388 (#143) 2024-11-15 17:38:55 -03:00
Oscar Zhou
bc44056815 fix(swarm): failed to deploy app template [BE-11385] (#138) 2024-11-15 11:53:22 +13:00
andres-portainer
17c92343e0 fix(compose): avoid leftovers in Run() BE-11381 (#129) 2024-11-13 20:24:20 -03:00
andres-portainer
cd6935b07a feat(edgestacks): add a retry period to edge stack deployments BE-11155 (#109)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2024-11-13 20:13:30 -03:00
andres-portainer
47d428f3eb fix(libstack): fix compose run BE-11381 (#126) 2024-11-13 14:38:53 -03:00
LP B
2baae7072f fix(edge/stacks): use default namespace when none is specified in manifest (#124) 2024-11-13 16:30:08 +13:00
andres-portainer
2e9e459aa3 fix(libstack): add a different timeout for WaitForStatus BE-11376 (#120) 2024-11-12 19:31:44 -03:00
andres-portainer
7444e2c1c7 fix(compose): provide the project name for proper validation BE-11375 (#118) 2024-11-12 17:18:40 -03:00
Oscar Zhou
d6469eb33d fix(libstack): empty project name [BE-11375] (#116) 2024-11-12 10:20:45 -03:00
Ali
a2da6f1827 fix(configmap): create portainer configmap if it doesn't exist [r8s-141] (#113) 2024-11-12 18:23:00 +13:00
Oscar Zhou
e6508140f8 version: bump version to 2.24.0 (#102) 2024-11-12 12:13:27 +13:00
andres-portainer
a7127bc74f feat(libstack): remove the docker-compose binary BE-10801 (#111)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
2024-11-11 19:05:56 -03:00
Malcolm Lockyer
55aa0c0c5d fix(ui): kubernetes create from file page - fix template load failed mistake in ce (#112) 2024-11-12 10:46:37 +13:00
Ali
d25de4f459 fix(more-resources): address CE review comments [r8s-103] (#110) 2024-11-12 10:41:43 +13:00
Yajith Dayarathna
6d31f4876a fix(more resources): fix porting and functionality [r8s-103] (#8)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2024-11-12 09:55:30 +13:00
Steven Kang
e6577ca269 kubernetes: improved the node view [r8s-47] (#108) 2024-11-12 09:42:14 +13:00
Ali
08d77b4333 fix(namespace): handle no accesses found [r8s-141] (#106) 2024-11-12 09:29:55 +13:00
Ali
1ead121c9b fix(apps): for helm uninstall, ignore manual associated resource deletion [r8s-124] (#103) 2024-11-12 09:03:22 +13:00
LP B
ad19b4a421 fix(app): relocate Skip TLS switch next to git repo URL field (#107) 2024-11-11 17:16:37 +01:00
LP B
6bc52dd39c feat(edge): kubernetes WaitForStatus support (#85) 2024-11-11 14:02:20 +01:00
Malcolm Lockyer
fd2b00bf3b fix(ui): kubernetes create from file page - fix template load failed message style [R8S-68] (#95) 2024-11-11 12:06:56 +13:00
Ali
cd8c6d1ce0 fix(apps): don't delete the 'kubernetes' service or duplicate service names [r8s-124] (#90) 2024-11-11 08:26:56 +13:00
Ali
e9fc6d5598 refactor(namespace): migrate namespace access view to react [r8s-141] (#87) 2024-11-11 08:17:20 +13:00
Steven Kang
8ed7cd80cb feat(ui): improve Kubernetes node view [r8s-47] (#84) 2024-11-07 14:10:19 +13:00
Malcolm Lockyer
81322664ea fix(ui): kubernetes create from manifest page misalignments and incorrect loading icon [R8S-68] (#88) 2024-11-07 09:04:24 +13:00
Ali
458d722d47 fix(ui): consistent widget padding [r8s-136] (#82) 2024-11-05 14:25:40 +13:00
Malcolm Lockyer
3c0d25f3bd fix(ui): rename create from manifest to create from file [BE-11335] (#86) 2024-11-05 14:10:08 +13:00
Oscar Zhou
ca7e4dd66e fix(edge/async): onboarding agent without predefined group cannot be associated [BE-11281] (#83) 2024-11-05 09:32:25 +13:00
Ali
c1316532eb fix(apps): update associated resources on deletion [r8s-124] (#75) 2024-11-01 21:03:49 +13:00
Ali
d418784346 fix(rbac): revert rbac detection logic [r8s-137] (#81) 2024-11-01 19:28:23 +13:00
andres-portainer
1061601714 feat(activity-log): set descending timestamps as the default sorting order BE-11343 (#66) 2024-10-31 18:07:26 -03:00
andres-portainer
2f3d4a5511 fix(activity-log): fix broken sorting BE-11342 (#65) 2024-10-31 17:25:38 -03:00
LP B
9ea62bda28 fix(app/image-details): export images to tar (#40) 2024-10-31 17:40:01 +01:00
Steven Kang
94b1d446c0 fix(ingresses): load cluster wide ingresses [r8s-78] (#78) 2024-10-31 13:08:09 +13:00
Ali
6c57a00a65 fix(cluster): UI RBAC alert fix [r8s-138] (#72) 2024-10-31 10:12:56 +13:00
Yajith Dayarathna
8808531cd5 update ci trigger paths for portainer-ee - develop (#68) 2024-10-29 12:23:31 +13:00
andres-portainer
966fca950b fix(oauth): add a timeout to getOAuthToken() BE-11283 (#63) 2024-10-28 17:28:22 -03:00
Yajith Dayarathna
e528cff615 bump golang version to 1.23.2 (#60) 2024-10-29 09:02:18 +13:00
andres-portainer
1d037f2f1f feat(websocket): improve websocket code sharing BE-11340 (#61) 2024-10-25 11:21:49 -03:00
James Carppe
b2d67795b3 Update bug report template for 2.21.4 (#62) 2024-10-25 15:49:31 +13:00
Ali
959c527be7 refactor(apps): migrate applications view to react [r8s-124] (#28) 2024-10-25 12:28:05 +13:00
andres-portainer
cc75167437 fix(swarm): fix service updates BE-11219 (#57) 2024-10-23 18:23:24 -03:00
andres-portainer
3114d4b5c5 fix(security): add initial support for HSTS and CSP BE-11311 (#47) 2024-10-21 13:52:11 -03:00
andres-portainer
ac293cda1c feat(database): share more database code between CE and EE BE-11303 (#43) 2024-10-18 10:33:10 -03:00
Ali
7b88975bcb fix(applications): scale resource usage by pod count [r8s-127] (#33) 2024-10-16 14:33:45 +13:00
James Carppe
da4b2e3a56 Updated bug report template for 2.23.0 (#32) 2024-10-16 09:23:02 +13:00
andres-portainer
369598bc96 Bump version to v2.23.0 (#29) 2024-10-14 13:55:11 -03:00
andres-portainer
61c5269353 fix(edgejobs): decouple the Edge Jobs from the reverse tunnel service BE-10866 (#11) 2024-10-14 10:37:13 -03:00
LP B
7a35b5b0e4 refactor(ui/code-editor): accept enum type (#22)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2024-10-14 13:52:51 +02:00
Yajith Dayarathna
20e9423390 chore: standalone repository workflow cleanup (#26) 2024-10-14 18:34:08 +13:00
Ali
cf230a1cbc fix(k8s-volumes): add missing json labels tag [r8s-108] (#27) 2024-10-14 13:37:59 +13:00
Ali
a06a09afcf fix(app): use standard resource request units [r8s-122] (#15) 2024-10-14 11:27:22 +13:00
Yajith Dayarathna
c88382ec1f fix(apps): persist table settings [r8s-120] (#10)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2024-10-14 11:27:04 +13:00
Ali
fd0bc652a9 fix(volumes): update external labels CE [r8s-108] (#7) 2024-10-14 10:48:13 +13:00
Ali
57e10dc911 fix(apps): group helm apps together [r8s-102] (#24) 2024-10-14 10:28:56 +13:00
Yajith Dayarathna
1110f745e1 fix(volumes): allow standard users to select volumes [r8s-109] (#9)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
2024-10-12 13:01:27 +13:00
Oscar Zhou
811d03a419 chore: rm old .vscode.example folders in sub-repo [BE-11287] (#17)
Co-authored-by: deviantony <anthony.lapenna@portainer.io>
2024-10-11 16:10:16 +02:00
andres-portainer
666c031821 fix(git): optimize the git cloning process in terms of space BE-11286 (#20) 2024-10-10 18:49:50 -03:00
andres-portainer
4e457d97ad fix(linters): add back removed linters and extend them to CE BE-11294 2024-10-10 17:05:03 -03:00
andres-portainer
364e4f1b4e fix(linters): add back removed linters and extend them to CE BE-11294 2024-10-10 12:06:20 -03:00
andres-portainer
8aae557266 fix(stacks): run webhooks in background to avoid GitHub timeouts BE-11260 2024-10-09 17:28:19 -03:00
Yajith Dayarathna
2bd880ec29 required changes to enable monorepo.
Co-authored-by: deviantony <anthony.lapenna@portainer.io>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
2024-10-09 08:37:23 +13:00
Oscar Zhou
b14438fd99 fix(edge): add agent id/name into edge api response [BE-10988] (#12256) 2024-10-08 19:17:09 +13:00
James Carppe
ba96d8a5fb Update bug report template for 2.21.3 (#12309) 2024-10-08 16:24:16 +13:00
Ali
db4b1dd024 fix(app): fix cpu type for decimals [r8s-107] (#12306) 2024-10-08 11:44:22 +13:00
Ali
469a4e94c2 fix(volumes): update the external, unused badges and used by col [r8s-105] (#12302) 2024-10-08 11:41:47 +13:00
Ali
44d6c0885e fix(node): call node usage [r8s-106] (#12304) 2024-10-08 11:39:05 +13:00
andres-portainer
9ce4ac9c9e fix(oauth): change the logging level from Debug to Error BE-4583 (#12305) 2024-10-07 18:21:05 -03:00
James Carppe
b40d22dc74 Update bug report template for 2.22.0 (#12283) 2024-10-03 14:53:37 +13:00
Steven Kang
a257696c25 fix access conditions when the restrict default namespace is enabled (#12280) 2024-10-02 15:55:05 +13:00
andres-portainer
f742937359 fix(endpoints): optimize the search performance BE-11267 (#12262) 2024-10-01 15:13:54 -03:00
Steven Kang
c0db48b29d fix ingress creation for none class (#12273) 2024-10-01 14:43:46 +13:00
Steven Kang
ea228c3d6d refactor(k8s): namespace core logic (#12142)
Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: James Carppe <85850129+jamescarppe@users.noreply.github.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
2024-10-01 14:15:51 +13:00
Ali
da010f3d08 fix(podman): ensure initial env type matches container runtime [r8s-98] (#12259) 2024-09-30 09:16:24 +13:00
Ali
32e94d4e4e feat(podman): support add podman envs in the wizard [r8s-20] (#12056) 2024-09-25 11:55:07 +12:00
Ali
db616bc8a5 fix(wizard): update nodeport placeholder [r8s-62] (#12255) 2024-09-25 11:36:50 +12:00
James Carppe
b8b46ec129 Update bug report template for 2.21.2 (#12251) 2024-09-24 11:42:15 +12:00
LP B
7d0b79a546 fix(app/images): export images to tar (#12223) 2024-09-23 21:55:45 +02:00
LP B
fd26565b14 fix(app/templates): non admins cannot load templates list (#12235) 2024-09-23 17:54:32 +02:00
Nik Wakelin
e0b6f2283a chore(branding): Changes Linode to Akamai Connected Cloud (#12221) 2024-09-23 09:21:02 +12:00
Oscar Zhou
d3d3d50569 fix(version): add specific version for updater image [BE-11153] (#12227) 2024-09-21 14:54:08 +12:00
andres-portainer
cee997e0b3 fix(edgestacks): reorder operations to properly update the endpoint relations BE-11233 (#12239) 2024-09-20 19:10:28 -03:00
LP B
80f53ed6ec fix(api): skip guessing env when there is no env in DB (#12238) 2024-09-20 17:56:41 -03:00
Chaim Lev-Ari
6f84317e7a feat(system): upgrade on swarm [EE-5848] (#11728)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
2024-09-20 18:00:38 +02:00
LP B
3cb484f06a fix(app/users): password validation hint + missing message on empty teams list (#12231) 2024-09-20 16:33:13 +02:00
LP B
61353cbe8a fix(app/edge): race between redirects when selecting a template (#12230) 2024-09-20 16:00:40 +02:00
Yajith Dayarathna
d647980c3a updating attest params (#12228) 2024-09-20 11:48:32 +12:00
Oscar Zhou
5740abe31b fix(authorization): add registry button disappear for admin [BE-11228] (#12213) 2024-09-20 08:18:51 +12:00
andres-portainer
5fd4f52e35 fix(jwt): fix handling of non-expiring JWT tokens BE-11242 (#12220) 2024-09-17 18:23:33 -03:00
Yajith Dayarathna
dbe7cd16d4 2024-09-CVE (#12189) 2024-09-11 11:08:46 +12:00
Yajith Dayarathna
2b630ca2dd enabling build attestations (#12211) 2024-09-11 10:57:52 +12:00
Oscar Zhou
2ede22646b fix(version): add specific version for updater image [BE-11153] (#12202) 2024-09-11 08:29:23 +12:00
James Carppe
994b6bb471 Update bug report template for 2.21.1 (#12207) 2024-09-10 14:33:32 +12:00
andres-portainer
92f338e0cd fix(users): fix data-race in userCreate() BE-11209 (#12193) 2024-09-05 22:28:04 -03:00
andres-portainer
7a176cf284 fix(teams): fix data-race in teamCreate() BE-11210 (#12195) 2024-09-05 21:36:13 -03:00
Oscar Zhou
80e607ab30 fix(stack): env placeholder as host path [BE-11187] (#12192) 2024-09-06 08:43:12 +12:00
Anthony Lapenna
6cff21477e service: update stop grace period description (#12173) 2024-09-05 08:47:06 +02:00
Yajith Dayarathna
4bb5a7f480 updating ci workflow (#12183) 2024-09-05 09:19:36 +12:00
andres-portainer
9a88511d00 fix(docker): avoid specifying the MAC address of container for Docker API < v1.44 BE-10880 (#12179) 2024-09-03 10:31:24 -03:00
Yajith Dayarathna
48cd614948 CVE 2024 43798 (#12171) 2024-09-03 09:27:24 +12:00
andres-portainer
2fe252d62b fix(jwt): generate JWT IDs BE-11179 (#12175) 2024-09-02 12:06:39 -03:00
LP B
8fae7f8438 feat(app/wizard): info panel telling to add env only once per swarm cluster (#11954) 2024-09-02 14:22:07 +02:00
andres-portainer
e4e55157e8 fix(bouncer): add support for JWT revocation BE-11179 (#12164) 2024-08-30 20:24:05 -03:00
Yajith Dayarathna
a5e246cc16 testing go directive change (#12124) 2024-08-30 08:27:42 +02:00
andres-portainer
d28dc59584 fix(git): optimize listFiles() BE-11184 (#12160) 2024-08-29 19:01:51 -03:00
andres-portainer
5353570721 task(code): remove unnecessary uses of govalidator BE-11181 (#12156) 2024-08-28 19:37:20 -03:00
andres-portainer
eb3e367ba8 fix(edgestacks): change the level of a logged line EE-6874 (#11396) 2024-08-28 18:16:34 -03:00
Chaim Lev-Ari
3c1441d462 refactor(users): migrate list view to react [EE-2202] (#11914) 2024-08-28 17:04:32 -03:00
Chaim Lev-Ari
33ce841040 refactor(docker/events): migrate list view to react [EE-2228] (#11581) 2024-08-28 16:41:15 -03:00
Chaim Lev-Ari
9797201c2a feat(docker): label gpu as nvidia only [EE-6999] (#11729) 2024-08-28 16:38:27 -03:00
Chaim Lev-Ari
6e14ac583b fix(access-control): fix dt column header typo [EE-7113] (#11853) 2024-08-28 16:37:12 -03:00
Anthony Lapenna
0b37b677c1 refactor: fix linting issues across the codebase (#12152) 2024-08-28 15:03:15 +02:00
Oscar Zhou
f59dd34154 fix(swarm/service): list task when filtering service [BE-11029] (#12146) 2024-08-28 18:28:38 +12:00
James Carppe
e8ec648886 Update bug report template for 2.21.0 (#12145) 2024-08-27 16:42:49 +12:00
Ali
10767a06df fix(invalidate): keep invalidate default behaviour [BE-11064] (#12080) 2024-08-27 09:48:50 +12:00
James Carppe
59b3375b59 Update bug report template for 2.21.0-rc2 (#12128) 2024-08-23 10:55:43 +12:00
andres-portainer
4408fd0cd3 chore(polling): simplify the polling logic BE-4585 (#12121) 2024-08-22 10:54:34 -03:00
Yajith Dayarathna
975a9517b9 undo change to go directive 2024-08-22 16:21:13 +12:00
Yajith Dayarathna
89c92b7834 updating go directive 2024-08-22 16:17:28 +12:00
Anthony Lapenna
747cea8084 security: bump dependencies to address CVEs (#12119) 2024-08-21 20:08:25 +12:00
Ali
f016b31388 fix(docker-desktop): support auth cookies [BE-11134] (#12108) 2024-08-21 18:21:51 +12:00
Oscar Zhou
8cd53a4b7a fix(registry): non admin can see add registry button [BE-10834] (#12112) 2024-08-21 11:00:00 +12:00
LP B
a39abe61c2 fix(api/edge_stacks): ensure edge stacks related endpoints list generation returns unique elements (#12101) 2024-08-20 10:20:03 +02:00
James Carppe
054898f821 Update bug report template for 2.21.0-rc1 (#12104) 2024-08-15 19:27:24 +12:00
Oscar Zhou
13d9b12a2e fix(group): create group twice when associating devices [EE-7418] (#12092) 2024-08-12 17:09:49 +12:00
LP B
aaec856282 fix(app/registries): enforce user accesses on registries (#12087) 2024-08-10 11:53:16 +02:00
andres-portainer
009eec9475 fix(compose): avoid the need to pass the file to remove the stack BE-11057 (#12065)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io>
2024-08-09 10:22:31 -03:00
Yajith Dayarathna
8d14535fd5 updating github workflow 2024-08-09 14:58:20 +12:00
Oscar Zhou
cc7f14951c fix(stack/remote): pass forceRecreate setting [EE-7374] (#12051) 2024-08-06 09:02:21 +12:00
Yajith Dayarathna
b67ff87f35 Installing docker-compose during test-server step (#12075) 2024-08-05 11:28:47 +12:00
andres-portainer
f55ef6e691 fix(pendingactions): remove excessive logging BE-11094 (#12071) 2024-08-02 16:35:14 -03:00
andres-portainer
560a1a00ca fix(scheduler): remove jobs that won't be used anymore BE-11045 (#12058) 2024-08-01 10:59:29 -03:00
andres-portainer
3b5ce1b053 fix(scheduler): remove unnecessary goroutines BE-11044 (#12059) 2024-08-01 10:58:53 -03:00
andres-portainer
03e8d05f18 fix(scheduler): fix a data race in a unit test BE-11084 (#12057) 2024-08-01 10:58:08 -03:00
Oscar Zhou
bedb7fb255 fix(swarm): auto multi-select volume with same name [EE-7240] (#11955) 2024-07-31 12:12:26 +12:00
Oscar Zhou
4d586f7a85 fix(docker): missing browse volume option [EE-7179] (#11901) 2024-07-30 08:53:17 +12:00
Oscar Zhou
6486a5d971 fix(edgestack): broken parallel setting in create view [EE-7245] (#11945) 2024-07-29 09:42:05 +12:00
andres-portainer
e3364457c4 fix(security): update github.com/containers/image/v5 to fix GO-2024-2842 BE-11055 (#12046) 2024-07-23 18:56:17 -03:00
andres-portainer
66119a8b57 fix(snapshots): remove the attempt to snapshot untrusted environments EE-7407 (#12044) 2024-07-23 18:43:31 -03:00
Ali
6eb9e906af fix(placements) filter out empty items in the required node affinity array [BE-11022] (#12034)
Co-authored-by: testa113 <testa113>
2024-07-23 09:31:08 +12:00
LP B
1900fb695d fix(docker/container): use nodeName to build links to networks used by containers (#12002) 2024-07-17 14:40:05 +02:00
Oscar Zhou
a62aac296b fix(host): show clear host info message [EE-7075] (#12011) 2024-07-12 08:45:50 +12:00
Oscar Zhou
5294aa2810 fix(stack): excessive alias count error [EE-7305] (#11990) 2024-07-11 14:09:25 +12:00
andres-portainer
31bdb948a8 chore(code): use int ranges in loops BE-10990 (#12028) 2024-07-10 19:22:47 -03:00
andres-portainer
468c12c75b chore(bbolt): update to v1.3.10 EE-7298 (#12006) 2024-07-09 15:19:27 -03:00
andres-portainer
220fe28830 fix(snapshots): fix background snapshots on environment creation EE-7273 (#12021) 2024-07-09 15:18:13 -03:00
andres-portainer
7fd1a644a6 chore(loops): remove loop var copy EE-7342 (#12023) 2024-07-09 12:47:34 -03:00
andres-portainer
6e7a42727a chore(kompose): remove the code EE-4917 (#12003) 2024-07-08 17:19:07 -03:00
LP B
ac4b129195 fix(docker/network): send target nodeName when removing a network on swarm (#12001) 2024-07-08 17:31:18 +02:00
Steven Kang
85bc14e470 fix(cve): remediate cves detected in docker scout (#12018) 2024-07-08 10:24:39 +12:00
Yajith Dayarathna
6e791a2cfe (fix)nightly code security scan (#12017) 2024-07-06 10:54:41 +12:00
andres-portainer
340830d121 chore(docker): clean up the code EE-7325 (#11997) 2024-07-05 16:19:46 -03:00
andres-portainer
faca64442f chore(code): use cmp.Or() EE-7333 (#12009) 2024-07-04 19:23:53 -03:00
andres-portainer
854474478c chore(go): update to Go v1.22.5 EE-7297 (#12005) 2024-07-03 22:29:20 -03:00
andres-portainer
4adce14485 fix(errors): improve error handling EE-4430 (#11987) 2024-06-28 17:35:26 -03:00
andres-portainer
dc62604ed8 chore(code): remove unused third-party code EE-7306 (#11988) 2024-06-28 17:33:53 -03:00
andres-portainer
f0d43f941f chore(code): replace interface{} with any EE-6513 (#11986) 2024-06-28 14:59:28 -03:00
andres-portainer
9c4935286f chore(jsoniter): remove leftover code EE-6702 (#11984) 2024-06-28 09:46:49 -03:00
andres-portainer
e1648425ea chore(nomad): remove Nomad code EE-7234 (#11980) 2024-06-28 08:54:44 -03:00
andres-portainer
19fa40286a chore(fdo): remove FDO code EE-7235 (#11981) 2024-06-28 08:42:16 -03:00
andres-portainer
1a3db327c7 fix(kube): improve error handling EE-7196 (#11976) 2024-06-27 10:45:11 -03:00
andres-portainer
1170004097 fix(kube): improve error handling EE-7199 (#11974) 2024-06-27 10:43:44 -03:00
Ali
d2b0eacbf5 chore(deps): upgrade typescript to 5.5 [EE-7294] (#11970)
Co-authored-by: testa113 <testa113>
2024-06-27 13:54:10 +12:00
andres-portainer
ca9f85a1ff fix(snapshots): enable the background snapshotter EE-7273 (#11973) 2024-06-26 18:27:39 -03:00
andres-portainer
9ee092aa5e chore(code): reduce the code duplication EE-7278 (#11969) 2024-06-26 18:14:22 -03:00
Oscar Zhou
39bdfa4512 fix(edgestack): gitops auto update issue [EE-7260] (#11965) 2024-06-27 08:48:03 +12:00
cmeng
e828615467 fix(host-info) host info improvement EE-7075 (#11884) 2024-06-26 12:18:22 -03:00
Ali
ba4526985a fix(app): show services explanation in app form [EE-7284] (#11967)
Co-authored-by: testa113 <testa113>
2024-06-26 10:44:20 +12:00
Chaim Lev-Ari
607feb183e refactor(ui/button): remove duplicate data-cy [EE-7242] (#11934) 2024-06-20 15:39:03 +02:00
LP B
9994ed157a fix(app): properly update the app state when losing connectivity to a remote environment while browsing it (#11942) 2024-06-19 13:44:57 +02:00
andres-portainer
bfa27d9103 chore(code): clean up the code EE-7251 (#11948) 2024-06-18 15:59:12 -03:00
Ali
be9d3285e1 fix(custom-templates): add stack validation, remove custom template validation [EE-7102] (#11938)
Co-authored-by: testa113 <testa113>
2024-06-17 09:24:54 +12:00
Chaim Lev-Ari
0f5988af49 fix(edge/stacks): load template [EE-7109] (#11848) 2024-06-16 07:54:00 +03:00
Chaim Lev-Ari
a28bd349ae fix(edge/update): show environment count when more than 100 [EE-6424] (#11917) 2024-06-14 18:37:45 -03:00
Chaim Lev-Ari
51f9977885 fix(endpoints): show toaster on delete [EE-7170] (#11889) 2024-06-13 18:32:17 -03:00
Ali
27865981df fix(namespace): sanitize owner label [EE-7122] (#11935)
Co-authored-by: testa113 <testa113>
2024-06-13 11:06:17 +12:00
James Carppe
ac3f1cd5c3 Add support for specifying the NFS server address in the mount point EE-7019 (#11921) 2024-06-12 11:23:08 -03:00
Dakota Walsh
7549b6cf3f fix(kubernetes): cluster setup screen text on own line EE-7112 (#11905) 2024-06-12 08:43:17 +12:00
Oscar Zhou
dd372ee122 fix(customtemplate): duplicated error handling [EE-7197] (#11913) 2024-06-11 22:11:15 +12:00
LP B
6a8e6734f3 feat(app): limit the docker API version supported by the frontend (#11855) 2024-06-10 20:54:31 +02:00
andres-portainer
4ba16f1b04 chore(errors): remove superfluous error handling EE-7192 (#11909) 2024-06-10 09:57:02 -03:00
andres-portainer
90a19cec5c chore(code): remove unnecessary type conversions EE-7191 (#11908) 2024-06-10 09:32:52 -03:00
Chaim Lev-Ari
8e480c9fab fix(ui): add accessibility labels to access control fieldset (#11439) 2024-06-09 14:34:22 +03:00
Chaim Lev-Ari
b0e3afa0b6 feat(edge/stacks): default refresh rate to 10s [EE-7155] (#11891) 2024-06-09 14:17:21 +03:00
Chaim Lev-Ari
eb6d251a73 feat(edge/jobs): migrate item view to react [EE-2220] (#11887) 2024-06-06 21:07:39 +03:00
Matt Hook
62c2bf86aa fix(db): fix missing portainer.edb in backups when encrypted portainer db is used [EE-6417] (#11885) 2024-06-06 12:36:27 +12:00
Oscar Zhou
4a7f96caf6 fix(stack): unable to delete invalid stack [EE-5753] (#11813) 2024-06-04 11:34:02 +12:00
Chaim Lev-Ari
9c70a43ac3 refactor(edge/groups): migrate view to react [EE-2219] (#11758) 2024-06-02 15:43:37 +03:00
Chaim Lev-Ari
b7cde35c3d fix(ui/datatables): make empty table label consistent [EE-6499] (#11612) 2024-06-02 12:29:20 +03:00
Chaim Lev-Ari
02fbdfec36 feat(edge/jobs): migrate create view to react [EE-2221] (#11867) 2024-06-02 11:10:38 +03:00
Chaim Lev-Ari
94c91035a7 refactor(custom-templates): migrate list view to react [EE-2256] (#11611) 2024-05-30 12:04:28 +03:00
Matt Hook
5c6c66f010 ix(pendingactions): fix deadlock and reduce needless debug logging [EE-7049] (#11869) 2024-05-30 14:55:16 +12:00
Oscar Zhou
0c870bf37b fix(compose): add project directory option to compose command [EE-7093] (#11870) 2024-05-30 08:47:07 +12:00
matias-portainer
9e0e0a12fa fix(waiting-room): add support for bulk deletion in waiting room EE-7136 (#11879) 2024-05-28 17:18:23 -03:00
andres-portainer
c5a1d7e051 fix(tunnels): make the tunnels more robust EE-7042 (#11877) 2024-05-28 16:42:56 -03:00
andres-portainer
aaab2fa9d8 fix(tls): add support for more cipher suites EE-7150 (#11874) 2024-05-28 15:49:31 -03:00
andres-portainer
ef4beef2ea task(endpoints): change the definition of /endpoints/remove EE-7126 (#11873) 2024-05-28 09:05:35 -03:00
Chaim Lev-Ari
1261887c9e fix(stacks): store filter state [EE-5159] (#11637) 2024-05-28 08:14:12 +03:00
cmeng
84fe3cf2a2 fix(stack): remove tailing slash of git url EE-6664 (#11773) 2024-05-28 09:24:29 +12:00
Chaim Lev-Ari
50fd7c6286 feat(docker/containers): limit items on volume selector [EE-7077] (#11845) 2024-05-23 13:15:36 +03:00
cmeng
d7b412eccc fix(container): replace container using correct node name EE-7066 (#11847) 2024-05-23 09:13:49 +12:00
Oscar Zhou
d283c63a33 fix(api/docker): no authorized user can call restricted api [EE-6808] (#11480) 2024-05-22 09:09:06 +12:00
James Carppe
d15e2cdc0c Update bug report template for 2.20.3 (#11846) 2024-05-21 12:50:29 +12:00
Matt Hook
9cef912c44 feat(dashboard): dashboard api [EE-7111] (#11843) 2024-05-21 11:09:29 +12:00
Oscar Zhou
659abe553d fix(edge/stack): edge stack env table pagination and action [EE-6836] (#11837) 2024-05-21 09:40:11 +12:00
Chaim Lev-Ari
014a590704 refactor(docker): migrate dashboard to react [EE-2191] (#11574) 2024-05-20 09:34:51 +03:00
cmeng
2669a44d79 fix(react-query): set react-query networkMode to offlineFirst EE-7081 (#11812) 2024-05-20 15:29:56 +12:00
Matt Hook
db8f9c6f6c fix(console): fix command not found [EE-6982] (#11825) 2024-05-20 14:35:29 +12:00
andres-portainer
2b01136d03 feat(demo): remove demo mode EE-6769 (#11841) 2024-05-17 20:00:01 -03:00
andres-portainer
fbbf550730 fix(endpoints): remove all the endpoints in the same transaction EE-7095 (#11839) 2024-05-17 16:45:06 -03:00
cmeng
3924d0f081 fix(deletion): delete objects batch by batch EE-7084 (#11833) 2024-05-16 14:34:50 +12:00
Matt Hook
00ab9e949a fix(pending-actions): correctly detect unreachable/down cluster [EE-7049] (#11809) 2024-05-16 09:03:10 +12:00
Chaim Lev-Ari
42d9dfba36 fix(docker/volumes): return 409 on volume conflict [EE-6748] (#11691) 2024-05-15 08:27:44 +03:00
Chaim Lev-Ari
a808f83e7d fix(ui): use expand button in sidebar and tables [EE-6844] (#11608) 2024-05-15 08:26:23 +03:00
Matt Hook
413b9c3b04 fix(terminal): don't close terminal on websocket close [EE-6631] (#11824) 2024-05-15 16:17:32 +12:00
Matt Hook
7edce528d6 fix(console): remove deprecated httputil and update console [EE-6468] (#10848) 2024-05-15 10:28:21 +12:00
Chaim Lev-Ari
836df78181 fix(templates): remove console.log [EE-7092] (#11815) 2024-05-14 09:11:05 +03:00
Ali
a80aa2b45c fix(app): ensure placement errors surface per node [EE-7065] (#11820)
Co-authored-by: testa113 <testa113>
2024-05-14 13:39:53 +12:00
Ali
9dd9ffdb3b fix(app): redirect to app after edit [EE-6385] (#11772)
Co-authored-by: testa113 <testa113>
2024-05-14 13:34:28 +12:00
Ali
b6daee2850 fix(app): surface placement rules from form [EE-6553] (#11816) 2024-05-14 13:34:06 +12:00
Ali
1ba4b590f4 fix(app): statefulset pvc summary [EE-6760] (#11802) 2024-05-14 13:33:25 +12:00
Ali
e73b1aa49c fix(docker): log cleanup errors during endpointforceupdate [EE-7055] (#11762) 2024-05-13 15:34:13 +12:00
Ali
6b5a402962 fix(errors): surface react docker errors to front end [EE-7053] (#11726)
Co-authored-by: testa113 <testa113>
2024-05-13 15:34:00 +12:00
Ali
55667a878a fix(gitops): manifest validation warning [EE-6859] (#11664) 2024-05-13 15:09:25 +12:00
Ali
a0ab82b866 fix(LDAP): skip pw validation on edit [EE-616] (#11666)
Co-authored-by: testa113 <testa113>
2024-05-13 15:08:48 +12:00
Matt Hook
6a51b6b41e fix(pending-actions): further refactoring [EE-7011] (#11806) 2024-05-10 11:59:58 +12:00
matias-portainer
b4e829e8c6 fix(waiting-room): add icon in list title EE-6687 (#11092) 2024-05-09 19:24:04 -03:00
Oscar Zhou
06ef12d0ff fix(image): github registry image truncated [EE-7021] (#11769) 2024-05-10 09:01:54 +12:00
Chaim Lev-Ari
cd5f342da0 refactor(edge/stacks): migrate edit view to react [EE-2222] (#11648) 2024-05-09 18:02:20 +03:00
Oscar Zhou
27e309754e fix(api): list docker volume performance [EE-6896] (#11541) 2024-05-09 13:02:56 +12:00
Ali
6ae0a972d4 fix(docker): surface node details docker error [EE-7054] (#11752)
Co-authored-by: testa113 <testa113>
2024-05-09 12:01:13 +12:00
Dakota Walsh
014c491205 fix(sidebar): environment names on hover EE-6854 (#11755) 2024-05-08 17:08:07 -04:00
Dakota Walsh
4ef71f4aca fix(account): enable add access token button EE-7059 (#11745) 2024-05-08 17:07:44 -04:00
Matt Hook
5a5a10821d fix(pendingactions): refactor pending actions [EE-7011] (#11780) 2024-05-09 08:10:10 +12:00
cmeng
9685e260ea fix(docker): keep /docker url prefix for DockerHandler EE-7073 (#11801) 2024-05-08 14:26:53 +12:00
Ali
f8871fcd2a fix(auth logs): fix typo in search keyword [EE-6742] (#11790)
Co-authored-by: testa113 <testa113>
2024-05-08 09:15:56 +12:00
Ali
6d17d8bc64 fix(be-overlay): consistency overlay with variants [EE-6742] (#11774)
Co-authored-by: testa113 <testa113>
2024-05-07 16:16:49 +12:00
Ali
46c6a0700f fix(app): show one tooltip to describe rollback feature [EE-6825] (#11777)
Co-authored-by: testa113 <testa113>
2024-05-07 15:27:22 +12:00
cmeng
5f8fd99fe8 fix(container): specify node name when get a container EE-6981 (#11748) 2024-05-07 11:34:46 +12:00
Chaim Lev-Ari
8a81d95253 refactor(edge/stacks): migrate create view to react [EE-2223] (#11575) 2024-05-06 08:08:03 +03:00
Prabhat Khera
f22aed34b5 fix(pending-action): pending action data format [EE-7064] (#11766) 2024-05-06 15:46:51 +12:00
Steven Kang
e75e6cb7f7 fix: windows container capability [EE-5814] (#11764) 2024-05-03 10:56:34 +12:00
Ali
14a365045d fix(configs): update unused badge logic [EE-6608] (#11500)
Co-authored-by: testa113 <testa113>
2024-05-03 09:13:33 +12:00
Prabhat Khera
9b6779515e fix(kubernetes): namespace yaml [EE-6701] (#11747) 2024-05-03 09:12:37 +12:00
Matt Hook
88ee1b5d19 fix(kube): correctly extract namespace from namespace manifest [EE-6555] (#11676)
Co-authored-by: Prabhat Khera <prabhat.khera@portainer.io>
2024-05-02 14:28:11 +12:00
Matt Hook
a45ec9a7b4 fix(kube): fix text in activity and authentication logs teasers [EE-6742] (#11683)
Co-authored-by: testa113 <testa113>
2024-05-02 14:23:56 +12:00
Ali
51605c6442 fix(app): explain rollback tooltip [EE-6825] (#11698)
Co-authored-by: testa113 <testa113>
2024-05-02 14:10:36 +12:00
Dakota Walsh
2fe213d864 fix(metadata): add mutli endpoint delete api EE-6872 (#11550) 2024-04-30 21:32:20 -04:00
Dakota Walsh
439f13af19 fix(migration): improper version EE-7048 (#11712) 2024-04-30 21:30:40 -04:00
James Carppe
2b5ecd3a57 Add 2.20.2 to bug report template (#11751) 2024-05-01 12:55:14 +12:00
cmeng
a9ead542b3 fix(edge-stack): add completed status EE-6210 (#11632) 2024-04-30 13:44:08 +12:00
Ali
7479302043 fix(jwt): handle kubeconfig with no expiry [EE-7044] (#11710)
Co-authored-by: testa113 <testa113>
2024-04-30 09:22:45 +12:00
Ali
10d20e5963 fix(version): reduce github requests [EE-7017] (#11677) 2024-04-26 08:46:02 +12:00
Ali
5a2e6d0e50 fix(app): avoid 'no label' error when deleting external app [EE-6019] (#11671) 2024-04-26 08:42:10 +12:00
andres-portainer
9068cfd892 chore(code): remove superfluous checks EE-7040 (#11692) 2024-04-25 11:25:23 -03:00
Chaim Lev-Ari
5560a444e5 fix(users): return json from create token [EE-6856] (#11577) 2024-04-25 10:10:42 +03:00
Matt Hook
505a2d5523 fix(jwt): upgrade jwt to remove deprecated jwt.StandardClaims [EE-6469] (#10850) 2024-04-23 17:33:36 +12:00
Ali
2463648161 fix(node): check more node role labels [EE-6968] (#11658)
Co-authored-by: testa113 <testa113>
2024-04-23 16:16:41 +12:00
Ali
48cf27a3b8 fix(migration): run post init migrations for edge after server starts [EE-6905] (#11546)
Co-authored-by: testa113 <testa113>
2024-04-23 16:15:28 +12:00
Matt Hook
39fce3e29b fix(published-ports): fix published port link and into a new component [EE-6592] (#11656) 2024-04-23 13:47:37 +12:00
Matt Hook
4f4c685085 fix(settings): fix crash during settings update when not using oauth [EE-7031] (#11662) 2024-04-23 12:58:28 +12:00
Prabhat Khera
d177a70c54 fix(stack): correct documentation link for stack ENV variables [EE-6902] (#11654) 2024-04-23 08:35:34 +12:00
James Carppe
cf8ec631dd Add 2.19.5 to bug report template (#11652) 2024-04-22 13:44:10 +12:00
Ali
ea61f36e5d fix(app): fix app stuck in loading [EE-7014] (#11651)
Co-authored-by: testa113 <testa113>
2024-04-22 13:11:41 +12:00
Oscar Zhou
ffc66647f8 feat(setting/oauth): add authstyle option [EE-6038] (#11610) 2024-04-22 10:35:19 +12:00
Oscar Zhou
6623475035 fix(stack/git): option to overwrite target path during dir move [EE-6871] (#11628) 2024-04-22 10:34:32 +12:00
cmeng
0dd12a218b fix(docker-client): explicitly set docker client scheme EE-6935 (#11520) 2024-04-22 09:00:45 +12:00
Chaim Lev-Ari
5f89d70fd8 refactor(datatables): remove angular table utilities [EE-4700] (#11634) 2024-04-21 04:47:09 +03:00
Ali
3ccbd40232 fix(stacks): conditionally hide node and namespace stacks [EE-6949] (#11527)
Co-authored-by: testa113 <testa113>
2024-04-19 17:33:22 +12:00
Prabhat Khera
7e9dd01265 fix(swagger): swagger docs for http status code 409 [EE-5767] (#11535) 2024-04-19 15:19:13 +12:00
Matt Hook
0fb3555a70 chore(kubectl): update kubectl to latest point release [EE-7018] (#11620) 2024-04-19 11:46:44 +12:00
andres-portainer
73ce754316 fix(workflows): upgrade Go to v1.21.9 EE-6939 (#11641) 2024-04-18 19:03:13 -03:00
Prabhat Khera
d304f330e8 fix(stack): fix stack env variable link [EE-6902] (#11624) 2024-04-19 07:00:22 +12:00
andres-portainer
7333598dba fix(mingit): upgrade to v2.44.0.1 EE-7023 (#11638) 2024-04-18 15:22:05 -03:00
Ali
bb61e73464 refactor(kube): events datatable react migration [EE-6450] (#11583)
Co-authored-by: testa113 <testa113>
2024-04-18 19:14:09 +12:00
Prabhat Khera
c15789eb73 fix(images): consider stopped containers for unused label [EE-6983] (#11629) 2024-04-18 17:14:39 +12:00
andres-portainer
e7a2b6268e fix(docker): upgrade to v24.0.9 EE-7016 (#11617) 2024-04-17 19:37:57 -03:00
andres-portainer
688fa3aa78 fix(go): upgrade Go to v1.21.9 in the nightly security scan EE-6939 (#11614) 2024-04-17 18:09:53 -03:00
Matt Hook
48bc7d0d92 fix(auth): prevent user enumeration attack [EE-6832] (#11589) 2024-04-17 16:08:27 +12:00
Prabhat Khera
d9df58e93a fix(pending-actions): clean pending actions for deleted environment [EE-6545] (#11598) 2024-04-16 15:09:10 +12:00
Oscar Zhou
37bba18c81 fix(api/endpoint): filter status for async devices [EE-6958] (#11509) 2024-04-16 13:37:04 +12:00
Matt Hook
40498d8ddd chore(docker): bump docker client to 26.0.1 [EE-6941] (#11592) 2024-04-16 08:27:58 +12:00
Prabhat Khera
b265810b95 fix(stacks): update info text for stack environment variables [EE-6902] (#11551) 2024-04-16 08:03:40 +12:00
Prabhat Khera
09837769d7 fix(pending-actions): fix create kubeclient to check endpoint status [EE-6545] (#11584) 2024-04-16 07:40:41 +12:00
Matt Hook
cf1fd17626 chore(api): bump docker and protobuf pkgs [EE-6941] (#11566) 2024-04-15 10:53:15 +12:00
Matt Hook
785f021898 chore(unpacker): use APIVersion as unpacker image tag [EE-6974] (#10955)
Co-authored-by: Prabhat Khera <91852476+prabhat-portainer@users.noreply.github.com>
2024-04-15 10:29:52 +12:00
Prabhat Khera
80cc9f18b5 chore(unpacker): use APIVersion as unpacker image tag [EE-6974] (#11506) 2024-04-15 10:29:24 +12:00
Matt Hook
5e7e91dd6d bump helm version (#11562) 2024-04-15 09:18:04 +12:00
Chaim Lev-Ari
1032b462b4 chore(deps): upgrade react-query to v4 [EE-6638] (#11041) 2024-04-14 17:54:25 +03:00
andres-portainer
104307b2b2 fix(protobuf): upgrade protobuf to v1.33 EE-6945 (#11570) 2024-04-12 17:52:35 -03:00
andres-portainer
f8c66a31d9 fix(go): upgrade Go to v1.21.9 EE-6939 (#11554) 2024-04-12 17:08:07 -03:00
Chaim Lev-Ari
2100155ab5 refactor(docker/containers): migrate inspect view to react [EE-2190] (#11005) 2024-04-11 19:07:58 +03:00
Chaim Lev-Ari
de473fc10e refactor(docker): remove EndpointProvider from exec [EE-6462] (#10840) 2024-04-11 19:04:58 +03:00
Chaim Lev-Ari
76e49ed9a8 refactor(kube/apps): migrate table to react [EE-4685] (#11028) 2024-04-11 10:11:17 +03:00
Chaim Lev-Ari
e9ebef15a0 refactor(rbac): migrate access table to react [EE-4710] (#10823) 2024-04-11 09:49:38 +03:00
Chaim Lev-Ari
6ff4fd3db2 refactor(templates): migrate list view to react [EE-2296] (#10999) 2024-04-11 09:29:30 +03:00
Ali
d38085a560 chore(data-cy): require data-cy attributes [EE-6880] (#11453) 2024-04-11 12:11:38 +12:00
Chaim Lev-Ari
3cad13388c refactor(ui): use external/system badge where applicable [EE-6952] (#11475) 2024-04-10 08:56:02 +03:00
Matt Hook
0b62456236 fix(backups): improved archive encryption [EE-6764] (#11489) 2024-04-10 10:45:49 +12:00
Chaim Lev-Ari
c22d280491 refactor(activity-logs): migrate activity logs table to react [EE-4714] (#10891) 2024-04-09 08:53:23 +03:00
Chaim Lev-Ari
960d18998f refactor(registries): migrate gitlab projects table to react [EE-4709] (#10792) 2024-04-09 08:52:44 +03:00
Chaim Lev-Ari
3f3db75d85 refactor(account): migrate access tokens table to react [EE-4701] (#10669) 2024-04-09 08:17:43 +03:00
Chaim Lev-Ari
48aab77058 refactor(rbac): migrate roles table to react [EE-4711] (#10772) 2024-04-09 08:11:29 +03:00
Chaim Lev-Ari
7e53d01d0f refactor(activity-logs): migrate auth logs table to react [EE-4715] (#10890) 2024-04-09 08:10:25 +03:00
Chaim Lev-Ari
bd271ec5a1 refactor(registries): migrate tags table to react [EE-6452] (#10990) 2024-04-09 08:08:14 +03:00
Matt Hook
8913e75484 fix(services): speed up service count on the kubernetes dashboard [EE-6967] (#11526) 2024-04-09 15:50:48 +12:00
Chaim Lev-Ari
c95ffa9e2d refactor(rbac): migrate access viewer table to react [EE-6447] (#11498) 2024-04-08 17:25:38 +03:00
Chaim Lev-Ari
ddb89f71b4 refactor(settings/auth): migrate ldap tables to react [EE-4712] (#10822) 2024-04-08 17:24:45 +03:00
Chaim Lev-Ari
45be6c2b45 refactor(tags): migrate tags to react [EE-4707] (#10771) 2024-04-08 17:23:49 +03:00
Chaim Lev-Ari
a00cb951bc refactor(kube/registries): migrate access table to react [EE-4706] (#10688) 2024-04-08 17:23:12 +03:00
Chaim Lev-Ari
f584bf3830 refactor(registries): migrate list view to react [EE-4704] (#10687) 2024-04-08 17:22:43 +03:00
Chaim Lev-Ari
9600eb6fa1 refactor(tables): use add and delete buttons [EE-6297] (#10668)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portaienr.io>
2024-04-08 17:21:41 +03:00
Chaim Lev-Ari
d88ef03ddb refactor(edge/jobs): migrate results table to react [EE-4679] (#10663) 2024-04-08 13:18:59 +03:00
Matt Hook
dc9d7ae3f1 fix(apikey): don't authenticate api key for external auth [EE-6932] (#11460) 2024-04-08 11:03:52 +12:00
James Carppe
a3c7eb0ce0 Update bug report template for 2.20.1 (#11505) 2024-04-05 14:56:19 +13:00
Chaim Lev-Ari
d1ba484be1 refactor(env/groups): migrate list view to react [EE-4703] (#10671) 2024-04-04 18:54:57 +03:00
Chaim Lev-Ari
521eb5f114 refactor(edge): use native progress tag for deployment counter [EE-6075] (#10936) 2024-04-04 18:12:27 +03:00
Chaim Lev-Ari
66770bebd4 refactor(edge/jobs): migrate view to react [EE-2236] (#10661) 2024-04-04 16:25:32 +03:00
Matt Hook
86c4b3059e fix(kube): use https when port is 443 in various tables [EE-6592] (#11443) 2024-04-04 14:36:38 +13:00
Ali
e3a8853212 fix(app): port namespace limit refresh from EE to CE [EE-6835] (#11483)
Co-authored-by: testa113 <testa113>
2024-04-04 08:19:04 +13:00
Ali
194b6e491d fix(namespace): wait for system ns setting to load before selecting existing ns [EE-6917] (#11481)
Co-authored-by: testa113 <testa113>
2024-04-04 08:18:13 +13:00
Chaim Lev-Ari
a439695248 refactor(users): migrate users table to react [EE-4708] (#10759) 2024-04-03 17:38:32 +03:00
Chaim Lev-Ari
86f1b8df6e refactor(kube/volumes): migrate storage table to react [EE-4697] (#11030) 2024-04-02 23:27:20 +03:00
Chaim Lev-Ari
a5faddc56c refactor(kube/cluster): migrate node apps table to react [EE-4691] (#11016) 2024-04-02 23:12:34 +03:00
Chaim Lev-Ari
9c68c6c9f3 refactor(kube/namespaces): migrate item apps table to react [EE-4693] (#11012) 2024-04-02 22:55:34 +03:00
Chaim Lev-Ari
d99486ee72 refactor(kube/namespaces): remove unused ingresses table [EE-6448] (#11029) 2024-04-02 22:41:45 +03:00
Chaim Lev-Ari
946166319f refactor(kube/apps): migrate integrated apps table to react [EE-4690] (#11025) 2024-04-02 22:37:47 +03:00
Chaim Lev-Ari
26bb028ace refactor(kube/namespaces): migrate table to react [EE-4694] (#10988) 2024-04-02 22:26:22 +03:00
Chaim Lev-Ari
da615afc92 refactor(kube/volumes): migrate to react [EE-4695] (#10987) 2024-04-02 22:10:22 +03:00
LP B
2b53bebcb3 fix(app): replace fields removed by Docker 25 and 26 (#11468)
* fix(app/volume): make optional Container and ContainerConfig fields removed in docker 26

* fix(app/image): use image.Size instead of image.VirtualSize removed in Docker 25
2024-03-29 13:57:14 +01:00
Chaim Lev-Ari
d336a14e50 feat(docker/services): show port ranges [EE-4012] (#10657) 2024-03-27 09:56:00 +02:00
cmeng
4ca6292805 fix(edge-stack): avoid reference of undefined EE-6914 (#11463) 2024-03-27 16:08:08 +13:00
andres-portainer
44ef5bb12a fix(kubernetes): avoid a deadlock EE-6901 (#11442) 2024-03-25 14:19:38 -03:00
andres-portainer
bf600f8b11 fix(kubernetes): sync with EE EE-6906 (#11448) 2024-03-22 16:14:00 -03:00
Prabhat Khera
d6d7afddbc chore(version): version bump to 2.22.0 [EE-6897] (#11438) 2024-03-22 14:37:27 +13:00
James Carppe
61642b8df6 Added 2.20.0 to bug report version option dropdown (#11430) 2024-03-19 16:34:03 +13:00
Matt Hook
07de1b2c06 fix(doclinks): fix help link paths [EE-6861] (#11418) 2024-03-19 11:46:41 +13:00
andres-portainer
bd3440bf3c fix(tests): fix data races EE-6867 (#11387) 2024-03-18 10:56:22 -03:00
Matt Hook
573f003226 fix(docs): fix all remaining webhook app links [EE-6861] (#11393) 2024-03-18 16:28:58 +13:00
Matt Hook
6e169662c2 fix(kube): fix edit application webhook link [EE-6861] (#11391) 2024-03-18 10:21:09 +13:00
cmeng
31658d4028 fix(stack): prepopulate when creating template from stack EE-6853 (#11380) 2024-03-18 09:36:06 +13:00
Oscar Zhou
bb02c69d14 chore(template/git): sync frontend code from ee (#11344) 2024-03-18 08:55:16 +13:00
Matt Hook
73307e164b fix(docs): make all doc links versioned [EE-6861] (#11382) 2024-03-15 16:57:51 +13:00
Matt Hook
9ea5efb6ba fix(stacks): update swagger stacks doc description [EE-6860] (#11384) 2024-03-15 16:47:14 +13:00
cmeng
3cd58cac54 fix(container): make blank string as valid value EE-6852 (#11373) 2024-03-15 09:01:47 +13:00
Prabhat Khera
1303a08f5a fix(auth): make createAccessToken api backward compatible [EE-6818] (#11326)
* fix(auth): make createAccessToken api backward compatible [EE-6818]

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

* fix messages
2024-03-14 09:02:28 +13:00
Ali
3b1d853090 fix(app): only show special message when limits change for existing app resource limit [EE-6837] (#11367)
Co-authored-by: testa113 <testa113>
2024-03-14 08:45:48 +13:00
cmeng
a2a4c85f2d fix(csrf): disable csrf secure cookie EE-6787 (#11300) 2024-03-13 11:22:11 +13:00
LP B
506ee389e3 fix(app): views not loading when quickly navigating in app (#11278) 2024-03-12 15:16:14 +01:00
Chaim Lev-Ari
8635bc9b9c fix(docker): apply private uac to edge admin [EE-6788] (#11285) 2024-03-12 09:59:36 +02:00
cmeng
447f497506 fix(edge-stack): deploy button is disabled EE-6819 (#11355) 2024-03-12 17:19:42 +13:00
Prabhat Khera
71292a60b1 address review commets (#11360) 2024-03-12 11:32:06 +13:00
Ali
51449490fa fix(app): on create don't mention previous values [EE-6837] (#11350)
Co-authored-by: testa113 <testa113>
2024-03-11 16:43:41 +13:00
Prabhat Khera
ae4970f0ed fix(container): autocomplete off for create container form [EE-6761] (#11336)
* autocomplete off doe create container form

* address review commets

* remove auto complete off from forms
2024-03-11 13:39:04 +13:00
Prabhat Khera
e96d5c245d fix(kube): stackname in daemonsets and statefulsets app [EE-6670] (#11352) 2024-03-11 10:04:51 +13:00
Chaim Lev-Ari
f8e3d75797 refactor(tests): wrap tests explicitly with provider [EE-6686] (#11090) 2024-03-10 14:22:01 +02:00
Chaim Lev-Ari
27aaf322b2 fix(kube/config): validate change window start [EE-6830] (#11329) 2024-03-10 09:42:33 +02:00
Matt Hook
b77132dbb1 fix(exec): improve alignment of help icon [EE-6816] (#11339) 2024-03-08 14:03:09 +13:00
Prabhat Khera
c35473f308 fix(kube-stacks): change wordings [EE-6670] (#11334) 2024-03-08 12:15:31 +13:00
Ali
a570073d12 fix(matomo): stop oauth link event [EE-6779] (#11332) 2024-03-08 10:17:29 +13:00
Oscar Zhou
0ad4826fab fix(stack): filter out orphan stacks that have same name as normal stacks [EE-6791] (#11291) 2024-03-08 09:56:10 +13:00
Matt Hook
6db7d31554 fix(exec): fix alignment and text size and alignment [EE-6816] (#11325) 2024-03-07 12:58:05 +13:00
cmeng
21d67a971d fix(menu): edge compute menu not clickable EE-6804 (#11319) 2024-03-07 12:11:58 +13:00
Prabhat Khera
8dfa5efa71 fix(stacks): make stackName kube stack specific field [EE-6670] (#11315)
* fix(stacks): make stackName kube stack specific field [EE-6670]

* fix wordings
2024-03-07 11:31:21 +13:00
Prabhat Khera
529750fa21 fix(UI): axios progress bar loading issue [EE-6781] (#11289) 2024-03-07 11:30:27 +13:00
Ali
96b1d36280 fix(time window): show errors for component [EE-6800] (#11317)
Co-authored-by: testa113 <testa113>
2024-03-07 09:03:22 +13:00
Chaim Lev-Ari
31c5a82749 fix(kube/setup): add a11y labels [EE-6747] (#11307) 2024-03-06 14:57:00 +02:00
Matt Hook
82516620e7 fix(contexthelp): remove extra slash from contexthelp docs link [EE-6780] (#11311) 2024-03-06 16:38:06 +13:00
Matt Hook
d26d5840f1 fix(helm): remove helm insights from the stack datatable [EE-6803] (#11314) 2024-03-06 16:36:58 +13:00
Dakota Walsh
ebd26316bf fix(datatable): title size EE-6774 (#11272) 2024-03-06 08:01:51 +13:00
Chaim Lev-Ari
18dbad232e fix(docker/images): export image [EE-6807] (#11306) 2024-03-05 19:30:48 +02:00
matias-portainer
ebcc98d5c5 fix(edge/templates): get correct default value for selectType env vars EE-6796 (#11294) 2024-03-04 10:35:24 -03:00
Matt Hook
e919da3771 fix(kube): update doc links to match new menu structure [EE-6759] (#11267) 2024-03-01 15:37:21 +13:00
Matt Hook
eda2dd20ee fix(help): add versioned doc links to support LTS/STS docs [EE-6780] (#11281) 2024-03-01 15:36:09 +13:00
cmeng
385fd95779 fix(edge-stacks): take not-found stack as removed EE-6758 (#11248) 2024-03-01 11:50:20 +13:00
cmeng
88185d7f6d fix(container): get old container info correctly EE-6716 (#11216) 2024-03-01 09:14:19 +13:00
cmeng
253cda8cef fix(stack): more space for add button EE-6773 (#11259) 2024-03-01 09:11:41 +13:00
Chaim Lev-Ari
b34afba7cd fix(auth): prevent unauthorized redirect on page load [EE-6777] (#11264) 2024-02-29 09:41:26 +02:00
Chaim Lev-Ari
6c70049ecc feat(kube): add a11y props for smoke tests [EE-6747] (#11263) 2024-02-29 09:26:13 +02:00
Chaim Lev-Ari
42c2a52a6b fix(ci): prevent tests from running twice [EE-6728] (#11197) 2024-02-29 08:11:49 +02:00
Chaim Lev-Ari
19a6a5c608 fix(docker): hide write buttons for non authorized [EE-6775] (#11260) 2024-02-27 12:36:44 +02:00
Prabhat Khera
d8e374fb76 fix(ui): autocomplete on edge custom template and stacks [EE-6761] (#11268) 2024-02-27 20:15:52 +13:00
Matt Hook
84ca6185dc fix(kube): make app autorefresh and show system settings stay [EE-6771] (#11257) 2024-02-27 11:18:44 +13:00
Prabhat Khera
5088634a41 fix(stack): auto complete dropdown in docker stacks [EE-6761] (#11253) 2024-02-26 11:43:15 +13:00
Ali
f6beedf0d5 fix(app): parse nan in validation check [EE-6714] (#11246) 2024-02-26 09:20:54 +13:00
Oscar Zhou
3caf1ddb7d fix(edge/template): validate app template env vars [EE-6743] (#11235) 2024-02-26 09:00:12 +13:00
Chaim Lev-Ari
c622f6da4e fix(docker): prevent non admins from passing security settings [EE-6765] (#11240) 2024-02-25 11:57:22 +02:00
cmeng
9ec7394124 fix(stack): make web editor readonly for git template EE-6706 (#11182) 2024-02-23 13:28:27 +13:00
Matt Hook
af8fde66b0 fix(dependancies): update compose and runc [EE-6744] (#11245) 2024-02-23 11:49:09 +13:00
Prabhat Khera
709315dde5 fix(ui): turn autocomplete off for git deployment [EE-6761] (#11242) 2024-02-23 08:44:03 +13:00
Ali
8856bae5c6 fix(app): NaN validation for autoscaling [EE-6714] (#11237) 2024-02-22 17:36:44 +13:00
Matt Hook
90451bfd47 fix(helm) tighten up helm requests [EE-6722] (#11236) 2024-02-22 11:35:33 +13:00
Ali
0c05539dee fix(input): allow clearing number inputs [EE-6714] (#11186) 2024-02-21 10:43:35 +13:00
Ali
a2a2c6cf3e fix(inputlist): update warning style [EE-6737] (#11221) 2024-02-21 08:29:10 +13:00
Matt Hook
76aa086d79 fix(libhttp): capitalize http error responses for better display [EE-6698] (#11114) 2024-02-21 07:51:46 +13:00
Chaim Lev-Ari
76fdfeaafc fix(ui): check for authorization [EE-6733] (#11208) 2024-02-20 11:06:09 +02:00
Chaim Lev-Ari
5932c78b88 fix(kube/apps): move namespace selector in apps view [EE-6612] (#11024) 2024-02-20 10:14:11 +02:00
Ali
68f5ca249f fix(app): remove insight from helm [EE-6693] (#11213)
Co-authored-by: testa113 <testa113>
2024-02-20 17:25:19 +13:00
Ali
2d87a8d8c3 fix(app): set values in react autoscaling form section [EE-6740] (#11219) 2024-02-20 09:35:27 +13:00
Prabhat Khera
988d4103d4 fix(git): update stack name for git stacks [EE-6670] (#11217) 2024-02-20 09:23:46 +13:00
Chaim Lev-Ari
ce3a1b8ba5 feat(a11y): add labels and roles [EE-6717] (#11181) 2024-02-19 16:37:26 +02:00
Oscar Zhou
6c89d3c0c9 fix(edge/template): custom template git fields not pre-filled [EE-6695] (#11112) 2024-02-19 08:39:05 +13:00
Ali
6b91fbf7f4 fix(app): update app type when changing data access policy [EE-6719] (#11211)
Co-authored-by: testa113 <testa113>
2024-02-19 08:08:22 +13:00
Ali
4f3f5e57b6 fix(app): avoid duplicate env requests [EE-6727] (#11194)
Co-authored-by: testa113 <testa113>
2024-02-16 14:02:05 +13:00
Prabhat Khera
6b3f30e32f fix(ui): update search placeholder [EE-6667] (#11190)
* update search placeholder

* remove box selector description
2024-02-16 12:34:06 +13:00
Matt Hook
bdeedb4018 fix(namespace): fix default namespace quota [EE-6700] (#11185) 2024-02-16 08:20:24 +13:00
Chaim Lev-Ari
50946e087c chore(eslint): add rule to check imports [EE-6730] (#11201) 2024-02-15 17:46:03 +02:00
Chaim Lev-Ari
7b89b04667 fix(auth): export hasAuthorizations [EE-6595] (#11199) 2024-02-15 14:05:52 +02:00
Chaim Lev-Ari
f5f84c5fa4 feat(ui): restrict views by role [EE-6595] (#11010) 2024-02-15 13:29:55 +02:00
Chaim Lev-Ari
437831fa80 feat(edge/stacks): add app templates to deploy types [EE-6632] (#11040) 2024-02-15 09:01:01 +02:00
Chaim Lev-Ari
31f5b42962 feat(auth): add useIsEdgeAdmin hook [EE-6627] (#11057) 2024-02-14 19:50:20 -03:00
Ali
7a6c872948 fix(insight): split insight from input [EE-6693] (#11176)
Co-authored-by: testa113 <testa113>
2024-02-15 10:45:59 +13:00
Chaim Lev-Ari
4bf18b1d65 feat(ui): write tests [EE-6685] (#11081) 2024-02-14 17:25:37 +02:00
Ali
2d25bf4afa fix(configs): correct 'external' display in tables [EE-6649] (#11110)
Co-authored-by: testa113 <testa113>
2024-02-14 11:48:09 +13:00
Ali
56ae19c5ab fix(stacks): add app form stacks input [EE-6693] (#11104) 2024-02-14 09:00:51 +13:00
Matt Hook
cdf9197274 fix(logs): add NOCOLOR option for use when exporting to greylog etc [EE-6696] (#11106) 2024-02-14 07:55:00 +13:00
Ali
901549e8dd fix(kube-owner): owner labels from resources created via manifest [EE-6647] (#11102)
Co-authored-by: testa113 <testa113>
2024-02-12 15:30:49 +13:00
Dakota Walsh
80b1cd19cb fix(restore): add S3 teaser EE-6675 (#11095) 2024-02-12 13:12:45 +13:00
Prabhat Khera
c4942de89b fix(ui): stackname auto fill on create from manifest screen [EE-6688] (#11099)
* fix(ui): stackname auto fill on create from manifest screen [EE-6688]

* address review comment
2024-02-12 10:54:29 +13:00
Ali
80d02f9cd1 fix(auth): isAdmin redirect for wizard [EE-6669] (#11074) 2024-02-12 08:04:39 +13:00
Prabhat Khera
671b22b5d6 fix(ui): scroll issue [EE-6667] (#11084)
* Fix scroll issue

* fix minorissue

* address review comments

* add comment
2024-02-09 15:35:34 +13:00
Steven Kang
43e56bf1c0 fix: pre-release build only after merging (#11097) 2024-02-09 15:26:43 +13:00
Matt Hook
a175619623 fix(docs): fix swagger docs for webhook params [EE-6668] (#11088) 2024-02-09 14:44:14 +13:00
Prabhat Khera
63c11d9310 fix(kube): ingress path duplication issue [EE-6649] (#11086) 2024-02-09 07:49:48 +13:00
Prabhat Khera
4c00b72ae3 fix stack name update issue (#11064) 2024-02-08 13:51:01 +13:00
Matt Hook
f4db09a534 fix(kube-apps): add helm insights, remove namespace insights panel [EE-6671] (#11077) 2024-02-08 11:38:04 +13:00
Prabhat Khera
01cd64037f fix(UI): some minor fixes [EE-6667] (#11061)
* minor tweeks for kubernetes settings

* address review comments
2024-02-06 12:17:38 +13:00
Steven Kang
a93344386c Pre-release as part of the CI (#11066)
* feat: add pre-release
* feat: add extension
* feat: fix typo
2024-02-05 18:24:16 +13:00
Prabhat Khera
a2195caa10 keep labels on edit ingress, configmaps and secrets (#11050) 2024-02-05 16:30:36 +13:00
Ali
9ad78753bc fix(r2a): don't set errors to undefined [EE-6665] (#11059)
Co-authored-by: testa113 <testa113>
2024-02-05 14:24:11 +13:00
Prabhat Khera
517190e28b chore(version): bump to 2.21.0 [EE-6652] (#11047)
* chore(version): bump to 2.21.0 [EE-6652]

* address review comments
2024-02-02 15:17:52 +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
Prabhat Khera
9fac997300 warning message placement and persisted folders heading (#9233) 2023-07-24 12:16:58 +12:00
Dakota Walsh
704d70c99b fix(gke): port metrics to the backend EE-5447 (#9041) 2023-07-24 12:16:29 +12:00
Oscar Zhou
e996d29d52 feat(edgestack/async): add Rollbackto field in stackPayload [EE-5684] (#9238) 2023-07-24 10:48:02 +12:00
cmeng
634326b5cd fix(container): column selection UI issue EE-5780 (#9242) 2023-07-24 10:12:43 +12:00
cmeng
94379763f8 fix(edge-stack): edfix(image): force remove button color EE-5787ge stack git authentication issues EE-5595 (#9237) 2023-07-21 16:37:30 +12:00
Ali
bb61723ba1 apply changes from EE (#9232)
Co-authored-by: testa113 <testa113>
2023-07-20 13:48:48 +12:00
cmeng
ff1f549590 fix(edge-stack): edge stack git authentication issues EE-5595 (#9228) 2023-07-20 09:11:38 +12:00
matias-portainer
b8f20a4f05 fix(waiting-room): remove breadcrumb EE-5781 (#9231) 2023-07-19 16:44:37 -03:00
matias-portainer
b5c5df798a chore(edgegroups): rename decoratedEdgeGroup property EE-5501 (#9212) 2023-07-19 12:28:17 -03:00
matias-portainer
88da28694c refactor(docker/events): fix null dataset EE-4667 (#9208) 2023-07-19 12:28:07 -03:00
Chaim Lev-Ari
4f0f53b9aa feat(edge/stacks): ui for status [EE-5593] (#9214) 2023-07-19 12:26:18 -03:00
Chaim Lev-Ari
03b9a9b65d fix(registry): find registry for image [EE-5660] (#9181) 2023-07-19 12:23:38 -03:00
Chaim Lev-Ari
fa755ffbca fix(edge/stacks): show registry field for git [EE-5742] (#9217) 2023-07-19 12:22:29 -03:00
Chaim Lev-Ari
5ad83d0adb fix(endpoints): filter by all edge stack states (#9218) 2023-07-19 12:21:47 -03:00
andres-portainer
9fa097d45f fix(endpointgroups): add transactions support to the User model to avoid a nil pointer dereference EE-5328 (#9221) 2023-07-17 21:23:35 -03:00
cmeng
7acd1080ad fix(edge-stack) make protainer compatible with previous agent EE-5614 (#9220) 2023-07-18 09:25:29 +12:00
andres-portainer
8c533bee67 feat(transactions): migrate some parts to use transactional code EE-5494 (#9213) 2023-07-17 17:36:00 -03:00
Chaim Lev-Ari
fbec123595 style(edge/stacks): remove duplicate component [EE-5554] (#9209) 2023-07-17 09:52:43 +03:00
Chaim Lev-Ari
09f60c3277 feat(docker): migrate files table to react [EE-4663] (#8916) 2023-07-16 10:59:58 +03:00
andres-portainer
146681e1c7 fix(snapshots): increase the chance of taking a snapshot for edge environments EE-4795 (#9211) 2023-07-14 12:34:50 -03:00
Chaim Lev-Ari
615af4fdee refactor(docker/configs): implement table in react [EE-4664] (#8912) 2023-07-14 08:48:08 +03:00
Chaim Lev-Ari
0bcb57568c feat(edge/stacks): increase status transparency [EE-5554] (#9094) 2023-07-13 23:55:52 +03:00
cmeng
db61fb149b feat(edge-stack): per-device-configs-for-edge-stack EE-5461 (#9203) 2023-07-13 15:41:47 -03:00
Chaim Lev-Ari
76b871d8a0 chore(deps): upgrade webpack loaders [EE-5126] (#9206) 2023-07-13 21:36:23 +03:00
Chaim Lev-Ari
a725883cbc refactor(docker/events): convert table to react [EE-4667] (#8937) 2023-07-13 12:55:22 +03:00
Chaim Lev-Ari
ecd54ab929 refactor(docker/images): convert table to react [EE-4668] (#8910) 2023-07-13 10:47:20 +03:00
Chaim Lev-Ari
0e9902fee9 refactor(settings): migrate view to react [EE-5509] (#9179) 2023-07-13 10:46:12 +03:00
cmeng
b93624fa1f fix(chisel): convert seed to private key file EE-5099 (#9149) 2023-07-13 15:19:40 +12:00
Matt Hook
91cfd2d0f2 fix(templates): show correct icon for tempate [EE-5426] (#9202)
* show correct os icon for swarm stacks

* add compose edge stack doc comment
2023-07-13 14:22:53 +12:00
Matt Hook
2d94f020d0 fix react table filter placement (#9200) 2023-07-13 11:47:53 +12:00
James Carppe
01b9c64216 Update 5NF references to 3NF, updated documentation links (#9201) 2023-07-13 11:46:37 +12:00
Oscar Zhou
b93aced176 feat(stack): introduce versioning for stack file [EE-5674] (#9184) 2023-07-13 11:06:24 +12:00
Chaim Lev-Ari
a216a1e960 refactor(edge/stacks): migrate list view to react [EE-2237] (#9186) 2023-07-12 17:26:52 +03:00
Chaim Lev-Ari
020ecb740a chore(ci): run test in github action [EE-3014] (#9187) 2023-07-12 09:27:33 +03:00
Chaim Lev-Ari
de5c959e24 fix(kube): deploy app with image [EE-5738] (#9194) 2023-07-12 09:26:50 +03:00
Dakota Walsh
a9c6fa5ac2 fix(docs): update kubeconfig import doc link EE-5478 (#9193) 2023-07-12 16:07:23 +12:00
Matt Hook
39c431392e indent submenus (#9192) 2023-07-12 15:32:59 +12:00
Matt Hook
cbe23dc753 more robust deletion strategy when removing endpoints (#9191) 2023-07-12 13:20:13 +12:00
Chaim Lev-Ari
afaeddb887 fix(edge/groups): skip count untrusted endpoints [EE-5672] (#9176) 2023-07-11 20:31:32 +03:00
LP B
39eed67fd7 fix(app): dark theme be teaser colors [EE-5621] (#9124)
* fix(app): dark theme be teaser colors

* fix(app): review comments and prettier format

* fix(app): revert changes on arbitrary class

* feat(app/teaser): blue lock svg on dark theme
2023-07-11 17:35:43 +02:00
andres-portainer
64b227b2e1 chore(code): clean up the code EE-5719 (#9183) 2023-07-10 23:26:54 -03:00
Matt Hook
979af5301e correctly identify master nodes (#9064) 2023-07-11 10:22:06 +12:00
Chaim Lev-Ari
10014ae171 refactor(ui/image-config): create react component [EE-5342] (#8856) 2023-07-10 18:56:12 +03:00
andres-portainer
bf51f1b6c9 chore(filenames): fix filenames EE-5717 (#9171) 2023-07-10 12:22:24 -03:00
Chaim Lev-Ari
60ae6a63fc refactor(settings): migrate ssl panel to react [EE-5506] (#9163) 2023-07-10 14:03:58 +03:00
Ali
c752b98120 fix(app): fix app ingress edge cases [EE-5663] (#9150)
Co-authored-by: testa113 <testa113>
2023-07-10 16:20:22 +12:00
Chaim Lev-Ari
8b11e1678e refactor(settings): migrate hidden containers panel to react [EE-5507] (#9119) 2023-07-10 03:39:11 +07:00
andres-portainer
eefb4c4287 fix(polling): clean up the logic for correctness and performance EE-5700 (#9169) 2023-07-07 18:00:20 -03:00
matias-portainer
29c1862754 fix(api): add missing public access middleware to routes EE-5191 (#9086) 2023-07-07 17:37:42 -03:00
Oscar Zhou
801b7d43ee refactor(edgestack): rename version to fileVersion (#9157) 2023-07-07 15:48:45 +12:00
Prabhat Khera
14d84c8025 fix(UI): message styling on ingress form EE-5665 (#9161) 2023-07-07 09:37:04 +12:00
Prabhat Khera
d8c4dcbe72 fix secret warning message styling (#9160) 2023-07-06 15:23:41 +12:00
andres-portainer
31d68f8091 fix(snapshots): avoid a last-write-wins situation EE-5701 (#9156) 2023-07-05 17:27:05 -03:00
andres-portainer
91088a5e0f fix(polling): reorder operations to avoid updating untrusted environments EE-5700 (#9155) 2023-07-05 17:26:52 -03:00
andres-portainer
e4ae4d5312 fix(edgegroups): fix updateEndpointStacks() EE-5699 (#9154) 2023-07-05 14:03:48 -03:00
andres-portainer
74515f102d fix(docker-proxy): reduce DB writes to optimize the proxy calls EE-5516 (#9148) 2023-07-05 09:25:05 -03:00
andres-portainer
b37120802e fix(edgegroups): avoid doing extra operations EE-5627 (#9144) 2023-07-04 16:57:20 -03:00
Chaim Lev-Ari
f5e09618f0 feat(edge): add EnvVar to stack details [EE-5463] (#9036) 2023-07-04 11:14:35 +07:00
Oscar Zhou
1a9a564553 fix/EE-5636/edge-stack-git-update-button-status (#9135) 2023-07-03 10:18:50 +12:00
andres-portainer
8a432ebbf8 fix(edgegroups): remove duplicated endpoints when updating an edge group EE-5679 (#9138) 2023-06-30 18:39:56 -03:00
Oscar Zhou
bc47061624 feat(edgestack): git stack versioning [EE-5458] (#9126) 2023-06-30 16:49:38 +12:00
Chaim Lev-Ari
ceabb2884b fix(edge/stacks): ignore missing file [EE-5649] (#9127) 2023-06-29 13:32:04 +07:00
Chaim Lev-Ari
f293ea41d3 refactor(settings): migrate helm cert panel to react [EE-5505] (#9132) 2023-06-29 13:31:17 +07:00
Chaim Lev-Ari
c452de82b7 refactor(libstack): use libstack [EE-5474] (#9122) 2023-06-28 08:03:52 +07:00
Chaim Lev-Ari
599d214e50 refactor(docker): remove EndpointProvider from commit [EE-5641] (#9123) 2023-06-28 08:02:43 +07:00
Oscar Zhou
f02ede00b3 fix(docker/tls): update tls certs for Docker API env [EE-4286] (#9112) 2023-06-28 08:51:58 +12:00
andres-portainer
f1f46f4da1 fix(boltdb): remove undefined behavior when deleting objects while iterating EE-5643 (#9129) 2023-06-27 16:42:52 -03:00
Oscar Zhou
c96e076871 feat(edge/stack): add stack deployment info struct [EE-5523] (#9042) 2023-06-26 18:12:15 +12:00
Ali
89c1d0e337 feat(app): add ingress to app service form [EE-5569] (#9106) 2023-06-26 16:21:19 +12:00
Chaim Lev-Ari
8c16fbb8aa refactor(libstack): move library to portainer [EE-5474] (#9120) 2023-06-26 08:11:05 +07:00
Chaim Lev-Ari
11571fd6ea refactor(edge/stacks): migrate envs table to react [EE-5613] (#9093) 2023-06-25 12:38:43 +07:00
Chaim Lev-Ari
dfc1a7b1d7 refactor(docker/images): remove EndpointProvider from build [EE-5551] (#9020) 2023-06-25 08:02:54 +07:00
cmeng
7cb6e3f66a feat(edge-stack): relative path support for edge stack EE-5521 (#9103) 2023-06-23 09:41:50 +12:00
andres-portainer
4cc96b4b30 feat(dataservices): unify access methods and abstract away redundant code [EE-5628] (#9115) 2023-06-22 18:28:07 -03:00
cmeng
4c6bbe9a2f fix(registry) undefined error on image tag view EE-4836 (#8885) 2023-06-23 09:07:52 +12:00
Chaim Lev-Ari
ea2f752a4f fix(edge/updates): validate amount of environments [EE-5053] (#9014) 2023-06-22 11:13:57 -03:00
Chaim Lev-Ari
4c8af378af fix(access-control): set user id when private (#8839) 2023-06-22 11:12:49 -03:00
Chaim Lev-Ari
e91b4f5c83 refactor(groups): migrate groups selectors to react [EE-3842] (#8936) 2023-06-22 11:11:10 -03:00
Chaim Lev-Ari
2018529add fix(kube/setup): toggle section on click [EE-4799] (#9107) 2023-06-22 11:10:04 -03:00
Chaim Lev-Ari
58651810bd fix(custom-templates): update template from git [EE-5534] (#9053) 2023-06-22 11:08:47 -03:00
Chaim Lev-Ari
2363d23de0 fix(stacks): fix stop and start [EE-5572] (#9050) 2023-06-22 11:08:24 -03:00
Chaim Lev-Ari
2cd5d55b00 fix(edge/stacks): fix ui issues [EE-5578] (#9070) 2023-06-22 11:08:04 -03:00
Prabhat Khera
3d22cde096 fix fallback rule (#9114) 2023-06-22 09:33:22 +12:00
Oscar Zhou
cd89487c41 fix(environment): blank environments list page [EE-5615] (#9113) 2023-06-22 09:05:58 +12:00
andres-portainer
b12e1aade4 fix(boltdb): remove extra allocation and copy from GetObject() EE-5622 (#9111) 2023-06-20 18:29:33 -03:00
andres-portainer
716c196682 feat(dataservices): abstract away some redundant code EE-5620 (#9110) 2023-06-20 17:51:34 -03:00
Chaim Lev-Ari
7dc6a1559f refactor(settings): kube settings panel [EE-5504] (#9079) 2023-06-20 11:02:39 +07:00
matias-portainer
806e1fdffa fix(environments): fix Add Environments button link EE-5616 (#9105) 2023-06-19 17:56:51 -03:00
LP B
2eca5e05d4 fix(edge-stack): URI too large error for edge stacks with a large amount of environments [EE-5583] (#9085)
* refactor(edge-stacks): filter endpoints by edgeStack

* feat(api/endpoints): edge stack filter support filtering on status in stack

* refactor(endpoints): use separate query params and not JSON query param when querying for an edge stack

* feat(api/endpoints): handle stack filter on dynamic groups + unique list with multiple groups sharing environments

* fix(app/endpoints): edge stack related query params type definition

* fix(api/endpoints): rebase conflicts on imports
2023-06-19 11:55:33 +02:00
Prabhat Khera
223dfe89dd fix(ingress): remove path from ingress host when added EE-5406 (#9099)
* remove path from ingress host when added

* add icon to message
2023-06-19 14:11:50 +12:00
Oscar Zhou
9f9cdf7d43 refactor(settings/backup): migrate backup setting module [EE-5508] (#9076) 2023-06-19 09:57:33 +12:00
Chaim Lev-Ari
caf87bb0b5 refactor(environments): migrate table to react [EE-4702] (#8882) 2023-06-18 12:18:55 +07:00
andres-portainer
f7dd73b0f7 feat(unit-testing): add a mock for the RequestBouncer EE-5610 (#9089) 2023-06-16 10:44:22 -03:00
andres-portainer
933e764a13 fix(endpoints): document an undocumented parameter EE-5556 (#9088) 2023-06-16 10:43:37 -03:00
andres-portainer
e43973da1a fix(logging): skip caller frames to give better context EE-5227 (#9087) 2023-06-15 18:34:11 -03:00
Ali
a2388226ad fix(app): path override validation [EE-5078] (#9077)
Co-authored-by: testa113 <testa113>
2023-06-15 09:05:19 +12:00
Oscar Zhou
0074bcc2ee fix(node): update minimum node version (#9078) 2023-06-14 21:44:12 +12:00
Ali
a4dfeda4ae fix(app): handle no options and volume mounts [EE-5078] (#9075)
* fix(app): handle no options and vol mounts EE-5078

* rm comment

---------

Co-authored-by: testa113 <testa113>
2023-06-14 16:22:44 +12:00
Prabhat Khera
90759182db fix fallback rule and wordings (#9074) 2023-06-14 13:45:25 +12:00
Chaim Lev-Ari
79822e1d3b refactor(edge): move stack response to a shared config [EE-5564] (#9033) 2023-06-13 13:20:02 +07:00
Ali
9d3f13ac92 fix(app): single delete config or secret [EE-5078] (#9069)
Co-authored-by: testa113 <testa113>
2023-06-13 17:03:55 +12:00
Oscar Zhou
2ac70b1eb6 feat(gitops): reword automatic update to gitops update for ui and docs (#9067) 2023-06-13 15:32:10 +12:00
Prabhat Khera
57fa044f2e feat(kubernetes): remove path and service from mandatory fields EE-5406 (#9054) 2023-06-13 12:38:00 +12:00
Ali
3721c1478e fix(app): fix app env var update issues [EE-5078] (#9066)
Co-authored-by: testa113 <testa113>
2023-06-13 09:14:10 +12:00
andres-portainer
424c98e256 fix(http): log HTTP server errors as DEBUG level EE-5225 (#9060) 2023-06-12 09:54:28 -03:00
Ali
2d69e93efa feat(app): rearrange app form services [EE-5566] (#9056) 2023-06-12 11:50:13 +12:00
Ali
d7fc2046d7 feat(config): separate configmaps and secrets [EE-5078] (#9029) 2023-06-12 09:46:48 +12:00
Ali
4a331b71e1 refactor(r2aform): remove validationData [EE-5559] (#9045)
* refactor(r2aform): remove validationData [EE-5559]

* update doc

---------

Co-authored-by: testa113 <testa113>
2023-06-12 08:48:10 +12:00
Chaim Lev-Ari
834ab7c158 fix(docker/images): show image tag [EE-5495] (#9051) 2023-06-11 08:55:19 +07:00
Chaim Lev-Ari
f799dd86c3 chore(deps): upgrade babel [EE-5219] (#9034) 2023-06-11 08:44:20 +07:00
Prabhat Khera
3233987a21 feat(kubernetes): add note teaser and styled application note on details EE-5364 (#9016) 2023-06-09 08:35:29 +12:00
Chaim Lev-Ari
58c1a60fee chore(edge): add test ids [EE-5323] (#9048) 2023-06-08 06:03:04 +07:00
Chaim Lev-Ari
8129e7590b feat(waiting-room): add beta alert to assignment [EE-5384] (#9028) 2023-06-08 06:02:36 +07:00
LP B
73950f3603 fix(app/stacks): swarm stack duplicate and migrate errors [EE-5520] (#9039)
* fix(dev): dev container script

* fix(app/stacks): make swarm stack migrate effectively target the target env and not the current env

* fix(app/stacks): make stack duplicate save the target swarm id on duplicated swarm stack
2023-06-07 14:28:40 +02:00
Chaim Lev-Ari
c7756f3018 refactor(settings): move app settings to panel [EE-5503] (#9043) 2023-06-07 12:16:47 +07:00
cmeng
4f04fe54a7 fix(edge-stack): transmit dot env file to agent [EE-4533] (#8664) 2023-06-06 09:39:08 +12:00
matias-portainer
c90a1be0e5 fix(edgegroups): allow edge groups with no environments or tags EE-4927 (#8439) 2023-06-05 10:18:34 -03:00
Matt Hook
0c5a0eb3a0 fix golint version (#9030) 2023-06-02 17:10:21 +12:00
Prabhat Khera
ecf7f7ec14 update docker go mod to 23.0.3 (#9024) 2023-06-02 11:55:37 +12:00
cmeng
e8e8329aab fix(registry): disable tls for azure [EE-3726] (#8605) 2023-06-02 11:53:46 +12:00
cmeng
4c2906e89d fix(edge) inconsistent heartbeat EE-5533 (#9011) 2023-06-02 10:36:14 +12:00
Matt Hook
fb2646b70c port changes from EE (#9003) 2023-06-02 08:35:15 +12:00
Matt Hook
3cd0409184 fix(build) cleanup build process [EE-5555] (#9026)
* improve makefile and related files

* update wording for build-all target
2023-06-01 13:50:34 +12:00
Matt Hook
1b041a029e increase timeout (#9023) 2023-05-31 18:11:34 +12:00
Ali
69776b4863 refactor(app): app service form to react [EE-5415] (#8994) 2023-05-31 17:58:41 +12:00
Chaim Lev-Ari
2d05103fed refactor(ui): migrate env var field to react [EE-4853] (#8451) 2023-05-31 10:08:41 +07:00
Matt Hook
6b5940e00e add card component (#9022) 2023-05-31 13:18:05 +12:00
Matt Hook
3a49dbf803 add copy to clipboard to web editor (#9009) 2023-05-31 12:28:11 +12:00
Chaim Lev-Ari
1cda08ca11 chore(deps): upgrade css tools [EE-5116] (#8990) 2023-05-31 01:35:25 +07:00
Chaim Lev-Ari
93bf630105 feat(edge/stacks): sync EE codechanges [EE-498] (#8580) 2023-05-31 01:33:22 +07:00
Chaim Lev-Ari
0ec7dfce69 chore(git): ignore go.work.sum [EE-5550] (#9019)
closes [EE-5550]
2023-05-30 21:35:46 +07:00
andres-portainer
eda07614ce chore(unit-test): simplify teardown EE-5536 (#9015) 2023-05-30 11:02:22 -03:00
Chaim Lev-Ari
b498cd657f chore(docs): replace cloudinovasi with portainer [EE-5547] (#9013) 2023-05-30 10:48:21 +07:00
Chaim Lev-Ari
61b568a738 fix(registry): sync config on change [EE-5460] (#8955) 2023-05-30 10:47:44 +07:00
Chaim Lev-Ari
d803d5f821 docs(build): update contrib guidelines to use makefile [EE-5519] (#8998) 2023-05-30 10:46:51 +07:00
Chaim Lev-Ari
2347133438 docs(build): update contrib guidelines to use makefile [EE-5519] (#8997) 2023-05-30 10:46:47 +07:00
Oscar Zhou
96de026eba fix(container/network): recreate container changes static IP [EE-5448] (#8960)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
2023-05-30 09:36:10 +12:00
LP B
d340c4ea96 fix(app/gitform): check if authentication is enabled before using form credentials (#8722) 2023-05-29 18:12:07 +02:00
Chaim Lev-Ari
9567072ce0 chore(deps): upgrade husky and lint-staged [EE-4842] (#8989) 2023-05-29 11:17:42 +07:00
Chaim Lev-Ari
d18b276e30 fix(settings): full width form fields [EE-4954] (#8867) 2023-05-29 10:16:58 +07:00
Ali
af77e33993 refactor(app): details widget migration [EE-5352] (#8886) 2023-05-29 15:06:14 +12:00
cmeng
fdd79cece8 fix(container): delete Mounts field from HostConfig object EE-5387 (#9001) 2023-05-29 09:01:42 +12:00
andres-portainer
ac94d344df fix(customtemplates): set TLSSkipVerify on update EE-5336 (#9007) 2023-05-26 00:29:09 -03:00
andres-portainer
bcbdb01785 fix(kubernetes): fix manifestFilePaths slice creation EE-4554 (#8023) 2023-05-25 11:38:14 -03:00
Chamhaw
a2f734051c fix(service): service related UI issues [EE-4062] (#7943) 2023-05-25 15:59:32 +12:00
cmeng
93866644c6 fix(GPU): EE-4331 error when enabling gpu on existing container (#8316) 2023-05-24 09:21:21 +12:00
Chaim Lev-Ari
6242952141 docs(stacks): require endpoint id [EE-5286] (#8988) 2023-05-23 10:25:32 +07:00
Chaim Lev-Ari
b4dd5c5989 docs(http): sort tags [EE-3697] (#8974) 2023-05-23 10:07:06 +07:00
Chaim Lev-Ari
ef00350922 docs(webhooks): document required endpoint and webhook type [EE-5286] (#8973) 2023-05-23 10:05:55 +07:00
matias-portainer
8acea44ee8 fix(edgejobs): sort logs result in UI EE-5304 (#8746) 2023-05-22 12:25:43 -03:00
Matt Hook
c193360741 cleanup docs (#8949) 2023-05-22 10:50:12 +12:00
Prabhat Khera
4f34a78f7f fix(ui): fix beta alert EE-5498 #8968 2023-05-22 10:17:11 +12:00
Chaim Lev-Ari
f96e7ff434 fix(ui): confirm deletion [EE-4612] (#8868) 2023-05-21 17:16:15 +07:00
Chaim Lev-Ari
e37e87971d fix(stacks): confirm enable tls verification [EE-5410] (#8896) 2023-05-21 12:27:29 +07:00
matias-portainer
5daef54456 fix(stacks): normalize stack name before performing actions EE-4839 (#8539) 2023-05-18 17:58:42 -03:00
andres-portainer
db93e5880f feat(endpointedge): add support for transactions EE-5327 (#8961) 2023-05-18 14:58:33 -03:00
Chaim Lev-Ari
881fa01eb2 fix(docker/networks): load containers from target node [EE-5446] (#8928) 2023-05-18 12:53:34 +07:00
Prabhat Khera
14fa60f6e6 fix(docker): fix extension spelling EE-5277 (#8956) 2023-05-18 10:21:07 +12:00
Prabhat Khera
b58cd1e87e fix(UI): update icons for beta and experimental features EE-5435 (#8940) 2023-05-18 10:19:44 +12:00
andres-portainer
395d86dcd1 feat(settings): add support for transactions EE-5331 (#8957) 2023-05-17 15:00:22 -03:00
andres-portainer
dbd476008b feat(snapshots): add support for transactions EE-5329 (#8947) 2023-05-17 11:57:05 -03:00
LP B
5a04338087 feat(api/stacks): use compose-unpacker to deploy stacks from git [EE-4758] (#8725)
* feat(api/stacks): use compose-unpacker to deploy stacks from git

* refactor(api/stacks): move stack operation as unpacker builder parameter + check builder func existence

* fix(api/stacks): defer removal of unpacker container after error check

* refactor(api/unpacker-builder): clearer code around client creation for standalone and swarm manager

* refactor(api/stacks): extract git stack check to utility function

* fix(api/stacks): apply skip tls when deploying with unpcker - ref EE-5023

* fix(api/stacks): defer close of docker client
2023-05-17 14:52:39 +02:00
Chaim Lev-Ari
dc5f866a24 feat(stacks): add ref to stack.env [EE-5145] (#8872) 2023-05-17 10:30:56 +07:00
Prabhat Khera
83551201fb fix(docker): add docker desktop extension flag in settings and add migration EE-5277 (#8948) 2023-05-17 14:31:46 +12:00
cmeng
e156243e43 fix(code-editor): highlight syntax web editor EE-5405 (#8871) 2023-05-17 14:07:21 +12:00
andres-portainer
1473cc208b feat(edgegroups): add support for transactions EE-5323 (#8946) 2023-05-16 16:07:03 -03:00
andres-portainer
d29b688eb9 feat(endpointgroups): implement support for transactions EE-5328 (#8944) 2023-05-16 14:47:31 -03:00
Chaim Lev-Ari
077046030d chore(deps): upgrade build-tools [EE-5117] (#8577) 2023-05-16 12:22:50 +07:00
Chaim Lev-Ari
5f3c0ff835 fix(ui/form): expandable form section [EE-4799] (#8866) 2023-05-16 10:39:52 +07:00
Chaim Lev-Ari
23e3cdb193 fix(stacks): show containers table [EE-5487] (#8935) 2023-05-16 10:30:34 +07:00
cmeng
e6984c5787 fix(icon) update ecr icon EE-4143 (#8880) 2023-05-16 14:08:23 +12:00
Matt Hook
0743f26ab8 fix(kube): updated kube terminology for configmaps/secrets [EE-4816] (#8770) 2023-05-16 09:21:50 +12:00
Dakota Walsh
8fa49d47f4 fix(docker): search published ports EE-4856 (#8939) 2023-05-15 12:26:42 +12:00
Chaim Lev-Ari
6ef53f0598 chore(deps): upgrade typescript [EE-4841] (#8247) 2023-05-14 16:24:37 +07:00
Chaim Lev-Ari
365316971b feat(waiting-room): choose relations when associated endpoint [EE-5187] (#8720) 2023-05-14 09:26:11 +07:00
andres-portainer
511adabce2 fix(http): drain and close response bodies EE-5486 (#8933) 2023-05-12 17:55:27 -03:00
andres-portainer
5b96136dd2 fix(customtemplates): set TLSSkipVerify properly EE-5336 (#8742) 2023-05-12 09:59:28 -03:00
Ali
42fce1ec57 fix(kube-tables): update table accessor fns [EE-5464] (#8920)
* fix(services): update accessor fns [EE-5464]

* small fixes

---------

Co-authored-by: testa113 <testa113>
2023-05-11 12:55:15 +12:00
Ali
22f4c5d650 separate internal configs with isInternal (#8690)
client-key: /Users/aliharris/.minikube/profiles/minikube/client.key

Co-authored-by: testa113 <testa113>
2023-05-11 08:13:54 +12:00
Prabhat Khera
945798a662 fix(kubernetes): fix light bulb panel for non docker envs EE-5418 2023-05-10 10:43:58 +12:00
Prabhat Khera
6a29198c5c fix note patching for Pod (#8915) 2023-05-10 10:42:56 +12:00
andres-portainer
7197ca435a fix(tls): add missing cipher suites EE-5465 (#8924) 2023-05-09 16:23:27 -03:00
Matt Hook
c3c2221437 fix(docs): fixing missing kube api endpoint docs [EE-5204] (#8843) 2023-05-09 16:42:26 +12:00
Matt Hook
d8fcce4c31 sync makefile with ee (#8918) 2023-05-09 15:19:38 +12:00
Chaim Lev-Ari
c86b76261a fix(gitops): make polling mechanism static button [EE-5420] (#8893) 2023-05-09 08:00:14 +07:00
Chaim Lev-Ari
acc340b324 fix(ui/code-editor): disable multi select [EE-5383] (#8861) 2023-05-09 07:59:34 +07:00
Chaim Lev-Ari
e0609e3d93 docs(teams): fix swagger [EE-5414] (#8890) 2023-05-08 16:00:00 +07:00
Prabhat Khera
926ca19a1b feat(UI): migrate console view to react EE-2276 (#8767) 2023-05-08 14:07:46 +12:00
hungdoo
c03b2ebbc1 wrap response rewrite operation with validation check (#7727) 2023-05-07 13:52:03 +12:00
andres-portainer
e82c88317e feat(edgestacks): add support for transactions EE-5326 (#8908) 2023-05-05 20:39:22 -03:00
cmeng
59f543f442 fix(web-editor) update web editor button color EE-5404 (#8892) 2023-05-05 16:49:11 +12:00
Matt Hook
f092b85f55 feat(makefile): improvements to the makefile and use gotestsum [EE-5439] (#8906)
* makefile improvements. use gotestsum

* increase timeout
2023-05-05 14:35:32 +12:00
Chaim Lev-Ari
cfed481d6e feat(license): remove untrusted devices from node count [EE-5357] (#8817) 2023-05-05 09:02:31 +07:00
Chaim Lev-Ari
5f6ddc2fad fix(edge/stacks): validate deployment type [EE-4580] (#8875) 2023-05-05 09:01:43 +07:00
Matt Hook
334eee0c8c fix(errors): wrap db errors, improve error handling (#8859)
* use error check func, wrap db object not found

* add errorlint and fix all the linting errors

* add exportloopref linter and fix errors

* fix incorrect error details returned on an api

* fix new errors

* increase linter timeout

* increase timeout to 10minutes

* increase timeout to 10minutes

* rebase and fix new lint errors

* make CE match EE

* fix govet issue
2023-05-05 12:19:47 +12:00
Oscar Zhou
550e235d59 fix(admin): infinite loop when initializaing admin user (#8905) 2023-05-05 11:45:03 +12:00
Matt Hook
9970fb3940 fix git options for kube (#8889) 2023-05-05 09:20:20 +12:00
pibica
5d2723f4b9 #8546 fix(logging): manage time in seconds or milliseconds (#8547) 2023-05-05 07:41:11 +12:00
andres-portainer
a062a0bfbe feat(resourcecontrol): add support for transactions EE-5431 (#8901) 2023-05-04 13:24:04 -03:00
andres-portainer
706d66a76e feat(teammemberships): add support for transactions EE-5412 (#8900) 2023-05-04 11:51:30 -03:00
Chaim Lev-Ari
2d22c4ff7d docs(stacks): require endpointId for delete [EE-4334] (#8897) 2023-05-04 21:32:46 +07:00
Chaim Lev-Ari
d77a0887a7 docs(endpoints): deprecate EdgeCheckinInterval [EE-5281] (#8864) 2023-05-04 21:31:57 +07:00
Chaim Lev-Ari
2383d243d5 docs(custom-templates): add missing parameters [EE-5233] (#8865) 2023-05-04 21:31:06 +07:00
Chaim Lev-Ari
426c132f97 refactor(edge/stacks): separate create by method [EE-4947] (#8898) 2023-05-04 21:11:19 +07:00
LP B
1ff19f8604 fix(app/home): env tile hover style [EE-5299] (#8765)
* fix(app/home): environment item hover

* fix(app/home): remove white border above env list footer

* fix(app/home): icon color on edit buttons hover in high contrast theme
2023-05-04 16:00:56 +02:00
Ali
14a581e86b fix(dialog): dialog migration issues [EE-5385] (#8849)
* fix(dialog): dialog migration issues [EE-5385]

* don't highlight slider tooltip text

---------

Co-authored-by: testa113 <testa113>
2023-05-04 16:23:27 +12:00
Oscar Zhou
ed279ba65b fix(edgestack): incorrect response code (#8873) 2023-05-04 10:01:33 +12:00
Oscar Zhou
19eceaf37f fix(restore/swarm): init primary endpoint after admin user is created (#8854) 2023-05-04 09:44:11 +12:00
Oscar Zhou
1963d064a3 fix(swarm/ui): keep stack detail page on the top [EE-4931] (#8858) 2023-05-04 09:29:47 +12:00
Matt Hook
58d130ee37 fix(buildscripts): make build process more closely resemble EE (#8881) 2023-05-03 12:13:28 +07:00
Ali
98e6393274 refactor(app): summary widget migration [EE-5351] (#8796)
* refactor(app): summary widget migration [EE-5351]

* update converter and limit display

---------

Co-authored-by: testa113 <testa113>
2023-05-03 15:55:25 +12:00
andres-portainer
745bbb7d79 feat(roles): add transactions support EE-5390 (#8878) 2023-05-02 19:05:18 -03:00
Chaim Lev-Ari
757461d58b chore(deps): upgrade react-table to v8 [EE-4837] (#8245) 2023-05-02 13:42:16 +07:00
Chaim Lev-Ari
f20d3e72b9 chore(build): remove grunt and add makefile [EE-4824] (#8125) 2023-05-02 10:13:37 +07:00
Prabhat Khera
731f3959c7 fix(UI): update application deploy/update messages EE-4005 (#8819) 2023-05-01 09:14:30 +12:00
cmeng
0f9a0e25f2 fix(login) hide password in console EE-5279 (#8774) 2023-04-29 07:24:33 +12:00
cmeng
ae339a0047 fix(stack) add skip TLS toggle for edit stack EE-5391 (#8851) 2023-04-28 13:35:33 +12:00
Chaim Lev-Ari
77f8b9333a refactor(stacks): break swagger docs by type [EE-5381] (#8820) 2023-04-27 11:03:55 +07:00
Chaim Lev-Ari
bbea0bc8a5 feat(edge): hide envs from waiting room [EE-5185] (#8688) 2023-04-27 09:23:10 +07:00
Chaim Lev-Ari
4b9c857d85 feat(waiting-room): show and filter by check in [EE-5186] (#8701) 2023-04-27 09:22:05 +07:00
Dakota Walsh
b5771df6a8 fix(image): allow dot in image names [EE-4595] (#8619) 2023-04-27 09:44:08 +12:00
matias-portainer
7ed8e9e167 fix(images): avoid returning null on registryId default value EE-5394 (#8841) 2023-04-26 10:24:45 -03:00
Prabhat Khera
80a3a5f16e feat(kubernetes): fix annotation validation EE-5021 (#8818) 2023-04-26 16:48:55 +12:00
Ali
3e654ff9b2 fix(deploy): return to referring view [EE-5345] (#8763)
* fix(deploy): return to referring view [EE-5345]

* no-underline -> no-decoration

---------

Co-authored-by: testa113 <testa113>
2023-04-26 11:23:15 +12:00
LP B
9b287f3020 fix(api/registry): encode X-Registry-Auth header using base64url instead of base64 [EE-4726] (#8492) 2023-04-24 13:57:39 +02:00
Oscar Zhou
a7404e00d1 fix(ci/security): intepret matrix summary as string not shell command (#8836) 2023-04-24 13:21:35 +12:00
Dakota Walsh
3654109332 fix(slider): update rc-slider [EE-5011] (#8611)
* fix(slider): update rc-slider [EE-5011]

* fix PasswordLengthSlider tooltip

* fix unnecessarily bulky className for SliderTooltip

* remove SliderTooltip inner div

* center slider handle value

* relative tooltip

* update z index

---------

Co-authored-by: testa113 <testa113>
2023-04-21 16:52:05 +12:00
Oscar Zhou
bf9dc8c2d0 feat(ci/security): remove deprecated github action command alert [EE-3059] (#8795) 2023-04-21 10:57:38 +12:00
cmeng
67f8e8f3c2 fix(webhook) remove NaN fom webhook url EE-5373 (#8816) 2023-04-21 10:56:53 +12:00
andres-portainer
56d6dfe02e feat(transactions): add transaction support for Registries EE-5382 (#8825) 2023-04-20 18:42:52 -03:00
Ali
861a9a5bbb fix(templates): update name validation [EE-5344] (#8760)
Co-authored-by: testa113 <testa113>
2023-04-21 09:39:55 +12:00
Matt Hook
1b470845b8 better logging during critical migration error (#8576) 2023-04-21 09:30:12 +12:00
Matt Hook
3c26aa8f34 feat(packages): upgrade packages [EE-5147] (#8658)
* upgrade packages

* update eksctl to match ee

* update helm to latest
2023-04-20 13:31:29 +12:00
Ali
de953da5a4 fix(editor): fix styles [EE-5369] (#8809)
* fix(editor): fix styles [EE-5369]

* rm hash

---------

Co-authored-by: testa113 <testa113>
2023-04-20 08:27:25 +12:00
Chaim Lev-Ari
5356d1feeb fix(edge/updates): add padding for edge groups [EE-5349] (#8772) 2023-04-18 13:40:12 +12:00
Matt Hook
7a8a20e0cc feat(libhelm): allow passing optional env and http client [EE-5252] (#8758) 2023-04-14 14:50:37 +12:00
andres-portainer
a7474188b9 chore(deps): unify dependencies EE-5360 (#8778) 2023-04-13 18:07:32 -03:00
cmeng
6fe56f89c6 fix(backup) add description text to backup EE-5283 (#8775) 2023-04-13 16:05:12 +12:00
Oscar Zhou
a98f480974 fix(swagger): correct endpoint api annotations [EE-5333] (#8761) 2023-04-13 15:31:27 +12:00
cmeng
8ccac7c98f fix(stack): upgrade docker-compose EE-5334 (#8757) 2023-04-11 17:56:00 +12:00
andres-portainer
e0ce3671e8 fix(tags): migrate to transactional code EE-5330 (#8755) 2023-04-10 19:03:51 -03:00
andres-portainer
62128d1069 fix(edgejobs): migrate to transactional code EE-5324 (#8747) 2023-04-10 15:59:34 -03:00
Oscar Zhou
a65ffe519a fix(k8s/gitops): missing git auth toggle in k8s app edit page [EE-5320] (#8741) 2023-04-10 20:14:13 +12:00
Ali
5ac1ea3df8 fix(ns): add selection caching back [EE-5273] (#8738)
Co-authored-by: testa113 <testa113>
2023-04-06 14:28:01 +12:00
Matt Hook
bf56bdb8f6 search for correct source directory when doing a restore (#8676) 2023-04-06 10:39:10 +12:00
cmeng
b00aa68c2b fix(homepage) move heartbeat logic to backend EE-5317 (#8737) 2023-04-06 09:09:22 +12:00
Matt Hook
8c5edd2c97 fix(docs): add missing swagger docs for upload file [EE-4886] (#8708)
* add docs for uploading files via host management features

* fix other doc issues
2023-04-04 16:59:34 +12:00
Oscar Zhou
c650868fe9 feat(templates): allow managing git based templates [EE-2600] (#7855)
Co-authored-by: itsconquest <william.conquest@portainer.io>
Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
2023-04-04 12:44:42 +12:00
cmeng
30a2bb0495 fix(security): potential vulnerability of path traversal attacks EE-5303 (#8728) 2023-04-04 09:00:17 +12:00
andres-portainer
1a451823d9 fix(edgestacks): fix a deadlock in UpdateEdgeStackFunc() (#8735) 2023-04-03 14:24:27 -03:00
Chaim Lev-Ari
feab2a757e feat(gitops): allow to skip tls verification [EE-5023] (#8668) 2023-04-03 09:19:17 +03:00
andres-portainer
17839aa473 fix(endpointrelation): change a callback so it is transactional EE-5312 (#8729) 2023-03-30 23:16:56 -03:00
Prabhat Khera
fc1aec3bb8 fix(ui): namespace caching issue EE-5273 (#8709)
* fix namespace caching issue

* fix(apps): add loading state [EE-5273]

* rm endpoint provider

* fix(namespace): remove caching [EE-5273]

* variable typo

---------

Co-authored-by: testa113 <testa113>
2023-03-31 13:24:57 +13:00
Chaim Lev-Ari
d64e7eacfc fix(ui/code-editor): stretch code editor content full height [EE-5202] (#8673) 2023-03-30 12:26:32 +03:00
Ali
7f805ac5be fix(ns): save filter to local storage [EE-5287] (#8723)
* fix(ns): save filter to local storage [EE-5287]

* allow system ns and save per user

---------

Co-authored-by: testa113 <testa113>
2023-03-30 11:21:05 +13:00
Chaim Lev-Ari
308a78db21 refactor(edge): deprecate IsEdgeDevice [EE-5046] (#8534) 2023-03-28 09:19:22 +03:00
andres-portainer
814fc9dfc0 fix(http): drain and close HTTP response bodies EE-5280 (#8716) 2023-03-27 15:14:16 -03:00
andres-portainer
3635df89dc fix(snapshots): change the snapshot object to maintain backwards compatibility EE-5240 (#8705) 2023-03-23 13:30:32 -03:00
Ali
30248eabb4 fix(apps) UI release fixes [EE-5197] (#8702)
* fix(apps) searchbar flex resizing and insights

* UI fixes

* update stacks datatable

---------

Co-authored-by: testa113 <testa113>
2023-03-23 08:20:30 +13:00
Ali
3636ac5c26 fix(dashboard): use faster proxy request [EE-5160] (#8693)
Co-authored-by: testa113 <testa113>
2023-03-22 15:34:44 +13:00
Prabhat Khera
f6e8b25cf3 fix Gpus null issue (#8692) 2023-03-21 16:06:01 +13:00
Oscar Zhou
124e0bf9b9 fix(stack/git): unexpected cursor movement in git text fields [EE-5143] (#8655) 2023-03-20 10:00:49 +13:00
Chaim Lev-Ari
45def82156 fix(ui/box-selector): BE link and use icons standard size [EE-5133] (#8607) 2023-03-19 13:37:35 +01:00
andres-portainer
76bdf6f220 fix(websocket): use the read part of the buffer instead of everything EE-5235 (#8685) 2023-03-17 17:23:24 -03:00
Ali
e142be399d fix(kubeconfig): fix download checkbox [EE-5199] (#8674)
Co-authored-by: testa113 <testa113>
2023-03-17 10:33:56 +13:00
Ali
13ba72ee07 fix(wizard): Capitalise Kubernetes [EE-5178] (#8662)
Co-authored-by: testa113 <testa113>
2023-03-16 18:50:54 +13:00
Dakota Walsh
f17a608dc7 fix(kubernetes): Prevent rerunning initial cluster detection [EE-5170] (#8666) 2023-03-16 15:39:26 +13:00
Prabhat Khera
6ee5cc6a56 fix(ui): namespace cache refresh on reload EE-5155 (#8644) 2023-03-16 10:10:37 +13:00
andres-portainer
44582732bb fix(home): exclude snapshots from the home page to improve the loading times EE-5154 (#8626) 2023-03-15 15:16:41 -03:00
andres-portainer
ea03024fbc fix(edgegroup): fix data race in edge group update EE-4441 (#8523) 2023-03-15 14:53:38 -03:00
Oscar Zhou
795e6a5b3c fix(stack/git): unable to move git repository error [EE-5144] (#8618) 2023-03-15 12:54:09 +13:00
andres-portainer
2b17cb9104 fix(kubernetes): fix data-race in GetKubeClient() EE-4436 (#8498) 2023-03-14 20:11:28 -03:00
andres-portainer
347f66b1f1 fix(edge): fix status inspect error message EE-5190 (#8661) 2023-03-14 13:28:20 -03:00
Ali
40c387f4f4 fix(annotations) ingress tip to match ee [EE-5158] (#8653)
Co-authored-by: testa113 <testa113>
2023-03-14 10:41:35 +13:00
andres-portainer
15cbdb8af9 chore(portainer): clean up the code EE-5188 (#8660) 2023-03-13 13:18:28 -03:00
matias-portainer
621a01ba3b fix(upgrade): remove yellow upgrade banner EE-5141 (#8640) 2023-03-13 09:01:27 -03:00
Ali
37f382d286 fix(kube): check for ns on enter [EE-5160] (#8647)
Co-authored-by: testa113 <testa113>
2023-03-13 13:57:11 +13:00
Prabhat Khera
77b49ae9c5 fix typo in delete image modal dialog (#8621) 2023-03-13 11:05:51 +13:00
Matt Hook
29648f517b reduce throttling in the kube client (#8630) 2023-03-13 09:44:27 +13:00
Ali
8f42af49e8 fix(annotation): update wording/styling [EE-5158] (#8642)
Co-authored-by: testa113 <testa113>
2023-03-10 16:52:09 +13:00
cmeng
0ab7987684 fix(edge-stack) always show edge group selector [EE-5157] (#8639) 2023-03-10 10:48:44 +13:00
Ali
31d956dbcb fix(app): restrict ns fix create app [EE-5123] (#8597)
* fix(app): restrict ns fix create app [EE-5123]

* fix node limits race condition

---------

Co-authored-by: testa113 <testa113>
2023-03-10 10:24:23 +13:00
Ali
2cc80e5e5d refactor(GPU): refactor to colocate and simplify UI work [EE-5127] (#8593)
* refactor to colocate and simplify

* fix(insights): text size to match portainer views

---------

Co-authored-by: testa113 <testa113>
2023-03-09 22:06:57 +13:00
matias-portainer
fb6e26a302 fix(stacks): pass WorkingDir to deployer command EE-5142 (#8615) 2023-03-08 19:34:57 -03:00
Matt Hook
9cca299833 fix(gotest): fix go tests as part of version bump to 2.19.0 (#8623)
* bump version to 2.19

* fix broken go tests
2023-03-08 17:23:34 +13:00
Chaim Lev-Ari
4c86be725d feat(system): upgrade portainer on kubernetes [EE-4625] (#8448) 2023-03-07 23:34:55 -03:00
Chaim Lev-Ari
0669ad77d3 fix(home): disable live connect for async [EE-5000] (#8462) 2023-03-07 21:27:34 -03:00
Matt Hook
2bfc956f58 bump version to 2.19 (#8617) 2023-03-08 13:24:59 +13:00
3989 changed files with 146416 additions and 83559 deletions

52
.air.toml Normal file
View File

@@ -0,0 +1,52 @@
root = "."
testdata_dir = "testdata"
tmp_dir = ".tmp"
[build]
args_bin = []
bin = "./dist/portainer"
cmd = "SKIP_GO_GET=true make build-server"
delay = 1000
exclude_dir = []
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = "./dist/portainer --log-level=DEBUG"
include_dir = ["api"]
include_ext = ["go"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true

View File

@@ -1,44 +0,0 @@
version: "2"
checks:
argument-count:
enabled: false
complex-logic:
enabled: false
file-lines:
enabled: false
method-complexity:
enabled: false
method-count:
enabled: false
method-lines:
enabled: false
nested-control-flow:
enabled: false
return-statements:
enabled: false
similar-code:
enabled: false
identical-code:
enabled: false
plugins:
gofmt:
enabled: true
eslint:
enabled: true
channel: "eslint-5"
config:
config: .eslintrc.yml
exclude_patterns:
- assets/
- build/
- dist/
- distribution/
- node_modules
- test/
- webpack/
- gruntfile.js
- webpack.config.js
- api/
- "!app/kubernetes/**"
- .github/
- .tmp/

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,7 +85,9 @@ overrides:
settings:
react:
version: 'detect'
rules:
no-console: error
import/order:
[
'error',
@@ -86,8 +100,8 @@ overrides:
no-plusplus: off
func-style: [error, 'declaration']
import/prefer-default-export: off
no-use-before-define: ['error', { functions: false }]
'@typescript-eslint/no-use-before-define': ['error', { functions: false }]
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 +120,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 +134,18 @@ 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
'react/jsx-props-no-spreading': off
- files:
- app/**/*.stories.*
rules:
'no-alert': off
'@typescript-eslint/no-restricted-imports': off
no-restricted-imports: off
'react/jsx-props-no-spreading': 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.

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

@@ -0,0 +1,185 @@
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).
Please note that we only provide support for current versions of Portainer. You can find a list of supported versions in our [lifecycle policy](https://docs.portainer.io/start/lifecycle).
**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 current versions of Portainer as per the lifecycle policy linked above. 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.27.1'
- '2.27.0'
- '2.26.1'
- '2.26.0'
- '2.25.1'
- '2.25.0'
- '2.24.1'
- '2.24.0'
- '2.23.0'
- '2.22.0'
- '2.21.5'
- '2.21.4'
- '2.21.3'
- '2.21.2'
- '2.21.1'
- '2.21.0'
- '2.20.3'
- '2.20.2'
- '2.20.1'
- '2.20.0'
- '2.19.5'
- '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'
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 5 nodes free
url: https://portainer.io/pricing/take5
about: Portainer Business Edition has more features, more support and you can now get 5 nodes free for as long as you want.
- 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.

View File

@@ -1,15 +0,0 @@
on:
push:
branches:
- develop
- 'release/**'
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: mschilde/auto-label-merge-conflicts@master
with:
CONFLICT_LABEL_NAME: 'has conflicts'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAX_RETRIES: 5
WAIT_MS: 5000

View File

@@ -1,47 +0,0 @@
name: Lint
on:
push:
branches:
- master
- develop
- release/*
pull_request:
branches:
- master
- develop
- release/*
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'yarn'
- uses: actions/setup-go@v3
with:
go-version: 1.19.4
- run: yarn --frozen-lockfile
- name: Run linters
uses: wearerequired/lint-action@v1
with:
eslint: true
eslint_extensions: ts,tsx,js,jsx
prettier: true
prettier_dir: app/
gofmt: true
gofmt_dir: api/
- name: Typecheck
uses: icrawl/action-tsc@v1
- name: GolangCI-Lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
working-directory: api
args: -c .golangci.yaml

View File

@@ -1,233 +0,0 @@
name: Nightly Code Security Scan
on:
schedule:
- cron: '0 8 * * *'
workflow_dispatch:
jobs:
client-dependencies:
name: Client dependency check
runs-on: ubuntu-latest
if: >- # only run for develop branch
github.ref == 'refs/heads/develop'
outputs:
js: ${{ steps.set-matrix.outputs.js_result }}
steps:
- uses: actions/checkout@master
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
continue-on-error: true # To make sure that artifact upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
json: true
- name: Upload js security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: js-security-scan-develop-result
path: snyk.json
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=table -export -export-filename="/data/js-result")
- name: Upload js result html file
uses: actions/upload-artifact@v3
with:
name: html-js-result-${{github.run_id}}
path: js-result.html
- name: Analyse the js result
id: set-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=matrix)
echo "::set-output name=js_result::${result}"
server-dependencies:
name: Server dependency check
runs-on: ubuntu-latest
if: >- # only run for develop branch
github.ref == 'refs/heads/develop'
outputs:
go: ${{ steps.set-matrix.outputs.go_result }}
steps:
- uses: actions/checkout@master
- uses: actions/setup-go@v3
with:
go-version: '1.19.4'
- name: Download go modules
run: cd ./api && go get -t -v -d ./...
- name: Run Snyk to check for vulnerabilities
continue-on-error: true # To make sure that artifact upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: |
yarn global add snyk
snyk test --file=./api/go.mod --json-file-output=snyk.json 2>/dev/null || :
- name: Upload go security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: go-security-scan-develop-result
path: snyk.json
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=table -export -export-filename="/data/go-result")
- name: Upload go result html file
uses: actions/upload-artifact@v3
with:
name: html-go-result-${{github.run_id}}
path: go-result.html
- name: Analyse the go result
id: set-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=matrix)
echo "::set-output name=go_result::${result}"
image-vulnerability:
name: Build docker image and Image vulnerability check
runs-on: ubuntu-latest
if: >-
github.ref == 'refs/heads/develop'
outputs:
image: ${{ steps.set-matrix.outputs.image_result }}
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Use golang 1.19.4
uses: actions/setup-go@v3
with:
go-version: '1.19.4'
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Install packages and build
run: yarn install && yarn build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: build/linux/Dockerfile
tags: trivy-portainer:${{ github.sha }}
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
- name: Load docker image
run: |
docker load --input /tmp/trivy-portainer-image.tar
- name: Run Trivy vulnerability scanner
uses: docker://docker.io/aquasec/trivy:latest
continue-on-error: true
with:
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
- name: Upload image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-develop-result
path: image-trivy.json
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=trivy -path="/data/image-trivy.json" -output-type=table -export -export-filename="/data/image-result")
- name: Upload go result html file
uses: actions/upload-artifact@v3
with:
name: html-image-result-${{github.run_id}}
path: image-result.html
- name: Analyse the trivy result
id: set-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=trivy -path="/data/image-trivy.json" -output-type=matrix)
echo "::set-output name=image_result::${result}"
result-analysis:
name: Analyse scan result
needs: [client-dependencies, server-dependencies, image-vulnerability]
runs-on: ubuntu-latest
if: >-
github.ref == 'refs/heads/develop'
strategy:
matrix:
js: ${{fromJson(needs.client-dependencies.outputs.js)}}
go: ${{fromJson(needs.server-dependencies.outputs.go)}}
image: ${{fromJson(needs.image-vulnerability.outputs.image)}}
steps:
- name: Display the results of js, go and image
run: |
echo ${{ matrix.js.status }}
echo ${{ matrix.go.status }}
echo ${{ matrix.image.status }}
echo ${{ matrix.js.summary }}
echo ${{ matrix.go.summary }}
echo ${{ matrix.image.summary }}
- name: Send Slack message
if: >-
matrix.js.status == 'failure' ||
matrix.go.status == 'failure' ||
matrix.image.status == 'failure'
uses: slackapi/slack-github-action@v1.18.0
with:
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Code Scanning Result (*${{ github.repository }}*)\n*<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|GitHub Actions Workflow URL>*"
}
}
],
"attachments": [
{
"color": "#FF0000",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*JS dependency check*: *${{ matrix.js.status }}*\n${{ matrix.js.summary }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Go dependency check*: *${{ matrix.go.status }}*\n${{ matrix.go.summary }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Image vulnerability check*: *${{ matrix.image.status }}*\n${{ matrix.image.summary }}\n"
}
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SECURITY_SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

View File

@@ -1,236 +0,0 @@
name: PR Code Security Scan
on:
pull_request_review:
types:
- submitted
- edited
paths:
- 'package.json'
- 'api/go.mod'
- 'gruntfile.js'
- 'build/linux/Dockerfile'
- 'build/linux/alpine.Dockerfile'
- 'build/windows/Dockerfile'
jobs:
client-dependencies:
name: Client dependency check
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
outputs:
jsdiff: ${{ steps.set-diff-matrix.outputs.js_diff_result }}
steps:
- uses: actions/checkout@master
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
continue-on-error: true # To make sure that artifact upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
json: true
- name: Upload js security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: js-security-scan-feat-result
path: snyk.json
- name: Download artifacts from develop branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mv ./snyk.json ./js-snyk-feature.json
(gh run download -n js-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
if [[ -e ./snyk.json ]]; then
mv ./snyk.json ./js-snyk-develop.json
else
echo "null" > ./js-snyk-develop.json
fi
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/js-snyk-feature.json" -compare-to="/data/js-snyk-develop.json" -output-type=table -export -export-filename="/data/js-result")
- name: Upload js result html file
uses: actions/upload-artifact@v3
with:
name: html-js-result-compare-to-develop-${{github.run_id}}
path: js-result.html
- name: Analyse the js diff result
id: set-diff-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/js-snyk-feature.json" -compare-to="./data/js-snyk-develop.json" -output-type=matrix)
echo "::set-output name=js_diff_result::${result}"
server-dependencies:
name: Server dependency check
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
outputs:
godiff: ${{ steps.set-diff-matrix.outputs.go_diff_result }}
steps:
- uses: actions/checkout@master
- uses: actions/setup-go@v3
with:
go-version: '1.19.4'
- name: Download go modules
run: cd ./api && go get -t -v -d ./...
- name: Run Snyk to check for vulnerabilities
continue-on-error: true # To make sure that artifact upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: |
yarn global add snyk
snyk test --file=./api/go.mod --json-file-output=snyk.json 2>/dev/null || :
- name: Upload go security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: go-security-scan-feature-result
path: snyk.json
- name: Download artifacts from develop branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mv ./snyk.json ./go-snyk-feature.json
(gh run download -n go-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
if [[ -e ./snyk.json ]]; then
mv ./snyk.json ./go-snyk-develop.json
else
echo "null" > ./go-snyk-develop.json
fi
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/go-snyk-feature.json" -compare-to="/data/go-snyk-develop.json" -output-type=table -export -export-filename="/data/go-result")
- name: Upload go result html file
uses: actions/upload-artifact@v3
with:
name: html-go-result-compare-to-develop-${{github.run_id}}
path: go-result.html
- name: Analyse the go diff result
id: set-diff-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/go-snyk-feature.json" -compare-to="/data/go-snyk-develop.json" -output-type=matrix)
echo "::set-output name=go_diff_result::${result}"
image-vulnerability:
name: Build docker image and Image vulnerability check
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
outputs:
imagediff: ${{ steps.set-diff-matrix.outputs.image_diff_result }}
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Use golang 1.19.4
uses: actions/setup-go@v3
with:
go-version: '1.19.4'
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Install packages and build
run: yarn install && yarn build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: build/linux/Dockerfile
tags: trivy-portainer:${{ github.sha }}
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
- name: Load docker image
run: |
docker load --input /tmp/trivy-portainer-image.tar
- name: Run Trivy vulnerability scanner
uses: docker://docker.io/aquasec/trivy:latest
continue-on-error: true
with:
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
- name: Upload image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-feature-result
path: image-trivy.json
- name: Download artifacts from develop branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mv ./image-trivy.json ./image-trivy-feature.json
(gh run download -n image-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
if [[ -e ./image-trivy.json ]]; then
mv ./image-trivy.json ./image-trivy-develop.json
else
echo "null" > ./image-trivy-develop.json
fi
- name: Export scan result to html file
run: |
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=trivy -path="/data/image-trivy-feature.json" -compare-to="/data/image-trivy-develop.json" -output-type=table -export -export-filename="/data/image-result")
- name: Upload image result html file
uses: actions/upload-artifact@v3
with:
name: html-image-result-compare-to-develop-${{github.run_id}}
path: image-result.html
- name: Analyse the image diff result
id: set-diff-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=trivy -path="/data/image-trivy-feature.json" -compare-to="./data/image-trivy-develop.json" -output-type=matrix)
echo "::set-output name=image_diff_result::${result}"
result-analysis:
name: Analyse scan result compared to develop
needs: [client-dependencies, server-dependencies, image-vulnerability]
runs-on: ubuntu-latest
if: >-
github.event.pull_request &&
github.event.review.body == '/scan'
strategy:
matrix:
jsdiff: ${{fromJson(needs.client-dependencies.outputs.jsdiff)}}
godiff: ${{fromJson(needs.server-dependencies.outputs.godiff)}}
imagediff: ${{fromJson(needs.image-vulnerability.outputs.imagediff)}}
steps:
- name: Check job status of diff result
if: >-
matrix.jsdiff.status == 'failure' ||
matrix.godiff.status == 'failure' ||
matrix.imagediff.status == 'failure'
run: |
echo ${{ matrix.jsdiff.status }}
echo ${{ matrix.godiff.status }}
echo ${{ matrix.imagediff.status }}
echo ${{ matrix.jsdiff.summary }}
echo ${{ matrix.godiff.summary }}
echo ${{ matrix.imagediff.summary }}
exit 1

View File

@@ -1,19 +0,0 @@
name: Automatic Rebase
on:
issue_comment:
types: [created]
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,27 +0,0 @@
name: Close Stale Issues
on:
schedule:
- cron: '0 12 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v4.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Issue Config
days-before-issue-stale: 60
days-before-issue-close: 7
stale-issue-label: 'status/stale'
exempt-all-issue-milestones: true # Do not stale issues in a milestone
exempt-issue-labels: kind/enhancement, kind/style, kind/workaround, kind/refactor, bug/need-confirmation, bug/confirmed, status/discuss
stale-issue-message: 'This issue has been marked as stale as it has not had recent activity, it will be closed if no further activity occurs in the next 7 days. If you believe that it has been incorrectly labelled as stale, leave a comment and the label will be removed.'
close-issue-message: 'Since no further activity has appeared on this issue it will be closed. If you believe that it has been incorrectly closed, leave a comment mentioning `portainer/support` and one of our staff will then review the issue. Note - If it is an old bug report, make sure that it is reproduceable in the latest version of Portainer as it may have already been fixed.'
# Pull Request Config
days-before-pr-stale: -1 # Do not stale pull request
days-before-pr-close: -1 # Do not close pull request

View File

@@ -1,29 +0,0 @@
name: Test
on: push
jobs:
test-client:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'yarn'
- run: yarn --frozen-lockfile
- name: Run tests
run: yarn test:client
# test-server:
# runs-on: ubuntu-latest
# env:
# GOPRIVATE: "github.com/portainer"
# steps:
# - uses: actions/checkout@v3
# - uses: actions/setup-go@v3
# with:
# go-version: '1.18'
# - name: Run tests
# run: |
# cd api
# go test ./...

View File

@@ -1,53 +0,0 @@
name: Validate
on:
pull_request:
branches:
- master
- develop
- 'release/*'
jobs:
openapi-spec:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Setup Node v14
uses: actions/setup-node@v2
with:
node-version: 14
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Setup Go v1.17.3
uses: actions/setup-go@v2
with:
go-version: '^1.17.3'
- name: Prebuild docs
run: yarn prebuild:docs
- name: Build OpenAPI 2.0 Spec
run: yarn build:docs
# Install dependencies globally to bypass installing all frontend deps
- name: Install swagger2openapi and swagger-cli
run: yarn global add swagger2openapi @apidevtools/swagger-cli
# OpenAPI2.0 does not support multiple body params (which we utilise in some of our handlers).
# OAS3.0 however does support multiple body params - hence its best to convert the generated OAS 2.0
# to OAS 3.0 and validate the output of generated OAS 3.0 instead.
- name: Convert OpenAPI 2.0 to OpenAPI 3.0 and validate spec
run: yarn validate:docs

4
.gitignore vendored
View File

@@ -11,8 +11,10 @@ storybook-static
*.DS_Store
.eslintcache
__debug_bin
__debug_bin*
api/docs
.idea
.env
go.work.sum

1
.godir
View File

@@ -1 +0,0 @@
portainer

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
- copyloopvar
- intrange
- perfsprint
linters-settings:
depguard:
rules:
main:
deny:
- pkg: 'encoding/json'
desc: 'use github.com/segmentio/encoding/json'
- 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

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd $(dirname -- "$0") && yarn lint-staged

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

@@ -1,55 +0,0 @@
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
stories: ['../app/**/*.stories.mdx', '../app/**/*.stories.@(ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
{
name: '@storybook/addon-postcss',
options: {
cssLoaderOptions: {
importLoaders: 1,
modules: {
localIdentName: '[path][name]__[local]',
auto: true,
exportLocalsConvention: 'camelCaseOnly',
},
},
postcssLoaderOptions: {
implementation: require('postcss'),
},
},
},
],
webpackFinal: (config) => {
config.resolve.plugins = [
...(config.resolve.plugins || []),
new TsconfigPathsPlugin({
extensions: config.resolve.extensions,
}),
];
const svgRule = config.module.rules.find((rule) => rule.test && typeof rule.test.test === 'function' && rule.test.test('.svg'));
svgRule.test = new RegExp(svgRule.test.source.replace('svg|', ''));
config.module.rules.unshift({
test: /\.svg$/i,
type: 'asset',
resourceQuery: { not: [/c/] }, // exclude react component if *.svg?url
});
config.module.rules.unshift({
test: /\.svg$/i,
issuer: /\.(js|ts)(x)?$/,
resourceQuery: /c/, // *.svg?c
use: [{ loader: '@svgr/webpack', options: { icon: true } }],
});
return config;
},
core: {
builder: 'webpack5',
},
staticDirs: ['./public'],
};

93
.storybook/main.ts Normal file
View File

@@ -0,0 +1,93 @@
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: [
'@storybook/addon-links',
'@storybook/addon-essentials',
{
name: '@storybook/addon-styling',
options: {
cssLoaderOptions: {
importLoaders: 1,
modules: {
localIdentName: '[path][name]__[local]',
auto: true,
exportLocalsConvention: 'camelCaseOnly',
},
},
postCss: {
implementation: postcss,
},
},
},
],
webpackFinal: (config) => {
const rules = config?.module?.rules || [];
const imageRule = rules.find((rule) => {
const test = (rule as { test: RegExp }).test;
if (!test) {
return false;
}
return test.test('.svg');
}) as { [key: string]: any };
imageRule.exclude = /\.svg$/;
rules.unshift({
test: /\.svg$/i,
type: 'asset',
resourceQuery: {
not: [/c/],
}, // exclude react component if *.svg?url
});
rules.unshift({
test: /\.svg$/i,
issuer: /\.(js|ts)(x)?$/,
resourceQuery: /c/,
// *.svg?c
use: [
{
loader: '@svgr/webpack',
options: {
icon: true,
},
},
],
});
return {
...config,
resolve: {
...config.resolve,
plugins: [
...(config.resolve?.plugins || []),
new TsconfigPathsPlugin({
extensions: config.resolve?.extensions,
}),
],
},
module: {
...config.module,
rules,
},
} satisfies Configuration;
},
staticDirs: ['./public'],
typescript: {
reactDocgen: 'react-docgen-typescript',
},
framework: {
name: '@storybook/react-webpack5',
options: {},
},
};
export default config;

View File

@@ -1,48 +0,0 @@
import '../app/assets/css';
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
import { initialize as initMSW, mswDecorator } from 'msw-storybook-addon';
import { handlers } from '@/setup-tests/server-handlers';
import { QueryClient, QueryClientProvider } from 'react-query';
// Initialize MSW
initMSW({
onUnhandledRequest: ({ method, url }) => {
if (url.pathname.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
`);
}
},
});
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
msw: {
handlers,
},
};
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
export const decorators = [
(Story) => (
<QueryClientProvider client={testQueryClient}>
<UIRouter plugins={[pushStateLocationPlugin]}>
<Story />
</UIRouter>
</QueryClientProvider>
),
mswDecorator,
];

51
.storybook/preview.tsx Normal file
View File

@@ -0,0 +1,51 @@
import '../app/assets/css';
import React from 'react';
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
import { initialize as initMSW, mswLoader } from 'msw-storybook-addon';
import { handlers } from '../app/setup-tests/server-handlers';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
initMSW(
{
onUnhandledRequest: ({ 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].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
msw: {
handlers,
},
};
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
export const decorators = [
(Story) => (
<QueryClientProvider client={testQueryClient}>
<UIRouter plugins={[pushStateLocationPlugin]}>
<Story />
</UIRouter>
</QueryClientProvider>
),
];
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

@@ -1,19 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/api/cmd/portainer",
"cwd": "${workspaceRoot}",
"env": {},
"showLog": true,
"args": ["--data", "${env:HOME}/portainer-data", "--assets", "${workspaceRoot}/dist"]
}
]
}

View File

@@ -1,182 +0,0 @@
{
// Place your portainer workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Component": {
"scope": "javascript",
"prefix": "mycomponent",
"description": "Dummy Angularjs Component",
"body": [
"import angular from 'angular';",
"import controller from './${TM_FILENAME_BASE}Controller'",
"",
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').component('$TM_FILENAME_BASE', {",
" templateUrl: './$TM_FILENAME_BASE.html',",
" controller,",
"});",
""
]
},
"Controller": {
"scope": "javascript",
"prefix": "mycontroller",
"body": [
"class ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/} {",
"\t/* @ngInject */",
"\tconstructor($0) {",
"\t}",
"}",
"",
"export default ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/};"
],
"description": "Dummy ES6+ controller"
},
"Service": {
"scope": "javascript",
"prefix": "myservice",
"description": "Dummy ES6+ service",
"body": [
"import angular from 'angular';",
"import PortainerError from 'Portainer/error';",
"",
"class $1 {",
" /* @ngInject */",
" constructor(\\$async, $0) {",
" this.\\$async = \\$async;",
"",
" this.getAsync = this.getAsync.bind(this);",
" this.getAllAsync = this.getAllAsync.bind(this);",
" this.createAsync = this.createAsync.bind(this);",
" this.updateAsync = this.updateAsync.bind(this);",
" this.deleteAsync = this.deleteAsync.bind(this);",
" }",
"",
" /**",
" * GET",
" */",
" async getAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" async getAllAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" get() {",
" if () {",
" return this.\\$async(this.getAsync);",
" }",
" return this.\\$async(this.getAllAsync);",
" }",
"",
" /**",
" * CREATE",
" */",
" async createAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" create() {",
" return this.\\$async(this.createAsync);",
" }",
"",
" /**",
" * UPDATE",
" */",
" async updateAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" update() {",
" return this.\\$async(this.updateAsync);",
" }",
"",
" /**",
" * DELETE",
" */",
" async deleteAsync() {",
" try {",
"",
" } catch (err) {",
" throw new PortainerError('', err);",
" }",
" }",
"",
" delete() {",
" return this.\\$async(this.deleteAsync);",
" }",
"}",
"",
"export default $1;",
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').service('$1', $1);"
]
},
"swagger-api-doc": {
"prefix": "swapi",
"scope": "go",
"description": "Snippet for a api doc",
"body": [
"// @id ",
"// @summary ",
"// @description ",
"// @description **Access policy**: ",
"// @tags ",
"// @security ApiKeyAuth",
"// @security jwt",
"// @accept json",
"// @produce json",
"// @param id path int true \"identifier\"",
"// @param body body Object true \"details\"",
"// @success 200 {object} portainer. \"Success\"",
"// @success 204 \"Success\"",
"// @failure 400 \"Invalid request\"",
"// @failure 403 \"Permission denied\"",
"// @failure 404 \" not found\"",
"// @failure 500 \"Server error\"",
"// @router /{id} [get]"
]
},
"analytics": {
"prefix": "nlt",
"body": ["analytics-on", "analytics-category=\"$1\"", "analytics-event=\"$2\""],
"description": "analytics"
},
"analytics-if": {
"prefix": "nltf",
"body": ["analytics-if=\"$1\""],
"description": "analytics"
},
"analytics-metadata": {
"prefix": "nltm",
"body": "analytics-properties=\"{ metadata: { $1 } }\""
}
}

View File

@@ -1,8 +0,0 @@
{
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast", "-E", "exportloopref"],
"gopls": {
"build.expandWorkspaceToModule": false
},
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"]
}

View File

@@ -79,25 +79,33 @@ The feature request process is similar to the bug report process but has an extr
Ensure you have Docker, Node.js, yarn, and Golang installed in the correct versions.
Install dependencies with yarn:
Install dependencies:
```sh
$ yarn
$ make deps
```
Then build and run the project in a Docker container:
```sh
$ yarn start
$ make dev
```
Portainer can now be accessed at <https://localhost:9443>.
Portainer server can now be accessed at <https://localhost:9443>. and UI dev server runs on <http://localhost:8999>.
if you want to build the project you can run:
```sh
make build-all
```
For additional make commands, run `make help`.
Find more detailed steps at <https://docs.portainer.io/contribute/build>.
### Build customisation
### Build customization
You can customise the following settings:
You can customize the following settings:
- `PORTAINER_DATA`: The host dir or volume name used by portainer (default is `/tmp/portainer`, which won't persist over reboots).
- `PORTAINER_PROJECT`: The root dir of the repository - `${portainerRoot}/dist/` is imported into the container to get the build artifacts and external tools (defaults to `your current dir`).

130
Makefile Normal file
View File

@@ -0,0 +1,130 @@
# See: https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63
# For a list of valid GOOS and GOARCH values
# Note: these can be overriden on the command line e.g. `make PLATFORM=<platform> ARCH=<arch>`
PLATFORM=$(shell go env GOOS)
ARCH=$(shell go env GOARCH)
# build target, can be one of "production", "testing", "development"
ENV=development
WEBPACK_CONFIG=webpack/webpack.$(ENV).js
TAG=local
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
.DEFAULT_GOAL := help
##@ Building
.PHONY: all init-dist build-storybook build build-client build-server build-image devops
init-dist:
@mkdir -p dist
all: tidy deps build-server build-client ## Build the client, server and download external dependancies (doesn't build an image)
build-all: all ## Alias for the 'all' target (used by CI)
build-client: init-dist ## Build the client
export NODE_ENV=$(ENV) && yarn build --config $(WEBPACK_CONFIG)
build-server: init-dist ## Build the server binary
./build/build_binary.sh "$(PLATFORM)" "$(ARCH)"
build-image: build-all ## Build the Portainer image locally
docker buildx build --load -t portainerci/portainer-ce:$(TAG) -f build/linux/Dockerfile .
build-storybook: ## Build and serve the storybook files
yarn storybook:build
devops: clean deps build-client ## Build the everything target specifically for CI
echo "Building the devops binary..."
@./build/build_binary_azuredevops.sh "$(PLATFORM)" "$(ARCH)"
##@ Build dependencies
.PHONY: deps server-deps client-deps tidy
deps: server-deps client-deps ## Download all client and server build dependancies
server-deps: init-dist ## Download dependant server binaries
@./build/download_binaries.sh $(PLATFORM) $(ARCH)
client-deps: ## Install client dependencies
yarn
tidy: ## Tidy up the go.mod file
@go mod tidy
##@ Cleanup
.PHONY: clean
clean: ## Remove all build and download artifacts
@echo "Clearing the dist directory..."
@rm -rf dist/*
##@ Testing
.PHONY: test test-client test-server
test: test-server test-client ## Run all tests
test-client: ## Run client tests
yarn test $(ARGS) --coverage
test-server: ## Run server tests
$(GOTESTSUM) --format pkgname-and-test-fails --format-hide-empty-pkg --hide-summary skipped -- -cover -covermode=atomic -coverprofile=coverage.out ./...
##@ Dev
.PHONY: dev dev-client dev-server
dev: ## Run both the client and server in development mode
make dev-server
make dev-client
dev-client: ## Run the client in development mode
yarn dev
dev-server: build-server ## Run the server in development mode
@./dev/run_container.sh
dev-server-podman: build-server ## Run the server in development mode
@./dev/run_container_podman.sh
##@ Format
.PHONY: format format-client format-server
format: format-client format-server ## Format all code
format-client: ## Format client code
yarn format
format-server: ## Format server code
go fmt ./...
##@ Lint
.PHONY: lint lint-client lint-server
lint: lint-client lint-server ## Lint all code
lint-client: ## Lint client code
yarn lint
lint-server: ## Lint server code
golangci-lint run --timeout=10m -c .golangci.yaml
##@ Extension
.PHONY: dev-extension
dev-extension: build-server build-client ## Run the extension in development mode
make local -f build/docker-extension/Makefile
##@ 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 -p pascalcase --markdownFiles ./
docs-validate: docs-build ## Validate docs
yarn swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml
yarn swagger-cli validate dist/docs/openapi.yaml
##@ Helpers
.PHONY: help
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

View File

@@ -9,7 +9,7 @@ Portainer consists of a single container that can run on any cluster. It can be
**Portainer Business Edition** builds on the open-source base and includes a range of advanced features and functions (like RBAC and Support) that are specific to the needs of business users.
- [Compare Portainer CE and Compare Portainer BE](https://portainer.io/products)
- [Take5 get 5 free nodes of Portainer Business for as long as you want them](https://portainer.io/pricing/take5)
- [Take3 get 3 free nodes of Portainer Business for as long as you want them](https://www.portainer.io/take-3)
- [Portainer BE install guide](https://install.portainer.io)
## Latest Version
@@ -21,8 +21,8 @@ Portainer CE is updated regularly. We aim to do an update release every couple o
## Getting started
- [Deploy Portainer](https://docs.portainer.io/start/install)
- [Documentation](https://documentation.portainer.io)
- [Contribute to the project](https://documentation.portainer.io/contributing/instructions/)
- [Documentation](https://docs.portainer.io)
- [Contribute to the project](https://docs.portainer.io/contribute/contribute)
## Features & Functions
@@ -30,23 +30,22 @@ View [this](https://www.portainer.io/products) table to see all of the Portainer
- [Portainer CE for Docker / Docker Swarm](https://www.portainer.io/solutions/docker)
- [Portainer CE for Kubernetes](https://www.portainer.io/solutions/kubernetes-ui)
- [Portainer CE for Azure ACI](https://www.portainer.io/solutions/serverless-containers)
## Getting help
Portainer CE is an open source project and is supported by the community. You can buy a supported version of Portainer at portainer.io
Learn more about Portainer's community support channels [here.](https://www.portainer.io/community_help)
Learn more about Portainer's community support channels [here.](https://www.portainer.io/get-support-for-portainer)
- Issues: https://github.com/portainer/portainer/issues
- Slack (chat): [https://portainer.io/slack](https://portainer.io/slack)
You can join the Portainer Community by visiting community.portainer.io. This will give you advance notice of events, content and other related Portainer content.
You can join the Portainer Community by visiting [https://www.portainer.io/join-our-community](https://www.portainer.io/join-our-community). This will give you advance notice of events, content and other related Portainer content.
## Reporting bugs and contributing
- Want to report a bug or request a feature? Please open [an issue](https://github.com/portainer/portainer/issues/new).
- Want to help us build **_portainer_**? Follow our [contribution guidelines](https://documentation.portainer.io/contributing/instructions/) to build it locally and make a pull request.
- Want to help us build **_portainer_**? Follow our [contribution guidelines](https://docs.portainer.io/contribute/contribute) to build it locally and make a pull request.
## Security
@@ -60,7 +59,7 @@ If you are a developer, and our code in this repo makes sense to you, we would l
**To make sure we focus our development effort in the right places we need to know which features get used most often. To give us this information we use [Matomo Analytics](https://matomo.org/), which is hosted in Germany and is fully GDPR compliant.**
When Portainer first starts, you are given the option to DISABLE analytics. If you **don't** choose to disable it, we collect anonymous usage as per [our privacy policy](https://www.portainer.io/documentation/in-app-analytics-and-privacy-policy/). **Please note**, there is no personally identifiable information sent or stored at any time and we only use the data to help us improve Portainer.
When Portainer first starts, you are given the option to DISABLE analytics. If you **don't** choose to disable it, we collect anonymous usage as per [our privacy policy](https://www.portainer.io/privacy-policy). **Please note**, there is no personally identifiable information sent or stored at any time and we only use the data to help us improve Portainer.
## Limitations

View File

@@ -1,26 +0,0 @@
linters:
# Disable all linters.
disable-all: true
enable:
- depguard
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"
# Create additional guards that follow the same configuration pattern.
# Results from all guards are aggregated together.
# additional-guards:
# - list-type: allowlist
# include-go-root: false
# packages:
# - github.com/sirupsen/logrus
# # Specify rules by which the linter ignores certain files for consideration.
# ignore-file-rules:
# - "!**/*_test.go"

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

@@ -4,12 +4,13 @@ import (
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/url"
"github.com/portainer/portainer/api/url"
)
// GetAgentVersionAndPlatform returns the agent version and platform
@@ -42,7 +43,9 @@ func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (port
if err != nil {
return 0, "", err
}
defer resp.Body.Close()
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
return 0, "", fmt.Errorf("Failed request with status %d", resp.StatusCode)

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

@@ -33,17 +33,19 @@ func Test_generateRandomKey(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := generateRandomKey(tt.wantLenth)
got := GenerateRandomKey(tt.wantLenth)
is.Equal(tt.wantLenth, len(got))
})
}
t.Run("Generated keys are unique", func(t *testing.T) {
keys := make(map[string]bool)
for i := 0; i < 100; i++ {
key := generateRandomKey(8)
for range 100 {
key := GenerateRandomKey(8)
_, ok := keys[string(key)]
is.False(ok)
keys[string(key)] = true
}
})

View File

@@ -1,69 +1,79 @@
package apikey
import (
lru "github.com/hashicorp/golang-lru"
portainer "github.com/portainer/portainer/api"
lru "github.com/hashicorp/golang-lru"
)
const defaultAPIKeyCacheSize = 1024
const DefaultAPIKeyCacheSize = 1024
// entry is a tuple containing the user and API key associated to an API key digest
type entry struct {
user portainer.User
type entry[T any] struct {
user T
apiKey portainer.APIKey
}
// apiKeyCache is a concurrency-safe, in-memory cache which primarily exists for to reduce database roundtrips.
type UserCompareFn[T any] func(T, portainer.UserID) bool
// ApiKeyCache is a concurrency-safe, in-memory cache which primarily exists for to reduce database roundtrips.
// We store the api-key digest (keys) and the associated user and key-data (values) in the cache.
// This is required because HTTP requests will contain only the api-key digest in the x-api-key request header;
// digest value must be mapped to a portainer user (and respective key data) for validation.
// This cache is used to avoid multiple database queries to retrieve these user/key associated to the digest.
type apiKeyCache struct {
type ApiKeyCache[T any] struct {
// cache type [string]entry cache (key: string(digest), value: user/key entry)
// note: []byte keys are not supported by golang-lru Cache
cache *lru.Cache
cache *lru.Cache
userCmpFn UserCompareFn[T]
}
// NewAPIKeyCache creates a new cache for API keys
func NewAPIKeyCache(cacheSize int) *apiKeyCache {
func NewAPIKeyCache[T any](cacheSize int, userCompareFn UserCompareFn[T]) *ApiKeyCache[T] {
cache, _ := lru.New(cacheSize)
return &apiKeyCache{cache: cache}
return &ApiKeyCache[T]{cache: cache, userCmpFn: userCompareFn}
}
// 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[T]) Get(digest string) (T, portainer.APIKey, bool) {
val, ok := c.cache.Get(digest)
if !ok {
return portainer.User{}, portainer.APIKey{}, false
var t T
return t, portainer.APIKey{}, false
}
tuple := val.(entry)
tuple := val.(entry[T])
return tuple.user, tuple.apiKey, true
}
// 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[T]) Set(digest string, user T, apiKey portainer.APIKey) {
c.cache.Add(digest, entry[T]{
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[T]) 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 {
func (c *ApiKeyCache[T]) InvalidateUserKeyCache(userId portainer.UserID) bool {
present := false
for _, k := range c.cache.Keys() {
user, _, _ := c.Get([]byte(k.(string)))
if user.ID == userId {
user, _, _ := c.Get(k.(string))
if c.userCmpFn(user, userId) {
present = c.cache.Remove(k)
}
}
return present
}

View File

@@ -10,32 +10,32 @@ import (
func Test_apiKeyCacheGet(t *testing.T) {
is := assert.New(t)
keyCache := NewAPIKeyCache(10)
keyCache := NewAPIKeyCache(10, compareUser)
// pre-populate cache
keyCache.cache.Add(string("foo"), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string(""), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string(""), entry[portainer.User]{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,
},
}
for _, test := range tests {
t.Run(string(test.digest), func(t *testing.T) {
t.Run(test.digest, func(t *testing.T) {
_, _, found := keyCache.Get(test.digest)
is.Equal(test.found, found)
})
@@ -45,43 +45,43 @@ func Test_apiKeyCacheGet(t *testing.T) {
func Test_apiKeyCacheSet(t *testing.T) {
is := assert.New(t)
keyCache := NewAPIKeyCache(10)
keyCache := NewAPIKeyCache(10, compareUser)
// 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)
tuple := val.(entry)
tuple := val.(entry[portainer.User])
is.Equal(portainer.User{ID: 2}, tuple.user)
val, ok = keyCache.cache.Get(string("foo"))
is.True(ok)
tuple = val.(entry)
tuple = val.(entry[portainer.User])
is.Equal(portainer.User{ID: 3}, tuple.user)
}
func Test_apiKeyCacheDelete(t *testing.T) {
is := assert.New(t)
keyCache := NewAPIKeyCache(10)
keyCache := NewAPIKeyCache(10, compareUser)
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.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
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)
})
}
@@ -128,19 +128,19 @@ func Test_apiKeyCacheLRU(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
keyCache := NewAPIKeyCache(test.cacheLen)
keyCache := NewAPIKeyCache(test.cacheLen, compareUser)
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)
}
})
@@ -150,10 +150,10 @@ func Test_apiKeyCacheLRU(t *testing.T) {
func Test_apiKeyCacheInvalidateUserKeyCache(t *testing.T) {
is := assert.New(t)
keyCache := NewAPIKeyCache(10)
keyCache := NewAPIKeyCache(10, compareUser)
t.Run("Removes users keys from cache", func(t *testing.T) {
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
ok := keyCache.InvalidateUserKeyCache(1)
is.True(ok)
@@ -163,8 +163,8 @@ func Test_apiKeyCacheInvalidateUserKeyCache(t *testing.T) {
})
t.Run("Does not affect other keys", func(t *testing.T) {
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("bar"), entry{user: portainer.User{ID: 2}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
keyCache.cache.Add(string("bar"), entry[portainer.User]{user: portainer.User{ID: 2}, apiKey: portainer.APIKey{}})
ok := keyCache.InvalidateUserKeyCache(1)
is.True(ok)

View File

@@ -1,9 +1,11 @@
package apikey
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"time"
portainer "github.com/portainer/portainer/api"
@@ -19,30 +21,45 @@ var ErrInvalidAPIKey = errors.New("Invalid API key")
type apiKeyService struct {
apiKeyRepository dataservices.APIKeyRepository
userRepository dataservices.UserService
cache *apiKeyCache
cache *ApiKeyCache[portainer.User]
}
// 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
}
func compareUser(u portainer.User, id portainer.UserID) bool {
return u.ID == id
}
func NewAPIKeyService(apiKeyRepository dataservices.APIKeyRepository, userRepository dataservices.UserService) *apiKeyService {
return &apiKeyService{
apiKeyRepository: apiKeyRepository,
userRepository: userRepository,
cache: NewAPIKeyCache(defaultAPIKeyCacheSize),
cache: NewAPIKeyCache(DefaultAPIKeyCacheSize, compareUser),
}
}
// 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 := GenerateRandomKey(32)
encodedRawAPIKey := base64.StdEncoding.EncodeToString(randKey)
prefixedAPIKey := portainerAPIKeyPrefix + encodedRawAPIKey
hashDigest := a.HashRaw(prefixedAPIKey)
apiKey := &portainer.APIKey{
@@ -53,8 +70,7 @@ func (a *apiKeyService) GenerateApiKey(user portainer.User, description string)
Digest: hashDigest,
}
err := a.apiKeyRepository.CreateAPIKey(apiKey)
if err != nil {
if err := a.apiKeyRepository.Create(apiKey); err != nil {
return "", nil, errors.Wrap(err, "Unable to create API key")
}
@@ -66,7 +82,7 @@ func (a *apiKeyService) GenerateApiKey(user portainer.User, description string)
// GetAPIKey returns an API key by its ID.
func (a *apiKeyService) GetAPIKey(apiKeyID portainer.APIKeyID) (*portainer.APIKey, error) {
return a.apiKeyRepository.GetAPIKey(apiKeyID)
return a.apiKeyRepository.Read(apiKeyID)
}
// GetAPIKeys returns all the API keys associated to a user.
@@ -76,8 +92,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) {
// get api key from cache if possible
func (a *apiKeyService) GetDigestUserAndKey(digest string) (portainer.User, portainer.APIKey, error) {
cachedUser, cachedKey, ok := a.cache.Get(digest)
if ok {
return cachedUser, cachedKey, nil
@@ -88,7 +103,7 @@ func (a *apiKeyService) GetDigestUserAndKey(digest []byte) (portainer.User, port
return portainer.User{}, portainer.APIKey{}, errors.Wrap(err, "Unable to retrieve API key")
}
user, err := a.userRepository.User(apiKey.UserID)
user, err := a.userRepository.Read(apiKey.UserID)
if err != nil {
return portainer.User{}, portainer.APIKey{}, errors.Wrap(err, "Unable to retrieve digest user")
}
@@ -105,21 +120,22 @@ func (a *apiKeyService) UpdateAPIKey(apiKey *portainer.APIKey) error {
if err != nil {
return errors.Wrap(err, "Unable to retrieve API key")
}
a.cache.Set(apiKey.Digest, user, *apiKey)
return a.apiKeyRepository.UpdateAPIKey(apiKey)
return a.apiKeyRepository.Update(apiKey.ID, apiKey)
}
// DeleteAPIKey deletes an API key and removes the digest/api-key entry from the cache.
func (a *apiKeyService) DeleteAPIKey(apiKeyID portainer.APIKeyID) error {
// get api-key digest to remove from cache
apiKey, err := a.apiKeyRepository.GetAPIKey(apiKeyID)
apiKey, err := a.apiKeyRepository.Read(apiKeyID)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Unable to retrieve API key: %d", apiKeyID))
}
// delete the user/api-key from cache
a.cache.Delete(apiKey.Digest)
return a.apiKeyRepository.DeleteAPIKey(apiKeyID)
return a.apiKeyRepository.Delete(apiKeyID)
}
func (a *apiKeyService) InvalidateUserKeyCache(userId portainer.UserID) bool {

View File

@@ -2,6 +2,7 @@ package apikey
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
"testing"
@@ -22,8 +23,7 @@ func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
func Test_GenerateApiKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -69,15 +69,14 @@ func Test_GenerateApiKey(t *testing.T) {
generatedDigest := sha256.Sum256([]byte(rawKey))
is.Equal(apiKey.Digest, generatedDigest[:])
is.Equal(apiKey.Digest, base64.StdEncoding.EncodeToString(generatedDigest[:]))
})
}
func Test_GetAPIKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -96,8 +95,7 @@ func Test_GetAPIKey(t *testing.T) {
func Test_GetAPIKeys(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -117,8 +115,7 @@ func Test_GetAPIKeys(t *testing.T) {
func Test_GetDigestUserAndKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -153,8 +150,7 @@ func Test_GetDigestUserAndKey(t *testing.T) {
func Test_UpdateAPIKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -199,8 +195,7 @@ func Test_UpdateAPIKey(t *testing.T) {
func Test_DeleteAPIKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -240,8 +235,7 @@ func Test_DeleteAPIKey(t *testing.T) {
func Test_InvalidateUserKeyCache(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
defer teardown()
_, store := datastore.MustNewTestStore(t, true, true)
service := NewAPIKeyService(store.APIKeyRepository(), store.User())

View File

@@ -3,6 +3,7 @@ package archive
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
@@ -14,7 +15,7 @@ import (
// abosolutePath should be an absolute path to a directory.
// Archive name will be <directoryName>.tar.gz and will be placed next to the directory.
func TarGzDir(absolutePath string) (string, error) {
targzPath := filepath.Join(absolutePath, fmt.Sprintf("%s.tar.gz", filepath.Base(absolutePath)))
targzPath := filepath.Join(absolutePath, filepath.Base(absolutePath)+".tar.gz")
outFile, err := os.Create(targzPath)
if err != nil {
return "", err
@@ -47,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
}
@@ -67,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
}
@@ -84,7 +93,7 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
for {
header, err := tarReader.Next()
if err == io.EOF {
if errors.Is(err, io.EOF) {
break
}
@@ -97,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)
@@ -109,7 +118,7 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
}
outFile.Close()
default:
return fmt.Errorf("Tar: uknown type: %v in %s",
return fmt.Errorf("tar: unknown type: %v in %s",
header.Typeflag,
header.Name)
}

View File

@@ -1,7 +1,6 @@
package archive
import (
"fmt"
"os"
"os/exec"
"path"
@@ -24,7 +23,7 @@ func listFiles(dir string) []string {
return items
}
func Test_shouldCreateArhive(t *testing.T) {
func Test_shouldCreateArchive(t *testing.T) {
tmpdir := t.TempDir()
content := []byte("content")
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
@@ -34,12 +33,11 @@ func Test_shouldCreateArhive(t *testing.T) {
gzPath, err := TarGzDir(tmpdir)
assert.Nil(t, err)
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
assert.Equal(t, filepath.Join(tmpdir, filepath.Base(tmpdir)+".tar.gz"), gzPath)
extractionDir := t.TempDir()
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
err = cmd.Run()
if err != nil {
if err := cmd.Run(); err != nil {
t.Fatal("Failed to extract archive: ", err)
}
extractedFiles := listFiles(extractionDir)
@@ -56,7 +54,7 @@ func Test_shouldCreateArhive(t *testing.T) {
wasExtracted("dir/.dotfile")
}
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
func Test_shouldCreateArchive2(t *testing.T) {
tmpdir := t.TempDir()
content := []byte("content")
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
@@ -66,12 +64,11 @@ func Test_shouldCreateArhiveXXXXX(t *testing.T) {
gzPath, err := TarGzDir(tmpdir)
assert.Nil(t, err)
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
assert.Equal(t, filepath.Join(tmpdir, filepath.Base(tmpdir)+".tar.gz"), gzPath)
extractionDir := t.TempDir()
r, _ := os.Open(gzPath)
ExtractTarGz(r, extractionDir)
if err != nil {
if err := ExtractTarGz(r, extractionDir); err != nil {
t.Fatal("Failed to extract archive: ", err)
}
extractedFiles := listFiles(extractionDir)

View File

@@ -3,7 +3,7 @@ package ecr
import (
"context"
"encoding/base64"
"fmt"
"errors"
"strings"
"time"
)
@@ -15,7 +15,7 @@ func (s *Service) GetEncodedAuthorizationToken() (token *string, expiry *time.Ti
}
if len(getAuthorizationTokenOutput.AuthorizationData) == 0 {
err = fmt.Errorf("AuthorizationData is empty")
err = errors.New("AuthorizationData is empty")
return
}
@@ -50,7 +50,7 @@ func (s *Service) ParseAuthorizationToken(token string) (username string, passwo
splitToken := strings.Split(token, ":")
if len(splitToken) < 2 {
err = fmt.Errorf("invalid ECR authorization token")
err = errors.New("invalid ECR authorization token")
return
}

View File

@@ -17,10 +17,11 @@ import (
"github.com/rs/zerolog/log"
)
const rwxr__r__ os.FileMode = 0744
const rwxr__r__ os.FileMode = 0o744
var filesToBackup = []string{
"certs",
"chisel",
"compose",
"config.json",
"custom_templates",
@@ -34,35 +35,9 @@ var filesToBackup = []string{
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
unlock := gate.Lock()
defer unlock()
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
return "", errors.Wrap(err, "Failed to create backup dir")
}
{
// new export
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
err := datastore.Export(exportFilename)
if err != nil {
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
} else {
log.Debug().Str("filename", exportFilename).Msg("file exported")
}
}
if err := backupDb(backupDirPath, datastore); err != nil {
return "", errors.Wrap(err, "Failed to backup database")
}
for _, filename := range filesToBackup {
err := filesystem.CopyPath(filepath.Join(filestorePath, filename), backupDirPath)
if err != nil {
return "", errors.Wrap(err, "Failed to create backup file")
}
backupDirPath, err := backupDatabaseAndFilesystem(gate, datastore, filestorePath)
if err != nil {
return "", err
}
archivePath, err := archive.TarGzDir(backupDirPath)
@@ -80,15 +55,41 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
return archivePath, nil
}
func backupDatabaseAndFilesystem(gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
unlock := gate.Lock()
defer unlock()
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
return "", errors.Wrap(err, "Failed to create backup dir")
}
// new export
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
if err := datastore.Export(exportFilename); err != nil {
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
} else {
log.Debug().Str("filename", exportFilename).Msg("file exported")
}
if err := backupDb(backupDirPath, datastore); err != nil {
return "", errors.Wrap(err, "Failed to backup database")
}
for _, filename := range filesToBackup {
if err := filesystem.CopyPath(filepath.Join(filestorePath, filename), backupDirPath); err != nil {
return "", errors.Wrap(err, "Failed to create backup file")
}
}
return backupDirPath, nil
}
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()
dbFileName := datastore.Connection().GetDatabaseFileName()
_, err := datastore.Backup(filepath.Join(backupDirPath, dbFileName))
return err
}
func encrypt(path string, passphrase string) (string, error) {
@@ -98,7 +99,7 @@ func encrypt(path string, passphrase string) (string, error) {
}
defer in.Close()
outFileName := fmt.Sprintf("%s.encrypted", path)
outFileName := path + ".encrypted"
out, err := os.Create(outFileName)
if err != nil {
return "", err

View File

@@ -3,8 +3,10 @@ package backup
import (
"context"
"io"
"io/fs"
"os"
"path/filepath"
"regexp"
"time"
"github.com/pkg/errors"
@@ -24,7 +26,7 @@ func RestoreArchive(archive io.Reader, password string, filestorePath string, ga
if password != "" {
archive, err = decrypt(archive, password)
if err != nil {
return errors.Wrap(err, "failed to decrypt the archive")
return errors.Wrap(err, "failed to decrypt the archive. Please ensure the password is correct and try again")
}
}
@@ -43,6 +45,12 @@ func RestoreArchive(archive io.Reader, password string, filestorePath string, ga
return errors.Wrap(err, "Failed to stop db")
}
// At some point, backups were created containing a subdirectory, now we need to handle both
restorePath, err = getRestoreSourcePath(restorePath)
if err != nil {
return errors.Wrap(err, "failed to restore from backup. Portainer database missing from backup file")
}
if err = restoreFiles(restorePath, filestorePath); err != nil {
return errors.Wrap(err, "failed to restore the system state")
}
@@ -59,6 +67,26 @@ func extractArchive(r io.Reader, destinationDirPath string) error {
return archive.ExtractTarGz(r, destinationDirPath)
}
func getRestoreSourcePath(dir string) (string, error) {
// find portainer.db or portainer.edb file. Return the parent directory
var portainerdbRegex = regexp.MustCompile(`^portainer.e?db$`)
backupDirPath := dir
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if portainerdbRegex.MatchString(d.Name()) {
backupDirPath = filepath.Dir(path)
return filepath.SkipDir
}
return nil
})
return backupDirPath, err
}
func restoreFiles(srcDir string, destinationDir string) error {
for _, filename := range filesToRestore {
err := filesystem.CopyPath(filepath.Join(srcDir, filename), destinationDir)

View File

@@ -1,9 +0,0 @@
package build
// Variables to be set during the build time
var BuildNumber string
var ImageTag string
var NodejsVersion string
var YarnVersion string
var WebpackVersion string
var GoVersion string

View File

@@ -0,0 +1,61 @@
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"math/big"
chshare "github.com/jpillora/chisel/share"
)
var one = new(big.Int).SetInt64(1)
// GenerateGo119CompatibleKey This function is basically copied from chshare.GenerateKey.
func GenerateGo119CompatibleKey(seed string) ([]byte, error) {
r := chshare.NewDetermRand([]byte(seed))
priv, err := ecdsaGenerateKey(elliptic.P256(), r)
if err != nil {
return nil, err
}
b, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("Unable to marshal ECDSA private key: %w", err)
}
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}), nil
}
// This function is copied from Go1.19
func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) {
params := c.Params()
// Note that for P-521 this will actually be 63 bits more than the order, as
// division rounds down, but the extra bit is inconsequential.
b := make([]byte, params.N.BitLen()/8+8)
_, err = io.ReadFull(rand, b)
if err != nil {
return
}
k = new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, one)
k.Mod(k, n)
k.Add(k, one)
return
}
// This function is copied from Go1.19
func ecdsaGenerateKey(c elliptic.Curve, rand io.Reader) (*ecdsa.PrivateKey, error) {
k, err := randFieldElement(c, rand)
if err != nil {
return nil, err
}
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}

View File

@@ -0,0 +1,37 @@
package crypto
import (
"reflect"
"testing"
)
func TestGenerateGo119CompatibleKey(t *testing.T) {
type args struct {
seed string
}
tests := []struct {
name string
args args
want []byte
wantErr bool
}{
{
name: "Generate Go 1.19 compatible private key with a given seed",
args: args{seed: "94qh17MCIk8BOkiI"},
want: []byte("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIHeohwk0Gy3RHVVViaHz7pz/HOiqA7fkv1FTM3mGgfT3oAoGCCqGSM49\nAwEHoUQDQgAEN7riX06xDsLNPuUmOvYFluNEakcFwZZRVvOcIYk/9VYnanDzW0Km\n8/BUUiKyJDuuGdS4fj9SlQ4iL8yBK01uKg==\n-----END EC PRIVATE KEY-----\n"),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GenerateGo119CompatibleKey(tt.args.seed)
if (err != nil) != tt.wantErr {
t.Errorf("GenerateGo119CompatibleKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GenerateGo119CompatibleKey()\ngot: Z %v\nwant: %v", got, tt.want)
}
})
}
}

View File

@@ -1,74 +0,0 @@
package chisel
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
)
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
func (service *Service) AddEdgeJob(endpoint *portainer.Endpoint, edgeJob *portainer.EdgeJob) {
if endpoint.Edge.AsyncMode {
return
}
service.mu.Lock()
tunnel := service.getTunnelDetails(endpoint.ID)
existingJobIndex := -1
for idx, existingJob := range tunnel.Jobs {
if existingJob.ID == edgeJob.ID {
existingJobIndex = idx
break
}
}
if existingJobIndex == -1 {
tunnel.Jobs = append(tunnel.Jobs, *edgeJob)
} else {
tunnel.Jobs[existingJobIndex] = *edgeJob
}
cache.Del(endpoint.ID)
service.mu.Unlock()
}
// RemoveEdgeJob will remove the specified Edge job from each tunnel it was registered with.
func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
service.mu.Lock()
for endpointID, tunnel := range service.tunnelDetailsMap {
n := 0
for _, edgeJob := range tunnel.Jobs {
if edgeJob.ID != edgeJobID {
tunnel.Jobs[n] = edgeJob
n++
}
}
tunnel.Jobs = tunnel.Jobs[:n]
cache.Del(endpointID)
}
service.mu.Unlock()
}
func (service *Service) RemoveEdgeJobFromEndpoint(endpointID portainer.EndpointID, edgeJobID portainer.EdgeJobID) {
service.mu.Lock()
tunnel := service.getTunnelDetails(endpointID)
n := 0
for _, edgeJob := range tunnel.Jobs {
if edgeJob.ID != edgeJobID {
tunnel.Jobs[n] = edgeJob
n++
}
}
tunnel.Jobs = tunnel.Jobs[:n]
cache.Del(endpointID)
service.mu.Unlock()
}

View File

@@ -3,6 +3,7 @@ package chisel
import (
"context"
"fmt"
"io"
"net/http"
"sync"
"time"
@@ -11,99 +12,134 @@ import (
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/proxy"
"github.com/dchest/uniuri"
chserver "github.com/jpillora/chisel/server"
"github.com/jpillora/chisel/share/ccrypto"
"github.com/rs/zerolog/log"
)
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.
// It is used to start a reverse tunnel server and to manage the connection status of each tunnel
// connected to the tunnel server.
type Service struct {
serverFingerprint string
serverPort string
tunnelDetailsMap map[portainer.EndpointID]*portainer.TunnelDetails
dataStore dataservices.DataStore
snapshotService portainer.SnapshotService
chiselServer *chserver.Server
shutdownCtx context.Context
ProxyManager *proxy.Manager
mu sync.Mutex
serverFingerprint string
serverPort string
activeTunnels map[portainer.EndpointID]*portainer.TunnelDetails
edgeJobs map[portainer.EndpointID][]portainer.EdgeJob
dataStore dataservices.DataStore
snapshotService portainer.SnapshotService
chiselServer *chserver.Server
shutdownCtx context.Context
ProxyManager *proxy.Manager
mu sync.RWMutex
fileService portainer.FileService
defaultCheckinInterval int
}
// NewService returns a pointer to a new instance of Service
func NewService(dataStore dataservices.DataStore, shutdownCtx context.Context) *Service {
func NewService(dataStore dataservices.DataStore, shutdownCtx context.Context, fileService portainer.FileService) *Service {
defaultCheckinInterval := portainer.DefaultEdgeAgentCheckinIntervalInSeconds
settings, err := dataStore.Settings().Settings()
if err == nil {
defaultCheckinInterval = settings.EdgeAgentCheckinInterval
} else {
log.Error().Err(err).Msg("unable to retrieve the settings from the database")
}
return &Service{
tunnelDetailsMap: make(map[portainer.EndpointID]*portainer.TunnelDetails),
dataStore: dataStore,
shutdownCtx: shutdownCtx,
activeTunnels: make(map[portainer.EndpointID]*portainer.TunnelDetails),
edgeJobs: make(map[portainer.EndpointID][]portainer.EdgeJob),
dataStore: dataStore,
shutdownCtx: shutdownCtx,
fileService: fileService,
defaultCheckinInterval: defaultCheckinInterval,
}
}
// pingAgent ping the given agent so that the agent can keep the tunnel alive
func (service *Service) pingAgent(endpointID portainer.EndpointID) error {
tunnel := service.GetTunnelDetails(endpointID)
requestURL := fmt.Sprintf("http://127.0.0.1:%d/ping", tunnel.Port)
endpoint, err := service.dataStore.Endpoint().Endpoint(endpointID)
if err != nil {
return err
}
tunnelAddr, err := service.TunnelAddr(endpoint)
if err != nil {
return err
}
requestURL := fmt.Sprintf("http://%s/ping", tunnelAddr)
req, err := http.NewRequest(http.MethodHead, requestURL, nil)
if err != nil {
return err
}
httpClient := &http.Client{
Timeout: 3 * time.Second,
Timeout: pingTimeout,
}
_, err = httpClient.Do(req)
return err
resp, err := httpClient.Do(req)
if err != nil {
return err
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
return nil
}
// KeepTunnelAlive keeps the tunnel of the given environment for maxAlive duration, or until ctx is done
func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx context.Context, maxAlive time.Duration) {
go func() {
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("max_alive_minutes", maxAlive.Minutes()).
Msg("start")
go service.keepTunnelAlive(endpointID, ctx, maxAlive)
}
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
pingTicker := time.NewTicker(tunnelCleanupInterval)
defer pingTicker.Stop()
func (service *Service) keepTunnelAlive(endpointID portainer.EndpointID, ctx context.Context, maxAlive time.Duration) {
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("max_alive_minutes", maxAlive.Minutes()).
Msg("KeepTunnelAlive: start")
for {
select {
case <-pingTicker.C:
service.SetTunnelStatusToActive(endpointID)
err := service.pingAgent(endpointID)
if err != nil {
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("ping agent")
}
case <-maxAliveTicker.C:
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("timeout_minutes", maxAlive.Minutes()).
Msg("tunnel keep alive timeout")
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
return
case <-ctx.Done():
err := ctx.Err()
pingTicker := time.NewTicker(tunnelCleanupInterval)
defer pingTicker.Stop()
for {
select {
case <-pingTicker.C:
service.UpdateLastActivity(endpointID)
if err := service.pingAgent(endpointID); err != nil {
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("tunnel stop")
return
Msg("KeepTunnelAlive: ping agent")
}
case <-maxAliveTicker.C:
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("timeout_minutes", maxAlive.Minutes()).
Msg("KeepTunnelAlive: tunnel keep alive timeout")
return
case <-ctx.Done():
err := ctx.Err()
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("KeepTunnelAlive: tunnel stop")
return
}
}()
}
}
// StartTunnelServer starts a tunnel server on the specified addr and port.
@@ -112,14 +148,14 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
// It starts the tunnel status verification process in the background.
// The snapshotter is used in the tunnel status verification process.
func (service *Service) StartTunnelServer(addr, port string, snapshotService portainer.SnapshotService) error {
keySeed, err := service.retrievePrivateKeySeed()
privateKeyFile, err := service.retrievePrivateKeyFile()
if err != nil {
return err
}
config := &chserver.Config{
Reverse: true,
KeySeed: keySeed,
KeyFile: privateKeyFile,
}
chiselServer, err := chserver.NewServer(config)
@@ -130,21 +166,21 @@ func (service *Service) StartTunnelServer(addr, port string, snapshotService por
service.serverFingerprint = chiselServer.GetFingerprint()
service.serverPort = port
err = chiselServer.Start(addr, port)
if err != nil {
if err := chiselServer.Start(addr, port); err != nil {
return err
}
service.chiselServer = chiselServer
// TODO: work-around Chisel default behavior.
// By default, Chisel will allow anyone to connect if no user exists.
username, password := generateRandomCredentials()
err = service.chiselServer.AddUser(username, password, "127.0.0.1")
if err != nil {
if err = service.chiselServer.AddUser(username, password, "127.0.0.1"); err != nil {
return err
}
service.snapshotService = snapshotService
go service.startTunnelVerificationLoop()
return nil
@@ -155,26 +191,43 @@ func (service *Service) StopTunnelServer() error {
return service.chiselServer.Close()
}
func (service *Service) retrievePrivateKeySeed() (string, error) {
var serverInfo *portainer.TunnelServerInfo
func (service *Service) retrievePrivateKeyFile() (string, error) {
privateKeyFile := service.fileService.GetDefaultChiselPrivateKeyPath()
serverInfo, err := service.dataStore.TunnelServer().Info()
if service.dataStore.IsErrObjectNotFound(err) {
keySeed := uniuri.NewLen(16)
if exists, _ := service.fileService.FileExists(privateKeyFile); exists {
log.Info().
Str("private-key", privateKeyFile).
Msg("found Chisel private key file on disk")
serverInfo = &portainer.TunnelServerInfo{
PrivateKeySeed: keySeed,
}
return privateKeyFile, nil
}
log.Debug().
Str("private-key", privateKeyFile).
Msg("chisel private key file does not exist")
privateKey, err := ccrypto.GenerateKey("")
if err != nil {
log.Error().
Err(err).
Msg("failed to generate chisel private key")
err := service.dataStore.TunnelServer().UpdateInfo(serverInfo)
if err != nil {
return "", err
}
} else if err != nil {
return "", err
}
return serverInfo.PrivateKeySeed, nil
if err = service.fileService.StoreChiselPrivateKey(privateKey); err != nil {
log.Error().
Err(err).
Msg("failed to save Chisel private key to disk")
return "", err
}
log.Info().
Str("private-key", privateKeyFile).
Msg("generated a new Chisel private key file")
return privateKeyFile, nil
}
func (service *Service) startTunnelVerificationLoop() {
@@ -201,63 +254,45 @@ func (service *Service) startTunnelVerificationLoop() {
}
}
// checkTunnels finds the first tunnel that has not had any activity recently
// and attempts to take a snapshot, then closes it and returns
func (service *Service) checkTunnels() {
tunnels := make(map[portainer.EndpointID]portainer.TunnelDetails)
service.mu.RLock()
service.mu.Lock()
for key, tunnel := range service.tunnelDetailsMap {
if tunnel.LastActivity.IsZero() || tunnel.Status == portainer.EdgeAgentIdle {
continue
}
if tunnel.Status == portainer.EdgeAgentManagementRequired && time.Since(tunnel.LastActivity) < requiredTimeout {
continue
}
if tunnel.Status == portainer.EdgeAgentActive && time.Since(tunnel.LastActivity) < activeTimeout {
continue
}
tunnels[key] = *tunnel
}
service.mu.Unlock()
for endpointID, tunnel := range tunnels {
for endpointID, tunnel := range service.activeTunnels {
elapsed := time.Since(tunnel.LastActivity)
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
Float64("status_time_seconds", elapsed.Seconds()).
Float64("last_activity_seconds", elapsed.Seconds()).
Msg("environment tunnel monitoring")
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed > requiredTimeout {
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
Float64("status_time_seconds", elapsed.Seconds()).
Float64("timeout_seconds", requiredTimeout.Seconds()).
Msg("REQUIRED state timeout exceeded")
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed < activeTimeout {
continue
}
if tunnel.Status == portainer.EdgeAgentActive && elapsed > activeTimeout {
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
Float64("status_time_seconds", elapsed.Seconds()).
Float64("timeout_seconds", activeTimeout.Seconds()).
Msg("ACTIVE state timeout exceeded")
tunnelPort := tunnel.Port
err := service.snapshotEnvironment(endpointID, tunnel.Port)
if err != nil {
log.Error().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("unable to snapshot Edge environment")
}
service.mu.RUnlock()
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("last_activity_seconds", elapsed.Seconds()).
Float64("timeout_seconds", activeTimeout.Seconds()).
Msg("last activity timeout exceeded")
if err := service.snapshotEnvironment(endpointID, tunnelPort); err != nil {
log.Error().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("unable to snapshot Edge environment")
}
service.SetTunnelStatusToIdle(portainer.EndpointID(endpointID))
service.close(endpointID)
return
}
service.mu.RUnlock()
}
func (service *Service) snapshotEnvironment(endpointID portainer.EndpointID, tunnelPort int) error {
@@ -266,14 +301,7 @@ func (service *Service) snapshotEnvironment(endpointID portainer.EndpointID, tun
return err
}
endpointURL := endpoint.URL
endpoint.URL = fmt.Sprintf("tcp://127.0.0.1:%d", tunnelPort)
err = service.snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
return err
}
endpoint.URL = endpointURL
return service.dataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
return service.snapshotService.SnapshotEndpoint(endpoint)
}

View File

@@ -0,0 +1,54 @@
package chisel
import (
"context"
"net"
"net/http"
"testing"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/stretchr/testify/require"
)
func TestPingAgentPanic(t *testing.T) {
endpoint := &portainer.Endpoint{
ID: 1,
EdgeID: "test-edge-id",
Type: portainer.EdgeAgentOnDockerEnvironment,
UserTrusted: true,
}
_, store := datastore.MustNewTestStore(t, true, true)
s := NewService(store, 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)
srv := &http.Server{Handler: mux}
errCh := make(chan error)
go func() {
errCh <- srv.Serve(ln)
}()
err = s.Open(endpoint)
require.NoError(t, err)
s.activeTunnels[endpoint.ID].Port = ln.Addr().(*net.TCPAddr).Port
require.Error(t, s.pingAgent(endpoint.ID))
require.NoError(t, srv.Shutdown(context.Background()))
require.ErrorIs(t, <-errCh, http.ErrServerClosed)
}

View File

@@ -5,14 +5,18 @@ import (
"errors"
"fmt"
"math/rand"
"net"
"strings"
"time"
"github.com/portainer/libcrypto"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/portainer/portainer/pkg/libcrypto"
"github.com/dchest/uniuri"
"github.com/rs/zerolog/log"
)
const (
@@ -20,18 +24,191 @@ const (
maxAvailablePort = 65535
)
var (
ErrNonEdgeEnv = errors.New("cannot open a tunnel for non-edge environments")
ErrAsyncEnv = errors.New("cannot open a tunnel for async edge environments")
ErrInvalidEnv = errors.New("cannot open a tunnel for an invalid environment")
)
// Open will mark the tunnel as REQUIRED so the agent opens it
func (s *Service) Open(endpoint *portainer.Endpoint) error {
if !endpointutils.IsEdgeEndpoint(endpoint) {
return ErrNonEdgeEnv
}
if endpoint.Edge.AsyncMode {
return ErrAsyncEnv
}
if endpoint.ID == 0 || endpoint.EdgeID == "" || !endpoint.UserTrusted {
return ErrInvalidEnv
}
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.activeTunnels[endpoint.ID]; ok {
return nil
}
defer cache.Del(endpoint.ID)
tun := &portainer.TunnelDetails{
Status: portainer.EdgeAgentManagementRequired,
Port: s.getUnusedPort(),
LastActivity: time.Now(),
}
username, password := generateRandomCredentials()
if s.chiselServer != nil {
authorizedRemote := fmt.Sprintf("^R:0.0.0.0:%d$", tun.Port)
if err := s.chiselServer.AddUser(username, password, authorizedRemote); err != nil {
return err
}
}
credentials, err := encryptCredentials(username, password, endpoint.EdgeID)
if err != nil {
return err
}
tun.Credentials = credentials
s.activeTunnels[endpoint.ID] = tun
return nil
}
// close removes the tunnel from the map so the agent will close it
func (s *Service) close(endpointID portainer.EndpointID) {
s.mu.Lock()
defer s.mu.Unlock()
tun, ok := s.activeTunnels[endpointID]
if !ok {
return
}
if len(tun.Credentials) > 0 && s.chiselServer != nil {
user, _, _ := strings.Cut(tun.Credentials, ":")
s.chiselServer.DeleteUser(user)
}
if s.ProxyManager != nil {
s.ProxyManager.DeleteEndpointProxy(endpointID)
}
delete(s.activeTunnels, endpointID)
cache.Del(endpointID)
}
// Config returns the tunnel details needed for the agent to connect
func (s *Service) Config(endpointID portainer.EndpointID) portainer.TunnelDetails {
s.mu.RLock()
defer s.mu.RUnlock()
if tun, ok := s.activeTunnels[endpointID]; ok {
return *tun
}
return portainer.TunnelDetails{Status: portainer.EdgeAgentIdle}
}
// TunnelAddr returns the address of the local tunnel, including the port, it
// will block until the tunnel is ready
func (s *Service) TunnelAddr(endpoint *portainer.Endpoint) (string, error) {
if err := s.Open(endpoint); err != nil {
return "", err
}
tun := s.Config(endpoint.ID)
checkinInterval := time.Duration(s.tryEffectiveCheckinInterval(endpoint)) * time.Second
for t0 := time.Now(); ; {
if time.Since(t0) > 2*checkinInterval {
s.close(endpoint.ID)
return "", errors.New("unable to open the tunnel")
}
// Check if the tunnel is established
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: tun.Port})
if err != nil {
time.Sleep(checkinInterval / 100)
continue
}
conn.Close()
break
}
s.UpdateLastActivity(endpoint.ID)
return fmt.Sprintf("127.0.0.1:%d", tun.Port), nil
}
// tryEffectiveCheckinInterval avoids a potential deadlock by returning a
// previous known value after a timeout
func (s *Service) tryEffectiveCheckinInterval(endpoint *portainer.Endpoint) int {
ch := make(chan int, 1)
go func() {
ch <- edge.EffectiveCheckinInterval(s.dataStore, endpoint)
}()
select {
case <-time.After(50 * time.Millisecond):
s.mu.RLock()
defer s.mu.RUnlock()
return s.defaultCheckinInterval
case i := <-ch:
s.mu.Lock()
s.defaultCheckinInterval = i
s.mu.Unlock()
return i
}
}
// UpdateLastActivity sets the current timestamp to avoid the tunnel timeout
func (s *Service) UpdateLastActivity(endpointID portainer.EndpointID) {
s.mu.Lock()
defer s.mu.Unlock()
if tun, ok := s.activeTunnels[endpointID]; ok {
tun.LastActivity = time.Now()
}
}
// NOTE: it needs to be called with the lock acquired
// getUnusedPort is used to generate an unused random port in the dynamic port range.
// Dynamic ports (also called private ports) are 49152 to 65535.
func (service *Service) getUnusedPort() int {
port := randomInt(minAvailablePort, maxAvailablePort)
for _, tunnel := range service.tunnelDetailsMap {
for _, tunnel := range service.activeTunnels {
if tunnel.Port == port {
return service.getUnusedPort()
}
}
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port})
if err == nil {
conn.Close()
log.Debug().
Int("port", port).
Msg("selected port is in use, trying a different one")
return service.getUnusedPort()
}
return port
}
@@ -39,146 +216,10 @@ func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// NOTE: it needs to be called with the lock acquired
func (service *Service) getTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
if tunnel, ok := service.tunnelDetailsMap[endpointID]; ok {
return tunnel
}
tunnel := &portainer.TunnelDetails{
Status: portainer.EdgeAgentIdle,
}
service.tunnelDetailsMap[endpointID] = tunnel
cache.Del(endpointID)
return tunnel
}
// GetTunnelDetails returns information about the tunnel associated to an environment(endpoint).
func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) portainer.TunnelDetails {
service.mu.Lock()
defer service.mu.Unlock()
return *service.getTunnelDetails(endpointID)
}
// GetActiveTunnel retrieves an active tunnel which allows communicating with edge agent
func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (portainer.TunnelDetails, error) {
if endpoint.Edge.AsyncMode {
return portainer.TunnelDetails{}, errors.New("cannot open tunnel on async endpoint")
}
tunnel := service.GetTunnelDetails(endpoint.ID)
if tunnel.Status == portainer.EdgeAgentActive {
// update the LastActivity
service.SetTunnelStatusToActive(endpoint.ID)
}
if tunnel.Status == portainer.EdgeAgentIdle || tunnel.Status == portainer.EdgeAgentManagementRequired {
err := service.SetTunnelStatusToRequired(endpoint.ID)
if err != nil {
return portainer.TunnelDetails{}, fmt.Errorf("failed opening tunnel to endpoint: %w", err)
}
if endpoint.EdgeCheckinInterval == 0 {
settings, err := service.dataStore.Settings().Settings()
if err != nil {
return portainer.TunnelDetails{}, fmt.Errorf("failed fetching settings from db: %w", err)
}
endpoint.EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
}
time.Sleep(2 * time.Duration(endpoint.EdgeCheckinInterval) * time.Second)
}
return service.GetTunnelDetails(endpoint.ID), nil
}
// SetTunnelStatusToActive update the status of the tunnel associated to the specified environment(endpoint).
// It sets the status to ACTIVE.
func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID) {
service.mu.Lock()
tunnel := service.getTunnelDetails(endpointID)
tunnel.Status = portainer.EdgeAgentActive
tunnel.Credentials = ""
tunnel.LastActivity = time.Now()
service.mu.Unlock()
cache.Del(endpointID)
}
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified environment(endpoint).
// It sets the status to IDLE.
// It removes any existing credentials associated to the tunnel.
func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
service.mu.Lock()
tunnel := service.getTunnelDetails(endpointID)
tunnel.Status = portainer.EdgeAgentIdle
tunnel.Port = 0
tunnel.LastActivity = time.Now()
credentials := tunnel.Credentials
if credentials != "" {
tunnel.Credentials = ""
service.chiselServer.DeleteUser(strings.Split(credentials, ":")[0])
}
service.ProxyManager.DeleteEndpointProxy(endpointID)
service.mu.Unlock()
cache.Del(endpointID)
}
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified environment(endpoint).
// It sets the status to REQUIRED.
// If no port is currently associated to the tunnel, it will associate a random unused port to the tunnel
// and generate temporary credentials that can be used to establish a reverse tunnel on that port.
// Credentials are encrypted using the Edge ID associated to the environment(endpoint).
func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
defer cache.Del(endpointID)
tunnel := service.getTunnelDetails(endpointID)
service.mu.Lock()
defer service.mu.Unlock()
if tunnel.Port == 0 {
endpoint, err := service.dataStore.Endpoint().Endpoint(endpointID)
if err != nil {
return err
}
tunnel.Status = portainer.EdgeAgentManagementRequired
tunnel.Port = service.getUnusedPort()
tunnel.LastActivity = time.Now()
username, password := generateRandomCredentials()
authorizedRemote := fmt.Sprintf("^R:0.0.0.0:%d$", tunnel.Port)
err = service.chiselServer.AddUser(username, password, authorizedRemote)
if err != nil {
return err
}
credentials, err := encryptCredentials(username, password, endpoint.EdgeID)
if err != nil {
return err
}
tunnel.Credentials = credentials
}
return nil
}
func generateRandomCredentials() (string, string) {
username := uniuri.NewLen(8)
password := uniuri.NewLen(8)
return username, password
}

View File

@@ -17,24 +17,20 @@ import (
type Service struct{}
var (
errInvalidEndpointProtocol = errors.New("Invalid environment protocol: Portainer only supports unix://, npipe:// or tcp://")
errSocketOrNamedPipeNotFound = errors.New("Unable to locate Unix socket or named pipe")
errInvalidSnapshotInterval = errors.New("Invalid snapshot interval")
errAdminPassExcludeAdminPassFile = errors.New("Cannot use --admin-password with --admin-password-file")
ErrInvalidEndpointProtocol = errors.New("Invalid environment protocol: Portainer only supports unix://, npipe:// or tcp://")
ErrSocketOrNamedPipeNotFound = errors.New("Unable to locate Unix socket or named pipe")
ErrInvalidSnapshotInterval = errors.New("Invalid snapshot interval")
ErrAdminPassExcludeAdminPassFile = errors.New("Cannot use --admin-password with --admin-password-file")
)
// ParseFlags parse the CLI flags and return a portainer.Flags struct
func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
kingpin.Version(version)
flags := &portainer.CLIFlags{
func CLIFlags() *portainer.CLIFlags {
return &portainer.CLIFlags{
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
AddrHTTPS: kingpin.Flag("bind-https", "Address and port to serve Portainer via https").Default(defaultHTTPSBindAddress).String(),
TunnelAddr: kingpin.Flag("tunnel-addr", "Address to serve the tunnel server").Default(defaultTunnelServerAddress).String(),
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
DemoEnvironment: kingpin.Flag("demo", "Demo environment").Bool(),
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
FeatureFlags: kingpin.Flag("feat", "List of feature flags").Strings(),
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
@@ -49,7 +45,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(),
@@ -62,8 +58,18 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
MaxBatchDelay: kingpin.Flag("max-batch-delay", "Maximum delay before a batch starts").Duration(),
SecretKeyName: kingpin.Flag("secret-key-name", "Secret key name for encryption and will be used as /run/secrets/<secret-key-name>.").Default(defaultSecretKeyName).String(),
LogLevel: kingpin.Flag("log-level", "Set the minimum logging level to show").Default("INFO").Enum("DEBUG", "INFO", "WARN", "ERROR"),
LogMode: kingpin.Flag("log-mode", "Set the logging output mode").Default("PRETTY").Enum("PRETTY", "JSON"),
LogMode: kingpin.Flag("log-mode", "Set the logging output mode").Default("PRETTY").Enum("NOCOLOR", "PRETTY", "JSON"),
KubectlShellImage: kingpin.Flag("kubectl-shell-image", "Kubectl shell image").Envar(portainer.KubectlShellImageEnvVar).Default(portainer.DefaultKubectlShellImage).String(),
PullLimitCheckDisabled: kingpin.Flag("pull-limit-check-disabled", "Pull limit check").Envar(portainer.PullLimitCheckDisabledEnvVar).Default(defaultPullLimitCheckDisabled).Bool(),
TrustedOrigins: kingpin.Flag("trusted-origins", "List of trusted origins for CSRF protection. Separate multiple origins with a comma.").Envar(portainer.TrustedOriginsEnvVar).String(),
}
}
// ParseFlags parse the CLI flags and return a portainer.Flags struct
func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
kingpin.Version(version)
flags := CLIFlags()
kingpin.Parse()
@@ -72,6 +78,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
if err != nil {
panic(err)
}
*flags.Assets = filepath.Join(filepath.Dir(ex), *flags.Assets)
}
@@ -80,21 +87,18 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
// ValidateFlags validates the values of the flags.
func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
displayDeprecationWarnings(flags)
err := validateEndpointURL(*flags.EndpointURL)
if err != nil {
if err := validateEndpointURL(*flags.EndpointURL); err != nil {
return err
}
err = validateSnapshotInterval(*flags.SnapshotInterval)
if err != nil {
if err := validateSnapshotInterval(*flags.SnapshotInterval); err != nil {
return err
}
if *flags.AdminPassword != "" && *flags.AdminPasswordFile != "" {
return errAdminPassExcludeAdminPassFile
return ErrAdminPassExcludeAdminPassFile
}
return nil
@@ -111,31 +115,38 @@ func displayDeprecationWarnings(flags *portainer.CLIFlags) {
}
func validateEndpointURL(endpointURL string) error {
if endpointURL != "" {
if !strings.HasPrefix(endpointURL, "unix://") && !strings.HasPrefix(endpointURL, "tcp://") && !strings.HasPrefix(endpointURL, "npipe://") {
return errInvalidEndpointProtocol
}
if endpointURL == "" {
return nil
}
if strings.HasPrefix(endpointURL, "unix://") || strings.HasPrefix(endpointURL, "npipe://") {
socketPath := strings.TrimPrefix(endpointURL, "unix://")
socketPath = strings.TrimPrefix(socketPath, "npipe://")
if _, err := os.Stat(socketPath); err != nil {
if os.IsNotExist(err) {
return errSocketOrNamedPipeNotFound
}
return err
if !strings.HasPrefix(endpointURL, "unix://") && !strings.HasPrefix(endpointURL, "tcp://") && !strings.HasPrefix(endpointURL, "npipe://") {
return ErrInvalidEndpointProtocol
}
if strings.HasPrefix(endpointURL, "unix://") || strings.HasPrefix(endpointURL, "npipe://") {
socketPath := strings.TrimPrefix(endpointURL, "unix://")
socketPath = strings.TrimPrefix(socketPath, "npipe://")
if _, err := os.Stat(socketPath); err != nil {
if os.IsNotExist(err) {
return ErrSocketOrNamedPipeNotFound
}
return err
}
}
return nil
}
func validateSnapshotInterval(snapshotInterval string) error {
if snapshotInterval != "" {
_, err := time.ParseDuration(snapshotInterval)
if err != nil {
return errInvalidSnapshotInterval
}
if snapshotInterval == "" {
return nil
}
if _, err := time.ParseDuration(snapshotInterval); err != nil {
return ErrInvalidSnapshotInterval
}
return nil
}

View File

@@ -9,16 +9,15 @@ 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)
answer, err := reader.ReadString('\n')
if err != nil {
return false, err
}
answer = strings.Replace(answer, "\n", "", -1)
answer = strings.ToLower(answer)
return answer == "y" || answer == "yes", nil
answer = strings.ReplaceAll(answer, "\n", "")
return strings.EqualFold(answer, "y") || strings.EqualFold(answer, "yes"), nil
}

View File

@@ -4,20 +4,21 @@
package cli
const (
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "/data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "/certs/ca.pem"
defaultTLSCertPath = "/certs/cert.pem"
defaultTLSKeyPath = "/certs/key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "/data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "/certs/ca.pem"
defaultTLSCertPath = "/certs/cert.pem"
defaultTLSKeyPath = "/certs/key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultPullLimitCheckDisabled = "false"
)

View File

@@ -1,21 +1,22 @@
package cli
const (
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "C:\\data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "C:\\certs\\ca.pem"
defaultTLSCertPath = "C:\\certs\\cert.pem"
defaultTLSKeyPath = "C:\\certs\\key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultSnapshotInterval = "5m"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "C:\\data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "C:\\certs\\ca.pem"
defaultTLSCertPath = "C:\\certs\\cert.pem"
defaultTLSKeyPath = "C:\\certs\\key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultSnapshotInterval = "5m"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultPullLimitCheckDisabled = "false"
)

View File

@@ -39,17 +39,25 @@ 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 "NOCOLOR":
log.Logger = log.Output(zerolog.ConsoleWriter{
Out: os.Stderr,
TimeFormat: "2006/01/02 03:04PM",
FormatMessage: formatMessage,
NoColor: true,
})
case "JSON":
log.Logger = log.Output(os.Stderr)
}
}
func formatMessage(i interface{}) string {
func formatMessage(i any) string {
if i == nil {
return ""
}
return fmt.Sprintf("%s |", i)
}

View File

@@ -1,19 +1,15 @@
package main
import (
"cmp"
"context"
"crypto/sha256"
"math/rand"
"os"
"path"
"strings"
"time"
libstack "github.com/portainer/docker-compose-wrapper"
"github.com/portainer/docker-compose-wrapper/compose"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/build"
"github.com/portainer/portainer/api/chisel"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/crypto"
@@ -22,19 +18,20 @@ 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/demo"
"github.com/portainer/portainer/api/datastore/migrator"
"github.com/portainer/portainer/api/datastore/postinit"
"github.com/portainer/portainer/api/docker"
dockerclient "github.com/portainer/portainer/api/docker/client"
"github.com/portainer/portainer/api/exec"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/git"
"github.com/portainer/portainer/api/hostmanagement/openamt"
"github.com/portainer/portainer/api/http"
"github.com/portainer/portainer/api/http/client"
"github.com/portainer/portainer/api/http/proxy"
kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/internal/edge"
"github.com/portainer/portainer/api/internal/edge/edgestacks"
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/portainer/portainer/api/internal/snapshot"
"github.com/portainer/portainer/api/internal/ssl"
"github.com/portainer/portainer/api/internal/upgrade"
@@ -43,24 +40,31 @@ 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/pendingactions/actions"
"github.com/portainer/portainer/api/pendingactions/handlers"
"github.com/portainer/portainer/api/platform"
"github.com/portainer/portainer/api/scheduler"
"github.com/portainer/portainer/api/stacks/deployments"
"github.com/portainer/portainer/pkg/build"
"github.com/portainer/portainer/pkg/featureflags"
"github.com/portainer/portainer/pkg/libhelm"
"github.com/portainer/portainer/pkg/libstack/compose"
"github.com/portainer/portainer/pkg/validate"
"github.com/gofrs/uuid"
"github.com/rs/zerolog/log"
)
func initCLI() *portainer.CLIFlags {
var cliService portainer.CLIService = &cli.Service{}
cliService := &cli.Service{}
flags, err := cliService.ParseFlags(portainer.APIVersion)
if err != nil {
log.Fatal().Err(err).Msg("failed parsing flags")
}
err = cliService.ValidateFlags(flags)
if err != nil {
if err := cliService.ValidateFlags(flags); err != nil {
log.Fatal().Err(err).Msg("failed validating flags")
}
@@ -90,15 +94,15 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
log.Fatal().Msg("failed creating database connection: expecting a boltdb database type but a different one was received")
}
store := datastore.NewStore(*flags.Data, fileService, connection)
store := datastore.NewStore(flags, fileService, connection)
isNew, err := store.Open()
if err != nil {
log.Fatal().Err(err).Msg("failed opening store")
}
if *flags.Rollback {
err := store.Rollback(false)
if err != nil {
if err := store.Rollback(false); err != nil {
log.Fatal().Err(err).Msg("failed rolling back")
}
@@ -107,8 +111,7 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
}
// Init sets some defaults - it's basically a migration
err = store.Init()
if err != nil {
if err := store.Init(); err != nil {
log.Fatal().Err(err).Msg("failed initializing data store")
}
@@ -118,57 +121,49 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
log.Fatal().Err(err).Msg("failed generating instance id")
}
migratorInstance := migrator.NewMigrator(&migrator.MigratorParameters{Flags: flags})
migratorCount := migratorInstance.GetMigratorCountOfCurrentAPIVersion()
// from MigrateData
v := models.Version{
SchemaVersion: portainer.APIVersion,
Edition: int(portainer.PortainerCE),
InstanceID: instanceId.String(),
MigratorCount: migratorCount,
}
store.VersionService.UpdateVersion(&v)
err = updateSettingsFromFlags(store, flags)
if err != nil {
if err := updateSettingsFromFlags(store, flags); err != nil {
log.Fatal().Err(err).Msg("failed updating settings from flags")
}
} else {
err = store.MigrateData()
if err != nil {
if err := store.MigrateData(); err != nil {
log.Fatal().Err(err).Msg("failed migration")
}
}
err = updateSettingsFromFlags(store, flags)
if err != nil {
if err := updateSettingsFromFlags(store, flags); err != nil {
log.Fatal().Err(err).Msg("failed updating settings from flags")
}
// this is for the db restore functionality - needs more tests.
go func() {
<-shutdownCtx.Done()
defer connection.Close()
}()
return store
}
func initComposeStackManager(composeDeployer libstack.Deployer, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
composeWrapper, err := exec.NewComposeStackManager(composeDeployer, proxyManager)
// 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 {
log.Fatal().Err(err).Msg("failed creating compose manager")
return false
}
return composeWrapper
}
func initSwarmStackManager(
assetsPath string,
configPath string,
signatureService portainer.DigitalSignatureService,
fileService portainer.FileService,
reverseTunnelService portainer.ReverseTunnelService,
dataStore dataservices.DataStore,
) (portainer.SwarmStackManager, error) {
return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService, dataStore)
return v.SchemaVersion == serverVersion && v.Edition == serverEdition
}
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore dataservices.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, assetsPath string) portainer.KubernetesDeployer {
@@ -183,41 +178,21 @@ 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
}
jwtService, err := jwt.NewService(userSessionTimeout, dataStore)
if err != nil {
return nil, err
}
return jwtService, nil
return jwt.NewService(userSessionTimeout, dataStore)
}
func initDigitalSignatureService() portainer.DigitalSignatureService {
return crypto.NewECDSAService(os.Getenv("AGENT_SECRET"))
}
func initCryptoService() portainer.CryptoService {
return &crypto.Service{}
}
func initLDAPService() portainer.LDAPService {
return &ldap.Service{}
}
func initOAuthService() portainer.OAuthService {
return oauth.NewService()
}
func initGitService(ctx context.Context) portainer.GitService {
return git.NewService(ctx)
}
func initSSLService(addr, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
slices := strings.Split(addr, ":")
host := slices[0]
if host == "" {
host = "0.0.0.0"
@@ -225,33 +200,25 @@ func initSSLService(addr, certPath, keyPath string, fileService portainer.FileSe
sslService := ssl.NewService(fileService, dataStore, shutdownTrigger)
err := sslService.Init(host, certPath, keyPath)
if err != nil {
if err := sslService.Init(host, certPath, keyPath); err != nil {
return nil, err
}
return sslService, nil
}
func initDockerClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService) *docker.ClientFactory {
return docker.NewClientFactory(signatureService, reverseTunnelService)
}
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*kubecli.ClientFactory, error) {
return kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, addrHTTPS, userSessionTimeout)
}
func initSnapshotService(
snapshotIntervalFromFlag string,
dataStore dataservices.DataStore,
dockerClientFactory *docker.ClientFactory,
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
}
@@ -272,34 +239,21 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
return err
}
if *flags.SnapshotInterval != "" {
settings.SnapshotInterval = *flags.SnapshotInterval
}
if *flags.Logo != "" {
settings.LogoURL = *flags.Logo
}
if *flags.EnableEdgeComputeFeatures {
settings.EnableEdgeComputeFeatures = *flags.EnableEdgeComputeFeatures
}
if *flags.Templates != "" {
settings.TemplatesURL = *flags.Templates
}
settings.SnapshotInterval = cmp.Or(*flags.SnapshotInterval, settings.SnapshotInterval)
settings.LogoURL = cmp.Or(*flags.Logo, settings.LogoURL)
settings.EnableEdgeComputeFeatures = cmp.Or(*flags.EnableEdgeComputeFeatures, settings.EnableEdgeComputeFeatures)
settings.TemplatesURL = cmp.Or(*flags.Templates, settings.TemplatesURL)
if *flags.Labels != nil {
settings.BlackListedLabels = *flags.Labels
}
settings.AgentSecret = ""
if agentKey, ok := os.LookupEnv("AGENT_SECRET"); ok {
settings.AgentSecret = agentKey
} else {
settings.AgentSecret = ""
}
err = dataStore.Settings().UpdateSettings(settings)
if err != nil {
if err := dataStore.Settings().UpdateSettings(settings); err != nil {
return err
}
@@ -322,6 +276,7 @@ func loadAndParseKeyPair(fileService portainer.FileService, signatureService por
if err != nil {
return err
}
return signatureService.ParseKeyPair(private, public)
}
@@ -330,7 +285,9 @@ func generateAndStoreKeyPair(fileService portainer.FileService, signatureService
if err != nil {
return err
}
privateHeader, publicHeader := signatureService.PEMHeaders()
return fileService.StoreKeyPair(private, public, privateHeader, publicHeader)
}
@@ -343,150 +300,10 @@ func initKeyPair(fileService portainer.FileService, signatureService portainer.D
if existingKeyPair {
return loadAndParseKeyPair(fileService, signatureService)
}
return generateAndStoreKeyPair(fileService, signatureService)
}
func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
tlsConfiguration := portainer.TLSConfiguration{
TLS: *flags.TLS,
TLSSkipVerify: *flags.TLSSkipVerify,
}
if *flags.TLS {
tlsConfiguration.TLSCACertPath = *flags.TLSCacert
tlsConfiguration.TLSCertPath = *flags.TLSCert
tlsConfiguration.TLSKeyPath = *flags.TLSKey
} else if !*flags.TLS && *flags.TLSSkipVerify {
tlsConfiguration.TLS = true
}
endpointID := dataStore.Endpoint().GetNextIdentifier()
endpoint := &portainer.Endpoint{
ID: portainer.EndpointID(endpointID),
Name: "primary",
URL: *flags.EndpointURL,
GroupID: portainer.EndpointGroupID(1),
Type: portainer.DockerEnvironment,
TLSConfig: tlsConfiguration,
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
TagIDs: []portainer.TagID{},
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.DockerSnapshot{},
Kubernetes: portainer.KubernetesDefault(),
SecuritySettings: portainer.EndpointSecuritySettings{
AllowVolumeBrowserForRegularUsers: false,
EnableHostManagementFeatures: false,
AllowSysctlSettingForRegularUsers: true,
AllowBindMountsForRegularUsers: true,
AllowPrivilegedModeForRegularUsers: true,
AllowHostNamespaceForRegularUsers: true,
AllowContainerCapabilitiesForRegularUsers: true,
AllowDeviceMappingForRegularUsers: true,
AllowStackManagementForRegularUsers: true,
},
}
if strings.HasPrefix(endpoint.URL, "tcp://") {
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(tlsConfiguration.TLSCACertPath, tlsConfiguration.TLSCertPath, tlsConfiguration.TLSKeyPath, tlsConfiguration.TLSSkipVerify)
if err != nil {
return err
}
agentOnDockerEnvironment, err := client.ExecutePingOperation(endpoint.URL, tlsConfig)
if err != nil {
return err
}
if agentOnDockerEnvironment {
endpoint.Type = portainer.AgentOnDockerEnvironment
}
}
err := snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
log.Error().
Str("endpoint", endpoint.Name).
Str("URL", endpoint.URL).
Err(err).
Msg("environment snapshot error")
}
return dataStore.Endpoint().Create(endpoint)
}
func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
if strings.HasPrefix(endpointURL, "tcp://") {
_, err := client.ExecutePingOperation(endpointURL, nil)
if err != nil {
return err
}
}
endpointID := dataStore.Endpoint().GetNextIdentifier()
endpoint := &portainer.Endpoint{
ID: portainer.EndpointID(endpointID),
Name: "primary",
URL: endpointURL,
GroupID: portainer.EndpointGroupID(1),
Type: portainer.DockerEnvironment,
TLSConfig: portainer.TLSConfiguration{},
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
TagIDs: []portainer.TagID{},
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.DockerSnapshot{},
Kubernetes: portainer.KubernetesDefault(),
SecuritySettings: portainer.EndpointSecuritySettings{
AllowVolumeBrowserForRegularUsers: false,
EnableHostManagementFeatures: false,
AllowSysctlSettingForRegularUsers: true,
AllowBindMountsForRegularUsers: true,
AllowPrivilegedModeForRegularUsers: true,
AllowHostNamespaceForRegularUsers: true,
AllowContainerCapabilitiesForRegularUsers: true,
AllowDeviceMappingForRegularUsers: true,
AllowStackManagementForRegularUsers: true,
},
}
err := snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
log.Error().
Str("endpoint", endpoint.Name).
Str("URL", endpoint.URL).Err(err).
Msg("environment snapshot error")
}
return dataStore.Endpoint().Create(endpoint)
}
func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
if *flags.EndpointURL == "" {
return nil
}
endpoints, err := dataStore.Endpoint().Endpoints()
if err != nil {
return err
}
if len(endpoints) > 0 {
log.Info().Msg("instance already has defined environments, skipping the environment defined via CLI")
return nil
}
if *flags.TLS || *flags.TLSSkipVerify {
return createTLSSecuredEndpoint(flags, dataStore, snapshotService)
}
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
}
func loadEncryptionSecretKey(keyfilename string) []byte {
content, err := os.ReadFile(path.Join("/run/secrets", keyfilename))
if err != nil {
@@ -501,6 +318,7 @@ func loadEncryptionSecretKey(keyfilename string) []byte {
// return a 32 byte hash of the secret (required for AES)
hash := sha256.Sum256(content)
return hash[:]
}
@@ -511,6 +329,18 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
featureflags.Parse(*flags.FeatureFlags, portainer.SupportedFeatureFlags)
}
trustedOrigins := []string{}
if *flags.TrustedOrigins != "" {
// validate if the trusted origins are valid urls
for _, origin := range strings.Split(*flags.TrustedOrigins, ",") {
if !validate.IsTrustedOrigin(origin) {
log.Fatal().Str("trusted_origin", origin).Msg("invalid url for trusted origin. Please check the trusted origins flag.")
}
trustedOrigins = append(trustedOrigins, origin)
}
}
fileService := initFileService(*flags.Data)
encryptionKey := loadEncryptionSecretKey(*flags.SecretKeyName)
if encryptionKey == nil {
@@ -523,6 +353,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")
@@ -540,17 +375,17 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatal().Err(err).Msg("failed initializing JWT service")
}
ldapService := initLDAPService()
ldapService := &ldap.Service{}
oauthService := initOAuthService()
oauthService := oauth.NewService()
gitService := initGitService(shutdownCtx)
gitService := git.NewService(shutdownCtx)
openAMTService := openamt.NewService()
cryptoService := initCryptoService()
cryptoService := &crypto.Service{}
digitalSignatureService := initDigitalSignatureService()
signatureService := initDigitalSignatureService()
edgeStacksService := edgestacks.NewService(dataStore)
@@ -564,21 +399,18 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatal().Err(err).Msg("failed to get SSL settings")
}
err = initKeyPair(fileService, digitalSignatureService)
if err != nil {
if err := initKeyPair(fileService, signatureService); err != nil {
log.Fatal().Err(err).Msg("failed initializing key pair")
}
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx, fileService)
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
dockerClientFactory := dockerclient.NewClientFactory(signatureService, reverseTunnelService)
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
kubernetesClientFactory, err := kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing snapshot service")
log.Fatal().Err(err).Msg("failed initializing Kubernetes Client Factory service")
}
snapshotService.Start()
authorizationService := authorization.NewService(dataStore)
authorizationService.K8sClientFactory = kubernetesClientFactory
@@ -587,52 +419,51 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService(*flags.BaseURL, *flags.AddrHTTPS, sslSettings.CertPath)
proxyManager := proxy.NewManager(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService)
proxyManager := proxy.NewManager(kubernetesClientFactory)
reverseTunnelService.ProxyManager = proxyManager
dockerConfigPath := fileService.GetDockerConfigPath()
composeDeployer, err := compose.NewComposeDeployer(*flags.Assets, dockerConfigPath)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing compose deployer")
}
composeDeployer := compose.NewComposeDeployer()
composeStackManager := initComposeStackManager(composeDeployer, reverseTunnelService, proxyManager)
composeStackManager := exec.NewComposeStackManager(composeDeployer, proxyManager, dataStore)
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
swarmStackManager, err := exec.NewSwarmStackManager(*flags.Assets, dockerConfigPath, signatureService, fileService, reverseTunnelService, dataStore)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing swarm stack manager")
}
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager, *flags.Assets)
pendingActionsService := pendingactions.NewService(dataStore, kubernetesClientFactory)
pendingActionsService.RegisterHandler(actions.CleanNAPWithOverridePolicies, handlers.NewHandlerCleanNAPWithOverridePolicies(authorizationService, dataStore))
pendingActionsService.RegisterHandler(actions.DeletePortainerK8sRegistrySecrets, handlers.NewHandlerDeleteRegistrySecrets(authorizationService, dataStore, kubernetesClientFactory))
pendingActionsService.RegisterHandler(actions.PostInitMigrateEnvironment, handlers.NewHandlerPostInitMigrateEnvironment(authorizationService, dataStore, kubernetesClientFactory, dockerClientFactory, *flags.Assets, kubernetesDeployer))
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx, pendingActionsService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing snapshot service")
}
snapshotService.Start()
proxyManager.NewProxyFactory(dataStore, signatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService)
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing helm package manager")
}
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
if err != nil {
log.Fatal().Err(err).Msg("failed loading edge jobs from database")
}
applicationStatus := initStatus(instanceID)
demoService := demo.NewService()
if *flags.DemoEnvironment {
err := demoService.Init(dataStore, cryptoService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing demo environment")
}
}
// channel to control when the admin user is created
adminCreationDone := make(chan struct{}, 1)
err = initEndpoint(flags, dataStore, snapshotService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing environment")
}
go endpointutils.InitEndpoint(shutdownCtx, adminCreationDone, flags, dataStore, snapshotService)
adminPasswordHash := ""
if *flags.AdminPasswordFile != "" {
content, err := fileService.GetFileContent(*flags.AdminPasswordFile, "")
if err != nil {
@@ -655,28 +486,30 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
if len(users) == 0 {
log.Info().Msg("created admin user with the given password.")
user := &portainer.User{
Username: "admin",
Role: portainer.AdministratorRole,
Password: adminPasswordHash,
}
err := dataStore.User().Create(user)
if err != nil {
if err := dataStore.User().Create(user); err != nil {
log.Fatal().Err(err).Msg("failed creating admin user")
}
// notify the admin user is created, the endpoint initialization can start
adminCreationDone <- struct{}{}
} else {
log.Info().Msg("instance already has an administrator user defined, skipping admin password related flags.")
}
}
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
if err != nil {
if err := reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService); err != nil {
log.Fatal().Err(err).Msg("failed starting tunnel server")
}
scheduler := scheduler.NewScheduler(shutdownCtx)
stackDeployer := deployments.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
stackDeployer := deployments.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer, dockerClientFactory, dataStore)
deployments.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
sslDBSettings, err := dataStore.SSLSettings().Settings()
@@ -684,7 +517,20 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatal().Msg("failed to fetch SSL settings from DB")
}
upgradeService, err := upgrade.NewService(*flags.Assets, composeDeployer)
platformService, err := platform.NewService(dataStore)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing platform service")
}
upgradeService, err := upgrade.NewService(
*flags.Assets,
kubernetesClientFactory,
dockerClientFactory,
composeStackManager,
dataStore,
fileService,
stackDeployer,
)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing upgrade service")
}
@@ -693,10 +539,12 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
// but some more complex migrations require access to a kubernetes or docker
// client. Therefore we run a separate migration process just before
// starting the server.
postInitMigrator := datastore.NewPostInitMigrator(
postInitMigrator := postinit.NewPostInitMigrator(
kubernetesClientFactory,
dockerClientFactory,
dataStore,
*flags.Assets,
kubernetesDeployer,
)
if err := postInitMigrator.PostInitMigrate(); err != nil {
log.Fatal().Err(err).Msg("failure during post init migrations")
@@ -727,7 +575,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
ProxyManager: proxyManager,
KubernetesTokenCacheManager: kubernetesTokenCacheManager,
KubeClusterAccessService: kubeClusterAccessService,
SignatureService: digitalSignatureService,
SignatureService: signatureService,
SnapshotService: snapshotService,
SSLService: sslService,
DockerClientFactory: dockerClientFactory,
@@ -736,14 +584,16 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
ShutdownCtx: shutdownCtx,
ShutdownTrigger: shutdownTrigger,
StackDeployer: stackDeployer,
DemoService: demoService,
UpgradeService: upgradeService,
AdminCreationDone: adminCreationDone,
PendingActionsService: pendingActionsService,
PlatformService: platformService,
PullLimitCheckDisabled: *flags.PullLimitCheckDisabled,
TrustedOrigins: trustedOrigins,
}
}
func main() {
rand.Seed(time.Now().UnixNano())
configureLogger()
setLoggingMode("PRETTY")
@@ -754,6 +604,7 @@ func main() {
for {
server := buildServer(flags)
log.Info().
Str("version", portainer.APIVersion).
Str("build_number", build.BuildNumber).
@@ -765,6 +616,7 @@ func main() {
Msg("starting Portainer")
err := server.Start()
log.Info().Err(err).Msg("HTTP server exited")
}
}

View File

@@ -0,0 +1,148 @@
// Package concurrent provides utilities for running multiple functions concurrently in Go.
// For example, many kubernetes calls can take a while to fulfill. Oftentimes in Portainer
// we need to get a list of objects from multiple kubernetes REST APIs. We can often call these
// apis concurrently to speed up the response time.
// This package provides a clean way to do just that.
//
// Examples:
// The ConfigMaps and Secrets function converted using concurrent.Run.
/*
// GetConfigMapsAndSecrets gets all the ConfigMaps AND all the Secrets for a
// given namespace in a k8s endpoint. The result is a list of both config maps
// and secrets. The IsSecret boolean property indicates if a given struct is a
// secret or configmap.
func (kcl *KubeClient) GetConfigMapsAndSecrets(namespace string) ([]models.K8sConfigMapOrSecret, error) {
// use closures to capture the current kube client and namespace by declaring wrapper functions
// that match the interface signature for concurrent.Func
listConfigMaps := func(ctx context.Context) (any, error) {
return kcl.cli.CoreV1().ConfigMaps(namespace).List(context.Background(), meta.ListOptions{})
}
listSecrets := func(ctx context.Context) (any, error) {
return kcl.cli.CoreV1().Secrets(namespace).List(context.Background(), meta.ListOptions{})
}
// run the functions concurrently and wait for results. We can also pass in a context to cancel.
// e.g. Deadline timer.
results, err := concurrent.Run(context.TODO(), listConfigMaps, listSecrets)
if err != nil {
return nil, err
}
var configMapList *core.ConfigMapList
var secretList *core.SecretList
for _, r := range results {
switch v := r.Result.(type) {
case *core.ConfigMapList:
configMapList = v
case *core.SecretList:
secretList = v
}
}
// TODO: Applications
var combined []models.K8sConfigMapOrSecret
for _, m := range configMapList.Items {
var cm models.K8sConfigMapOrSecret
cm.UID = string(m.UID)
cm.Name = m.Name
cm.Namespace = m.Namespace
cm.Annotations = m.Annotations
cm.Data = m.Data
cm.CreationDate = m.CreationTimestamp.Time.UTC().Format(time.RFC3339)
combined = append(combined, cm)
}
for _, s := range secretList.Items {
var secret models.K8sConfigMapOrSecret
secret.UID = string(s.UID)
secret.Name = s.Name
secret.Namespace = s.Namespace
secret.Annotations = s.Annotations
secret.Data = msbToMss(s.Data)
secret.CreationDate = s.CreationTimestamp.Time.UTC().Format(time.RFC3339)
secret.IsSecret = true
secret.SecretType = string(s.Type)
combined = append(combined, secret)
}
return combined, nil
}
*/
package concurrent
import (
"context"
"sync"
)
// Result contains the result and any error returned from running a client task function
type Result struct {
Result any // the result of running the task function
Err error // any error that occurred while running the task function
}
// Func is a function returns a result or error
type Func func(ctx context.Context) (any, error)
// Run runs a list of functions returns the results
func Run(ctx context.Context, maxConcurrency int, tasks ...Func) ([]Result, error) {
var wg sync.WaitGroup
resultsChan := make(chan Result, len(tasks))
taskChan := make(chan Func, len(tasks))
localCtx, cancelCtx := context.WithCancel(ctx)
defer cancelCtx()
runTask := func() {
defer wg.Done()
for fn := range taskChan {
result, err := fn(localCtx)
resultsChan <- Result{Result: result, Err: err}
}
}
// Set maxConcurrency to the number of tasks if zero or negative
if maxConcurrency <= 0 {
maxConcurrency = len(tasks)
}
// Start worker goroutines
for range maxConcurrency {
wg.Add(1)
go runTask()
}
// Add tasks to the task channel
for _, fn := range tasks {
taskChan <- fn
}
// Close the task channel to signal workers to stop when all tasks are done
close(taskChan)
// Wait for all workers to complete
wg.Wait()
close(resultsChan)
// Collect the results and cancel on error
results := make([]Result, 0, len(tasks))
for r := range resultsChan {
if r.Err != nil {
cancelCtx()
return nil, r.Err
}
results = append(results, r)
}
return results, nil
}

View File

@@ -5,22 +5,23 @@ import (
)
type ReadTransaction interface {
GetObject(bucketName string, key []byte, object interface{}) error
GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error
GetObject(bucketName string, key []byte, object any) error
GetRawBytes(bucketName string, key []byte) ([]byte, error)
GetAll(bucketName string, obj any, append func(o any) (any, error)) error
GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, append func(o any) (any, error)) error
KeyExists(bucketName string, key []byte) (bool, error)
}
type Transaction interface {
ReadTransaction
SetServiceName(bucketName string) error
UpdateObject(bucketName string, key []byte, object interface{}) error
UpdateObject(bucketName string, key []byte, object any) error
DeleteObject(bucketName string, key []byte) error
CreateObject(bucketName string, fn func(uint64) (int, interface{})) error
CreateObjectWithId(bucketName string, id int, obj interface{}) error
CreateObjectWithStringId(bucketName string, id []byte, obj interface{}) error
DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error
CreateObject(bucketName string, fn func(uint64) (int, any)) error
CreateObjectWithId(bucketName string, id int, obj any) error
CreateObjectWithStringId(bucketName string, id []byte, obj any) error
DeleteAllObjects(bucketName string, obj any, matching func(o any) (id int, ok bool)) error
GetNextIdentifier(bucketName string) int
}
@@ -41,13 +42,14 @@ type Connection interface {
GetDatabaseFileName() string
GetDatabaseFilePath() string
GetStorePath() string
GetDatabaseFileSize() (int64, error)
IsEncryptedStore() bool
NeedsEncryptionMigration() (bool, error)
SetEncrypted(encrypted bool)
BackupMetadata() (map[string]interface{}, error)
RestoreMetadata(s map[string]interface{}) error
BackupMetadata() (map[string]any, error)
RestoreMetadata(s map[string]any) error
UpdateObjectFunc(bucketName string, key []byte, object any, updateFn func()) error
ConvertToKey(v int) []byte

View File

@@ -1,52 +1,214 @@
package crypto
import (
"bufio"
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"io"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/scrypt"
)
// NOTE: has to go with what is considered to be a simplistic in that it omits any
// authentication of the encrypted data.
// Person with better knowledge is welcomed to improve it.
// sourced from https://golang.org/src/crypto/cipher/example_test.go
const (
// AES GCM settings
aesGcmHeader = "AES256-GCM" // The encrypted file header
aesGcmBlockSize = 1024 * 1024 // 1MB block for aes gcm
var emptySalt []byte = make([]byte, 0)
// Argon2 settings
// Recommded settings lower memory hardware according to current OWASP recommendations
// Considering some people run portainer on a NAS I think it's prudent not to assume we're on server grade hardware
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
argon2MemoryCost = 12 * 1024
argon2TimeCost = 3
argon2Threads = 1
argon2KeyLength = 32
)
// AesEncrypt reads from input, encrypts with AES-256 and writes to the output.
// passphrase is used to generate an encryption key.
// AesEncrypt reads from input, encrypts with AES-256 and writes to output. passphrase is used to generate an encryption key
func AesEncrypt(input io.Reader, output io.Writer, passphrase []byte) error {
// making a 32 bytes key that would correspond to AES-256
// don't necessarily need a salt, so just kept in empty
key, err := scrypt.Key(passphrase, emptySalt, 32768, 8, 1, 32)
if err != nil {
return err
}
block, err := aes.NewCipher(key)
if err != nil {
return err
}
// If the key is unique for each ciphertext, then it's ok to use a zero
// IV.
var iv [aes.BlockSize]byte
stream := cipher.NewOFB(block, iv[:])
writer := &cipher.StreamWriter{S: stream, W: output}
// Copy the input to the output, encrypting as we go.
if _, err := io.Copy(writer, input); err != nil {
return err
if err := aesEncryptGCM(input, output, passphrase); err != nil {
return fmt.Errorf("error encrypting file: %w", err)
}
return nil
}
// AesDecrypt reads from input, decrypts with AES-256 and returns the reader to a read decrypted content from.
// passphrase is used to generate an encryption key.
// AesDecrypt reads from input, decrypts with AES-256 and returns the reader to read the decrypted content from
func AesDecrypt(input io.Reader, passphrase []byte) (io.Reader, error) {
// Read file header to determine how it was encrypted
inputReader := bufio.NewReader(input)
header, err := inputReader.Peek(len(aesGcmHeader))
if err != nil {
return nil, fmt.Errorf("error reading encrypted backup file header: %w", err)
}
if string(header) == aesGcmHeader {
reader, err := aesDecryptGCM(inputReader, passphrase)
if err != nil {
return nil, fmt.Errorf("error decrypting file: %w", err)
}
return reader, nil
}
// Use the previous decryption routine which has no header (to support older archives)
reader, err := aesDecryptOFB(inputReader, passphrase)
if err != nil {
return nil, fmt.Errorf("error decrypting legacy file backup: %w", err)
}
return reader, nil
}
// aesEncryptGCM reads from input, encrypts with AES-256 and writes to output. passphrase is used to generate an encryption key.
func aesEncryptGCM(input io.Reader, output io.Writer, passphrase []byte) error {
// Derive key using argon2 with a random salt
salt := make([]byte, 16) // 16 bytes salt
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return err
}
key := argon2.IDKey(passphrase, salt, argon2TimeCost, argon2MemoryCost, argon2Threads, 32)
block, err := aes.NewCipher(key)
if err != nil {
return err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
// Generate nonce
nonce, err := NewRandomNonce(aesgcm.NonceSize())
if err != nil {
return err
}
// write the header
if _, err := output.Write([]byte(aesGcmHeader)); err != nil {
return err
}
// Write nonce and salt to the output file
if _, err := output.Write(salt); err != nil {
return err
}
if _, err := output.Write(nonce.Value()); err != nil {
return err
}
// Buffer for reading plaintext blocks
buf := make([]byte, aesGcmBlockSize) // Adjust buffer size as needed
ciphertext := make([]byte, len(buf)+aesgcm.Overhead())
// Encrypt plaintext in blocks
for {
n, err := io.ReadFull(input, buf)
if n == 0 {
break // end of plaintext input
}
if err != nil && !(errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)) {
return err
}
// Seal encrypts the plaintext using the nonce returning the updated slice.
ciphertext = aesgcm.Seal(ciphertext[:0], nonce.Value(), buf[:n], nil)
_, err = output.Write(ciphertext)
if err != nil {
return err
}
nonce.Increment()
}
return nil
}
// aesDecryptGCM reads from input, decrypts with AES-256 and returns the reader to read the decrypted content from.
func aesDecryptGCM(input io.Reader, passphrase []byte) (io.Reader, error) {
// Reader & verify header
header := make([]byte, len(aesGcmHeader))
if _, err := io.ReadFull(input, header); err != nil {
return nil, err
}
if string(header) != aesGcmHeader {
return nil, errors.New("invalid header")
}
// Read salt
salt := make([]byte, 16) // Salt size
if _, err := io.ReadFull(input, salt); err != nil {
return nil, err
}
key := argon2.IDKey(passphrase, salt, argon2TimeCost, argon2MemoryCost, argon2Threads, 32)
// Initialize AES cipher block
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Create GCM mode with the cipher block
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Read nonce from the input reader
nonce := NewNonce(aesgcm.NonceSize())
if err := nonce.Read(input); err != nil {
return nil, err
}
// Initialize a buffer to store decrypted data
buf := bytes.Buffer{}
plaintext := make([]byte, aesGcmBlockSize)
// Decrypt the ciphertext in blocks
for {
// Read a block of ciphertext from the input reader
ciphertextBlock := make([]byte, aesGcmBlockSize+aesgcm.Overhead()) // Adjust block size as needed
n, err := io.ReadFull(input, ciphertextBlock)
if n == 0 {
break // end of ciphertext
}
if err != nil && !(errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)) {
return nil, err
}
// Decrypt the block of ciphertext
plaintext, err = aesgcm.Open(plaintext[:0], nonce.Value(), ciphertextBlock[:n], nil)
if err != nil {
return nil, err
}
if _, err := buf.Write(plaintext); err != nil {
return nil, err
}
nonce.Increment()
}
return &buf, nil
}
// aesDecryptOFB reads from input, decrypts with AES-256 and returns the reader to a read decrypted content from.
// passphrase is used to generate an encryption key.
// note: This function used to decrypt files that were encrypted without a header i.e. old archives
func aesDecryptOFB(input io.Reader, passphrase []byte) (io.Reader, error) {
var emptySalt []byte = make([]byte, 0)
// making a 32 bytes key that would correspond to AES-256
// don't necessarily need a salt, so just kept in empty
key, err := scrypt.Key(passphrase, emptySalt, 32768, 8, 1, 32)
@@ -59,11 +221,9 @@ func AesDecrypt(input io.Reader, passphrase []byte) (io.Reader, error) {
return nil, err
}
// If the key is unique for each ciphertext, then it's ok to use a zero
// IV.
// If the key is unique for each ciphertext, then it's ok to use a zero IV.
var iv [aes.BlockSize]byte
stream := cipher.NewOFB(block, iv[:])
reader := &cipher.StreamReader{S: stream, R: input}
return reader, nil

View File

@@ -2,6 +2,7 @@ package crypto
import (
"io"
"math/rand"
"os"
"path/filepath"
"testing"
@@ -9,7 +10,19 @@ import (
"github.com/stretchr/testify/assert"
)
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func randBytes(n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return b
}
func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
const passphrase = "passphrase"
tmpdir := t.TempDir()
var (
@@ -18,17 +31,99 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
decryptedFilePath = filepath.Join(tmpdir, "decrypted")
)
content := []byte("content")
content := randBytes(1024*1024*100 + 523)
os.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
encryptedFileWriter, _ := os.Create(encryptedFilePath)
err := AesEncrypt(originFile, encryptedFileWriter, []byte(passphrase))
assert.Nil(t, err, "Failed to encrypt a file")
encryptedFileWriter.Close()
encryptedContent, err := os.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
encryptedFileReader, _ := os.Open(encryptedFilePath)
defer encryptedFileReader.Close()
decryptedFileWriter, _ := os.Create(decryptedFilePath)
defer decryptedFileWriter.Close()
decryptedReader, err := AesDecrypt(encryptedFileReader, []byte(passphrase))
assert.Nil(t, err, "Failed to decrypt file")
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
}
func Test_encryptAndDecrypt_withStrongPassphrase(t *testing.T) {
const passphrase = "A strong passphrase with special characters: !@#$%^&*()_+"
tmpdir := t.TempDir()
var (
originFilePath = filepath.Join(tmpdir, "origin2")
encryptedFilePath = filepath.Join(tmpdir, "encrypted2")
decryptedFilePath = filepath.Join(tmpdir, "decrypted2")
)
content := randBytes(500)
os.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
encryptedFileWriter, _ := os.Create(encryptedFilePath)
err := AesEncrypt(originFile, encryptedFileWriter, []byte(passphrase))
assert.Nil(t, err, "Failed to encrypt a file")
encryptedFileWriter.Close()
encryptedContent, err := os.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
encryptedFileReader, _ := os.Open(encryptedFilePath)
defer encryptedFileReader.Close()
decryptedFileWriter, _ := os.Create(decryptedFilePath)
defer decryptedFileWriter.Close()
decryptedReader, err := AesDecrypt(encryptedFileReader, []byte(passphrase))
assert.Nil(t, err, "Failed to decrypt file")
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
}
func Test_encryptAndDecrypt_withTheSamePasswordSmallFile(t *testing.T) {
tmpdir := t.TempDir()
var (
originFilePath = filepath.Join(tmpdir, "origin2")
encryptedFilePath = filepath.Join(tmpdir, "encrypted2")
decryptedFilePath = filepath.Join(tmpdir, "decrypted2")
)
content := randBytes(500)
os.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
encryptedFileWriter, _ := os.Create(encryptedFilePath)
defer encryptedFileWriter.Close()
err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
assert.Nil(t, err, "Failed to encrypt a file")
encryptedFileWriter.Close()
encryptedContent, err := os.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
@@ -57,7 +152,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
decryptedFilePath = filepath.Join(tmpdir, "decrypted")
)
content := []byte("content")
content := randBytes(1024 * 50)
os.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
@@ -96,7 +191,7 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
decryptedFilePath = filepath.Join(tmpdir, "decrypted")
)
content := []byte("content")
content := randBytes(1034)
os.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
@@ -117,11 +212,6 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
decryptedFileWriter, _ := os.Create(decryptedFilePath)
defer decryptedFileWriter.Close()
decryptedReader, err := AesDecrypt(encryptedFileReader, []byte("garbage"))
assert.Nil(t, err, "Should allow to decrypt with wrong passphrase")
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
assert.NotEqual(t, content, decryptedContent, "Original and decrypted content should NOT match")
_, err = AesDecrypt(encryptedFileReader, []byte("garbage"))
assert.NotNil(t, err, "Should not allow decrypt with wrong passphrase")
}

View File

@@ -7,9 +7,8 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/hex"
"math/big"
"github.com/portainer/libcrypto"
"github.com/portainer/portainer/pkg/libcrypto"
)
const (
@@ -115,9 +114,6 @@ func (service *ECDSAService) CreateSignature(message string) (string, error) {
hash := libcrypto.HashFromBytes([]byte(message))
r := big.NewInt(0)
s := big.NewInt(0)
r, s, err := ecdsa.Sign(rand.Reader, service.privateKey, hash)
if err != nil {
return "", err

61
api/crypto/nonce.go Normal file
View File

@@ -0,0 +1,61 @@
package crypto
import (
"crypto/rand"
"errors"
"io"
)
type Nonce struct {
val []byte
}
func NewNonce(size int) *Nonce {
return &Nonce{val: make([]byte, size)}
}
// NewRandomNonce generates a new initial nonce with the lower byte set to a random value
// This ensures there are plenty of nonce values availble before rolling over
// Based on ideas from the Secure Programming Cookbook for C and C++ by John Viega, Matt Messier
// https://www.oreilly.com/library/view/secure-programming-cookbook/0596003943/ch04s09.html
func NewRandomNonce(size int) (*Nonce, error) {
randomBytes := 1
if size <= randomBytes {
return nil, errors.New("nonce size must be greater than the number of random bytes")
}
randomPart := make([]byte, randomBytes)
if _, err := rand.Read(randomPart); err != nil {
return nil, err
}
zeroPart := make([]byte, size-randomBytes)
nonceVal := append(randomPart, zeroPart...)
return &Nonce{val: nonceVal}, nil
}
func (n *Nonce) Read(stream io.Reader) error {
_, err := io.ReadFull(stream, n.val)
return err
}
func (n *Nonce) Value() []byte {
return n.val
}
func (n *Nonce) Increment() error {
// Start incrementing from the least significant byte
for i := len(n.val) - 1; i >= 0; i-- {
// Increment the current byte
n.val[i]++
// Check for overflow
if n.val[i] != 0 {
// No overflow, nonce is successfully incremented
return nil
}
}
// If we reach here, it means the nonce has overflowed
return errors.New("nonce overflow")
}

View File

@@ -20,6 +20,14 @@ func CreateTLSConfiguration() *tls.Config {
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
},
}
}

View File

@@ -5,8 +5,10 @@ import (
"errors"
"fmt"
"io"
"math"
"os"
"path"
"strconv"
"time"
portainer "github.com/portainer/portainer/api"
@@ -60,6 +62,15 @@ func (connection *DbConnection) GetStorePath() string {
return connection.Path
}
func (connection *DbConnection) GetDatabaseFileSize() (int64, error) {
file, err := os.Stat(connection.GetDatabaseFilePath())
if err != nil {
return 0, fmt.Errorf("Failed to stat database file path: %s err: %w", connection.GetDatabaseFilePath(), err)
}
return file.Size(), nil
}
func (connection *DbConnection) SetEncrypted(flag bool) {
connection.isEncrypted = flag
}
@@ -72,7 +83,6 @@ func (connection *DbConnection) IsEncryptedStore() bool {
// NeedsEncryptionMigration returns true if database encryption is enabled and
// we have an un-encrypted DB that requires migration to an encrypted DB
func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
// Cases: Note, we need to check both portainer.db and portainer.edb
// to determine if it's a new store. We only need to differentiate between cases 2,3 and 5
@@ -120,11 +130,11 @@ func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
// Open opens and initializes the BoltDB database.
func (connection *DbConnection) Open() error {
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
// Now we open the db
databasePath := connection.GetDatabaseFilePath()
db, err := bolt.Open(databasePath, 0600, &bolt.Options{
Timeout: 1 * time.Second,
InitialMmapSize: connection.InitialMmapSize,
@@ -143,6 +153,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()
}
@@ -175,6 +187,7 @@ func (connection *DbConnection) ViewTx(fn func(portainer.Transaction) error) err
func (connection *DbConnection) BackupTo(w io.Writer) error {
return connection.View(func(tx *bolt.Tx) error {
_, err := tx.WriteTo(w)
return err
})
}
@@ -182,13 +195,14 @@ func (connection *DbConnection) BackupTo(w io.Writer) error {
func (connection *DbConnection) ExportRaw(filename string) error {
databasePath := connection.GetDatabaseFilePath()
if _, err := os.Stat(databasePath); err != nil {
return fmt.Errorf("stat on %s failed: %s", databasePath, err)
return fmt.Errorf("stat on %s failed, error: %w", databasePath, err)
}
b, err := connection.ExportJSON(databasePath, true)
if err != nil {
return err
}
return os.WriteFile(filename, b, 0600)
}
@@ -198,9 +212,24 @@ func (connection *DbConnection) ExportRaw(filename string) error {
func (connection *DbConnection) ConvertToKey(v int) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(v))
return b
}
// keyToString Converts a key to a string value suitable for logging
func keyToString(b []byte) string {
if len(b) != 8 {
return string(b)
}
v := binary.BigEndian.Uint64(b)
if v <= math.MaxInt32 {
return strconv.FormatUint(v, 10)
}
return string(b)
}
// CreateBucket is a generic function used to create a bucket inside a database.
func (connection *DbConnection) SetServiceName(bucketName string) error {
return connection.UpdateTx(func(tx portainer.Transaction) error {
@@ -209,12 +238,38 @@ func (connection *DbConnection) SetServiceName(bucketName string) error {
}
// GetObject is a generic function used to retrieve an unmarshalled object from a database.
func (connection *DbConnection) GetObject(bucketName string, key []byte, object interface{}) error {
func (connection *DbConnection) GetObject(bucketName string, key []byte, object any) error {
return connection.ViewTx(func(tx portainer.Transaction) error {
return tx.GetObject(bucketName, key, object)
})
}
func (connection *DbConnection) GetRawBytes(bucketName string, key []byte) ([]byte, error) {
var value []byte
err := connection.ViewTx(func(tx portainer.Transaction) error {
var err error
value, err = tx.GetRawBytes(bucketName, key)
return err
})
return value, err
}
func (connection *DbConnection) KeyExists(bucketName string, key []byte) (bool, error) {
var exists bool
err := connection.ViewTx(func(tx portainer.Transaction) error {
var err error
exists, err = tx.KeyExists(bucketName, key)
return err
})
return exists, err
}
func (connection *DbConnection) getEncryptionKey() []byte {
if !connection.isEncrypted {
return nil
@@ -224,7 +279,7 @@ func (connection *DbConnection) getEncryptionKey() []byte {
}
// UpdateObject is a generic function used to update an object inside a database.
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object interface{}) error {
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object any) error {
return connection.UpdateTx(func(tx portainer.Transaction) error {
return tx.UpdateObject(bucketName, key, object)
})
@@ -237,10 +292,10 @@ func (connection *DbConnection) UpdateObjectFunc(bucketName string, key []byte,
data := bucket.Get(key)
if data == nil {
return dserrors.ErrObjectNotFound
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
}
@@ -265,7 +320,7 @@ func (connection *DbConnection) DeleteObject(bucketName string, key []byte) erro
// DeleteAllObjects delete all objects where matching() returns (id, ok).
// TODO: think about how to return the error inside (maybe change ok to type err, and use "notfound"?
func (connection *DbConnection) DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error {
func (connection *DbConnection) DeleteAllObjects(bucketName string, obj any, matching func(o any) (id int, ok bool)) error {
return connection.UpdateTx(func(tx portainer.Transaction) error {
return tx.DeleteAllObjects(bucketName, obj, matching)
})
@@ -284,71 +339,64 @@ func (connection *DbConnection) GetNextIdentifier(bucketName string) int {
}
// CreateObject creates a new object in the bucket, using the next bucket sequence id
func (connection *DbConnection) CreateObject(bucketName string, fn func(uint64) (int, interface{})) error {
func (connection *DbConnection) CreateObject(bucketName string, fn func(uint64) (int, any)) error {
return connection.UpdateTx(func(tx portainer.Transaction) error {
return tx.CreateObject(bucketName, fn)
})
}
// CreateObjectWithId creates a new object in the bucket, using the specified id
func (connection *DbConnection) CreateObjectWithId(bucketName string, id int, obj interface{}) error {
func (connection *DbConnection) CreateObjectWithId(bucketName string, id int, obj any) error {
return connection.UpdateTx(func(tx portainer.Transaction) error {
return tx.CreateObjectWithId(bucketName, id, obj)
})
}
// CreateObjectWithStringId creates a new object in the bucket, using the specified id
func (connection *DbConnection) CreateObjectWithStringId(bucketName string, id []byte, obj interface{}) error {
func (connection *DbConnection) CreateObjectWithStringId(bucketName string, id []byte, obj any) error {
return connection.UpdateTx(func(tx portainer.Transaction) error {
return tx.CreateObjectWithStringId(bucketName, id, obj)
})
}
func (connection *DbConnection) GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
func (connection *DbConnection) GetAll(bucketName string, obj any, appendFn func(o any) (any, error)) error {
return connection.ViewTx(func(tx portainer.Transaction) error {
return tx.GetAll(bucketName, obj, append)
return tx.GetAll(bucketName, obj, appendFn)
})
}
// TODO: decide which Unmarshal to use, and use one...
func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
func (connection *DbConnection) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, appendFn func(o any) (any, error)) error {
return connection.ViewTx(func(tx portainer.Transaction) error {
return tx.GetAllWithJsoniter(bucketName, obj, append)
})
}
func (connection *DbConnection) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error {
return connection.ViewTx(func(tx portainer.Transaction) error {
return tx.GetAllWithKeyPrefix(bucketName, keyPrefix, obj, append)
return tx.GetAllWithKeyPrefix(bucketName, keyPrefix, obj, appendFn)
})
}
// BackupMetadata will return a copy of the boltdb sequence numbers for all buckets.
func (connection *DbConnection) BackupMetadata() (map[string]interface{}, error) {
buckets := map[string]interface{}{}
func (connection *DbConnection) BackupMetadata() (map[string]any, error) {
buckets := map[string]any{}
err := connection.View(func(tx *bolt.Tx) error {
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
return tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
bucketName := string(name)
seqId := bucket.Sequence()
buckets[bucketName] = int(seqId)
return nil
})
return err
})
return buckets, err
}
// RestoreMetadata will restore the boltdb sequence numbers for all buckets.
func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error {
func (connection *DbConnection) RestoreMetadata(s map[string]any) error {
var err error
for bucketName, v := range s {
id, ok := v.(float64) // JSON ints are unmarshalled to interface as float64. See: https://pkg.go.dev/encoding/json#Decoder.Decode
if !ok {
log.Error().Str("bucket", bucketName).Msg("failed to restore metadata to bucket, skipped")
continue
}

View File

@@ -87,10 +87,7 @@ func Test_NeedsEncryptionMigration(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
connection := DbConnection{Path: dir}
if tc.dbname == "both" {

View File

@@ -1,15 +1,15 @@
package boltdb
import (
"encoding/json"
"time"
"github.com/rs/zerolog/log"
"github.com/segmentio/encoding/json"
bolt "go.etcd.io/bbolt"
)
func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) {
buckets := map[string]interface{}{}
func backupMetadata(connection *bolt.DB) (map[string]any, error) {
buckets := map[string]any{}
err := connection.View(func(tx *bolt.Tx) error {
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
@@ -39,7 +39,7 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
}
defer connection.Close()
backup := make(map[string]interface{})
backup := make(map[string]any)
if metadata {
meta, err := backupMetadata(connection)
if err != nil {
@@ -49,10 +49,10 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
backup["__metadata"] = meta
}
err = connection.View(func(tx *bolt.Tx) error {
err = tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
if err := connection.View(func(tx *bolt.Tx) error {
return tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
bucketName := string(name)
var list []interface{}
var list []any
version := make(map[string]string)
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
@@ -60,7 +60,7 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
continue
}
var obj interface{}
var obj any
err := c.UnmarshalObject(v, &obj)
if err != nil {
log.Error().
@@ -84,27 +84,22 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
return nil
}
if len(list) > 0 {
if bucketName == "ssl" ||
bucketName == "settings" ||
bucketName == "tunnel_server" {
backup[bucketName] = nil
if len(list) > 0 {
backup[bucketName] = list[0]
}
return nil
if bucketName == "ssl" ||
bucketName == "settings" ||
bucketName == "tunnel_server" {
backup[bucketName] = nil
if len(list) > 0 {
backup[bucketName] = list[0]
}
backup[bucketName] = list
return nil
}
backup[bucketName] = list
return nil
})
return err
})
if err != nil {
}); err != nil {
return []byte("{}"), err
}

View File

@@ -1,38 +1,44 @@
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")
var errEncryptedStringTooShort = errors.New("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 any) ([]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
func (connection *DbConnection) UnmarshalObject(data []byte, object interface{}) error {
func (connection *DbConnection) UnmarshalObject(data []byte, object any) error {
var err error
if connection.getEncryptionKey() != nil {
data, err = decrypt(data, connection.getEncryptionKey())
@@ -40,8 +46,8 @@ func (connection *DbConnection) UnmarshalObject(data []byte, object interface{})
return errors.Wrap(err, "Failed decrypting object")
}
}
e := json.Unmarshal(data, object)
if e != nil {
if e := json.Unmarshal(data, object); e != nil {
// Special case for the VERSION bucket. Here we're not using json
// So we need to return it as a string
s, ok := object.(*string)
@@ -51,34 +57,10 @@ func (connection *DbConnection) UnmarshalObject(data []byte, object interface{})
*s = string(data)
}
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
@@ -88,22 +70,20 @@ func encrypt(plaintext []byte, passphrase []byte) (encrypted []byte, err error)
if err != nil {
return encrypted, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return encrypted, err
}
ciphertextByte := gcm.Seal(
nonce,
nonce,
plaintext,
nil)
return ciphertextByte, nil
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err error) {
if string(encrypted) == "false" {
return []byte("false"), nil
}
block, err := aes.NewCipher(passphrase)
if err != nil {
return encrypted, errors.Wrap(err, "Error creating cypher block")
@@ -120,11 +100,8 @@ func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err err
}
nonce, ciphertextByteClean := encrypted[:nonceSize], encrypted[nonceSize:]
plaintextByte, err = gcm.Open(
nil,
nonce,
ciphertextByteClean,
nil)
plaintextByte, err = gcm.Open(nil, nonce, ciphertextByteClean, nil)
if err != nil {
return encrypted, errors.Wrap(err, "Error decrypting text")
}

View File

@@ -10,7 +10,7 @@ import (
)
const (
jsonobject = `{"LogoURL":"","BlackListedLabels":[],"AuthenticationMethod":1,"InternalAuthSettings": {"RequiredPasswordLength": 12}"LDAPSettings":{"AnonymousMode":true,"ReaderDN":"","URL":"","TLSConfig":{"TLS":false,"TLSSkipVerify":false},"StartTLS":false,"SearchSettings":[{"BaseDN":"","Filter":"","UserNameAttribute":""}],"GroupSearchSettings":[{"GroupBaseDN":"","GroupFilter":"","GroupAttribute":""}],"AutoCreateUsers":true},"OAuthSettings":{"ClientID":"","AccessTokenURI":"","AuthorizationURI":"","ResourceURI":"","RedirectURI":"","UserIdentifier":"","Scopes":"","OAuthAutoCreateUsers":false,"DefaultTeamID":0,"SSO":true,"LogoutURI":"","KubeSecretKey":"j0zLVtY/lAWBk62ByyF0uP80SOXaitsABP0TTJX8MhI="},"OpenAMTConfiguration":{"Enabled":false,"MPSServer":"","MPSUser":"","MPSPassword":"","MPSToken":"","CertFileContent":"","CertFileName":"","CertFilePassword":"","DomainName":""},"FeatureFlagSettings":{},"SnapshotInterval":"5m","TemplatesURL":"https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json","EdgeAgentCheckinInterval":5,"EnableEdgeComputeFeatures":false,"UserSessionTimeout":"8h","KubeconfigExpiry":"0","EnableTelemetry":true,"HelmRepositoryURL":"https://charts.bitnami.com/bitnami","KubectlShellImage":"portainer/kubectl-shell","DisplayDonationHeader":false,"DisplayExternalContributors":false,"EnableHostManagementFeatures":false,"AllowVolumeBrowserForRegularUsers":false,"AllowBindMountsForRegularUsers":false,"AllowPrivilegedModeForRegularUsers":false,"AllowHostNamespaceForRegularUsers":false,"AllowStackManagementForRegularUsers":false,"AllowDeviceMappingForRegularUsers":false,"AllowContainerCapabilitiesForRegularUsers":false}`
jsonobject = `{"LogoURL":"","BlackListedLabels":[],"AuthenticationMethod":1,"InternalAuthSettings": {"RequiredPasswordLength": 12}"LDAPSettings":{"AnonymousMode":true,"ReaderDN":"","URL":"","TLSConfig":{"TLS":false,"TLSSkipVerify":false},"StartTLS":false,"SearchSettings":[{"BaseDN":"","Filter":"","UserNameAttribute":""}],"GroupSearchSettings":[{"GroupBaseDN":"","GroupFilter":"","GroupAttribute":""}],"AutoCreateUsers":true},"OAuthSettings":{"ClientID":"","AccessTokenURI":"","AuthorizationURI":"","ResourceURI":"","RedirectURI":"","UserIdentifier":"","Scopes":"","OAuthAutoCreateUsers":false,"DefaultTeamID":0,"SSO":true,"LogoutURI":"","KubeSecretKey":"j0zLVtY/lAWBk62ByyF0uP80SOXaitsABP0TTJX8MhI="},"OpenAMTConfiguration":{"Enabled":false,"MPSServer":"","MPSUser":"","MPSPassword":"","MPSToken":"","CertFileContent":"","CertFileName":"","CertFilePassword":"","DomainName":""},"FeatureFlagSettings":{},"SnapshotInterval":"5m","TemplatesURL":"https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json","EdgeAgentCheckinInterval":5,"EnableEdgeComputeFeatures":false,"UserSessionTimeout":"8h","KubeconfigExpiry":"0","EnableTelemetry":true,"HelmRepositoryURL":"https://kubernetes.github.io/ingress-nginx","KubectlShellImage":"portainer/kubectl-shell","DisplayDonationHeader":false,"DisplayExternalContributors":false,"EnableHostManagementFeatures":false,"AllowVolumeBrowserForRegularUsers":false,"AllowBindMountsForRegularUsers":false,"AllowPrivilegedModeForRegularUsers":false,"AllowHostNamespaceForRegularUsers":false,"AllowStackManagementForRegularUsers":false,"AllowDeviceMappingForRegularUsers":false,"AllowContainerCapabilitiesForRegularUsers":false}`
passphrase = "my secret key"
)
@@ -25,7 +25,7 @@ func Test_MarshalObjectUnencrypted(t *testing.T) {
uuid := uuid.Must(uuid.NewV4())
tests := []struct {
object interface{}
object any
expected string
}{
{
@@ -57,7 +57,7 @@ func Test_MarshalObjectUnencrypted(t *testing.T) {
expected: uuid.String(),
},
{
object: map[string]interface{}{"key": "value"},
object: map[string]any{"key": "value"},
expected: `{"key":"value"}`,
},
{
@@ -73,11 +73,11 @@ func Test_MarshalObjectUnencrypted(t *testing.T) {
expected: `["1","2","3"]`,
},
{
object: []map[string]interface{}{{"key1": "value1"}, {"key2": "value2"}},
object: []map[string]any{{"key1": "value1"}, {"key2": "value2"}},
expected: `[{"key1":"value1"},{"key2":"value2"}]`,
},
{
object: []interface{}{1, "2", false, map[string]interface{}{"key1": "value1"}},
object: []any{1, "2", false, map[string]any{"key1": "value1"}},
expected: `[1,"2",false,{"key1":"value1"}]`,
},
}
@@ -129,7 +129,7 @@ func Test_UnMarshalObjectUnencrypted(t *testing.T) {
var object string
err := conn.UnmarshalObject(test.object, &object)
is.NoError(err)
is.Equal(test.expected, string(object))
is.Equal(test.expected, object)
})
}
}

View File

@@ -2,9 +2,11 @@ package boltdb
import (
"bytes"
"fmt"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
bolt "go.etcd.io/bbolt"
)
@@ -19,21 +21,45 @@ func (tx *DbTransaction) SetServiceName(bucketName string) error {
return err
}
func (tx *DbTransaction) GetObject(bucketName string, key []byte, object interface{}) error {
func (tx *DbTransaction) GetObject(bucketName string, key []byte, object any) error {
bucket := tx.tx.Bucket([]byte(bucketName))
value := bucket.Get(key)
if value == nil {
return dserrors.ErrObjectNotFound
return fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
}
data := make([]byte, len(value))
copy(data, value)
return tx.conn.UnmarshalObjectWithJsoniter(data, object)
return tx.conn.UnmarshalObject(value, object)
}
func (tx *DbTransaction) UpdateObject(bucketName string, key []byte, object interface{}) error {
func (tx *DbTransaction) GetRawBytes(bucketName string, key []byte) ([]byte, error) {
bucket := tx.tx.Bucket([]byte(bucketName))
value := bucket.Get(key)
if value == nil {
return nil, fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
}
if tx.conn.getEncryptionKey() != nil {
var err error
if value, err = decrypt(value, tx.conn.getEncryptionKey()); err != nil {
return value, errors.Wrap(err, "Failed decrypting object")
}
}
return value, nil
}
func (tx *DbTransaction) KeyExists(bucketName string, key []byte) (bool, error) {
bucket := tx.tx.Bucket([]byte(bucketName))
value := bucket.Get(key)
return value != nil, nil
}
func (tx *DbTransaction) UpdateObject(bucketName string, key []byte, object any) error {
data, err := tx.conn.MarshalObject(object)
if err != nil {
return err
@@ -48,7 +74,9 @@ func (tx *DbTransaction) DeleteObject(bucketName string, key []byte) error {
return bucket.Delete(key)
}
func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error {
func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj any, matchingFn func(o any) (id int, ok bool)) error {
var ids []int
bucket := tx.tx.Bucket([]byte(bucketName))
cursor := bucket.Cursor()
@@ -58,11 +86,14 @@ func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj interface{}, ma
return err
}
if id, ok := matching(obj); ok {
err := bucket.Delete(tx.conn.ConvertToKey(id))
if err != nil {
return err
}
if id, ok := matchingFn(obj); ok {
ids = append(ids, id)
}
}
for _, id := range ids {
if err := bucket.Delete(tx.conn.ConvertToKey(id)); err != nil {
return err
}
}
@@ -71,9 +102,10 @@ func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj interface{}, ma
func (tx *DbTransaction) GetNextIdentifier(bucketName string) int {
bucket := tx.tx.Bucket([]byte(bucketName))
id, err := bucket.NextSequence()
if err != nil {
log.Error().Err(err).Str("bucket", bucketName).Msg("failed to get the next identifer")
log.Error().Err(err).Str("bucket", bucketName).Msg("failed to get the next identifier")
return 0
}
@@ -81,7 +113,7 @@ func (tx *DbTransaction) GetNextIdentifier(bucketName string) int {
return int(id)
}
func (tx *DbTransaction) CreateObject(bucketName string, fn func(uint64) (int, interface{})) error {
func (tx *DbTransaction) CreateObject(bucketName string, fn func(uint64) (int, any)) error {
bucket := tx.tx.Bucket([]byte(bucketName))
seqId, _ := bucket.NextSequence()
@@ -92,10 +124,10 @@ func (tx *DbTransaction) CreateObject(bucketName string, fn func(uint64) (int, i
return err
}
return bucket.Put(tx.conn.ConvertToKey(int(id)), data)
return bucket.Put(tx.conn.ConvertToKey(id), data)
}
func (tx *DbTransaction) CreateObjectWithId(bucketName string, id int, obj interface{}) error {
func (tx *DbTransaction) CreateObjectWithId(bucketName string, id int, obj any) error {
bucket := tx.tx.Bucket([]byte(bucketName))
data, err := tx.conn.MarshalObject(obj)
if err != nil {
@@ -105,7 +137,7 @@ func (tx *DbTransaction) CreateObjectWithId(bucketName string, id int, obj inter
return bucket.Put(tx.conn.ConvertToKey(id), data)
}
func (tx *DbTransaction) CreateObjectWithStringId(bucketName string, id []byte, obj interface{}) error {
func (tx *DbTransaction) CreateObjectWithStringId(bucketName string, id []byte, obj any) error {
bucket := tx.tx.Bucket([]byte(bucketName))
data, err := tx.conn.MarshalObject(obj)
if err != nil {
@@ -115,54 +147,29 @@ func (tx *DbTransaction) CreateObjectWithStringId(bucketName string, id []byte,
return bucket.Put(id, data)
}
func (tx *DbTransaction) GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
func (tx *DbTransaction) GetAll(bucketName string, obj any, appendFn func(o any) (any, error)) error {
bucket := tx.tx.Bucket([]byte(bucketName))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
return bucket.ForEach(func(k []byte, v []byte) error {
err := tx.conn.UnmarshalObject(v, obj)
if err == nil {
obj, err = appendFn(obj)
}
return err
})
}
func (tx *DbTransaction) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, appendFn func(o any) (any, error)) error {
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.UnmarshalObject(v, obj)
if err != nil {
return err
}
obj, err = append(obj)
if err != nil {
return err
}
}
return nil
}
func (tx *DbTransaction) GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
bucket := tx.tx.Bucket([]byte(bucketName))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
err := tx.conn.UnmarshalObjectWithJsoniter(v, obj)
if err != nil {
return err
}
obj, err = append(obj)
if err != nil {
return err
}
}
return nil
}
func (tx *DbTransaction) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error {
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)
if err != nil {
return err
}
obj, err = append(obj)
obj, err = appendFn(obj)
if err != nil {
return err
}

View File

@@ -5,7 +5,7 @@ import (
"testing"
portainer "github.com/portainer/portainer/api"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/dataservices"
)
const testBucketName = "test-bucket"
@@ -97,7 +97,7 @@ func TestTxs(t *testing.T) {
err = conn.ViewTx(func(tx portainer.Transaction) error {
return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj)
})
if err != dserrors.ErrObjectNotFound {
if !dataservices.IsErrObjectNotFound(err) {
t.Fatal(err)
}

View File

@@ -9,8 +9,7 @@ import (
// NewDatabase should use config options to return a connection to the requested database
func NewDatabase(storeType, storePath string, encryptionKey []byte) (connection portainer.Connection, err error) {
switch storeType {
case "boltdb":
if storeType == "boltdb" {
return &boltdb.DbConnection{
Path: storePath,
EncryptionKey: encryptionKey,

View File

@@ -1,49 +1,50 @@
package apikeyrepository
import (
"bytes"
"errors"
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "api_key"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "api_key"
// Service represents a service for managing api-key data.
type Service struct {
connection portainer.Connection
dataservices.BaseDataService[portainer.APIKey, portainer.APIKeyID]
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
if err := connection.SetServiceName(BucketName); err != nil {
return nil, err
}
return &Service{
connection: connection,
BaseDataService: dataservices.BaseDataService[portainer.APIKey, portainer.APIKeyID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
// 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(
err := service.Connection.GetAll(
BucketName,
&portainer.APIKey{},
func(obj interface{}) (interface{}, error) {
func(obj any) (any, error) {
record, ok := obj.(*portainer.APIKey)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
return nil, fmt.Errorf("failed to convert to APIKey object: %s", obj)
}
if record.UserID == userID {
@@ -58,19 +59,19 @@ 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(
stop := errors.New("ok")
err := service.Connection.GetAll(
BucketName,
&portainer.APIKey{},
func(obj interface{}) (interface{}, error) {
func(obj any) (any, error) {
key, ok := obj.(*portainer.APIKey)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
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
}
@@ -78,48 +79,25 @@ func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, err
return &portainer.APIKey{}, nil
})
if err == stop {
if errors.Is(err, stop) {
return k, nil
}
if err == nil {
return nil, errors.ErrObjectNotFound
return nil, dserrors.ErrObjectNotFound
}
return nil, err
}
// CreateAPIKey creates a new APIKey object.
func (service *Service) CreateAPIKey(record *portainer.APIKey) error {
return service.connection.CreateObject(
// Create creates a new APIKey object.
func (service *Service) Create(record *portainer.APIKey) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
func(id uint64) (int, any) {
record.ID = portainer.APIKeyID(id)
return int(record.ID), record
},
)
}
// GetAPIKey retrieves an existing APIKey object by api key ID.
func (service *Service) GetAPIKey(keyID portainer.APIKeyID) (*portainer.APIKey, error) {
var key portainer.APIKey
identifier := service.connection.ConvertToKey(int(keyID))
err := service.connection.GetObject(BucketName, identifier, &key)
if err != nil {
return nil, err
}
return &key, nil
}
func (service *Service) UpdateAPIKey(key *portainer.APIKey) error {
identifier := service.connection.ConvertToKey(int(key.ID))
return service.connection.UpdateObject(BucketName, identifier, key)
}
func (service *Service) DeleteAPIKey(ID portainer.APIKeyID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
}

80
api/dataservices/base.go Normal file
View File

@@ -0,0 +1,80 @@
package dataservices
import (
portainer "github.com/portainer/portainer/api"
"golang.org/x/exp/constraints"
)
type BaseCRUD[T any, I constraints.Integer] interface {
Create(element *T) error
Read(ID I) (*T, error)
Exists(ID I) (bool, error)
ReadAll() ([]T, error)
Update(ID I, element *T) error
Delete(ID I) error
}
type BaseDataService[T any, I constraints.Integer] struct {
Bucket string
Connection portainer.Connection
}
func (s *BaseDataService[T, I]) BucketName() string {
return s.Bucket
}
func (service *BaseDataService[T, I]) Tx(tx portainer.Transaction) BaseDataServiceTx[T, I] {
return BaseDataServiceTx[T, I]{
Bucket: service.Bucket,
Connection: service.Connection,
Tx: tx,
}
}
func (service BaseDataService[T, I]) Read(ID I) (*T, error) {
var element *T
return element, service.Connection.ViewTx(func(tx portainer.Transaction) error {
var err error
element, err = service.Tx(tx).Read(ID)
return err
})
}
func (service BaseDataService[T, I]) Exists(ID I) (bool, error) {
var exists bool
err := service.Connection.ViewTx(func(tx portainer.Transaction) error {
var err error
exists, err = service.Tx(tx).Exists(ID)
return err
})
return exists, err
}
func (service BaseDataService[T, I]) ReadAll() ([]T, error) {
var collection = make([]T, 0)
return collection, service.Connection.ViewTx(func(tx portainer.Transaction) error {
var err error
collection, err = service.Tx(tx).ReadAll()
return err
})
}
func (service BaseDataService[T, I]) Update(ID I, element *T) error {
return service.Connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Tx(tx).Update(ID, element)
})
}
func (service BaseDataService[T, I]) Delete(ID I) error {
return service.Connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Tx(tx).Delete(ID)
})
}

View File

@@ -0,0 +1,55 @@
package dataservices
import (
portainer "github.com/portainer/portainer/api"
"golang.org/x/exp/constraints"
)
type BaseDataServiceTx[T any, I constraints.Integer] struct {
Bucket string
Connection portainer.Connection
Tx portainer.Transaction
}
func (service BaseDataServiceTx[T, I]) BucketName() string {
return service.Bucket
}
func (service BaseDataServiceTx[T, I]) Read(ID I) (*T, error) {
var element T
identifier := service.Connection.ConvertToKey(int(ID))
err := service.Tx.GetObject(service.Bucket, identifier, &element)
if err != nil {
return nil, err
}
return &element, nil
}
func (service BaseDataServiceTx[T, I]) Exists(ID I) (bool, error) {
identifier := service.Connection.ConvertToKey(int(ID))
return service.Tx.KeyExists(service.Bucket, identifier)
}
func (service BaseDataServiceTx[T, I]) ReadAll() ([]T, error) {
var collection = make([]T, 0)
return collection, service.Tx.GetAll(
service.Bucket,
new(T),
AppendFn(&collection),
)
}
func (service BaseDataServiceTx[T, I]) Update(ID I, element *T) error {
identifier := service.Connection.ConvertToKey(int(ID))
return service.Tx.UpdateObject(service.Bucket, identifier, element)
}
func (service BaseDataServiceTx[T, I]) Delete(ID I) error {
identifier := service.Connection.ConvertToKey(int(ID))
return service.Tx.DeleteObject(service.Bucket, identifier)
}

View File

@@ -1,25 +1,16 @@
package customtemplate
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "customtemplates"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "customtemplates"
// Service represents a service for managing custom template data.
type Service struct {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
dataservices.BaseDataService[portainer.CustomTemplate, portainer.CustomTemplateID]
}
// NewService creates a new instance of a service.
@@ -30,64 +21,20 @@ func NewService(connection portainer.Connection) (*Service, error) {
}
return &Service{
connection: connection,
BaseDataService: dataservices.BaseDataService[portainer.CustomTemplate, portainer.CustomTemplateID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
// CustomTemplates return an array containing all the custom templates.
func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
var customTemplates = make([]portainer.CustomTemplate, 0)
err := service.connection.GetAll(
BucketName,
&portainer.CustomTemplate{},
func(obj interface{}) (interface{}, error) {
//var tag portainer.Tag
customTemplate, ok := obj.(*portainer.CustomTemplate)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to CustomTemplate object")
return nil, fmt.Errorf("Failed to convert to CustomTemplate object: %s", obj)
}
customTemplates = append(customTemplates, *customTemplate)
return &portainer.CustomTemplate{}, nil
})
return customTemplates, err
}
// CustomTemplate returns an custom template by ID.
func (service *Service) CustomTemplate(ID portainer.CustomTemplateID) (*portainer.CustomTemplate, error) {
var customTemplate portainer.CustomTemplate
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &customTemplate)
if err != nil {
return nil, err
}
return &customTemplate, nil
}
// UpdateCustomTemplate updates an custom template.
func (service *Service) UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, customTemplate)
}
// DeleteCustomTemplate deletes an custom template.
func (service *Service) DeleteCustomTemplate(ID portainer.CustomTemplateID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
}
// CreateCustomTemplate uses the existing id and saves it.
// TODO: where does the ID come from, and is it safe?
func (service *Service) Create(customTemplate *portainer.CustomTemplate) error {
return service.connection.CreateObjectWithId(BucketName, int(customTemplate.ID), customTemplate)
return service.Connection.CreateObjectWithId(BucketName, int(customTemplate.ID), customTemplate)
}
// GetNextIdentifier returns the next identifier for a custom template.
func (service *Service) GetNextIdentifier() int {
return service.connection.GetNextIdentifier(BucketName)
return service.Connection.GetNextIdentifier(BucketName)
}

View File

@@ -2,6 +2,7 @@ package edgegroup
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
@@ -9,7 +10,7 @@ const BucketName = "edgegroups"
// Service represents a service for managing Edge group data.
type Service struct {
connection portainer.Connection
dataservices.BaseDataService[portainer.EdgeGroup, portainer.EdgeGroupID]
}
func (service *Service) BucketName() string {
@@ -24,69 +25,36 @@ func NewService(connection portainer.Connection) (*Service, error) {
}
return &Service{
connection: connection,
BaseDataService: dataservices.BaseDataService[portainer.EdgeGroup, portainer.EdgeGroupID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
service: service,
tx: tx,
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.EdgeGroup, portainer.EdgeGroupID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
}
}
// EdgeGroups return a slice containing all the Edge groups.
func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
var groups []portainer.EdgeGroup
var err error
err = service.connection.ViewTx(func(tx portainer.Transaction) error {
groups, err = service.Tx(tx).EdgeGroups()
return err
})
return groups, err
}
// EdgeGroup returns an Edge group by ID.
func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error) {
var group *portainer.EdgeGroup
var err error
err = service.connection.ViewTx(func(tx portainer.Transaction) error {
group, err = service.Tx(tx).EdgeGroup(ID)
return err
})
return group, err
}
// UpdateEdgeGroup updates an edge group.
func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, group)
}
// Deprecated: UpdateEdgeGroupFunc updates an edge group inside a transaction avoiding data races.
func (service *Service) UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFunc func(edgeGroup *portainer.EdgeGroup)) error {
id := service.connection.ConvertToKey(int(ID))
id := service.Connection.ConvertToKey(int(ID))
edgeGroup := &portainer.EdgeGroup{}
return service.connection.UpdateObjectFunc(BucketName, id, edgeGroup, func() {
return service.Connection.UpdateObjectFunc(BucketName, id, edgeGroup, func() {
updateFunc(edgeGroup)
})
}
// DeleteEdgeGroup deletes an Edge group.
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Tx(tx).DeleteEdgeGroup(ID)
})
}
// CreateEdgeGroup assign an ID to a new Edge group and saves it.
func (service *Service) Create(group *portainer.EdgeGroup) error {
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Tx(tx).Create(group)
})
}

View File

@@ -2,60 +2,13 @@ package edgegroup
import (
"errors"
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
type ServiceTx struct {
service *Service
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// EdgeGroups return a slice containing all the Edge groups.
func (service ServiceTx) EdgeGroups() ([]portainer.EdgeGroup, error) {
var groups = make([]portainer.EdgeGroup, 0)
err := service.tx.GetAllWithJsoniter(
BucketName,
&portainer.EdgeGroup{},
func(obj interface{}) (interface{}, error) {
group, ok := obj.(*portainer.EdgeGroup)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeGroup object")
return nil, fmt.Errorf("Failed to convert to EdgeGroup object: %s", obj)
}
groups = append(groups, *group)
return &portainer.EdgeGroup{}, nil
})
return groups, err
}
// EdgeGroup returns an Edge group by ID.
func (service ServiceTx) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error) {
var group portainer.EdgeGroup
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.GetObject(BucketName, identifier, &group)
if err != nil {
return nil, err
}
return &group, nil
}
// UpdateEdgeGroup updates an edge group.
func (service ServiceTx) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
identifier := service.service.connection.ConvertToKey(int(ID))
return service.tx.UpdateObject(BucketName, identifier, group)
dataservices.BaseDataServiceTx[portainer.EdgeGroup, portainer.EdgeGroupID]
}
// UpdateEdgeGroupFunc is a no-op inside a transaction.
@@ -63,16 +16,10 @@ func (service ServiceTx) UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFun
return errors.New("cannot be called inside a transaction")
}
// DeleteEdgeGroup deletes an Edge group.
func (service ServiceTx) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
identifier := service.service.connection.ConvertToKey(int(ID))
return service.tx.DeleteObject(BucketName, identifier)
}
func (service ServiceTx) Create(group *portainer.EdgeGroup) error {
return service.tx.CreateObject(
return service.Tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
func(id uint64) (int, any) {
group.ID = portainer.EdgeGroupID(id)
return int(group.ID), group
},

View File

@@ -1,11 +1,8 @@
package edgejob
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
@@ -13,11 +10,7 @@ const BucketName = "edgejobs"
// Service represents a service for managing edge jobs data.
type Service struct {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
dataservices.BaseDataService[portainer.EdgeJob, portainer.EdgeJobID]
}
// NewService creates a new instance of a service.
@@ -28,86 +21,50 @@ func NewService(connection portainer.Connection) (*Service, error) {
}
return &Service{
connection: connection,
BaseDataService: dataservices.BaseDataService[portainer.EdgeJob, portainer.EdgeJobID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
service: service,
tx: tx,
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.EdgeJob, portainer.EdgeJobID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
}
}
// EdgeJobs returns a list of Edge jobs
func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
var edgeJobs = make([]portainer.EdgeJob, 0)
err := service.connection.GetAll(
BucketName,
&portainer.EdgeJob{},
func(obj interface{}) (interface{}, error) {
job, ok := obj.(*portainer.EdgeJob)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object")
return nil, fmt.Errorf("Failed to convert to EdgeJob object: %s", obj)
}
edgeJobs = append(edgeJobs, *job)
return &portainer.EdgeJob{}, nil
})
return edgeJobs, err
}
// EdgeJob returns an Edge job by ID
func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
var edgeJob portainer.EdgeJob
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &edgeJob)
if err != nil {
return nil, err
}
return &edgeJob, nil
}
// Create creates a new EdgeJob
func (service *Service) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
func (service *Service) Create(edgeJob *portainer.EdgeJob) error {
return service.CreateWithID(portainer.EdgeJobID(service.GetNextIdentifier()), edgeJob)
}
// CreateWithID creates a new EdgeJob
func (service *Service) CreateWithID(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
edgeJob.ID = ID
return service.connection.CreateObjectWithId(
return service.Connection.CreateObjectWithId(
BucketName,
int(edgeJob.ID),
edgeJob,
)
}
// Deprecated: use UpdateEdgeJobFunc instead
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, edgeJob)
}
// UpdateEdgeJobFunc updates an edge job inside a transaction avoiding data races.
func (service *Service) UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc func(edgeJob *portainer.EdgeJob)) error {
id := service.connection.ConvertToKey(int(ID))
id := service.Connection.ConvertToKey(int(ID))
edgeJob := &portainer.EdgeJob{}
return service.connection.UpdateObjectFunc(BucketName, id, edgeJob, func() {
return service.Connection.UpdateObjectFunc(BucketName, id, edgeJob, func() {
updateFunc(edgeJob)
})
}
// DeleteEdgeJob deletes an Edge job
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service *Service) GetNextIdentifier() int {
return service.connection.GetNextIdentifier(BucketName)
return service.Connection.GetNextIdentifier(BucketName)
}

View File

@@ -2,68 +2,25 @@ package edgejob
import (
"errors"
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
type ServiceTx struct {
service *Service
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// EdgeJobs returns a list of Edge jobs
func (service ServiceTx) EdgeJobs() ([]portainer.EdgeJob, error) {
var edgeJobs = make([]portainer.EdgeJob, 0)
err := service.tx.GetAll(
BucketName,
&portainer.EdgeJob{},
func(obj interface{}) (interface{}, error) {
job, ok := obj.(*portainer.EdgeJob)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object")
return nil, fmt.Errorf("failed to convert to EdgeJob object: %s", obj)
}
edgeJobs = append(edgeJobs, *job)
return &portainer.EdgeJob{}, nil
})
return edgeJobs, err
}
// EdgeJob returns an Edge job by ID
func (service ServiceTx) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
var edgeJob portainer.EdgeJob
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.GetObject(BucketName, identifier, &edgeJob)
if err != nil {
return nil, err
}
return &edgeJob, nil
dataservices.BaseDataServiceTx[portainer.EdgeJob, portainer.EdgeJobID]
}
// Create creates a new EdgeJob
func (service ServiceTx) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
edgeJob.ID = ID
return service.tx.CreateObjectWithId(BucketName, int(edgeJob.ID), edgeJob)
func (service ServiceTx) Create(edgeJob *portainer.EdgeJob) error {
return service.CreateWithID(portainer.EdgeJobID(service.GetNextIdentifier()), edgeJob)
}
// UpdateEdgeJob updates an edge job
func (service ServiceTx) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
identifier := service.service.connection.ConvertToKey(int(ID))
return service.tx.UpdateObject(BucketName, identifier, edgeJob)
// CreateWithID creates a new EdgeJob
func (service ServiceTx) CreateWithID(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
edgeJob.ID = ID
return service.Tx.CreateObjectWithId(BucketName, int(edgeJob.ID), edgeJob)
}
// UpdateEdgeJobFunc is a no-op inside a transaction.
@@ -71,14 +28,7 @@ func (service ServiceTx) UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc fu
return errors.New("cannot be called inside a transaction")
}
// DeleteEdgeJob deletes an Edge job
func (service ServiceTx) DeleteEdgeJob(ID portainer.EdgeJobID) error {
identifier := service.service.connection.ConvertToKey(int(ID))
return service.tx.DeleteObject(BucketName, identifier)
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service ServiceTx) GetNextIdentifier() int {
return service.tx.GetNextIdentifier(BucketName)
return service.Tx.GetNextIdentifier(BucketName)
}

View File

@@ -1,12 +1,10 @@
package edgestack
import (
"fmt"
"sync"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
@@ -17,7 +15,7 @@ type Service struct {
connection portainer.Connection
idxVersion map[portainer.EdgeStackID]int
mu sync.RWMutex
cacheInvalidationFn func(portainer.EdgeStackID)
cacheInvalidationFn func(portainer.Transaction, portainer.EdgeStackID)
}
func (service *Service) BucketName() string {
@@ -25,7 +23,7 @@ func (service *Service) BucketName() string {
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection, cacheInvalidationFn func(portainer.EdgeStackID)) (*Service, error) {
func NewService(connection portainer.Connection, cacheInvalidationFn func(portainer.Transaction, portainer.EdgeStackID)) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
@@ -38,7 +36,7 @@ func NewService(connection portainer.Connection, cacheInvalidationFn func(portai
}
if s.cacheInvalidationFn == nil {
s.cacheInvalidationFn = func(portainer.EdgeStackID) {}
s.cacheInvalidationFn = func(portainer.Transaction, portainer.EdgeStackID) {}
}
es, err := s.EdgeStacks()
@@ -64,22 +62,11 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
var stacks = make([]portainer.EdgeStack, 0)
err := service.connection.GetAll(
return stacks, service.connection.GetAll(
BucketName,
&portainer.EdgeStack{},
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(*portainer.EdgeStack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object")
return nil, fmt.Errorf("Failed to convert to EdgeStack object: %s", obj)
}
stacks = append(stacks, *stack)
return &portainer.EdgeStack{}, nil
})
return stacks, err
dataservices.AppendFn(&stacks),
)
}
// EdgeStack returns an Edge stack by ID.
@@ -119,7 +106,7 @@ func (service *Service) Create(id portainer.EdgeStackID, edgeStack *portainer.Ed
service.mu.Lock()
service.idxVersion[id] = edgeStack.Version
service.cacheInvalidationFn(id)
service.cacheInvalidationFn(service.connection, id)
service.mu.Unlock()
return nil
@@ -138,7 +125,7 @@ func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *por
}
service.idxVersion[ID] = edgeStack.Version
service.cacheInvalidationFn(ID)
service.cacheInvalidationFn(service.connection, ID)
return nil
}
@@ -155,10 +142,15 @@ func (service *Service) UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFunc
updateFunc(edgeStack)
service.idxVersion[ID] = edgeStack.Version
service.cacheInvalidationFn(ID)
service.cacheInvalidationFn(service.connection, ID)
})
}
// UpdateEdgeStackFuncTx is a helper function used to call UpdateEdgeStackFunc inside a transaction.
func (service *Service) UpdateEdgeStackFuncTx(tx portainer.Transaction, ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error {
return service.Tx(tx).UpdateEdgeStackFunc(ID, updateFunc)
}
// DeleteEdgeStack deletes an Edge stack.
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
service.mu.Lock()
@@ -173,7 +165,7 @@ func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
delete(service.idxVersion, ID)
service.cacheInvalidationFn(ID)
service.cacheInvalidationFn(service.connection, ID)
return nil
}

View File

@@ -1,7 +1,6 @@
package edgestack
import (
"errors"
"fmt"
portainer "github.com/portainer/portainer/api"
@@ -25,11 +24,11 @@ func (service ServiceTx) EdgeStacks() ([]portainer.EdgeStack, error) {
err := service.tx.GetAll(
BucketName,
&portainer.EdgeStack{},
func(obj interface{}) (interface{}, error) {
func(obj any) (any, error) {
stack, ok := obj.(*portainer.EdgeStack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object")
return nil, fmt.Errorf("Failed to convert to EdgeStack object: %s", obj)
return nil, fmt.Errorf("failed to convert to EdgeStack object: %s", obj)
}
stacks = append(stacks, *stack)
@@ -45,8 +44,7 @@ func (service ServiceTx) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeSta
var stack portainer.EdgeStack
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.GetObject(BucketName, identifier, &stack)
if err != nil {
if err := service.tx.GetObject(BucketName, identifier, &stack); err != nil {
return nil, err
}
@@ -66,18 +64,17 @@ func (service ServiceTx) EdgeStackVersion(ID portainer.EdgeStackID) (int, bool)
func (service ServiceTx) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
edgeStack.ID = id
err := service.tx.CreateObjectWithId(
if err := service.tx.CreateObjectWithId(
BucketName,
int(edgeStack.ID),
edgeStack,
)
if err != nil {
); err != nil {
return err
}
service.service.mu.Lock()
service.service.idxVersion[id] = edgeStack.Version
service.service.cacheInvalidationFn(id)
service.service.cacheInvalidationFn(service.tx, id)
service.service.mu.Unlock()
return nil
@@ -90,20 +87,26 @@ func (service ServiceTx) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *po
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.UpdateObject(BucketName, identifier, edgeStack)
if err != nil {
if err := service.tx.UpdateObject(BucketName, identifier, edgeStack); err != nil {
return err
}
service.service.idxVersion[ID] = edgeStack.Version
service.service.cacheInvalidationFn(ID)
service.service.cacheInvalidationFn(service.tx, ID)
return nil
}
// UpdateEdgeStackFunc is a no-op inside a transaction.
// Deprecated: use UpdateEdgeStack inside a transaction instead.
func (service ServiceTx) UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error {
return errors.New("cannot be called inside a transaction")
edgeStack, err := service.EdgeStack(ID)
if err != nil {
return err
}
updateFunc(edgeStack)
return service.UpdateEdgeStack(ID, edgeStack)
}
// DeleteEdgeStack deletes an Edge stack.
@@ -113,14 +116,13 @@ func (service ServiceTx) DeleteEdgeStack(ID portainer.EdgeStackID) error {
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.DeleteObject(BucketName, identifier)
if err != nil {
if err := service.tx.DeleteObject(BucketName, identifier); err != nil {
return err
}
delete(service.service.idxVersion, ID)
service.service.cacheInvalidationFn(ID)
service.service.cacheInvalidationFn(service.tx, ID)
return nil
}

View File

@@ -5,6 +5,9 @@ import (
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
// BucketName represents the name of the bucket where this service stores data.
@@ -34,7 +37,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
idxEdgeID: make(map[string]portainer.EndpointID),
}
es, err := s.Endpoints()
es, err := s.endpoints()
if err != nil {
return nil, err
}
@@ -89,8 +92,7 @@ func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
})
}
// Endpoints return an array containing all the environments(endpoints).
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
func (service *Service) endpoints() ([]portainer.Endpoint, error) {
var endpoints []portainer.Endpoint
var err error
@@ -99,8 +101,14 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
return err
})
return endpoints, err
}
// Endpoints return an array containing all the environments(endpoints).
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
endpoints, err := service.endpoints()
if err != nil {
return endpoints, err
return nil, err
}
for i, e := range endpoints {
@@ -139,15 +147,35 @@ 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
service.connection.UpdateTx(func(tx portainer.Transaction) error {
if err := service.connection.UpdateTx(func(tx portainer.Transaction) error {
identifier = service.Tx(tx).GetNextIdentifier()
return nil
})
}); err != nil {
log.Error().Err(err).Str("bucket", BucketName).Msg("could not get the next identifier")
}
return identifier
}

View File

@@ -1,9 +1,8 @@
package endpoint
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
@@ -21,13 +20,15 @@ func (service ServiceTx) BucketName() string {
// Endpoint returns an environment(endpoint) by ID.
func (service ServiceTx) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
var endpoint portainer.Endpoint
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.GetObject(BucketName, identifier, &endpoint)
if err != nil {
if err := service.tx.GetObject(BucketName, identifier, &endpoint); err != nil {
return nil, err
}
endpoint.LastCheckInDate, _ = service.service.Heartbeat(ID)
return &endpoint, nil
}
@@ -35,8 +36,7 @@ func (service ServiceTx) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.UpdateObject(BucketName, identifier, endpoint)
if err != nil {
if err := service.tx.UpdateObject(BucketName, identifier, endpoint); err != nil {
return err
}
@@ -44,6 +44,7 @@ func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *porta
if len(endpoint.EdgeID) > 0 {
service.service.idxEdgeID[endpoint.EdgeID] = ID
}
service.service.heartbeats.Store(ID, endpoint.LastCheckInDate)
service.service.mu.Unlock()
@@ -56,8 +57,7 @@ func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *porta
func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.DeleteObject(BucketName, identifier)
if err != nil {
if err := service.tx.DeleteObject(BucketName, identifier); err != nil {
return err
}
@@ -65,9 +65,11 @@ func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
for edgeID, endpointID := range service.service.idxEdgeID {
if endpointID == ID {
delete(service.service.idxEdgeID, edgeID)
break
}
}
service.service.heartbeats.Delete(ID)
service.service.mu.Unlock()
@@ -80,22 +82,11 @@ func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
func (service ServiceTx) Endpoints() ([]portainer.Endpoint, error) {
var endpoints = make([]portainer.Endpoint, 0)
err := service.tx.GetAllWithJsoniter(
return endpoints, service.tx.GetAll(
BucketName,
&portainer.Endpoint{},
func(obj interface{}) (interface{}, error) {
endpoint, ok := obj.(*portainer.Endpoint)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Endpoint object")
return nil, fmt.Errorf("failed to convert to Endpoint object: %s", obj)
}
endpoints = append(endpoints, *endpoint)
return &portainer.Endpoint{}, nil
})
return endpoints, err
dataservices.AppendFn(&endpoints),
)
}
func (service ServiceTx) EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool) {
@@ -116,8 +107,7 @@ func (service ServiceTx) UpdateHeartbeat(endpointID portainer.EndpointID) {
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
func (service ServiceTx) Create(endpoint *portainer.Endpoint) error {
err := service.tx.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint)
if err != nil {
if err := service.tx.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint); err != nil {
return err
}
@@ -125,12 +115,31 @@ func (service ServiceTx) Create(endpoint *portainer.Endpoint) error {
if len(endpoint.EdgeID) > 0 {
service.service.idxEdgeID[endpoint.EdgeID] = endpoint.ID
}
service.service.heartbeats.Store(endpoint.ID, endpoint.LastCheckInDate)
service.service.mu.Unlock()
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

@@ -1,25 +1,15 @@
package endpointgroup
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"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 {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
dataservices.BaseDataService[portainer.EndpointGroup, portainer.EndpointGroupID]
}
// NewService creates a new instance of a service.
@@ -30,69 +20,28 @@ func NewService(connection portainer.Connection) (*Service, error) {
}
return &Service{
connection: connection,
BaseDataService: dataservices.BaseDataService[portainer.EndpointGroup, portainer.EndpointGroupID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
service: service,
tx: tx,
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.EndpointGroup, portainer.EndpointGroupID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
}
}
// EndpointGroup returns an environment(endpoint) group by ID.
func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
var endpointGroup portainer.EndpointGroup
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &endpointGroup)
if err != nil {
return nil, err
}
return &endpointGroup, nil
}
// UpdateEndpointGroup updates an environment(endpoint) group.
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, endpointGroup)
}
// DeleteEndpointGroup deletes an environment(endpoint) group.
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
}
// EndpointGroups return an array containing all the environment(endpoint) groups.
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
var endpointGroups = make([]portainer.EndpointGroup, 0)
err := service.connection.GetAll(
BucketName,
&portainer.EndpointGroup{},
func(obj interface{}) (interface{}, error) {
endpointGroup, ok := obj.(*portainer.EndpointGroup)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointGroup object")
return nil, fmt.Errorf("Failed to convert to EndpointGroup object: %s", obj)
}
endpointGroups = append(endpointGroups, *endpointGroup)
return &portainer.EndpointGroup{}, nil
})
return endpointGroups, err
}
// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it.
func (service *Service) Create(endpointGroup *portainer.EndpointGroup) error {
return service.connection.CreateObject(
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
func(id uint64) (int, any) {
endpointGroup.ID = portainer.EndpointGroupID(id)
return int(endpointGroup.ID), endpointGroup
},

View File

@@ -1,74 +1,19 @@
package endpointgroup
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
type ServiceTx struct {
service *Service
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// EndpointGroup returns an environment(endpoint) group by ID.
func (service ServiceTx) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
var endpointGroup portainer.EndpointGroup
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.GetObject(BucketName, identifier, &endpointGroup)
if err != nil {
return nil, err
}
return &endpointGroup, nil
}
// UpdateEndpointGroup updates an environment(endpoint) group.
func (service ServiceTx) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
identifier := service.service.connection.ConvertToKey(int(ID))
return service.tx.UpdateObject(BucketName, identifier, endpointGroup)
}
// DeleteEndpointGroup deletes an environment(endpoint) group.
func (service ServiceTx) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
identifier := service.service.connection.ConvertToKey(int(ID))
return service.tx.DeleteObject(BucketName, identifier)
}
// EndpointGroups return an array containing all the environment(endpoint) groups.
func (service ServiceTx) EndpointGroups() ([]portainer.EndpointGroup, error) {
var endpointGroups = make([]portainer.EndpointGroup, 0)
err := service.tx.GetAll(
BucketName,
&portainer.EndpointGroup{},
func(obj interface{}) (interface{}, error) {
endpointGroup, ok := obj.(*portainer.EndpointGroup)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointGroup object")
return nil, fmt.Errorf("failed to convert to EndpointGroup object: %s", obj)
}
endpointGroups = append(endpointGroups, *endpointGroup)
return &portainer.EndpointGroup{}, nil
})
return endpointGroups, err
dataservices.BaseDataServiceTx[portainer.EndpointGroup, portainer.EndpointGroupID]
}
// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it.
func (service ServiceTx) Create(endpointGroup *portainer.EndpointGroup) error {
return service.tx.CreateObject(
return service.Tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
func(id uint64) (int, any) {
endpointGroup.ID = portainer.EndpointGroupID(id)
return int(endpointGroup.ID), endpointGroup
},

View File

@@ -1,9 +1,10 @@
package endpointrelation
import (
"fmt"
"sync"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
@@ -14,22 +15,28 @@ const BucketName = "endpoint_relations"
// Service represents a service for managing environment(endpoint) relation data.
type Service struct {
connection portainer.Connection
updateStackFn func(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
connection portainer.Connection
updateStackFn func(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
updateStackFnTx func(tx portainer.Transaction, ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
endpointRelationsCache []portainer.EndpointRelation
mu sync.Mutex
}
func (service *Service) BucketName() string {
return BucketName
}
func (service *Service) RegisterUpdateStackFunction(updateFunc func(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error) {
func (service *Service) RegisterUpdateStackFunction(
updateFunc func(portainer.EdgeStackID, func(*portainer.EdgeStack)) error,
updateFuncTx func(portainer.Transaction, portainer.EdgeStackID, func(*portainer.EdgeStack)) error,
) {
service.updateStackFn = updateFunc
service.updateStackFnTx = updateFuncTx
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
if err := connection.SetServiceName(BucketName); err != nil {
return nil, err
}
@@ -49,22 +56,11 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error) {
var all = make([]portainer.EndpointRelation, 0)
err := service.connection.GetAll(
return all, service.connection.GetAll(
BucketName,
&portainer.EndpointRelation{},
func(obj interface{}) (interface{}, error) {
r, ok := obj.(*portainer.EndpointRelation)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointRelation object")
return nil, fmt.Errorf("Failed to convert to EndpointRelation object: %s", obj)
}
all = append(all, *r)
return &portainer.EndpointRelation{}, nil
})
return all, err
dataservices.AppendFn(&all),
)
}
// EndpointRelation returns a Environment(Endpoint) relation object by EndpointID
@@ -72,8 +68,7 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
var endpointRelation portainer.EndpointRelation
identifier := service.connection.ConvertToKey(int(endpointID))
err := service.connection.GetObject(BucketName, identifier, &endpointRelation)
if err != nil {
if err := service.connection.GetObject(BucketName, identifier, &endpointRelation); err != nil {
return nil, err
}
@@ -85,6 +80,10 @@ func (service *Service) Create(endpointRelation *portainer.EndpointRelation) err
err := service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
cache.Del(endpointRelation.EndpointID)
service.mu.Lock()
service.endpointRelationsCache = nil
service.mu.Unlock()
return err
}
@@ -101,6 +100,10 @@ func (service *Service) UpdateEndpointRelation(endpointID portainer.EndpointID,
updatedRelationState, _ := service.EndpointRelation(endpointID)
service.mu.Lock()
service.endpointRelationsCache = nil
service.mu.Unlock()
service.updateEdgeStacksAfterRelationChange(previousRelationState, updatedRelationState)
return nil
@@ -117,27 +120,15 @@ func (service *Service) DeleteEndpointRelation(endpointID portainer.EndpointID)
return err
}
service.mu.Lock()
service.endpointRelationsCache = nil
service.mu.Unlock()
service.updateEdgeStacksAfterRelationChange(deletedRelation, nil)
return nil
}
func (service *Service) InvalidateEdgeCacheForEdgeStack(edgeStackID portainer.EdgeStackID) {
rels, err := service.EndpointRelations()
if err != nil {
log.Error().Err(err).Msg("cannot retrieve endpoint relations")
return
}
for _, rel := range rels {
for id := range rel.EdgeStacks {
if edgeStackID == id {
cache.Del(rel.EndpointID)
}
}
}
}
func (service *Service) updateEdgeStacksAfterRelationChange(previousRelationState *portainer.EndpointRelation, updatedRelationState *portainer.EndpointRelation) {
relations, _ := service.EndpointRelations()
@@ -168,19 +159,24 @@ func (service *Service) updateEdgeStacksAfterRelationChange(previousRelationStat
// list how many time this stack is referenced in all relations
// in order to update the stack deployments count
for refStackId, refStackEnabled := range stacksToUpdate {
if refStackEnabled {
numDeployments := 0
for _, r := range relations {
for sId, enabled := range r.EdgeStacks {
if enabled && sId == refStackId {
numDeployments += 1
}
if !refStackEnabled {
continue
}
numDeployments := 0
for _, r := range relations {
for sId, enabled := range r.EdgeStacks {
if enabled && sId == refStackId {
numDeployments += 1
}
}
}
service.updateStackFn(refStackId, func(edgeStack *portainer.EdgeStack) {
edgeStack.NumDeployments = numDeployments
})
if err := service.updateStackFn(refStackId, func(edgeStack *portainer.EdgeStack) {
edgeStack.NumDeployments = numDeployments
}); err != nil {
log.Error().Err(err).Msg("could not update the number of deployments")
}
}
}

View File

@@ -1,9 +1,8 @@
package endpointrelation
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
@@ -22,22 +21,11 @@ func (service ServiceTx) BucketName() string {
func (service ServiceTx) EndpointRelations() ([]portainer.EndpointRelation, error) {
var all = make([]portainer.EndpointRelation, 0)
err := service.tx.GetAll(
return all, service.tx.GetAll(
BucketName,
&portainer.EndpointRelation{},
func(obj interface{}) (interface{}, error) {
r, ok := obj.(*portainer.EndpointRelation)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointRelation object")
return nil, fmt.Errorf("failed to convert to EndpointRelation object: %s", obj)
}
all = append(all, *r)
return &portainer.EndpointRelation{}, nil
})
return all, err
dataservices.AppendFn(&all),
)
}
// EndpointRelation returns an Environment(Endpoint) relation object by EndpointID
@@ -45,8 +33,7 @@ func (service ServiceTx) EndpointRelation(endpointID portainer.EndpointID) (*por
var endpointRelation portainer.EndpointRelation
identifier := service.service.connection.ConvertToKey(int(endpointID))
err := service.tx.GetObject(BucketName, identifier, &endpointRelation)
if err != nil {
if err := service.tx.GetObject(BucketName, identifier, &endpointRelation); err != nil {
return nil, err
}
@@ -58,6 +45,10 @@ func (service ServiceTx) Create(endpointRelation *portainer.EndpointRelation) er
err := service.tx.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
cache.Del(endpointRelation.EndpointID)
service.service.mu.Lock()
service.service.endpointRelationsCache = nil
service.service.mu.Unlock()
return err
}
@@ -74,6 +65,10 @@ func (service ServiceTx) UpdateEndpointRelation(endpointID portainer.EndpointID,
updatedRelationState, _ := service.EndpointRelation(endpointID)
service.service.mu.Lock()
service.service.endpointRelationsCache = nil
service.service.mu.Unlock()
service.updateEdgeStacksAfterRelationChange(previousRelationState, updatedRelationState)
return nil
@@ -90,27 +85,44 @@ func (service ServiceTx) DeleteEndpointRelation(endpointID portainer.EndpointID)
return err
}
service.service.mu.Lock()
service.service.endpointRelationsCache = nil
service.service.mu.Unlock()
service.updateEdgeStacksAfterRelationChange(deletedRelation, nil)
return nil
}
func (service ServiceTx) InvalidateEdgeCacheForEdgeStack(edgeStackID portainer.EdgeStackID) {
rels, err := service.EndpointRelations()
rels, err := service.cachedEndpointRelations()
if err != nil {
log.Error().Err(err).Msg("cannot retrieve endpoint relations")
return
}
for _, rel := range rels {
for id := range rel.EdgeStacks {
if edgeStackID == id {
cache.Del(rel.EndpointID)
}
if _, ok := rel.EdgeStacks[edgeStackID]; ok {
cache.Del(rel.EndpointID)
}
}
}
func (service ServiceTx) cachedEndpointRelations() ([]portainer.EndpointRelation, error) {
service.service.mu.Lock()
defer service.service.mu.Unlock()
if service.service.endpointRelationsCache == nil {
var err error
service.service.endpointRelationsCache, err = service.EndpointRelations()
if err != nil {
return nil, err
}
}
return service.service.endpointRelationsCache, nil
}
func (service ServiceTx) updateEdgeStacksAfterRelationChange(previousRelationState *portainer.EndpointRelation, updatedRelationState *portainer.EndpointRelation) {
relations, _ := service.EndpointRelations()
@@ -141,19 +153,24 @@ func (service ServiceTx) updateEdgeStacksAfterRelationChange(previousRelationSta
// list how many time this stack is referenced in all relations
// in order to update the stack deployments count
for refStackId, refStackEnabled := range stacksToUpdate {
if refStackEnabled {
numDeployments := 0
for _, r := range relations {
for sId, enabled := range r.EdgeStacks {
if enabled && sId == refStackId {
numDeployments += 1
}
if !refStackEnabled {
continue
}
numDeployments := 0
for _, r := range relations {
for sId, enabled := range r.EdgeStacks {
if enabled && sId == refStackId {
numDeployments += 1
}
}
}
service.service.updateStackFn(refStackId, func(edgeStack *portainer.EdgeStack) {
edgeStack.NumDeployments = numDeployments
})
if err := service.service.updateStackFnTx(service.tx, refStackId, func(edgeStack *portainer.EdgeStack) {
edgeStack.NumDeployments = numDeployments
}); err != nil {
log.Error().Err(err).Msg("could not update the number of deployments")
}
}
}

View File

@@ -1,9 +1,10 @@
package errors
import "errors"
import (
"errors"
)
var (
// TODO: i'm pretty sure this needs wrapping at several levels
ErrObjectNotFound = errors.New("object not found inside the database")
ErrWrongDBEdition = errors.New("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
ErrDBImportFailed = errors.New("importing backup failed")

View File

@@ -1,17 +1,12 @@
package extension
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "extension"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "extension"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
@@ -51,22 +46,12 @@ func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extensio
func (service *Service) Extensions() ([]portainer.Extension, error) {
var extensions = make([]portainer.Extension, 0)
err := service.connection.GetAll(
return extensions, service.connection.GetAll(
BucketName,
&portainer.Extension{},
func(obj interface{}) (interface{}, error) {
extension, ok := obj.(*portainer.Extension)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Extension object")
return nil, fmt.Errorf("Failed to convert to Extension object: %s", obj)
}
dataservices.AppendFn(&extensions),
)
extensions = append(extensions, *extension)
return &portainer.Extension{}, nil
})
return extensions, err
}
// Persist persists a extension inside the database.

View File

@@ -1,95 +0,0 @@
package fdoprofile
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "fdo_profiles"
)
// Service represents a service for managingFDO Profiles data.
type Service struct {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
}, nil
}
// FDOProfiles return an array containing all the FDO Profiles.
func (service *Service) FDOProfiles() ([]portainer.FDOProfile, error) {
var fdoProfiles = make([]portainer.FDOProfile, 0)
err := service.connection.GetAll(
BucketName,
&portainer.FDOProfile{},
func(obj interface{}) (interface{}, error) {
fdoProfile, ok := obj.(*portainer.FDOProfile)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to FDOProfile object")
return nil, fmt.Errorf("Failed to convert to FDOProfile object: %s", obj)
}
fdoProfiles = append(fdoProfiles, *fdoProfile)
return &portainer.FDOProfile{}, nil
})
return fdoProfiles, err
}
// FDOProfile returns an FDO Profile by ID.
func (service *Service) FDOProfile(ID portainer.FDOProfileID) (*portainer.FDOProfile, error) {
var FDOProfile portainer.FDOProfile
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &FDOProfile)
if err != nil {
return nil, err
}
return &FDOProfile, nil
}
// Create assign an ID to a new FDO Profile and saves it.
func (service *Service) Create(FDOProfile *portainer.FDOProfile) error {
return service.connection.CreateObjectWithId(
BucketName,
int(FDOProfile.ID),
FDOProfile,
)
}
// Update updates an FDO Profile.
func (service *Service) Update(ID portainer.FDOProfileID, FDOProfile *portainer.FDOProfile) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, FDOProfile)
}
// Delete deletes an FDO Profile.
func (service *Service) Delete(ID portainer.FDOProfileID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
}
// GetNextIdentifier returns the next identifier for a FDO Profile.
func (service *Service) GetNextIdentifier() int {
return service.connection.GetNextIdentifier(BucketName)
}

View File

@@ -1,25 +1,16 @@
package helmuserrepository
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "helm_user_repository"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "helm_user_repository"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
dataservices.BaseDataService[portainer.HelmUserRepository, portainer.HelmUserRepositoryID]
}
// NewService creates a new instance of a service.
@@ -30,75 +21,33 @@ func NewService(connection portainer.Connection) (*Service, error) {
}
return &Service{
connection: connection,
BaseDataService: dataservices.BaseDataService[portainer.HelmUserRepository, portainer.HelmUserRepositoryID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
// HelmUserRepository returns an array of all HelmUserRepository
func (service *Service) HelmUserRepositories() ([]portainer.HelmUserRepository, error) {
var repos = make([]portainer.HelmUserRepository, 0)
err := service.connection.GetAll(
BucketName,
&portainer.HelmUserRepository{},
func(obj interface{}) (interface{}, error) {
r, ok := obj.(*portainer.HelmUserRepository)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
}
repos = append(repos, *r)
return &portainer.HelmUserRepository{}, nil
})
return repos, err
}
// HelmUserRepositoryByUserID return an array containing all the HelmUserRepository objects where the specified userID is present.
func (service *Service) HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error) {
var result = make([]portainer.HelmUserRepository, 0)
err := service.connection.GetAll(
return result, service.Connection.GetAll(
BucketName,
&portainer.HelmUserRepository{},
func(obj interface{}) (interface{}, error) {
record, ok := obj.(*portainer.HelmUserRepository)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
}
if record.UserID == userID {
result = append(result, *record)
}
return &portainer.HelmUserRepository{}, nil
})
return result, err
dataservices.FilterFn(&result, func(e portainer.HelmUserRepository) bool {
return e.UserID == userID
}),
)
}
// CreateHelmUserRepository creates a new HelmUserRepository object.
func (service *Service) Create(record *portainer.HelmUserRepository) error {
return service.connection.CreateObject(
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
func(id uint64) (int, any) {
record.ID = portainer.HelmUserRepositoryID(id)
return int(record.ID), record
},
)
}
// UpdateHelmUserRepostory updates an registry.
func (service *Service) UpdateHelmUserRepository(ID portainer.HelmUserRepositoryID, registry *portainer.HelmUserRepository) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, registry)
}
// DeleteHelmUserRepository deletes an registry.
func (service *Service) DeleteHelmUserRepository(ID portainer.HelmUserRepositoryID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
}

View File

@@ -0,0 +1,68 @@
package dataservices
import (
"errors"
"fmt"
perrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
)
// ErrStop signals the stop of computation when filtering results
var ErrStop = errors.New("stop")
func IsErrObjectNotFound(e error) bool {
return errors.Is(e, perrors.ErrObjectNotFound)
}
// AppendFn appends elements to the given collection slice
func AppendFn[T any](collection *[]T) func(obj any) (any, error) {
return func(obj any) (any, error) {
element, ok := obj.(*T)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
return nil, fmt.Errorf("failed to convert to %T object: %#v", new(T), obj)
}
*collection = append(*collection, *element)
return new(T), nil
}
}
// FilterFn appends elements to the given collection when the predicate is true
func FilterFn[T any](collection *[]T, predicate func(T) bool) func(obj any) (any, error) {
return func(obj any) (any, error) {
element, ok := obj.(*T)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
return nil, fmt.Errorf("failed to convert to %T object: %#v", new(T), obj)
}
if predicate(*element) {
*collection = append(*collection, *element)
}
return new(T), nil
}
}
// FirstFn sets the element to the first one that satisfies the predicate and stops the computation, returns ErrStop on
// success
func FirstFn[T any](element *T, predicate func(T) bool) func(obj any) (any, error) {
return func(obj any) (any, error) {
e, ok := obj.(*T)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
return nil, fmt.Errorf("failed to convert to %T object: %#v", new(T), obj)
}
if predicate(*e) {
*element = *e
return new(T), ErrStop
}
return new(T), nil
}
}

View File

@@ -1,15 +1,8 @@
package dataservices
// "github.com/portainer/portainer/api/dataservices"
import (
"io"
"time"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
)
type (
@@ -22,7 +15,6 @@ type (
Endpoint() EndpointService
EndpointGroup() EndpointGroupService
EndpointRelation() EndpointRelationService
FDOProfile() FDOProfileService
HelmUserRepository() HelmUserRepositoryService
Registry() RegistryService
ResourceControl() ResourceControlService
@@ -39,10 +31,11 @@ type (
User() UserService
Version() VersionService
Webhook() WebhookService
PendingActions() PendingActionsService
}
// DataStore defines the interface to manage the data
DataStore interface {
Connection() portainer.Connection
Open() (newStore bool, err error)
Init() error
Close() error
@@ -51,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
@@ -59,36 +52,28 @@ type (
// CustomTemplateService represents a service to manage custom templates
CustomTemplateService interface {
BaseCRUD[portainer.CustomTemplate, portainer.CustomTemplateID]
GetNextIdentifier() int
CustomTemplates() ([]portainer.CustomTemplate, error)
CustomTemplate(ID portainer.CustomTemplateID) (*portainer.CustomTemplate, error)
Create(customTemplate *portainer.CustomTemplate) error
UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error
DeleteCustomTemplate(ID portainer.CustomTemplateID) error
BucketName() string
}
// EdgeGroupService represents a service to manage Edge groups
EdgeGroupService interface {
EdgeGroups() ([]portainer.EdgeGroup, error)
EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error)
Create(group *portainer.EdgeGroup) error
UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error
BaseCRUD[portainer.EdgeGroup, portainer.EdgeGroupID]
UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFunc func(group *portainer.EdgeGroup)) error
DeleteEdgeGroup(ID portainer.EdgeGroupID) error
BucketName() string
}
// EdgeJobService represents a service to manage Edge jobs
EdgeJobService interface {
EdgeJobs() ([]portainer.EdgeJob, error)
EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error)
Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
BaseCRUD[portainer.EdgeJob, portainer.EdgeJobID]
CreateWithID(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc func(edgeJob *portainer.EdgeJob)) error
DeleteEdgeJob(ID portainer.EdgeJobID) error
GetNextIdentifier() int
BucketName() string
}
PendingActionsService interface {
BaseCRUD[portainer.PendingAction, portainer.PendingActionID]
GetNextIdentifier() int
DeleteByEndpointID(ID portainer.EndpointID) error
}
// EdgeStackService represents a service to manage Edge stacks
@@ -108,6 +93,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)
@@ -120,12 +106,7 @@ type (
// EndpointGroupService represents a service for managing environment(endpoint) group data
EndpointGroupService interface {
EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error)
EndpointGroups() ([]portainer.EndpointGroup, error)
Create(group *portainer.EndpointGroup) error
UpdateEndpointGroup(ID portainer.EndpointGroupID, group *portainer.EndpointGroup) error
DeleteEndpointGroup(ID portainer.EndpointGroupID) error
BucketName() string
BaseCRUD[portainer.EndpointGroup, portainer.EndpointGroupID]
}
// EndpointRelationService represents a service for managing environment(endpoint) relations data
@@ -138,74 +119,33 @@ type (
BucketName() string
}
// FDOProfileService represents a service to manage FDO Profiles
FDOProfileService interface {
FDOProfiles() ([]portainer.FDOProfile, error)
FDOProfile(ID portainer.FDOProfileID) (*portainer.FDOProfile, error)
Create(FDOProfile *portainer.FDOProfile) error
Update(ID portainer.FDOProfileID, FDOProfile *portainer.FDOProfile) error
Delete(ID portainer.FDOProfileID) error
GetNextIdentifier() int
BucketName() string
}
// HelmUserRepositoryService represents a service to manage HelmUserRepositories
HelmUserRepositoryService interface {
HelmUserRepositories() ([]portainer.HelmUserRepository, error)
BaseCRUD[portainer.HelmUserRepository, portainer.HelmUserRepositoryID]
HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error)
Create(record *portainer.HelmUserRepository) error
UpdateHelmUserRepository(ID portainer.HelmUserRepositoryID, repository *portainer.HelmUserRepository) error
DeleteHelmUserRepository(ID portainer.HelmUserRepositoryID) error
BucketName() string
}
// JWTService represents a service for managing JWT tokens
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 {
Registry(ID portainer.RegistryID) (*portainer.Registry, error)
Registries() ([]portainer.Registry, error)
Create(registry *portainer.Registry) error
UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error
DeleteRegistry(ID portainer.RegistryID) error
BucketName() string
BaseCRUD[portainer.Registry, portainer.RegistryID]
}
// ResourceControlService represents a service for managing resource control data
ResourceControlService interface {
ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error)
BaseCRUD[portainer.ResourceControl, portainer.ResourceControlID]
ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error)
ResourceControls() ([]portainer.ResourceControl, error)
Create(rc *portainer.ResourceControl) error
UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error
DeleteResourceControl(ID portainer.ResourceControlID) error
BucketName() string
}
// RoleService represents a service for managing user roles
RoleService interface {
Role(ID portainer.RoleID) (*portainer.Role, error)
Roles() ([]portainer.Role, error)
Create(role *portainer.Role) error
UpdateRole(ID portainer.RoleID, role *portainer.Role) error
BucketName() string
BaseCRUD[portainer.Role, portainer.RoleID]
}
// APIKeyRepositoryService
APIKeyRepository interface {
CreateAPIKey(key *portainer.APIKey) error
GetAPIKey(keyID portainer.APIKeyID) (*portainer.APIKey, error)
UpdateAPIKey(key *portainer.APIKey) error
DeleteAPIKey(ID portainer.APIKeyID) error
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
@@ -216,12 +156,7 @@ type (
}
SnapshotService interface {
Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error)
Snapshots() ([]portainer.Snapshot, error)
UpdateSnapshot(snapshot *portainer.Snapshot) error
DeleteSnapshot(endpointID portainer.EndpointID) error
Create(snapshot *portainer.Snapshot) error
BucketName() string
BaseCRUD[portainer.Snapshot, portainer.EndpointID]
}
// SSLSettingsService represents a service for managing application settings
@@ -233,53 +168,33 @@ type (
// StackService represents a service for managing stack data
StackService interface {
Stack(ID portainer.StackID) (*portainer.Stack, error)
BaseCRUD[portainer.Stack, portainer.StackID]
StackByName(name string) (*portainer.Stack, error)
StacksByName(name string) ([]portainer.Stack, error)
Stacks() ([]portainer.Stack, error)
Create(stack *portainer.Stack) error
UpdateStack(ID portainer.StackID, stack *portainer.Stack) error
DeleteStack(ID portainer.StackID) error
GetNextIdentifier() int
StackByWebhookID(ID string) (*portainer.Stack, error)
RefreshableStacks() ([]portainer.Stack, error)
BucketName() string
}
// TagService represents a service for managing tag data
TagService interface {
Tags() ([]portainer.Tag, error)
Tag(ID portainer.TagID) (*portainer.Tag, error)
Create(tag *portainer.Tag) error
UpdateTag(ID portainer.TagID, tag *portainer.Tag) error
BaseCRUD[portainer.Tag, portainer.TagID]
UpdateTagFunc(ID portainer.TagID, updateFunc func(tag *portainer.Tag)) error
DeleteTag(ID portainer.TagID) error
BucketName() string
}
// TeamService represents a service for managing user data
TeamService interface {
Team(ID portainer.TeamID) (*portainer.Team, error)
BaseCRUD[portainer.Team, portainer.TeamID]
TeamByName(name string) (*portainer.Team, error)
Teams() ([]portainer.Team, error)
Create(team *portainer.Team) error
UpdateTeam(ID portainer.TeamID, team *portainer.Team) error
DeleteTeam(ID portainer.TeamID) error
BucketName() string
}
// TeamMembershipService represents a service for managing team membership data
TeamMembershipService interface {
TeamMembership(ID portainer.TeamMembershipID) (*portainer.TeamMembership, error)
TeamMemberships() ([]portainer.TeamMembership, error)
BaseCRUD[portainer.TeamMembership, portainer.TeamMembershipID]
TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error)
TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error)
Create(membership *portainer.TeamMembership) error
UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error
DeleteTeamMembership(ID portainer.TeamMembershipID) error
DeleteTeamMembershipByUserID(userID portainer.UserID) error
DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error
BucketName() string
DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.TeamID, userID portainer.UserID) error
}
@@ -292,38 +207,24 @@ type (
// UserService represents a service for managing user data
UserService interface {
User(ID portainer.UserID) (*portainer.User, error)
BaseCRUD[portainer.User, portainer.UserID]
UserByUsername(username string) (*portainer.User, error)
Users() ([]portainer.User, error)
UsersByRole(role portainer.UserRole) ([]portainer.User, error)
Create(user *portainer.User) error
UpdateUser(ID portainer.UserID, user *portainer.User) error
DeleteUser(ID portainer.UserID) error
BucketName() string
}
// VersionService represents a service for managing version data
VersionService interface {
Edition() (portainer.SoftwareEdition, error)
InstanceID() (string, error)
UpdateInstanceID(ID string) error
Edition() (portainer.SoftwareEdition, error)
Version() (*models.Version, error)
UpdateVersion(*models.Version) error
}
// WebhookService represents a service for managing webhook data.
WebhookService interface {
Webhooks() ([]portainer.Webhook, error)
Webhook(ID portainer.WebhookID) (*portainer.Webhook, error)
Create(portainer *portainer.Webhook) error
UpdateWebhook(ID portainer.WebhookID, webhook *portainer.Webhook) error
BaseCRUD[portainer.Webhook, portainer.WebhookID]
WebhookByResourceID(resourceID string) (*portainer.Webhook, error)
WebhookByToken(token string) (*portainer.Webhook, error)
DeleteWebhook(ID portainer.WebhookID) error
BucketName() string
}
)
func IsErrObjectNotFound(e error) bool {
return e == errors.ErrObjectNotFound
}

View File

@@ -0,0 +1,105 @@
package pendingactions
import (
"fmt"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
const (
BucketName = "pending_actions"
)
type Service struct {
dataservices.BaseDataService[portainer.PendingAction, portainer.PendingActionID]
}
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.PendingAction, portainer.PendingActionID]
}
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.PendingAction, portainer.PendingActionID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
func (s Service) Create(config *portainer.PendingAction) error {
return s.Connection.UpdateTx(func(tx portainer.Transaction) error {
return s.Tx(tx).Create(config)
})
}
func (s Service) Update(ID portainer.PendingActionID, config *portainer.PendingAction) error {
return s.Connection.UpdateTx(func(tx portainer.Transaction) error {
return s.Tx(tx).Update(ID, config)
})
}
func (s Service) DeleteByEndpointID(ID portainer.EndpointID) error {
return s.Connection.UpdateTx(func(tx portainer.Transaction) error {
return s.Tx(tx).DeleteByEndpointID(ID)
})
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.PendingAction, portainer.PendingActionID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
}
}
func (s ServiceTx) Create(config *portainer.PendingAction) error {
return s.Tx.CreateObject(BucketName, func(id uint64) (int, any) {
config.ID = portainer.PendingActionID(id)
config.CreatedAt = time.Now().Unix()
return int(config.ID), config
})
}
func (s ServiceTx) Update(ID portainer.PendingActionID, config *portainer.PendingAction) error {
return s.BaseDataServiceTx.Update(ID, config)
}
func (s ServiceTx) DeleteByEndpointID(ID portainer.EndpointID) error {
log.Debug().Int("endpointId", int(ID)).Msg("deleting pending actions for endpoint")
pendingActions, err := s.BaseDataServiceTx.ReadAll()
if err != nil {
return fmt.Errorf("failed to retrieve pending-actions for endpoint (%d): %w", ID, err)
}
for _, pendingAction := range pendingActions {
if pendingAction.EndpointID == ID {
err := s.BaseDataServiceTx.Delete(pendingAction.ID)
if err != nil {
log.Debug().Int("endpointId", int(ID)).Msgf("failed to delete pending action: %v", err)
}
}
}
return nil
}
// GetNextIdentifier returns the next identifier for a custom template.
func (service ServiceTx) GetNextIdentifier() int {
return service.Tx.GetNextIdentifier(BucketName)
}
// GetNextIdentifier returns the next identifier for a custom template.
func (service *Service) GetNextIdentifier() int {
return service.Connection.GetNextIdentifier(BucketName)
}

View File

@@ -1,25 +1,16 @@
package registry
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/dataservices"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "registries"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "registries"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
dataservices.BaseDataService[portainer.Registry, portainer.RegistryID]
}
// NewService creates a new instance of a service.
@@ -30,64 +21,30 @@ func NewService(connection portainer.Connection) (*Service, error) {
}
return &Service{
connection: connection,
BaseDataService: dataservices.BaseDataService[portainer.Registry, portainer.RegistryID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
// Registry returns an registry by ID.
func (service *Service) Registry(ID portainer.RegistryID) (*portainer.Registry, error) {
var registry portainer.Registry
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &registry)
if err != nil {
return nil, err
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.Registry, portainer.RegistryID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
}
return &registry, nil
}
// Registries returns an array containing all the registries.
func (service *Service) Registries() ([]portainer.Registry, error) {
var registries = make([]portainer.Registry, 0)
err := service.connection.GetAll(
BucketName,
&portainer.Registry{},
func(obj interface{}) (interface{}, error) {
registry, ok := obj.(*portainer.Registry)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Registry object")
return nil, fmt.Errorf("Failed to convert to Registry object: %s", obj)
}
registries = append(registries, *registry)
return &portainer.Registry{}, nil
})
return registries, err
}
// CreateRegistry creates a new registry.
// Create creates a new registry.
func (service *Service) Create(registry *portainer.Registry) error {
return service.connection.CreateObject(
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
func(id uint64) (int, any) {
registry.ID = portainer.RegistryID(id)
return int(registry.ID), registry
},
)
}
// UpdateRegistry updates an registry.
func (service *Service) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, registry)
}
// DeleteRegistry deletes an registry.
func (service *Service) DeleteRegistry(ID portainer.RegistryID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
}

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