Compare commits
227 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 515ef880c0 | |||
| b89f1d314f | |||
| be3cb0690c | |||
| 835a7e41e6 | |||
| 5aae1cd991 | |||
| 34532deccb | |||
| 80c8e483c9 | |||
| 9421e9d452 | |||
| 55cda8c78e | |||
| 4190fc1b4e | |||
| ac5491e864 | |||
| 8cbd23c059 | |||
| 3800a958da | |||
| 09348b8a25 | |||
| 1ef9c249b7 | |||
| 33ac61c600 | |||
| bdb84617fe | |||
| 2d5c834590 | |||
| 280ca22aeb | |||
| 753150e03c | |||
| 517abc662a | |||
| 04e9ee3b3e | |||
| 273ea5df23 | |||
| 6cc95e11ae | |||
| 9133cbf544 | |||
| 111f641979 | |||
| da370316df | |||
| f69825d859 | |||
| 9fd5669a23 | |||
| efc2bf9292 | |||
| 4d74a00492 | |||
| 68011fb293 | |||
| 25f84c0b3e | |||
| 13766cc465 | |||
| 5e6e3048d5 | |||
| 18e755e30e | |||
| a63bd2cea4 | |||
| ef8e611e0a | |||
| cc60836bb8 | |||
| e2830019d7 | |||
| 20de243299 | |||
| e8af981746 | |||
| fa4711946d | |||
| 566e37535f | |||
| 3529a36f92 | |||
| 9c775396fd | |||
| 65871207f0 | |||
| dc3e20acac | |||
| 91a477d9fb | |||
| 9339d10233 | |||
| e7af3296fc | |||
| 5182220d0a | |||
| 9d7173fb5f | |||
| 69f9a509c8 | |||
| 93ce33fac8 | |||
| 55706cbf35 | |||
| e0934bb7fa | |||
| c4235c84a7 | |||
| 4fd4aa8138 | |||
| 8aec5adb66 | |||
| d490061c1f | |||
| b23b0f7c8d | |||
| c94cfb1308 | |||
| 4be2c061f5 | |||
| 0356288fd9 | |||
| 3b95c333fc | |||
| 11404aaecb | |||
| ccb6dd7f1a | |||
| 6e0dd34cc8 | |||
| 61ef133bb8 | |||
| f5d896bce1 | |||
| 6c98271e43 | |||
| 4700e38e5d | |||
| 2f1b5ec979 | |||
| 39088b16b3 | |||
| 680cb3b36a | |||
| 1fce2b83d7 | |||
| 92c8692bbe | |||
| 65060725df | |||
| b920c542dd | |||
| be99f646f8 | |||
| 6d2775a42e | |||
| 4bcefb4866 | |||
| f16d2e5d28 | |||
| 9960137a7c | |||
| 62c625c446 | |||
| acaa564557 | |||
| d727cbc373 | |||
| 31403bfc7e | |||
| cf91a8352d | |||
| 549579d935 | |||
| 0be1888f11 | |||
| 2856d0ed64 | |||
| d70812be0d | |||
| efc88c0073 | |||
| 00d8391a02 | |||
| 4403503d82 | |||
| 8ae64523b3 | |||
| ccfd5e4500 | |||
| a755e6be15 | |||
| 17561c1c0c | |||
| d73b162d16 | |||
| d5c4671320 | |||
| f9065367b9 | |||
| 218b8bf300 | |||
| aa9e73002f | |||
| b86b721b0f | |||
| 95292d20f4 | |||
| fb7c23a241 | |||
| a3146fff36 | |||
| 483aa80e40 | |||
| fb4ffaec35 | |||
| 1a801d86f0 | |||
| fe41d23244 | |||
| 0e163adf8d | |||
| eb4a06d422 | |||
| 1fee55ddd2 | |||
| 2cbc5027aa | |||
| 305a7a354a | |||
| 948e2bb715 | |||
| 02b6829819 | |||
| 3d3d65d44e | |||
| 6714cba2f8 | |||
| 582b1a16fc | |||
| 2be897c4a1 | |||
| 8616f9b742 | |||
| 5d7d68bc44 | |||
| cea9969463 | |||
| 5fb5ea7ae0 | |||
| 5f89255d9c | |||
| e5f6363244 | |||
| 5d9423b93c | |||
| 2443a0f568 | |||
| 08643ed872 | |||
| 6a3eda4bce | |||
| 889c36f64a | |||
| c8fb3adda3 | |||
| f15be1d92a | |||
| d9ae249ffe | |||
| 04de06c07f | |||
| 59d53940fe | |||
| db16888379 | |||
| 8880876bcd | |||
| bfe5a49263 | |||
| 6e11c10bab | |||
| cb9ab3b375 | |||
| b13dac0f6d | |||
| 0144a98b3b | |||
| 64a08c59e9 | |||
| 1090c82beb | |||
| 6094dc115b | |||
| 30513695b5 | |||
| dd2be9fb1e | |||
| e265b8b67c | |||
| cc1ce9412a | |||
| 8eb8df2b30 | |||
| c0bd2dfdaf | |||
| bf65a38d5a | |||
| 0ea21f2317 | |||
| b5f839a920 | |||
| 29025e7dd4 | |||
| 692981b615 | |||
| d6545b6af5 | |||
| 6bbf62fe64 | |||
| 6b3ddf11d4 | |||
| 77c9124e8a | |||
| 2c3dcdd14e | |||
| ec913b45d6 | |||
| 51c672af21 | |||
| ff178641be | |||
| a43454076b | |||
| a7eaa0f3fa | |||
| 8ad11fc88f | |||
| 43a95874f4 | |||
| b4f4c3212a | |||
| d44f57ed6f | |||
| eba08cdca0 | |||
| de3a3f88a0 | |||
| f6b2c879bc | |||
| f5fbcd4d9d | |||
| f8b68a809f | |||
| 6258c02353 | |||
| 0fd20277c1 | |||
| 988064a542 | |||
| 380b23a9f5 | |||
| 158b43194c | |||
| 1bbe98379a | |||
| 8f9b265f5a | |||
| 1cdd3fdfe2 | |||
| 4e95139909 | |||
| 704d75596d | |||
| a8938779bf | |||
| bb6f4e026a | |||
| b64166ff25 | |||
| bac1c28fa9 | |||
| a17da6d2cd | |||
| 24c2baf6cc | |||
| 22b4d029fd | |||
| b126472ec7 | |||
| a46fa3b2c4 | |||
| a374157d6f | |||
| 861ed662e2 | |||
| 99b89a8ec5 | |||
| 95750c2339 | |||
| 165d6165dc | |||
| fe6ed55cab | |||
| edea9e3481 | |||
| c08b5af85a | |||
| ed861044a7 | |||
| a83321ebe6 | |||
| 513cd9c9b3 | |||
| dc94bf141e | |||
| 24471a9ae1 | |||
| aca6d33548 | |||
| ca77b85c65 | |||
| 1fd4291630 | |||
| 08dd7f6d2a | |||
| ce4b0e759c | |||
| 538e7a823b | |||
| 956e8d3c59 | |||
| 1c5458f0d4 | |||
| f6085ffad7 | |||
| 490bda2eaf | |||
| d601d8eb7b | |||
| b0564b9238 | |||
| 8922585a70 | |||
| d7cf2284dc |
@@ -1,52 +0,0 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = ".tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./dist/portainer"
|
||||
cmd = "SKIP_GO_GET=true make build-server"
|
||||
delay = 1000
|
||||
exclude_dir = []
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = "./dist/portainer --log-level=DEBUG"
|
||||
include_dir = ["api"]
|
||||
include_ext = ["go"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
silent = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
@@ -87,7 +87,6 @@ overrides:
|
||||
version: 'detect'
|
||||
|
||||
rules:
|
||||
no-console: error
|
||||
import/order:
|
||||
[
|
||||
'error',
|
||||
@@ -141,11 +140,9 @@ overrides:
|
||||
'react/jsx-no-constructed-context-values': off
|
||||
'@typescript-eslint/no-restricted-imports': off
|
||||
no-restricted-imports: off
|
||||
'react/jsx-props-no-spreading': off
|
||||
- files:
|
||||
- app/**/*.stories.*
|
||||
rules:
|
||||
'no-alert': off
|
||||
'@typescript-eslint/no-restricted-imports': off
|
||||
no-restricted-imports: off
|
||||
'react/jsx-props-no-spreading': off
|
||||
|
||||
@@ -2,17 +2,16 @@ name: Bug Report
|
||||
description: Create a report to help us improve.
|
||||
labels: kind/bug,bug/need-confirmation
|
||||
body:
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Welcome!
|
||||
|
||||
|
||||
The issue tracker is for reporting bugs. If you have an [idea for a new feature](https://github.com/orgs/portainer/discussions/categories/ideas) or a [general question about Portainer](https://github.com/orgs/portainer/discussions/categories/help) please post in our [GitHub Discussions](https://github.com/orgs/portainer/discussions).
|
||||
|
||||
|
||||
You can also ask for help in our [community Slack channel](https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA).
|
||||
|
||||
Please note that we only provide support for current versions of Portainer. You can find a list of supported versions in our [lifecycle policy](https://docs.portainer.io/start/lifecycle).
|
||||
|
||||
|
||||
**DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS**.
|
||||
|
||||
- type: checkboxes
|
||||
@@ -44,7 +43,7 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -70,7 +69,7 @@ body:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -91,44 +90,23 @@ body:
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Portainer version
|
||||
description: We only provide support for current versions of Portainer as per the lifecycle policy linked above. If you are on an older version of Portainer we recommend [updating first](https://docs.portainer.io/start/upgrade) in case your bug has already been fixed.
|
||||
description: We only provide support for the most recent version of Portainer and the previous 3 versions. If you are on an older version of Portainer we recommend [upgrading first](https://docs.portainer.io/start/upgrade) in case your bug has already been fixed.
|
||||
multiple: false
|
||||
options:
|
||||
- '2.31.3'
|
||||
- '2.31.2'
|
||||
- '2.31.1'
|
||||
- '2.31.0'
|
||||
- '2.30.1'
|
||||
- '2.30.0'
|
||||
- '2.29.2'
|
||||
- '2.29.1'
|
||||
- '2.29.0'
|
||||
- '2.28.1'
|
||||
- '2.28.0'
|
||||
- '2.27.9'
|
||||
- '2.27.8'
|
||||
- '2.27.7'
|
||||
- '2.27.6'
|
||||
- '2.27.5'
|
||||
- '2.27.4'
|
||||
- '2.27.3'
|
||||
- '2.27.2'
|
||||
- '2.27.1'
|
||||
- '2.27.0'
|
||||
- '2.26.1'
|
||||
- '2.26.0'
|
||||
- '2.25.1'
|
||||
- '2.25.0'
|
||||
- '2.24.1'
|
||||
- '2.24.0'
|
||||
- '2.23.0'
|
||||
- '2.22.0'
|
||||
- '2.21.5'
|
||||
- '2.21.4'
|
||||
- '2.21.3'
|
||||
- '2.21.2'
|
||||
- '2.21.1'
|
||||
- '2.21.0'
|
||||
- '2.19.4'
|
||||
- '2.19.3'
|
||||
- '2.19.2'
|
||||
- '2.19.1'
|
||||
- '2.19.0'
|
||||
- '2.18.4'
|
||||
- '2.18.3'
|
||||
- '2.18.2'
|
||||
- '2.18.1'
|
||||
- '2.17.1'
|
||||
- '2.17.0'
|
||||
- '2.16.2'
|
||||
- '2.16.1'
|
||||
- '2.16.0'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -166,7 +144,7 @@ body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Browser
|
||||
description: |
|
||||
description: |
|
||||
Enter your browser and version. Example: Google Chrome 114.0
|
||||
validations:
|
||||
required: false
|
||||
|
||||
+3
-4
@@ -9,10 +9,7 @@ linters:
|
||||
- gosimple
|
||||
- govet
|
||||
- errorlint
|
||||
- copyloopvar
|
||||
- intrange
|
||||
- perfsprint
|
||||
- ineffassign
|
||||
- exportloopref
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
@@ -21,6 +18,8 @@ linters-settings:
|
||||
deny:
|
||||
- pkg: 'encoding/json'
|
||||
desc: 'use github.com/segmentio/encoding/json'
|
||||
- pkg: 'github.com/sirupsen/logrus'
|
||||
desc: 'logging is allowed only by github.com/rs/zerolog'
|
||||
- pkg: 'golang.org/x/exp'
|
||||
desc: 'exp is not allowed'
|
||||
- pkg: 'github.com/portainer/libcrypto'
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
|
||||
import { initialize as initMSW, mswLoader } from 'msw-storybook-addon';
|
||||
import { handlers } from '../app/setup-tests/server-handlers';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
|
||||
initMSW(
|
||||
{
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceRoot}/api/cmd/portainer",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {},
|
||||
"showLog": true,
|
||||
"args": ["--data", "${env:HOME}/portainer-data", "--assets", "${workspaceRoot}/dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
{
|
||||
// Place your portainer workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"React Named Export Component": {
|
||||
"prefix": "rnec",
|
||||
"body": [
|
||||
"export function $TM_FILENAME_BASE() {",
|
||||
" return <div>$TM_FILENAME_BASE</div>;",
|
||||
"}"
|
||||
],
|
||||
"description": "React Named Export Component"
|
||||
},
|
||||
"Component": {
|
||||
"scope": "javascript",
|
||||
"prefix": "mycomponent",
|
||||
"description": "Dummy Angularjs Component",
|
||||
"body": [
|
||||
"import angular from 'angular';",
|
||||
"import controller from './${TM_FILENAME_BASE}Controller'",
|
||||
"",
|
||||
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').component('$TM_FILENAME_BASE', {",
|
||||
" templateUrl: './$TM_FILENAME_BASE.html',",
|
||||
" controller,",
|
||||
"});",
|
||||
""
|
||||
]
|
||||
},
|
||||
"Controller": {
|
||||
"scope": "javascript",
|
||||
"prefix": "mycontroller",
|
||||
"body": [
|
||||
"class ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/} {",
|
||||
"\t/* @ngInject */",
|
||||
"\tconstructor($0) {",
|
||||
"\t}",
|
||||
"}",
|
||||
"",
|
||||
"export default ${TM_FILENAME_BASE/(.*)/${1:/capitalize}/};"
|
||||
],
|
||||
"description": "Dummy ES6+ controller"
|
||||
},
|
||||
"Service": {
|
||||
"scope": "javascript",
|
||||
"prefix": "myservice",
|
||||
"description": "Dummy ES6+ service",
|
||||
"body": [
|
||||
"import angular from 'angular';",
|
||||
"import PortainerError from 'Portainer/error';",
|
||||
"",
|
||||
"class $1 {",
|
||||
" /* @ngInject */",
|
||||
" constructor(\\$async, $0) {",
|
||||
" this.\\$async = \\$async;",
|
||||
"",
|
||||
" this.getAsync = this.getAsync.bind(this);",
|
||||
" this.getAllAsync = this.getAllAsync.bind(this);",
|
||||
" this.createAsync = this.createAsync.bind(this);",
|
||||
" this.updateAsync = this.updateAsync.bind(this);",
|
||||
" this.deleteAsync = this.deleteAsync.bind(this);",
|
||||
" }",
|
||||
"",
|
||||
" /**",
|
||||
" * GET",
|
||||
" */",
|
||||
" async getAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" async getAllAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" get() {",
|
||||
" if () {",
|
||||
" return this.\\$async(this.getAsync);",
|
||||
" }",
|
||||
" return this.\\$async(this.getAllAsync);",
|
||||
" }",
|
||||
"",
|
||||
" /**",
|
||||
" * CREATE",
|
||||
" */",
|
||||
" async createAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" create() {",
|
||||
" return this.\\$async(this.createAsync);",
|
||||
" }",
|
||||
"",
|
||||
" /**",
|
||||
" * UPDATE",
|
||||
" */",
|
||||
" async updateAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" update() {",
|
||||
" return this.\\$async(this.updateAsync);",
|
||||
" }",
|
||||
"",
|
||||
" /**",
|
||||
" * DELETE",
|
||||
" */",
|
||||
" async deleteAsync() {",
|
||||
" try {",
|
||||
"",
|
||||
" } catch (err) {",
|
||||
" throw new PortainerError('', err);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" delete() {",
|
||||
" return this.\\$async(this.deleteAsync);",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"export default $1;",
|
||||
"angular.module('portainer.${TM_DIRECTORY/.*\\/app\\/([^\\/]*)(\\/.*)?$/$1/}').service('$1', $1);"
|
||||
]
|
||||
},
|
||||
"swagger-api-doc": {
|
||||
"prefix": "swapi",
|
||||
"scope": "go",
|
||||
"description": "Snippet for a api doc",
|
||||
"body": [
|
||||
"// @id ",
|
||||
"// @summary ",
|
||||
"// @description ",
|
||||
"// @description **Access policy**: ",
|
||||
"// @tags ",
|
||||
"// @security ApiKeyAuth",
|
||||
"// @security jwt",
|
||||
"// @accept json",
|
||||
"// @produce json",
|
||||
"// @param id path int true \"identifier\"",
|
||||
"// @param body body Object true \"details\"",
|
||||
"// @success 200 {object} portainer. \"Success\"",
|
||||
"// @success 204 \"Success\"",
|
||||
"// @failure 400 \"Invalid request\"",
|
||||
"// @failure 403 \"Permission denied\"",
|
||||
"// @failure 404 \" not found\"",
|
||||
"// @failure 500 \"Server error\"",
|
||||
"// @router /{id} [get]"
|
||||
]
|
||||
},
|
||||
"analytics": {
|
||||
"prefix": "nlt",
|
||||
"body": ["analytics-on", "analytics-category=\"$1\"", "analytics-event=\"$2\""],
|
||||
"description": "analytics"
|
||||
},
|
||||
"analytics-if": {
|
||||
"prefix": "nltf",
|
||||
"body": ["analytics-if=\"$1\""],
|
||||
"description": "analytics"
|
||||
},
|
||||
"analytics-metadata": {
|
||||
"prefix": "nltm",
|
||||
"body": "analytics-properties=\"{ metadata: { $1 } }\""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintFlags": ["--fast", "-E", "exportloopref"],
|
||||
"gopls": {
|
||||
"build.expandWorkspaceToModule": false
|
||||
},
|
||||
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"]
|
||||
}
|
||||
@@ -9,7 +9,7 @@ ENV=development
|
||||
WEBPACK_CONFIG=webpack/webpack.$(ENV).js
|
||||
TAG=local
|
||||
|
||||
SWAG=go run github.com/swaggo/swag/cmd/swag@v1.16.2
|
||||
SWAG=go run github.com/swaggo/swag/cmd/swag@v1.16.2
|
||||
GOTESTSUM=go run gotest.tools/gotestsum@latest
|
||||
|
||||
# Don't change anything below this line unless you know what you're doing
|
||||
@@ -17,13 +17,11 @@ GOTESTSUM=go run gotest.tools/gotestsum@latest
|
||||
|
||||
|
||||
##@ Building
|
||||
.PHONY: all init-dist build-storybook build build-client build-server build-image devops
|
||||
.PHONY: init-dist build-storybook build build-client build-server build-image devops
|
||||
init-dist:
|
||||
@mkdir -p dist
|
||||
|
||||
all: tidy deps build-server build-client ## Build the client, server and download external dependancies (doesn't build an image)
|
||||
|
||||
build-all: all ## Alias for the 'all' target (used by CI)
|
||||
build-all: deps build-server build-client ## Build the client, server and download external dependancies (doesn't build an image)
|
||||
|
||||
build-client: init-dist ## Build the client
|
||||
export NODE_ENV=$(ENV) && yarn build --config $(WEBPACK_CONFIG)
|
||||
@@ -32,7 +30,7 @@ build-server: init-dist ## Build the server binary
|
||||
./build/build_binary.sh "$(PLATFORM)" "$(ARCH)"
|
||||
|
||||
build-image: build-all ## Build the Portainer image locally
|
||||
docker buildx build --load -t portainerci/portainer-ce:$(TAG) -f build/linux/Dockerfile .
|
||||
docker buildx build --load -t portainerci/portainer:$(TAG) -f build/linux/Dockerfile .
|
||||
|
||||
build-storybook: ## Build and serve the storybook files
|
||||
yarn storybook:build
|
||||
@@ -52,7 +50,7 @@ client-deps: ## Install client dependencies
|
||||
yarn
|
||||
|
||||
tidy: ## Tidy up the go.mod file
|
||||
@go mod tidy
|
||||
cd api && go mod tidy
|
||||
|
||||
|
||||
##@ Cleanup
|
||||
@@ -66,26 +64,27 @@ clean: ## Remove all build and download artifacts
|
||||
.PHONY: test test-client test-server
|
||||
test: test-server test-client ## Run all tests
|
||||
|
||||
test-deps: init-dist
|
||||
./build/download_docker_compose_binary.sh $(PLATFORM) $(ARCH) $(shell jq -r '.dockerCompose' < "./binary-version.json")
|
||||
|
||||
test-client: ## Run client tests
|
||||
yarn test $(ARGS) --coverage
|
||||
yarn test $(ARGS)
|
||||
|
||||
test-server: ## Run server tests
|
||||
$(GOTESTSUM) --format pkgname-and-test-fails --format-hide-empty-pkg --hide-summary skipped -- -cover -covermode=atomic -coverprofile=coverage.out ./...
|
||||
$(GOTESTSUM) --format pkgname-and-test-fails --format-hide-empty-pkg --hide-summary skipped -- -cover ./...
|
||||
|
||||
##@ Dev
|
||||
.PHONY: dev dev-client dev-server
|
||||
dev: ## Run both the client and server in development mode
|
||||
dev: ## Run both the client and server in development mode
|
||||
make dev-server
|
||||
make dev-client
|
||||
|
||||
dev-client: ## Run the client in development mode
|
||||
dev-client: ## Run the client in development mode
|
||||
yarn dev
|
||||
|
||||
dev-server: build-server ## Run the server in development mode
|
||||
@./dev/run_container.sh
|
||||
|
||||
dev-server-podman: build-server ## Run the server in development mode
|
||||
@./dev/run_container_podman.sh
|
||||
|
||||
##@ Format
|
||||
.PHONY: format format-client format-server
|
||||
@@ -118,7 +117,7 @@ dev-extension: build-server build-client ## Run the extension in development mod
|
||||
##@ Docs
|
||||
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
|
||||
docs-build: init-dist ## Build docs
|
||||
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 -p pascalcase --markdownFiles ./
|
||||
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 -p pascalcase --markdownFiles ./
|
||||
|
||||
docs-validate: docs-build ## Validate docs
|
||||
yarn swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/url"
|
||||
"github.com/portainer/portainer/api/internal/url"
|
||||
)
|
||||
|
||||
// GetAgentVersionAndPlatform returns the agent version and platform
|
||||
|
||||
@@ -3,6 +3,7 @@ package apikey
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/portainer/portainer/api/internal/securecookie"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -33,19 +34,17 @@ func Test_generateRandomKey(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GenerateRandomKey(tt.wantLenth)
|
||||
got := securecookie.GenerateRandomKey(tt.wantLenth)
|
||||
is.Equal(tt.wantLenth, len(got))
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Generated keys are unique", func(t *testing.T) {
|
||||
keys := make(map[string]bool)
|
||||
|
||||
for range 100 {
|
||||
key := GenerateRandomKey(8)
|
||||
for i := 0; i < 100; i++ {
|
||||
key := securecookie.GenerateRandomKey(8)
|
||||
_, ok := keys[string(key)]
|
||||
is.False(ok)
|
||||
|
||||
keys[string(key)] = true
|
||||
}
|
||||
})
|
||||
|
||||
+17
-27
@@ -1,79 +1,69 @@
|
||||
package apikey
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
const DefaultAPIKeyCacheSize = 1024
|
||||
const defaultAPIKeyCacheSize = 1024
|
||||
|
||||
// entry is a tuple containing the user and API key associated to an API key digest
|
||||
type entry[T any] struct {
|
||||
user T
|
||||
type entry struct {
|
||||
user portainer.User
|
||||
apiKey portainer.APIKey
|
||||
}
|
||||
|
||||
type UserCompareFn[T any] func(T, portainer.UserID) bool
|
||||
|
||||
// ApiKeyCache is a concurrency-safe, in-memory cache which primarily exists for to reduce database roundtrips.
|
||||
// apiKeyCache is a concurrency-safe, in-memory cache which primarily exists for to reduce database roundtrips.
|
||||
// We store the api-key digest (keys) and the associated user and key-data (values) in the cache.
|
||||
// This is required because HTTP requests will contain only the api-key digest in the x-api-key request header;
|
||||
// digest value must be mapped to a portainer user (and respective key data) for validation.
|
||||
// This cache is used to avoid multiple database queries to retrieve these user/key associated to the digest.
|
||||
type ApiKeyCache[T any] struct {
|
||||
type apiKeyCache struct {
|
||||
// cache type [string]entry cache (key: string(digest), value: user/key entry)
|
||||
// note: []byte keys are not supported by golang-lru Cache
|
||||
cache *lru.Cache
|
||||
userCmpFn UserCompareFn[T]
|
||||
cache *lru.Cache
|
||||
}
|
||||
|
||||
// NewAPIKeyCache creates a new cache for API keys
|
||||
func NewAPIKeyCache[T any](cacheSize int, userCompareFn UserCompareFn[T]) *ApiKeyCache[T] {
|
||||
func NewAPIKeyCache(cacheSize int) *apiKeyCache {
|
||||
cache, _ := lru.New(cacheSize)
|
||||
|
||||
return &ApiKeyCache[T]{cache: cache, userCmpFn: userCompareFn}
|
||||
return &apiKeyCache{cache: cache}
|
||||
}
|
||||
|
||||
// Get returns the user/key associated to an api-key's digest
|
||||
// This is required because HTTP requests will contain the digest of the API key in header,
|
||||
// the digest value must be mapped to a portainer user.
|
||||
func (c *ApiKeyCache[T]) Get(digest string) (T, portainer.APIKey, bool) {
|
||||
func (c *apiKeyCache) Get(digest string) (portainer.User, portainer.APIKey, bool) {
|
||||
val, ok := c.cache.Get(digest)
|
||||
if !ok {
|
||||
var t T
|
||||
|
||||
return t, portainer.APIKey{}, false
|
||||
return portainer.User{}, portainer.APIKey{}, false
|
||||
}
|
||||
|
||||
tuple := val.(entry[T])
|
||||
tuple := val.(entry)
|
||||
|
||||
return tuple.user, tuple.apiKey, true
|
||||
}
|
||||
|
||||
// Set persists a user/key entry to the cache
|
||||
func (c *ApiKeyCache[T]) Set(digest string, user T, apiKey portainer.APIKey) {
|
||||
c.cache.Add(digest, entry[T]{
|
||||
func (c *apiKeyCache) Set(digest string, user portainer.User, apiKey portainer.APIKey) {
|
||||
c.cache.Add(digest, entry{
|
||||
user: user,
|
||||
apiKey: apiKey,
|
||||
})
|
||||
}
|
||||
|
||||
// Delete evicts a digest's user/key entry key from the cache
|
||||
func (c *ApiKeyCache[T]) Delete(digest string) {
|
||||
func (c *apiKeyCache) Delete(digest string) {
|
||||
c.cache.Remove(digest)
|
||||
}
|
||||
|
||||
// InvalidateUserKeyCache loops through all the api-keys associated to a user and removes them from the cache
|
||||
func (c *ApiKeyCache[T]) InvalidateUserKeyCache(userId portainer.UserID) bool {
|
||||
func (c *apiKeyCache) InvalidateUserKeyCache(userId portainer.UserID) bool {
|
||||
present := false
|
||||
|
||||
for _, k := range c.cache.Keys() {
|
||||
user, _, _ := c.Get(k.(string))
|
||||
if c.userCmpFn(user, userId) {
|
||||
if user.ID == userId {
|
||||
present = c.cache.Remove(k)
|
||||
}
|
||||
}
|
||||
|
||||
return present
|
||||
}
|
||||
|
||||
+14
-14
@@ -10,11 +10,11 @@ import (
|
||||
func Test_apiKeyCacheGet(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
keyCache := NewAPIKeyCache(10, compareUser)
|
||||
keyCache := NewAPIKeyCache(10)
|
||||
|
||||
// pre-populate cache
|
||||
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{}, apiKey: portainer.APIKey{}})
|
||||
keyCache.cache.Add(string(""), entry[portainer.User]{user: portainer.User{}, apiKey: portainer.APIKey{}})
|
||||
keyCache.cache.Add(string("foo"), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
|
||||
keyCache.cache.Add(string(""), entry{user: portainer.User{}, apiKey: portainer.APIKey{}})
|
||||
|
||||
tests := []struct {
|
||||
digest string
|
||||
@@ -35,7 +35,7 @@ func Test_apiKeyCacheGet(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.digest, func(t *testing.T) {
|
||||
t.Run(string(test.digest), func(t *testing.T) {
|
||||
_, _, found := keyCache.Get(test.digest)
|
||||
is.Equal(test.found, found)
|
||||
})
|
||||
@@ -45,7 +45,7 @@ func Test_apiKeyCacheGet(t *testing.T) {
|
||||
func Test_apiKeyCacheSet(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
keyCache := NewAPIKeyCache(10, compareUser)
|
||||
keyCache := NewAPIKeyCache(10)
|
||||
|
||||
// pre-populate cache
|
||||
keyCache.Set("bar", portainer.User{ID: 2}, portainer.APIKey{})
|
||||
@@ -57,23 +57,23 @@ func Test_apiKeyCacheSet(t *testing.T) {
|
||||
val, ok := keyCache.cache.Get(string("bar"))
|
||||
is.True(ok)
|
||||
|
||||
tuple := val.(entry[portainer.User])
|
||||
tuple := val.(entry)
|
||||
is.Equal(portainer.User{ID: 2}, tuple.user)
|
||||
|
||||
val, ok = keyCache.cache.Get(string("foo"))
|
||||
is.True(ok)
|
||||
|
||||
tuple = val.(entry[portainer.User])
|
||||
tuple = val.(entry)
|
||||
is.Equal(portainer.User{ID: 3}, tuple.user)
|
||||
}
|
||||
|
||||
func Test_apiKeyCacheDelete(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
keyCache := NewAPIKeyCache(10, compareUser)
|
||||
keyCache := NewAPIKeyCache(10)
|
||||
|
||||
t.Run("Delete an existing entry", func(t *testing.T) {
|
||||
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||
keyCache.Delete("foo")
|
||||
|
||||
_, ok := keyCache.cache.Get(string("foo"))
|
||||
@@ -128,7 +128,7 @@ func Test_apiKeyCacheLRU(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
keyCache := NewAPIKeyCache(test.cacheLen, compareUser)
|
||||
keyCache := NewAPIKeyCache(test.cacheLen)
|
||||
|
||||
for _, key := range test.key {
|
||||
keyCache.Set(key, portainer.User{ID: 1}, portainer.APIKey{})
|
||||
@@ -150,10 +150,10 @@ func Test_apiKeyCacheLRU(t *testing.T) {
|
||||
func Test_apiKeyCacheInvalidateUserKeyCache(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
keyCache := NewAPIKeyCache(10, compareUser)
|
||||
keyCache := NewAPIKeyCache(10)
|
||||
|
||||
t.Run("Removes users keys from cache", func(t *testing.T) {
|
||||
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||
|
||||
ok := keyCache.InvalidateUserKeyCache(1)
|
||||
is.True(ok)
|
||||
@@ -163,8 +163,8 @@ func Test_apiKeyCacheInvalidateUserKeyCache(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Does not affect other keys", func(t *testing.T) {
|
||||
keyCache.cache.Add(string("foo"), entry[portainer.User]{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||
keyCache.cache.Add(string("bar"), entry[portainer.User]{user: portainer.User{ID: 2}, apiKey: portainer.APIKey{}})
|
||||
keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}})
|
||||
keyCache.cache.Add(string("bar"), entry{user: portainer.User{ID: 2}, apiKey: portainer.APIKey{}})
|
||||
|
||||
ok := keyCache.InvalidateUserKeyCache(1)
|
||||
is.True(ok)
|
||||
|
||||
+10
-25
@@ -1,15 +1,14 @@
|
||||
package apikey
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/securecookie"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -21,45 +20,30 @@ var ErrInvalidAPIKey = errors.New("Invalid API key")
|
||||
type apiKeyService struct {
|
||||
apiKeyRepository dataservices.APIKeyRepository
|
||||
userRepository dataservices.UserService
|
||||
cache *ApiKeyCache[portainer.User]
|
||||
}
|
||||
|
||||
// GenerateRandomKey generates a random key of specified length
|
||||
// source: https://github.com/gorilla/securecookie/blob/master/securecookie.go#L515
|
||||
func GenerateRandomKey(length int) []byte {
|
||||
k := make([]byte, length)
|
||||
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
func compareUser(u portainer.User, id portainer.UserID) bool {
|
||||
return u.ID == id
|
||||
cache *apiKeyCache
|
||||
}
|
||||
|
||||
func NewAPIKeyService(apiKeyRepository dataservices.APIKeyRepository, userRepository dataservices.UserService) *apiKeyService {
|
||||
return &apiKeyService{
|
||||
apiKeyRepository: apiKeyRepository,
|
||||
userRepository: userRepository,
|
||||
cache: NewAPIKeyCache(DefaultAPIKeyCacheSize, compareUser),
|
||||
cache: NewAPIKeyCache(defaultAPIKeyCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
// HashRaw computes a hash digest of provided raw API key.
|
||||
func (a *apiKeyService) HashRaw(rawKey string) string {
|
||||
hashDigest := sha256.Sum256([]byte(rawKey))
|
||||
|
||||
return base64.StdEncoding.EncodeToString(hashDigest[:])
|
||||
}
|
||||
|
||||
// GenerateApiKey generates a raw API key for a user (for one-time display).
|
||||
// The generated API key is stored in the cache and database.
|
||||
func (a *apiKeyService) GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error) {
|
||||
randKey := GenerateRandomKey(32)
|
||||
randKey := securecookie.GenerateRandomKey(32)
|
||||
encodedRawAPIKey := base64.StdEncoding.EncodeToString(randKey)
|
||||
prefixedAPIKey := portainerAPIKeyPrefix + encodedRawAPIKey
|
||||
|
||||
hashDigest := a.HashRaw(prefixedAPIKey)
|
||||
|
||||
apiKey := &portainer.APIKey{
|
||||
@@ -70,7 +54,8 @@ func (a *apiKeyService) GenerateApiKey(user portainer.User, description string)
|
||||
Digest: hashDigest,
|
||||
}
|
||||
|
||||
if err := a.apiKeyRepository.Create(apiKey); err != nil {
|
||||
err := a.apiKeyRepository.Create(apiKey)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "Unable to create API key")
|
||||
}
|
||||
|
||||
@@ -93,6 +78,7 @@ func (a *apiKeyService) GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey,
|
||||
// GetDigestUserAndKey returns the user and api-key associated to a specified hash digest.
|
||||
// A cache lookup is performed first; if the user/api-key is not found in the cache, respective database lookups are performed.
|
||||
func (a *apiKeyService) GetDigestUserAndKey(digest string) (portainer.User, portainer.APIKey, error) {
|
||||
// get api key from cache if possible
|
||||
cachedUser, cachedKey, ok := a.cache.Get(digest)
|
||||
if ok {
|
||||
return cachedUser, cachedKey, nil
|
||||
@@ -120,21 +106,20 @@ func (a *apiKeyService) UpdateAPIKey(apiKey *portainer.APIKey) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to retrieve API key")
|
||||
}
|
||||
|
||||
a.cache.Set(apiKey.Digest, user, *apiKey)
|
||||
|
||||
return a.apiKeyRepository.Update(apiKey.ID, apiKey)
|
||||
}
|
||||
|
||||
// DeleteAPIKey deletes an API key and removes the digest/api-key entry from the cache.
|
||||
func (a *apiKeyService) DeleteAPIKey(apiKeyID portainer.APIKeyID) error {
|
||||
// get api-key digest to remove from cache
|
||||
apiKey, err := a.apiKeyRepository.Read(apiKeyID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Unable to retrieve API key: %d", apiKeyID))
|
||||
}
|
||||
|
||||
// delete the user/api-key from cache
|
||||
a.cache.Delete(apiKey.Digest)
|
||||
|
||||
return a.apiKeyRepository.Delete(apiKeyID)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// abosolutePath should be an absolute path to a directory.
|
||||
// Archive name will be <directoryName>.tar.gz and will be placed next to the directory.
|
||||
func TarGzDir(absolutePath string) (string, error) {
|
||||
targzPath := filepath.Join(absolutePath, filepath.Base(absolutePath)+".tar.gz")
|
||||
targzPath := filepath.Join(absolutePath, fmt.Sprintf("%s.tar.gz", filepath.Base(absolutePath)))
|
||||
outFile, err := os.Create(targzPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -23,7 +24,7 @@ func listFiles(dir string) []string {
|
||||
return items
|
||||
}
|
||||
|
||||
func Test_shouldCreateArchive(t *testing.T) {
|
||||
func Test_shouldCreateArhive(t *testing.T) {
|
||||
tmpdir := t.TempDir()
|
||||
content := []byte("content")
|
||||
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
@@ -33,11 +34,12 @@ func Test_shouldCreateArchive(t *testing.T) {
|
||||
|
||||
gzPath, err := TarGzDir(tmpdir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filepath.Join(tmpdir, filepath.Base(tmpdir)+".tar.gz"), gzPath)
|
||||
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||
|
||||
extractionDir := t.TempDir()
|
||||
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
|
||||
if err := cmd.Run(); err != nil {
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
t.Fatal("Failed to extract archive: ", err)
|
||||
}
|
||||
extractedFiles := listFiles(extractionDir)
|
||||
@@ -54,7 +56,7 @@ func Test_shouldCreateArchive(t *testing.T) {
|
||||
wasExtracted("dir/.dotfile")
|
||||
}
|
||||
|
||||
func Test_shouldCreateArchive2(t *testing.T) {
|
||||
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
|
||||
tmpdir := t.TempDir()
|
||||
content := []byte("content")
|
||||
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
@@ -64,11 +66,12 @@ func Test_shouldCreateArchive2(t *testing.T) {
|
||||
|
||||
gzPath, err := TarGzDir(tmpdir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filepath.Join(tmpdir, filepath.Base(tmpdir)+".tar.gz"), gzPath)
|
||||
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||
|
||||
extractionDir := t.TempDir()
|
||||
r, _ := os.Open(gzPath)
|
||||
if err := ExtractTarGz(r, extractionDir); err != nil {
|
||||
ExtractTarGz(r, extractionDir)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to extract archive: ", err)
|
||||
}
|
||||
extractedFiles := listFiles(extractionDir)
|
||||
|
||||
+50
-5
@@ -2,6 +2,7 @@ package archive
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -11,6 +12,50 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// UnzipArchive will unzip an archive from bytes into the dest destination folder on disk
|
||||
func UnzipArchive(archiveData []byte, dest string) error {
|
||||
zipReader, err := zip.NewReader(bytes.NewReader(archiveData), int64(len(archiveData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, zipFile := range zipReader.File {
|
||||
err := extractFileFromArchive(zipFile, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFileFromArchive(file *zip.File, dest string) error {
|
||||
f, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fpath := filepath.Join(dest, file.Name)
|
||||
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return outFile.Close()
|
||||
}
|
||||
|
||||
// UnzipFile will decompress a zip archive, moving all files and folders
|
||||
// within the zip file (parameter 1) to an output directory (parameter 2).
|
||||
func UnzipFile(src string, dest string) error {
|
||||
@@ -31,11 +76,11 @@ func UnzipFile(src string, dest string) error {
|
||||
if f.FileInfo().IsDir() {
|
||||
// Make Folder
|
||||
os.MkdirAll(p, os.ModePerm)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := unzipFile(f, p); err != nil {
|
||||
err = unzipFile(f, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -48,20 +93,20 @@ func unzipFile(f *zip.File, p string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't make a path %s", p)
|
||||
}
|
||||
|
||||
outFile, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't create file %s", p)
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't open zip file %s in the archive", f.Name)
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
if _, err = io.Copy(outFile, rc); err != nil {
|
||||
_, err = io.Copy(outFile, rc)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't copy an archived file content")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package ecr
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -15,7 +15,7 @@ func (s *Service) GetEncodedAuthorizationToken() (token *string, expiry *time.Ti
|
||||
}
|
||||
|
||||
if len(getAuthorizationTokenOutput.AuthorizationData) == 0 {
|
||||
err = errors.New("AuthorizationData is empty")
|
||||
err = fmt.Errorf("AuthorizationData is empty")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (s *Service) ParseAuthorizationToken(token string) (username string, passwo
|
||||
|
||||
splitToken := strings.Split(token, ":")
|
||||
if len(splitToken) < 2 {
|
||||
err = errors.New("invalid ECR authorization token")
|
||||
err = fmt.Errorf("invalid ECR authorization token")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+31
-36
@@ -21,7 +21,6 @@ const rwxr__r__ os.FileMode = 0o744
|
||||
|
||||
var filesToBackup = []string{
|
||||
"certs",
|
||||
"chisel",
|
||||
"compose",
|
||||
"config.json",
|
||||
"custom_templates",
|
||||
@@ -31,13 +30,40 @@ var filesToBackup = []string{
|
||||
"portainer.key",
|
||||
"portainer.pub",
|
||||
"tls",
|
||||
"chisel",
|
||||
}
|
||||
|
||||
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
|
||||
func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
|
||||
backupDirPath, err := backupDatabaseAndFilesystem(gate, datastore, filestorePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
unlock := gate.Lock()
|
||||
defer unlock()
|
||||
|
||||
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
|
||||
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to create backup dir")
|
||||
}
|
||||
|
||||
{
|
||||
// new export
|
||||
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
||||
|
||||
err := datastore.Export(exportFilename)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
|
||||
} else {
|
||||
log.Debug().Str("filename", exportFilename).Msg("file exported")
|
||||
}
|
||||
}
|
||||
|
||||
if err := backupDb(backupDirPath, datastore); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to backup database")
|
||||
}
|
||||
|
||||
for _, filename := range filesToBackup {
|
||||
err := filesystem.CopyPath(filepath.Join(filestorePath, filename), backupDirPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to create backup file")
|
||||
}
|
||||
}
|
||||
|
||||
archivePath, err := archive.TarGzDir(backupDirPath)
|
||||
@@ -55,37 +81,6 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
|
||||
return archivePath, nil
|
||||
}
|
||||
|
||||
func backupDatabaseAndFilesystem(gate *offlinegate.OfflineGate, datastore dataservices.DataStore, filestorePath string) (string, error) {
|
||||
unlock := gate.Lock()
|
||||
defer unlock()
|
||||
|
||||
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
|
||||
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to create backup dir")
|
||||
}
|
||||
|
||||
// new export
|
||||
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
||||
|
||||
if err := datastore.Export(exportFilename); err != nil {
|
||||
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
|
||||
} else {
|
||||
log.Debug().Str("filename", exportFilename).Msg("file exported")
|
||||
}
|
||||
|
||||
if err := backupDb(backupDirPath, datastore); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to backup database")
|
||||
}
|
||||
|
||||
for _, filename := range filesToBackup {
|
||||
if err := filesystem.CopyPath(filepath.Join(filestorePath, filename), backupDirPath); err != nil {
|
||||
return "", errors.Wrap(err, "Failed to create backup file")
|
||||
}
|
||||
}
|
||||
|
||||
return backupDirPath, nil
|
||||
}
|
||||
|
||||
func backupDb(backupDirPath string, datastore dataservices.DataStore) error {
|
||||
dbFileName := datastore.Connection().GetDatabaseFileName()
|
||||
_, err := datastore.Backup(filepath.Join(backupDirPath, dbFileName))
|
||||
@@ -99,7 +94,7 @@ func encrypt(path string, passphrase string) (string, error) {
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
outFileName := path + ".encrypted"
|
||||
outFileName := fmt.Sprintf("%s.encrypted", path)
|
||||
out, err := os.Create(outFileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package build
|
||||
|
||||
import "runtime"
|
||||
|
||||
// Variables to be set during the build time
|
||||
var BuildNumber string
|
||||
var ImageTag string
|
||||
var NodejsVersion string
|
||||
var YarnVersion string
|
||||
var WebpackVersion string
|
||||
var GoVersion string = runtime.Version()
|
||||
var GitCommit string
|
||||
@@ -0,0 +1,82 @@
|
||||
package chisel
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
)
|
||||
|
||||
// EdgeJobs retrieves the edge jobs for the given environment
|
||||
func (service *Service) EdgeJobs(endpointID portainer.EndpointID) []portainer.EdgeJob {
|
||||
service.mu.RLock()
|
||||
defer service.mu.RUnlock()
|
||||
|
||||
return append(
|
||||
make([]portainer.EdgeJob, 0, len(service.edgeJobs[endpointID])),
|
||||
service.edgeJobs[endpointID]...,
|
||||
)
|
||||
}
|
||||
|
||||
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
|
||||
func (service *Service) AddEdgeJob(endpoint *portainer.Endpoint, edgeJob *portainer.EdgeJob) {
|
||||
if endpoint.Edge.AsyncMode {
|
||||
return
|
||||
}
|
||||
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
existingJobIndex := -1
|
||||
for idx, existingJob := range service.edgeJobs[endpoint.ID] {
|
||||
if existingJob.ID == edgeJob.ID {
|
||||
existingJobIndex = idx
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if existingJobIndex == -1 {
|
||||
service.edgeJobs[endpoint.ID] = append(service.edgeJobs[endpoint.ID], *edgeJob)
|
||||
} else {
|
||||
service.edgeJobs[endpoint.ID][existingJobIndex] = *edgeJob
|
||||
}
|
||||
|
||||
cache.Del(endpoint.ID)
|
||||
}
|
||||
|
||||
// RemoveEdgeJob will remove the specified Edge job from each tunnel it was registered with.
|
||||
func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
|
||||
service.mu.Lock()
|
||||
|
||||
for endpointID := range service.edgeJobs {
|
||||
n := 0
|
||||
for _, edgeJob := range service.edgeJobs[endpointID] {
|
||||
if edgeJob.ID != edgeJobID {
|
||||
service.edgeJobs[endpointID][n] = edgeJob
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
service.edgeJobs[endpointID] = service.edgeJobs[endpointID][:n]
|
||||
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
|
||||
service.mu.Unlock()
|
||||
}
|
||||
|
||||
func (service *Service) RemoveEdgeJobFromEndpoint(endpointID portainer.EndpointID, edgeJobID portainer.EdgeJobID) {
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
n := 0
|
||||
for _, edgeJob := range service.edgeJobs[endpointID] {
|
||||
if edgeJob.ID != edgeJobID {
|
||||
service.edgeJobs[endpointID][n] = edgeJob
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
service.edgeJobs[endpointID] = service.edgeJobs[endpointID][:n]
|
||||
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
@@ -287,7 +287,7 @@ func (service *Service) checkTunnels() {
|
||||
Msg("unable to snapshot Edge environment")
|
||||
}
|
||||
|
||||
service.close(endpointID)
|
||||
service.close(portainer.EndpointID(endpointID))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package chisel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
@@ -37,11 +36,8 @@ func TestPingAgentPanic(t *testing.T) {
|
||||
ln, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := &http.Server{Handler: mux}
|
||||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- srv.Serve(ln)
|
||||
require.NoError(t, http.Serve(ln, mux))
|
||||
}()
|
||||
|
||||
err = s.Open(endpoint)
|
||||
@@ -49,6 +45,4 @@ func TestPingAgentPanic(t *testing.T) {
|
||||
s.activeTunnels[endpoint.ID].Port = ln.Addr().(*net.TCPAddr).Port
|
||||
|
||||
require.Error(t, s.pingAgent(endpoint.ID))
|
||||
require.NoError(t, srv.Shutdown(context.Background()))
|
||||
require.ErrorIs(t, <-errCh, http.ErrServerClosed)
|
||||
}
|
||||
|
||||
+20
-25
@@ -17,20 +17,24 @@ import (
|
||||
type Service struct{}
|
||||
|
||||
var (
|
||||
ErrInvalidEndpointProtocol = errors.New("Invalid environment protocol: Portainer only supports unix://, npipe:// or tcp://")
|
||||
ErrSocketOrNamedPipeNotFound = errors.New("Unable to locate Unix socket or named pipe")
|
||||
ErrInvalidSnapshotInterval = errors.New("Invalid snapshot interval")
|
||||
ErrAdminPassExcludeAdminPassFile = errors.New("Cannot use --admin-password with --admin-password-file")
|
||||
errInvalidEndpointProtocol = errors.New("Invalid environment protocol: Portainer only supports unix://, npipe:// or tcp://")
|
||||
errSocketOrNamedPipeNotFound = errors.New("Unable to locate Unix socket or named pipe")
|
||||
errInvalidSnapshotInterval = errors.New("Invalid snapshot interval")
|
||||
errAdminPassExcludeAdminPassFile = errors.New("Cannot use --admin-password with --admin-password-file")
|
||||
)
|
||||
|
||||
func CLIFlags() *portainer.CLIFlags {
|
||||
return &portainer.CLIFlags{
|
||||
// ParseFlags parse the CLI flags and return a portainer.Flags struct
|
||||
func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
kingpin.Version(version)
|
||||
|
||||
flags := &portainer.CLIFlags{
|
||||
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
||||
AddrHTTPS: kingpin.Flag("bind-https", "Address and port to serve Portainer via https").Default(defaultHTTPSBindAddress).String(),
|
||||
TunnelAddr: kingpin.Flag("tunnel-addr", "Address to serve the tunnel server").Default(defaultTunnelServerAddress).String(),
|
||||
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
|
||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||
DemoEnvironment: kingpin.Flag("demo", "Demo environment").Bool(),
|
||||
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
|
||||
FeatureFlags: kingpin.Flag("feat", "List of feature flags").Strings(),
|
||||
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
|
||||
@@ -59,18 +63,7 @@ func CLIFlags() *portainer.CLIFlags {
|
||||
SecretKeyName: kingpin.Flag("secret-key-name", "Secret key name for encryption and will be used as /run/secrets/<secret-key-name>.").Default(defaultSecretKeyName).String(),
|
||||
LogLevel: kingpin.Flag("log-level", "Set the minimum logging level to show").Default("INFO").Enum("DEBUG", "INFO", "WARN", "ERROR"),
|
||||
LogMode: kingpin.Flag("log-mode", "Set the logging output mode").Default("PRETTY").Enum("NOCOLOR", "PRETTY", "JSON"),
|
||||
KubectlShellImage: kingpin.Flag("kubectl-shell-image", "Kubectl shell image").Envar(portainer.KubectlShellImageEnvVar).Default(portainer.DefaultKubectlShellImage).String(),
|
||||
PullLimitCheckDisabled: kingpin.Flag("pull-limit-check-disabled", "Pull limit check").Envar(portainer.PullLimitCheckDisabledEnvVar).Default(defaultPullLimitCheckDisabled).Bool(),
|
||||
TrustedOrigins: kingpin.Flag("trusted-origins", "List of trusted origins for CSRF protection. Separate multiple origins with a comma.").Envar(portainer.TrustedOriginsEnvVar).String(),
|
||||
CSP: kingpin.Flag("csp", "Content Security Policy (CSP) header").Envar(portainer.CSPEnvVar).Default("true").Bool(),
|
||||
}
|
||||
}
|
||||
|
||||
// ParseFlags parse the CLI flags and return a portainer.Flags struct
|
||||
func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
kingpin.Version(version)
|
||||
|
||||
flags := CLIFlags()
|
||||
|
||||
kingpin.Parse()
|
||||
|
||||
@@ -90,16 +83,18 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||
displayDeprecationWarnings(flags)
|
||||
|
||||
if err := validateEndpointURL(*flags.EndpointURL); err != nil {
|
||||
err := validateEndpointURL(*flags.EndpointURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateSnapshotInterval(*flags.SnapshotInterval); err != nil {
|
||||
err = validateSnapshotInterval(*flags.SnapshotInterval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *flags.AdminPassword != "" && *flags.AdminPasswordFile != "" {
|
||||
return ErrAdminPassExcludeAdminPassFile
|
||||
return errAdminPassExcludeAdminPassFile
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -121,16 +116,15 @@ func validateEndpointURL(endpointURL string) error {
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(endpointURL, "unix://") && !strings.HasPrefix(endpointURL, "tcp://") && !strings.HasPrefix(endpointURL, "npipe://") {
|
||||
return ErrInvalidEndpointProtocol
|
||||
return errInvalidEndpointProtocol
|
||||
}
|
||||
|
||||
if strings.HasPrefix(endpointURL, "unix://") || strings.HasPrefix(endpointURL, "npipe://") {
|
||||
socketPath := strings.TrimPrefix(endpointURL, "unix://")
|
||||
socketPath = strings.TrimPrefix(socketPath, "npipe://")
|
||||
|
||||
if _, err := os.Stat(socketPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return ErrSocketOrNamedPipeNotFound
|
||||
return errSocketOrNamedPipeNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -145,8 +139,9 @@ func validateSnapshotInterval(snapshotInterval string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := time.ParseDuration(snapshotInterval); err != nil {
|
||||
return ErrInvalidSnapshotInterval
|
||||
_, err := time.ParseDuration(snapshotInterval)
|
||||
if err != nil {
|
||||
return errInvalidSnapshotInterval
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+3
-1
@@ -19,5 +19,7 @@ func Confirm(message string) (bool, error) {
|
||||
}
|
||||
|
||||
answer = strings.ReplaceAll(answer, "\n", "")
|
||||
return strings.EqualFold(answer, "y") || strings.EqualFold(answer, "yes"), nil
|
||||
answer = strings.ToLower(answer)
|
||||
|
||||
return answer == "y" || answer == "yes", nil
|
||||
}
|
||||
|
||||
+16
-17
@@ -4,21 +4,20 @@
|
||||
package cli
|
||||
|
||||
const (
|
||||
defaultBindAddress = ":9000"
|
||||
defaultHTTPSBindAddress = ":9443"
|
||||
defaultTunnelServerAddress = "0.0.0.0"
|
||||
defaultTunnelServerPort = "8000"
|
||||
defaultDataDirectory = "/data"
|
||||
defaultAssetsDirectory = "./"
|
||||
defaultTLS = "false"
|
||||
defaultTLSSkipVerify = "false"
|
||||
defaultTLSCACertPath = "/certs/ca.pem"
|
||||
defaultTLSCertPath = "/certs/cert.pem"
|
||||
defaultTLSKeyPath = "/certs/key.pem"
|
||||
defaultHTTPDisabled = "false"
|
||||
defaultHTTPEnabled = "false"
|
||||
defaultSSL = "false"
|
||||
defaultBaseURL = "/"
|
||||
defaultSecretKeyName = "portainer"
|
||||
defaultPullLimitCheckDisabled = "false"
|
||||
defaultBindAddress = ":9000"
|
||||
defaultHTTPSBindAddress = ":9443"
|
||||
defaultTunnelServerAddress = "0.0.0.0"
|
||||
defaultTunnelServerPort = "8000"
|
||||
defaultDataDirectory = "/data"
|
||||
defaultAssetsDirectory = "./"
|
||||
defaultTLS = "false"
|
||||
defaultTLSSkipVerify = "false"
|
||||
defaultTLSCACertPath = "/certs/ca.pem"
|
||||
defaultTLSCertPath = "/certs/cert.pem"
|
||||
defaultTLSKeyPath = "/certs/key.pem"
|
||||
defaultHTTPDisabled = "false"
|
||||
defaultHTTPEnabled = "false"
|
||||
defaultSSL = "false"
|
||||
defaultBaseURL = "/"
|
||||
defaultSecretKeyName = "portainer"
|
||||
)
|
||||
|
||||
+17
-18
@@ -1,22 +1,21 @@
|
||||
package cli
|
||||
|
||||
const (
|
||||
defaultBindAddress = ":9000"
|
||||
defaultHTTPSBindAddress = ":9443"
|
||||
defaultTunnelServerAddress = "0.0.0.0"
|
||||
defaultTunnelServerPort = "8000"
|
||||
defaultDataDirectory = "C:\\data"
|
||||
defaultAssetsDirectory = "./"
|
||||
defaultTLS = "false"
|
||||
defaultTLSSkipVerify = "false"
|
||||
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
||||
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
||||
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
||||
defaultHTTPDisabled = "false"
|
||||
defaultHTTPEnabled = "false"
|
||||
defaultSSL = "false"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultBaseURL = "/"
|
||||
defaultSecretKeyName = "portainer"
|
||||
defaultPullLimitCheckDisabled = "false"
|
||||
defaultBindAddress = ":9000"
|
||||
defaultHTTPSBindAddress = ":9443"
|
||||
defaultTunnelServerAddress = "0.0.0.0"
|
||||
defaultTunnelServerPort = "8000"
|
||||
defaultDataDirectory = "C:\\data"
|
||||
defaultAssetsDirectory = "./"
|
||||
defaultTLS = "false"
|
||||
defaultTLSSkipVerify = "false"
|
||||
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
||||
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
||||
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
||||
defaultHTTPDisabled = "false"
|
||||
defaultHTTPEnabled = "false"
|
||||
defaultSSL = "false"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultBaseURL = "/"
|
||||
defaultSecretKeyName = "portainer"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
type pairListBool []portainer.Pair
|
||||
|
||||
// Set implementation for a list of portainer.Pair
|
||||
func (l *pairListBool) Set(value string) error {
|
||||
p := new(portainer.Pair)
|
||||
|
||||
// default to true. example setting=true is equivalent to setting
|
||||
parts := strings.SplitN(value, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
p.Name = parts[0]
|
||||
p.Value = "true"
|
||||
} else {
|
||||
p.Name = parts[0]
|
||||
p.Value = parts[1]
|
||||
}
|
||||
|
||||
*l = append(*l, *p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implementation for a list of pair
|
||||
func (l *pairListBool) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsCumulative implementation for a list of pair
|
||||
func (l *pairListBool) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func BoolPairs(s kingpin.Settings) (target *[]portainer.Pair) {
|
||||
target = new([]portainer.Pair)
|
||||
s.SetValue((*pairListBool)(target))
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package logs
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/rs/zerolog/pkgerrors"
|
||||
)
|
||||
|
||||
func ConfigureLogger() {
|
||||
func configureLogger() {
|
||||
zerolog.ErrorStackFieldName = "stack_trace"
|
||||
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
@@ -21,7 +21,7 @@ func ConfigureLogger() {
|
||||
log.Logger = log.Logger.With().Caller().Stack().Logger()
|
||||
}
|
||||
|
||||
func SetLoggingLevel(level string) {
|
||||
func setLoggingLevel(level string) {
|
||||
switch level {
|
||||
case "ERROR":
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
@@ -34,7 +34,7 @@ func SetLoggingLevel(level string) {
|
||||
}
|
||||
}
|
||||
|
||||
func SetLoggingMode(mode string) {
|
||||
func setLoggingMode(mode string) {
|
||||
switch mode {
|
||||
case "PRETTY":
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{
|
||||
@@ -54,7 +54,7 @@ func SetLoggingMode(mode string) {
|
||||
}
|
||||
}
|
||||
|
||||
func formatMessage(i any) string {
|
||||
func formatMessage(i interface{}) string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
+138
-103
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"os"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/apikey"
|
||||
"github.com/portainer/portainer/api/build"
|
||||
"github.com/portainer/portainer/api/chisel"
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/datastore/migrator"
|
||||
"github.com/portainer/portainer/api/datastore/postinit"
|
||||
"github.com/portainer/portainer/api/demo"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/exec"
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/edge"
|
||||
"github.com/portainer/portainer/api/internal/edge/edgestacks"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/internal/snapshot"
|
||||
@@ -39,34 +41,28 @@ import (
|
||||
"github.com/portainer/portainer/api/kubernetes"
|
||||
kubecli "github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/portainer/portainer/api/ldap"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/api/oauth"
|
||||
"github.com/portainer/portainer/api/pendingactions"
|
||||
"github.com/portainer/portainer/api/pendingactions/actions"
|
||||
"github.com/portainer/portainer/api/pendingactions/handlers"
|
||||
"github.com/portainer/portainer/api/platform"
|
||||
"github.com/portainer/portainer/api/scheduler"
|
||||
"github.com/portainer/portainer/api/stacks/deployments"
|
||||
"github.com/portainer/portainer/pkg/build"
|
||||
"github.com/portainer/portainer/pkg/featureflags"
|
||||
"github.com/portainer/portainer/pkg/libhelm"
|
||||
libhelmtypes "github.com/portainer/portainer/pkg/libhelm/types"
|
||||
"github.com/portainer/portainer/pkg/libstack"
|
||||
"github.com/portainer/portainer/pkg/libstack/compose"
|
||||
"github.com/portainer/portainer/pkg/validate"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func initCLI() *portainer.CLIFlags {
|
||||
cliService := &cli.Service{}
|
||||
|
||||
var cliService portainer.CLIService = &cli.Service{}
|
||||
flags, err := cliService.ParseFlags(portainer.APIVersion)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed parsing flags")
|
||||
}
|
||||
|
||||
if err := cliService.ValidateFlags(flags); err != nil {
|
||||
err = cliService.ValidateFlags(flags)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed validating flags")
|
||||
}
|
||||
|
||||
@@ -96,15 +92,15 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
log.Fatal().Msg("failed creating database connection: expecting a boltdb database type but a different one was received")
|
||||
}
|
||||
|
||||
store := datastore.NewStore(flags, fileService, connection)
|
||||
|
||||
store := datastore.NewStore(*flags.Data, fileService, connection)
|
||||
isNew, err := store.Open()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed opening store")
|
||||
}
|
||||
|
||||
if *flags.Rollback {
|
||||
if err := store.Rollback(false); err != nil {
|
||||
err := store.Rollback(false)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed rolling back")
|
||||
}
|
||||
|
||||
@@ -113,7 +109,8 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
}
|
||||
|
||||
// Init sets some defaults - it's basically a migration
|
||||
if err := store.Init(); err != nil {
|
||||
err = store.Init()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing data store")
|
||||
}
|
||||
|
||||
@@ -123,7 +120,7 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
log.Fatal().Err(err).Msg("failed generating instance id")
|
||||
}
|
||||
|
||||
migratorInstance := migrator.NewMigrator(&migrator.MigratorParameters{Flags: flags})
|
||||
migratorInstance := migrator.NewMigrator(&migrator.MigratorParameters{})
|
||||
migratorCount := migratorInstance.GetMigratorCountOfCurrentAPIVersion()
|
||||
|
||||
// from MigrateData
|
||||
@@ -135,23 +132,25 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
}
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
|
||||
if err := updateSettingsFromFlags(store, flags); err != nil {
|
||||
err = updateSettingsFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed updating settings from flags")
|
||||
}
|
||||
} else {
|
||||
if err := store.MigrateData(); err != nil {
|
||||
err = store.MigrateData()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed migration")
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateSettingsFromFlags(store, flags); err != nil {
|
||||
err = updateSettingsFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed updating settings from flags")
|
||||
}
|
||||
|
||||
// this is for the db restore functionality - needs more tests.
|
||||
go func() {
|
||||
<-shutdownCtx.Done()
|
||||
|
||||
defer connection.Close()
|
||||
}()
|
||||
|
||||
@@ -168,12 +167,32 @@ func checkDBSchemaServerVersionMatch(dbStore dataservices.DataStore, serverVersi
|
||||
return v.SchemaVersion == serverVersion && v.Edition == serverEdition
|
||||
}
|
||||
|
||||
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore dataservices.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager) portainer.KubernetesDeployer {
|
||||
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager)
|
||||
func initComposeStackManager(composeDeployer libstack.Deployer, proxyManager *proxy.Manager) portainer.ComposeStackManager {
|
||||
composeWrapper, err := exec.NewComposeStackManager(composeDeployer, proxyManager)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed creating compose manager")
|
||||
}
|
||||
|
||||
return composeWrapper
|
||||
}
|
||||
|
||||
func initHelmPackageManager() (libhelmtypes.HelmPackageManager, error) {
|
||||
return libhelm.NewHelmPackageManager()
|
||||
func initSwarmStackManager(
|
||||
assetsPath string,
|
||||
configPath string,
|
||||
signatureService portainer.DigitalSignatureService,
|
||||
fileService portainer.FileService,
|
||||
reverseTunnelService portainer.ReverseTunnelService,
|
||||
dataStore dataservices.DataStore,
|
||||
) (portainer.SwarmStackManager, error) {
|
||||
return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService, dataStore)
|
||||
}
|
||||
|
||||
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore dataservices.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) {
|
||||
return libhelm.NewHelmPackageManager(libhelm.HelmConfig{BinaryPath: assetsPath})
|
||||
}
|
||||
|
||||
func initAPIKeyService(datastore dataservices.DataStore) apikey.APIKeyService {
|
||||
@@ -185,16 +204,36 @@ func initJWTService(userSessionTimeout string, dataStore dataservices.DataStore)
|
||||
userSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||
}
|
||||
|
||||
return jwt.NewService(userSessionTimeout, dataStore)
|
||||
jwtService, err := jwt.NewService(userSessionTimeout, dataStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jwtService, nil
|
||||
}
|
||||
|
||||
func initDigitalSignatureService() portainer.DigitalSignatureService {
|
||||
return crypto.NewECDSAService(os.Getenv("AGENT_SECRET"))
|
||||
}
|
||||
|
||||
func initCryptoService() portainer.CryptoService {
|
||||
return &crypto.Service{}
|
||||
}
|
||||
|
||||
func initLDAPService() portainer.LDAPService {
|
||||
return &ldap.Service{}
|
||||
}
|
||||
|
||||
func initOAuthService() portainer.OAuthService {
|
||||
return oauth.NewService()
|
||||
}
|
||||
|
||||
func initGitService(ctx context.Context) portainer.GitService {
|
||||
return git.NewService(ctx)
|
||||
}
|
||||
|
||||
func initSSLService(addr, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
|
||||
slices := strings.Split(addr, ":")
|
||||
|
||||
host := slices[0]
|
||||
if host == "" {
|
||||
host = "0.0.0.0"
|
||||
@@ -202,13 +241,22 @@ func initSSLService(addr, certPath, keyPath string, fileService portainer.FileSe
|
||||
|
||||
sslService := ssl.NewService(fileService, dataStore, shutdownTrigger)
|
||||
|
||||
if err := sslService.Init(host, certPath, keyPath); err != nil {
|
||||
err := sslService.Init(host, certPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sslService, nil
|
||||
}
|
||||
|
||||
func initDockerClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService) *dockerclient.ClientFactory {
|
||||
return dockerclient.NewClientFactory(signatureService, reverseTunnelService)
|
||||
}
|
||||
|
||||
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*kubecli.ClientFactory, error) {
|
||||
return kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, addrHTTPS, userSessionTimeout)
|
||||
}
|
||||
|
||||
func initSnapshotService(
|
||||
snapshotIntervalFromFlag string,
|
||||
dataStore dataservices.DataStore,
|
||||
@@ -241,21 +289,34 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
|
||||
return err
|
||||
}
|
||||
|
||||
settings.SnapshotInterval = cmp.Or(*flags.SnapshotInterval, settings.SnapshotInterval)
|
||||
settings.LogoURL = cmp.Or(*flags.Logo, settings.LogoURL)
|
||||
settings.EnableEdgeComputeFeatures = cmp.Or(*flags.EnableEdgeComputeFeatures, settings.EnableEdgeComputeFeatures)
|
||||
settings.TemplatesURL = cmp.Or(*flags.Templates, settings.TemplatesURL)
|
||||
if *flags.SnapshotInterval != "" {
|
||||
settings.SnapshotInterval = *flags.SnapshotInterval
|
||||
}
|
||||
|
||||
if *flags.Logo != "" {
|
||||
settings.LogoURL = *flags.Logo
|
||||
}
|
||||
|
||||
if *flags.EnableEdgeComputeFeatures {
|
||||
settings.EnableEdgeComputeFeatures = *flags.EnableEdgeComputeFeatures
|
||||
}
|
||||
|
||||
if *flags.Templates != "" {
|
||||
settings.TemplatesURL = *flags.Templates
|
||||
}
|
||||
|
||||
if *flags.Labels != nil {
|
||||
settings.BlackListedLabels = *flags.Labels
|
||||
}
|
||||
|
||||
settings.AgentSecret = ""
|
||||
if agentKey, ok := os.LookupEnv("AGENT_SECRET"); ok {
|
||||
settings.AgentSecret = agentKey
|
||||
} else {
|
||||
settings.AgentSecret = ""
|
||||
}
|
||||
|
||||
if err := dataStore.Settings().UpdateSettings(settings); err != nil {
|
||||
err = dataStore.Settings().UpdateSettings(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -278,7 +339,6 @@ func loadAndParseKeyPair(fileService portainer.FileService, signatureService por
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return signatureService.ParseKeyPair(private, public)
|
||||
}
|
||||
|
||||
@@ -287,9 +347,7 @@ func generateAndStoreKeyPair(fileService portainer.FileService, signatureService
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateHeader, publicHeader := signatureService.PEMHeaders()
|
||||
|
||||
return fileService.StoreKeyPair(private, public, privateHeader, publicHeader)
|
||||
}
|
||||
|
||||
@@ -302,7 +360,6 @@ func initKeyPair(fileService portainer.FileService, signatureService portainer.D
|
||||
if existingKeyPair {
|
||||
return loadAndParseKeyPair(fileService, signatureService)
|
||||
}
|
||||
|
||||
return generateAndStoreKeyPair(fileService, signatureService)
|
||||
}
|
||||
|
||||
@@ -320,7 +377,6 @@ func loadEncryptionSecretKey(keyfilename string) []byte {
|
||||
|
||||
// return a 32 byte hash of the secret (required for AES)
|
||||
hash := sha256.Sum256(content)
|
||||
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
@@ -331,18 +387,6 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
featureflags.Parse(*flags.FeatureFlags, portainer.SupportedFeatureFlags)
|
||||
}
|
||||
|
||||
trustedOrigins := []string{}
|
||||
if *flags.TrustedOrigins != "" {
|
||||
// validate if the trusted origins are valid urls
|
||||
for _, origin := range strings.Split(*flags.TrustedOrigins, ",") {
|
||||
if !validate.IsTrustedOrigin(origin) {
|
||||
log.Fatal().Str("trusted_origin", origin).Msg("invalid url for trusted origin. Please check the trusted origins flag.")
|
||||
}
|
||||
|
||||
trustedOrigins = append(trustedOrigins, origin)
|
||||
}
|
||||
}
|
||||
|
||||
fileService := initFileService(*flags.Data)
|
||||
encryptionKey := loadEncryptionSecretKey(*flags.SecretKeyName)
|
||||
if encryptionKey == nil {
|
||||
@@ -377,18 +421,17 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
log.Fatal().Err(err).Msg("failed initializing JWT service")
|
||||
}
|
||||
|
||||
ldapService := &ldap.Service{}
|
||||
ldapService := initLDAPService()
|
||||
|
||||
oauthService := oauth.NewService()
|
||||
oauthService := initOAuthService()
|
||||
|
||||
gitService := git.NewService(shutdownCtx)
|
||||
gitService := initGitService(shutdownCtx)
|
||||
|
||||
// Setting insecureSkipVerify to true to preserve the old behaviour.
|
||||
openAMTService := openamt.NewService(true)
|
||||
openAMTService := openamt.NewService()
|
||||
|
||||
cryptoService := &crypto.Service{}
|
||||
cryptoService := initCryptoService()
|
||||
|
||||
signatureService := initDigitalSignatureService()
|
||||
digitalSignatureService := initDigitalSignatureService()
|
||||
|
||||
edgeStacksService := edgestacks.NewService(dataStore)
|
||||
|
||||
@@ -402,18 +445,15 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
log.Fatal().Err(err).Msg("failed to get SSL settings")
|
||||
}
|
||||
|
||||
if err := initKeyPair(fileService, signatureService); err != nil {
|
||||
err = initKeyPair(fileService, digitalSignatureService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing key pair")
|
||||
}
|
||||
|
||||
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx, fileService)
|
||||
|
||||
dockerClientFactory := dockerclient.NewClientFactory(signatureService, reverseTunnelService)
|
||||
|
||||
kubernetesClientFactory, err := kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing Kubernetes Client Factory service")
|
||||
}
|
||||
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
|
||||
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
|
||||
|
||||
authorizationService := authorization.NewService(dataStore)
|
||||
authorizationService.K8sClientFactory = kubernetesClientFactory
|
||||
@@ -428,45 +468,56 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
dockerConfigPath := fileService.GetDockerConfigPath()
|
||||
|
||||
composeDeployer := compose.NewComposeDeployer()
|
||||
composeDeployer, err := compose.NewComposeDeployer(*flags.Assets, dockerConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing compose deployer")
|
||||
}
|
||||
|
||||
composeStackManager := exec.NewComposeStackManager(composeDeployer, proxyManager, dataStore)
|
||||
composeStackManager := initComposeStackManager(composeDeployer, proxyManager)
|
||||
|
||||
swarmStackManager, err := exec.NewSwarmStackManager(*flags.Assets, dockerConfigPath, signatureService, fileService, reverseTunnelService, dataStore)
|
||||
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing swarm stack manager")
|
||||
}
|
||||
|
||||
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager)
|
||||
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
|
||||
|
||||
pendingActionsService := pendingactions.NewService(dataStore, kubernetesClientFactory)
|
||||
pendingActionsService.RegisterHandler(actions.CleanNAPWithOverridePolicies, handlers.NewHandlerCleanNAPWithOverridePolicies(authorizationService, dataStore))
|
||||
pendingActionsService.RegisterHandler(actions.DeletePortainerK8sRegistrySecrets, handlers.NewHandlerDeleteRegistrySecrets(authorizationService, dataStore, kubernetesClientFactory))
|
||||
pendingActionsService.RegisterHandler(actions.PostInitMigrateEnvironment, handlers.NewHandlerPostInitMigrateEnvironment(authorizationService, dataStore, kubernetesClientFactory, dockerClientFactory, *flags.Assets, kubernetesDeployer))
|
||||
pendingActionsService := pendingactions.NewService(dataStore, kubernetesClientFactory, dockerClientFactory, authorizationService, shutdownCtx, *flags.Assets, kubernetesDeployer)
|
||||
|
||||
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx, pendingActionsService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing snapshot service")
|
||||
}
|
||||
|
||||
snapshotService.Start()
|
||||
|
||||
proxyManager.NewProxyFactory(dataStore, signatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService)
|
||||
proxyManager.NewProxyFactory(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService)
|
||||
|
||||
helmPackageManager, err := initHelmPackageManager()
|
||||
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing helm package manager")
|
||||
}
|
||||
|
||||
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed loading edge jobs from database")
|
||||
}
|
||||
|
||||
applicationStatus := initStatus(instanceID)
|
||||
|
||||
demoService := demo.NewService()
|
||||
if *flags.DemoEnvironment {
|
||||
err := demoService.Init(dataStore, cryptoService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing demo environment")
|
||||
}
|
||||
}
|
||||
|
||||
// channel to control when the admin user is created
|
||||
adminCreationDone := make(chan struct{}, 1)
|
||||
|
||||
go endpointutils.InitEndpoint(shutdownCtx, adminCreationDone, flags, dataStore, snapshotService)
|
||||
|
||||
adminPasswordHash := ""
|
||||
|
||||
if *flags.AdminPasswordFile != "" {
|
||||
content, err := fileService.GetFileContent(*flags.AdminPasswordFile, "")
|
||||
if err != nil {
|
||||
@@ -489,14 +540,14 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
if len(users) == 0 {
|
||||
log.Info().Msg("created admin user with the given password.")
|
||||
|
||||
user := &portainer.User{
|
||||
Username: "admin",
|
||||
Role: portainer.AdministratorRole,
|
||||
Password: adminPasswordHash,
|
||||
}
|
||||
|
||||
if err := dataStore.User().Create(user); err != nil {
|
||||
err := dataStore.User().Create(user)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed creating admin user")
|
||||
}
|
||||
|
||||
@@ -507,7 +558,8 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
}
|
||||
}
|
||||
|
||||
if err := reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService); err != nil {
|
||||
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed starting tunnel server")
|
||||
}
|
||||
|
||||
@@ -520,20 +572,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
log.Fatal().Msg("failed to fetch SSL settings from DB")
|
||||
}
|
||||
|
||||
platformService, err := platform.NewService(dataStore)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing platform service")
|
||||
}
|
||||
|
||||
upgradeService, err := upgrade.NewService(
|
||||
*flags.Assets,
|
||||
kubernetesClientFactory,
|
||||
dockerClientFactory,
|
||||
composeStackManager,
|
||||
dataStore,
|
||||
fileService,
|
||||
stackDeployer,
|
||||
)
|
||||
upgradeService, err := upgrade.NewService(*flags.Assets, composeDeployer, kubernetesClientFactory)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing upgrade service")
|
||||
}
|
||||
@@ -559,7 +598,6 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
Status: applicationStatus,
|
||||
BindAddress: *flags.Addr,
|
||||
BindAddressHTTPS: *flags.AddrHTTPS,
|
||||
CSP: *flags.CSP,
|
||||
HTTPEnabled: sslDBSettings.HTTPEnabled,
|
||||
AssetsPath: *flags.Assets,
|
||||
DataStore: dataStore,
|
||||
@@ -579,7 +617,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
ProxyManager: proxyManager,
|
||||
KubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
||||
KubeClusterAccessService: kubeClusterAccessService,
|
||||
SignatureService: signatureService,
|
||||
SignatureService: digitalSignatureService,
|
||||
SnapshotService: snapshotService,
|
||||
SSLService: sslService,
|
||||
DockerClientFactory: dockerClientFactory,
|
||||
@@ -588,27 +626,24 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
ShutdownCtx: shutdownCtx,
|
||||
ShutdownTrigger: shutdownTrigger,
|
||||
StackDeployer: stackDeployer,
|
||||
DemoService: demoService,
|
||||
UpgradeService: upgradeService,
|
||||
AdminCreationDone: adminCreationDone,
|
||||
PendingActionsService: pendingActionsService,
|
||||
PlatformService: platformService,
|
||||
PullLimitCheckDisabled: *flags.PullLimitCheckDisabled,
|
||||
TrustedOrigins: trustedOrigins,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
logs.ConfigureLogger()
|
||||
logs.SetLoggingMode("PRETTY")
|
||||
configureLogger()
|
||||
setLoggingMode("PRETTY")
|
||||
|
||||
flags := initCLI()
|
||||
|
||||
logs.SetLoggingLevel(*flags.LogLevel)
|
||||
logs.SetLoggingMode(*flags.LogMode)
|
||||
setLoggingLevel(*flags.LogLevel)
|
||||
setLoggingMode(*flags.LogMode)
|
||||
|
||||
for {
|
||||
server := buildServer(flags)
|
||||
|
||||
log.Info().
|
||||
Str("version", portainer.APIVersion).
|
||||
Str("build_number", build.BuildNumber).
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
// Package concurrent provides utilities for running multiple functions concurrently in Go.
|
||||
// For example, many kubernetes calls can take a while to fulfill. Oftentimes in Portainer
|
||||
// we need to get a list of objects from multiple kubernetes REST APIs. We can often call these
|
||||
// apis concurrently to speed up the response time.
|
||||
// This package provides a clean way to do just that.
|
||||
//
|
||||
// Examples:
|
||||
// The ConfigMaps and Secrets function converted using concurrent.Run.
|
||||
/*
|
||||
|
||||
// GetConfigMapsAndSecrets gets all the ConfigMaps AND all the Secrets for a
|
||||
// given namespace in a k8s endpoint. The result is a list of both config maps
|
||||
// and secrets. The IsSecret boolean property indicates if a given struct is a
|
||||
// secret or configmap.
|
||||
func (kcl *KubeClient) GetConfigMapsAndSecrets(namespace string) ([]models.K8sConfigMapOrSecret, error) {
|
||||
|
||||
// use closures to capture the current kube client and namespace by declaring wrapper functions
|
||||
// that match the interface signature for concurrent.Func
|
||||
|
||||
listConfigMaps := func(ctx context.Context) (any, error) {
|
||||
return kcl.cli.CoreV1().ConfigMaps(namespace).List(context.Background(), meta.ListOptions{})
|
||||
}
|
||||
|
||||
listSecrets := func(ctx context.Context) (any, error) {
|
||||
return kcl.cli.CoreV1().Secrets(namespace).List(context.Background(), meta.ListOptions{})
|
||||
}
|
||||
|
||||
// run the functions concurrently and wait for results. We can also pass in a context to cancel.
|
||||
// e.g. Deadline timer.
|
||||
results, err := concurrent.Run(context.TODO(), listConfigMaps, listSecrets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var configMapList *core.ConfigMapList
|
||||
var secretList *core.SecretList
|
||||
for _, r := range results {
|
||||
switch v := r.Result.(type) {
|
||||
case *core.ConfigMapList:
|
||||
configMapList = v
|
||||
case *core.SecretList:
|
||||
secretList = v
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Applications
|
||||
var combined []models.K8sConfigMapOrSecret
|
||||
for _, m := range configMapList.Items {
|
||||
var cm models.K8sConfigMapOrSecret
|
||||
cm.UID = string(m.UID)
|
||||
cm.Name = m.Name
|
||||
cm.Namespace = m.Namespace
|
||||
cm.Annotations = m.Annotations
|
||||
cm.Data = m.Data
|
||||
cm.CreationDate = m.CreationTimestamp.Time.UTC().Format(time.RFC3339)
|
||||
combined = append(combined, cm)
|
||||
}
|
||||
|
||||
for _, s := range secretList.Items {
|
||||
var secret models.K8sConfigMapOrSecret
|
||||
secret.UID = string(s.UID)
|
||||
secret.Name = s.Name
|
||||
secret.Namespace = s.Namespace
|
||||
secret.Annotations = s.Annotations
|
||||
secret.Data = msbToMss(s.Data)
|
||||
secret.CreationDate = s.CreationTimestamp.Time.UTC().Format(time.RFC3339)
|
||||
secret.IsSecret = true
|
||||
secret.SecretType = string(s.Type)
|
||||
combined = append(combined, secret)
|
||||
}
|
||||
|
||||
return combined, nil
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
package concurrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Result contains the result and any error returned from running a client task function
|
||||
type Result struct {
|
||||
Result any // the result of running the task function
|
||||
Err error // any error that occurred while running the task function
|
||||
}
|
||||
|
||||
// Func is a function returns a result or error
|
||||
type Func func(ctx context.Context) (any, error)
|
||||
|
||||
// Run runs a list of functions returns the results
|
||||
func Run(ctx context.Context, maxConcurrency int, tasks ...Func) ([]Result, error) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
resultsChan := make(chan Result, len(tasks))
|
||||
taskChan := make(chan Func, len(tasks))
|
||||
|
||||
localCtx, cancelCtx := context.WithCancel(ctx)
|
||||
defer cancelCtx()
|
||||
|
||||
runTask := func() {
|
||||
defer wg.Done()
|
||||
|
||||
for fn := range taskChan {
|
||||
result, err := fn(localCtx)
|
||||
resultsChan <- Result{Result: result, Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
// Set maxConcurrency to the number of tasks if zero or negative
|
||||
if maxConcurrency <= 0 {
|
||||
maxConcurrency = len(tasks)
|
||||
}
|
||||
|
||||
// Start worker goroutines
|
||||
for range maxConcurrency {
|
||||
wg.Add(1)
|
||||
go runTask()
|
||||
}
|
||||
|
||||
// Add tasks to the task channel
|
||||
for _, fn := range tasks {
|
||||
taskChan <- fn
|
||||
}
|
||||
|
||||
// Close the task channel to signal workers to stop when all tasks are done
|
||||
close(taskChan)
|
||||
|
||||
// Wait for all workers to complete
|
||||
wg.Wait()
|
||||
close(resultsChan)
|
||||
|
||||
// Collect the results and cancel on error
|
||||
results := make([]Result, 0, len(tasks))
|
||||
for r := range resultsChan {
|
||||
if r.Err != nil {
|
||||
cancelCtx()
|
||||
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
results = append(results, r)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
+11
-13
@@ -5,23 +5,22 @@ import (
|
||||
)
|
||||
|
||||
type ReadTransaction interface {
|
||||
GetObject(bucketName string, key []byte, object any) error
|
||||
GetRawBytes(bucketName string, key []byte) ([]byte, error)
|
||||
GetAll(bucketName string, obj any, append func(o any) (any, error)) error
|
||||
GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, append func(o any) (any, error)) error
|
||||
KeyExists(bucketName string, key []byte) (bool, error)
|
||||
GetObject(bucketName string, key []byte, object interface{}) error
|
||||
GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
}
|
||||
|
||||
type Transaction interface {
|
||||
ReadTransaction
|
||||
|
||||
SetServiceName(bucketName string) error
|
||||
UpdateObject(bucketName string, key []byte, object any) error
|
||||
UpdateObject(bucketName string, key []byte, object interface{}) error
|
||||
DeleteObject(bucketName string, key []byte) error
|
||||
CreateObject(bucketName string, fn func(uint64) (int, any)) error
|
||||
CreateObjectWithId(bucketName string, id int, obj any) error
|
||||
CreateObjectWithStringId(bucketName string, id []byte, obj any) error
|
||||
DeleteAllObjects(bucketName string, obj any, matching func(o any) (id int, ok bool)) error
|
||||
CreateObject(bucketName string, fn func(uint64) (int, interface{})) error
|
||||
CreateObjectWithId(bucketName string, id int, obj interface{}) error
|
||||
CreateObjectWithStringId(bucketName string, id []byte, obj interface{}) error
|
||||
DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error
|
||||
GetNextIdentifier(bucketName string) int
|
||||
}
|
||||
|
||||
@@ -42,14 +41,13 @@ type Connection interface {
|
||||
GetDatabaseFileName() string
|
||||
GetDatabaseFilePath() string
|
||||
GetStorePath() string
|
||||
GetDatabaseFileSize() (int64, error)
|
||||
|
||||
IsEncryptedStore() bool
|
||||
NeedsEncryptionMigration() (bool, error)
|
||||
SetEncrypted(encrypted bool)
|
||||
|
||||
BackupMetadata() (map[string]any, error)
|
||||
RestoreMetadata(s map[string]any) error
|
||||
BackupMetadata() (map[string]interface{}, error)
|
||||
RestoreMetadata(s map[string]interface{}) error
|
||||
|
||||
UpdateObjectFunc(bucketName string, key []byte, object any, updateFn func()) error
|
||||
ConvertToKey(v int) []byte
|
||||
|
||||
+5
-3
@@ -31,7 +31,8 @@ const (
|
||||
|
||||
// AesEncrypt reads from input, encrypts with AES-256 and writes to output. passphrase is used to generate an encryption key
|
||||
func AesEncrypt(input io.Reader, output io.Writer, passphrase []byte) error {
|
||||
if err := aesEncryptGCM(input, output, passphrase); err != nil {
|
||||
err := aesEncryptGCM(input, output, passphrase)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encrypting file: %w", err)
|
||||
}
|
||||
|
||||
@@ -141,7 +142,7 @@ func aesDecryptGCM(input io.Reader, passphrase []byte) (io.Reader, error) {
|
||||
}
|
||||
|
||||
if string(header) != aesGcmHeader {
|
||||
return nil, errors.New("invalid header")
|
||||
return nil, fmt.Errorf("invalid header")
|
||||
}
|
||||
|
||||
// Read salt
|
||||
@@ -193,7 +194,8 @@ func aesDecryptGCM(input io.Reader, passphrase []byte) (io.Reader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := buf.Write(plaintext); err != nil {
|
||||
_, err = buf.Write(plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
+26
-59
@@ -8,7 +8,6 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
@@ -62,15 +61,6 @@ func (connection *DbConnection) GetStorePath() string {
|
||||
return connection.Path
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetDatabaseFileSize() (int64, error) {
|
||||
file, err := os.Stat(connection.GetDatabaseFilePath())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Failed to stat database file path: %s err: %w", connection.GetDatabaseFilePath(), err)
|
||||
}
|
||||
|
||||
return file.Size(), nil
|
||||
}
|
||||
|
||||
func (connection *DbConnection) SetEncrypted(flag bool) {
|
||||
connection.isEncrypted = flag
|
||||
}
|
||||
@@ -83,6 +73,7 @@ func (connection *DbConnection) IsEncryptedStore() bool {
|
||||
// NeedsEncryptionMigration returns true if database encryption is enabled and
|
||||
// we have an un-encrypted DB that requires migration to an encrypted DB
|
||||
func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
|
||||
|
||||
// Cases: Note, we need to check both portainer.db and portainer.edb
|
||||
// to determine if it's a new store. We only need to differentiate between cases 2,3 and 5
|
||||
|
||||
@@ -130,16 +121,14 @@ func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
|
||||
|
||||
// Open opens and initializes the BoltDB database.
|
||||
func (connection *DbConnection) Open() error {
|
||||
|
||||
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
|
||||
|
||||
// Now we open the db
|
||||
databasePath := connection.GetDatabaseFilePath()
|
||||
|
||||
db, err := bolt.Open(databasePath, 0600, &bolt.Options{
|
||||
Timeout: 1 * time.Second,
|
||||
InitialMmapSize: connection.InitialMmapSize,
|
||||
FreelistType: bolt.FreelistMapType,
|
||||
NoFreelistSync: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -189,7 +178,6 @@ func (connection *DbConnection) ViewTx(fn func(portainer.Transaction) error) err
|
||||
func (connection *DbConnection) BackupTo(w io.Writer) error {
|
||||
return connection.View(func(tx *bolt.Tx) error {
|
||||
_, err := tx.WriteTo(w)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -204,7 +192,6 @@ func (connection *DbConnection) ExportRaw(filename string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filename, b, 0600)
|
||||
}
|
||||
|
||||
@@ -214,7 +201,6 @@ func (connection *DbConnection) ExportRaw(filename string) error {
|
||||
func (connection *DbConnection) ConvertToKey(v int) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(v))
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -226,7 +212,7 @@ func keyToString(b []byte) string {
|
||||
|
||||
v := binary.BigEndian.Uint64(b)
|
||||
if v <= math.MaxInt32 {
|
||||
return strconv.FormatUint(v, 10)
|
||||
return fmt.Sprintf("%d", v)
|
||||
}
|
||||
|
||||
return string(b)
|
||||
@@ -240,38 +226,12 @@ func (connection *DbConnection) SetServiceName(bucketName string) error {
|
||||
}
|
||||
|
||||
// GetObject is a generic function used to retrieve an unmarshalled object from a database.
|
||||
func (connection *DbConnection) GetObject(bucketName string, key []byte, object any) error {
|
||||
func (connection *DbConnection) GetObject(bucketName string, key []byte, object interface{}) error {
|
||||
return connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
return tx.GetObject(bucketName, key, object)
|
||||
})
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetRawBytes(bucketName string, key []byte) ([]byte, error) {
|
||||
var value []byte
|
||||
|
||||
err := connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
var err error
|
||||
value, err = tx.GetRawBytes(bucketName, key)
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (connection *DbConnection) KeyExists(bucketName string, key []byte) (bool, error) {
|
||||
var exists bool
|
||||
|
||||
err := connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
var err error
|
||||
exists, err = tx.KeyExists(bucketName, key)
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return exists, err
|
||||
}
|
||||
|
||||
func (connection *DbConnection) getEncryptionKey() []byte {
|
||||
if !connection.isEncrypted {
|
||||
return nil
|
||||
@@ -281,7 +241,7 @@ func (connection *DbConnection) getEncryptionKey() []byte {
|
||||
}
|
||||
|
||||
// UpdateObject is a generic function used to update an object inside a database.
|
||||
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object any) error {
|
||||
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object interface{}) error {
|
||||
return connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return tx.UpdateObject(bucketName, key, object)
|
||||
})
|
||||
@@ -322,7 +282,7 @@ func (connection *DbConnection) DeleteObject(bucketName string, key []byte) erro
|
||||
|
||||
// DeleteAllObjects delete all objects where matching() returns (id, ok).
|
||||
// TODO: think about how to return the error inside (maybe change ok to type err, and use "notfound"?
|
||||
func (connection *DbConnection) DeleteAllObjects(bucketName string, obj any, matching func(o any) (id int, ok bool)) error {
|
||||
func (connection *DbConnection) DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error {
|
||||
return connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return tx.DeleteAllObjects(bucketName, obj, matching)
|
||||
})
|
||||
@@ -341,64 +301,71 @@ func (connection *DbConnection) GetNextIdentifier(bucketName string) int {
|
||||
}
|
||||
|
||||
// CreateObject creates a new object in the bucket, using the next bucket sequence id
|
||||
func (connection *DbConnection) CreateObject(bucketName string, fn func(uint64) (int, any)) error {
|
||||
func (connection *DbConnection) CreateObject(bucketName string, fn func(uint64) (int, interface{})) error {
|
||||
return connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return tx.CreateObject(bucketName, fn)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateObjectWithId creates a new object in the bucket, using the specified id
|
||||
func (connection *DbConnection) CreateObjectWithId(bucketName string, id int, obj any) error {
|
||||
func (connection *DbConnection) CreateObjectWithId(bucketName string, id int, obj interface{}) error {
|
||||
return connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return tx.CreateObjectWithId(bucketName, id, obj)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateObjectWithStringId creates a new object in the bucket, using the specified id
|
||||
func (connection *DbConnection) CreateObjectWithStringId(bucketName string, id []byte, obj any) error {
|
||||
func (connection *DbConnection) CreateObjectWithStringId(bucketName string, id []byte, obj interface{}) error {
|
||||
return connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return tx.CreateObjectWithStringId(bucketName, id, obj)
|
||||
})
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetAll(bucketName string, obj any, appendFn func(o any) (any, error)) error {
|
||||
func (connection *DbConnection) GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||
return connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
return tx.GetAll(bucketName, obj, appendFn)
|
||||
return tx.GetAll(bucketName, obj, append)
|
||||
})
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, appendFn func(o any) (any, error)) error {
|
||||
// TODO: decide which Unmarshal to use, and use one...
|
||||
func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||
return connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
return tx.GetAllWithKeyPrefix(bucketName, keyPrefix, obj, appendFn)
|
||||
return tx.GetAllWithJsoniter(bucketName, obj, append)
|
||||
})
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||
return connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
return tx.GetAllWithKeyPrefix(bucketName, keyPrefix, obj, append)
|
||||
})
|
||||
}
|
||||
|
||||
// BackupMetadata will return a copy of the boltdb sequence numbers for all buckets.
|
||||
func (connection *DbConnection) BackupMetadata() (map[string]any, error) {
|
||||
buckets := map[string]any{}
|
||||
func (connection *DbConnection) BackupMetadata() (map[string]interface{}, error) {
|
||||
buckets := map[string]interface{}{}
|
||||
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
return tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
bucketName := string(name)
|
||||
seqId := bucket.Sequence()
|
||||
buckets[bucketName] = int(seqId)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return buckets, err
|
||||
}
|
||||
|
||||
// RestoreMetadata will restore the boltdb sequence numbers for all buckets.
|
||||
func (connection *DbConnection) RestoreMetadata(s map[string]any) error {
|
||||
func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error {
|
||||
var err error
|
||||
|
||||
for bucketName, v := range s {
|
||||
id, ok := v.(float64) // JSON ints are unmarshalled to interface as float64. See: https://pkg.go.dev/encoding/json#Decoder.Decode
|
||||
if !ok {
|
||||
log.Error().Str("bucket", bucketName).Msg("failed to restore metadata to bucket, skipped")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,10 @@ func Test_NeedsEncryptionMigration(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
connection := DbConnection{Path: dir}
|
||||
|
||||
if tc.dbname == "both" {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
func backupMetadata(connection *bolt.DB) (map[string]any, error) {
|
||||
buckets := map[string]any{}
|
||||
func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) {
|
||||
buckets := map[string]interface{}{}
|
||||
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
@@ -39,7 +39,7 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
backup := make(map[string]any)
|
||||
backup := make(map[string]interface{})
|
||||
if metadata {
|
||||
meta, err := backupMetadata(connection)
|
||||
if err != nil {
|
||||
@@ -49,10 +49,10 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
|
||||
backup["__metadata"] = meta
|
||||
}
|
||||
|
||||
if err := connection.View(func(tx *bolt.Tx) error {
|
||||
return tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
err = connection.View(func(tx *bolt.Tx) error {
|
||||
err = tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
bucketName := string(name)
|
||||
var list []any
|
||||
var list []interface{}
|
||||
version := make(map[string]string)
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
@@ -60,7 +60,7 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
|
||||
continue
|
||||
}
|
||||
|
||||
var obj any
|
||||
var obj interface{}
|
||||
err := c.UnmarshalObject(v, &obj)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
@@ -84,22 +84,27 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
|
||||
return nil
|
||||
}
|
||||
|
||||
if bucketName == "ssl" ||
|
||||
bucketName == "settings" ||
|
||||
bucketName == "tunnel_server" {
|
||||
backup[bucketName] = nil
|
||||
if len(list) > 0 {
|
||||
backup[bucketName] = list[0]
|
||||
if len(list) > 0 {
|
||||
if bucketName == "ssl" ||
|
||||
bucketName == "settings" ||
|
||||
bucketName == "tunnel_server" {
|
||||
backup[bucketName] = nil
|
||||
if len(list) > 0 {
|
||||
backup[bucketName] = list[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
backup[bucketName] = list
|
||||
return nil
|
||||
}
|
||||
|
||||
backup[bucketName] = list
|
||||
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return []byte("{}"), err
|
||||
}
|
||||
|
||||
|
||||
+18
-13
@@ -5,16 +5,17 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/segmentio/encoding/json"
|
||||
)
|
||||
|
||||
var errEncryptedStringTooShort = errors.New("encrypted string too short")
|
||||
var errEncryptedStringTooShort = fmt.Errorf("encrypted string too short")
|
||||
|
||||
// MarshalObject encodes an object to binary format
|
||||
func (connection *DbConnection) MarshalObject(object any) ([]byte, error) {
|
||||
func (connection *DbConnection) MarshalObject(object interface{}) ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Special case for the VERSION bucket. Here we're not using json
|
||||
@@ -38,7 +39,7 @@ func (connection *DbConnection) MarshalObject(object any) ([]byte, error) {
|
||||
}
|
||||
|
||||
// UnmarshalObject decodes an object from binary data
|
||||
func (connection *DbConnection) UnmarshalObject(data []byte, object any) error {
|
||||
func (connection *DbConnection) UnmarshalObject(data []byte, object interface{}) error {
|
||||
var err error
|
||||
if connection.getEncryptionKey() != nil {
|
||||
data, err = decrypt(data, connection.getEncryptionKey())
|
||||
@@ -46,8 +47,8 @@ func (connection *DbConnection) UnmarshalObject(data []byte, object any) error {
|
||||
return errors.Wrap(err, "Failed decrypting object")
|
||||
}
|
||||
}
|
||||
|
||||
if e := json.Unmarshal(data, object); e != nil {
|
||||
e := json.Unmarshal(data, object)
|
||||
if e != nil {
|
||||
// Special case for the VERSION bucket. Here we're not using json
|
||||
// So we need to return it as a string
|
||||
s, ok := object.(*string)
|
||||
@@ -57,7 +58,6 @@ func (connection *DbConnection) UnmarshalObject(data []byte, object any) error {
|
||||
|
||||
*s = string(data)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -70,20 +70,22 @@ func encrypt(plaintext []byte, passphrase []byte) (encrypted []byte, err error)
|
||||
if err != nil {
|
||||
return encrypted, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return encrypted, err
|
||||
}
|
||||
|
||||
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
||||
ciphertextByte := gcm.Seal(
|
||||
nonce,
|
||||
nonce,
|
||||
plaintext,
|
||||
nil)
|
||||
return ciphertextByte, nil
|
||||
}
|
||||
|
||||
func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err error) {
|
||||
if string(encrypted) == "false" {
|
||||
return []byte("false"), nil
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(passphrase)
|
||||
if err != nil {
|
||||
return encrypted, errors.Wrap(err, "Error creating cypher block")
|
||||
@@ -100,8 +102,11 @@ func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err err
|
||||
}
|
||||
|
||||
nonce, ciphertextByteClean := encrypted[:nonceSize], encrypted[nonceSize:]
|
||||
|
||||
plaintextByte, err = gcm.Open(nil, nonce, ciphertextByteClean, nil)
|
||||
plaintextByte, err = gcm.Open(
|
||||
nil,
|
||||
nonce,
|
||||
ciphertextByteClean,
|
||||
nil)
|
||||
if err != nil {
|
||||
return encrypted, errors.Wrap(err, "Error decrypting text")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func Test_MarshalObjectUnencrypted(t *testing.T) {
|
||||
uuid := uuid.Must(uuid.NewV4())
|
||||
|
||||
tests := []struct {
|
||||
object any
|
||||
object interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
@@ -57,7 +57,7 @@ func Test_MarshalObjectUnencrypted(t *testing.T) {
|
||||
expected: uuid.String(),
|
||||
},
|
||||
{
|
||||
object: map[string]any{"key": "value"},
|
||||
object: map[string]interface{}{"key": "value"},
|
||||
expected: `{"key":"value"}`,
|
||||
},
|
||||
{
|
||||
@@ -73,11 +73,11 @@ func Test_MarshalObjectUnencrypted(t *testing.T) {
|
||||
expected: `["1","2","3"]`,
|
||||
},
|
||||
{
|
||||
object: []map[string]any{{"key1": "value1"}, {"key2": "value2"}},
|
||||
object: []map[string]interface{}{{"key1": "value1"}, {"key2": "value2"}},
|
||||
expected: `[{"key1":"value1"},{"key2":"value2"}]`,
|
||||
},
|
||||
{
|
||||
object: []any{1, "2", false, map[string]any{"key1": "value1"}},
|
||||
object: []interface{}{1, "2", false, map[string]interface{}{"key1": "value1"}},
|
||||
expected: `[1,"2",false,{"key1":"value1"}]`,
|
||||
},
|
||||
}
|
||||
|
||||
+22
-39
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
@@ -21,7 +20,7 @@ func (tx *DbTransaction) SetServiceName(bucketName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) GetObject(bucketName string, key []byte, object any) error {
|
||||
func (tx *DbTransaction) GetObject(bucketName string, key []byte, object interface{}) error {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
|
||||
value := bucket.Get(key)
|
||||
@@ -32,34 +31,7 @@ func (tx *DbTransaction) GetObject(bucketName string, key []byte, object any) er
|
||||
return tx.conn.UnmarshalObject(value, object)
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) GetRawBytes(bucketName string, key []byte) ([]byte, error) {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
|
||||
value := bucket.Get(key)
|
||||
if value == nil {
|
||||
return nil, fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
|
||||
}
|
||||
|
||||
if tx.conn.getEncryptionKey() != nil {
|
||||
var err error
|
||||
|
||||
if value, err = decrypt(value, tx.conn.getEncryptionKey()); err != nil {
|
||||
return value, errors.Wrap(err, "Failed decrypting object")
|
||||
}
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) KeyExists(bucketName string, key []byte) (bool, error) {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
|
||||
value := bucket.Get(key)
|
||||
|
||||
return value != nil, nil
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) UpdateObject(bucketName string, key []byte, object any) error {
|
||||
func (tx *DbTransaction) UpdateObject(bucketName string, key []byte, object interface{}) error {
|
||||
data, err := tx.conn.MarshalObject(object)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -74,7 +46,7 @@ func (tx *DbTransaction) DeleteObject(bucketName string, key []byte) error {
|
||||
return bucket.Delete(key)
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj any, matchingFn func(o any) (id int, ok bool)) error {
|
||||
func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj interface{}, matchingFn func(o interface{}) (id int, ok bool)) error {
|
||||
var ids []int
|
||||
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
@@ -102,18 +74,16 @@ func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj any, matchingFn
|
||||
|
||||
func (tx *DbTransaction) GetNextIdentifier(bucketName string) int {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
|
||||
id, err := bucket.NextSequence()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("bucket", bucketName).Msg("failed to get the next identifier")
|
||||
|
||||
log.Error().Err(err).Str("bucket", bucketName).Msg("failed to get the next identifer")
|
||||
return 0
|
||||
}
|
||||
|
||||
return int(id)
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) CreateObject(bucketName string, fn func(uint64) (int, any)) error {
|
||||
func (tx *DbTransaction) CreateObject(bucketName string, fn func(uint64) (int, interface{})) error {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
|
||||
seqId, _ := bucket.NextSequence()
|
||||
@@ -127,7 +97,7 @@ func (tx *DbTransaction) CreateObject(bucketName string, fn func(uint64) (int, a
|
||||
return bucket.Put(tx.conn.ConvertToKey(id), data)
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) CreateObjectWithId(bucketName string, id int, obj any) error {
|
||||
func (tx *DbTransaction) CreateObjectWithId(bucketName string, id int, obj interface{}) error {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
data, err := tx.conn.MarshalObject(obj)
|
||||
if err != nil {
|
||||
@@ -137,7 +107,7 @@ func (tx *DbTransaction) CreateObjectWithId(bucketName string, id int, obj any)
|
||||
return bucket.Put(tx.conn.ConvertToKey(id), data)
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) CreateObjectWithStringId(bucketName string, id []byte, obj any) error {
|
||||
func (tx *DbTransaction) CreateObjectWithStringId(bucketName string, id []byte, obj interface{}) error {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
data, err := tx.conn.MarshalObject(obj)
|
||||
if err != nil {
|
||||
@@ -147,7 +117,7 @@ func (tx *DbTransaction) CreateObjectWithStringId(bucketName string, id []byte,
|
||||
return bucket.Put(id, data)
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) GetAll(bucketName string, obj any, appendFn func(o any) (any, error)) error {
|
||||
func (tx *DbTransaction) GetAll(bucketName string, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
|
||||
return bucket.ForEach(func(k []byte, v []byte) error {
|
||||
@@ -160,7 +130,20 @@ func (tx *DbTransaction) GetAll(bucketName string, obj any, appendFn func(o any)
|
||||
})
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, appendFn func(o any) (any, error)) error {
|
||||
func (tx *DbTransaction) GetAllWithJsoniter(bucketName string, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
|
||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||
|
||||
return bucket.ForEach(func(k []byte, v []byte) error {
|
||||
err := tx.conn.UnmarshalObject(v, obj)
|
||||
if err == nil {
|
||||
obj, err = appendFn(obj)
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (tx *DbTransaction) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
|
||||
cursor := tx.tx.Bucket([]byte(bucketName)).Cursor()
|
||||
|
||||
for k, v := cursor.Seek(keyPrefix); k != nil && bytes.HasPrefix(k, keyPrefix); k, v = cursor.Next() {
|
||||
|
||||
@@ -21,7 +21,8 @@ type Service struct {
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
if err := connection.SetServiceName(BucketName); err != nil {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer
|
||||
err := service.Connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.APIKey{},
|
||||
func(obj any) (any, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
record, ok := obj.(*portainer.APIKey)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
|
||||
@@ -61,11 +62,11 @@ func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer
|
||||
// Note: there is a 1-to-1 mapping of api-key and digest
|
||||
func (service *Service) GetAPIKeyByDigest(digest string) (*portainer.APIKey, error) {
|
||||
var k *portainer.APIKey
|
||||
stop := errors.New("ok")
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.Connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.APIKey{},
|
||||
func(obj any) (any, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
key, ok := obj.(*portainer.APIKey)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
|
||||
@@ -94,7 +95,7 @@ func (service *Service) GetAPIKeyByDigest(digest string) (*portainer.APIKey, err
|
||||
func (service *Service) Create(record *portainer.APIKey) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
record.ID = portainer.APIKeyID(id)
|
||||
|
||||
return int(record.ID), record
|
||||
|
||||
@@ -9,8 +9,7 @@ import (
|
||||
type BaseCRUD[T any, I constraints.Integer] interface {
|
||||
Create(element *T) error
|
||||
Read(ID I) (*T, error)
|
||||
Exists(ID I) (bool, error)
|
||||
ReadAll(predicates ...func(T) bool) ([]T, error)
|
||||
ReadAll() ([]T, error)
|
||||
Update(ID I, element *T) error
|
||||
Delete(ID I) error
|
||||
}
|
||||
@@ -43,26 +42,12 @@ func (service BaseDataService[T, I]) Read(ID I) (*T, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (service BaseDataService[T, I]) Exists(ID I) (bool, error) {
|
||||
var exists bool
|
||||
|
||||
err := service.Connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
var err error
|
||||
exists, err = service.Tx(tx).Exists(ID)
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// ReadAll retrieves all the elements that satisfy all the provided predicates.
|
||||
func (service BaseDataService[T, I]) ReadAll(predicates ...func(T) bool) ([]T, error) {
|
||||
func (service BaseDataService[T, I]) ReadAll() ([]T, error) {
|
||||
var collection = make([]T, 0)
|
||||
|
||||
return collection, service.Connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
var err error
|
||||
collection, err = service.Tx(tx).ReadAll(predicates...)
|
||||
collection, err = service.Tx(tx).ReadAll()
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package dataservices
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/slicesx"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testObject struct {
|
||||
ID int
|
||||
Value int
|
||||
}
|
||||
|
||||
type mockConnection struct {
|
||||
store map[int]testObject
|
||||
|
||||
portainer.Connection
|
||||
}
|
||||
|
||||
func (m mockConnection) UpdateObject(bucket string, key []byte, value interface{}) error {
|
||||
obj := value.(*testObject)
|
||||
|
||||
m.store[obj.ID] = *obj
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockConnection) GetAll(bucketName string, obj any, appendFn func(o any) (any, error)) error {
|
||||
for _, v := range m.store {
|
||||
if _, err := appendFn(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockConnection) UpdateTx(fn func(portainer.Transaction) error) error {
|
||||
return fn(m)
|
||||
}
|
||||
|
||||
func (m mockConnection) ViewTx(fn func(portainer.Transaction) error) error {
|
||||
return fn(m)
|
||||
}
|
||||
|
||||
func (m mockConnection) ConvertToKey(v int) []byte {
|
||||
return []byte(strconv.Itoa(v))
|
||||
}
|
||||
|
||||
func TestReadAll(t *testing.T) {
|
||||
service := BaseDataService[testObject, int]{
|
||||
Bucket: "testBucket",
|
||||
Connection: mockConnection{store: make(map[int]testObject)},
|
||||
}
|
||||
|
||||
data := []testObject{
|
||||
{ID: 1, Value: 1},
|
||||
{ID: 2, Value: 2},
|
||||
{ID: 3, Value: 3},
|
||||
{ID: 4, Value: 4},
|
||||
{ID: 5, Value: 5},
|
||||
}
|
||||
|
||||
for _, item := range data {
|
||||
err := service.Update(item.ID, &item)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// ReadAll without predicates
|
||||
result, err := service.ReadAll()
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := append([]testObject{}, data...)
|
||||
|
||||
require.ElementsMatch(t, expected, result)
|
||||
|
||||
// ReadAll with predicates
|
||||
hasLowID := func(obj testObject) bool { return obj.ID < 3 }
|
||||
isEven := func(obj testObject) bool { return obj.Value%2 == 0 }
|
||||
|
||||
result, err = service.ReadAll(hasLowID, isEven)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected = slicesx.Filter(expected, hasLowID)
|
||||
expected = slicesx.Filter(expected, isEven)
|
||||
|
||||
require.ElementsMatch(t, expected, result)
|
||||
}
|
||||
@@ -28,38 +28,13 @@ func (service BaseDataServiceTx[T, I]) Read(ID I) (*T, error) {
|
||||
return &element, nil
|
||||
}
|
||||
|
||||
func (service BaseDataServiceTx[T, I]) Exists(ID I) (bool, error) {
|
||||
identifier := service.Connection.ConvertToKey(int(ID))
|
||||
|
||||
return service.Tx.KeyExists(service.Bucket, identifier)
|
||||
}
|
||||
|
||||
// ReadAll retrieves all the elements that satisfy all the provided predicates.
|
||||
func (service BaseDataServiceTx[T, I]) ReadAll(predicates ...func(T) bool) ([]T, error) {
|
||||
func (service BaseDataServiceTx[T, I]) ReadAll() ([]T, error) {
|
||||
var collection = make([]T, 0)
|
||||
|
||||
if len(predicates) == 0 {
|
||||
return collection, service.Tx.GetAll(
|
||||
service.Bucket,
|
||||
new(T),
|
||||
AppendFn(&collection),
|
||||
)
|
||||
}
|
||||
|
||||
filterFn := func(element T) bool {
|
||||
for _, p := range predicates {
|
||||
if !p(element) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return collection, service.Tx.GetAll(
|
||||
return collection, service.Tx.GetAllWithJsoniter(
|
||||
service.Bucket,
|
||||
new(T),
|
||||
FilterFn(&collection, filterFn),
|
||||
AppendFn(&collection),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ func (service ServiceTx) UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFun
|
||||
func (service ServiceTx) Create(group *portainer.EdgeGroup) error {
|
||||
return service.Tx.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
group.ID = portainer.EdgeGroupID(id)
|
||||
return int(group.ID), group
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ type Service struct {
|
||||
connection portainer.Connection
|
||||
idxVersion map[portainer.EdgeStackID]int
|
||||
mu sync.RWMutex
|
||||
cacheInvalidationFn func(portainer.Transaction, portainer.EdgeStackID)
|
||||
cacheInvalidationFn func(portainer.EdgeStackID)
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
@@ -23,7 +23,7 @@ func (service *Service) BucketName() string {
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection, cacheInvalidationFn func(portainer.Transaction, portainer.EdgeStackID)) (*Service, error) {
|
||||
func NewService(connection portainer.Connection, cacheInvalidationFn func(portainer.EdgeStackID)) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -36,7 +36,7 @@ func NewService(connection portainer.Connection, cacheInvalidationFn func(portai
|
||||
}
|
||||
|
||||
if s.cacheInvalidationFn == nil {
|
||||
s.cacheInvalidationFn = func(portainer.Transaction, portainer.EdgeStackID) {}
|
||||
s.cacheInvalidationFn = func(portainer.EdgeStackID) {}
|
||||
}
|
||||
|
||||
es, err := s.EdgeStacks()
|
||||
@@ -106,7 +106,7 @@ func (service *Service) Create(id portainer.EdgeStackID, edgeStack *portainer.Ed
|
||||
|
||||
service.mu.Lock()
|
||||
service.idxVersion[id] = edgeStack.Version
|
||||
service.cacheInvalidationFn(service.connection, id)
|
||||
service.cacheInvalidationFn(id)
|
||||
service.mu.Unlock()
|
||||
|
||||
return nil
|
||||
@@ -125,7 +125,7 @@ func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *por
|
||||
}
|
||||
|
||||
service.idxVersion[ID] = edgeStack.Version
|
||||
service.cacheInvalidationFn(service.connection, ID)
|
||||
service.cacheInvalidationFn(ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func (service *Service) UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFunc
|
||||
updateFunc(edgeStack)
|
||||
|
||||
service.idxVersion[ID] = edgeStack.Version
|
||||
service.cacheInvalidationFn(service.connection, ID)
|
||||
service.cacheInvalidationFn(ID)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
||||
|
||||
delete(service.idxVersion, ID)
|
||||
|
||||
service.cacheInvalidationFn(service.connection, ID)
|
||||
service.cacheInvalidationFn(ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func (service ServiceTx) EdgeStacks() ([]portainer.EdgeStack, error) {
|
||||
err := service.tx.GetAll(
|
||||
BucketName,
|
||||
&portainer.EdgeStack{},
|
||||
func(obj any) (any, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(*portainer.EdgeStack)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object")
|
||||
@@ -44,7 +44,8 @@ func (service ServiceTx) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeSta
|
||||
var stack portainer.EdgeStack
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
if err := service.tx.GetObject(BucketName, identifier, &stack); err != nil {
|
||||
err := service.tx.GetObject(BucketName, identifier, &stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -64,17 +65,18 @@ func (service ServiceTx) EdgeStackVersion(ID portainer.EdgeStackID) (int, bool)
|
||||
func (service ServiceTx) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||
edgeStack.ID = id
|
||||
|
||||
if err := service.tx.CreateObjectWithId(
|
||||
err := service.tx.CreateObjectWithId(
|
||||
BucketName,
|
||||
int(edgeStack.ID),
|
||||
edgeStack,
|
||||
); err != nil {
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.service.mu.Lock()
|
||||
service.service.idxVersion[id] = edgeStack.Version
|
||||
service.service.cacheInvalidationFn(service.tx, id)
|
||||
service.service.cacheInvalidationFn(id)
|
||||
service.service.mu.Unlock()
|
||||
|
||||
return nil
|
||||
@@ -87,12 +89,13 @@ func (service ServiceTx) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *po
|
||||
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
if err := service.tx.UpdateObject(BucketName, identifier, edgeStack); err != nil {
|
||||
err := service.tx.UpdateObject(BucketName, identifier, edgeStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.service.idxVersion[ID] = edgeStack.Version
|
||||
service.service.cacheInvalidationFn(service.tx, ID)
|
||||
service.service.cacheInvalidationFn(ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -116,13 +119,14 @@ func (service ServiceTx) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
||||
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
if err := service.tx.DeleteObject(BucketName, identifier); err != nil {
|
||||
err := service.tx.DeleteObject(BucketName, identifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(service.service.idxVersion, ID)
|
||||
|
||||
service.service.cacheInvalidationFn(service.tx, ID)
|
||||
service.service.cacheInvalidationFn(ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package edgestackstatus
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
)
|
||||
|
||||
var _ dataservices.EdgeStackStatusService = &Service{}
|
||||
|
||||
const BucketName = "edge_stack_status"
|
||||
|
||||
type Service struct {
|
||||
conn portainer.Connection
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
if err := connection.SetServiceName(BucketName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{conn: connection}, nil
|
||||
}
|
||||
|
||||
func (s *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
return ServiceTx{
|
||||
service: s,
|
||||
tx: tx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Create(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID, status *portainer.EdgeStackStatusForEnv) error {
|
||||
return s.conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return s.Tx(tx).Create(edgeStackID, endpointID, status)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) Read(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID) (*portainer.EdgeStackStatusForEnv, error) {
|
||||
var element *portainer.EdgeStackStatusForEnv
|
||||
|
||||
return element, s.conn.ViewTx(func(tx portainer.Transaction) error {
|
||||
var err error
|
||||
element, err = s.Tx(tx).Read(edgeStackID, endpointID)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) ReadAll(edgeStackID portainer.EdgeStackID) ([]portainer.EdgeStackStatusForEnv, error) {
|
||||
var collection = make([]portainer.EdgeStackStatusForEnv, 0)
|
||||
|
||||
return collection, s.conn.ViewTx(func(tx portainer.Transaction) error {
|
||||
var err error
|
||||
collection, err = s.Tx(tx).ReadAll(edgeStackID)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) Update(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID, status *portainer.EdgeStackStatusForEnv) error {
|
||||
return s.conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return s.Tx(tx).Update(edgeStackID, endpointID, status)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) Delete(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID) error {
|
||||
return s.conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return s.Tx(tx).Delete(edgeStackID, endpointID)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) DeleteAll(edgeStackID portainer.EdgeStackID) error {
|
||||
return s.conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return s.Tx(tx).DeleteAll(edgeStackID)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) Clear(edgeStackID portainer.EdgeStackID, relatedEnvironmentsIDs []portainer.EndpointID) error {
|
||||
return s.conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return s.Tx(tx).Clear(edgeStackID, relatedEnvironmentsIDs)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) key(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID) []byte {
|
||||
return append(s.conn.ConvertToKey(int(edgeStackID)), s.conn.ConvertToKey(int(endpointID))...)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package edgestackstatus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
)
|
||||
|
||||
var _ dataservices.EdgeStackStatusService = &Service{}
|
||||
|
||||
type ServiceTx struct {
|
||||
service *Service
|
||||
tx portainer.Transaction
|
||||
}
|
||||
|
||||
func (service ServiceTx) Create(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID, status *portainer.EdgeStackStatusForEnv) error {
|
||||
identifier := service.service.key(edgeStackID, endpointID)
|
||||
return service.tx.CreateObjectWithStringId(BucketName, identifier, status)
|
||||
}
|
||||
|
||||
func (s ServiceTx) Read(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID) (*portainer.EdgeStackStatusForEnv, error) {
|
||||
var status portainer.EdgeStackStatusForEnv
|
||||
identifier := s.service.key(edgeStackID, endpointID)
|
||||
|
||||
if err := s.tx.GetObject(BucketName, identifier, &status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &status, nil
|
||||
}
|
||||
|
||||
func (s ServiceTx) ReadAll(edgeStackID portainer.EdgeStackID) ([]portainer.EdgeStackStatusForEnv, error) {
|
||||
keyPrefix := s.service.conn.ConvertToKey(int(edgeStackID))
|
||||
|
||||
statuses := make([]portainer.EdgeStackStatusForEnv, 0)
|
||||
|
||||
if err := s.tx.GetAllWithKeyPrefix(BucketName, keyPrefix, &portainer.EdgeStackStatusForEnv{}, dataservices.AppendFn(&statuses)); err != nil {
|
||||
return nil, fmt.Errorf("unable to retrieve EdgeStackStatus for EdgeStack %d: %w", edgeStackID, err)
|
||||
}
|
||||
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
func (s ServiceTx) Update(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID, status *portainer.EdgeStackStatusForEnv) error {
|
||||
identifier := s.service.key(edgeStackID, endpointID)
|
||||
return s.tx.UpdateObject(BucketName, identifier, status)
|
||||
}
|
||||
|
||||
func (s ServiceTx) Delete(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID) error {
|
||||
identifier := s.service.key(edgeStackID, endpointID)
|
||||
return s.tx.DeleteObject(BucketName, identifier)
|
||||
}
|
||||
|
||||
func (s ServiceTx) DeleteAll(edgeStackID portainer.EdgeStackID) error {
|
||||
keyPrefix := s.service.conn.ConvertToKey(int(edgeStackID))
|
||||
|
||||
statuses := make([]portainer.EdgeStackStatusForEnv, 0)
|
||||
|
||||
if err := s.tx.GetAllWithKeyPrefix(BucketName, keyPrefix, &portainer.EdgeStackStatusForEnv{}, dataservices.AppendFn(&statuses)); err != nil {
|
||||
return fmt.Errorf("unable to retrieve EdgeStackStatus for EdgeStack %d: %w", edgeStackID, err)
|
||||
}
|
||||
|
||||
for _, status := range statuses {
|
||||
if err := s.tx.DeleteObject(BucketName, s.service.key(edgeStackID, status.EndpointID)); err != nil {
|
||||
return fmt.Errorf("unable to delete EdgeStackStatus for EdgeStack %d and Endpoint %d: %w", edgeStackID, status.EndpointID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s ServiceTx) Clear(edgeStackID portainer.EdgeStackID, relatedEnvironmentsIDs []portainer.EndpointID) error {
|
||||
for _, envID := range relatedEnvironmentsIDs {
|
||||
existingStatus, err := s.Read(edgeStackID, envID)
|
||||
if err != nil && !dataservices.IsErrObjectNotFound(err) {
|
||||
return fmt.Errorf("unable to retrieve status for environment %d: %w", envID, err)
|
||||
}
|
||||
|
||||
var deploymentInfo portainer.StackDeploymentInfo
|
||||
if existingStatus != nil {
|
||||
deploymentInfo = existingStatus.DeploymentInfo
|
||||
}
|
||||
|
||||
if err := s.Update(edgeStackID, envID, &portainer.EdgeStackStatusForEnv{
|
||||
EndpointID: envID,
|
||||
Status: []portainer.EdgeStackDeploymentStatus{},
|
||||
DeploymentInfo: deploymentInfo,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
@@ -159,7 +157,6 @@ func (service *Service) EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}),
|
||||
)
|
||||
@@ -169,13 +166,11 @@ func (service *Service) EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
var identifier int
|
||||
|
||||
if err := service.connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
service.connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
identifier = service.Tx(tx).GetNextIdentifier()
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Str("bucket", BucketName).Msg("could not get the next identifier")
|
||||
}
|
||||
})
|
||||
|
||||
return identifier
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ func (service ServiceTx) BucketName() string {
|
||||
// Endpoint returns an environment(endpoint) by ID.
|
||||
func (service ServiceTx) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
|
||||
var endpoint portainer.Endpoint
|
||||
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
if err := service.tx.GetObject(BucketName, identifier, &endpoint); err != nil {
|
||||
err := service.tx.GetObject(BucketName, identifier, &endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ func (service ServiceTx) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
|
||||
func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
if err := service.tx.UpdateObject(BucketName, identifier, endpoint); err != nil {
|
||||
err := service.tx.UpdateObject(BucketName, identifier, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -44,7 +45,6 @@ func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *porta
|
||||
if len(endpoint.EdgeID) > 0 {
|
||||
service.service.idxEdgeID[endpoint.EdgeID] = ID
|
||||
}
|
||||
|
||||
service.service.heartbeats.Store(ID, endpoint.LastCheckInDate)
|
||||
service.service.mu.Unlock()
|
||||
|
||||
@@ -57,7 +57,8 @@ func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *porta
|
||||
func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
if err := service.tx.DeleteObject(BucketName, identifier); err != nil {
|
||||
err := service.tx.DeleteObject(BucketName, identifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -69,7 +70,6 @@ func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
service.service.heartbeats.Delete(ID)
|
||||
service.service.mu.Unlock()
|
||||
|
||||
@@ -82,7 +82,7 @@ func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||
func (service ServiceTx) Endpoints() ([]portainer.Endpoint, error) {
|
||||
var endpoints = make([]portainer.Endpoint, 0)
|
||||
|
||||
return endpoints, service.tx.GetAll(
|
||||
return endpoints, service.tx.GetAllWithJsoniter(
|
||||
BucketName,
|
||||
&portainer.Endpoint{},
|
||||
dataservices.AppendFn(&endpoints),
|
||||
@@ -107,7 +107,8 @@ func (service ServiceTx) UpdateHeartbeat(endpointID portainer.EndpointID) {
|
||||
|
||||
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
|
||||
func (service ServiceTx) Create(endpoint *portainer.Endpoint) error {
|
||||
if err := service.tx.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint); err != nil {
|
||||
err := service.tx.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -115,7 +116,6 @@ func (service ServiceTx) Create(endpoint *portainer.Endpoint) error {
|
||||
if len(endpoint.EdgeID) > 0 {
|
||||
service.service.idxEdgeID[endpoint.EdgeID] = endpoint.ID
|
||||
}
|
||||
|
||||
service.service.heartbeats.Store(endpoint.ID, endpoint.LastCheckInDate)
|
||||
service.service.mu.Unlock()
|
||||
|
||||
@@ -134,7 +134,6 @@ func (service ServiceTx) EndpointsByTeamID(teamID portainer.TeamID) ([]portainer
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
func (service *Service) Create(endpointGroup *portainer.EndpointGroup) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
endpointGroup.ID = portainer.EndpointGroupID(id)
|
||||
return int(endpointGroup.ID), endpointGroup
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ type ServiceTx struct {
|
||||
func (service ServiceTx) Create(endpointGroup *portainer.EndpointGroup) error {
|
||||
return service.Tx.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
endpointGroup.ID = portainer.EndpointGroupID(id)
|
||||
return int(endpointGroup.ID), endpointGroup
|
||||
},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package endpointrelation
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
@@ -15,15 +13,11 @@ const BucketName = "endpoint_relations"
|
||||
|
||||
// Service represents a service for managing environment(endpoint) relation data.
|
||||
type Service struct {
|
||||
connection portainer.Connection
|
||||
updateStackFn func(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
|
||||
updateStackFnTx func(tx portainer.Transaction, ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
|
||||
endpointRelationsCache []portainer.EndpointRelation
|
||||
mu sync.Mutex
|
||||
connection portainer.Connection
|
||||
updateStackFn func(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
|
||||
updateStackFnTx func(tx portainer.Transaction, ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
|
||||
}
|
||||
|
||||
var _ dataservices.EndpointRelationService = &Service{}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
@@ -38,7 +32,8 @@ func (service *Service) RegisterUpdateStackFunction(
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
if err := connection.SetServiceName(BucketName); err != nil {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -70,7 +65,8 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
|
||||
var endpointRelation portainer.EndpointRelation
|
||||
identifier := service.connection.ConvertToKey(int(endpointID))
|
||||
|
||||
if err := service.connection.GetObject(BucketName, identifier, &endpointRelation); err != nil {
|
||||
err := service.connection.GetObject(BucketName, identifier, &endpointRelation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -82,10 +78,6 @@ func (service *Service) Create(endpointRelation *portainer.EndpointRelation) err
|
||||
err := service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
|
||||
cache.Del(endpointRelation.EndpointID)
|
||||
|
||||
service.mu.Lock()
|
||||
service.endpointRelationsCache = nil
|
||||
service.mu.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -102,27 +94,11 @@ func (service *Service) UpdateEndpointRelation(endpointID portainer.EndpointID,
|
||||
|
||||
updatedRelationState, _ := service.EndpointRelation(endpointID)
|
||||
|
||||
service.mu.Lock()
|
||||
service.endpointRelationsCache = nil
|
||||
service.mu.Unlock()
|
||||
|
||||
service.updateEdgeStacksAfterRelationChange(previousRelationState, updatedRelationState)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error {
|
||||
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return service.Tx(tx).AddEndpointRelationsForEdgeStack(endpointIDs, edgeStackID)
|
||||
})
|
||||
}
|
||||
|
||||
func (service *Service) RemoveEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error {
|
||||
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return service.Tx(tx).RemoveEndpointRelationsForEdgeStack(endpointIDs, edgeStackID)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteEndpointRelation deletes an Environment(Endpoint) relation object
|
||||
func (service *Service) DeleteEndpointRelation(endpointID portainer.EndpointID) error {
|
||||
deletedRelation, _ := service.EndpointRelation(endpointID)
|
||||
@@ -134,15 +110,27 @@ func (service *Service) DeleteEndpointRelation(endpointID portainer.EndpointID)
|
||||
return err
|
||||
}
|
||||
|
||||
service.mu.Lock()
|
||||
service.endpointRelationsCache = nil
|
||||
service.mu.Unlock()
|
||||
|
||||
service.updateEdgeStacksAfterRelationChange(deletedRelation, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) InvalidateEdgeCacheForEdgeStack(edgeStackID portainer.EdgeStackID) {
|
||||
rels, err := service.EndpointRelations()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cannot retrieve endpoint relations")
|
||||
return
|
||||
}
|
||||
|
||||
for _, rel := range rels {
|
||||
for id := range rel.EdgeStacks {
|
||||
if edgeStackID == id {
|
||||
cache.Del(rel.EndpointID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (service *Service) updateEdgeStacksAfterRelationChange(previousRelationState *portainer.EndpointRelation, updatedRelationState *portainer.EndpointRelation) {
|
||||
relations, _ := service.EndpointRelations()
|
||||
|
||||
@@ -173,24 +161,19 @@ func (service *Service) updateEdgeStacksAfterRelationChange(previousRelationStat
|
||||
// list how many time this stack is referenced in all relations
|
||||
// in order to update the stack deployments count
|
||||
for refStackId, refStackEnabled := range stacksToUpdate {
|
||||
if !refStackEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
numDeployments := 0
|
||||
|
||||
for _, r := range relations {
|
||||
for sId, enabled := range r.EdgeStacks {
|
||||
if enabled && sId == refStackId {
|
||||
numDeployments += 1
|
||||
if refStackEnabled {
|
||||
numDeployments := 0
|
||||
for _, r := range relations {
|
||||
for sId, enabled := range r.EdgeStacks {
|
||||
if enabled && sId == refStackId {
|
||||
numDeployments += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := service.updateStackFn(refStackId, func(edgeStack *portainer.EdgeStack) {
|
||||
edgeStack.NumDeployments = numDeployments
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("could not update the number of deployments")
|
||||
service.updateStackFn(refStackId, func(edgeStack *portainer.EdgeStack) {
|
||||
edgeStack.NumDeployments = numDeployments
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ type ServiceTx struct {
|
||||
tx portainer.Transaction
|
||||
}
|
||||
|
||||
var _ dataservices.EndpointRelationService = &ServiceTx{}
|
||||
|
||||
func (service ServiceTx) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
@@ -35,7 +33,8 @@ func (service ServiceTx) EndpointRelation(endpointID portainer.EndpointID) (*por
|
||||
var endpointRelation portainer.EndpointRelation
|
||||
identifier := service.service.connection.ConvertToKey(int(endpointID))
|
||||
|
||||
if err := service.tx.GetObject(BucketName, identifier, &endpointRelation); err != nil {
|
||||
err := service.tx.GetObject(BucketName, identifier, &endpointRelation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -47,10 +46,6 @@ func (service ServiceTx) Create(endpointRelation *portainer.EndpointRelation) er
|
||||
err := service.tx.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
|
||||
cache.Del(endpointRelation.EndpointID)
|
||||
|
||||
service.service.mu.Lock()
|
||||
service.service.endpointRelationsCache = nil
|
||||
service.service.mu.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -67,75 +62,11 @@ func (service ServiceTx) UpdateEndpointRelation(endpointID portainer.EndpointID,
|
||||
|
||||
updatedRelationState, _ := service.EndpointRelation(endpointID)
|
||||
|
||||
service.service.mu.Lock()
|
||||
service.service.endpointRelationsCache = nil
|
||||
service.service.mu.Unlock()
|
||||
|
||||
service.updateEdgeStacksAfterRelationChange(previousRelationState, updatedRelationState)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service ServiceTx) AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error {
|
||||
for _, endpointID := range endpointIDs {
|
||||
rel, err := service.EndpointRelation(endpointID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rel.EdgeStacks[edgeStackID] = true
|
||||
|
||||
identifier := service.service.connection.ConvertToKey(int(endpointID))
|
||||
err = service.tx.UpdateObject(BucketName, identifier, rel)
|
||||
cache.Del(endpointID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
service.service.mu.Lock()
|
||||
service.service.endpointRelationsCache = nil
|
||||
service.service.mu.Unlock()
|
||||
|
||||
if err := service.service.updateStackFnTx(service.tx, edgeStackID, func(edgeStack *portainer.EdgeStack) {
|
||||
edgeStack.NumDeployments += len(endpointIDs)
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("could not update the number of deployments")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service ServiceTx) RemoveEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error {
|
||||
for _, endpointID := range endpointIDs {
|
||||
rel, err := service.EndpointRelation(endpointID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(rel.EdgeStacks, edgeStackID)
|
||||
|
||||
identifier := service.service.connection.ConvertToKey(int(endpointID))
|
||||
err = service.tx.UpdateObject(BucketName, identifier, rel)
|
||||
cache.Del(endpointID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
service.service.mu.Lock()
|
||||
service.service.endpointRelationsCache = nil
|
||||
service.service.mu.Unlock()
|
||||
|
||||
if err := service.service.updateStackFnTx(service.tx, edgeStackID, func(edgeStack *portainer.EdgeStack) {
|
||||
edgeStack.NumDeployments -= len(endpointIDs)
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("could not update the number of deployments")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEndpointRelation deletes an Environment(Endpoint) relation object
|
||||
func (service ServiceTx) DeleteEndpointRelation(endpointID portainer.EndpointID) error {
|
||||
deletedRelation, _ := service.EndpointRelation(endpointID)
|
||||
@@ -147,44 +78,27 @@ func (service ServiceTx) DeleteEndpointRelation(endpointID portainer.EndpointID)
|
||||
return err
|
||||
}
|
||||
|
||||
service.service.mu.Lock()
|
||||
service.service.endpointRelationsCache = nil
|
||||
service.service.mu.Unlock()
|
||||
|
||||
service.updateEdgeStacksAfterRelationChange(deletedRelation, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service ServiceTx) InvalidateEdgeCacheForEdgeStack(edgeStackID portainer.EdgeStackID) {
|
||||
rels, err := service.cachedEndpointRelations()
|
||||
rels, err := service.EndpointRelations()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cannot retrieve endpoint relations")
|
||||
return
|
||||
}
|
||||
|
||||
for _, rel := range rels {
|
||||
if _, ok := rel.EdgeStacks[edgeStackID]; ok {
|
||||
cache.Del(rel.EndpointID)
|
||||
for id := range rel.EdgeStacks {
|
||||
if edgeStackID == id {
|
||||
cache.Del(rel.EndpointID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (service ServiceTx) cachedEndpointRelations() ([]portainer.EndpointRelation, error) {
|
||||
service.service.mu.Lock()
|
||||
defer service.service.mu.Unlock()
|
||||
|
||||
if service.service.endpointRelationsCache == nil {
|
||||
var err error
|
||||
service.service.endpointRelationsCache, err = service.EndpointRelations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return service.service.endpointRelationsCache, nil
|
||||
}
|
||||
|
||||
func (service ServiceTx) updateEdgeStacksAfterRelationChange(previousRelationState *portainer.EndpointRelation, updatedRelationState *portainer.EndpointRelation) {
|
||||
relations, _ := service.EndpointRelations()
|
||||
|
||||
@@ -215,24 +129,19 @@ func (service ServiceTx) updateEdgeStacksAfterRelationChange(previousRelationSta
|
||||
// list how many time this stack is referenced in all relations
|
||||
// in order to update the stack deployments count
|
||||
for refStackId, refStackEnabled := range stacksToUpdate {
|
||||
if !refStackEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
numDeployments := 0
|
||||
|
||||
for _, r := range relations {
|
||||
for sId, enabled := range r.EdgeStacks {
|
||||
if enabled && sId == refStackId {
|
||||
numDeployments += 1
|
||||
if refStackEnabled {
|
||||
numDeployments := 0
|
||||
for _, r := range relations {
|
||||
for sId, enabled := range r.EdgeStacks {
|
||||
if enabled && sId == refStackId {
|
||||
numDeployments += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := service.service.updateStackFnTx(service.tx, refStackId, func(edgeStack *portainer.EdgeStack) {
|
||||
edgeStack.NumDeployments = numDeployments
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("could not update the number of deployments")
|
||||
service.service.updateStackFnTx(service.tx, refStackId, func(edgeStack *portainer.EdgeStack) {
|
||||
edgeStack.NumDeployments = numDeployments
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package fdoprofile
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
)
|
||||
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
const BucketName = "fdo_profiles"
|
||||
|
||||
// Service represents a service for managingFDO Profiles data.
|
||||
type Service struct {
|
||||
dataservices.BaseDataService[portainer.FDOProfile, portainer.FDOProfileID]
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
BaseDataService: dataservices.BaseDataService[portainer.FDOProfile, portainer.FDOProfileID]{
|
||||
Bucket: BucketName,
|
||||
Connection: connection,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Create assign an ID to a new FDO Profile and saves it.
|
||||
func (service *Service) Create(FDOProfile *portainer.FDOProfile) error {
|
||||
return service.Connection.CreateObjectWithId(
|
||||
BucketName,
|
||||
int(FDOProfile.ID),
|
||||
FDOProfile,
|
||||
)
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a FDO Profile.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return service.Connection.GetNextIdentifier(BucketName)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func (service *Service) HelmUserRepositoryByUserID(userID portainer.UserID) ([]p
|
||||
func (service *Service) Create(record *portainer.HelmUserRepository) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
record.ID = portainer.HelmUserRepositoryID(id)
|
||||
return int(record.ID), record
|
||||
},
|
||||
|
||||
@@ -17,8 +17,8 @@ func IsErrObjectNotFound(e error) bool {
|
||||
}
|
||||
|
||||
// AppendFn appends elements to the given collection slice
|
||||
func AppendFn[T any](collection *[]T) func(obj any) (any, error) {
|
||||
return func(obj any) (any, error) {
|
||||
func AppendFn[T any](collection *[]T) func(obj interface{}) (interface{}, error) {
|
||||
return func(obj interface{}) (interface{}, error) {
|
||||
element, ok := obj.(*T)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
|
||||
@@ -32,8 +32,8 @@ func AppendFn[T any](collection *[]T) func(obj any) (any, error) {
|
||||
}
|
||||
|
||||
// FilterFn appends elements to the given collection when the predicate is true
|
||||
func FilterFn[T any](collection *[]T, predicate func(T) bool) func(obj any) (any, error) {
|
||||
return func(obj any) (any, error) {
|
||||
func FilterFn[T any](collection *[]T, predicate func(T) bool) func(obj interface{}) (interface{}, error) {
|
||||
return func(obj interface{}) (interface{}, error) {
|
||||
element, ok := obj.(*T)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
|
||||
@@ -50,8 +50,8 @@ func FilterFn[T any](collection *[]T, predicate func(T) bool) func(obj any) (any
|
||||
|
||||
// FirstFn sets the element to the first one that satisfies the predicate and stops the computation, returns ErrStop on
|
||||
// success
|
||||
func FirstFn[T any](element *T, predicate func(T) bool) func(obj any) (any, error) {
|
||||
return func(obj any) (any, error) {
|
||||
func FirstFn[T any](element *T, predicate func(T) bool) func(obj interface{}) (interface{}, error) {
|
||||
return func(obj interface{}) (interface{}, error) {
|
||||
e, ok := obj.(*T)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("type assertion failed")
|
||||
|
||||
@@ -12,10 +12,10 @@ type (
|
||||
EdgeGroup() EdgeGroupService
|
||||
EdgeJob() EdgeJobService
|
||||
EdgeStack() EdgeStackService
|
||||
EdgeStackStatus() EdgeStackStatusService
|
||||
Endpoint() EndpointService
|
||||
EndpointGroup() EndpointGroupService
|
||||
EndpointRelation() EndpointRelationService
|
||||
FDOProfile() FDOProfileService
|
||||
HelmUserRepository() HelmUserRepositoryService
|
||||
Registry() RegistryService
|
||||
ResourceControl() ResourceControlService
|
||||
@@ -40,8 +40,8 @@ type (
|
||||
Open() (newStore bool, err error)
|
||||
Init() error
|
||||
Close() error
|
||||
UpdateTx(func(tx DataStoreTx) error) error
|
||||
ViewTx(func(tx DataStoreTx) error) error
|
||||
UpdateTx(func(DataStoreTx) error) error
|
||||
ViewTx(func(DataStoreTx) error) error
|
||||
MigrateData() error
|
||||
Rollback(force bool) error
|
||||
CheckCurrentEdition() error
|
||||
@@ -72,7 +72,7 @@ type (
|
||||
}
|
||||
|
||||
PendingActionsService interface {
|
||||
BaseCRUD[portainer.PendingAction, portainer.PendingActionID]
|
||||
BaseCRUD[portainer.PendingActions, portainer.PendingActionsID]
|
||||
GetNextIdentifier() int
|
||||
DeleteByEndpointID(ID portainer.EndpointID) error
|
||||
}
|
||||
@@ -90,16 +90,6 @@ type (
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
EdgeStackStatusService interface {
|
||||
Create(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID, status *portainer.EdgeStackStatusForEnv) error
|
||||
Read(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID) (*portainer.EdgeStackStatusForEnv, error)
|
||||
ReadAll(edgeStackID portainer.EdgeStackID) ([]portainer.EdgeStackStatusForEnv, error)
|
||||
Update(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID, status *portainer.EdgeStackStatusForEnv) error
|
||||
Delete(edgeStackID portainer.EdgeStackID, endpointID portainer.EndpointID) error
|
||||
DeleteAll(edgeStackID portainer.EdgeStackID) error
|
||||
Clear(edgeStackID portainer.EdgeStackID, relatedEnvironmentsIDs []portainer.EndpointID) error
|
||||
}
|
||||
|
||||
// EndpointService represents a service for managing environment(endpoint) data
|
||||
EndpointService interface {
|
||||
Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error)
|
||||
@@ -126,12 +116,16 @@ type (
|
||||
EndpointRelation(EndpointID portainer.EndpointID) (*portainer.EndpointRelation, error)
|
||||
Create(endpointRelation *portainer.EndpointRelation) error
|
||||
UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error
|
||||
AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error
|
||||
RemoveEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error
|
||||
DeleteEndpointRelation(EndpointID portainer.EndpointID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// FDOProfileService represents a service to manage FDO Profiles
|
||||
FDOProfileService interface {
|
||||
BaseCRUD[portainer.FDOProfile, portainer.FDOProfileID]
|
||||
GetNextIdentifier() int
|
||||
}
|
||||
|
||||
// HelmUserRepositoryService represents a service to manage HelmUserRepositories
|
||||
HelmUserRepositoryService interface {
|
||||
BaseCRUD[portainer.HelmUserRepository, portainer.HelmUserRepositoryID]
|
||||
@@ -170,7 +164,6 @@ type (
|
||||
|
||||
SnapshotService interface {
|
||||
BaseCRUD[portainer.Snapshot, portainer.EndpointID]
|
||||
ReadWithoutSnapshotRaw(ID portainer.EndpointID) (*portainer.Snapshot, error)
|
||||
}
|
||||
|
||||
// SSLSettingsService represents a service for managing application settings
|
||||
|
||||
@@ -14,11 +14,11 @@ const (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
dataservices.BaseDataService[portainer.PendingAction, portainer.PendingActionID]
|
||||
dataservices.BaseDataService[portainer.PendingActions, portainer.PendingActionsID]
|
||||
}
|
||||
|
||||
type ServiceTx struct {
|
||||
dataservices.BaseDataServiceTx[portainer.PendingAction, portainer.PendingActionID]
|
||||
dataservices.BaseDataServiceTx[portainer.PendingActions, portainer.PendingActionsID]
|
||||
}
|
||||
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
@@ -28,20 +28,20 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
}
|
||||
|
||||
return &Service{
|
||||
BaseDataService: dataservices.BaseDataService[portainer.PendingAction, portainer.PendingActionID]{
|
||||
BaseDataService: dataservices.BaseDataService[portainer.PendingActions, portainer.PendingActionsID]{
|
||||
Bucket: BucketName,
|
||||
Connection: connection,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s Service) Create(config *portainer.PendingAction) error {
|
||||
func (s Service) Create(config *portainer.PendingActions) error {
|
||||
return s.Connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return s.Tx(tx).Create(config)
|
||||
})
|
||||
}
|
||||
|
||||
func (s Service) Update(ID portainer.PendingActionID, config *portainer.PendingAction) error {
|
||||
func (s Service) Update(ID portainer.PendingActionsID, config *portainer.PendingActions) error {
|
||||
return s.Connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return s.Tx(tx).Update(ID, config)
|
||||
})
|
||||
@@ -55,7 +55,7 @@ func (s Service) DeleteByEndpointID(ID portainer.EndpointID) error {
|
||||
|
||||
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
return ServiceTx{
|
||||
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.PendingAction, portainer.PendingActionID]{
|
||||
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.PendingActions, portainer.PendingActionsID]{
|
||||
Bucket: BucketName,
|
||||
Connection: service.Connection,
|
||||
Tx: tx,
|
||||
@@ -63,16 +63,16 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
}
|
||||
}
|
||||
|
||||
func (s ServiceTx) Create(config *portainer.PendingAction) error {
|
||||
return s.Tx.CreateObject(BucketName, func(id uint64) (int, any) {
|
||||
config.ID = portainer.PendingActionID(id)
|
||||
func (s ServiceTx) Create(config *portainer.PendingActions) error {
|
||||
return s.Tx.CreateObject(BucketName, func(id uint64) (int, interface{}) {
|
||||
config.ID = portainer.PendingActionsID(id)
|
||||
config.CreatedAt = time.Now().Unix()
|
||||
|
||||
return int(config.ID), config
|
||||
})
|
||||
}
|
||||
|
||||
func (s ServiceTx) Update(ID portainer.PendingActionID, config *portainer.PendingAction) error {
|
||||
func (s ServiceTx) Update(ID portainer.PendingActionsID, config *portainer.PendingActions) error {
|
||||
return s.BaseDataServiceTx.Update(ID, config)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
func (service *Service) Create(registry *portainer.Registry) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
registry.ID = portainer.RegistryID(id)
|
||||
return int(registry.ID), registry
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ type ServiceTx struct {
|
||||
func (service ServiceTx) Create(registry *portainer.Registry) error {
|
||||
return service.Tx.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
registry.ID = portainer.RegistryID(id)
|
||||
return int(registry.ID), registry
|
||||
},
|
||||
|
||||
@@ -48,11 +48,11 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
// if no ResourceControl was found.
|
||||
func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
||||
var resourceControl *portainer.ResourceControl
|
||||
stop := errors.New("ok")
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.Connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.ResourceControl{},
|
||||
func(obj any) (any, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
rc, ok := obj.(*portainer.ResourceControl)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
|
||||
@@ -84,7 +84,7 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
|
||||
func (service *Service) Create(resourceControl *portainer.ResourceControl) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
resourceControl.ID = portainer.ResourceControlID(id)
|
||||
return int(resourceControl.ID), resourceControl
|
||||
},
|
||||
|
||||
@@ -19,11 +19,11 @@ type ServiceTx struct {
|
||||
// if no ResourceControl was found.
|
||||
func (service ServiceTx) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
||||
var resourceControl *portainer.ResourceControl
|
||||
stop := errors.New("ok")
|
||||
stop := fmt.Errorf("ok")
|
||||
err := service.Tx.GetAll(
|
||||
BucketName,
|
||||
&portainer.ResourceControl{},
|
||||
func(obj any) (any, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
rc, ok := obj.(*portainer.ResourceControl)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
|
||||
@@ -55,7 +55,7 @@ func (service ServiceTx) ResourceControlByResourceIDAndType(resourceID string, r
|
||||
func (service ServiceTx) Create(resourceControl *portainer.ResourceControl) error {
|
||||
return service.Tx.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
resourceControl.ID = portainer.ResourceControlID(id)
|
||||
return int(resourceControl.ID), resourceControl
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
func (service *Service) Create(role *portainer.Role) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
role.ID = portainer.RoleID(id)
|
||||
return int(role.ID), role
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ type ServiceTx struct {
|
||||
func (service ServiceTx) Create(role *portainer.Role) error {
|
||||
return service.Tx.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
role.ID = portainer.RoleID(id)
|
||||
return int(role.ID), role
|
||||
},
|
||||
|
||||
@@ -38,33 +38,3 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
func (service *Service) Create(snapshot *portainer.Snapshot) error {
|
||||
return service.Connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
|
||||
}
|
||||
|
||||
func (service *Service) ReadWithoutSnapshotRaw(ID portainer.EndpointID) (*portainer.Snapshot, error) {
|
||||
var snapshot *portainer.Snapshot
|
||||
|
||||
err := service.Connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
var err error
|
||||
snapshot, err = service.Tx(tx).ReadWithoutSnapshotRaw(ID)
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return snapshot, err
|
||||
}
|
||||
|
||||
func (service *Service) ReadRawMessage(ID portainer.EndpointID) (*portainer.SnapshotRawMessage, error) {
|
||||
var snapshot *portainer.SnapshotRawMessage
|
||||
|
||||
err := service.Connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
var err error
|
||||
snapshot, err = service.Tx(tx).ReadRawMessage(ID)
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return snapshot, err
|
||||
}
|
||||
|
||||
func (service *Service) CreateRawMessage(snapshot *portainer.SnapshotRawMessage) error {
|
||||
return service.Connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
|
||||
}
|
||||
|
||||
@@ -12,42 +12,3 @@ type ServiceTx struct {
|
||||
func (service ServiceTx) Create(snapshot *portainer.Snapshot) error {
|
||||
return service.Tx.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
|
||||
}
|
||||
|
||||
func (service ServiceTx) ReadWithoutSnapshotRaw(ID portainer.EndpointID) (*portainer.Snapshot, error) {
|
||||
var snapshot struct {
|
||||
Docker *struct {
|
||||
X struct{} `json:"DockerSnapshotRaw"`
|
||||
*portainer.DockerSnapshot
|
||||
} `json:"Docker"`
|
||||
|
||||
portainer.Snapshot
|
||||
}
|
||||
|
||||
identifier := service.Connection.ConvertToKey(int(ID))
|
||||
|
||||
if err := service.Tx.GetObject(service.Bucket, identifier, &snapshot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if snapshot.Docker != nil {
|
||||
snapshot.Snapshot.Docker = snapshot.Docker.DockerSnapshot
|
||||
}
|
||||
|
||||
return &snapshot.Snapshot, nil
|
||||
}
|
||||
|
||||
func (service ServiceTx) ReadRawMessage(ID portainer.EndpointID) (*portainer.SnapshotRawMessage, error) {
|
||||
var snapshot = portainer.SnapshotRawMessage{}
|
||||
|
||||
identifier := service.Connection.ConvertToKey(int(ID))
|
||||
|
||||
if err := service.Tx.GetObject(service.Bucket, identifier, &snapshot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &snapshot, nil
|
||||
}
|
||||
|
||||
func (service ServiceTx) CreateRawMessage(snapshot *portainer.SnapshotRawMessage) error {
|
||||
return service.Tx.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestService_StackByWebhookID(t *testing.T) {
|
||||
|
||||
b := stackBuilder{t: t, store: store}
|
||||
b.createNewStack(newGuidString(t))
|
||||
for range 10 {
|
||||
for i := 0; i < 10; i++ {
|
||||
b.createNewStack("")
|
||||
}
|
||||
webhookID := newGuidString(t)
|
||||
|
||||
@@ -42,7 +42,7 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
||||
func (service *Service) Create(tag *portainer.Tag) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
tag.ID = portainer.TagID(id)
|
||||
return int(tag.ID), tag
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ type ServiceTx struct {
|
||||
func (service ServiceTx) Create(tag *portainer.Tag) error {
|
||||
return service.Tx.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
tag.ID = portainer.TagID(id)
|
||||
return int(tag.ID), tag
|
||||
},
|
||||
|
||||
@@ -68,7 +68,7 @@ func (service *Service) TeamByName(name string) (*portainer.Team, error) {
|
||||
func (service *Service) Create(team *portainer.Team) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
team.ID = portainer.TeamID(id)
|
||||
return int(team.ID), team
|
||||
},
|
||||
|
||||
@@ -72,7 +72,7 @@ func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]port
|
||||
func (service *Service) Create(membership *portainer.TeamMembership) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
membership.ID = portainer.TeamMembershipID(id)
|
||||
return int(membership.ID), membership
|
||||
},
|
||||
@@ -84,8 +84,8 @@ func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) er
|
||||
return service.Connection.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj any) (id int, ok bool) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
@@ -105,8 +105,8 @@ func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) er
|
||||
return service.Connection.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj any) (id int, ok bool) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
@@ -125,8 +125,8 @@ func (service *Service) DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.T
|
||||
return service.Connection.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj any) (id int, ok bool) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
|
||||
@@ -43,7 +43,7 @@ func (service ServiceTx) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]por
|
||||
func (service ServiceTx) Create(membership *portainer.TeamMembership) error {
|
||||
return service.Tx.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
membership.ID = portainer.TeamMembershipID(id)
|
||||
return int(membership.ID), membership
|
||||
},
|
||||
@@ -55,7 +55,7 @@ func (service ServiceTx) DeleteTeamMembershipByUserID(userID portainer.UserID) e
|
||||
return service.Tx.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj any) (id int, ok bool) {
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
@@ -76,7 +76,7 @@ func (service ServiceTx) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) e
|
||||
return service.Tx.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj any) (id int, ok bool) {
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
@@ -96,7 +96,7 @@ func (service ServiceTx) DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.
|
||||
return service.Tx.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj any) (id int, ok bool) {
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
|
||||
@@ -53,7 +53,7 @@ func (service ServiceTx) UsersByRole(role portainer.UserRole) ([]portainer.User,
|
||||
func (service ServiceTx) Create(user *portainer.User) error {
|
||||
return service.Tx.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
user.ID = portainer.UserID(id)
|
||||
user.Username = strings.ToLower(user.Username)
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User,
|
||||
func (service *Service) Create(user *portainer.User) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
user.ID = portainer.UserID(id)
|
||||
user.Username = strings.ToLower(user.Username)
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error)
|
||||
func (service *Service) Create(webhook *portainer.Webhook) error {
|
||||
return service.Connection.CreateObject(
|
||||
BucketName,
|
||||
func(id uint64) (int, any) {
|
||||
func(id uint64) (int, interface{}) {
|
||||
webhook.ID = portainer.WebhookID(id)
|
||||
return int(webhook.ID), webhook
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
@@ -32,7 +33,7 @@ func TestStoreCreation(t *testing.T) {
|
||||
func TestBackup(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, true, true)
|
||||
backupFileName := store.backupFilename()
|
||||
t.Run("Backup should create "+backupFileName, func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("Backup should create %s", backupFileName), func(t *testing.T) {
|
||||
v := models.Version{
|
||||
Edition: int(portainer.PortainerCE),
|
||||
SchemaVersion: portainer.APIVersion,
|
||||
|
||||
@@ -16,9 +16,8 @@ import (
|
||||
)
|
||||
|
||||
// NewStore initializes a new Store and the associated services
|
||||
func NewStore(cliFlags *portainer.CLIFlags, fileService portainer.FileService, connection portainer.Connection) *Store {
|
||||
func NewStore(storePath string, fileService portainer.FileService, connection portainer.Connection) *Store {
|
||||
return &Store{
|
||||
flags: cliFlags,
|
||||
fileService: fileService,
|
||||
connection: connection,
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func (store *Store) checkOrCreateDefaultSettings() error {
|
||||
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
|
||||
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
||||
KubeconfigExpiry: portainer.DefaultKubeconfigExpiry,
|
||||
KubectlShellImage: *store.flags.KubectlShellImage,
|
||||
KubectlShellImage: portainer.DefaultKubectlShellImage,
|
||||
|
||||
IsDockerDesktopExtension: isDDExtention,
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func (store *Store) MigrateData() error {
|
||||
return errors.Wrap(err, "while migrating legacy version")
|
||||
}
|
||||
|
||||
migratorParams := store.newMigratorParameters(version, store.flags)
|
||||
migratorParams := store.newMigratorParameters(version)
|
||||
migrator := migrator.NewMigrator(migratorParams)
|
||||
|
||||
if !migrator.NeedsMigration() {
|
||||
@@ -40,11 +40,13 @@ func (store *Store) MigrateData() error {
|
||||
}
|
||||
|
||||
// before we alter anything in the DB, create a backup
|
||||
if _, err := store.Backup(""); err != nil {
|
||||
_, err = store.Backup("")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while backing up database")
|
||||
}
|
||||
|
||||
if err := store.FailSafeMigrate(migrator, version); err != nil {
|
||||
err = store.FailSafeMigrate(migrator, version)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to migrate database")
|
||||
|
||||
log.Warn().Err(err).Msg("migration failed, restoring database to previous version")
|
||||
@@ -60,9 +62,8 @@ func (store *Store) MigrateData() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) newMigratorParameters(version *models.Version, flags *portainer.CLIFlags) *migrator.MigratorParameters {
|
||||
func (store *Store) newMigratorParameters(version *models.Version) *migrator.MigratorParameters {
|
||||
return &migrator.MigratorParameters{
|
||||
Flags: flags,
|
||||
CurrentDBVersion: version,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
EndpointService: store.EndpointService,
|
||||
@@ -83,7 +84,6 @@ func (store *Store) newMigratorParameters(version *models.Version, flags *portai
|
||||
DockerhubService: store.DockerHubService,
|
||||
AuthorizationService: authorization.NewService(store),
|
||||
EdgeStackService: store.EdgeStackService,
|
||||
EdgeStackStatusService: store.EdgeStackStatusService,
|
||||
EdgeJobService: store.EdgeJobService,
|
||||
TunnelServerService: store.TunnelServerService,
|
||||
PendingActionsService: store.PendingActionsService,
|
||||
@@ -139,7 +139,8 @@ func (store *Store) connectionRollback(force bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := store.Restore(); err != nil {
|
||||
err := store.Restore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ func TestMigrateData(t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
migratorParams := store.newMigratorParameters(v, store.flags)
|
||||
migratorParams := store.newMigratorParameters(v)
|
||||
m := migrator.NewMigrator(migratorParams)
|
||||
latestMigrations := m.LatestMigrations()
|
||||
|
||||
@@ -321,7 +321,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
|
||||
// importJSON reads input JSON and commits it to a portainer datastore.Store.
|
||||
// Errors are logged with the testing package.
|
||||
func importJSON(t *testing.T, r io.Reader, store *Store) error {
|
||||
objects := make(map[string]any)
|
||||
objects := make(map[string]interface{})
|
||||
|
||||
// Parse json into map of objects.
|
||||
d := json.NewDecoder(r)
|
||||
@@ -337,9 +337,9 @@ func importJSON(t *testing.T, r io.Reader, store *Store) error {
|
||||
for k, v := range objects {
|
||||
switch k {
|
||||
case "version":
|
||||
versions, ok := v.(map[string]any)
|
||||
versions, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Logf("failed casting %s to map[string]any", k)
|
||||
t.Logf("failed casting %s to map[string]interface{}", k)
|
||||
}
|
||||
|
||||
// New format db
|
||||
@@ -404,9 +404,9 @@ func importJSON(t *testing.T, r io.Reader, store *Store) error {
|
||||
}
|
||||
|
||||
case "dockerhub":
|
||||
obj, ok := v.([]any)
|
||||
obj, ok := v.([]interface{})
|
||||
if !ok {
|
||||
t.Logf("failed to cast %s to []any", k)
|
||||
t.Logf("failed to cast %s to []interface{}", k)
|
||||
}
|
||||
err := con.CreateObjectWithStringId(
|
||||
k,
|
||||
@@ -418,9 +418,9 @@ func importJSON(t *testing.T, r io.Reader, store *Store) error {
|
||||
}
|
||||
|
||||
case "ssl":
|
||||
obj, ok := v.(map[string]any)
|
||||
obj, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Logf("failed to case %s to map[string]any", k)
|
||||
t.Logf("failed to case %s to map[string]interface{}", k)
|
||||
}
|
||||
err := con.CreateObjectWithStringId(
|
||||
k,
|
||||
@@ -432,9 +432,9 @@ func importJSON(t *testing.T, r io.Reader, store *Store) error {
|
||||
}
|
||||
|
||||
case "settings":
|
||||
obj, ok := v.(map[string]any)
|
||||
obj, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Logf("failed to case %s to map[string]any", k)
|
||||
t.Logf("failed to case %s to map[string]interface{}", k)
|
||||
}
|
||||
err := con.CreateObjectWithStringId(
|
||||
k,
|
||||
@@ -446,9 +446,9 @@ func importJSON(t *testing.T, r io.Reader, store *Store) error {
|
||||
}
|
||||
|
||||
case "tunnel_server":
|
||||
obj, ok := v.(map[string]any)
|
||||
obj, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Logf("failed to case %s to map[string]any", k)
|
||||
t.Logf("failed to case %s to map[string]interface{}", k)
|
||||
}
|
||||
err := con.CreateObjectWithStringId(
|
||||
k,
|
||||
@@ -462,18 +462,18 @@ func importJSON(t *testing.T, r io.Reader, store *Store) error {
|
||||
continue
|
||||
|
||||
default:
|
||||
objlist, ok := v.([]any)
|
||||
objlist, ok := v.([]interface{})
|
||||
if !ok {
|
||||
t.Logf("failed to cast %s to []any", k)
|
||||
t.Logf("failed to cast %s to []interface{}", k)
|
||||
}
|
||||
|
||||
for _, obj := range objlist {
|
||||
value, ok := obj.(map[string]any)
|
||||
value, ok := obj.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Logf("failed to cast %v to map[string]any", obj)
|
||||
t.Logf("failed to cast %v to map[string]interface{}", obj)
|
||||
} else {
|
||||
var ok bool
|
||||
var id any
|
||||
var id interface{}
|
||||
switch k {
|
||||
case "endpoint_relations":
|
||||
// TODO: need to make into an int, then do that weird
|
||||
|
||||
@@ -12,13 +12,13 @@ const dummyLogoURL = "example.com"
|
||||
|
||||
// initTestingDBConn creates a settings service with raw database DB connection
|
||||
// for unit testing usage only since using NewStore will cause cycle import inside migrator pkg
|
||||
func initTestingSettingsService(dbConn portainer.Connection, preSetObj map[string]any) error {
|
||||
func initTestingSettingsService(dbConn portainer.Connection, preSetObj map[string]interface{}) error {
|
||||
//insert a obj
|
||||
return dbConn.UpdateObject("settings", []byte("SETTINGS"), preSetObj)
|
||||
}
|
||||
|
||||
func setup(store *Store) error {
|
||||
dummySettingsObj := map[string]any{
|
||||
dummySettingsObj := map[string]interface{}{
|
||||
"LogoURL": dummyLogoURL,
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ func TestMigrateSettings(t *testing.T) {
|
||||
}
|
||||
|
||||
m := migrator.NewMigrator(&migrator.MigratorParameters{
|
||||
Flags: store.flags,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointRelationService: store.EndpointRelationService,
|
||||
|
||||
@@ -99,7 +99,7 @@ func (store *Store) getOrMigrateLegacyVersion() (*models.Version, error) {
|
||||
return &models.Version{
|
||||
SchemaVersion: dbVersionToSemanticVersion(dbVersion),
|
||||
Edition: edition,
|
||||
InstanceID: instanceId,
|
||||
InstanceID: string(instanceId),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -111,6 +111,5 @@ func (store *Store) finishMigrateLegacyVersion(versionToWrite *models.Version) e
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyDBVersionKey))
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyEditionKey))
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyInstanceKey))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) migrateEdgeStacksStatuses_2_31_0() error {
|
||||
edgeStacks, err := m.edgeStackService.EdgeStacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, edgeStack := range edgeStacks {
|
||||
for envID, status := range edgeStack.Status {
|
||||
if err := m.edgeStackStatusService.Create(edgeStack.ID, envID, &portainer.EdgeStackStatusForEnv{
|
||||
EndpointID: envID,
|
||||
Status: status.Status,
|
||||
DeploymentInfo: status.DeploymentInfo,
|
||||
ReadyRePullImage: status.ReadyRePullImage,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
edgeStack.Status = nil
|
||||
|
||||
if err := m.edgeStackService.UpdateEdgeStack(edgeStack.ID, &edgeStack); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
perrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
)
|
||||
|
||||
func (m *Migrator) addEndpointRelationForEdgeAgents_2_32_0() error {
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if endpointutils.IsEdgeEndpoint(&endpoint) {
|
||||
_, err := m.endpointRelationService.EndpointRelation(endpoint.ID)
|
||||
if err != nil && errors.Is(err, perrors.ErrObjectNotFound) {
|
||||
relation := &portainer.EndpointRelation{
|
||||
EndpointID: endpoint.ID,
|
||||
EdgeStacks: make(map[portainer.EdgeStackID]bool),
|
||||
}
|
||||
|
||||
if err := m.endpointRelationService.Create(relation); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -15,7 +15,7 @@ func migrationError(err error, context string) error {
|
||||
return errors.Wrap(err, "failed in "+context)
|
||||
}
|
||||
|
||||
func GetFunctionName(i any) string {
|
||||
func GetFunctionName(i interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
||||
|
||||
@@ -39,19 +39,20 @@ func (m *Migrator) Migrate() error {
|
||||
latestMigrations := m.LatestMigrations()
|
||||
if latestMigrations.Version.Equal(schemaVersion) &&
|
||||
version.MigratorCount != len(latestMigrations.MigrationFuncs) {
|
||||
if err := runMigrations(latestMigrations.MigrationFuncs); err != nil {
|
||||
err := runMigrations(latestMigrations.MigrationFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newMigratorCount = len(latestMigrations.MigrationFuncs)
|
||||
}
|
||||
} else {
|
||||
// regular path when major/minor/patch versions differ
|
||||
for _, migration := range m.migrations {
|
||||
if schemaVersion.LessThan(migration.Version) {
|
||||
log.Info().Msgf("migrating data to %s", migration.Version.String())
|
||||
|
||||
if err := runMigrations(migration.MigrationFuncs); err != nil {
|
||||
log.Info().Msgf("migrating data to %s", migration.Version.String())
|
||||
err := runMigrations(migration.MigrationFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -62,14 +63,16 @@ func (m *Migrator) Migrate() error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.Always(); err != nil {
|
||||
err = m.Always()
|
||||
if err != nil {
|
||||
return migrationError(err, "Always migrations returned error")
|
||||
}
|
||||
|
||||
version.SchemaVersion = portainer.APIVersion
|
||||
version.MigratorCount = newMigratorCount
|
||||
|
||||
if err := m.versionService.UpdateVersion(version); err != nil {
|
||||
err = m.versionService.UpdateVersion(version)
|
||||
if err != nil {
|
||||
return migrationError(err, "StoreDBVersion")
|
||||
}
|
||||
|
||||
@@ -96,7 +99,6 @@ func (m *Migrator) NeedsMigration() bool {
|
||||
// In this particular instance we should log a fatal error
|
||||
if m.CurrentDBEdition() != portainer.PortainerCE {
|
||||
log.Fatal().Msg("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/chisel/crypto"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
@@ -38,11 +37,9 @@ func (m *Migrator) convertSeedToPrivateKeyForDB100() error {
|
||||
log.Info().Msg("ServerInfo object not found")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Error().
|
||||
Err(err).
|
||||
Msg("Failed to read ServerInfo from DB")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -52,15 +49,14 @@ func (m *Migrator) convertSeedToPrivateKeyForDB100() error {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Msg("Failed to read ServerInfo from DB")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.fileService.StoreChiselPrivateKey(key); err != nil {
|
||||
err = m.fileService.StoreChiselPrivateKey(key)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Msg("Failed to save Chisel private key to disk")
|
||||
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -68,14 +64,14 @@ func (m *Migrator) convertSeedToPrivateKeyForDB100() error {
|
||||
}
|
||||
|
||||
serverInfo.PrivateKeySeed = ""
|
||||
if err := m.TunnelServerService.UpdateInfo(serverInfo); err != nil {
|
||||
err = m.TunnelServerService.UpdateInfo(serverInfo)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Msg("Failed to clean private key seed in DB")
|
||||
} else {
|
||||
log.Info().Msg("Success to migrate private key seed to private key file")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -88,13 +84,10 @@ func (m *Migrator) updateEdgeStackStatusForDB100() error {
|
||||
}
|
||||
|
||||
for _, edgeStack := range edgeStacks {
|
||||
for environmentID, environmentStatus := range edgeStack.Status {
|
||||
// Skip if status is already updated
|
||||
if len(environmentStatus.Status) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if environmentStatus.Details == nil {
|
||||
for environmentID, environmentStatus := range edgeStack.Status {
|
||||
// skip if status is already updated
|
||||
if len(environmentStatus.Status) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -153,7 +146,8 @@ func (m *Migrator) updateEdgeStackStatusForDB100() error {
|
||||
edgeStack.Status[environmentID] = environmentStatus
|
||||
}
|
||||
|
||||
if err := m.edgeStackService.UpdateEdgeStack(edgeStack.ID, &edgeStack); err != nil {
|
||||
err = m.edgeStackService.UpdateEdgeStack(edgeStack.ID, &edgeStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"github.com/segmentio/encoding/json"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (migrator *Migrator) migratePendingActionsDataForDB130() error {
|
||||
log.Info().Msg("Migrating pending actions data")
|
||||
|
||||
pendingActions, err := migrator.pendingActionsService.ReadAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pa := range pendingActions {
|
||||
actionData, err := json.Marshal(pa.ActionData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pa.ActionData = string(actionData)
|
||||
|
||||
// Update the pending action
|
||||
err = migrator.pendingActionsService.Update(pa.ID, &pa)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -18,7 +18,8 @@ func (m *Migrator) updateResourceControlsToDBVersion22() error {
|
||||
for _, resourceControl := range legacyResourceControls {
|
||||
resourceControl.AdministratorsOnly = false
|
||||
|
||||
if err := m.resourceControlService.Update(resourceControl.ID, &resourceControl); err != nil {
|
||||
err := m.resourceControlService.Update(resourceControl.ID, &resourceControl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -41,8 +42,8 @@ func (m *Migrator) updateUsersAndRolesToDBVersion22() error {
|
||||
|
||||
for _, user := range legacyUsers {
|
||||
user.PortainerAuthorizations = authorization.DefaultPortainerAuthorizations()
|
||||
|
||||
if err := m.userService.Update(user.ID, &user); err != nil {
|
||||
err = m.userService.Update(user.ID, &user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -51,47 +52,38 @@ func (m *Migrator) updateUsersAndRolesToDBVersion22() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
endpointAdministratorRole.Priority = 1
|
||||
endpointAdministratorRole.Authorizations = authorization.DefaultEndpointAuthorizationsForEndpointAdministratorRole()
|
||||
|
||||
if err := m.roleService.Update(endpointAdministratorRole.ID, endpointAdministratorRole); err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.roleService.Update(endpointAdministratorRole.ID, endpointAdministratorRole)
|
||||
|
||||
helpDeskRole, err := m.roleService.Read(portainer.RoleID(2))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
helpDeskRole.Priority = 2
|
||||
helpDeskRole.Authorizations = authorization.DefaultEndpointAuthorizationsForHelpDeskRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
if err := m.roleService.Update(helpDeskRole.ID, helpDeskRole); err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.roleService.Update(helpDeskRole.ID, helpDeskRole)
|
||||
|
||||
standardUserRole, err := m.roleService.Read(portainer.RoleID(3))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
standardUserRole.Priority = 3
|
||||
standardUserRole.Authorizations = authorization.DefaultEndpointAuthorizationsForStandardUserRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
if err := m.roleService.Update(standardUserRole.ID, standardUserRole); err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.roleService.Update(standardUserRole.ID, standardUserRole)
|
||||
|
||||
readOnlyUserRole, err := m.roleService.Read(portainer.RoleID(4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
readOnlyUserRole.Priority = 4
|
||||
readOnlyUserRole.Authorizations = authorization.DefaultEndpointAuthorizationsForReadOnlyUserRole(settings.AllowVolumeBrowserForRegularUsers)
|
||||
|
||||
if err := m.roleService.Update(readOnlyUserRole.ID, readOnlyUserRole); err != nil {
|
||||
err = m.roleService.Update(readOnlyUserRole.ID, readOnlyUserRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ func (m *Migrator) updateStacksToDB24() error {
|
||||
for idx := range stacks {
|
||||
stack := &stacks[idx]
|
||||
stack.Status = portainer.StackStatusActive
|
||||
|
||||
if err := m.stackService.Update(stack.ID, stack); err != nil {
|
||||
err := m.stackService.Update(stack.ID, stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func (m *Migrator) updateDockerhubToDB32() error {
|
||||
migrated = true
|
||||
} else {
|
||||
// delete subsequent duplicates
|
||||
m.registryService.Delete(r.ID)
|
||||
m.registryService.Delete(portainer.RegistryID(r.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
@@ -18,7 +20,7 @@ func (m *Migrator) migrateSettingsToDB33() error {
|
||||
}
|
||||
|
||||
log.Info().Msg("setting default kubectl shell image")
|
||||
settings.KubectlShellImage = *m.flags.KubectlShellImage
|
||||
settings.KubectlShellImage = portainer.DefaultKubectlShellImage
|
||||
|
||||
return m.settingsService.UpdateSettings(settings)
|
||||
}
|
||||
|
||||
@@ -75,10 +75,6 @@ func (m *Migrator) updateEdgeStackStatusForDB80() error {
|
||||
|
||||
for _, edgeStack := range edgeStacks {
|
||||
for endpointId, status := range edgeStack.Status {
|
||||
if status.Details == nil {
|
||||
status.Details = &portainer.EdgeStackStatusDetails{}
|
||||
}
|
||||
|
||||
switch status.Type {
|
||||
case portainer.EdgeStackStatusPending:
|
||||
status.Details.Pending = true
|
||||
@@ -97,10 +93,10 @@ func (m *Migrator) updateEdgeStackStatusForDB80() error {
|
||||
edgeStack.Status[endpointId] = status
|
||||
}
|
||||
|
||||
if err := m.edgeStackService.UpdateEdgeStack(edgeStack.ID, &edgeStack); err != nil {
|
||||
err = m.edgeStackService.UpdateEdgeStack(edgeStack.ID, &edgeStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,16 +3,17 @@ package migrator
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/dataservices/dockerhub"
|
||||
"github.com/portainer/portainer/api/dataservices/edgejob"
|
||||
"github.com/portainer/portainer/api/dataservices/edgestack"
|
||||
"github.com/portainer/portainer/api/dataservices/edgestackstatus"
|
||||
"github.com/portainer/portainer/api/dataservices/endpoint"
|
||||
"github.com/portainer/portainer/api/dataservices/endpointgroup"
|
||||
"github.com/portainer/portainer/api/dataservices/endpointrelation"
|
||||
"github.com/portainer/portainer/api/dataservices/extension"
|
||||
"github.com/portainer/portainer/api/dataservices/fdoprofile"
|
||||
"github.com/portainer/portainer/api/dataservices/pendingactions"
|
||||
"github.com/portainer/portainer/api/dataservices/registry"
|
||||
"github.com/portainer/portainer/api/dataservices/resourcecontrol"
|
||||
@@ -27,15 +28,12 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices/user"
|
||||
"github.com/portainer/portainer/api/dataservices/version"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type (
|
||||
// Migrator defines a service to migrate data after a Portainer version update.
|
||||
Migrator struct {
|
||||
flags *portainer.CLIFlags
|
||||
currentDBVersion *models.Version
|
||||
migrations []Migrations
|
||||
|
||||
@@ -43,6 +41,7 @@ type (
|
||||
endpointService *endpoint.Service
|
||||
endpointRelationService *endpointrelation.Service
|
||||
extensionService *extension.Service
|
||||
fdoProfilesService *fdoprofile.Service
|
||||
registryService *registry.Service
|
||||
resourceControlService *resourcecontrol.Service
|
||||
roleService *role.Service
|
||||
@@ -58,7 +57,6 @@ type (
|
||||
authorizationService *authorization.Service
|
||||
dockerhubService *dockerhub.Service
|
||||
edgeStackService *edgestack.Service
|
||||
edgeStackStatusService *edgestackstatus.Service
|
||||
edgeJobService *edgejob.Service
|
||||
TunnelServerService *tunnelserver.Service
|
||||
pendingActionsService *pendingactions.Service
|
||||
@@ -66,12 +64,12 @@ type (
|
||||
|
||||
// MigratorParameters represents the required parameters to create a new Migrator instance.
|
||||
MigratorParameters struct {
|
||||
Flags *portainer.CLIFlags
|
||||
CurrentDBVersion *models.Version
|
||||
EndpointGroupService *endpointgroup.Service
|
||||
EndpointService *endpoint.Service
|
||||
EndpointRelationService *endpointrelation.Service
|
||||
ExtensionService *extension.Service
|
||||
FDOProfilesService *fdoprofile.Service
|
||||
RegistryService *registry.Service
|
||||
ResourceControlService *resourcecontrol.Service
|
||||
RoleService *role.Service
|
||||
@@ -87,7 +85,6 @@ type (
|
||||
AuthorizationService *authorization.Service
|
||||
DockerhubService *dockerhub.Service
|
||||
EdgeStackService *edgestack.Service
|
||||
EdgeStackStatusService *edgestackstatus.Service
|
||||
EdgeJobService *edgejob.Service
|
||||
TunnelServerService *tunnelserver.Service
|
||||
PendingActionsService *pendingactions.Service
|
||||
@@ -97,12 +94,12 @@ type (
|
||||
// NewMigrator creates a new Migrator.
|
||||
func NewMigrator(parameters *MigratorParameters) *Migrator {
|
||||
migrator := &Migrator{
|
||||
flags: parameters.Flags,
|
||||
currentDBVersion: parameters.CurrentDBVersion,
|
||||
endpointGroupService: parameters.EndpointGroupService,
|
||||
endpointService: parameters.EndpointService,
|
||||
endpointRelationService: parameters.EndpointRelationService,
|
||||
extensionService: parameters.ExtensionService,
|
||||
fdoProfilesService: parameters.FDOProfilesService,
|
||||
registryService: parameters.RegistryService,
|
||||
resourceControlService: parameters.ResourceControlService,
|
||||
roleService: parameters.RoleService,
|
||||
@@ -118,7 +115,6 @@ func NewMigrator(parameters *MigratorParameters) *Migrator {
|
||||
authorizationService: parameters.AuthorizationService,
|
||||
dockerhubService: parameters.DockerhubService,
|
||||
edgeStackService: parameters.EdgeStackService,
|
||||
edgeStackStatusService: parameters.EdgeStackStatusService,
|
||||
edgeJobService: parameters.EdgeJobService,
|
||||
TunnelServerService: parameters.TunnelServerService,
|
||||
pendingActionsService: parameters.PendingActionsService,
|
||||
@@ -243,15 +239,8 @@ func (m *Migrator) initMigrations() {
|
||||
m.addMigrations("2.20.2",
|
||||
m.cleanPendingActionsForDeletedEndpointsForDB111,
|
||||
)
|
||||
m.addMigrations("2.22.0",
|
||||
m.migratePendingActionsDataForDB130,
|
||||
)
|
||||
|
||||
m.addMigrations("2.31.0", m.migrateEdgeStacksStatuses_2_31_0)
|
||||
|
||||
m.addMigrations("2.32.0", m.addEndpointRelationForEdgeAgents_2_32_0)
|
||||
|
||||
// Add new migrations above...
|
||||
// Add new migrations below...
|
||||
// One function per migration, each versions migration funcs in the same file.
|
||||
}
|
||||
|
||||
|
||||
@@ -5,48 +5,47 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/pendingactions/actions"
|
||||
"github.com/portainer/portainer/api/pendingactions/handlers"
|
||||
)
|
||||
|
||||
type cleanNAPWithOverridePolicies struct {
|
||||
EndpointGroupID portainer.EndpointGroupID
|
||||
}
|
||||
|
||||
func Test_ConvertCleanNAPWithOverridePoliciesPayload(t *testing.T) {
|
||||
t.Run("test ConvertCleanNAPWithOverridePoliciesPayload", func(t *testing.T) {
|
||||
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
defer store.Close()
|
||||
|
||||
gid := portainer.EndpointGroupID(1)
|
||||
|
||||
testData := []struct {
|
||||
Name string
|
||||
PendingAction portainer.PendingAction
|
||||
Expected any
|
||||
PendingAction portainer.PendingActions
|
||||
Expected *actions.CleanNAPWithOverridePoliciesPayload
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
Name: "test actiondata with EndpointGroupID 1",
|
||||
PendingAction: handlers.NewCleanNAPWithOverridePolicies(
|
||||
1,
|
||||
&gid,
|
||||
),
|
||||
Expected: portainer.EndpointGroupID(1),
|
||||
PendingAction: portainer.PendingActions{
|
||||
EndpointID: 1,
|
||||
Action: "CleanNAPWithOverridePolicies",
|
||||
ActionData: &actions.CleanNAPWithOverridePoliciesPayload{
|
||||
EndpointGroupID: 1,
|
||||
},
|
||||
},
|
||||
Expected: &actions.CleanNAPWithOverridePoliciesPayload{
|
||||
EndpointGroupID: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test actionData nil",
|
||||
PendingAction: handlers.NewCleanNAPWithOverridePolicies(
|
||||
2,
|
||||
nil,
|
||||
),
|
||||
PendingAction: portainer.PendingActions{
|
||||
EndpointID: 2,
|
||||
Action: "CleanNAPWithOverridePolicies",
|
||||
ActionData: nil,
|
||||
},
|
||||
Expected: nil,
|
||||
},
|
||||
{
|
||||
Name: "test actionData empty and expected error",
|
||||
PendingAction: portainer.PendingAction{
|
||||
PendingAction: portainer.PendingActions{
|
||||
EndpointID: 2,
|
||||
Action: actions.CleanNAPWithOverridePolicies,
|
||||
Action: "CleanNAPWithOverridePolicies",
|
||||
ActionData: "",
|
||||
},
|
||||
Expected: nil,
|
||||
@@ -69,24 +68,22 @@ func Test_ConvertCleanNAPWithOverridePoliciesPayload(t *testing.T) {
|
||||
|
||||
for _, endpointPendingAction := range pendingActions {
|
||||
t.Run(d.Name, func(t *testing.T) {
|
||||
if endpointPendingAction.Action == actions.CleanNAPWithOverridePolicies {
|
||||
var payload cleanNAPWithOverridePolicies
|
||||
|
||||
err := endpointPendingAction.UnmarshallActionData(&payload)
|
||||
|
||||
if endpointPendingAction.Action == "CleanNAPWithOverridePolicies" {
|
||||
actionData, err := actions.ConvertCleanNAPWithOverridePoliciesPayload(endpointPendingAction.ActionData)
|
||||
if d.Err && err == nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if d.Expected == nil && payload.EndpointGroupID != 0 {
|
||||
t.Errorf("expected nil, got %d", payload.EndpointGroupID)
|
||||
if d.Expected == nil && actionData != nil {
|
||||
t.Errorf("expected nil , got %d", actionData)
|
||||
}
|
||||
|
||||
if d.Expected != nil {
|
||||
expected := d.Expected.(portainer.EndpointGroupID)
|
||||
if d.Expected != nil && expected != payload.EndpointGroupID {
|
||||
t.Errorf("expected EndpointGroupID %d, got %d", expected, payload.EndpointGroupID)
|
||||
}
|
||||
if d.Expected != nil && actionData == nil {
|
||||
t.Errorf("expected not nil , got %d", actionData)
|
||||
}
|
||||
|
||||
if d.Expected != nil && actionData.EndpointGroupID != d.Expected.EndpointGroupID {
|
||||
t.Errorf("expected EndpointGroupID %d , got %d", d.Expected.EndpointGroupID, actionData.EndpointGroupID)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,6 +2,8 @@ package postinit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
@@ -11,7 +13,6 @@ import (
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/portainer/portainer/api/pendingactions/actions"
|
||||
"github.com/portainer/portainer/pkg/endpoints"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -50,29 +51,17 @@ func (postInitMigrator *PostInitMigrator) PostInitMigrate() error {
|
||||
|
||||
for _, environment := range environments {
|
||||
// edge environments will run after the server starts, in pending actions
|
||||
if endpoints.IsEdgeEndpoint(&environment) {
|
||||
// Skip edge environments that do not have direct connectivity
|
||||
if !endpoints.HasDirectConnectivity(&environment) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Int("endpoint_id", int(environment.ID)).
|
||||
Msg("adding pending action 'PostInitMigrateEnvironment' for environment")
|
||||
|
||||
if err := postInitMigrator.createPostInitMigrationPendingAction(environment.ID); err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Int("endpoint_id", int(environment.ID)).
|
||||
Msg("error creating pending action for environment")
|
||||
if endpointutils.IsEdgeEndpoint(&environment) {
|
||||
log.Info().Msgf("Adding pending action 'PostInitMigrateEnvironment' for environment %d", environment.ID)
|
||||
err = postInitMigrator.createPostInitMigrationPendingAction(environment.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error creating pending action for environment %d", environment.ID)
|
||||
}
|
||||
} else {
|
||||
// Non-edge environments will run before the server starts.
|
||||
if err := postInitMigrator.MigrateEnvironment(&environment); err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Int("endpoint_id", int(environment.ID)).
|
||||
Msg("error running post-init migrations for non-edge environment")
|
||||
// non-edge environments will run before the server starts.
|
||||
err = postInitMigrator.MigrateEnvironment(&environment)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error running post-init migrations for non-edge environment %d", environment.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +74,28 @@ func (postInitMigrator *PostInitMigrator) PostInitMigrate() error {
|
||||
// this function exists for readability, not reusability
|
||||
// TODO: This should be moved into pending actions as part of the pending action migration
|
||||
func (postInitMigrator *PostInitMigrator) createPostInitMigrationPendingAction(environmentID portainer.EndpointID) error {
|
||||
// If there are no pending actions for the given endpoint, create one
|
||||
err := postInitMigrator.dataStore.PendingActions().Create(&portainer.PendingAction{
|
||||
migrateEnvPendingAction := portainer.PendingActions{
|
||||
EndpointID: environmentID,
|
||||
Action: actions.PostInitMigrateEnvironment,
|
||||
})
|
||||
}
|
||||
|
||||
// Get all pending actions and filter them by endpoint, action and action args that are equal to the migrateEnvPendingAction
|
||||
pendingActions, err := postInitMigrator.dataStore.PendingActions().ReadAll()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error retrieving pending actions")
|
||||
return fmt.Errorf("failed to retrieve pending actions for environment %d: %w", environmentID, err)
|
||||
}
|
||||
for _, pendingAction := range pendingActions {
|
||||
if pendingAction.EndpointID == environmentID &&
|
||||
pendingAction.Action == migrateEnvPendingAction.Action &&
|
||||
reflect.DeepEqual(pendingAction.ActionData, migrateEnvPendingAction.ActionData) {
|
||||
log.Debug().Msgf("Migration pending action for environment %d already exists, skipping creating another", environmentID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no pending actions for the given endpoint, create one
|
||||
err = postInitMigrator.dataStore.PendingActions().Create(&migrateEnvPendingAction)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error creating pending action for environment %d", environmentID)
|
||||
}
|
||||
@@ -103,7 +109,7 @@ func (migrator *PostInitMigrator) MigrateEnvironment(environment *portainer.Endp
|
||||
switch {
|
||||
case endpointutils.IsKubernetesEndpoint(environment):
|
||||
// get the kubeclient for the environment, and skip all kube migrations if there's an error
|
||||
kubeclient, err := migrator.kubeFactory.GetPrivilegedKubeClient(environment)
|
||||
kubeclient, err := migrator.kubeFactory.GetKubeClient(environment)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error creating kubeclient for environment: %d", environment.ID)
|
||||
return err
|
||||
|
||||
+16
-20
@@ -13,11 +13,11 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices/edgegroup"
|
||||
"github.com/portainer/portainer/api/dataservices/edgejob"
|
||||
"github.com/portainer/portainer/api/dataservices/edgestack"
|
||||
"github.com/portainer/portainer/api/dataservices/edgestackstatus"
|
||||
"github.com/portainer/portainer/api/dataservices/endpoint"
|
||||
"github.com/portainer/portainer/api/dataservices/endpointgroup"
|
||||
"github.com/portainer/portainer/api/dataservices/endpointrelation"
|
||||
"github.com/portainer/portainer/api/dataservices/extension"
|
||||
"github.com/portainer/portainer/api/dataservices/fdoprofile"
|
||||
"github.com/portainer/portainer/api/dataservices/helmuserrepository"
|
||||
"github.com/portainer/portainer/api/dataservices/pendingactions"
|
||||
"github.com/portainer/portainer/api/dataservices/registry"
|
||||
@@ -40,12 +40,9 @@ import (
|
||||
"github.com/segmentio/encoding/json"
|
||||
)
|
||||
|
||||
var _ dataservices.DataStore = &Store{}
|
||||
|
||||
// Store defines the implementation of portainer.DataStore using
|
||||
// BoltDB as the storage system.
|
||||
type Store struct {
|
||||
flags *portainer.CLIFlags
|
||||
connection portainer.Connection
|
||||
|
||||
fileService portainer.FileService
|
||||
@@ -54,11 +51,11 @@ type Store struct {
|
||||
EdgeGroupService *edgegroup.Service
|
||||
EdgeJobService *edgejob.Service
|
||||
EdgeStackService *edgestack.Service
|
||||
EdgeStackStatusService *edgestackstatus.Service
|
||||
EndpointGroupService *endpointgroup.Service
|
||||
EndpointService *endpoint.Service
|
||||
EndpointRelationService *endpointrelation.Service
|
||||
ExtensionService *extension.Service
|
||||
FDOProfilesService *fdoprofile.Service
|
||||
HelmUserRepositoryService *helmuserrepository.Service
|
||||
RegistryService *registry.Service
|
||||
ResourceControlService *resourcecontrol.Service
|
||||
@@ -104,21 +101,13 @@ func (store *Store) initServices() error {
|
||||
}
|
||||
store.EndpointRelationService = endpointRelationService
|
||||
|
||||
edgeStackService, err := edgestack.NewService(store.connection, func(tx portainer.Transaction, ID portainer.EdgeStackID) {
|
||||
endpointRelationService.Tx(tx).InvalidateEdgeCacheForEdgeStack(ID)
|
||||
})
|
||||
edgeStackService, err := edgestack.NewService(store.connection, endpointRelationService.InvalidateEdgeCacheForEdgeStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.EdgeStackService = edgeStackService
|
||||
endpointRelationService.RegisterUpdateStackFunction(edgeStackService.UpdateEdgeStackFunc, edgeStackService.UpdateEdgeStackFuncTx)
|
||||
|
||||
edgeStackStatusService, err := edgestackstatus.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.EdgeStackStatusService = edgeStackStatusService
|
||||
|
||||
edgeGroupService, err := edgegroup.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -149,6 +138,12 @@ func (store *Store) initServices() error {
|
||||
}
|
||||
store.ExtensionService = extensionService
|
||||
|
||||
fdoProfilesService, err := fdoprofile.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.FDOProfilesService = fdoProfilesService
|
||||
|
||||
helmUserRepositoryService, err := helmuserrepository.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -279,10 +274,6 @@ func (store *Store) EdgeStack() dataservices.EdgeStackService {
|
||||
return store.EdgeStackService
|
||||
}
|
||||
|
||||
func (store *Store) EdgeStackStatus() dataservices.EdgeStackStatusService {
|
||||
return store.EdgeStackStatusService
|
||||
}
|
||||
|
||||
// Environment(Endpoint) gives access to the Environment(Endpoint) data management layer
|
||||
func (store *Store) Endpoint() dataservices.EndpointService {
|
||||
return store.EndpointService
|
||||
@@ -298,6 +289,11 @@ func (store *Store) EndpointRelation() dataservices.EndpointRelationService {
|
||||
return store.EndpointRelationService
|
||||
}
|
||||
|
||||
// FDOProfile gives access to the FDOProfile data management layer
|
||||
func (store *Store) FDOProfile() dataservices.FDOProfileService {
|
||||
return store.FDOProfilesService
|
||||
}
|
||||
|
||||
// HelmUserRepository access the helm user repository settings
|
||||
func (store *Store) HelmUserRepository() dataservices.HelmUserRepositoryService {
|
||||
return store.HelmUserRepositoryService
|
||||
@@ -402,7 +398,7 @@ type storeExport struct {
|
||||
User []portainer.User `json:"users,omitempty"`
|
||||
Version models.Version `json:"version,omitempty"`
|
||||
Webhook []portainer.Webhook `json:"webhooks,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (store *Store) Export(filename string) (err error) {
|
||||
@@ -620,7 +616,7 @@ func (store *Store) Import(filename string) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(s, &backup)
|
||||
err = json.Unmarshal([]byte(s), &backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -32,10 +32,6 @@ func (tx *StoreTx) EdgeStack() dataservices.EdgeStackService {
|
||||
return tx.store.EdgeStackService.Tx(tx.tx)
|
||||
}
|
||||
|
||||
func (tx *StoreTx) EdgeStackStatus() dataservices.EdgeStackStatusService {
|
||||
return tx.store.EdgeStackStatusService.Tx(tx.tx)
|
||||
}
|
||||
|
||||
func (tx *StoreTx) Endpoint() dataservices.EndpointService {
|
||||
return tx.store.EndpointService.Tx(tx.tx)
|
||||
}
|
||||
@@ -48,6 +44,7 @@ func (tx *StoreTx) EndpointRelation() dataservices.EndpointRelationService {
|
||||
return tx.store.EndpointRelationService.Tx(tx.tx)
|
||||
}
|
||||
|
||||
func (tx *StoreTx) FDOProfile() dataservices.FDOProfileService { return nil }
|
||||
func (tx *StoreTx) HelmUserRepository() dataservices.HelmUserRepositoryService { return nil }
|
||||
|
||||
func (tx *StoreTx) Registry() dataservices.RegistryService {
|
||||
@@ -73,10 +70,7 @@ func (tx *StoreTx) Snapshot() dataservices.SnapshotService {
|
||||
}
|
||||
|
||||
func (tx *StoreTx) SSLSettings() dataservices.SSLSettingsService { return nil }
|
||||
|
||||
func (tx *StoreTx) Stack() dataservices.StackService {
|
||||
return tx.store.StackService.Tx(tx.tx)
|
||||
}
|
||||
func (tx *StoreTx) Stack() dataservices.StackService { return nil }
|
||||
|
||||
func (tx *StoreTx) Tag() dataservices.TagService {
|
||||
return tx.store.TagService.Tx(tx.tx)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user