Compare commits

...

168 Commits

Author SHA1 Message Date
Anthony Lapenna
8c3ac35f02 feat(toolkit): removed unused yarn command 2021-10-30 19:01:14 +00:00
deviantony
140ec51143 feat(toolkit): updated toolkit 2021-10-30 12:25:31 -04:00
deviantony
327bbd4ca7 feat(toolkit): update to use vscode remote 2021-10-30 10:30:42 -04:00
Matt Hook
8f4589e535 fix(migration): bubble up recovered panic in new error EE-1971 (#5997)
* fix(migration): bubble up recovered panic in new error EE-1971

* improve code and add comments
2021-10-30 22:32:57 +13:00
Hui
0caf5ca59e fix(migration): ignore volumes with no created timestamp EE-1966 2021-10-30 11:09:11 +13:00
Matt Hook
cec8f34ae9 fix(helm): allow clearing global helm repo EE-1965 (#5991)
* fix(helm): allow clearing global helm repo EE-1965

* fix(helm): show hint if global helm repo is blank EE-1965

* fix(helm): skip loading charts if repo is blank EE-1965

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-29 11:46:55 +13:00
Hui
71de07bbea feat(stack): support force update for git-based stacks EE-1611 2021-10-29 10:35:21 +13:00
Sven Dowideit
76ced401f0 chore(build): reduce the time to run yarn build:server from 1.5minutes, to 10 seconds (#5987)
* reduce the time to run yarn build:server from 1.5minutes, to 10 seconds

Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>

* add yarn test:server

Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
2021-10-28 21:18:13 +10:00
wheresolivia
33001a8654 add data-cy attribute to helm menu in ce kube sidebar (#5985) 2021-10-27 17:12:12 +13:00
Marcelo Rydel
f738af0f34 fix(stacks): fix missing type prop in stack view [EE-1950] (#5972) 2021-10-26 19:26:13 -03:00
cong meng
5c85c563e1 fix(image) EE-1955 unable to tag image (#5974)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-26 15:22:28 +13:00
Simon Meng
db00390cd2 Merge remote-tracking branch 'origin/release/2.9' into develop
# Conflicts:
#	api/http/handler/websocket/shell_pod.go
#	app/portainer/components/box-selector/box-selector-item/box-selector-item.html
#	app/portainer/rbac/components/access-viewer/access-viewer-datatable/access-viewer-datatable.html
#	app/portainer/settings/authentication/ldap/ad-settings/ad-settings.html
#	app/portainer/settings/authentication/ldap/index.js
#	app/portainer/settings/authentication/ldap/ldap-settings-custom/ldap-settings-custom.html
#	app/portainer/settings/authentication/ldap/ldap-settings.model.js
#	app/portainer/settings/authentication/ldap/ldap-settings/ldap-settings.controller.js
#	app/portainer/views/settings/authentication/settingsAuthenticationController.js
2021-10-26 10:58:19 +13:00
Marcelo Rydel
32756f9e1b fix(git-stacks): UI bugs when using a PAT when deploying from Git [EE-1731] (#5882) 2021-10-25 18:19:05 -03:00
Sven Dowideit
5ba80c3a44 sorry, wrong place to push to
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
2021-10-22 13:34:19 +10:00
Sven Dowideit
77f73378ea try this, but reset later
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
2021-10-22 13:29:33 +10:00
Marcelo Rydel
734f077861 fix(environments): Endpoint deletion modal missing [EE-1887] (#5904) 2021-10-21 09:23:08 -03:00
Richard Wei
b5ec8c52fb fix standard user not able to access nodes stats (#5951) 2021-10-21 11:56:21 +13:00
Richard Wei
988efe6b02 pull request to develop from EE-1867 (#5958) 2021-10-21 11:55:56 +13:00
Richard Wei
40a6645e23 fix user not able to get nodes (#5950) 2021-10-21 11:55:37 +13:00
Marcelo Rydel
cf60235696 fix(compose): force recreate containers [EE-1906] (#5926) 2021-10-20 09:01:38 -03:00
Stéphane Busso
65cc5342a7 Bump dbversion 2021-10-20 20:48:33 +13:00
Stéphane Busso
90a18b5ded Bump dbversion 2021-10-20 20:35:18 +13:00
Hui
b29961e01e fix(stack): auto update breaks after restarting Portainer EE-1915 2021-10-20 16:01:04 +13:00
Hui
d17e7c8160 fix(stack): auto update breaks after restarting Portainer EE-1915 2021-10-20 16:00:40 +13:00
Matt Hook
d3cc1a24cc docs(versions): add new tool-versions json file (#5741)
* Add new tool-versions json file to help devs choose the right versions.  Allows querying from doc sites and CI build tools

* add newline at end of file
2021-10-20 12:56:51 +13:00
Snyk bot
fb7cdacbaa fix: build/windows/Dockerfile to reduce vulnerabilities (#5913)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE313-APKTOOLS-1533754
- https://snyk.io/vuln/SNYK-ALPINE313-OPENSSL-1089239
- https://snyk.io/vuln/SNYK-ALPINE313-OPENSSL-1569446
- https://snyk.io/vuln/SNYK-ALPINE313-OPENSSL-1569448
- https://snyk.io/vuln/SNYK-ALPINE313-OPENSSL-1569448
2021-10-20 08:22:21 +10:00
Matt Hook
ec24826228 pass the correct build arch down not the arch of the machine doing the building EE-1920 (#5929) 2021-10-20 10:02:30 +13:00
Matt Hook
f0efc4f904 bump to 2.9.2 2021-10-19 15:51:16 +13:00
cong meng
d18c8d0e88 fix(registry) EE-1861 improve registry selection (#5925)
* fix(registry) EE-1861 improve registry selection (#5899)

* fix(registry) EE-1861 hide anonymous dockerhub registry if user has an authenticated one

* fix(registry) EE-1861 pick up a best match dockerhub registry

* fix(registry) EE-1861 set the anonymous registry as default if it is shown

* fix(registry) EE-1861 refactor how to match registry

Co-authored-by: Simon Meng <simon.meng@portainer.io>

* fix(registry) EE-1861 fail to select registry with same name

* fix(registry) EE-1861 show registry modal when pull and push image

* fix(registry) EE-1861 cleanup code

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-19 14:54:53 +13:00
cong meng
4f350ab6f5 fix(registry) EE-1861 improve registry selection (#5921)
* fix(registry) EE-1861 fail to select registry with same name

* fix(registry) EE-1861 show registry modal when pull and push image

* fix(registry) EE-1861 cleanup code

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-19 14:54:44 +13:00
Sven Dowideit
623079442f fix(swagger): double quotes in swagger param breaks parser (#5806)
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
2021-10-19 10:25:53 +10:00
fhanportainer
1ff5f25e40 fix(registry): ignore pull limit in non-docker hub registry. (#5917) 2021-10-19 13:21:57 +13:00
fhanportainer
ff87e687ec fix(registry): ignore pull limit in non-docker hub registry. (#5918) 2021-10-19 13:21:54 +13:00
Marcelo Rydel
d4fd295c86 fix(roles): Missing manage access button in user roles [EE-1875] (#5891)
fix(roles): Missing manage access button in user roles [EE-1875]  (#5891)
2021-10-18 18:35:39 -03:00
Richard Wei
62f418836f upgrade chart.js to 2.7.3 & add ticks.precision:0 (#5789) 2021-10-18 22:48:52 +13:00
Richard Wei
ce5ea28727 add warning message for adding registry to namespace (#5793) 2021-10-18 22:46:22 +13:00
Richard Wei
00c7464c25 fix roder for environments in high contrast mode (#5800) 2021-10-18 22:45:00 +13:00
Sven Dowideit
5eced421d5 prevent exception when showing stats on windows container (#5890)
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
2021-10-18 16:36:22 +13:00
Matt Hook
006634e007 fix(helm): allow settings to be saved offline EE-1907 (#5908)
* skip validating default helm repo to allow offline saving of settings. Default repo is hardcoded and correct.

* dont validate the helm repo if the repo hasn't changed or is the default

* fix logic
2021-10-18 15:08:38 +13:00
Matt Hook
3cde10bcac fix(helm) allow settings to be saved offline EE-1907 (#5907)
* allow settings to be saved offline.  Due to helm repo validation not working for bitnami when offline!

* @hookenz
dont validate the helm repo if the repo hasn't changed or is the default
2021-10-18 15:08:27 +13:00
cong meng
9dcd5651e8 fix(registry) EE-1861 improve registry selection (#5899)
* fix(registry) EE-1861 hide anonymous dockerhub registry if user has an authenticated one

* fix(registry) EE-1861 pick up a best match dockerhub registry

* fix(registry) EE-1861 set the anonymous registry as default if it is shown

* fix(registry) EE-1861 refactor how to match registry

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-15 21:42:46 +13:00
Chaim Lev-Ari
ba1f0f4018 chore(build): clean gruntfile (#5411) 2021-10-15 09:17:05 +03:00
cong meng
41999e149f fix(edge) EE-1720 activate tunnel and remove proxy cache when needed (#5775)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-15 18:13:20 +13:00
andres-portainer
dfe0b3f69d fix(namespaces): remove the stacks from the data store when deleting their corresponding Kubernetes namespace EE-1872 (#5885)
* fix(namespaces): remove the stacks from the data store when deleting their corresponding Kubernetes namespace EE-1872

* add endpoint ID checking

Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: ArrisLee <arris_li@hotmail.com>
2021-10-14 19:15:04 -03:00
andres-portainer
588ce549ad fix(namespaces): remove the stacks from the data store when deleting their corresponding Kubernetes namespace EE-1872 (#5893)
* fix(namespaces): remove the stacks from the data store when deleting their corresponding Kubernetes namespace EE-1872

* add endpoint ID checking

Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: ArrisLee <arris_li@hotmail.com>
2021-10-14 19:14:57 -03:00
Marcelo Rydel
edb25ee10d fix(services): pre fill service registry and image [EE-1769] (#5798)
fix(services): pre fill service registry and image [EE-1769]  (#5798)
2021-10-14 09:42:10 -03:00
Marcelo Rydel
12e7aa6b60 fix(environments): don't override with local IP [EE-1561] (#5785)
fix(environments): don't override with local IP [EE-1561] (#5785)
2021-10-14 09:40:14 -03:00
cong meng
f544d4447c fix(rbac) EE-1867 regular user unable to access pod and node stats view (#5886)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-14 17:00:31 +13:00
Richard Wei
158cdf596a fix(css): fix decl.moveTo is not a function error in css EE-1744 (#5717)
* fix decl.moveTo is not a function error in css

* Update vendor-override.css
2021-10-13 14:10:37 +13:00
fhanportainer
3d6c6e2604 feat(ldap): LDAP admin auto population EE-568 (#5875)
* feat(ldap): added ldap custom admin group component

* feat(ldap): added ldap custom admin group to LDAP and MS AD pages

* fix(ui): LDAP group search config label

* fix(ldap): removed testing code.

* fix(ldap): fixed default text in ldap custom admin group component
2021-10-13 11:29:00 +13:00
Marcelo Rydel
1ee363f8c9 overrite stack name for update (#5743) 2021-10-12 18:48:28 -03:00
Marcelo Rydel
109b27594a save settings draft (#5872) 2021-10-12 14:51:43 -03:00
zees-dev
54d47ebc76 feat(docker/kubernetes): backend docker and kubernetes dependency updates (#5861)
* client-go library update + go mod tidy

* update all k8s methods to include context

* docker/cli updated to v20.10.9 (latest)

* - removed docker/docker to docker/engine replace directive
- go mod tidy

* docker/docker updated to v20.10.9 (latest)
2021-10-12 15:32:14 +13:00
Hui
e6d690e31e fix(swagger) swagger annotations fixes and improvements EE-1205 2021-10-12 12:12:08 +13:00
cong meng
6a67e8142d fix(frontend) prevent notification showing Object Object EE-1745 (#5778)
* fix(frontend) prevent notification showing Object Object EE-1745

* fix(frontend) fix notification args in wrong order EE-1745

* fix(rbac) add metrics rbac for regular users EE-1745

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-12 10:37:07 +13:00
Chaim Lev-Ari
d93d88fead fix(app): add data-cy to box-selector (#5869) 2021-10-12 10:14:01 +13:00
Chaim Lev-Ari
8383bc05c5 fix(compose): use tcp for agent proxy EE-1807 (#5854) 2021-10-11 12:08:07 +13:00
Richard Wei
685552a661 fix(wizard): fix wizard not visible in dark theme EE-1800 (#5822)
* fix wizard not visible in dark theme
2021-10-08 14:59:01 +13:00
Richard Wei
1b0e58a4e8 fix upload file not selectable on mac (#5808) 2021-10-08 12:17:22 +13:00
wheresolivia
0200a668df fix(ui): ldap group search config labelclose EE-1846 (#5850)
Co-authored-by: olivia.wang <olivia.wang@wherescape.com>
2021-10-08 12:01:10 +13:00
Chaim Lev-Ari
151dfe7e65 fix(compose): use tcp for agent proxy EE-1807 (#5854) 2021-10-08 11:59:50 +13:00
fhanportainer
dcd1e902cd fix(ldap): enable user/group setting in custom ldap (#5858) 2021-10-08 11:39:16 +13:00
Chaim Lev-Ari
ed89587cb9 fix(ldap): enable user/group setting in custom ldap (#5855) 2021-10-08 10:43:04 +13:00
zees-dev
c93ec8d08c added swagger docs to websocketShellPodExec (#5840) 2021-10-08 10:32:43 +13:00
zees-dev
dad762de9f added swagger docs to websocketShellPodExec (#5840) 2021-10-07 15:32:07 +13:00
Richard Wei
661931d8b0 fix(template): add name validation for template name EE-1806 (#5823)
* add name validation for tempalte name
2021-10-07 13:02:56 +13:00
Chaim Lev-Ari
b7841e7fc3 feat(app): highlight be provided value [EE-882] (#5703) (#5835) 2021-10-07 11:59:53 +13:00
Richard Wei
84e57cebc9 fix set namespace to default-namespace (#5820) 2021-10-07 11:06:53 +13:00
Matt Hook
8096c5e8bc remove default value for compose path (#5832)
Co-authored-by: cheloRydel <marcelorydel26@gmail.com>
2021-10-07 08:07:00 +13:00
Marcelo Rydel
fd9427cd0b remove default value for compose path (#5821) 2021-10-06 10:12:36 -03:00
Chaim Lev-Ari
e60dbba93b feat(app): highlight be provided value [EE-882] (#5703) 2021-10-06 09:24:26 +03:00
Stéphane Busso
551d287982 Merge branch 'release/2.9' of github.com:portainer/portainer into release/2.9 2021-10-02 09:26:23 +13:00
zees-dev
8421113d49 portainer version updates (#5807) 2021-10-02 08:40:03 +13:00
Matt Hook
6bd72d21a8 fix(migration) datastore always marked new and migrations skipped EE-1775 (#5788)
* fix issue with broken store init

* minor logic improvement

* Remove fileexists logic as its redundant and handled implicitely by bolt.Open

* Added re-open test on IsNew flag.  Essential for migrations to be able to run
2021-10-01 20:35:43 +13:00
Chaim Lev-Ari
fc4ff59bfd fix(db): warn on missing docker id when migrating to db 31 (#5781)
* fix(db): warn on missing docker id when migrating to db 31

* fix(db): guard against nil exception
2021-10-01 15:27:39 +10:00
Chaim Lev-Ari
885ae16278 fix(db): warn on missing docker id when migrating to db 31 (#5782)
* fix(db): warn on missing docker id when migrating to db 31

* fix(db): guard against nil exception
2021-10-01 15:27:31 +10:00
Luis Louis
cd651f2cba fix(template): Remove the no registry available on the registriesDataTable (#5774) 2021-10-01 18:15:32 +13:00
cong meng
328abfd74e fix(stack) normalize stack name EE-1701 (#5776)
* fix(stack) normalize stack name EE-1701

* fix(stack) normalize swarm stack name and fix rebase error EE-1701

* fix(stack) add front end stack name validation EE-1701

* fix(stack) make stack name regex as a const EE-1701

* fix(stack) reuse stack name regex for compose and swarm EE-1701

* fix(stack) add name validation for stack duplication form EE-1701

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-10-01 16:56:34 +13:00
Marcelo Rydel
fbcf67bc1e filter empty stacks in dropdown (#5771)
filter empty stacks in dropdown (#5771)
2021-09-30 09:32:38 -03:00
Chaim Lev-Ari
7fb2e44146 chore(build): set node_env to testing (#5410) 2021-09-30 12:00:54 +03:00
cong meng
0cb5656db6 feat(frontend) auto generate agent version EE-1266 (#5794)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-09-30 21:07:13 +13:00
Richard Wei
e4fd43e4fc fix icon line up issue in sidebar (#5790) 2021-09-30 18:23:13 +13:00
Richard Wei
34c2a16363 fix custom logo not updated (#5634) 2021-09-30 15:55:08 +13:00
Chaim Lev-Ari
0f33e4ae99 fix(wizard): align wizard grid (#5752)
* fix(wizard): align wizard grid [EE-1753]
2021-09-30 15:54:15 +13:00
Richard Wei
75071dfade feat(k8s): add filter for k8s application type EE-1627 (#5733)
* add filter for k8s application type
2021-09-30 15:53:03 +13:00
Richard Wei
34f6e11f1d fix showing create from application form when create from url (#5724) 2021-09-30 12:59:19 +13:00
Dmitry Salakhov
2ecc8ab5c9 feat(k8s): support git automated sync for k8s applications [EE-577] (#5548)
* feat(stack): backport changes to CE EE-1189

* feat(stack): front end backport changes to CE EE-1199 (#5455)

* feat(stack): front end backport changes to CE EE-1199

* fix k8s deploy logic

* fixed web editor confirmation message typo. EE-1501

* fix(stack): fixed issue auth detail not remembered EE-1502 (#5459)

* show status in buttons

* removed onChangeRef function.

* moved buttons in git form to its own component

* removed unused variable.

Co-authored-by: ArrisLee <arris_li@hotmail.com>

* moved formvalue to kube app component

* fix(stack): failed to pull and redeploy compose format k8s stack

* fixed form value

* fix(k8s): file content overridden when deployment failed with compose format EE-1548

* updated API response to get IsComposeFormat and show appropriate text.

* feat(k8s): front end backport to CE

* feat(kube): kube app auto update backend (#5547)

* error message updates for different file type

* not display creation source for external application

* added confirmation modal to advanced app created by web editor

* stop showing confirmation modal when updating application

* disable rollback button when application type is not applicatiom form

* only update file after deployment succeded

* Revert "only update file after deployment succeded"

This reverts commit b94bd2e96f.

* fix(k8s): file content overridden when deployment failed with compose format EE-1556

* added analytics-on directive to pull and redeploy button

* fix(kube): don't valide resource control access for kube (#5568)

* added missing question mark to k8s confirmation modal

* fixed webhook format issue

* added question marks to k8s app confirmation modal

* added space in additional file list.

* ignoring error on deletion

* fix(k8s): Git authentication info not persisted

* added RepositoryMechanismTypes constant

* updated analytics functions

* covert RepositoryMechanism to constant

* fixed typo

* removed unused function.

* post tech review updates

* fixed save settings n redeploy button

* refact kub deploy logic

* Revert "refact kub deploy logic"

This reverts commit cbfdd58ece.

* feat(k8s): utilize user token for k8s auto update EE-1594

* feat(k8s): persist kub stack name EE-1630

* feat(k8s): support delete kub stack

* fix(app): updated logic to delete stack for different kind apps. (#5648)

* fix(app): updated logic to delete stack for different kind apps.

* renamed variable

* fix import

* added StackName field.

* fixed stack id not found issue.

* fix(k8s): fixed qusetion mark alignment issue in PAT field. (#5611)

* fix(k8s): fixed qusetion mark alignment issue in PAT field.

* moved inline css to file.

* fix(git-form: made auth input text full width

* add ignore deleted arg

* tech review updates

* typo fix

* fix(k8s): added console error when deleting k8s service.

* fix(console): added no-console config

* fix(deploy): added missing service.

* fix: use stack editor as an owner when exists (#5678)

* fix: tempalte/content based stacks edit/delete

* fix(stack): remove stack when no app. (#5769)

* fix(stack): remove stack when no app.

* support compose format in delete

Co-authored-by: ArrisLee <arris_li@hotmail.com>

Co-authored-by: Hui <arris_li@hotmail.com>
Co-authored-by: fhanportainer <79428273+fhanportainer@users.noreply.github.com>
Co-authored-by: Felix Han <felix.han@portainer.io>
2021-09-30 12:58:10 +13:00
Marcelo Rydel
fce885901f fix(custom-templates): XSS issue in Custom Template Note <EE-1054> (#5766)
fix(custom-templates): XSS issue in Custom Template Note <EE-1054> (#5766)
2021-09-29 16:47:39 -03:00
Richard Wei
fe8f50512c set isolated as default for k8s app deploy (#5770) 2021-09-29 15:54:25 +13:00
zees-dev
e3b6e4a1d3 feat(configurations): portainer k8s configurations lingo update for explicitness EE-1626 (#5722)
* kubernetes sidebar configuration lingo updated

* configurations list view updated

* updated configurations list add config button

* - updated create and update configuration buttons to display type of configuration being created/updated
- configuration filter displays explicit configuration type

* updated create configuration sub-title

* add configmap wording update

* portainer service lingo updated in k8s app creation and update forms

* publishing mode text updates

* KubernetesApplicationPublishingTypes updated INTERNAL and CLUSTER to CLUSTER_IP and NODE_PORT respectively

* application ports datatable updated

* updated service and ingress lingo on application view page

* reduced spacing to fit in ConfigMaps & Secrets in sidenav for different screen res
2021-09-29 13:58:04 +13:00
Hui
01529203f1 fix(DB): modify new data store checking logic (#5756)
* update new data store check logic

* cleanup
2021-09-29 10:24:26 +10:00
zees-dev
af98660a55 feat(helm): helm apps deployed by portainer not marked as external EE-1624 (#5637)
* helm lib update

* helm handler requires kubernetes deployer to modify helm deployed resources

* AddAppLabels updated to be more generic - support for adding multiple labels using map

* path installed helm release manifest with portainer labels using kubectl

* updated helm handler unit tests to use mock KubernetesDeployer

* adding labels to manifest retrieved from release

* optional namespace support for k8s raw manifest deployment

* - inline postprocessing support when extracting
- get namespace from yaml support
- added and updated tests

* lowercase error wrapping

* updated libhelm dep
2021-09-29 10:12:45 +10:00
Chaim Lev-Ari
50f63ae865 feat(applications): show status indication [EE-1623] (#5614)
* feat(applications): show status indication

* feat(k8s/applications): move colors to theme

* fix helm application indicator for main header

* refactor(k8s/apps): receive more general ok status

Co-authored-by: waysonwei <degui.wei@gmail.com>
2021-09-29 10:10:51 +10:00
Matt Hook
7b72130433 feat(kubeshell) allow overriding default kubeshell image EE-1756 (#5755)
* feat(kubeshell) allow overriding default kubeshell

* Add missing error check and struct tag

* Add migrator for kube shell image and add it as a default in the db

* Fix file name to match migrator pattern

* remove default as it's now coming from the db

* remove blank line

* - conflict resolution code update
- logging migration error on migration failures

* - migrateDBVersionTo34 -> migrateDBVersionToDB34 (naming consistency)

Co-authored-by: zees-dev <dev.786zshan@gmail.com>
2021-09-29 11:39:45 +13:00
testA113
7611cc415a added selectors (#5616)
* added selectors

* moved selector to html element
2021-09-28 22:10:41 +13:00
Sven Dowideit
9045e17cba fix(docker): EE-348: fix Docker stats when using cgroups v2 (#5609)
Signed-off-by: Sven Dowideit <sven@mini.home.org.au>
2021-09-28 13:40:04 +10:00
Anthony Lapenna
46ffca92fd feat(k8s): remove cluster status panel (#5570) 2021-09-28 13:48:06 +13:00
Richard Wei
f0a88b7367 add wiggle room back to edge endpoint (#5739) 2021-09-27 20:33:46 +13:00
Sven Dowideit
7437006359 fix(swagger): EE-868: elide the password field in the swagger docs (#5636)
Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
2021-09-27 14:00:04 +10:00
Sven Dowideit
9c80501738 fix(k8s): EE-1631: backport fixes for API proxy (#5608)
* fix(k8s): EE-1585: the K8s API uses other mediatypes, so we can't rely on parsing JSON bodies for security.

Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>

* fix(k8s): EE-1511 add striped prefix back to location header if response status is 301 moved permanently

Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>

* feat(k8s): EE-1631:improve the secrets handling by removing un-necessary code

Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
2021-09-27 13:16:17 +10:00
zees-dev
377326085d feat(db): upgrade auto-backup backup and rollback support EE-867 EE-1158 (#5341)
* backport migration EE code structure

* filesystem copy function

* set db status to updating before migration - reset on completion

* support for auto-backup on version upgrade

* - rollback cli flag support (with confirmation)
- rollback implementation backport from EE

* removed edition as it is not required in CE

* migrated test datastore from bolttest to bolt package to make it usable for testing

* backported failsafe migration

* - backported tests from EE
- refactored tests to use test datastore

* test store implementing datastore interface

* addressed PR issues/improvements

* refactor test

* added backup file removal error logging

* resolved conflicts, updated code

* fixed missing bolttest package - migrated to bolt

* feat(migration): wrap migration errors to provide context for failure EE-1742 (#5711)

* feat(migrator): wrap errors to provide more context to failures EE-1742

* add overall failure back in. diff log file

* updated helm tests pointing to correct teststore

Co-authored-by: Matt Hook <hookenz@gmail.com>
2021-09-27 13:52:50 +13:00
Richard Wei
03d34076d8 fix error message not last long enough for user to copy error (#5642) 2021-09-27 10:09:23 +13:00
huib-portainer
09cf4c1bbe Update Bug_report.md
fix(link): Fixed the link referencing how to obtain the Portainer logs
2021-09-27 09:59:44 +13:00
Chaim Lev-Ari
9c279e7fae fix(k8s/ns): validate ingress ctrl host pattern (#5662)
* fix(k8s/ns): validate ingress ctrl host pattern

* feat(kube/ns): validate ingress hostname
2021-09-24 14:02:10 +03:00
Chaim Lev-Ari
db04bc9f38 fix(k8s/ns): validate ingress ctrl host pattern (#5663)
* fix(k8s/ns): validate ingress ctrl host pattern

* feat(kube/ns): validate ingress hostname
2021-09-24 14:02:06 +03:00
zees-dev
7d40a83d03 feat(kubectl-shell): page state refreshes in k8s endpoint do not close shell EE-1628 (#5685)
* converting all kubernetes view reload to partial state heirarchy refresh

* updated helm and kube kustom templates headers to use the reusable k8s page header component
2021-09-24 20:21:50 +12:00
Chaim Lev-Ari
d4f581a596 feat(kube): use local kubectl for all deployments (#5488) 2021-09-24 16:56:22 +12:00
testA113
5ad3cacefd Xt 321 automate k8s smoke test data cy attributes (#5734)
* added data-cy attributes for robust ui test automation
2021-09-24 13:00:55 +12:00
Richard Wei
6ac9c4367e show ip address of pod (#5613) 2021-09-23 14:34:24 +12:00
Simon Meng
8aa03bb81b Merge remote-tracking branch 'origin/release/2.9' into develop
# Conflicts:
#	app/kubernetes/views/applications/create/createApplication.html
#	app/kubernetes/views/configurations/create/createConfiguration.html
2021-09-23 12:09:13 +12:00
fhanportainer
d14c7b0309 fix(name): fixed namespace creation issue when a registry attached. (#5646)
* fix(name): fixed namespace creation issue when a registry attached.

* fix(name): moved copy object to upper level of the function
2021-09-23 09:13:25 +12:00
fhanportainer
cbeb13636c fix(name): fixed namespace creation issue when a registry attached. (#5675) 2021-09-23 09:13:19 +12:00
Hui
a6138dd5a3 fix(migration): add debug logging for volume migration (#5700)
* add debug logging

* Update api/bolt/migrator/migrate_dbversion31.go

* log resource control delete

Co-authored-by: Stéphane Busso <sbusso@users.noreply.github.com>
2021-09-23 09:12:39 +12:00
Hui
5752e74be6 add debug log (#5702) 2021-09-23 09:12:35 +12:00
Matt Hook
cb37497444 doc(readme) fix slack link (#5701)
* doc(readme) fix slack link

* Use shared invite link underneath
2021-09-23 08:59:29 +12:00
Chaim Lev-Ari
0b64250647 Revert "refactor(settings): backport auth views (#5672)" (#5704)
This reverts commit 45af1f3d8b.
2021-09-22 10:17:22 +03:00
Chaim Lev-Ari
45af1f3d8b refactor(settings): backport auth views (#5672) 2021-09-22 10:11:53 +03:00
Chaim Lev-Ari
fc52830c7d fix(customtemplates): show correct type (#5669) 2021-09-22 08:00:16 +03:00
Chaim Lev-Ari
4890f50443 fix(customtemplates): show correct type (#5668) 2021-09-22 08:00:11 +03:00
Chaim Lev-Ari
6d510c4f30 fix(k8s/apps): edit url deployed app (#5652) 2021-09-22 07:59:32 +03:00
Chaim Lev-Ari
cad530ec04 fix(k8s/apps): edit url deployed app (#5653) 2021-09-22 07:59:28 +03:00
Chaim Lev-Ari
e63732484a fix(registries): put anon docker at top (#5671) 2021-09-22 07:55:28 +03:00
Chaim Lev-Ari
ec3233fb09 fix(registries): put anon docker at top (#5670) 2021-09-22 07:55:25 +03:00
Richard Wei
bcdc342cbd fix(k8s): fixerror handling for namespace restricted user EE-1703 (#5693)
* fix error handler for namespace when user have no namespace access
2021-09-22 16:01:42 +12:00
Richard Wei
e1f725d01a fix(k8s): fix error handling for namespace restricted user EE-1703 (#5692)
* fix error handler for user has no namespace access
2021-09-22 16:01:28 +12:00
Richard Wei
b876f2d17d fix danger button hover color (#5605) 2021-09-22 15:17:52 +12:00
mariyam-portainer
b0ec67826c Rename portainerbusiness.yml to config.yml 2021-09-22 15:07:23 +12:00
mariyam-portainer
b89d828878 Rename Portainer Business to portainerbusiness.yml 2021-09-22 15:06:25 +12:00
mariyam-portainer
e59df8134d Create Portainer Business 2021-09-22 15:04:05 +12:00
zees-dev
092d217985 table settings propagated through nested tables (#5699) 2021-09-22 13:42:13 +12:00
zees-dev
ad94162019 table settings propagated through nested tables (#5698) 2021-09-22 13:42:04 +12:00
Richard Wei
0efbf5bbf3 rename endpoint to environment in wizard breadcrumb header (#5696) 2021-09-22 13:18:52 +12:00
Richard Wei
c26ba23c53 rename endpoint to environment in wizard breadcrumb header (#5697) 2021-09-22 13:18:42 +12:00
Richard Wei
69096f664d fit(ui): use new portainer in login page and license page EE-1637 (#5604)
* use new portainer in login page and license page
2021-09-22 11:16:12 +12:00
Richard Wei
48c762c98b fix(notification): fix error in kube application stack name with space EE-1726 (#5691)
* fix error in kube application stack name with space
2021-09-21 20:58:23 +12:00
Richard Wei
488d86d200 fix(notification): fix error in kube application stack name with space EE-1726 (#5690)
* fix error in kube application stack name with space
2021-09-21 20:58:08 +12:00
Richard Wei
f10e0e4124 fix application table background not working with dark mode (#5617) 2021-09-21 19:29:25 +12:00
cong meng
5316cca3de fix(edge) EE-1733 cant edit edge groups (#5689)
* fix(edge) EE-1733 cant edit edge groups

* fix(edge) EE-1733 correct json names of a few edge objects

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-09-21 17:41:27 +12:00
cong meng
4267304e50 fix(edge) EE-1733 cant edit edge groups (#5687)
* fix(edge) EE-1733 cant edit edge groups

* fix(edge) EE-1733 correct json names of a few edge objects

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-09-21 17:41:14 +12:00
Richard Wei
deecbadce1 fix(k8s):fix difficulties selecting mixed protocols when creating k8s application EE-1073 (#5591)
* fix difficulties selecting mixed protocols when creating k8s application
2021-09-21 16:20:22 +12:00
fhanportainer
ecc9813750 fix(stack): fixed issue cannot deploy git stack without username. (#5680) 2021-09-21 13:42:04 +12:00
fhanportainer
24f11902b2 fix(stack): fixed issue cannot deploy git stack without username. (#5681) 2021-09-21 13:42:01 +12:00
cong meng
33118babdd fix(k8s) keep tunnel alive for websocket connection EE-1690 (#5677)
* fix(k8s) EE-1690 keep tunnel alive for websocket connection

* fix(k8s) EE-1690 fix comment

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-09-21 13:12:37 +12:00
cong meng
2aec348814 fix(k8s) keep tunnel alive for websocket connection EE-1690 (#5679)
* fix(k8s) EE-1690 keep tunnel alive for websocket connection

* fix(k8s) EE-1690 fix comment

Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-09-21 13:12:31 +12:00
Richard Wei
4d63459d67 fix edge heartbeat show red when use search filter (#5682) 2021-09-21 10:19:22 +12:00
Richard Wei
483559af09 fix edge heartbeat turn red when use search filter (#5683) 2021-09-21 10:19:18 +12:00
Richard Wei
1796545d2e fix authentication toggle on by default - set to off (#5666) 2021-09-20 22:36:22 +12:00
Richard Wei
a50795063c fix git stack authentication on by default - set to off (#5667) 2021-09-20 22:36:16 +12:00
Richard Wei
7c9f7a2a8b fix error description on stats for non-admin users (#5665) 2021-09-20 15:41:46 +12:00
Richard Wei
af8065e8c2 fix error description on stats for non-admin user (#5664) 2021-09-20 15:41:40 +12:00
Richard Wei
49d2c68a19 fix icon not displayed when template created via upload file (#5659) 2021-09-20 12:20:45 +12:00
Richard Wei
dc769b4c4d fix icon not displayed when template created via upload file (#5658) 2021-09-20 12:20:38 +12:00
Richard Wei
50393519ba fix(swagger): fix swagger api docs endpoint(s) rename to environment(s) EE-1661 (#5628)
* fix swagger api docs endpoint(s) rename to environment(s)
2021-09-20 12:14:59 +12:00
Richard Wei
dd808bb7bd fix(swagger): fix swagger api docs endpoint(s) rename to environment(s) EE-1661 (#5629)
* fix swagger api docs endpoint(s) rename to environment(s)
2021-09-20 12:14:22 +12:00
zees-dev
16dc58a5f1 fixed k8s app edit config dropdown default (#5647) 2021-09-20 11:08:24 +12:00
zees-dev
d911c50f1b fixed k8s app edit config dropdown default (#5651) 2021-09-20 11:08:18 +12:00
zees-dev
f6f31b8872 fixed docker image pull text on error scenario (#5656) 2021-09-20 01:42:55 +12:00
zees-dev
414f2c8c60 fixed docker image pull text on error scenario (#5655) 2021-09-20 01:42:39 +12:00
Chaim Lev-Ari
1f4a7b32e3 fix(customtemplate): edit custom template [EE-1691] (#5633) 2021-09-17 09:24:23 +03:00
Chaim Lev-Ari
689c2193c0 fix(customtemplate): edit custom template [EE-1691] (#5632) 2021-09-17 09:24:01 +03:00
zees-dev
a781021072 docker image pull toast fix (#5644) 2021-09-17 18:22:57 +12:00
cong meng
9121e8e69c fix(UI) EE-1657 Fix the agent version number in the UI (#5619)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-09-17 17:22:21 +12:00
zees-dev
53a2205f06 docker image pull toast fix (#5644) 2021-09-17 14:48:37 +12:00
Matt Hook
9492e30dc2 feat(helm/tests): update libhelm with new search mock EE-1599 (#5615)
* feat(helm/tests) add repo search and update libhelm with new mock EE-1599

* also enable repo search test
2021-09-16 16:56:46 +12:00
zees-dev
d2cbdf935a using new app metadata property to distinguish helm apps (#5627) 2021-09-16 16:09:39 +12:00
zees-dev
a098e24cca using new app metadata property to distinguish helm apps (#5624) 2021-09-16 16:09:33 +12:00
zees-dev
05efac44f6 helm templates blog post link fix (#5626) 2021-09-16 10:00:55 +12:00
zees-dev
555c9f238f fix webpack dev server (#5631) 2021-09-15 17:55:06 +12:00
634 changed files with 13840 additions and 6085 deletions

View File

@@ -30,7 +30,7 @@ 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/archive/1.23.2/faq/#how-do-i-get-the-logs-from-portainer)
You can see how [here](https://documentation.portainer.io/r/portainer-logs)
**Steps to reproduce the issue:**

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Portainer Business
url: https://www.portainer.io/portainerbusiness
about: Would you and your co-workers benefit from our enterprise edition which provides functionality to deploy Portainer at scale?

View File

@@ -44,7 +44,7 @@ Portainer CE is an open source project and is supported by the community. You ca
Learn more about Portainers community support channels [here.](https://www.portainer.io/help_about)
- Issues: https://github.com/portainer/portainer/issues
- Slack (chat): https://portainer.io/slack/
- Slack (chat): [https://portainer.slack.com/](https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA)
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.
@@ -59,7 +59,7 @@ You can join the Portainer Community by visiting community.portainer.io. This wi
## WORK FOR US
If you are a developer, and our code in this repo makes sense to you, we would love to hear from you. We are always on the hunt for awesome devs, either freelance or employed. Drop us a line to info@portainer.io with your details and we will be in touch.
If you are a developer, and our code in this repo makes sense to you, we would love to hear from you. We are always on the hunt for awesome devs, either freelance or employed. Drop us a line to info@portainer.io with your details and we will be in touch.
## Privacy

View File

@@ -4,7 +4,7 @@ You can find out more about Portainer at [http://portainer.io](http://portainer.
# Authentication
Most of the API endpoints require to be authenticated as well as some level of authorization to be used.
Most of the API environments(endpoints) require to be authenticated as well as some level of authorization to be used.
Portainer API uses JSON Web Token to manage authentication and thus requires you to provide a token in the **Authorization** header of each request
with the **Bearer** authentication mechanism.
@@ -16,7 +16,7 @@ Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIs
# Security
Each API endpoint has an associated access policy, it is documented in the description of each endpoint.
Each API environment(endpoint) has an associated access policy, it is documented in the description of each environment(endpoint).
Different access policies are available:
@@ -27,27 +27,27 @@ Different access policies are available:
### Public access
No authentication is required to access the endpoints with this access policy.
No authentication is required to access the environments(endpoints) with this access policy.
### Authenticated access
Authentication is required to access the endpoints with this access policy.
Authentication is required to access the environments(endpoints) with this access policy.
### Restricted access
Authentication is required to access the endpoints with this access policy.
Authentication is required to access the environments(endpoints) with this access policy.
Extra-checks might be added to ensure access to the resource is granted. Returned data might also be filtered.
### Administrator access
Authentication as well as an administrator role are required to access the endpoints with this access policy.
Authentication as well as an administrator role are required to access the environments(endpoints) with this access policy.
# Execute Docker requests
Portainer **DO NOT** expose specific endpoints to manage your Docker resources (create a container, remove a volume, etc...).
Portainer **DO NOT** expose specific environments(endpoints) to manage your Docker resources (create a container, remove a volume, etc...).
Instead, it acts as a reverse-proxy to the Docker HTTP API. This means that you can execute Docker requests **via** the Portainer HTTP API.
To do so, you can use the `/endpoints/{id}/docker` Portainer API endpoint (which is not documented below due to Swagger limitations). This endpoint has a restricted access policy so you still need to be authenticated to be able to query this endpoint. Any query on this endpoint will be proxied to the Docker API of the associated endpoint (requests and responses objects are the same as documented in the Docker API).
To do so, you can use the `/endpoints/{id}/docker` Portainer API environment(endpoint) (which is not documented below due to Swagger limitations). This environment(endpoint) has a restricted access policy so you still need to be authenticated to be able to query this environment(endpoint). Any query on this environment(endpoint) will be proxied to the Docker API of the associated environment(endpoint) (requests and responses objects are the same as documented in the Docker API).
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://documentation.portainer.io/api/api-examples/).

142
api/bolt/backup.go Normal file
View File

@@ -0,0 +1,142 @@
package bolt
import (
"fmt"
"os"
"path"
"time"
plog "github.com/portainer/portainer/api/bolt/log"
)
var backupDefaults = struct {
backupDir string
commonDir string
databaseFileName string
}{
"backups",
"common",
databaseFileName,
}
var backupLog = plog.NewScopedLog("bolt, backup")
//
// Backup Helpers
//
// createBackupFolders create initial folders for backups
func (store *Store) createBackupFolders() {
// create common dir
commonDir := store.commonBackupDir()
if exists, _ := store.fileService.FileExists(commonDir); !exists {
if err := os.MkdirAll(commonDir, 0700); err != nil {
backupLog.Error("Error while creating common backup folder", err)
}
}
}
func (store *Store) databasePath() string {
return path.Join(store.path, databaseFileName)
}
func (store *Store) commonBackupDir() string {
return path.Join(store.path, backupDefaults.backupDir, backupDefaults.commonDir)
}
func (store *Store) copyDBFile(from string, to string) error {
backupLog.Info(fmt.Sprintf("Copying db file from %s to %s", from, to))
err := store.fileService.Copy(from, to, true)
if err != nil {
backupLog.Error("Failed", err)
}
return err
}
// BackupOptions provide a helper to inject backup options
type BackupOptions struct {
Version int
BackupDir string
BackupFileName string
BackupPath string
}
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
if options == nil {
options = &BackupOptions{}
}
if options.Version == 0 {
options.Version, _ = store.version()
}
if options.BackupDir == "" {
options.BackupDir = store.commonBackupDir()
}
if options.BackupFileName == "" {
options.BackupFileName = fmt.Sprintf("%s.%s.%s", backupDefaults.databaseFileName, fmt.Sprintf("%03d", options.Version), time.Now().Format("20060102150405"))
}
if options.BackupPath == "" {
options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
}
return options
}
// BackupWithOptions backup current database with options
func (store *Store) BackupWithOptions(options *BackupOptions) (string, error) {
backupLog.Info("creating db backup")
store.createBackupFolders()
options = store.setupOptions(options)
return options.BackupPath, store.copyDBFile(store.databasePath(), options.BackupPath)
}
// RestoreWithOptions previously saved backup for the current Edition with options
// Restore strategies:
// - default: restore latest from current edition
// - restore a specific
func (store *Store) RestoreWithOptions(options *BackupOptions) error {
options = store.setupOptions(options)
// Check if backup file exist before restoring
_, err := os.Stat(options.BackupPath)
if os.IsNotExist(err) {
backupLog.Error(fmt.Sprintf("Backup file to restore does not exist %s", options.BackupPath), err)
return err
}
err = store.Close()
if err != nil {
backupLog.Error("Error while closing store before restore", err)
return err
}
backupLog.Info("Restoring db backup")
err = store.copyDBFile(options.BackupPath, store.databasePath())
if err != nil {
return err
}
return store.Open()
}
// RemoveWithOptions removes backup database based on supplied options
func (store *Store) RemoveWithOptions(options *BackupOptions) error {
backupLog.Info("Removing db backup")
options = store.setupOptions(options)
_, err := os.Stat(options.BackupPath)
if os.IsNotExist(err) {
backupLog.Error(fmt.Sprintf("Backup file to remove does not exist %s", options.BackupPath), err)
return err
}
backupLog.Info(fmt.Sprintf("Removing db file at %s", options.BackupPath))
err = os.Remove(options.BackupPath)
if err != nil {
backupLog.Error("Failed", err)
return err
}
return nil
}

116
api/bolt/backup_test.go Normal file
View File

@@ -0,0 +1,116 @@
package bolt
import (
"fmt"
"os"
"path"
"path/filepath"
"testing"
portainer "github.com/portainer/portainer/api"
)
// isFileExist is helper function to check for file existence
func isFileExist(path string) bool {
matches, err := filepath.Glob(path)
if err != nil {
return false
}
return len(matches) > 0
}
func TestCreateBackupFolders(t *testing.T) {
store, teardown := MustNewTestStore(false)
defer teardown()
backupPath := path.Join(store.path, backupDefaults.backupDir)
if isFileExist(backupPath) {
t.Error("Expect backups folder to not exist")
}
store.createBackupFolders()
if !isFileExist(backupPath) {
t.Error("Expect backups folder to exist")
}
}
func TestStoreCreation(t *testing.T) {
store, teardown := MustNewTestStore(true)
defer teardown()
if store == nil {
t.Error("Expect to create a store")
}
if store.edition() != portainer.PortainerCE {
t.Error("Expect to get CE Edition")
}
}
func TestBackup(t *testing.T) {
store, teardown := MustNewTestStore(true)
defer teardown()
t.Run("Backup should create default db backup", func(t *testing.T) {
store.VersionService.StoreDBVersion(portainer.DBVersion)
store.BackupWithOptions(nil)
backupFileName := path.Join(store.path, "backups", "common", fmt.Sprintf("portainer.db.%03d.*", portainer.DBVersion))
if !isFileExist(backupFileName) {
t.Errorf("Expect backup file to be created %s", backupFileName)
}
})
t.Run("BackupWithOption should create a name specific backup at common path", func(t *testing.T) {
store.BackupWithOptions(&BackupOptions{
BackupFileName: beforePortainerVersionUpgradeBackup,
BackupDir: store.commonBackupDir(),
})
backupFileName := path.Join(store.path, "backups", "common", beforePortainerVersionUpgradeBackup)
if !isFileExist(backupFileName) {
t.Errorf("Expect backup file to be created %s", backupFileName)
}
})
}
func TestRemoveWithOptions(t *testing.T) {
store, teardown := MustNewTestStore(true)
defer teardown()
t.Run("successfully removes file if existent", func(t *testing.T) {
store.createBackupFolders()
options := &BackupOptions{
BackupDir: store.commonBackupDir(),
BackupFileName: "test.txt",
}
filePath := path.Join(options.BackupDir, options.BackupFileName)
f, err := os.Create(filePath)
if err != nil {
t.Fatalf("file should be created; err=%s", err)
}
f.Close()
err = store.RemoveWithOptions(options)
if err != nil {
t.Errorf("RemoveWithOptions should successfully remove file; err=%w", err)
}
if isFileExist(f.Name()) {
t.Errorf("RemoveWithOptions should successfully remove file; file=%s", f.Name())
}
})
t.Run("fails to removes file if non-existent", func(t *testing.T) {
options := &BackupOptions{
BackupDir: store.commonBackupDir(),
BackupFileName: "test.txt",
}
err := store.RemoveWithOptions(options)
if err == nil {
t.Error("RemoveWithOptions should fail for non-existent file")
}
})
}

View File

@@ -2,7 +2,6 @@ package bolt
import (
"io"
"log"
"path"
"time"
@@ -21,7 +20,6 @@ import (
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/bolt/extension"
"github.com/portainer/portainer/api/bolt/internal"
"github.com/portainer/portainer/api/bolt/migrator"
"github.com/portainer/portainer/api/bolt/registry"
"github.com/portainer/portainer/api/bolt/resourcecontrol"
"github.com/portainer/portainer/api/bolt/role"
@@ -36,7 +34,6 @@ import (
"github.com/portainer/portainer/api/bolt/user"
"github.com/portainer/portainer/api/bolt/version"
"github.com/portainer/portainer/api/bolt/webhook"
"github.com/portainer/portainer/api/internal/authorization"
)
const (
@@ -76,6 +73,14 @@ type Store struct {
WebhookService *webhook.Service
}
func (store *Store) version() (int, error) {
version, err := store.VersionService.DBVersion()
if err == errors.ErrObjectNotFound {
version = 0
}
return version, err
}
func (store *Store) edition() portainer.SoftwareEdition {
edition, err := store.VersionService.Edition()
if err == errors.ErrObjectNotFound {
@@ -85,25 +90,13 @@ func (store *Store) edition() portainer.SoftwareEdition {
}
// NewStore initializes a new Store and the associated services
func NewStore(storePath string, fileService portainer.FileService) (*Store, error) {
store := &Store{
func NewStore(storePath string, fileService portainer.FileService) *Store {
return &Store{
path: storePath,
fileService: fileService,
isNew: true,
connection: &internal.DbConnection{},
}
databasePath := path.Join(storePath, databaseFileName)
databaseFileExists, err := fileService.FileExists(databasePath)
if err != nil {
return nil, err
}
if databaseFileExists {
store.isNew = false
}
return store, nil
}
// Open opens and initializes the BoltDB database.
@@ -115,7 +108,17 @@ func (store *Store) Open() error {
}
store.connection.DB = db
return store.initServices()
err = store.initServices()
if err != nil {
return err
}
// if we have DBVersion in the database then ensure we flag this as NOT a new store
if _, err := store.VersionService.DBVersion(); err == nil {
store.isNew = false
}
return nil
}
// Close closes the BoltDB database.
@@ -133,64 +136,6 @@ func (store *Store) IsNew() bool {
return store.isNew
}
// CheckCurrentEdition checks if current edition is community edition
func (store *Store) CheckCurrentEdition() error {
if store.edition() != portainer.PortainerCE {
return errors.ErrWrongDBEdition
}
return nil
}
// MigrateData automatically migrate the data based on the DBVersion.
// This process is only triggered on an existing database, not if the database was just created.
// if force is true, then migrate regardless.
func (store *Store) MigrateData(force bool) error {
if store.isNew && !force {
return store.VersionService.StoreDBVersion(portainer.DBVersion)
}
version, err := store.VersionService.DBVersion()
if err == errors.ErrObjectNotFound {
version = 0
} else if err != nil {
return err
}
if version < portainer.DBVersion {
migratorParams := &migrator.Parameters{
DB: store.connection.DB,
DatabaseVersion: version,
EndpointGroupService: store.EndpointGroupService,
EndpointService: store.EndpointService,
EndpointRelationService: store.EndpointRelationService,
ExtensionService: store.ExtensionService,
RegistryService: store.RegistryService,
ResourceControlService: store.ResourceControlService,
RoleService: store.RoleService,
ScheduleService: store.ScheduleService,
SettingsService: store.SettingsService,
StackService: store.StackService,
TagService: store.TagService,
TeamMembershipService: store.TeamMembershipService,
UserService: store.UserService,
VersionService: store.VersionService,
FileService: store.fileService,
DockerhubService: store.DockerHubService,
AuthorizationService: authorization.NewService(store),
}
migrator := migrator.NewMigrator(migratorParams)
log.Printf("Migrating database from version %v to %v.\n", version, portainer.DBVersion)
err = migrator.Migrate()
if err != nil {
log.Printf("An error occurred during database migration: %s\n", err)
return err
}
}
return nil
}
// BackupTo backs up db to a provided writer.
// It does hot backup and doesn't block other database reads and writes
func (store *Store) BackupTo(w io.Writer) error {
@@ -199,3 +144,11 @@ func (store *Store) BackupTo(w io.Writer) error {
return err
})
}
// CheckCurrentEdition checks if current edition is community edition
func (store *Store) CheckCurrentEdition() error {
if store.edition() != portainer.PortainerCE {
return errors.ErrWrongDBEdition
}
return nil
}

View File

@@ -95,7 +95,7 @@ func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
return internal.DeleteObject(service.connection, BucketName, identifier)
}
// GetNextIdentifier returns the next identifier for an endpoint.
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service *Service) GetNextIdentifier() int {
return internal.GetNextIdentifier(service.connection, BucketName)
}

View File

@@ -95,7 +95,7 @@ func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
return internal.DeleteObject(service.connection, BucketName, identifier)
}
// GetNextIdentifier returns the next identifier for an endpoint.
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service *Service) GetNextIdentifier() int {
return internal.GetNextIdentifier(service.connection, BucketName)
}

View File

@@ -11,7 +11,7 @@ const (
BucketName = "endpoints"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}
@@ -28,7 +28,7 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
}, nil
}
// Endpoint returns an endpoint by ID.
// Endpoint returns an environment(endpoint) by ID.
func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
var endpoint portainer.Endpoint
identifier := internal.Itob(int(ID))
@@ -41,19 +41,19 @@ func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
return &endpoint, nil
}
// UpdateEndpoint updates an endpoint.
// UpdateEndpoint updates an environment(endpoint).
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
identifier := internal.Itob(int(ID))
return internal.UpdateObject(service.connection, BucketName, identifier, endpoint)
}
// DeleteEndpoint deletes an endpoint.
// DeleteEndpoint deletes an environment(endpoint).
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
identifier := internal.Itob(int(ID))
return internal.DeleteObject(service.connection, BucketName, identifier)
}
// Endpoints return an array containing all the endpoints.
// Endpoints return an array containing all the environments(endpoints).
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
var endpoints = make([]portainer.Endpoint, 0)
@@ -76,12 +76,12 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
return endpoints, err
}
// CreateEndpoint assign an ID to a new endpoint and saves it.
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
return service.connection.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
// We manually manage sequences for endpoints
// We manually manage sequences for environments(endpoints)
err := bucket.SetSequence(uint64(endpoint.ID))
if err != nil {
return err
@@ -96,12 +96,12 @@ func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
})
}
// GetNextIdentifier returns the next identifier for an endpoint.
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service *Service) GetNextIdentifier() int {
return internal.GetNextIdentifier(service.connection, BucketName)
}
// Synchronize creates, updates and deletes endpoints inside a single transaction.
// Synchronize creates, updates and deletes environments(endpoints) inside a single transaction.
func (service *Service) Synchronize(toCreate, toUpdate, toDelete []*portainer.Endpoint) error {
return service.connection.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))

View File

@@ -12,7 +12,7 @@ const (
BucketName = "endpoint_groups"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}
@@ -29,7 +29,7 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
}, nil
}
// EndpointGroup returns an endpoint group by ID.
// EndpointGroup returns an environment(endpoint) group by ID.
func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
var endpointGroup portainer.EndpointGroup
identifier := internal.Itob(int(ID))
@@ -42,19 +42,19 @@ func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.
return &endpointGroup, nil
}
// UpdateEndpointGroup updates an endpoint group.
// UpdateEndpointGroup updates an environment(endpoint) group.
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
identifier := internal.Itob(int(ID))
return internal.UpdateObject(service.connection, BucketName, identifier, endpointGroup)
}
// DeleteEndpointGroup deletes an endpoint group.
// DeleteEndpointGroup deletes an environment(endpoint) group.
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
identifier := internal.Itob(int(ID))
return internal.DeleteObject(service.connection, BucketName, identifier)
}
// EndpointGroups return an array containing all the endpoint groups.
// EndpointGroups return an array containing all the environment(endpoint) groups.
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
var endpointGroups = make([]portainer.EndpointGroup, 0)
@@ -77,7 +77,7 @@ func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
return endpointGroups, err
}
// CreateEndpointGroup assign an ID to a new endpoint group and saves it.
// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it.
func (service *Service) CreateEndpointGroup(endpointGroup *portainer.EndpointGroup) error {
return service.connection.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))

View File

@@ -11,7 +11,7 @@ const (
BucketName = "endpoint_relations"
)
// Service represents a service for managing endpoint relation data.
// Service represents a service for managing environment(endpoint) relation data.
type Service struct {
connection *internal.DbConnection
}
@@ -28,7 +28,7 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
}, nil
}
// EndpointRelation returns a Endpoint relation object by EndpointID
// EndpointRelation returns a Environment(Endpoint) relation object by EndpointID
func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*portainer.EndpointRelation, error) {
var endpointRelation portainer.EndpointRelation
identifier := internal.Itob(int(endpointID))
@@ -55,13 +55,13 @@ func (service *Service) CreateEndpointRelation(endpointRelation *portainer.Endpo
})
}
// UpdateEndpointRelation updates an Endpoint relation object
// UpdateEndpointRelation updates an Environment(Endpoint) relation object
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
identifier := internal.Itob(int(EndpointID))
return internal.UpdateObject(service.connection, BucketName, identifier, endpointRelation)
}
// DeleteEndpointRelation deletes an Endpoint relation object
// DeleteEndpointRelation deletes an Environment(Endpoint) relation object
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
identifier := internal.Itob(int(EndpointID))
return internal.DeleteObject(service.connection, BucketName, identifier)

View File

@@ -12,7 +12,7 @@ const (
BucketName = "extension"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -12,7 +12,7 @@ const (
BucketName = "helm_user_repository"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -47,6 +47,7 @@ func (store *Store) Init() error {
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
KubeconfigExpiry: portainer.DefaultKubeconfigExpiry,
KubectlShellImage: portainer.DefaultKubectlShellImage,
}
err = store.SettingsService.UpdateSettings(defaultSettings)

View File

@@ -17,7 +17,7 @@ func UnmarshalObject(data []byte, object interface{}) error {
}
// UnmarshalObjectWithJsoniter decodes an object from binary data
// using the jsoniter library. It is mainly used to accelerate endpoint
// using the jsoniter library. It is mainly used to accelerate environment(endpoint)
// decoding at the moment.
func UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary

149
api/bolt/migrate_data.go Normal file
View File

@@ -0,0 +1,149 @@
package bolt
import (
"fmt"
"github.com/portainer/portainer/api/cli"
werrors "github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
plog "github.com/portainer/portainer/api/bolt/log"
"github.com/portainer/portainer/api/bolt/migrator"
"github.com/portainer/portainer/api/internal/authorization"
)
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
var migrateLog = plog.NewScopedLog("bolt, migrate")
// FailSafeMigrate backup and restore DB if migration fail
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator) (err error) {
defer func() {
if e := recover(); e != nil {
store.Rollback(true)
err = fmt.Errorf("%v", e)
}
}()
// !Important: we must use a named return value in the function definition and not a local
// !variable referenced from the closure or else the return value will be incorrectly set
return migrator.Migrate()
}
// MigrateData automatically migrate the data based on the DBVersion.
// This process is only triggered on an existing database, not if the database was just created.
// if force is true, then migrate regardless.
func (store *Store) MigrateData(force bool) error {
if store.isNew && !force {
return store.VersionService.StoreDBVersion(portainer.DBVersion)
}
migrator, err := store.newMigrator()
if err != nil {
return err
}
// backup db file before upgrading DB to support rollback
isUpdating, err := store.VersionService.IsUpdating()
if err != nil && err != errors.ErrObjectNotFound {
return err
}
if !isUpdating && migrator.Version() != portainer.DBVersion {
err = store.backupVersion(migrator)
if err != nil {
return werrors.Wrapf(err, "failed to backup database")
}
}
if migrator.Version() < portainer.DBVersion {
migrateLog.Info(fmt.Sprintf("Migrating database from version %v to %v.\n", migrator.Version(), portainer.DBVersion))
err = store.FailSafeMigrate(migrator)
if err != nil {
migrateLog.Error("An error occurred during database migration", err)
return err
}
}
return nil
}
func (store *Store) newMigrator() (*migrator.Migrator, error) {
version, err := store.version()
if err != nil {
return nil, err
}
migratorParams := &migrator.Parameters{
DB: store.connection.DB,
DatabaseVersion: version,
EndpointGroupService: store.EndpointGroupService,
EndpointService: store.EndpointService,
EndpointRelationService: store.EndpointRelationService,
ExtensionService: store.ExtensionService,
RegistryService: store.RegistryService,
ResourceControlService: store.ResourceControlService,
RoleService: store.RoleService,
ScheduleService: store.ScheduleService,
SettingsService: store.SettingsService,
StackService: store.StackService,
TagService: store.TagService,
TeamMembershipService: store.TeamMembershipService,
UserService: store.UserService,
VersionService: store.VersionService,
FileService: store.fileService,
DockerhubService: store.DockerHubService,
AuthorizationService: authorization.NewService(store),
}
return migrator.NewMigrator(migratorParams), nil
}
// getBackupRestoreOptions returns options to store db at common backup dir location; used by:
// - db backup prior to version upgrade
// - db rollback
func getBackupRestoreOptions(store *Store) *BackupOptions {
return &BackupOptions{
BackupDir: store.commonBackupDir(),
BackupFileName: beforePortainerVersionUpgradeBackup,
}
}
// backupVersion will backup the database or panic if any errors occur
func (store *Store) backupVersion(migrator *migrator.Migrator) error {
migrateLog.Info("Backing up database prior to version upgrade...")
options := getBackupRestoreOptions(store)
_, err := store.BackupWithOptions(options)
if err != nil {
migrateLog.Error("An error occurred during database backup", err)
removalErr := store.RemoveWithOptions(options)
if removalErr != nil {
migrateLog.Error("An error occurred during store removal prior to backup", err)
}
return err
}
return nil
}
// Rollback to a pre-upgrade backup copy/snapshot of portainer.db
func (store *Store) Rollback(force bool) error {
if !force {
confirmed, err := cli.Confirm("Are you sure you want to rollback your database to the previous backup?")
if err != nil || !confirmed {
return err
}
}
options := getBackupRestoreOptions(store)
err := store.RestoreWithOptions(options)
if err != nil {
return err
}
return store.Close()
}

View File

@@ -0,0 +1,172 @@
package bolt
import (
"fmt"
"log"
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
)
// testVersion is a helper which tests current store version against wanted version
func testVersion(store *Store, versionWant int, t *testing.T) {
if v, _ := store.version(); v != versionWant {
t.Errorf("Expect store version to be %d but was %d", versionWant, v)
}
}
func TestMigrateData(t *testing.T) {
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
store, teardown := MustNewTestStore(false)
defer teardown()
if !store.IsNew() {
t.Error("Expect a new DB")
}
store.MigrateData(false)
testVersion(store, portainer.DBVersion, t)
store.Close()
store.Open()
if store.IsNew() {
t.Error("Expect store to NOT be new DB")
}
})
tests := []struct {
version int
expectedVersion int
}{
{version: 2, expectedVersion: portainer.DBVersion},
{version: 21, expectedVersion: portainer.DBVersion},
}
for _, tc := range tests {
store, teardown := MustNewTestStore(true)
defer teardown()
// Setup data
store.VersionService.StoreDBVersion(tc.version)
// Required roles by migrations 22.2
store.RoleService.CreateRole(&portainer.Role{ID: 1})
store.RoleService.CreateRole(&portainer.Role{ID: 2})
store.RoleService.CreateRole(&portainer.Role{ID: 3})
store.RoleService.CreateRole(&portainer.Role{ID: 4})
t.Run(fmt.Sprintf("MigrateData for version %d", tc.version), func(t *testing.T) {
store.MigrateData(true)
testVersion(store, tc.expectedVersion, t)
})
t.Run(fmt.Sprintf("Restoring DB after migrateData for version %d", tc.version), func(t *testing.T) {
store.Rollback(true)
store.Open()
testVersion(store, tc.version, t)
})
}
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
store, teardown := MustNewTestStore(false)
defer teardown()
version := 2
store.VersionService.StoreDBVersion(version)
store.MigrateData(true)
testVersion(store, version, t)
})
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
store, teardown := MustNewTestStore(false)
defer teardown()
store.VersionService.StoreDBVersion(0)
store.MigrateData(true)
options := store.setupOptions(getBackupRestoreOptions(store))
if !isFileExist(options.BackupPath) {
t.Errorf("Backup file should exist; file=%s", options.BackupPath)
}
})
t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
store, teardown := MustNewTestStore(false)
defer teardown()
store.VersionService.StoreIsUpdating(true)
store.MigrateData(true)
options := store.setupOptions(getBackupRestoreOptions(store))
if isFileExist(options.BackupPath) {
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
}
})
t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
store, teardown := MustNewTestStore(false)
defer teardown()
store.MigrateData(true)
options := store.setupOptions(getBackupRestoreOptions(store))
if isFileExist(options.BackupPath) {
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
}
})
}
func Test_getBackupRestoreOptions(t *testing.T) {
store, teardown := MustNewTestStore(false)
defer teardown()
options := getBackupRestoreOptions(store)
wantDir := store.commonBackupDir()
if !strings.HasSuffix(options.BackupDir, wantDir) {
log.Fatalf("incorrect backup dir; got=%s, want=%s", options.BackupDir, wantDir)
}
wantFilename := "portainer.db.bak"
if options.BackupFileName != wantFilename {
log.Fatalf("incorrect backup file; got=%s, want=%s", options.BackupFileName, wantFilename)
}
}
func TestRollback(t *testing.T) {
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
version := 21
store, teardown := MustNewTestStore(false)
defer teardown()
store.VersionService.StoreDBVersion(version)
_, err := store.BackupWithOptions(getBackupRestoreOptions(store))
if err != nil {
log.Fatal(err)
}
// Change the current edition
err = store.VersionService.StoreDBVersion(version + 10)
if err != nil {
log.Fatal(err)
}
err = store.Rollback(true)
if err != nil {
t.Logf("Rollback failed: %s", err)
t.Fail()
return
}
store.Open()
testVersion(store, version, t)
})
}

View File

@@ -0,0 +1,327 @@
package migrator
import (
"fmt"
werrors "github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
)
func migrationError(err error, context string) error {
return werrors.Wrap(err, "failed in "+context)
}
// Migrate checks the database version and migrate the existing data to the most recent data model.
func (m *Migrator) Migrate() error {
// set DB to updating status
err := m.versionService.StoreIsUpdating(true)
if err != nil {
return migrationError(err, "StoreIsUpdating")
}
// Portainer < 1.12
if m.currentDBVersion < 1 {
err := m.updateAdminUserToDBVersion1()
if err != nil {
return migrationError(err, "updateAdminUserToDBVersion1")
}
}
// Portainer 1.12.x
if m.currentDBVersion < 2 {
err := m.updateResourceControlsToDBVersion2()
if err != nil {
return migrationError(err, "updateResourceControlsToDBVersion2")
}
err = m.updateEndpointsToDBVersion2()
if err != nil {
return migrationError(err, "updateEndpointsToDBVersion2")
}
}
// Portainer 1.13.x
if m.currentDBVersion < 3 {
err := m.updateSettingsToDBVersion3()
if err != nil {
return migrationError(err, "updateSettingsToDBVersion3")
}
}
// Portainer 1.14.0
if m.currentDBVersion < 4 {
err := m.updateEndpointsToDBVersion4()
if err != nil {
return migrationError(err, "updateEndpointsToDBVersion4")
}
}
// https://github.com/portainer/portainer/issues/1235
if m.currentDBVersion < 5 {
err := m.updateSettingsToVersion5()
if err != nil {
return migrationError(err, "updateSettingsToVersion5")
}
}
// https://github.com/portainer/portainer/issues/1236
if m.currentDBVersion < 6 {
err := m.updateSettingsToVersion6()
if err != nil {
return migrationError(err, "updateSettingsToVersion6")
}
}
// https://github.com/portainer/portainer/issues/1449
if m.currentDBVersion < 7 {
err := m.updateSettingsToVersion7()
if err != nil {
return migrationError(err, "updateSettingsToVersion7")
}
}
if m.currentDBVersion < 8 {
err := m.updateEndpointsToVersion8()
if err != nil {
return migrationError(err, "updateEndpointsToVersion8")
}
}
// https: //github.com/portainer/portainer/issues/1396
if m.currentDBVersion < 9 {
err := m.updateEndpointsToVersion9()
if err != nil {
return migrationError(err, "updateEndpointsToVersion9")
}
}
// https://github.com/portainer/portainer/issues/461
if m.currentDBVersion < 10 {
err := m.updateEndpointsToVersion10()
if err != nil {
return migrationError(err, "updateEndpointsToVersion10")
}
}
// https://github.com/portainer/portainer/issues/1906
if m.currentDBVersion < 11 {
err := m.updateEndpointsToVersion11()
if err != nil {
return migrationError(err, "updateEndpointsToVersion11")
}
}
// Portainer 1.18.0
if m.currentDBVersion < 12 {
err := m.updateEndpointsToVersion12()
if err != nil {
return migrationError(err, "updateEndpointsToVersion12")
}
err = m.updateEndpointGroupsToVersion12()
if err != nil {
return migrationError(err, "updateEndpointGroupsToVersion12")
}
err = m.updateStacksToVersion12()
if err != nil {
return migrationError(err, "updateStacksToVersion12")
}
}
// Portainer 1.19.0
if m.currentDBVersion < 13 {
err := m.updateSettingsToVersion13()
if err != nil {
return migrationError(err, "updateSettingsToVersion13")
}
}
// Portainer 1.19.2
if m.currentDBVersion < 14 {
err := m.updateResourceControlsToDBVersion14()
if err != nil {
return migrationError(err, "updateResourceControlsToDBVersion14")
}
}
// Portainer 1.20.0
if m.currentDBVersion < 15 {
err := m.updateSettingsToDBVersion15()
if err != nil {
return migrationError(err, "updateSettingsToDBVersion15")
}
err = m.updateTemplatesToVersion15()
if err != nil {
return migrationError(err, "updateTemplatesToVersion15")
}
}
if m.currentDBVersion < 16 {
err := m.updateSettingsToDBVersion16()
if err != nil {
return migrationError(err, "updateSettingsToDBVersion16")
}
}
// Portainer 1.20.1
if m.currentDBVersion < 17 {
err := m.updateExtensionsToDBVersion17()
if err != nil {
return migrationError(err, "updateExtensionsToDBVersion17")
}
}
// Portainer 1.21.0
if m.currentDBVersion < 18 {
err := m.updateUsersToDBVersion18()
if err != nil {
return migrationError(err, "updateUsersToDBVersion18")
}
err = m.updateEndpointsToDBVersion18()
if err != nil {
return migrationError(err, "updateEndpointsToDBVersion18")
}
err = m.updateEndpointGroupsToDBVersion18()
if err != nil {
return migrationError(err, "updateEndpointGroupsToDBVersion18")
}
err = m.updateRegistriesToDBVersion18()
if err != nil {
return migrationError(err, "updateRegistriesToDBVersion18")
}
}
// Portainer 1.22.0
if m.currentDBVersion < 19 {
err := m.updateSettingsToDBVersion19()
if err != nil {
return migrationError(err, "updateSettingsToDBVersion19")
}
}
// Portainer 1.22.1
if m.currentDBVersion < 20 {
err := m.updateUsersToDBVersion20()
if err != nil {
return migrationError(err, "updateUsersToDBVersion20")
}
err = m.updateSettingsToDBVersion20()
if err != nil {
return migrationError(err, "updateSettingsToDBVersion20")
}
err = m.updateSchedulesToDBVersion20()
if err != nil {
return migrationError(err, "updateSchedulesToDBVersion20")
}
}
// Portainer 1.23.0
// DBVersion 21 is missing as it was shipped as via hotfix 1.22.2
if m.currentDBVersion < 22 {
err := m.updateResourceControlsToDBVersion22()
if err != nil {
return migrationError(err, "updateResourceControlsToDBVersion22")
}
err = m.updateUsersAndRolesToDBVersion22()
if err != nil {
return migrationError(err, "updateUsersAndRolesToDBVersion22")
}
}
// Portainer 1.24.0
if m.currentDBVersion < 23 {
err := m.updateTagsToDBVersion23()
if err != nil {
return migrationError(err, "updateTagsToDBVersion23")
}
err = m.updateEndpointsAndEndpointGroupsToDBVersion23()
if err != nil {
return migrationError(err, "updateEndpointsAndEndpointGroupsToDBVersion23")
}
}
// Portainer 1.24.1
if m.currentDBVersion < 24 {
err := m.updateSettingsToDB24()
if err != nil {
return migrationError(err, "updateSettingsToDB24")
}
}
// Portainer 2.0.0
if m.currentDBVersion < 25 {
err := m.updateSettingsToDB25()
if err != nil {
return migrationError(err, "updateSettingsToDB25")
}
err = m.updateStacksToDB24()
if err != nil {
return migrationError(err, "updateStacksToDB24")
}
}
// Portainer 2.1.0
if m.currentDBVersion < 26 {
err := m.updateEndpointSettingsToDB25()
if err != nil {
return migrationError(err, "updateEndpointSettingsToDB25")
}
}
// Portainer 2.2.0
if m.currentDBVersion < 27 {
err := m.updateStackResourceControlToDB27()
if err != nil {
return migrationError(err, "updateStackResourceControlToDB27")
}
}
// Portainer 2.6.0
if m.currentDBVersion < 30 {
err := m.migrateDBVersionToDB30()
if err != nil {
return migrationError(err, "migrateDBVersionToDB30")
}
}
// Portainer 2.9.0
if m.currentDBVersion < 32 {
err := m.migrateDBVersionToDB32()
if err != nil {
return migrationError(err, "migrateDBVersionToDB32")
}
}
// Portainer 2.9.1
if m.currentDBVersion < 33 {
err := m.migrateDBVersionToDB33()
if err != nil {
return migrationError(err, "migrateDBVersionToDB33")
}
}
// Portainer 2.10
if m.currentDBVersion < 34 {
if err := m.migrateDBVersionToDB34(); err != nil {
return migrationError(err, "migrateDBVersionToDB34")
}
}
err = m.versionService.StoreDBVersion(portainer.DBVersion)
if err != nil {
return migrationError(err, "StoreDBVersion")
}
migrateLog.Info(fmt.Sprintf("Updated DB version to %d", portainer.DBVersion))
// reset DB updating status
return m.versionService.StoreIsUpdating(false)
}

View File

@@ -2,6 +2,7 @@ package migrator
import (
"fmt"
"log"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
@@ -167,6 +168,7 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
totalSnapshots := len(endpoint.Snapshots)
if totalSnapshots == 0 {
log.Println("[DEBUG] [volume migration] [message: no snapshot found]")
continue
}
@@ -174,11 +176,13 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
endpointDockerID, err := snapshotutils.FetchDockerID(snapshot)
if err != nil {
return fmt.Errorf("failed fetching environment docker id: %w", err)
log.Printf("[WARN] [bolt,migrator,v31] [message: failed fetching environment docker id] [err: %s]", err)
continue
}
if volumesData, done := snapshot.SnapshotRaw.Volumes.(map[string]interface{}); done {
if volumesData["Volumes"] == nil {
log.Println("[DEBUG] [volume migration] [message: no volume data found]")
continue
}
@@ -199,7 +203,7 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
if err != nil {
return fmt.Errorf("failed deleting resource control %d: %w", resourceControl.ID, err)
}
log.Printf("[DEBUG] [volume migration] [message: legacy resource control(%s) has been deleted]", resourceControl.ResourceID)
}
}
@@ -210,8 +214,16 @@ func findResourcesToUpdateForDB32(dockerID string, volumesData map[string]interf
volumes := volumesData["Volumes"].([]interface{})
for _, volumeMeta := range volumes {
volume := volumeMeta.(map[string]interface{})
volumeName := volume["Name"].(string)
oldResourceID := fmt.Sprintf("%s%s", volumeName, volume["CreatedAt"].(string))
volumeName, nameExist := volume["Name"].(string)
if !nameExist {
continue
}
createTime, createTimeExist := volume["CreatedAt"].(string)
if !createTimeExist {
continue
}
oldResourceID := fmt.Sprintf("%s%s", volumeName, createTime)
resourceControl, ok := volumeResourceControls[oldResourceID]
if ok {

View File

@@ -0,0 +1,21 @@
package migrator
import portainer "github.com/portainer/portainer/api"
func (m *Migrator) migrateDBVersionToDB33() error {
if err := m.migrateSettingsToDB33(); err != nil {
return err
}
return nil
}
func (m *Migrator) migrateSettingsToDB33() error {
settings, err := m.settingsService.Settings()
if err != nil {
return err
}
settings.KubectlShellImage = portainer.DefaultKubectlShellImage
return m.settingsService.UpdateSettings(settings)
}

View File

@@ -4,7 +4,7 @@ import (
portainer "github.com/portainer/portainer/api"
)
func (m *Migrator) migrateDBVersionTo33() error {
func (m *Migrator) migrateDBVersionToDB34() error {
err := migrateStackEntryPoint(m.stackService)
if err != nil {
return err

View File

@@ -14,7 +14,7 @@ import (
)
func TestMigrateStackEntryPoint(t *testing.T) {
dbConn, err := bolt.Open(path.Join(t.TempDir(), "portainer-ee-mig-33.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
dbConn, err := bolt.Open(path.Join(t.TempDir(), "portainer-ee-mig-34.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
assert.NoError(t, err, "failed to init testing DB connection")
defer dbConn.Close()

View File

@@ -27,8 +27,9 @@ var migrateLog = plog.NewScopedLog("bolt, migrate")
type (
// Migrator defines a service to migrate data after a Portainer version update.
Migrator struct {
currentDBVersion int
db *bolt.DB
db *bolt.DB
currentDBVersion int
endpointGroupService *endpointgroup.Service
endpointService *endpoint.Service
endpointRelationService *endpointrelation.Service
@@ -97,295 +98,7 @@ func NewMigrator(parameters *Parameters) *Migrator {
}
}
// Migrate checks the database version and migrate the existing data to the most recent data model.
func (m *Migrator) Migrate() error {
// Portainer < 1.12
if m.currentDBVersion < 1 {
err := m.updateAdminUserToDBVersion1()
if err != nil {
return err
}
}
// Portainer 1.12.x
if m.currentDBVersion < 2 {
err := m.updateResourceControlsToDBVersion2()
if err != nil {
return err
}
err = m.updateEndpointsToDBVersion2()
if err != nil {
return err
}
}
// Portainer 1.13.x
if m.currentDBVersion < 3 {
err := m.updateSettingsToDBVersion3()
if err != nil {
return err
}
}
// Portainer 1.14.0
if m.currentDBVersion < 4 {
err := m.updateEndpointsToDBVersion4()
if err != nil {
return err
}
}
// https://github.com/portainer/portainer/issues/1235
if m.currentDBVersion < 5 {
err := m.updateSettingsToVersion5()
if err != nil {
return err
}
}
// https://github.com/portainer/portainer/issues/1236
if m.currentDBVersion < 6 {
err := m.updateSettingsToVersion6()
if err != nil {
return err
}
}
// https://github.com/portainer/portainer/issues/1449
if m.currentDBVersion < 7 {
err := m.updateSettingsToVersion7()
if err != nil {
return err
}
}
if m.currentDBVersion < 8 {
err := m.updateEndpointsToVersion8()
if err != nil {
return err
}
}
// https: //github.com/portainer/portainer/issues/1396
if m.currentDBVersion < 9 {
err := m.updateEndpointsToVersion9()
if err != nil {
return err
}
}
// https://github.com/portainer/portainer/issues/461
if m.currentDBVersion < 10 {
err := m.updateEndpointsToVersion10()
if err != nil {
return err
}
}
// https://github.com/portainer/portainer/issues/1906
if m.currentDBVersion < 11 {
err := m.updateEndpointsToVersion11()
if err != nil {
return err
}
}
// Portainer 1.18.0
if m.currentDBVersion < 12 {
err := m.updateEndpointsToVersion12()
if err != nil {
return err
}
err = m.updateEndpointGroupsToVersion12()
if err != nil {
return err
}
err = m.updateStacksToVersion12()
if err != nil {
return err
}
}
// Portainer 1.19.0
if m.currentDBVersion < 13 {
err := m.updateSettingsToVersion13()
if err != nil {
return err
}
}
// Portainer 1.19.2
if m.currentDBVersion < 14 {
err := m.updateResourceControlsToDBVersion14()
if err != nil {
return err
}
}
// Portainer 1.20.0
if m.currentDBVersion < 15 {
err := m.updateSettingsToDBVersion15()
if err != nil {
return err
}
err = m.updateTemplatesToVersion15()
if err != nil {
return err
}
}
if m.currentDBVersion < 16 {
err := m.updateSettingsToDBVersion16()
if err != nil {
return err
}
}
// Portainer 1.20.1
if m.currentDBVersion < 17 {
err := m.updateExtensionsToDBVersion17()
if err != nil {
return err
}
}
// Portainer 1.21.0
if m.currentDBVersion < 18 {
err := m.updateUsersToDBVersion18()
if err != nil {
return err
}
err = m.updateEndpointsToDBVersion18()
if err != nil {
return err
}
err = m.updateEndpointGroupsToDBVersion18()
if err != nil {
return err
}
err = m.updateRegistriesToDBVersion18()
if err != nil {
return err
}
}
// Portainer 1.22.0
if m.currentDBVersion < 19 {
err := m.updateSettingsToDBVersion19()
if err != nil {
return err
}
}
// Portainer 1.22.1
if m.currentDBVersion < 20 {
err := m.updateUsersToDBVersion20()
if err != nil {
return err
}
err = m.updateSettingsToDBVersion20()
if err != nil {
return err
}
err = m.updateSchedulesToDBVersion20()
if err != nil {
return err
}
}
// Portainer 1.23.0
// DBVersion 21 is missing as it was shipped as via hotfix 1.22.2
if m.currentDBVersion < 22 {
err := m.updateResourceControlsToDBVersion22()
if err != nil {
return err
}
err = m.updateUsersAndRolesToDBVersion22()
if err != nil {
return err
}
}
// Portainer 1.24.0
if m.currentDBVersion < 23 {
err := m.updateTagsToDBVersion23()
if err != nil {
return err
}
err = m.updateEndpointsAndEndpointGroupsToDBVersion23()
if err != nil {
return err
}
}
// Portainer 1.24.1
if m.currentDBVersion < 24 {
err := m.updateSettingsToDB24()
if err != nil {
return err
}
}
// Portainer 2.0.0
if m.currentDBVersion < 25 {
err := m.updateSettingsToDB25()
if err != nil {
return err
}
err = m.updateStacksToDB24()
if err != nil {
return err
}
}
// Portainer 2.1.0
if m.currentDBVersion < 26 {
err := m.updateEndpointSettingsToDB25()
if err != nil {
return err
}
}
// Portainer 2.2.0
if m.currentDBVersion < 27 {
err := m.updateStackResourceControlToDB27()
if err != nil {
return err
}
}
// Portainer 2.6.0
if m.currentDBVersion < 30 {
err := m.migrateDBVersionToDB30()
if err != nil {
return err
}
}
// Portainer 2.9.0
if m.currentDBVersion < 32 {
err := m.migrateDBVersionToDB32()
if err != nil {
return err
}
}
if m.currentDBVersion < 33 {
if err := m.migrateDBVersionTo33(); err != nil {
return err
}
}
return m.versionService.StoreDBVersion(portainer.DBVersion)
// Version exposes version of database
func (migrator *Migrator) Version() int {
return migrator.currentDBVersion
}

View File

@@ -12,7 +12,7 @@ const (
BucketName = "registries"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -12,7 +12,7 @@ const (
BucketName = "resource_control"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -12,7 +12,7 @@ const (
BucketName = "roles"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -196,7 +196,7 @@ func (store *Store) EdgeStack() portainer.EdgeStackService {
return store.EdgeStackService
}
// Endpoint gives access to the Endpoint data management layer
// Environment(Endpoint) gives access to the Environment(Endpoint) data management layer
func (store *Store) Endpoint() portainer.EndpointService {
return store.EndpointService
}

View File

@@ -11,7 +11,7 @@ const (
settingsKey = "SETTINGS"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -16,7 +16,7 @@ const (
BucketName = "stacks"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}
@@ -192,8 +192,8 @@ func (service *Service) RefreshableStacks() ([]portainer.Stack, error) {
bucket := tx.Bucket([]byte(BucketName))
cursor := bucket.Cursor()
var stack portainer.Stack
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
stack := portainer.Stack{}
err := internal.UnmarshalObject(v, &stack)
if err != nil {
return err

View File

@@ -4,18 +4,12 @@ import (
"testing"
"time"
"github.com/portainer/portainer/api/bolt"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/bolt/bolttest"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/filesystem"
"github.com/stretchr/testify/assert"
)
func newGuidString(t *testing.T) string {
@@ -35,7 +29,7 @@ func TestService_StackByWebhookID(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
}
store, teardown := bolttest.MustNewTestStore(true)
store, teardown := bolt.MustNewTestStore(true)
defer teardown()
b := stackBuilder{t: t, store: store}
@@ -93,7 +87,7 @@ func Test_RefreshableStacks(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
}
store, teardown := bolttest.MustNewTestStore(true)
store, teardown := bolt.MustNewTestStore(true)
defer teardown()
staticStack := portainer.Stack{ID: 1}

View File

@@ -12,7 +12,7 @@ const (
BucketName = "tags"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -15,7 +15,7 @@ const (
BucketName = "teams"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -12,7 +12,7 @@ const (
BucketName = "team_membership"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -1,4 +1,4 @@
package bolttest
package bolt
import (
"io/ioutil"
@@ -6,13 +6,12 @@ import (
"os"
"github.com/pkg/errors"
"github.com/portainer/portainer/api/bolt"
"github.com/portainer/portainer/api/filesystem"
)
var errTempDir = errors.New("can't create a temp dir")
func MustNewTestStore(init bool) (*bolt.Store, func()) {
func MustNewTestStore(init bool) (*Store, func()) {
store, teardown, err := NewTestStore(init)
if err != nil {
if !errors.Is(err, errTempDir) {
@@ -24,7 +23,7 @@ func MustNewTestStore(init bool) (*bolt.Store, func()) {
return store, teardown
}
func NewTestStore(init bool) (*bolt.Store, func(), error) {
func NewTestStore(init bool) (*Store, func(), error) {
// Creates unique temp directory in a concurrency friendly manner.
dataStorePath, err := ioutil.TempDir("", "boltdb")
if err != nil {
@@ -36,11 +35,7 @@ func NewTestStore(init bool) (*bolt.Store, func(), error) {
return nil, nil, err
}
store, err := bolt.NewStore(dataStorePath, fileService)
if err != nil {
return nil, nil, err
}
store := NewStore(dataStorePath, fileService)
err = store.Open()
if err != nil {
return nil, nil, err
@@ -60,7 +55,7 @@ func NewTestStore(init bool) (*bolt.Store, func(), error) {
return store, teardown, nil
}
func teardown(store *bolt.Store, dataStorePath string) {
func teardown(store *Store, dataStorePath string) {
err := store.Close()
if err != nil {
log.Fatalln(err)

View File

@@ -11,7 +11,7 @@ const (
infoKey = "INFO"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -15,7 +15,7 @@ const (
BucketName = "users"
)
// Service represents a service for managing endpoint data.
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection *internal.DbConnection
}

View File

@@ -15,6 +15,7 @@ const (
versionKey = "DB_VERSION"
instanceKey = "INSTANCE_ID"
editionKey = "EDITION"
updatingKey = "DB_UPDATING"
)
// Service represents a service to manage stored versions.
@@ -83,6 +84,21 @@ func (service *Service) StoreDBVersion(version int) error {
})
}
// IsUpdating retrieves the database updating status.
func (service *Service) IsUpdating() (bool, error) {
isUpdating, err := service.getKey(updatingKey)
if err != nil {
return false, err
}
return strconv.ParseBool(string(isUpdating))
}
// StoreIsUpdating store the database updating status.
func (service *Service) StoreIsUpdating(isUpdating bool) error {
return service.setKey(updatingKey, strconv.FormatBool(isUpdating))
}
// InstanceID retrieves the stored instance ID.
func (service *Service) InstanceID() (string, error) {
var data []byte

View File

@@ -6,7 +6,7 @@ import (
portainer "github.com/portainer/portainer/api"
)
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an endpoint.
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *portainer.EdgeJob) {
tunnel := service.GetTunnelDetails(endpointID)

View File

@@ -3,7 +3,9 @@ package chisel
import (
"context"
"fmt"
"github.com/portainer/portainer/api/http/proxy"
"log"
"net/http"
"strconv"
"time"
@@ -31,6 +33,7 @@ type Service struct {
snapshotService portainer.SnapshotService
chiselServer *chserver.Server
shutdownCtx context.Context
ProxyManager *proxy.Manager
}
// NewService returns a pointer to a new instance of Service
@@ -42,6 +45,55 @@ func NewService(dataStore portainer.DataStore, shutdownCtx context.Context) *Ser
}
}
// 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)
req, err := http.NewRequest(http.MethodHead, requestURL, nil)
if err != nil {
return err
}
httpClient := &http.Client{
Timeout: 3 * time.Second,
}
_, err = httpClient.Do(req)
if err != nil {
return err
}
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.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: start for %.0f minutes]\n", endpointID, maxAlive.Minutes())
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
pingTicker := time.NewTicker(tunnelCleanupInterval)
defer pingTicker.Stop()
for {
select {
case <-pingTicker.C:
service.SetTunnelStatusToActive(endpointID)
err := service.pingAgent(endpointID)
if err != nil {
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [warning: ping agent err=%s]\n", endpointID, err)
}
case <-maxAliveTicker.C:
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as %.0f minutes timeout]\n", endpointID, maxAlive.Minutes())
return
case <-ctx.Done():
err := ctx.Err()
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as err=%s]\n", endpointID, err)
return
}
}
}()
}
// StartTunnelServer starts a tunnel server on the specified addr and port.
// It uses a seed to generate a new private/public key pair. If the seed cannot
// be found inside the database, it will generate a new one randomly and persist it.
@@ -165,18 +217,13 @@ func (service *Service) checkTunnels() {
}
}
if len(tunnel.Jobs) > 0 {
endpointID, err := strconv.Atoi(item.Key)
if err != nil {
log.Printf("[ERROR] [chisel,conversion] Invalid environment identifier (id: %s): %s", item.Key, err)
continue
}
service.SetTunnelStatusToIdle(portainer.EndpointID(endpointID))
} else {
service.tunnelDetailsMap.Remove(item.Key)
endpointID, err := strconv.Atoi(item.Key)
if err != nil {
log.Printf("[ERROR] [chisel,conversion] Invalid environment identifier (id: %s): %s", item.Key, err)
continue
}
service.SetTunnelStatusToIdle(portainer.EndpointID(endpointID))
}
}

View File

@@ -38,7 +38,7 @@ func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// GetTunnelDetails returns information about the tunnel associated to an endpoint.
// GetTunnelDetails returns information about the tunnel associated to an environment(endpoint).
func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
key := strconv.Itoa(int(endpointID))
@@ -56,7 +56,48 @@ func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) *porta
}
}
// SetTunnelStatusToActive update the status of the tunnel associated to the specified endpoint.
// GetActiveTunnel retrieves an active tunnel which allows communicating with edge agent
func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (*portainer.TunnelDetails, error) {
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 nil, fmt.Errorf("failed opening tunnel to endpoint: %w", err)
}
if endpoint.EdgeCheckinInterval == 0 {
settings, err := service.dataStore.Settings().Settings()
if err != nil {
return nil, fmt.Errorf("failed fetching settings from db: %w", err)
}
endpoint.EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
}
waitForAgentToConnect := 2 * time.Duration(endpoint.EdgeCheckinInterval)
for waitForAgentToConnect >= 0 {
waitForAgentToConnect--
time.Sleep(time.Second)
tunnel = service.GetTunnelDetails(endpoint.ID)
if tunnel.Status == portainer.EdgeAgentActive {
break
}
}
}
tunnel = service.GetTunnelDetails(endpoint.ID)
return tunnel, nil
}
// SetTunnelStatusToActive update the status of the tunnel associated to the specified environment(endpoint).
// It sets the status to ACTIVE.
func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID) {
tunnel := service.GetTunnelDetails(endpointID)
@@ -68,7 +109,7 @@ func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID)
service.tunnelDetailsMap.Set(key, tunnel)
}
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified endpoint.
// 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) {
@@ -86,13 +127,15 @@ func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
key := strconv.Itoa(int(endpointID))
service.tunnelDetailsMap.Set(key, tunnel)
service.ProxyManager.DeleteEndpointProxy(endpointID)
}
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified endpoint.
// 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 endpoint.
// Credentials are encrypted using the Edge ID associated to the environment(endpoint).
func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
tunnel := service.GetTunnelDetails(endpointID)

View File

@@ -47,6 +47,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(),
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").Default(defaultSnapshotInterval).String(),
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),

24
api/cli/confirm.go Normal file
View File

@@ -0,0 +1,24 @@
package cli
import (
"bufio"
"log"
"os"
"strings"
)
// Confirm starts a rollback db cli application
func Confirm(message string) (bool, error) {
log.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
}

View File

@@ -56,17 +56,24 @@ func initFileService(dataStorePath string) portainer.FileService {
return fileService
}
func initDataStore(dataStorePath string, fileService portainer.FileService, shutdownCtx context.Context) portainer.DataStore {
store, err := bolt.NewStore(dataStorePath, fileService)
if err != nil {
log.Fatalf("failed creating data store: %v", err)
}
err = store.Open()
func initDataStore(dataStorePath string, rollback bool, fileService portainer.FileService, shutdownCtx context.Context) portainer.DataStore {
store := bolt.NewStore(dataStorePath, fileService)
err := store.Open()
if err != nil {
log.Fatalf("failed opening store: %v", err)
}
if rollback {
err := store.Rollback(false)
if err != nil {
log.Fatalf("failed rolling back: %s", err)
}
log.Println("Exiting rollback")
os.Exit(0)
return nil
}
err = store.Init()
if err != nil {
log.Fatalf("failed initializing data store: %v", err)
@@ -99,8 +106,8 @@ func initSwarmStackManager(assetsPath string, configPath string, signatureServic
return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService)
}
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, assetsPath string) portainer.KubernetesDeployer {
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, assetsPath)
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, assetsPath string) portainer.KubernetesDeployer {
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager, assetsPath)
}
func initHelmPackageManager(assetsPath string) (libhelm.HelmPackageManager, error) {
@@ -399,7 +406,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
fileService := initFileService(*flags.Data)
dataStore := initDataStore(*flags.Data, fileService, shutdownCtx)
dataStore := initDataStore(*flags.Data, *flags.Rollback, fileService, shutdownCtx)
if err := dataStore.CheckCurrentEdition(); err != nil {
log.Fatal(err)
@@ -460,6 +467,8 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
proxyManager := proxy.NewManager(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager)
reverseTunnelService.ProxyManager = proxyManager
dockerConfigPath := fileService.GetDockerConfigPath()
composeStackManager := initComposeStackManager(*flags.Assets, dockerConfigPath, reverseTunnelService, proxyManager)
@@ -469,7 +478,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatalf("failed initializing swarm stack manager: %s", err)
}
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, *flags.Assets)
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
if err != nil {
@@ -542,7 +551,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
}
scheduler := scheduler.NewScheduler(shutdownCtx)
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager)
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
stacks.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
return &http.Server{

View File

@@ -22,7 +22,7 @@ const (
)
// ECDSAService is a service used to create digital signatures when communicating with
// an agent based environment. It will automatically generates a key pair using ECDSA or
// an agent based environment(endpoint). It will automatically generates a key pair using ECDSA or
// can also reuse an existing ECDSA key pair.
type ECDSAService struct {
privateKey *ecdsa.PrivateKey

View File

@@ -34,8 +34,8 @@ func NewClientFactory(signatureService portainer.DigitalSignatureService, revers
}
// createClient is a generic function to create a Docker client based on
// a specific endpoint configuration. The nodeName parameter can be used
// with an agent enabled endpoint to target a specific node in an agent cluster.
// a specific environment(endpoint) configuration. The nodeName parameter can be used
// with an agent enabled environment(endpoint) to target a specific node in an agent cluster.
func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint, nodeName string) (*client.Client, error) {
if endpoint.Type == portainer.AzureEnvironment {
return nil, errUnsupportedEnvironmentType
@@ -91,7 +91,11 @@ func createEdgeClient(endpoint *portainer.Endpoint, signatureService portainer.D
headers[portainer.PortainerAgentTargetHeader] = nodeName
}
tunnel := reverseTunnelService.GetTunnelDetails(endpoint.ID)
tunnel, err := reverseTunnelService.GetActiveTunnel(endpoint)
if err != nil {
return nil, err
}
endpointURL := fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
return client.NewClientWithOpts(

View File

@@ -12,7 +12,7 @@ import (
"github.com/portainer/portainer/api"
)
// Snapshotter represents a service used to create endpoint snapshots
// Snapshotter represents a service used to create environment(endpoint) snapshots
type Snapshotter struct {
clientFactory *ClientFactory
}
@@ -24,7 +24,7 @@ func NewSnapshotter(clientFactory *ClientFactory) *Snapshotter {
}
}
// CreateSnapshot creates a snapshot of a specific Docker endpoint
// CreateSnapshot creates a snapshot of a specific Docker environment(endpoint)
func (snapshotter *Snapshotter) CreateSnapshot(endpoint *portainer.Endpoint) (*portainer.DockerSnapshot, error) {
cli, err := snapshotter.clientFactory.CreateClient(endpoint, "")
if err != nil {

5
api/exec/common.go Normal file
View File

@@ -0,0 +1,5 @@
package exec
import "regexp"
var stackNameNormalizeRegex = regexp.MustCompile("[^-_a-z0-9]+")

View File

@@ -6,7 +6,6 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strings"
"github.com/pkg/errors"
@@ -47,7 +46,7 @@ func (manager *ComposeStackManager) ComposeSyntaxMaxVersion() string {
func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint) error {
url, proxy, err := manager.fetchEndpointProxy(endpoint)
if err != nil {
return errors.Wrap(err, "failed to featch environment proxy")
return errors.Wrap(err, "failed to fetch environment proxy")
}
if proxy != nil {
@@ -80,9 +79,8 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
}
// NormalizeStackName returns a new stack name with unsupported characters replaced
func (w *ComposeStackManager) NormalizeStackName(name string) string {
r := regexp.MustCompile("[^a-z0-9]+")
return r.ReplaceAllString(strings.ToLower(name), "")
func (manager *ComposeStackManager) NormalizeStackName(name string) string {
return stackNameNormalizeRegex.ReplaceAllString(strings.ToLower(name), "")
}
func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpoint) (string, *factory.ProxyServer, error) {
@@ -90,7 +88,7 @@ func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpo
return "", nil, nil
}
proxy, err := manager.proxyManager.CreateComposeProxyServer(endpoint)
proxy, err := manager.proxyManager.CreateAgentProxyServer(endpoint)
if err != nil {
return "", nil, err
}

View File

@@ -0,0 +1,23 @@
package exectest
import (
portainer "github.com/portainer/portainer/api"
)
type kubernetesMockDeployer struct{}
func NewKubernetesDeployer() portainer.KubernetesDeployer {
return &kubernetesMockDeployer{}
}
func (deployer *kubernetesMockDeployer) Deploy(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
return "", nil
}
func (deployer *kubernetesMockDeployer) Remove(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
return "", nil
}
func (deployer *kubernetesMockDeployer) ConvertCompose(data []byte) ([]byte, error) {
return nil, nil
}

View File

@@ -2,27 +2,22 @@ package exec
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os/exec"
"path"
"runtime"
"strings"
"time"
"github.com/pkg/errors"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/proxy/factory"
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/kubernetes/cli"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
)
// KubernetesDeployer represents a service to deploy resources inside a Kubernetes environment.
// KubernetesDeployer represents a service to deploy resources inside a Kubernetes environment(endpoint).
type KubernetesDeployer struct {
binaryPath string
dataStore portainer.DataStore
@@ -30,10 +25,11 @@ type KubernetesDeployer struct {
signatureService portainer.DigitalSignatureService
kubernetesClientFactory *cli.ClientFactory
kubernetesTokenCacheManager *kubernetes.TokenCacheManager
proxyManager *proxy.Manager
}
// NewKubernetesDeployer initializes a new KubernetesDeployer service.
func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheManager, kubernetesClientFactory *cli.ClientFactory, datastore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, binaryPath string) *KubernetesDeployer {
func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheManager, kubernetesClientFactory *cli.ClientFactory, datastore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, binaryPath string) *KubernetesDeployer {
return &KubernetesDeployer{
binaryPath: binaryPath,
dataStore: datastore,
@@ -41,32 +37,33 @@ func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheMan
signatureService: signatureService,
kubernetesClientFactory: kubernetesClientFactory,
kubernetesTokenCacheManager: kubernetesTokenCacheManager,
proxyManager: proxyManager,
}
}
func (deployer *KubernetesDeployer) getToken(request *http.Request, endpoint *portainer.Endpoint, setLocalAdminToken bool) (string, error) {
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return "", err
}
kubecli, err := deployer.kubernetesClientFactory.GetKubeClient(endpoint)
func (deployer *KubernetesDeployer) getToken(userID portainer.UserID, endpoint *portainer.Endpoint, setLocalAdminToken bool) (string, error) {
kubeCLI, err := deployer.kubernetesClientFactory.GetKubeClient(endpoint)
if err != nil {
return "", err
}
tokenCache := deployer.kubernetesTokenCacheManager.GetOrCreateTokenCache(int(endpoint.ID))
tokenManager, err := kubernetes.NewTokenManager(kubecli, deployer.dataStore, tokenCache, setLocalAdminToken)
tokenManager, err := kubernetes.NewTokenManager(kubeCLI, deployer.dataStore, tokenCache, setLocalAdminToken)
if err != nil {
return "", err
}
if tokenData.Role == portainer.AdministratorRole {
user, err := deployer.dataStore.User().User(userID)
if err != nil {
return "", errors.Wrap(err, "failed to fetch the user")
}
if user.Role == portainer.AdministratorRole {
return tokenManager.GetAdminServiceAccountToken(), nil
}
token, err := tokenManager.GetUserServiceAccountToken(int(tokenData.ID), endpoint.ID)
token, err := tokenManager.GetUserServiceAccountToken(int(user.ID), endpoint.ID)
if err != nil {
return "", err
}
@@ -77,156 +74,62 @@ func (deployer *KubernetesDeployer) getToken(request *http.Request, endpoint *po
return token, nil
}
// Deploy will deploy a Kubernetes manifest inside a specific namespace in a Kubernetes endpoint.
// Otherwise it will use kubectl to deploy the manifest.
func (deployer *KubernetesDeployer) Deploy(request *http.Request, endpoint *portainer.Endpoint, stackConfig string, namespace string) (string, error) {
if endpoint.Type == portainer.KubernetesLocalEnvironment {
token, err := deployer.getToken(request, endpoint, true)
if err != nil {
return "", err
}
// Deploy upserts Kubernetes resources defined in manifest(s)
func (deployer *KubernetesDeployer) Deploy(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
return deployer.command("apply", userID, endpoint, manifestFiles, namespace)
}
command := path.Join(deployer.binaryPath, "kubectl")
if runtime.GOOS == "windows" {
command = path.Join(deployer.binaryPath, "kubectl.exe")
}
// Remove deletes Kubernetes resources defined in manifest(s)
func (deployer *KubernetesDeployer) Remove(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
return deployer.command("delete", userID, endpoint, manifestFiles, namespace)
}
args := make([]string, 0)
args = append(args, "--server", endpoint.URL)
args = append(args, "--insecure-skip-tls-verify")
args = append(args, "--token", token)
func (deployer *KubernetesDeployer) command(operation string, userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
token, err := deployer.getToken(userID, endpoint, endpoint.Type == portainer.KubernetesLocalEnvironment)
if err != nil {
return "", errors.Wrap(err, "failed generating a user token")
}
command := path.Join(deployer.binaryPath, "kubectl")
if runtime.GOOS == "windows" {
command = path.Join(deployer.binaryPath, "kubectl.exe")
}
args := []string{"--token", token}
if namespace != "" {
args = append(args, "--namespace", namespace)
args = append(args, "apply", "-f", "-")
}
var stderr bytes.Buffer
cmd := exec.Command(command, args...)
cmd.Stderr = &stderr
cmd.Stdin = strings.NewReader(stackConfig)
output, err := cmd.Output()
if endpoint.Type == portainer.AgentOnKubernetesEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
url, proxy, err := deployer.getAgentURL(endpoint)
if err != nil {
return "", errors.New(stderr.String())
return "", errors.WithMessage(err, "failed generating endpoint URL")
}
return string(output), nil
defer proxy.Close()
args = append(args, "--server", url)
args = append(args, "--insecure-skip-tls-verify")
}
// agent
endpointURL := endpoint.URL
if endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
tunnel := deployer.reverseTunnelService.GetTunnelDetails(endpoint.ID)
if tunnel.Status == portainer.EdgeAgentIdle {
err := deployer.reverseTunnelService.SetTunnelStatusToRequired(endpoint.ID)
if err != nil {
return "", err
}
settings, err := deployer.dataStore.Settings().Settings()
if err != nil {
return "", err
}
waitForAgentToConnect := time.Duration(settings.EdgeAgentCheckinInterval) * time.Second
time.Sleep(waitForAgentToConnect * 2)
}
endpointURL = fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
if operation == "delete" {
args = append(args, "--ignore-not-found=true")
}
transport := &http.Transport{}
if endpoint.TLSConfig.TLS {
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
if err != nil {
return "", err
}
transport.TLSClientConfig = tlsConfig
args = append(args, operation)
for _, path := range manifestFiles {
args = append(args, "-f", strings.TrimSpace(path))
}
httpCli := &http.Client{
Transport: transport,
}
var stderr bytes.Buffer
cmd := exec.Command(command, args...)
cmd.Stderr = &stderr
if !strings.HasPrefix(endpointURL, "http") {
endpointURL = fmt.Sprintf("https://%s", endpointURL)
}
url, err := url.Parse(fmt.Sprintf("%s/v2/kubernetes/stack", endpointURL))
output, err := cmd.Output()
if err != nil {
return "", err
return "", errors.Wrapf(err, "failed to execute kubectl command: %q", stderr.String())
}
reqPayload, err := json.Marshal(
struct {
StackConfig string
Namespace string
}{
StackConfig: stackConfig,
Namespace: namespace,
})
if err != nil {
return "", err
}
req, err := http.NewRequest(http.MethodPost, url.String(), bytes.NewReader(reqPayload))
if err != nil {
return "", err
}
signature, err := deployer.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
if err != nil {
return "", err
}
token, err := deployer.getToken(request, endpoint, false)
if err != nil {
return "", err
}
req.Header.Set(portainer.PortainerAgentPublicKeyHeader, deployer.signatureService.EncodedPublicKey())
req.Header.Set(portainer.PortainerAgentSignatureHeader, signature)
req.Header.Set(portainer.PortainerAgentKubernetesSATokenHeader, token)
resp, err := httpCli.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var errorResponseData struct {
Message string
Details string
}
err = json.NewDecoder(resp.Body).Decode(&errorResponseData)
if err != nil {
output, parseStringErr := ioutil.ReadAll(resp.Body)
if parseStringErr != nil {
return "", parseStringErr
}
return "", fmt.Errorf("Failed parsing, body: %s, error: %w", output, err)
}
return "", fmt.Errorf("Deployment to agent failed: %s", errorResponseData.Details)
}
var responseData struct{ Output string }
err = json.NewDecoder(resp.Body).Decode(&responseData)
if err != nil {
parsedOutput, parseStringErr := ioutil.ReadAll(resp.Body)
if parseStringErr != nil {
return "", parseStringErr
}
return "", fmt.Errorf("Failed decoding, body: %s, err: %w", parsedOutput, err)
}
return responseData.Output, nil
return string(output), nil
}
// ConvertCompose leverages the kompose binary to deploy a compose compliant manifest.
@@ -251,3 +154,12 @@ func (deployer *KubernetesDeployer) ConvertCompose(data []byte) ([]byte, error)
return output, nil
}
func (deployer *KubernetesDeployer) getAgentURL(endpoint *portainer.Endpoint) (string, *factory.ProxyServer, error) {
proxy, err := deployer.proxyManager.CreateAgentProxyServer(endpoint)
if err != nil {
return "", nil, err
}
return fmt.Sprintf("http://127.0.0.1:%d/kubernetes", proxy.Port), proxy, nil
}

View File

@@ -8,7 +8,6 @@ import (
"os"
"os/exec"
"path"
"regexp"
"runtime"
"strings"
@@ -45,19 +44,26 @@ func NewSwarmStackManager(binaryPath, configPath string, signatureService portai
}
// Login executes the docker login command against a list of registries (including DockerHub).
func (manager *SwarmStackManager) Login(registries []portainer.Registry, endpoint *portainer.Endpoint) {
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
func (manager *SwarmStackManager) Login(registries []portainer.Registry, endpoint *portainer.Endpoint) error {
command, args, err := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
if err != nil {
return err
}
for _, registry := range registries {
if registry.Authentication {
registryArgs := append(args, "login", "--username", registry.Username, "--password", registry.Password, registry.URL)
runCommandAndCaptureStdErr(command, registryArgs, nil, "")
}
}
return nil
}
// Logout executes the docker logout command.
func (manager *SwarmStackManager) Logout(endpoint *portainer.Endpoint) error {
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
command, args, err := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
if err != nil {
return err
}
args = append(args, "logout")
return runCommandAndCaptureStdErr(command, args, nil, "")
}
@@ -65,7 +71,10 @@ func (manager *SwarmStackManager) Logout(endpoint *portainer.Endpoint) error {
// Deploy executes the docker stack deploy command.
func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error {
filePaths := stackutils.GetStackFilePaths(stack)
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
command, args, err := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
if err != nil {
return err
}
if prune {
args = append(args, "stack", "deploy", "--prune", "--with-registry-auth")
@@ -85,7 +94,10 @@ func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, end
// Remove executes the docker stack rm command.
func (manager *SwarmStackManager) Remove(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
command, args, err := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
if err != nil {
return err
}
args = append(args, "stack", "rm", stack.Name)
return runCommandAndCaptureStdErr(command, args, nil, "")
}
@@ -109,7 +121,7 @@ func runCommandAndCaptureStdErr(command string, args []string, env []string, wor
return nil
}
func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, configPath string, endpoint *portainer.Endpoint) (string, []string) {
func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, configPath string, endpoint *portainer.Endpoint) (string, []string, error) {
// Assume Linux as a default
command := path.Join(binaryPath, "docker")
@@ -122,7 +134,10 @@ func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, config
endpointURL := endpoint.URL
if endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
tunnel := manager.reverseTunnelService.GetTunnelDetails(endpoint.ID)
tunnel, err := manager.reverseTunnelService.GetActiveTunnel(endpoint)
if err != nil {
return "", nil, err
}
endpointURL = fmt.Sprintf("tcp://127.0.0.1:%d", tunnel.Port)
}
@@ -142,7 +157,7 @@ func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, config
}
}
return command, args
return command, args, nil
}
func (manager *SwarmStackManager) updateDockerCLIConfiguration(configPath string) error {
@@ -190,8 +205,7 @@ func (manager *SwarmStackManager) retrieveConfigurationFromDisk(path string) (ma
}
func (manager *SwarmStackManager) NormalizeStackName(name string) string {
r := regexp.MustCompile("[^a-z0-9]+")
return r.ReplaceAllString(strings.ToLower(name), "")
return stackNameNormalizeRegex.ReplaceAllString(strings.ToLower(name), "")
}
func configureFilePaths(args []string, filePaths []string) []string {

View File

@@ -288,7 +288,7 @@ func (service *Service) StoreTLSFileFromBytes(folder string, fileType portainer.
return path.Join(service.fileStorePath, tlsFilePath), nil
}
// GetPathForTLSFile returns the absolute path to a specific TLS file for an endpoint.
// GetPathForTLSFile returns the absolute path to a specific TLS file for an environment(endpoint).
func (service *Service) GetPathForTLSFile(folder string, fileType portainer.TLSFileType) (string, error) {
var fileName string
switch fileType {

23
api/filesystem/write.go Normal file
View File

@@ -0,0 +1,23 @@
package filesystem
import (
"os"
"path/filepath"
"github.com/pkg/errors"
)
func WriteToFile(dst string, content []byte) error {
if err := os.MkdirAll(filepath.Dir(dst), 0744); err != nil {
return errors.Wrapf(err, "failed to create filestructure for the path %q", dst)
}
file, err := os.Create(dst)
if err != nil {
return errors.Wrapf(err, "failed to open a file %q", dst)
}
defer file.Close()
_, err = file.Write(content)
return errors.Wrapf(err, "failed to write a file %q", dst)
}

View File

@@ -0,0 +1,48 @@
package filesystem
import (
"io/ioutil"
"path"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_WriteFile_CanStoreContentInANewFile(t *testing.T) {
tmpDir := t.TempDir()
tmpFilePath := path.Join(tmpDir, "dummy")
content := []byte("content")
err := WriteToFile(tmpFilePath, content)
assert.NoError(t, err)
fileContent, _ := ioutil.ReadFile(tmpFilePath)
assert.Equal(t, content, fileContent)
}
func Test_WriteFile_CanOverwriteExistingFile(t *testing.T) {
tmpDir := t.TempDir()
tmpFilePath := path.Join(tmpDir, "dummy")
err := WriteToFile(tmpFilePath, []byte("content"))
assert.NoError(t, err)
content := []byte("new content")
err = WriteToFile(tmpFilePath, content)
assert.NoError(t, err)
fileContent, _ := ioutil.ReadFile(tmpFilePath)
assert.Equal(t, content, fileContent)
}
func Test_WriteFile_CanWriteANestedPath(t *testing.T) {
tmpDir := t.TempDir()
tmpFilePath := path.Join(tmpDir, "dir", "sub-dir", "dummy")
content := []byte("content")
err := WriteToFile(tmpFilePath, content)
assert.NoError(t, err)
fileContent, _ := ioutil.ReadFile(tmpFilePath)
assert.Equal(t, content, fileContent)
}

View File

@@ -3,57 +3,48 @@ module github.com/portainer/portainer/api
go 1.16
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.4.16
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/Microsoft/go-winio v0.4.17
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/boltdb/bolt v1.3.1
github.com/containerd/containerd v1.3.1 // indirect
github.com/containerd/containerd v1.5.7 // indirect
github.com/coreos/go-semver v0.3.0
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/docker/cli v0.0.0-20191126203649-54d085b857e9
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0
github.com/docker/cli v20.10.9+incompatible
github.com/docker/docker v20.10.9+incompatible
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
github.com/go-git/go-git/v5 v5.3.0
github.com/go-ldap/ldap/v3 v3.1.8
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gofrs/uuid v4.0.0+incompatible
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/mux v1.7.3
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/websocket v1.4.1
github.com/gorilla/websocket v1.4.2
github.com/joho/godotenv v1.3.0
github.com/jpillora/chisel v0.0.0-20190724232113-f3a8df20e389
github.com/json-iterator/go v1.1.10
github.com/json-iterator/go v1.1.11
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
github.com/mattn/go-shellwords v1.0.6 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
github.com/pkg/errors v0.9.1
github.com/portainer/docker-compose-wrapper v0.0.0-20210909083948-8be0d98451a1
github.com/portainer/docker-compose-wrapper v0.0.0-20211018221743-10a04c9d4f19
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a
github.com/portainer/libhelm v0.0.0-20210906035629-b5635edd5d97
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
github.com/swaggo/swag v1.7.3
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gotest.tools v2.2.0+incompatible // indirect
k8s.io/api v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v0.17.2
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/client-go v0.22.2
)
replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20200204220554-5f6d6f3f2203
replace golang.org/x/sys => golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456

File diff suppressed because it is too large Load Diff

View File

@@ -102,7 +102,7 @@ func Get(url string, timeout int) ([]byte, error) {
return body, nil
}
// ExecutePingOperation will send a SystemPing operation HTTP request to a Docker environment
// ExecutePingOperation will send a SystemPing operation HTTP request to a Docker environment(endpoint)
// using the specified host and optional TLS configuration.
// It uses a new Http.Client for each operation.
func ExecutePingOperation(host string, tlsConfig *tls.Config) (bool, error) {

View File

@@ -3,7 +3,7 @@ package errors
import "errors"
var (
// ErrEndpointAccessDenied Access denied to endpoint error
// ErrEndpointAccessDenied Access denied to environment(endpoint) error
ErrEndpointAccessDenied = errors.New("Access denied to environment")
// ErrUnauthorized Unauthorized error
ErrUnauthorized = errors.New("Unauthorized")

View File

@@ -39,7 +39,8 @@ func (payload *authenticatePayload) Validate(r *http.Request) error {
// @id AuthenticateUser
// @summary Authenticate
// @description Use this endpoint to authenticate against Portainer using a username and password.
// @description **Access policy**: public
// @description Use this environment(endpoint) to authenticate against Portainer using a username and password.
// @tags auth
// @accept json
// @produce json

View File

@@ -44,6 +44,7 @@ func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuth
// @id ValidateOAuth
// @summary Authenticate with OAuth
// @description **Access policy**: public
// @tags auth
// @accept json
// @produce json

View File

@@ -10,6 +10,7 @@ import (
// @id Logout
// @summary Logout
// @description **Access policy**: authenticated
// @security jwt
// @tags auth
// @success 204 "Success"

View File

@@ -27,8 +27,9 @@ func (p *backupPayload) Validate(r *http.Request) error {
// @description **Access policy**: admin
// @tags backup
// @security jwt
// @accept json
// @produce octet-stream
// @param Password body string false "Password to encrypt the backup with"
// @param body body backupPayload false "An object contains the password to encrypt the backup with"
// @success 200 "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"

View File

@@ -22,10 +22,9 @@ type restorePayload struct {
// @description Triggers a system restore using provided backup file
// @description **Access policy**: public
// @tags backup
// @param FileContent body []byte true "Content of the backup"
// @param FileName body string true "File name"
// @param Password body string false "Password to decrypt the backup with"
// @success 200 "Success"
// @accept json
// @param restorePayload body restorePayload true "Restore request payload"
// @success 200 "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /restore [post]

View File

@@ -3,6 +3,7 @@ package customtemplates
import (
"errors"
"net/http"
"regexp"
"strconv"
"github.com/asaskevich/govalidator"
@@ -129,9 +130,20 @@ func (payload *customTemplateFromFileContentPayload) Validate(r *http.Request) e
if payload.Type != portainer.KubernetesStack && payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack {
return errors.New("Invalid custom template type")
}
if !isValidNote(payload.Note) {
return errors.New("Invalid note. <img> tag is not supported")
}
return nil
}
func isValidNote(note string) bool {
if govalidator.IsNull(note) {
return true
}
match, _ := regexp.MatchString("<img", note)
return !match
}
func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*portainer.CustomTemplate, error) {
var payload customTemplateFromFileContentPayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
@@ -218,6 +230,9 @@ func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request)
if payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack {
return errors.New("Invalid custom template type")
}
if !isValidNote(payload.Note) {
return errors.New("Invalid note. <img> tag is not supported")
}
return nil
}
@@ -279,10 +294,15 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er
if err != nil {
return errors.New("Invalid custom template description")
}
payload.Description = description
logo, _ := request.RetrieveMultiPartFormValue(r, "Logo", true)
payload.Logo = logo
note, _ := request.RetrieveMultiPartFormValue(r, "Note", true)
if !isValidNote(note) {
return errors.New("Invalid note. <img> tag is not supported")
}
payload.Note = note
typeNumeral, _ := request.RetrieveNumericMultiPartFormValue(r, "Type", true)

View File

@@ -16,7 +16,7 @@ import (
// @id CustomTemplateDelete
// @summary Remove a template
// @description Remove a template.
// @description **Access policy**: authorized
// @description **Access policy**: authenticated
// @tags custom_templates
// @security jwt
// @param id path int true "Template identifier"

View File

@@ -18,7 +18,7 @@ type fileResponse struct {
// @id CustomTemplateFile
// @summary Get Template stack file content.
// @description Retrieve the content of the Stack file for the specified custom template
// @description **Access policy**: authorized
// @description **Access policy**: authenticated
// @tags custom_templates
// @security jwt
// @produce json

View File

@@ -19,7 +19,6 @@ import (
// @description **Access policy**: authenticated
// @tags custom_templates
// @security jwt
// @accept json
// @produce json
// @param id path int true "Template identifier"
// @success 200 {object} portainer.CustomTemplate "Success"

View File

@@ -51,6 +51,9 @@ func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Description) {
return errors.New("Invalid custom template description")
}
if !isValidNote(payload.Note) {
return errors.New("Invalid note. <img> tag is not supported")
}
return nil
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/portainer/portainer/api/internal/authorization"
)
// Handler is the HTTP handler used to handle endpoint group operations.
// Handler is the HTTP handler used to handle environment(endpoint) group operations.
type Handler struct {
*mux.Router
DataStore portainer.DataStore
@@ -18,7 +18,7 @@ type Handler struct {
GitService portainer.GitService
}
// NewHandler creates a handler to manage endpoint group operations.
// NewHandler creates a handler to manage environment(endpoint) group operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),

View File

@@ -34,7 +34,7 @@ func (payload *edgeGroupCreatePayload) Validate(r *http.Request) error {
// @id EdgeGroupCreate
// @summary Create an EdgeGroup
// @description
// @description **Access policy**: administrator
// @tags edge_groups
// @security jwt
// @accept json

View File

@@ -13,11 +13,9 @@ import (
// @id EdgeGroupDelete
// @summary Deletes an EdgeGroup
// @description
// @description **Access policy**: administrator
// @tags edge_groups
// @security jwt
// @accept json
// @produce json
// @param id path int true "EdgeGroup Id"
// @success 204
// @failure 503 "Edge compute features are disabled"

View File

@@ -12,10 +12,9 @@ import (
// @id EdgeGroupInspect
// @summary Inspects an EdgeGroup
// @description
// @description **Access policy**: administrator
// @tags edge_groups
// @security jwt
// @accept json
// @produce json
// @param id path int true "EdgeGroup Id"
// @success 200 {object} portainer.EdgeGroup

View File

@@ -17,10 +17,9 @@ type decoratedEdgeGroup struct {
// @id EdgeGroupList
// @summary list EdgeGroups
// @description
// @description **Access policy**: administrator
// @tags edge_groups
// @security jwt
// @accept json
// @produce json
// @success 200 {array} portainer.EdgeGroup{HasEdgeStack=bool} "EdgeGroups"
// @failure 500

View File

@@ -36,7 +36,7 @@ func (payload *edgeGroupUpdatePayload) Validate(r *http.Request) error {
// @id EgeGroupUpdate
// @summary Updates an EdgeGroup
// @description
// @description **Access policy**: administrator
// @tags edge_groups
// @security jwt
// @accept json

View File

@@ -9,13 +9,13 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// Handler is the HTTP handler used to handle endpoint group operations.
// Handler is the HTTP handler used to handle environment(endpoint) group operations.
type Handler struct {
*mux.Router
DataStore portainer.DataStore
}
// NewHandler creates a handler to manage endpoint group operations.
// NewHandler creates a handler to manage environment(endpoint) group operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),

View File

@@ -16,10 +16,9 @@ import (
// @id EdgeJobCreate
// @summary Create an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param method query string true "Creation Method" Enums(file, string)
// @param body_string body edgeJobCreateFromFileContentPayload true "EdgeGroup data when method is string"

View File

@@ -13,11 +13,9 @@ import (
// @id EdgeJobDelete
// @summary Delete an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @success 204
// @failure 500

View File

@@ -16,10 +16,9 @@ type edgeJobFileResponse struct {
// @id EdgeJobFile
// @summary Fetch a file of an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @success 200 {object} edgeJobFileResponse

View File

@@ -17,10 +17,9 @@ type edgeJobInspectResponse struct {
// @id EdgeJobInspect
// @summary Inspect an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @success 200 {object} portainer.EdgeJob

View File

@@ -9,10 +9,9 @@ import (
// @id EdgeJobList
// @summary Fetch EdgeJobs list
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @success 200 {array} portainer.EdgeJob
// @failure 500

View File

@@ -13,10 +13,9 @@ import (
// @id EdgeJobTasksClear
// @summary Clear the log for a specifc task on an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @param taskID path string true "Task Id"

View File

@@ -12,10 +12,9 @@ import (
// @id EdgeJobTasksCollect
// @summary Collect the log for a specifc task on an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @param taskID path string true "Task Id"

View File

@@ -15,10 +15,9 @@ type fileResponse struct {
// @id EdgeJobTaskLogsInspect
// @summary Fetch the log for a specifc task on an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @param taskID path string true "Task Id"

View File

@@ -19,10 +19,9 @@ type taskContainer struct {
// @id EdgeJobTasksList
// @summary Fetch the list of tasks on an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeJob Id"
// @success 200 {array} taskContainer

View File

@@ -30,7 +30,7 @@ func (payload *edgeJobUpdatePayload) Validate(r *http.Request) error {
// @id EdgeJobUpdate
// @summary Update an EdgeJob
// @description
// @description **Access policy**: administrator
// @tags edge_jobs
// @security jwt
// @accept json

View File

@@ -19,10 +19,9 @@ import (
// @id EdgeStackCreate
// @summary Create an EdgeStack
// @description
// @description **Access policy**: administrator
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param method query string true "Creation Method" Enums(file,string,repository)
// @param body_string body swarmStackFromFileContentPayload true "Required when using method=string"
@@ -67,8 +66,8 @@ type swarmStackFromFileContentPayload struct {
EdgeGroups []portainer.EdgeGroupID `example:"1"`
// Deployment type to deploy this stack
// Valid values are: 0 - 'compose', 1 - 'kubernetes'
// for compose stacks will use kompose to convert to kubernetes manifest for kubernetes endpoints
// kubernetes deploytype is enabled only for kubernetes endpoints
// for compose stacks will use kompose to convert to kubernetes manifest for kubernetes environments(endpoints)
// kubernetes deploytype is enabled only for kubernetes environments(endpoints)
DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1"`
}
@@ -185,8 +184,8 @@ type swarmStackFromGitRepositoryPayload struct {
EdgeGroups []portainer.EdgeGroupID `example:"1"`
// Deployment type to deploy this stack
// Valid values are: 0 - 'compose', 1 - 'kubernetes'
// for compose stacks will use kompose to convert to kubernetes manifest for kubernetes endpoints
// kubernetes deploytype is enabled only for kubernetes endpoints
// for compose stacks will use kompose to convert to kubernetes manifest for kubernetes environments(endpoints)
// kubernetes deploytype is enabled only for kubernetes environments(endpoints)
DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1"`
}
@@ -402,7 +401,7 @@ func (handler *Handler) validateUniqueName(name string) error {
return nil
}
// updateEndpointRelations adds a relation between the Edge Stack to the related endpoints
// updateEndpointRelations adds a relation between the Edge Stack to the related environments(endpoints)
func updateEndpointRelations(endpointRelationService portainer.EndpointRelationService, edgeStackID portainer.EdgeStackID, relatedEndpointIds []portainer.EndpointID) error {
for _, endpointID := range relatedEndpointIds {
relation, err := endpointRelationService.EndpointRelation(endpointID)

View File

@@ -13,11 +13,9 @@ import (
// @id EdgeStackDelete
// @summary Delete an EdgeStack
// @description
// @description **Access policy**: administrator
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeStack Id"
// @success 204
// @failure 500

View File

@@ -17,10 +17,9 @@ type stackFileResponse struct {
// @id EdgeStackFile
// @summary Fetches the stack file for an EdgeStack
// @description
// @description **Access policy**: administrator
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeStack Id"
// @success 200 {object} stackFileResponse

View File

@@ -12,10 +12,9 @@ import (
// @id EdgeStackInspect
// @summary Inspect an EdgeStack
// @description
// @description **Access policy**: administrator
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @param id path string true "EdgeStack Id"
// @success 200 {object} portainer.EdgeStack

View File

@@ -9,10 +9,9 @@ import (
// @id EdgeStackList
// @summary Fetches the list of EdgeStacks
// @description
// @description **Access policy**: administrator
// @tags edge_stacks
// @security jwt
// @accept json
// @produce json
// @success 200 {array} portainer.EdgeStack
// @failure 500

View File

@@ -33,7 +33,7 @@ func (payload *updateStatusPayload) Validate(r *http.Request) error {
// @id EdgeStackStatusUpdate
// @summary Update an EdgeStack status
// @description Authorized only if the request is done by an Edge Endpoint
// @description Authorized only if the request is done by an Edge Environment(Endpoint)
// @tags edge_stacks
// @accept json
// @produce json

View File

@@ -33,7 +33,7 @@ func (payload *updateEdgeStackPayload) Validate(r *http.Request) error {
// @id EdgeStackUpdate
// @summary Update an EdgeStack
// @description
// @description **Access policy**: administrator
// @tags edge_stacks
// @security jwt
// @accept json
@@ -164,11 +164,11 @@ func (handler *Handler) edgeStackUpdate(w http.ResponseWriter, r *http.Request)
hasDockerEndpoint, err := hasDockerEndpoint(handler.DataStore.Endpoint(), relatedEndpointIds)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for existence of docker endpoint", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for existence of docker environment", err}
}
if hasDockerEndpoint {
return &httperror.HandlerError{http.StatusBadRequest, "Edge stack with docker endpoint cannot be deployed with kubernetes config", err}
return &httperror.HandlerError{http.StatusBadRequest, "Edge stack with docker environment cannot be deployed with kubernetes config", err}
}
_, err = handler.FileService.StoreEdgeStackFileFromBytes(stackFolder, stack.ManifestPath, []byte(payload.StackFileContent))

View File

@@ -19,7 +19,7 @@ func hasEndpointPredicate(endpointService portainer.EndpointService, endpointIDs
for _, endpointID := range endpointIDs {
endpoint, err := endpointService.Endpoint(endpointID)
if err != nil {
return false, fmt.Errorf("failed to retrieve endpoint from database: %w", err)
return false, fmt.Errorf("failed to retrieve environment from database: %w", err)
}
if predicate(endpoint) {

View File

@@ -13,7 +13,7 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// Handler is the HTTP handler used to handle endpoint group operations.
// Handler is the HTTP handler used to handle environment(endpoint) group operations.
type Handler struct {
*mux.Router
requestBouncer *security.RequestBouncer
@@ -23,7 +23,7 @@ type Handler struct {
KubernetesDeployer portainer.KubernetesDeployer
}
// NewHandler creates a handler to manage endpoint group operations.
// NewHandler creates a handler to manage environment(endpoint) group operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
@@ -49,7 +49,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
func (handler *Handler) convertAndStoreKubeManifestIfNeeded(edgeStack *portainer.EdgeStack, relatedEndpointIds []portainer.EndpointID) error {
hasKubeEndpoint, err := hasKubeEndpoint(handler.DataStore.Endpoint(), relatedEndpointIds)
if err != nil {
return fmt.Errorf("unable to check if edge stack has kube endpoints: %w", err)
return fmt.Errorf("unable to check if edge stack has kube environments: %w", err)
}
if !hasKubeEndpoint {

View File

@@ -17,7 +17,7 @@ type templateFileFormat struct {
// @id EdgeTemplateList
// @summary Fetches the list of Edge Templates
// @description
// @description **Access policy**: administrator
// @tags edge_templates
// @security jwt
// @accept json

View File

@@ -10,14 +10,14 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// Handler is the HTTP handler used to handle edge endpoint operations.
// Handler is the HTTP handler used to handle edge environment(endpoint) operations.
type Handler struct {
*mux.Router
requestBouncer *security.RequestBouncer
DataStore portainer.DataStore
}
// NewHandler creates a handler to manage endpoint operations.
// NewHandler creates a handler to manage environment(endpoint) operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),

View File

@@ -21,11 +21,11 @@ func (payload *logsPayload) Validate(r *http.Request) error {
// endpointEdgeJobsLogs
// @summary Inspect an EdgeJob Log
// @description
// @description **Access policy**: public
// @tags edge, endpoints
// @accept json
// @produce json
// @param id path string true "Endpoint Id"
// @param id path string true "environment(endpoint) Id"
// @param jobID path string true "Job Id"
// @success 200
// @failure 500

View File

@@ -18,12 +18,12 @@ type configResponse struct {
Name string
}
// @summary Inspect an Edge Stack for an Endpoint
// @description
// @summary Inspect an Edge Stack for an Environment(Endpoint)
// @description **Access policy**: public
// @tags edge, endpoints, edge_stacks
// @accept json
// @produce json
// @param id path string true "Endpoint Id"
// @param id path string true "environment(endpoint) Id"
// @param stackId path string true "EdgeStack Id"
// @success 200 {object} configResponse
// @failure 500

View File

@@ -10,7 +10,7 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// Handler is the HTTP handler used to handle edge endpoint operations.
// Handler is the HTTP handler used to handle edge environment(endpoint) operations.
type Handler struct {
*mux.Router
requestBouncer *security.RequestBouncer
@@ -19,7 +19,7 @@ type Handler struct {
ReverseTunnelService portainer.ReverseTunnelService
}
// NewHandler creates a handler to manage endpoint operations.
// NewHandler creates a handler to manage environment(endpoint) operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),

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