Compare commits
360 Commits
snyk-fix-a
...
chore/remo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2c9911d96 | ||
|
|
a748e15c16 | ||
|
|
cfdb9c126f | ||
|
|
851a3346a9 | ||
|
|
c9aae27b29 | ||
|
|
087848539f | ||
|
|
a74e389521 | ||
|
|
eff6ec9df9 | ||
|
|
8dec95c2cd | ||
|
|
5b02f636d7 | ||
|
|
ac458d0daa | ||
|
|
5b5dc320d5 | ||
|
|
d04747b309 | ||
|
|
07dd6bbe84 | ||
|
|
406ff8812c | ||
|
|
5942f4ff58 | ||
|
|
adf92ce5e0 | ||
|
|
fed3d14adf | ||
|
|
73db588080 | ||
|
|
6769326c8b | ||
|
|
e6d0e297dd | ||
|
|
0cd272211a | ||
|
|
6570f1f8eb | ||
|
|
1c180346e4 | ||
|
|
1d5d1bb12d | ||
|
|
0c27316034 | ||
|
|
d3bed3072b | ||
|
|
329e8bcad5 | ||
|
|
4bdf30c038 | ||
|
|
7793b98813 | ||
|
|
02de7b2715 | ||
|
|
9c0e0607a4 | ||
|
|
baf9c3db0a | ||
|
|
6c193a8a45 | ||
|
|
48a0f40621 | ||
|
|
4dc643acd9 | ||
|
|
1d42db93f1 | ||
|
|
33c3f8460c | ||
|
|
dd0d1737b0 | ||
|
|
3d28a6f877 | ||
|
|
2fc518f221 | ||
|
|
137ce37096 | ||
|
|
e529327851 | ||
|
|
3625ab6faa | ||
|
|
afb024d2a4 | ||
|
|
b2bc4b92d6 | ||
|
|
e5fd0c9595 | ||
|
|
649c1c9cee | ||
|
|
919a854d93 | ||
|
|
7fe0712b61 | ||
|
|
b4a6f6911c | ||
|
|
59d35d26d8 | ||
|
|
95558ed4ad | ||
|
|
e1b474d04f | ||
|
|
9732d1b5d8 | ||
|
|
701410d259 | ||
|
|
123754cee7 | ||
|
|
d75d2ba9ce | ||
|
|
046738c967 | ||
|
|
0436be7bc4 | ||
|
|
94d64997cc | ||
|
|
294d1668d4 | ||
|
|
4bd6618fb9 | ||
|
|
62197a67f7 | ||
|
|
c1dc1b49d1 | ||
|
|
b917e12b62 | ||
|
|
a8ccd2b153 | ||
|
|
68975620c5 | ||
|
|
67d3abcc9d | ||
|
|
90b0cb84f4 | ||
|
|
b22cdb3559 | ||
|
|
37896661d6 | ||
|
|
f38b8234d9 | ||
|
|
52e150fa29 | ||
|
|
929749c0da | ||
|
|
09bf5d03f4 | ||
|
|
ac6f52ab76 | ||
|
|
0ddcad66f3 | ||
|
|
930d9e5628 | ||
|
|
8936ae9b7a | ||
|
|
db9d87c918 | ||
|
|
b59a0ba823 | ||
|
|
2188005b48 | ||
|
|
a1528475ba | ||
|
|
5cbf52377d | ||
|
|
756ac034ec | ||
|
|
1008afd1fe | ||
|
|
563ead85cc | ||
|
|
eba5879ec8 | ||
|
|
b48aa1274d | ||
|
|
3e485c3152 | ||
|
|
dffd45c5f9 | ||
|
|
c1cc8bad77 | ||
|
|
8dcc5e4adb | ||
|
|
4558ce84cf | ||
|
|
adc87b8f8e | ||
|
|
ce8455953e | ||
|
|
a61b18dd93 | ||
|
|
d6a3fe23e9 | ||
|
|
cbaba43842 | ||
|
|
c173888b64 | ||
|
|
82e9e2a895 | ||
|
|
4fee359247 | ||
|
|
9cdc0da615 | ||
|
|
8fd0efa34f | ||
|
|
79bfd8f6fe | ||
|
|
2114c15f55 | ||
|
|
d2f6d1e415 | ||
|
|
241440a474 | ||
|
|
2e19f4ea6d | ||
|
|
95bc508462 | ||
|
|
d78b762f7b | ||
|
|
9dfac98a26 | ||
|
|
e26a607d28 | ||
|
|
6dc1841c14 | ||
|
|
c28be7aced | ||
|
|
dd01165224 | ||
|
|
d484a0eb64 | ||
|
|
fe8e834dbf | ||
|
|
0f0513c684 | ||
|
|
7006c17ce4 | ||
|
|
253a3a2b40 | ||
|
|
1e4c4e2616 | ||
|
|
75f40fe485 | ||
|
|
61e8e68c31 | ||
|
|
583346321e | ||
|
|
4cfa584c7c | ||
|
|
d012a4efc4 | ||
|
|
e0f3a8c0a2 | ||
|
|
bb48ab00cb | ||
|
|
eccc8131dd | ||
|
|
c21921a08d | ||
|
|
573e05d1c7 | ||
|
|
246e351817 | ||
|
|
6775c7b6ec | ||
|
|
881e99df53 | ||
|
|
78dcba614d | ||
|
|
30e23ea5b4 | ||
|
|
e1e81731b9 | ||
|
|
16377221f9 | ||
|
|
a0237852ef | ||
|
|
193e0c7d6f | ||
|
|
77c29ff87e | ||
|
|
2868da296a | ||
|
|
ff10588383 | ||
|
|
6b02d9a1e3 | ||
|
|
9f3d5185b0 | ||
|
|
f94147b07b | ||
|
|
49d02e0386 | ||
|
|
e82d0cfbdb | ||
|
|
c8051b68d4 | ||
|
|
37d4a80769 | ||
|
|
9ef2e27aae | ||
|
|
9e1f80cf37 | ||
|
|
459c95169a | ||
|
|
5048f08b5f | ||
|
|
e785d1572e | ||
|
|
95a4f83466 | ||
|
|
4edf232e41 | ||
|
|
903cf284e7 | ||
|
|
a550bfaedb | ||
|
|
446febb0f6 | ||
|
|
cb9fe2606c | ||
|
|
55211ef00e | ||
|
|
e48ceb15e9 | ||
|
|
1b12cc9f31 | ||
|
|
0365ed8e70 | ||
|
|
7624ff10ee | ||
|
|
535a26412f | ||
|
|
ee5600b6af | ||
|
|
3f51d077ac | ||
|
|
0219d41ba7 | ||
|
|
f3e2ccd487 | ||
|
|
368e6b2a44 | ||
|
|
1100a2bd28 | ||
|
|
16dc66f173 | ||
|
|
c1f94be9b2 | ||
|
|
58947fee69 | ||
|
|
0c995ae1c8 | ||
|
|
f6d6be90e4 | ||
|
|
5488389278 | ||
|
|
69f498c431 | ||
|
|
669327da7c | ||
|
|
191f8e17ee | ||
|
|
ae2bec4bd9 | ||
|
|
367f3dd6d4 | ||
|
|
8f1ac38963 | ||
|
|
7a6ff10268 | ||
|
|
fd91de3571 | ||
|
|
ab3a6f402e | ||
|
|
d3edb7ebd5 | ||
|
|
c23b8b2816 | ||
|
|
724f1f63b7 | ||
|
|
c6ae8467c0 | ||
|
|
56087bcbb3 | ||
|
|
315c1c7e1e | ||
|
|
819dc4d561 | ||
|
|
380a64d546 | ||
|
|
6429546462 | ||
|
|
ebfb71da05 | ||
|
|
ae0b9b1e30 | ||
|
|
e9de484c3e | ||
|
|
83a1ce9d2a | ||
|
|
66fd039933 | ||
|
|
1722257d68 | ||
|
|
7d8b037761 | ||
|
|
cd52e04a5a | ||
|
|
a0fa64781a | ||
|
|
43e3cb476b | ||
|
|
a1a88eb5e4 | ||
|
|
cb79dc18f8 | ||
|
|
e9384a6987 | ||
|
|
90a0e6fe35 | ||
|
|
e5f8466fb9 | ||
|
|
c3110a85b2 | ||
|
|
89eda13eb3 | ||
|
|
c96551e410 | ||
|
|
9f7d5ac842 | ||
|
|
648c1db437 | ||
|
|
4e20d70a99 | ||
|
|
3b2f0ff9eb | ||
|
|
fcb76f570e | ||
|
|
c384d834f5 | ||
|
|
45e2ed3d86 | ||
|
|
6e0f83b99e | ||
|
|
4fe2a7c750 | ||
|
|
f8b8d549fd | ||
|
|
1b0db4971f | ||
|
|
6063f368ea | ||
|
|
8ef584e41c | ||
|
|
ceaee4e175 | ||
|
|
1e21961e6a | ||
|
|
5777c18297 | ||
|
|
ef1d648c07 | ||
|
|
393d1fc91d | ||
|
|
f9fe440401 | ||
|
|
fad376b415 | ||
|
|
d3f094cb18 | ||
|
|
1950c4ca2b | ||
|
|
5232427a5b | ||
|
|
0fac1f85f7 | ||
|
|
70ce4e70d9 | ||
|
|
47f2490059 | ||
|
|
4d123895ea | ||
|
|
36e7981ab7 | ||
|
|
53025178ef | ||
|
|
f71fe87ba7 | ||
|
|
6078234d07 | ||
|
|
fa162cafc1 | ||
|
|
9ef5636718 | ||
|
|
7accdf704c | ||
|
|
d570aee554 | ||
|
|
a7d458f0bd | ||
|
|
1a9d793f2f | ||
|
|
0242c8e4ef | ||
|
|
6c4c958bf0 | ||
|
|
dd1662c8b8 | ||
|
|
fdfebcf731 | ||
|
|
9ce3e7d20d | ||
|
|
bf8b9463d3 | ||
|
|
9375e577b0 | ||
|
|
d95a67a567 | ||
|
|
160e210ffe | ||
|
|
c9eaad6237 | ||
|
|
2edff939ef | ||
|
|
13338c46bb | ||
|
|
ea05814af4 | ||
|
|
0fe2ddf535 | ||
|
|
9af9395b73 | ||
|
|
d9cc7eda51 | ||
|
|
77c3f9131b | ||
|
|
2b2580fb61 | ||
|
|
f870619fb6 | ||
|
|
602e42739e | ||
|
|
326a8abdc7 | ||
|
|
c0f3d0193d | ||
|
|
f9427c8fb2 | ||
|
|
9b02f575ef | ||
|
|
5b4f6098d8 | ||
|
|
ccaf2bedb7 | ||
|
|
88757d2617 | ||
|
|
d79586cf6a | ||
|
|
a9b1a9c194 | ||
|
|
eb5036b96f | ||
|
|
2f0dbf2ae1 | ||
|
|
c79be58700 | ||
|
|
d24e5ff71e | ||
|
|
6536d36c24 | ||
|
|
6174940ac2 | ||
|
|
4c98fcd7db | ||
|
|
ad8054ac1f | ||
|
|
a54c54ef24 | ||
|
|
27095ede22 | ||
|
|
e2789ab354 | ||
|
|
d4f4bb532f | ||
|
|
c6ab5d5717 | ||
|
|
234627f278 | ||
|
|
87214d48be | ||
|
|
a2a35a1851 | ||
|
|
11f0574ad3 | ||
|
|
9fbc6177a6 | ||
|
|
b91e06a60a | ||
|
|
ad3f4ff711 | ||
|
|
7edcfd6eab | ||
|
|
735b2063ea | ||
|
|
bce4d02dd2 | ||
|
|
e84126ec13 | ||
|
|
3a324acb0e | ||
|
|
c6f7427283 | ||
|
|
ace01eac9d | ||
|
|
8d304b78cb | ||
|
|
c17baa36ef | ||
|
|
8cbff097e4 | ||
|
|
294738cb0d | ||
|
|
69bc815acd | ||
|
|
fb62edefbc | ||
|
|
5e35ff8b8a | ||
|
|
20053b1f07 | ||
|
|
cc6c5d45b7 | ||
|
|
f480e0ccf6 | ||
|
|
d85149e328 | ||
|
|
cee241e77c | ||
|
|
8ec9515225 | ||
|
|
d4ffaaef2f | ||
|
|
eda8347091 | ||
|
|
4c23513a41 | ||
|
|
81d1f35bdc | ||
|
|
36c93c7f57 | ||
|
|
b67f404d8d | ||
|
|
95fb5a4baa | ||
|
|
dd372637cb | ||
|
|
c1a4856e9d | ||
|
|
92b7e64689 | ||
|
|
a750259a2c | ||
|
|
87accfce5d | ||
|
|
29f0daa7ea | ||
|
|
a247db7e93 | ||
|
|
1fbaf5fcbf | ||
|
|
c981e6ff7b | ||
|
|
ee1ee633d7 | ||
|
|
a7ab0a5662 | ||
|
|
bed4257194 | ||
|
|
5ee570e075 | ||
|
|
9666c21b8a | ||
|
|
5cf789a8e4 | ||
|
|
6a4a353b92 | ||
|
|
02355acfa8 | ||
|
|
04eb718f88 | ||
|
|
36888b5ad4 | ||
|
|
7bd971f838 | ||
|
|
c3ce4d8b53 | ||
|
|
fb3a31a4fd | ||
|
|
b6852b5e30 | ||
|
|
34e2178752 | ||
|
|
83a17de1c0 | ||
|
|
e5b27d7a57 | ||
|
|
fb14a85483 | ||
|
|
8d4cb5e16b | ||
|
|
ad8b8399c4 | ||
|
|
8ff2fa66b6 |
@@ -83,6 +83,7 @@ overrides:
|
||||
'newlines-between': 'always',
|
||||
},
|
||||
]
|
||||
no-plusplus: off
|
||||
func-style: [error, 'declaration']
|
||||
import/prefer-default-export: off
|
||||
no-use-before-define: ['error', { functions: false }]
|
||||
@@ -99,12 +100,17 @@ overrides:
|
||||
'@typescript-eslint/explicit-module-boundary-types': off
|
||||
'@typescript-eslint/no-unused-vars': 'error'
|
||||
'@typescript-eslint/no-explicit-any': 'error'
|
||||
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either' }]
|
||||
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either', controlComponents: ['Input', 'Checkbox'] }]
|
||||
'react/function-component-definition': ['error', { 'namedComponents': 'function-declaration' }]
|
||||
'react/jsx-no-bind': off
|
||||
'no-await-in-loop': 'off'
|
||||
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }]
|
||||
'regex/invalid': ['error', [{ 'regex': 'data-feather="(.*)"', 'message': 'Please use `react-feather` package instead' }]]
|
||||
'regex/invalid': ['error', [{ 'regex': '<Icon icon="(.*)"', 'message': 'Please directly import the `lucide-react` icon instead of using the string' }]]
|
||||
overrides: # allow props spreading for hoc files
|
||||
- files:
|
||||
- app/**/with*.ts{,x}
|
||||
rules:
|
||||
'react/jsx-props-no-spreading': off
|
||||
- files:
|
||||
- app/**/*.test.*
|
||||
extends:
|
||||
|
||||
9
.github/workflows/lint.yml
vendored
9
.github/workflows/lint.yml
vendored
@@ -23,6 +23,9 @@ jobs:
|
||||
with:
|
||||
node-version: '14'
|
||||
cache: 'yarn'
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19.4
|
||||
- run: yarn --frozen-lockfile
|
||||
|
||||
- name: Run linters
|
||||
@@ -36,3 +39,9 @@ jobs:
|
||||
gofmt_dir: api/
|
||||
- name: Typecheck
|
||||
uses: icrawl/action-tsc@v1
|
||||
- name: GolangCI-Lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
working-directory: api
|
||||
args: -c .golangci.yaml
|
||||
|
||||
8
.github/workflows/nightly-security-scan.yml
vendored
8
.github/workflows/nightly-security-scan.yml
vendored
@@ -101,15 +101,15 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Use golang 1.18
|
||||
- name: Use golang 1.19.4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
go-version: '1.19.4'
|
||||
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 18.x
|
||||
|
||||
- name: Install packages and build
|
||||
run: yarn install && yarn build
|
||||
|
||||
8
.github/workflows/pr-security.yml
vendored
8
.github/workflows/pr-security.yml
vendored
@@ -136,15 +136,15 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Use golang 1.18
|
||||
- name: Use golang 1.19.4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
go-version: '1.19.4'
|
||||
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 18.x
|
||||
|
||||
- name: Install packages and build
|
||||
run: yarn install && yarn build
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ storybook-static
|
||||
.tmp
|
||||
**/.vscode/settings.json
|
||||
**/.vscode/tasks.json
|
||||
.vscode
|
||||
*.DS_Store
|
||||
|
||||
.eslintcache
|
||||
|
||||
10
README.md
10
README.md
@@ -12,21 +12,15 @@ Portainer consists of a single container that can run on any cluster. It can be
|
||||
- [Take5 – get 5 free nodes of Portainer Business for as long as you want them](https://portainer.io/pricing/take5)
|
||||
- [Portainer BE install guide](https://install.portainer.io)
|
||||
|
||||
## Demo
|
||||
|
||||
You can try out the public demo instance: http://demo.portainer.io/ (login with the username **admin** and the password **tryportainer**).
|
||||
|
||||
Please note that the public demo cluster is **reset every 15min**.
|
||||
|
||||
## Latest Version
|
||||
|
||||
Portainer CE is updated regularly. We aim to do an update release every couple of months.
|
||||
|
||||
**The latest version of Portainer is 2.13.x**.
|
||||
[](https://github.com/portainer/portainer/releases/latest)
|
||||
|
||||
## Getting started
|
||||
|
||||
- [Deploy Portainer](https://docs.portainer.io/v/ce-2.9/start/install)
|
||||
- [Deploy Portainer](https://docs.portainer.io/start/install)
|
||||
- [Documentation](https://documentation.portainer.io)
|
||||
- [Contribute to the project](https://documentation.portainer.io/contributing/instructions/)
|
||||
|
||||
|
||||
26
api/.golangci.yaml
Normal file
26
api/.golangci.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
linters:
|
||||
# Disable all linters.
|
||||
disable-all: true
|
||||
enable:
|
||||
- depguard
|
||||
linters-settings:
|
||||
depguard:
|
||||
list-type: denylist
|
||||
include-go-root: true
|
||||
packages:
|
||||
- github.com/sirupsen/logrus
|
||||
- golang.org/x/exp
|
||||
packages-with-error-message:
|
||||
- github.com/sirupsen/logrus: 'logging is allowed only by github.com/rs/zerolog'
|
||||
ignore-file-rules:
|
||||
- "**/*_test.go"
|
||||
# Create additional guards that follow the same configuration pattern.
|
||||
# Results from all guards are aggregated together.
|
||||
# additional-guards:
|
||||
# - list-type: allowlist
|
||||
# include-go-root: false
|
||||
# packages:
|
||||
# - github.com/sirupsen/logrus
|
||||
# # Specify rules by which the linter ignores certain files for consideration.
|
||||
# ignore-file-rules:
|
||||
# - "!**/*_test.go"
|
||||
@@ -2,7 +2,6 @@ package adminmonitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -11,9 +10,9 @@ import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
)
|
||||
|
||||
var logFatalf = log.Fatalf
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const RedirectReasonAdminInitTimeout string = "AdminInitTimeout"
|
||||
|
||||
@@ -22,11 +21,11 @@ type Monitor struct {
|
||||
datastore dataservices.DataStore
|
||||
shutdownCtx context.Context
|
||||
cancellationFunc context.CancelFunc
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
adminInitDisabled bool
|
||||
}
|
||||
|
||||
// New creates a monitor that when started will wait for the timeout duration and then sends the timeout signal to disable the application
|
||||
// New creates a monitor that when started will wait for the timeout duration and then shutdown the application unless it has been initialized.
|
||||
func New(timeout time.Duration, datastore dataservices.DataStore, shutdownCtx context.Context) *Monitor {
|
||||
return &Monitor{
|
||||
timeout: timeout,
|
||||
@@ -49,24 +48,29 @@ func (m *Monitor) Start() {
|
||||
m.cancellationFunc = cancellationFunc
|
||||
|
||||
go func() {
|
||||
log.Println("[DEBUG] [internal,init] [message: start initialization monitor ]")
|
||||
log.Debug().Msg("start initialization monitor")
|
||||
|
||||
select {
|
||||
case <-time.After(m.timeout):
|
||||
initialized, err := m.WasInitialized()
|
||||
if err != nil {
|
||||
logFatalf("%s", err)
|
||||
log.Error().Err(err).Msg("AdminMonitor failed to determine if Portainer is Initialized")
|
||||
return
|
||||
}
|
||||
|
||||
if !initialized {
|
||||
log.Println("[INFO] [internal,init] The Portainer instance timed out for security purposes. To re-enable your Portainer instance, you will need to restart Portainer")
|
||||
log.Info().Msg("the Portainer instance timed out for security purposes, to re-enable your Portainer instance, you will need to restart Portainer")
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.adminInitDisabled = true
|
||||
return
|
||||
}
|
||||
case <-cancellationCtx.Done():
|
||||
log.Println("[DEBUG] [internal,init] [message: canceling initialization monitor]")
|
||||
log.Debug().Msg("canceling initialization monitor")
|
||||
case <-m.shutdownCtx.Done():
|
||||
log.Println("[DEBUG] [internal,init] [message: shutting down initialization monitor]")
|
||||
log.Debug().Msg("shutting down initialization monitor")
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -79,6 +83,7 @@ func (m *Monitor) Stop() {
|
||||
if m.cancellationFunc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.cancellationFunc()
|
||||
m.cancellationFunc = nil
|
||||
}
|
||||
@@ -89,12 +94,14 @@ func (m *Monitor) WasInitialized() (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(users) > 0, nil
|
||||
}
|
||||
|
||||
func (m *Monitor) WasInstanceDisabled() bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
return m.adminInitDisabled
|
||||
}
|
||||
|
||||
@@ -102,12 +109,10 @@ func (m *Monitor) WasInstanceDisabled() bool {
|
||||
// Otherwise, it will pass through the request to next
|
||||
func (m *Monitor) WithRedirect(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if m.WasInstanceDisabled() {
|
||||
if strings.HasPrefix(r.RequestURI, "/api") && r.RequestURI != "/api/status" && r.RequestURI != "/api/settings/public" {
|
||||
w.Header().Set("redirect-reason", RedirectReasonAdminInitTimeout)
|
||||
httperror.WriteError(w, http.StatusSeeOther, "Administrator initialization timeout", nil)
|
||||
return
|
||||
}
|
||||
if m.WasInstanceDisabled() && strings.HasPrefix(r.RequestURI, "/api") && r.RequestURI != "/api/status" && r.RequestURI != "/api/settings/public" {
|
||||
w.Header().Set("redirect-reason", RedirectReasonAdminInitTimeout)
|
||||
httperror.WriteError(w, http.StatusSeeOther, "Administrator initialization timeout", nil)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
71
api/agent/version.go
Normal file
71
api/agent/version.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/url"
|
||||
)
|
||||
|
||||
// GetAgentVersionAndPlatform returns the agent version and platform
|
||||
//
|
||||
// it sends a ping to the agent and parses the version and platform from the headers
|
||||
func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (portainer.AgentPlatform, string, error) {
|
||||
httpCli := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
if tlsConfig != nil {
|
||||
httpCli.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
parsedURL, err := url.ParseURL(endpointUrl + "/ping")
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
parsedURL.Scheme = "https"
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, parsedURL.String(), nil)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
resp, err := httpCli.Do(req)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return 0, "", fmt.Errorf("Failed request with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
version := resp.Header.Get(portainer.PortainerAgentHeader)
|
||||
if version == "" {
|
||||
return 0, "", errors.New("Version Header is missing")
|
||||
}
|
||||
|
||||
agentPlatformHeader := resp.Header.Get(portainer.HTTPResponseAgentPlatform)
|
||||
if agentPlatformHeader == "" {
|
||||
return 0, "", errors.New("Agent Platform Header is missing")
|
||||
}
|
||||
|
||||
agentPlatformNumber, err := strconv.Atoi(agentPlatformHeader)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
if agentPlatformNumber == 0 {
|
||||
return 0, "", errors.New("Agent platform is invalid")
|
||||
}
|
||||
|
||||
return portainer.AgentPlatform(agentPlatformNumber), version, nil
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const portainerAPIKeyPrefix = "ptr_"
|
||||
|
||||
@@ -2,7 +2,7 @@ package apikey
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"log"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
|
||||
@@ -20,7 +22,7 @@ func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
|
||||
func Test_GenerateApiKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -74,7 +76,7 @@ func Test_GenerateApiKey(t *testing.T) {
|
||||
func Test_GetAPIKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -94,7 +96,7 @@ func Test_GetAPIKey(t *testing.T) {
|
||||
func Test_GetAPIKeys(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -115,7 +117,7 @@ func Test_GetAPIKeys(t *testing.T) {
|
||||
func Test_GetDigestUserAndKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -151,7 +153,7 @@ func Test_GetDigestUserAndKey(t *testing.T) {
|
||||
func Test_UpdateAPIKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -169,11 +171,9 @@ func Test_UpdateAPIKey(t *testing.T) {
|
||||
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
|
||||
is.NoError(err)
|
||||
|
||||
log.Println(apiKey)
|
||||
log.Println(apiKeyGot)
|
||||
log.Debug().Str("wanted", fmt.Sprintf("%+v", apiKey)).Str("got", fmt.Sprintf("%+v", apiKeyGot)).Msg("")
|
||||
|
||||
is.Equal(apiKey.LastUsed, apiKeyGot.LastUsed)
|
||||
|
||||
})
|
||||
|
||||
t.Run("Successfully updates api-key in cache upon api-key update", func(t *testing.T) {
|
||||
@@ -199,7 +199,7 @@ func Test_UpdateAPIKey(t *testing.T) {
|
||||
func Test_DeleteAPIKey(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
@@ -240,7 +240,7 @@ func Test_DeleteAPIKey(t *testing.T) {
|
||||
func Test_InvalidateUserKeyCache(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
|
||||
|
||||
@@ -34,3 +34,45 @@ func TarFileInBuffer(fileContent []byte, fileName string, mode int64) ([]byte, e
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// tarFileInBuffer represents a tar archive buffer.
|
||||
type tarFileInBuffer struct {
|
||||
b *bytes.Buffer
|
||||
w *tar.Writer
|
||||
}
|
||||
|
||||
func NewTarFileInBuffer() *tarFileInBuffer {
|
||||
var b bytes.Buffer
|
||||
return &tarFileInBuffer{
|
||||
b: &b,
|
||||
w: tar.NewWriter(&b),
|
||||
}
|
||||
}
|
||||
|
||||
// Put puts a single file to tar archive buffer.
|
||||
func (t *tarFileInBuffer) Put(fileContent []byte, fileName string, mode int64) error {
|
||||
hdr := &tar.Header{
|
||||
Name: fileName,
|
||||
Mode: mode,
|
||||
Size: int64(len(fileContent)),
|
||||
}
|
||||
|
||||
if err := t.w.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := t.w.Write(fileContent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bytes returns the archive as a byte array.
|
||||
func (t *tarFileInBuffer) Bytes() []byte {
|
||||
return t.b.Bytes()
|
||||
}
|
||||
|
||||
func (t *tarFileInBuffer) Close() error {
|
||||
return t.w.Close()
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -27,22 +25,18 @@ func listFiles(dir string) []string {
|
||||
}
|
||||
|
||||
func Test_shouldCreateArhive(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
|
||||
os.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||
os.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
|
||||
|
||||
gzPath, err := TarGzDir(tmpdir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||
|
||||
extractionDir, _ := ioutils.TempDir("", "extract")
|
||||
defer os.RemoveAll(extractionDir)
|
||||
|
||||
extractionDir := t.TempDir()
|
||||
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
@@ -53,7 +47,7 @@ func Test_shouldCreateArhive(t *testing.T) {
|
||||
wasExtracted := func(p string) {
|
||||
fullpath := path.Join(extractionDir, p)
|
||||
assert.Contains(t, extractedFiles, fullpath)
|
||||
copyContent, _ := ioutil.ReadFile(fullpath)
|
||||
copyContent, _ := os.ReadFile(fullpath)
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
@@ -63,22 +57,18 @@ func Test_shouldCreateArhive(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
|
||||
os.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||
os.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
|
||||
|
||||
gzPath, err := TarGzDir(tmpdir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||
|
||||
extractionDir, _ := ioutils.TempDir("", "extract")
|
||||
defer os.RemoveAll(extractionDir)
|
||||
|
||||
extractionDir := t.TempDir()
|
||||
r, _ := os.Open(gzPath)
|
||||
ExtractTarGz(r, extractionDir)
|
||||
if err != nil {
|
||||
@@ -89,7 +79,7 @@ func Test_shouldCreateArhiveXXXXX(t *testing.T) {
|
||||
wasExtracted := func(p string) {
|
||||
fullpath := path.Join(extractionDir, p)
|
||||
assert.Contains(t, extractedFiles, fullpath)
|
||||
copyContent, _ := ioutil.ReadFile(fullpath)
|
||||
copyContent, _ := os.ReadFile(fullpath)
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// UnzipArchive will unzip an archive from bytes into the dest destination folder on disk
|
||||
@@ -36,7 +36,7 @@ func extractFileFromArchive(file *zip.File, dest string) error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnzipFile(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "unzip-test-")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
dir := t.TempDir()
|
||||
/*
|
||||
Archive structure.
|
||||
├── 0
|
||||
@@ -21,7 +18,7 @@ func TestUnzipFile(t *testing.T) {
|
||||
└── 0.txt
|
||||
*/
|
||||
|
||||
err = UnzipFile("./testdata/sample_archive.zip", dir)
|
||||
err := UnzipFile("./testdata/sample_archive.zip", dir)
|
||||
|
||||
assert.NoError(t, err)
|
||||
archiveDir := dir + "/sample_archive"
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/archive"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/http/offlinegate"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const rwxr__r__ os.FileMode = 0744
|
||||
@@ -47,9 +48,9 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
|
||||
|
||||
err := datastore.Export(exportFilename)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Debugf("failed to export to %s", exportFilename)
|
||||
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
|
||||
} else {
|
||||
logrus.Debugf("exported to %s", exportFilename)
|
||||
log.Debug().Str("filename", exportFilename).Msg("file exported")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package chisel
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
)
|
||||
|
||||
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
|
||||
@@ -23,6 +24,8 @@ func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *por
|
||||
tunnel.Jobs[existingJobIndex] = *edgeJob
|
||||
}
|
||||
|
||||
cache.Del(endpointID)
|
||||
|
||||
service.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -30,8 +33,7 @@ func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *por
|
||||
func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
|
||||
service.mu.Lock()
|
||||
|
||||
for _, tunnel := range service.tunnelDetailsMap {
|
||||
// Filter in-place
|
||||
for endpointID, tunnel := range service.tunnelDetailsMap {
|
||||
n := 0
|
||||
for _, edgeJob := range tunnel.Jobs {
|
||||
if edgeJob.ID != edgeJobID {
|
||||
@@ -41,7 +43,28 @@ func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
|
||||
}
|
||||
|
||||
tunnel.Jobs = tunnel.Jobs[:n]
|
||||
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
|
||||
service.mu.Unlock()
|
||||
}
|
||||
|
||||
func (service *Service) RemoveEdgeJobFromEndpoint(endpointID portainer.EndpointID, edgeJobID portainer.EdgeJobID) {
|
||||
service.mu.Lock()
|
||||
tunnel := service.getTunnelDetails(endpointID)
|
||||
|
||||
n := 0
|
||||
for _, edgeJob := range tunnel.Jobs {
|
||||
if edgeJob.ID != edgeJobID {
|
||||
tunnel.Jobs[n] = edgeJob
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
tunnel.Jobs = tunnel.Jobs[:n]
|
||||
|
||||
cache.Del(endpointID)
|
||||
|
||||
service.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -3,16 +3,17 @@ package chisel
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
chserver "github.com/jpillora/chisel/server"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
chserver "github.com/jpillora/chisel/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -64,7 +65,11 @@ func (service *Service) pingAgent(endpointID portainer.EndpointID) error {
|
||||
// KeepTunnelAlive keeps the tunnel of the given environment for maxAlive duration, or until ctx is done
|
||||
func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx context.Context, maxAlive time.Duration) {
|
||||
go func() {
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: start for %.0f minutes]\n", endpointID, maxAlive.Minutes())
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Float64("max_alive_minutes", maxAlive.Minutes()).
|
||||
Msg("start")
|
||||
|
||||
maxAliveTicker := time.NewTicker(maxAlive)
|
||||
defer maxAliveTicker.Stop()
|
||||
pingTicker := time.NewTicker(tunnelCleanupInterval)
|
||||
@@ -76,14 +81,25 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
|
||||
service.SetTunnelStatusToActive(endpointID)
|
||||
err := service.pingAgent(endpointID)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [warning: ping agent err=%s]\n", endpointID, err)
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Err(err).
|
||||
Msg("ping agent")
|
||||
}
|
||||
case <-maxAliveTicker.C:
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as %.0f minutes timeout]\n", endpointID, maxAlive.Minutes())
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Float64("timeout_minutes", maxAlive.Minutes()).
|
||||
Msg("tunnel keep alive timeout")
|
||||
|
||||
return
|
||||
case <-ctx.Done():
|
||||
err := ctx.Err()
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as err=%s]\n", endpointID, err)
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Err(err).
|
||||
Msg("tunnel stop")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -162,7 +178,10 @@ func (service *Service) retrievePrivateKeySeed() (string, error) {
|
||||
}
|
||||
|
||||
func (service *Service) startTunnelVerificationLoop() {
|
||||
log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
|
||||
log.Debug().
|
||||
Float64("check_interval_seconds", tunnelCleanupInterval.Seconds()).
|
||||
Msg("starting tunnel management process")
|
||||
|
||||
ticker := time.NewTicker(tunnelCleanupInterval)
|
||||
|
||||
for {
|
||||
@@ -170,10 +189,12 @@ func (service *Service) startTunnelVerificationLoop() {
|
||||
case <-ticker.C:
|
||||
service.checkTunnels()
|
||||
case <-service.shutdownCtx.Done():
|
||||
log.Println("[DEBUG] Shutting down tunnel service")
|
||||
log.Debug().Msg("shutting down tunnel service")
|
||||
|
||||
if err := service.StopTunnelServer(); err != nil {
|
||||
log.Printf("Stopped tunnel service: %s", err)
|
||||
log.Debug().Err(err).Msg("stopped tunnel service")
|
||||
}
|
||||
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
@@ -185,32 +206,53 @@ func (service *Service) checkTunnels() {
|
||||
|
||||
service.mu.Lock()
|
||||
for key, tunnel := range service.tunnelDetailsMap {
|
||||
if tunnel.LastActivity.IsZero() || tunnel.Status == portainer.EdgeAgentIdle {
|
||||
continue
|
||||
}
|
||||
|
||||
if tunnel.Status == portainer.EdgeAgentManagementRequired && time.Since(tunnel.LastActivity) < requiredTimeout {
|
||||
continue
|
||||
}
|
||||
|
||||
if tunnel.Status == portainer.EdgeAgentActive && time.Since(tunnel.LastActivity) < activeTimeout {
|
||||
continue
|
||||
}
|
||||
|
||||
tunnels[key] = *tunnel
|
||||
}
|
||||
service.mu.Unlock()
|
||||
|
||||
for endpointID, tunnel := range tunnels {
|
||||
if tunnel.LastActivity.IsZero() || tunnel.Status == portainer.EdgeAgentIdle {
|
||||
continue
|
||||
}
|
||||
|
||||
elapsed := time.Since(tunnel.LastActivity)
|
||||
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [message: environment tunnel monitoring]", endpointID, tunnel.Status, elapsed.Seconds())
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Str("status", tunnel.Status).
|
||||
Float64("status_time_seconds", elapsed.Seconds()).
|
||||
Msg("environment tunnel monitoring")
|
||||
|
||||
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() < requiredTimeout.Seconds() {
|
||||
continue
|
||||
} else if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() > requiredTimeout.Seconds() {
|
||||
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: REQUIRED state timeout exceeded]", endpointID, tunnel.Status, elapsed.Seconds(), requiredTimeout.Seconds())
|
||||
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed > requiredTimeout {
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Str("status", tunnel.Status).
|
||||
Float64("status_time_seconds", elapsed.Seconds()).
|
||||
Float64("timeout_seconds", requiredTimeout.Seconds()).
|
||||
Msg("REQUIRED state timeout exceeded")
|
||||
}
|
||||
|
||||
if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() < activeTimeout.Seconds() {
|
||||
continue
|
||||
} else if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() > activeTimeout.Seconds() {
|
||||
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %d] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: ACTIVE state timeout exceeded]", endpointID, tunnel.Status, elapsed.Seconds(), activeTimeout.Seconds())
|
||||
if tunnel.Status == portainer.EdgeAgentActive && elapsed > activeTimeout {
|
||||
log.Debug().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Str("status", tunnel.Status).
|
||||
Float64("status_time_seconds", elapsed.Seconds()).
|
||||
Float64("timeout_seconds", activeTimeout.Seconds()).
|
||||
Msg("ACTIVE state timeout exceeded")
|
||||
|
||||
err := service.snapshotEnvironment(endpointID, tunnel.Port)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] [snapshot] Unable to snapshot Edge environment (id: %d): %s", endpointID, err)
|
||||
log.Error().
|
||||
Int("endpoint_id", int(endpointID)).
|
||||
Err(err).
|
||||
Msg("unable to snapshot Edge environment")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
"github.com/portainer/libcrypto"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,6 +51,8 @@ func (service *Service) getTunnelDetails(endpointID portainer.EndpointID) *porta
|
||||
|
||||
service.tunnelDetailsMap[endpointID] = tunnel
|
||||
|
||||
cache.Del(endpointID)
|
||||
|
||||
return tunnel
|
||||
}
|
||||
|
||||
@@ -99,6 +103,8 @@ func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID)
|
||||
tunnel.Credentials = ""
|
||||
tunnel.LastActivity = time.Now()
|
||||
service.mu.Unlock()
|
||||
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
|
||||
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified environment(endpoint).
|
||||
@@ -121,6 +127,8 @@ func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
|
||||
service.ProxyManager.DeleteEndpointProxy(endpointID)
|
||||
|
||||
service.mu.Unlock()
|
||||
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
|
||||
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified environment(endpoint).
|
||||
@@ -129,6 +137,8 @@ func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
|
||||
// and generate temporary credentials that can be used to establish a reverse tunnel on that port.
|
||||
// Credentials are encrypted using the Edge ID associated to the environment(endpoint).
|
||||
func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
|
||||
defer cache.Del(endpointID)
|
||||
|
||||
tunnel := service.getTunnelDetails(endpointID)
|
||||
|
||||
service.mu.Lock()
|
||||
|
||||
@@ -2,15 +2,14 @@ package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
@@ -62,6 +61,8 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
MaxBatchSize: kingpin.Flag("max-batch-size", "Maximum size of a batch").Int(),
|
||||
MaxBatchDelay: kingpin.Flag("max-batch-delay", "Maximum delay before a batch starts").Duration(),
|
||||
SecretKeyName: kingpin.Flag("secret-key-name", "Secret key name for encryption and will be used as /run/secrets/<secret-key-name>.").Default(defaultSecretKeyName).String(),
|
||||
LogLevel: kingpin.Flag("log-level", "Set the minimum logging level to show").Default("INFO").Enum("DEBUG", "INFO", "WARN", "ERROR"),
|
||||
LogMode: kingpin.Flag("log-mode", "Set the logging output mode").Default("PRETTY").Enum("PRETTY", "JSON"),
|
||||
}
|
||||
|
||||
kingpin.Parse()
|
||||
@@ -101,11 +102,11 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||
|
||||
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
|
||||
if *flags.NoAnalytics {
|
||||
log.Println("Warning: The --no-analytics flag has been kept to allow migration of instances running a previous version of Portainer with this flag enabled, to version 2.0 where enabling this flag will have no effect.")
|
||||
log.Warn().Msg("the --no-analytics flag has been kept to allow migration of instances running a previous version of Portainer with this flag enabled, to version 2.0 where enabling this flag will have no effect")
|
||||
}
|
||||
|
||||
if *flags.SSL {
|
||||
log.Println("Warning: SSL is enabled by default and there is no need for the --ssl flag. It has been kept to allow migration of instances running a previous version of Portainer with this flag enabled")
|
||||
log.Warn().Msg("SSL is enabled by default and there is no need for the --ssl flag, it has been kept to allow migration of instances running a previous version of Portainer with this flag enabled")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Confirm starts a rollback db cli application
|
||||
func Confirm(message string) (bool, error) {
|
||||
log.Printf("%s [y/N]", message)
|
||||
fmt.Printf("%s [y/N]", message)
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
answer, err := reader.ReadString('\n')
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"strings"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func importFromJson(fileService portainer.FileService, store *datastore.Store) {
|
||||
// EXPERIMENTAL - if used with an incomplete json file, it will fail, as we don't have a way to default the model values
|
||||
importFile := "/data/import.json"
|
||||
if exists, _ := fileService.FileExists(importFile); exists {
|
||||
if err := store.Import(importFile); err != nil {
|
||||
logrus.WithError(err).Debugf("Import %s failed", importFile)
|
||||
|
||||
// TODO: should really rollback on failure, but then we have nothing.
|
||||
} else {
|
||||
logrus.Printf("Successfully imported %s to new portainer database", importFile)
|
||||
}
|
||||
// TODO: this is bad - its to ensure that any defaults that were broken in import, or migrations get set back to what we want
|
||||
// I also suspect that everything from "Init to Init" is potentially a migration
|
||||
err := store.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed initializing data store: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
stdlog "log"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/pkgerrors"
|
||||
)
|
||||
|
||||
func configureLogger() {
|
||||
logger := logrus.New() // logger is to implicitly substitute stdlib's log
|
||||
log.SetOutput(logger.Writer())
|
||||
zerolog.ErrorStackFieldName = "stack_trace"
|
||||
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
|
||||
formatter := &logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true}
|
||||
stdlog.SetFlags(0)
|
||||
stdlog.SetOutput(log.Logger)
|
||||
|
||||
logger.SetFormatter(formatter)
|
||||
logrus.SetFormatter(formatter)
|
||||
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
log.Logger = log.Logger.With().Caller().Stack().Logger()
|
||||
}
|
||||
|
||||
func setLoggingLevel(level string) {
|
||||
switch level {
|
||||
case "ERROR":
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
case "WARN":
|
||||
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||
case "INFO":
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
case "DEBUG":
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func setLoggingMode(mode string) {
|
||||
switch mode {
|
||||
case "PRETTY":
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{
|
||||
Out: os.Stderr,
|
||||
NoColor: true,
|
||||
TimeFormat: "2006/01/02 03:04PM",
|
||||
FormatMessage: formatMessage})
|
||||
case "JSON":
|
||||
log.Logger = log.Output(os.Stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func formatMessage(i interface{}) string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s |", i)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,15 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/portainer/libhelm"
|
||||
libstack "github.com/portainer/docker-compose-wrapper"
|
||||
"github.com/portainer/docker-compose-wrapper/compose"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/apikey"
|
||||
"github.com/portainer/portainer/api/build"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/database"
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/demo"
|
||||
@@ -36,43 +36,51 @@ import (
|
||||
kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/edge"
|
||||
"github.com/portainer/portainer/api/internal/edge/edgestacks"
|
||||
"github.com/portainer/portainer/api/internal/snapshot"
|
||||
"github.com/portainer/portainer/api/internal/ssl"
|
||||
"github.com/portainer/portainer/api/internal/upgrade"
|
||||
"github.com/portainer/portainer/api/jwt"
|
||||
"github.com/portainer/portainer/api/kubernetes"
|
||||
kubecli "github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/portainer/portainer/api/ldap"
|
||||
"github.com/portainer/portainer/api/oauth"
|
||||
"github.com/portainer/portainer/api/scheduler"
|
||||
"github.com/portainer/portainer/api/stacks"
|
||||
"github.com/portainer/portainer/api/stacks/deployments"
|
||||
"github.com/portainer/portainer/pkg/libhelm"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func initCLI() *portainer.CLIFlags {
|
||||
var cliService portainer.CLIService = &cli.Service{}
|
||||
flags, err := cliService.ParseFlags(portainer.APIVersion)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed parsing flags: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed parsing flags")
|
||||
}
|
||||
|
||||
err = cliService.ValidateFlags(flags)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed validating flags:%v", err)
|
||||
log.Fatal().Err(err).Msg("failed validating flags")
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func initFileService(dataStorePath string) portainer.FileService {
|
||||
fileService, err := filesystem.NewService(dataStorePath, "")
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed creating file service: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed creating file service")
|
||||
}
|
||||
|
||||
return fileService
|
||||
}
|
||||
|
||||
func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService portainer.FileService, shutdownCtx context.Context) dataservices.DataStore {
|
||||
connection, err := database.NewDatabase("boltdb", *flags.Data, secretKey)
|
||||
if err != nil {
|
||||
logrus.Fatalf("failed creating database connection: %s", err)
|
||||
log.Fatal().Err(err).Msg("failed creating database connection")
|
||||
}
|
||||
|
||||
if bconn, ok := connection.(*boltdb.DbConnection); ok {
|
||||
@@ -80,80 +88,74 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
bconn.MaxBatchDelay = *flags.MaxBatchDelay
|
||||
bconn.InitialMmapSize = *flags.InitialMmapSize
|
||||
} else {
|
||||
logrus.Fatalf("failed creating database connection: expecting a boltdb database type but a different one was received")
|
||||
log.Fatal().Msg("failed creating database connection: expecting a boltdb database type but a different one was received")
|
||||
}
|
||||
|
||||
store := datastore.NewStore(*flags.Data, fileService, connection)
|
||||
isNew, err := store.Open()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed opening store: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed opening store")
|
||||
}
|
||||
|
||||
if *flags.Rollback {
|
||||
err := store.Rollback(false)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed rolling back: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed rolling back")
|
||||
}
|
||||
|
||||
logrus.Println("Exiting rollback")
|
||||
log.Info().Msg("exiting rollback")
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init sets some defaults - it's basically a migration
|
||||
err = store.Init()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed initializing data store: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing data store")
|
||||
}
|
||||
|
||||
if isNew {
|
||||
// from MigrateData
|
||||
store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
|
||||
err := updateSettingsFromFlags(store, flags)
|
||||
instanceId, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed updating settings from flags: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed generating instance id")
|
||||
}
|
||||
|
||||
// from MigrateData
|
||||
v := models.Version{
|
||||
SchemaVersion: portainer.APIVersion,
|
||||
Edition: int(portainer.PortainerCE),
|
||||
InstanceID: instanceId.String(),
|
||||
}
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
|
||||
err = updateSettingsFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed updating settings from flags")
|
||||
}
|
||||
} else {
|
||||
storedVersion, err := store.VersionService.DBVersion()
|
||||
err = store.MigrateData()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Something Failed during creation of new database: %v", err)
|
||||
}
|
||||
if storedVersion != portainer.DBVersion {
|
||||
err = store.MigrateData()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed migration: %v", err)
|
||||
}
|
||||
log.Fatal().Err(err).Msg("failed migration")
|
||||
}
|
||||
}
|
||||
|
||||
err = updateSettingsFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed updating settings from flags: %v", err)
|
||||
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()
|
||||
|
||||
exportFilename := path.Join(*flags.Data, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
||||
|
||||
err := store.Export(exportFilename)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Debugf("Failed to export to %s", exportFilename)
|
||||
} else {
|
||||
logrus.Debugf("exported to %s", exportFilename)
|
||||
}
|
||||
connection.Close()
|
||||
}()
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
func initComposeStackManager(assetsPath string, configPath string, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
|
||||
composeWrapper, err := exec.NewComposeStackManager(assetsPath, configPath, proxyManager)
|
||||
func initComposeStackManager(composeDeployer libstack.Deployer, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
|
||||
composeWrapper, err := exec.NewComposeStackManager(composeDeployer, proxyManager)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed creating compose manager: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed creating compose manager")
|
||||
}
|
||||
|
||||
return composeWrapper
|
||||
@@ -183,10 +185,15 @@ func initAPIKeyService(datastore dataservices.DataStore) apikey.APIKeyService {
|
||||
}
|
||||
|
||||
func initJWTService(userSessionTimeout string, dataStore dataservices.DataStore) (dataservices.JWTService, error) {
|
||||
if userSessionTimeout == "" {
|
||||
userSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||
}
|
||||
|
||||
jwtService, err := jwt.NewService(userSessionTimeout, dataStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jwtService, nil
|
||||
}
|
||||
|
||||
@@ -206,8 +213,8 @@ func initOAuthService() portainer.OAuthService {
|
||||
return oauth.NewService()
|
||||
}
|
||||
|
||||
func initGitService() portainer.GitService {
|
||||
return git.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) {
|
||||
@@ -231,11 +238,17 @@ func initDockerClientFactory(signatureService portainer.DigitalSignatureService,
|
||||
return docker.NewClientFactory(signatureService, reverseTunnelService)
|
||||
}
|
||||
|
||||
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string, dataStore dataservices.DataStore) *kubecli.ClientFactory {
|
||||
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID, dataStore)
|
||||
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*kubecli.ClientFactory, error) {
|
||||
return kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, addrHTTPS, userSessionTimeout)
|
||||
}
|
||||
|
||||
func initSnapshotService(snapshotIntervalFromFlag string, dataStore dataservices.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory, shutdownCtx context.Context) (portainer.SnapshotService, error) {
|
||||
func initSnapshotService(
|
||||
snapshotIntervalFromFlag string,
|
||||
dataStore dataservices.DataStore,
|
||||
dockerClientFactory *docker.ClientFactory,
|
||||
kubernetesClientFactory *kubecli.ClientFactory,
|
||||
shutdownCtx context.Context,
|
||||
) (portainer.SnapshotService, error) {
|
||||
dockerSnapshotter := docker.NewSnapshotter(dockerClientFactory)
|
||||
kubernetesSnapshotter := kubernetes.NewSnapshotter(kubernetesClientFactory)
|
||||
|
||||
@@ -302,12 +315,7 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
|
||||
sslSettings.HTTPEnabled = true
|
||||
}
|
||||
|
||||
err = dataStore.SSLSettings().UpdateSettings(sslSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return dataStore.SSLSettings().UpdateSettings(sslSettings)
|
||||
}
|
||||
|
||||
// enableFeaturesFromFlags turns on or off feature flags
|
||||
@@ -341,11 +349,7 @@ func enableFeaturesFromFlags(dataStore dataservices.DataStore, flags *portainer.
|
||||
return fmt.Errorf("feature flag's '%s' value should be true or false", feat.Name)
|
||||
}
|
||||
|
||||
if featureState {
|
||||
logrus.Printf("Feature %v : on", *correspondingFeature)
|
||||
} else {
|
||||
logrus.Printf("Feature %v : off", *correspondingFeature)
|
||||
}
|
||||
log.Info().Str("feature", string(*correspondingFeature)).Bool("state", featureState).Msg("")
|
||||
|
||||
settings.FeatureFlagSettings[*correspondingFeature] = featureState
|
||||
}
|
||||
@@ -373,7 +377,7 @@ func generateAndStoreKeyPair(fileService portainer.FileService, signatureService
|
||||
func initKeyPair(fileService portainer.FileService, signatureService portainer.DigitalSignatureService) error {
|
||||
existingKeyPair, err := fileService.KeyPairFilesExist()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed checking for existing key pair: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed checking for existing key pair")
|
||||
}
|
||||
|
||||
if existingKeyPair {
|
||||
@@ -443,7 +447,11 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.
|
||||
|
||||
err := snapshotService.SnapshotEndpoint(endpoint)
|
||||
if err != nil {
|
||||
logrus.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
|
||||
log.Error().
|
||||
Str("endpoint", endpoint.Name).
|
||||
Str("URL", endpoint.URL).
|
||||
Err(err).
|
||||
Msg("environment snapshot error")
|
||||
}
|
||||
|
||||
return dataStore.Endpoint().Create(endpoint)
|
||||
@@ -488,7 +496,10 @@ func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStor
|
||||
|
||||
err := snapshotService.SnapshotEndpoint(endpoint)
|
||||
if err != nil {
|
||||
logrus.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
|
||||
log.Error().
|
||||
Str("endpoint", endpoint.Name).
|
||||
Str("URL", endpoint.URL).Err(err).
|
||||
Msg("environment snapshot error")
|
||||
}
|
||||
|
||||
return dataStore.Endpoint().Create(endpoint)
|
||||
@@ -505,7 +516,8 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, s
|
||||
}
|
||||
|
||||
if len(endpoints) > 0 {
|
||||
logrus.Println("Instance already has defined environments. Skipping the environment defined via CLI.")
|
||||
log.Info().Msg("instance already has defined environments, skipping the environment defined via CLI")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -519,9 +531,9 @@ func loadEncryptionSecretKey(keyfilename string) []byte {
|
||||
content, err := os.ReadFile(path.Join("/run/secrets", keyfilename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
logrus.Printf("Encryption key file `%s` not present", keyfilename)
|
||||
log.Info().Str("filename", keyfilename).Msg("encryption key file not present")
|
||||
} else {
|
||||
logrus.Printf("Error reading encryption key file: %v", err)
|
||||
log.Info().Err(err).Msg("error reading encryption key file")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -538,38 +550,42 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
fileService := initFileService(*flags.Data)
|
||||
encryptionKey := loadEncryptionSecretKey(*flags.SecretKeyName)
|
||||
if encryptionKey == nil {
|
||||
logrus.Println("Proceeding without encryption key")
|
||||
log.Info().Msg("proceeding without encryption key")
|
||||
}
|
||||
|
||||
dataStore := initDataStore(flags, encryptionKey, fileService, shutdownCtx)
|
||||
|
||||
if err := dataStore.CheckCurrentEdition(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
instanceID, err := dataStore.Version().InstanceID()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed getting instance id: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed getting instance id")
|
||||
}
|
||||
|
||||
apiKeyService := initAPIKeyService(dataStore)
|
||||
|
||||
settings, err := dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
jwtService, err := initJWTService(settings.UserSessionTimeout, dataStore)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed initializing JWT service: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing JWT service")
|
||||
}
|
||||
|
||||
err = enableFeaturesFromFlags(dataStore, flags)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed enabling feature flag: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed enabling feature flag")
|
||||
}
|
||||
|
||||
ldapService := initLDAPService()
|
||||
|
||||
oauthService := initOAuthService()
|
||||
gitService := initGitService()
|
||||
|
||||
gitService := initGitService(shutdownCtx)
|
||||
|
||||
openAMTService := openamt.NewService()
|
||||
|
||||
@@ -577,29 +593,31 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
digitalSignatureService := initDigitalSignatureService()
|
||||
|
||||
edgeStacksService := edgestacks.NewService(dataStore)
|
||||
|
||||
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
sslSettings, err := sslService.GetSSLSettings()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to get ssl settings: %s", err)
|
||||
log.Fatal().Err(err).Msg("failed to get SSL settings")
|
||||
}
|
||||
|
||||
err = initKeyPair(fileService, digitalSignatureService)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed initializing key pair: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing key pair")
|
||||
}
|
||||
|
||||
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
|
||||
|
||||
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
|
||||
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID, dataStore)
|
||||
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
|
||||
|
||||
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed initializing snapshot service: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing snapshot service")
|
||||
}
|
||||
snapshotService.Start()
|
||||
|
||||
@@ -616,23 +634,28 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
dockerConfigPath := fileService.GetDockerConfigPath()
|
||||
|
||||
composeStackManager := initComposeStackManager(*flags.Assets, dockerConfigPath, reverseTunnelService, proxyManager)
|
||||
composeDeployer, err := compose.NewComposeDeployer(*flags.Assets, dockerConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing compose deployer")
|
||||
}
|
||||
|
||||
composeStackManager := initComposeStackManager(composeDeployer, reverseTunnelService, proxyManager)
|
||||
|
||||
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed initializing swarm stack manager: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing swarm stack manager")
|
||||
}
|
||||
|
||||
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
|
||||
|
||||
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed initializing helm package manager: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing helm package manager")
|
||||
}
|
||||
|
||||
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed loading edge jobs from database: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed loading edge jobs from database")
|
||||
}
|
||||
|
||||
applicationStatus := initStatus(instanceID)
|
||||
@@ -641,24 +664,25 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
if *flags.DemoEnvironment {
|
||||
err := demoService.Init(dataStore, cryptoService)
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing demo environment: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing demo environment")
|
||||
}
|
||||
}
|
||||
|
||||
err = initEndpoint(flags, dataStore, snapshotService)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed initializing environment: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed initializing environment")
|
||||
}
|
||||
|
||||
adminPasswordHash := ""
|
||||
if *flags.AdminPasswordFile != "" {
|
||||
content, err := fileService.GetFileContent(*flags.AdminPasswordFile, "")
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed getting admin password file: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed getting admin password file")
|
||||
}
|
||||
|
||||
adminPasswordHash, err = cryptoService.Hash(strings.TrimSuffix(string(content), "\n"))
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed hashing admin password: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed hashing admin password")
|
||||
}
|
||||
} else if *flags.AdminPassword != "" {
|
||||
adminPasswordHash = *flags.AdminPassword
|
||||
@@ -667,38 +691,61 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
if adminPasswordHash != "" {
|
||||
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed getting admin user: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed getting admin user")
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
logrus.Println("Created admin user with the given password.")
|
||||
log.Info().Msg("created admin user with the given password.")
|
||||
user := &portainer.User{
|
||||
Username: "admin",
|
||||
Role: portainer.AdministratorRole,
|
||||
Password: adminPasswordHash,
|
||||
}
|
||||
|
||||
err := dataStore.User().Create(user)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed creating admin user: %v", err)
|
||||
log.Fatal().Err(err).Msg("failed creating admin user")
|
||||
}
|
||||
} else {
|
||||
logrus.Println("Instance already has an administrator user defined. Skipping admin password related flags.")
|
||||
log.Info().Msg("instance already has an administrator user defined, skipping admin password related flags.")
|
||||
}
|
||||
}
|
||||
|
||||
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed starting tunnel server: %v", err)
|
||||
}
|
||||
|
||||
sslDBSettings, err := dataStore.SSLSettings().Settings()
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to fetch ssl settings from DB")
|
||||
log.Fatal().Err(err).Msg("failed starting tunnel server")
|
||||
}
|
||||
|
||||
scheduler := scheduler.NewScheduler(shutdownCtx)
|
||||
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
|
||||
stacks.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
|
||||
stackDeployer := deployments.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
|
||||
deployments.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
|
||||
|
||||
sslDBSettings, err := dataStore.SSLSettings().Settings()
|
||||
if err != nil {
|
||||
log.Fatal().Msg("failed to fetch SSL settings from DB")
|
||||
}
|
||||
|
||||
upgradeService, err := upgrade.NewService(*flags.Assets, composeDeployer)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed initializing upgrade service")
|
||||
}
|
||||
|
||||
// FIXME: In 2.16 we changed the way ingress controller permissions are
|
||||
// stored. Instead of being stored as annotation on an ingress rule, we keep
|
||||
// them in our database. However, in order to run the migration we need an
|
||||
// admin kube client to run lookup the old ingress rules and compare them
|
||||
// with the current existing ingress classes.
|
||||
//
|
||||
// Unfortunately, our migrations run as part of the database initialization
|
||||
// and our kubeclients require an initialized database. So it is not
|
||||
// possible to do this migration as part of our normal flow. We DO have a
|
||||
// migration which toggles a boolean in kubernetes configuration that
|
||||
// indicated that this "post init" migration should be run. If/when this is
|
||||
// resolved we can remove this function.
|
||||
err = kubernetesClientFactory.PostInitMigrateIngresses()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failure during post init migrations")
|
||||
}
|
||||
|
||||
return &http.Server{
|
||||
AuthorizationService: authorizationService,
|
||||
@@ -709,12 +756,13 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
HTTPEnabled: sslDBSettings.HTTPEnabled,
|
||||
AssetsPath: *flags.Assets,
|
||||
DataStore: dataStore,
|
||||
EdgeStacksService: edgeStacksService,
|
||||
SwarmStackManager: swarmStackManager,
|
||||
ComposeStackManager: composeStackManager,
|
||||
KubernetesDeployer: kubernetesDeployer,
|
||||
HelmPackageManager: helmPackageManager,
|
||||
CryptoService: cryptoService,
|
||||
APIKeyService: apiKeyService,
|
||||
CryptoService: cryptoService,
|
||||
JWTService: jwtService,
|
||||
FileService: fileService,
|
||||
LDAPService: ldapService,
|
||||
@@ -734,26 +782,34 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
ShutdownTrigger: shutdownTrigger,
|
||||
StackDeployer: stackDeployer,
|
||||
DemoService: demoService,
|
||||
UpgradeService: upgradeService,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flags := initCLI()
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
configureLogger()
|
||||
setLoggingMode("PRETTY")
|
||||
|
||||
flags := initCLI()
|
||||
|
||||
setLoggingLevel(*flags.LogLevel)
|
||||
setLoggingMode(*flags.LogMode)
|
||||
|
||||
for {
|
||||
server := buildServer(flags)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"Version": portainer.APIVersion,
|
||||
"BuildNumber": build.BuildNumber,
|
||||
"ImageTag": build.ImageTag,
|
||||
"NodejsVersion": build.NodejsVersion,
|
||||
"YarnVersion": build.YarnVersion,
|
||||
"WebpackVersion": build.WebpackVersion,
|
||||
"GoVersion": build.GoVersion},
|
||||
).Print("[INFO] [cmd,main] Starting Portainer")
|
||||
log.Info().
|
||||
Str("version", portainer.APIVersion).
|
||||
Str("build_number", build.BuildNumber).
|
||||
Str("image_tag", build.ImageTag).
|
||||
Str("nodejs_version", build.NodejsVersion).
|
||||
Str("yarn_version", build.YarnVersion).
|
||||
Str("webpack_version", build.WebpackVersion).
|
||||
Str("go_version", build.GoVersion).
|
||||
Msg("starting Portainer")
|
||||
|
||||
err := server.Start()
|
||||
logrus.Printf("[INFO] [cmd,main] Http server exited: %v\n", err)
|
||||
log.Info().Err(err).Msg("HTTP server exited")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
@@ -21,7 +22,7 @@ func (m mockKingpinSetting) SetValue(value kingpin.Value) {
|
||||
func Test_enableFeaturesFromFlags(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
tests := []struct {
|
||||
@@ -76,7 +77,7 @@ func Test_optionalFeature(t *testing.T) {
|
||||
|
||||
is := assert.New(t)
|
||||
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
// Enable the test feature
|
||||
|
||||
@@ -24,13 +24,13 @@ type Connection interface {
|
||||
SetServiceName(bucketName string) error
|
||||
GetObject(bucketName string, key []byte, object interface{}) error
|
||||
UpdateObject(bucketName string, key []byte, object interface{}) error
|
||||
UpdateObjectFunc(bucketName string, key []byte, object any, updateFn func()) error
|
||||
DeleteObject(bucketName string, key []byte) error
|
||||
DeleteAllObjects(bucketName string, matching func(o interface{}) (id int, ok bool)) error
|
||||
DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error
|
||||
GetNextIdentifier(bucketName string) int
|
||||
CreateObject(bucketName string, fn func(uint64) (int, interface{})) error
|
||||
CreateObjectWithId(bucketName string, id int, obj interface{}) error
|
||||
CreateObjectWithStringId(bucketName string, id []byte, obj interface{}) error
|
||||
CreateObjectWithSetSequence(bucketName string, id int, obj interface{}) error
|
||||
GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
ConvertToKey(v int) []byte
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// Person with better knowledge is welcomed to improve it.
|
||||
// sourced from https://golang.org/src/crypto/cipher/example_test.go
|
||||
|
||||
var emptySalt []byte = make([]byte, 0, 0)
|
||||
var emptySalt []byte = make([]byte, 0)
|
||||
|
||||
// AesEncrypt reads from input, encrypts with AES-256 and writes to the output.
|
||||
// passphrase is used to generate an encryption key.
|
||||
|
||||
@@ -2,18 +2,15 @@ package crypto
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "encrypt")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
var (
|
||||
originFilePath = filepath.Join(tmpdir, "origin")
|
||||
@@ -22,7 +19,7 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(originFilePath, content, 0600)
|
||||
os.WriteFile(originFilePath, content, 0600)
|
||||
|
||||
originFile, _ := os.Open(originFilePath)
|
||||
defer originFile.Close()
|
||||
@@ -32,7 +29,7 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
|
||||
err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
|
||||
assert.Nil(t, err, "Failed to encrypt a file")
|
||||
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
|
||||
encryptedContent, err := os.ReadFile(encryptedFilePath)
|
||||
assert.Nil(t, err, "Couldn't read encrypted file")
|
||||
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
|
||||
|
||||
@@ -47,13 +44,12 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
|
||||
io.Copy(decryptedFileWriter, decryptedReader)
|
||||
|
||||
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
|
||||
decryptedContent, _ := os.ReadFile(decryptedFilePath)
|
||||
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
|
||||
}
|
||||
|
||||
func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "encrypt")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
var (
|
||||
originFilePath = filepath.Join(tmpdir, "origin")
|
||||
@@ -62,7 +58,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||
)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(originFilePath, content, 0600)
|
||||
os.WriteFile(originFilePath, content, 0600)
|
||||
|
||||
originFile, _ := os.Open(originFilePath)
|
||||
defer originFile.Close()
|
||||
@@ -72,7 +68,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||
|
||||
err := AesEncrypt(originFile, encryptedFileWriter, []byte(""))
|
||||
assert.Nil(t, err, "Failed to encrypt a file")
|
||||
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
|
||||
encryptedContent, err := os.ReadFile(encryptedFilePath)
|
||||
assert.Nil(t, err, "Couldn't read encrypted file")
|
||||
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
|
||||
|
||||
@@ -87,13 +83,12 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||
|
||||
io.Copy(decryptedFileWriter, decryptedReader)
|
||||
|
||||
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
|
||||
decryptedContent, _ := os.ReadFile(decryptedFilePath)
|
||||
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
|
||||
}
|
||||
|
||||
func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T) {
|
||||
tmpdir, _ := ioutils.TempDir("", "encrypt")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
var (
|
||||
originFilePath = filepath.Join(tmpdir, "origin")
|
||||
@@ -102,7 +97,7 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
|
||||
)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(originFilePath, content, 0600)
|
||||
os.WriteFile(originFilePath, content, 0600)
|
||||
|
||||
originFile, _ := os.Open(originFilePath)
|
||||
defer originFile.Close()
|
||||
@@ -112,7 +107,7 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
|
||||
|
||||
err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
|
||||
assert.Nil(t, err, "Failed to encrypt a file")
|
||||
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
|
||||
encryptedContent, err := os.ReadFile(encryptedFilePath)
|
||||
assert.Nil(t, err, "Couldn't read encrypted file")
|
||||
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
|
||||
|
||||
@@ -127,6 +122,6 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
|
||||
|
||||
io.Copy(decryptedFileWriter, decryptedReader)
|
||||
|
||||
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
|
||||
decryptedContent, _ := os.ReadFile(decryptedFilePath)
|
||||
assert.NotEqual(t, content, decryptedContent, "Original and decrypted content should NOT match")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package crypto
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// CreateServerTLSConfiguration creates a basic tls.Config to be used by servers with recommended TLS settings
|
||||
@@ -63,7 +63,7 @@ func CreateTLSConfigurationFromDisk(caCertPath, certPath, keyPath string, skipSe
|
||||
}
|
||||
|
||||
if !skipServerVerification && caCertPath != "" {
|
||||
caCert, err := ioutil.ReadFile(caCertPath)
|
||||
caCert, err := os.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package boltdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@@ -120,7 +121,7 @@ func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
|
||||
// Open opens and initializes the BoltDB database.
|
||||
func (connection *DbConnection) Open() error {
|
||||
|
||||
logrus.Infof("Loading PortainerDB: %s", connection.GetDatabaseFileName())
|
||||
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
|
||||
|
||||
// Now we open the db
|
||||
databasePath := connection.GetDatabaseFilePath()
|
||||
@@ -161,11 +162,11 @@ func (connection *DbConnection) ExportRaw(filename string) error {
|
||||
return fmt.Errorf("stat on %s failed: %s", databasePath, err)
|
||||
}
|
||||
|
||||
b, err := connection.ExportJson(databasePath, true)
|
||||
b, err := connection.ExportJSON(databasePath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filename, b, 0600)
|
||||
return os.WriteFile(filename, b, 0600)
|
||||
}
|
||||
|
||||
// ConvertToKey returns an 8-byte big endian representation of v.
|
||||
@@ -177,7 +178,7 @@ func (connection *DbConnection) ConvertToKey(v int) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
// CreateBucket is a generic function used to create a bucket inside a database database.
|
||||
// CreateBucket is a generic function used to create a bucket inside a database.
|
||||
func (connection *DbConnection) SetServiceName(bucketName string) error {
|
||||
return connection.Batch(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
||||
@@ -185,7 +186,7 @@ func (connection *DbConnection) SetServiceName(bucketName string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetObject is a generic function used to retrieve an unmarshalled object from a database database.
|
||||
// GetObject is a generic function used to retrieve an unmarshalled object from a database.
|
||||
func (connection *DbConnection) GetObject(bucketName string, key []byte, object interface{}) error {
|
||||
var data []byte
|
||||
|
||||
@@ -217,7 +218,7 @@ func (connection *DbConnection) getEncryptionKey() []byte {
|
||||
return connection.EncryptionKey
|
||||
}
|
||||
|
||||
// UpdateObject is a generic function used to update an object inside a database database.
|
||||
// UpdateObject is a generic function used to update an object inside a database.
|
||||
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object interface{}) error {
|
||||
data, err := connection.MarshalObject(object)
|
||||
if err != nil {
|
||||
@@ -230,7 +231,33 @@ func (connection *DbConnection) UpdateObject(bucketName string, key []byte, obje
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteObject is a generic function used to delete an object inside a database database.
|
||||
// UpdateObjectFunc is a generic function used to update an object safely without race conditions.
|
||||
func (connection *DbConnection) UpdateObjectFunc(bucketName string, key []byte, object any, updateFn func()) error {
|
||||
return connection.Batch(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
data := bucket.Get(key)
|
||||
if data == nil {
|
||||
return dserrors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
err := connection.UnmarshalObjectWithJsoniter(data, object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateFn()
|
||||
|
||||
data, err = connection.MarshalObject(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteObject is a generic function used to delete an object inside a database.
|
||||
func (connection *DbConnection) DeleteObject(bucketName string, key []byte) error {
|
||||
return connection.Batch(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
@@ -240,13 +267,12 @@ func (connection *DbConnection) DeleteObject(bucketName string, key []byte) erro
|
||||
|
||||
// DeleteAllObjects delete all objects where matching() returns (id, ok).
|
||||
// TODO: think about how to return the error inside (maybe change ok to type err, and use "notfound"?
|
||||
func (connection *DbConnection) DeleteAllObjects(bucketName string, matching func(o interface{}) (id int, ok bool)) error {
|
||||
func (connection *DbConnection) DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error {
|
||||
return connection.Batch(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var obj interface{}
|
||||
err := connection.UnmarshalObject(v, &obj)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -324,30 +350,10 @@ func (connection *DbConnection) CreateObjectWithStringId(bucketName string, id [
|
||||
})
|
||||
}
|
||||
|
||||
// CreateObjectWithSetSequence creates a new object in the bucket, using the specified id, and sets the bucket sequence
|
||||
// avoid this :)
|
||||
func (connection *DbConnection) CreateObjectWithSetSequence(bucketName string, id int, obj interface{}) error {
|
||||
return connection.Batch(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
// We manually manage sequences for schedules
|
||||
err := bucket.SetSequence(uint64(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := connection.MarshalObject(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(connection.ConvertToKey(id), data)
|
||||
})
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(bucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
err := connection.UnmarshalObject(v, obj)
|
||||
@@ -362,6 +368,7 @@ func (connection *DbConnection) GetAll(bucketName string, obj interface{}, appen
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -387,13 +394,33 @@ func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interf
|
||||
return err
|
||||
}
|
||||
|
||||
func (connection *DbConnection) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
||||
return connection.View(func(tx *bolt.Tx) error {
|
||||
cursor := tx.Bucket([]byte(bucketName)).Cursor()
|
||||
|
||||
for k, v := cursor.Seek(keyPrefix); k != nil && bytes.HasPrefix(k, keyPrefix); k, v = cursor.Next() {
|
||||
err := connection.UnmarshalObjectWithJsoniter(v, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj, err = append(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BackupMetadata will return a copy of the boltdb sequence numbers for all buckets.
|
||||
func (connection *DbConnection) BackupMetadata() (map[string]interface{}, error) {
|
||||
buckets := map[string]interface{}{}
|
||||
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
bucketName := string(name)
|
||||
bucket = tx.Bucket([]byte(bucketName))
|
||||
seqId := bucket.Sequence()
|
||||
buckets[bucketName] = int(seqId)
|
||||
return nil
|
||||
@@ -405,13 +432,14 @@ func (connection *DbConnection) BackupMetadata() (map[string]interface{}, error)
|
||||
return buckets, err
|
||||
}
|
||||
|
||||
// RestoreMetadata will restore the boltdb sequence numbers for all buckets.
|
||||
func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error {
|
||||
var err error
|
||||
|
||||
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 {
|
||||
logrus.Errorf("Failed to restore metadata to bucket %s, skipped", bucketName)
|
||||
log.Error().Str("bucket", bucketName).Msg("failed to restore metadata to bucket, skipped")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -420,6 +448,7 @@ func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.SetSequence(uint64(id))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) {
|
||||
err := connection.View(func(tx *bolt.Tx) error {
|
||||
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||
bucketName := string(name)
|
||||
bucket = tx.Bucket([]byte(bucketName))
|
||||
seqId := bucket.Sequence()
|
||||
buckets[bucketName] = int(seqId)
|
||||
return nil
|
||||
@@ -28,11 +27,11 @@ func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) {
|
||||
|
||||
// ExportJSON creates a JSON representation from a DbConnection. You can include
|
||||
// the database's metadata or ignore it. Ensure the database is closed before
|
||||
// using this function
|
||||
// using this function.
|
||||
// inspired by github.com/konoui/boltdb-exporter (which has no license)
|
||||
// but very much simplified, based on how we use boltdb
|
||||
func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, error) {
|
||||
logrus.WithField("databasePath", databasePath).Infof("exportJson")
|
||||
func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, error) {
|
||||
log.Debug().Str("databasePath", databasePath).Msg("exportJson")
|
||||
|
||||
connection, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second, ReadOnly: true})
|
||||
if err != nil {
|
||||
@@ -44,8 +43,9 @@ func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, e
|
||||
if metadata {
|
||||
meta, err := backupMetadata(connection)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("Failed exporting metadata: %v", err)
|
||||
log.Error().Err(err).Msg("failed exporting metadata")
|
||||
}
|
||||
|
||||
backup["__metadata"] = meta
|
||||
}
|
||||
|
||||
@@ -59,22 +59,31 @@ func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, e
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var obj interface{}
|
||||
err := c.UnmarshalObject(v, &obj)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("Failed to unmarshal (bucket %s): %v", bucketName, string(v))
|
||||
log.Error().
|
||||
Str("bucket", bucketName).
|
||||
Str("object", string(v)).
|
||||
Err(err).
|
||||
Msg("failed to unmarshal")
|
||||
|
||||
obj = v
|
||||
}
|
||||
|
||||
if bucketName == "version" {
|
||||
version[string(k)] = string(v)
|
||||
} else {
|
||||
list = append(list, obj)
|
||||
}
|
||||
}
|
||||
|
||||
if bucketName == "version" {
|
||||
backup[bucketName] = version
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(list) > 0 {
|
||||
if bucketName == "ssl" ||
|
||||
bucketName == "settings" ||
|
||||
@@ -91,8 +100,10 @@ func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, e
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return []byte("{}"), err
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@ func NewDatabase(storeType, storePath string, encryptionKey []byte) (connection
|
||||
EncryptionKey: encryptionKey,
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown storage database: %s", storeType)
|
||||
|
||||
return nil, fmt.Errorf("Unknown storage database: %s", storeType)
|
||||
}
|
||||
|
||||
8
api/database/models/version.go
Normal file
8
api/database/models/version.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package models
|
||||
|
||||
type Version struct {
|
||||
SchemaVersion string
|
||||
MigratorCount int
|
||||
Edition int
|
||||
InstanceID string
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -41,12 +42,14 @@ func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
record, ok := obj.(*portainer.APIKey)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to APIKey object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
|
||||
return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
|
||||
}
|
||||
|
||||
if record.UserID == userID {
|
||||
result = append(result, *record)
|
||||
}
|
||||
|
||||
return &portainer.APIKey{}, nil
|
||||
})
|
||||
|
||||
@@ -64,18 +67,21 @@ func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, err
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
key, ok := obj.(*portainer.APIKey)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to APIKey object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
|
||||
return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
|
||||
}
|
||||
if bytes.Equal(key.Digest, digest) {
|
||||
k = key
|
||||
return nil, stop
|
||||
}
|
||||
|
||||
return &portainer.APIKey{}, nil
|
||||
})
|
||||
|
||||
if err == stop {
|
||||
return k, nil
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -44,10 +45,11 @@ func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
|
||||
//var tag portainer.Tag
|
||||
customTemplate, ok := obj.(*portainer.CustomTemplate)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to CustomTemplate object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to CustomTemplate object")
|
||||
return nil, fmt.Errorf("Failed to convert to CustomTemplate object: %s", obj)
|
||||
}
|
||||
customTemplates = append(customTemplates, *customTemplate)
|
||||
|
||||
return &portainer.CustomTemplate{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -4,13 +4,12 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "edgegroups"
|
||||
)
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
const BucketName = "edgegroups"
|
||||
|
||||
// Service represents a service for managing Edge group data.
|
||||
type Service struct {
|
||||
@@ -43,10 +42,11 @@ func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
group, ok := obj.(*portainer.EdgeGroup)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeGroup object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeGroup object")
|
||||
return nil, fmt.Errorf("Failed to convert to EdgeGroup object: %s", obj)
|
||||
}
|
||||
groups = append(groups, *group)
|
||||
|
||||
return &portainer.EdgeGroup{}, nil
|
||||
})
|
||||
|
||||
@@ -66,12 +66,22 @@ func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGrou
|
||||
return &group, nil
|
||||
}
|
||||
|
||||
// UpdateEdgeGroup updates an Edge group.
|
||||
// Deprecated: Use UpdateEdgeGroupFunc instead.
|
||||
func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, group)
|
||||
}
|
||||
|
||||
// UpdateEdgeGroupFunc updates an edge group inside a transaction avoiding data races.
|
||||
func (service *Service) UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFunc func(edgeGroup *portainer.EdgeGroup)) error {
|
||||
id := service.connection.ConvertToKey(int(ID))
|
||||
edgeGroup := &portainer.EdgeGroup{}
|
||||
|
||||
return service.connection.UpdateObjectFunc(BucketName, id, edgeGroup, func() {
|
||||
updateFunc(edgeGroup)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteEdgeGroup deletes an Edge group.
|
||||
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -41,13 +42,14 @@ func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
|
||||
BucketName,
|
||||
&portainer.EdgeJob{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
//var tag portainer.Tag
|
||||
job, ok := obj.(*portainer.EdgeJob)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeJob object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object")
|
||||
return nil, fmt.Errorf("Failed to convert to EdgeJob object: %s", obj)
|
||||
}
|
||||
|
||||
edgeJobs = append(edgeJobs, *job)
|
||||
|
||||
return &portainer.EdgeJob{}, nil
|
||||
})
|
||||
|
||||
@@ -78,12 +80,22 @@ func (service *Service) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJo
|
||||
)
|
||||
}
|
||||
|
||||
// UpdateEdgeJob updates an Edge job by ID
|
||||
// Deprecated: use UpdateEdgeJobFunc instead
|
||||
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, edgeJob)
|
||||
}
|
||||
|
||||
// UpdateEdgeJobFunc updates an edge job inside a transaction avoiding data races.
|
||||
func (service *Service) UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc func(edgeJob *portainer.EdgeJob)) error {
|
||||
id := service.connection.ConvertToKey(int(ID))
|
||||
edgeJob := &portainer.EdgeJob{}
|
||||
|
||||
return service.connection.UpdateObjectFunc(BucketName, id, edgeJob, func() {
|
||||
updateFunc(edgeJob)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteEdgeJob deletes an Edge job
|
||||
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
@@ -2,9 +2,12 @@ package edgestack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -15,6 +18,8 @@ const (
|
||||
// Service represents a service for managing Edge stack data.
|
||||
type Service struct {
|
||||
connection portainer.Connection
|
||||
idxVersion map[portainer.EdgeStackID]int
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
@@ -28,9 +33,21 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
s := &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
idxVersion: make(map[portainer.EdgeStackID]int),
|
||||
}
|
||||
|
||||
es, err := s.EdgeStacks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, e := range es {
|
||||
s.idxVersion[e.ID] = e.Version
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// EdgeStacks returns an array containing all edge stacks
|
||||
@@ -41,13 +58,14 @@ func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
|
||||
BucketName,
|
||||
&portainer.EdgeStack{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
//var tag portainer.Tag
|
||||
stack, ok := obj.(*portainer.EdgeStack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EdgeStack object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object")
|
||||
return nil, fmt.Errorf("Failed to convert to EdgeStack object: %s", obj)
|
||||
}
|
||||
|
||||
stacks = append(stacks, *stack)
|
||||
|
||||
return &portainer.EdgeStack{}, nil
|
||||
})
|
||||
|
||||
@@ -67,28 +85,144 @@ func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStac
|
||||
return &stack, nil
|
||||
}
|
||||
|
||||
// EdgeStackVersion returns the version of the given edge stack ID directly from an in-memory index
|
||||
func (service *Service) EdgeStackVersion(ID portainer.EdgeStackID) (int, bool) {
|
||||
service.mu.RLock()
|
||||
v, ok := service.idxVersion[ID]
|
||||
service.mu.RUnlock()
|
||||
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// CreateEdgeStack saves an Edge stack object to db.
|
||||
func (service *Service) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||
|
||||
edgeStack.ID = id
|
||||
|
||||
return service.connection.CreateObjectWithId(
|
||||
err := service.connection.CreateObjectWithId(
|
||||
BucketName,
|
||||
int(edgeStack.ID),
|
||||
edgeStack,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.mu.Lock()
|
||||
service.idxVersion[id] = edgeStack.Version
|
||||
service.mu.Unlock()
|
||||
|
||||
for endpointID := range edgeStack.Status {
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateEdgeStack updates an Edge stack.
|
||||
// Deprecated: Use UpdateEdgeStackFunc instead.
|
||||
func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
prevEdgeStack, err := service.EdgeStack(ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, edgeStack)
|
||||
|
||||
err = service.connection.UpdateObject(BucketName, identifier, edgeStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.idxVersion[ID] = edgeStack.Version
|
||||
|
||||
// Invalidate cache for removed environments
|
||||
for endpointID := range prevEdgeStack.Status {
|
||||
if _, ok := edgeStack.Status[endpointID]; !ok {
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate cache when version changes and for added environments
|
||||
for endpointID := range edgeStack.Status {
|
||||
if prevEdgeStack.Version == edgeStack.Version {
|
||||
if _, ok := prevEdgeStack.Status[endpointID]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateEdgeStackFunc updates an Edge stack inside a transaction avoiding data races.
|
||||
func (service *Service) UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error {
|
||||
id := service.connection.ConvertToKey(int(ID))
|
||||
edgeStack := &portainer.EdgeStack{}
|
||||
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
return service.connection.UpdateObjectFunc(BucketName, id, edgeStack, func() {
|
||||
prevEndpoints := make(map[portainer.EndpointID]struct{}, len(edgeStack.Status))
|
||||
for endpointID := range edgeStack.Status {
|
||||
if _, ok := edgeStack.Status[endpointID]; !ok {
|
||||
prevEndpoints[endpointID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
updateFunc(edgeStack)
|
||||
|
||||
prevVersion := service.idxVersion[ID]
|
||||
service.idxVersion[ID] = edgeStack.Version
|
||||
|
||||
// Invalidate cache for removed environments
|
||||
for endpointID := range prevEndpoints {
|
||||
if _, ok := edgeStack.Status[endpointID]; !ok {
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate cache when version changes and for added environments
|
||||
for endpointID := range edgeStack.Status {
|
||||
if prevVersion == edgeStack.Version {
|
||||
if _, ok := prevEndpoints[endpointID]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteEdgeStack deletes an Edge stack.
|
||||
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
|
||||
edgeStack, err := service.EdgeStack(ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
|
||||
err = service.connection.DeleteObject(BucketName, identifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(service.idxVersion, ID)
|
||||
|
||||
for endpointID := range edgeStack.Status {
|
||||
cache.Del(endpointID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
|
||||
@@ -2,9 +2,13 @@ package endpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -15,6 +19,9 @@ const (
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
type Service struct {
|
||||
connection portainer.Connection
|
||||
mu sync.RWMutex
|
||||
idxEdgeID map[string]portainer.EndpointID
|
||||
heartbeats sync.Map
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
@@ -28,9 +35,25 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
s := &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
idxEdgeID: make(map[string]portainer.EndpointID),
|
||||
}
|
||||
|
||||
es, err := s.Endpoints()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, e := range es {
|
||||
if len(e.EdgeID) > 0 {
|
||||
s.idxEdgeID[e.EdgeID] = e.ID
|
||||
}
|
||||
|
||||
s.heartbeats.Store(e.ID, e.LastCheckInDate)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Endpoint returns an environment(endpoint) by ID.
|
||||
@@ -43,19 +66,54 @@ func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoint.LastCheckInDate, _ = service.Heartbeat(ID)
|
||||
|
||||
return &endpoint, nil
|
||||
}
|
||||
|
||||
// UpdateEndpoint updates an environment(endpoint).
|
||||
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, endpoint)
|
||||
|
||||
err := service.connection.UpdateObject(BucketName, identifier, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.mu.Lock()
|
||||
if len(endpoint.EdgeID) > 0 {
|
||||
service.idxEdgeID[endpoint.EdgeID] = ID
|
||||
}
|
||||
service.heartbeats.Store(ID, endpoint.LastCheckInDate)
|
||||
service.mu.Unlock()
|
||||
|
||||
cache.Del(endpoint.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEndpoint deletes an environment(endpoint).
|
||||
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
|
||||
err := service.connection.DeleteObject(BucketName, identifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.mu.Lock()
|
||||
for edgeID, endpointID := range service.idxEdgeID {
|
||||
if endpointID == ID {
|
||||
delete(service.idxEdgeID, edgeID)
|
||||
break
|
||||
}
|
||||
}
|
||||
service.heartbeats.Delete(ID)
|
||||
service.mu.Unlock()
|
||||
|
||||
cache.Del(ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Endpoints return an array containing all the environments(endpoints).
|
||||
@@ -68,19 +126,63 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
endpoint, ok := obj.(*portainer.Endpoint)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Endpoint object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Endpoint object")
|
||||
return nil, fmt.Errorf("failed to convert to Endpoint object: %s", obj)
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, *endpoint)
|
||||
|
||||
return &portainer.Endpoint{}, nil
|
||||
})
|
||||
|
||||
return endpoints, err
|
||||
if err != nil {
|
||||
return endpoints, err
|
||||
}
|
||||
|
||||
for i, e := range endpoints {
|
||||
t, _ := service.Heartbeat(e.ID)
|
||||
endpoints[i].LastCheckInDate = t
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// EndpointIDByEdgeID returns the EndpointID from the given EdgeID using an in-memory index
|
||||
func (service *Service) EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool) {
|
||||
service.mu.RLock()
|
||||
endpointID, ok := service.idxEdgeID[edgeID]
|
||||
service.mu.RUnlock()
|
||||
|
||||
return endpointID, ok
|
||||
}
|
||||
|
||||
func (service *Service) Heartbeat(endpointID portainer.EndpointID) (int64, bool) {
|
||||
if t, ok := service.heartbeats.Load(endpointID); ok {
|
||||
return t.(int64), true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (service *Service) UpdateHeartbeat(endpointID portainer.EndpointID) {
|
||||
service.heartbeats.Store(endpointID, time.Now().Unix())
|
||||
}
|
||||
|
||||
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
|
||||
func (service *Service) Create(endpoint *portainer.Endpoint) error {
|
||||
return service.connection.CreateObjectWithSetSequence(BucketName, int(endpoint.ID), endpoint)
|
||||
err := service.connection.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.mu.Lock()
|
||||
if len(endpoint.EdgeID) > 0 {
|
||||
service.idxEdgeID[endpoint.EdgeID] = endpoint.ID
|
||||
}
|
||||
service.heartbeats.Store(endpoint.ID, endpoint.LastCheckInDate)
|
||||
service.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -66,13 +67,14 @@ func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||
BucketName,
|
||||
&portainer.EndpointGroup{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
//var tag portainer.Tag
|
||||
endpointGroup, ok := obj.(*portainer.EndpointGroup)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EndpointGroup object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointGroup object")
|
||||
return nil, fmt.Errorf("Failed to convert to EndpointGroup object: %s", obj)
|
||||
}
|
||||
|
||||
endpointGroups = append(endpointGroups, *endpointGroup)
|
||||
|
||||
return &portainer.EndpointGroup{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -33,7 +35,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
//EndpointRelations returns an array of all EndpointRelations
|
||||
// EndpointRelations returns an array of all EndpointRelations
|
||||
func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error) {
|
||||
var all = make([]portainer.EndpointRelation, 0)
|
||||
|
||||
@@ -43,10 +45,12 @@ func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
r, ok := obj.(*portainer.EndpointRelation)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to EndpointRelation object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointRelation object")
|
||||
return nil, fmt.Errorf("Failed to convert to EndpointRelation object: %s", obj)
|
||||
}
|
||||
|
||||
all = append(all, *r)
|
||||
|
||||
return &portainer.EndpointRelation{}, nil
|
||||
})
|
||||
|
||||
@@ -68,17 +72,26 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
|
||||
|
||||
// CreateEndpointRelation saves endpointRelation
|
||||
func (service *Service) Create(endpointRelation *portainer.EndpointRelation) error {
|
||||
return service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
|
||||
err := service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
|
||||
cache.Del(endpointRelation.EndpointID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateEndpointRelation updates an Environment(Endpoint) relation object
|
||||
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
|
||||
identifier := service.connection.ConvertToKey(int(EndpointID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, endpointRelation)
|
||||
func (service *Service) UpdateEndpointRelation(endpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
|
||||
identifier := service.connection.ConvertToKey(int(endpointID))
|
||||
err := service.connection.UpdateObject(BucketName, identifier, endpointRelation)
|
||||
cache.Del(endpointID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteEndpointRelation deletes an Environment(Endpoint) relation object
|
||||
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
|
||||
identifier := service.connection.ConvertToKey(int(EndpointID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
func (service *Service) DeleteEndpointRelation(endpointID portainer.EndpointID) error {
|
||||
identifier := service.connection.ConvertToKey(int(endpointID))
|
||||
err := service.connection.DeleteObject(BucketName, identifier)
|
||||
cache.Del(endpointID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import "errors"
|
||||
|
||||
var (
|
||||
// TODO: i'm pretty sure this needs wrapping at several levels
|
||||
ErrObjectNotFound = errors.New("object not found inside the database")
|
||||
ErrWrongDBEdition = errors.New("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
|
||||
ErrDBImportFailed = errors.New("importing backup failed")
|
||||
ErrObjectNotFound = errors.New("object not found inside the database")
|
||||
ErrWrongDBEdition = errors.New("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
|
||||
ErrDBImportFailed = errors.New("importing backup failed")
|
||||
ErrDatabaseIsUpdating = errors.New("database is currently in updating state. Failed prior upgrade. Please restore from backup or delete the database and restart Portainer")
|
||||
)
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,10 +57,12 @@ func (service *Service) Extensions() ([]portainer.Extension, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
extension, ok := obj.(*portainer.Extension)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Extension object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Extension object")
|
||||
return nil, fmt.Errorf("Failed to convert to Extension object: %s", obj)
|
||||
}
|
||||
|
||||
extensions = append(extensions, *extension)
|
||||
|
||||
return &portainer.Extension{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,8 +44,9 @@ func (service *Service) FDOProfiles() ([]portainer.FDOProfile, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
fdoProfile, ok := obj.(*portainer.FDOProfile)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to FDOProfile object")
|
||||
return nil, fmt.Errorf("failed to convert to FDOProfile object: %s", obj)
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to FDOProfile object")
|
||||
|
||||
return nil, fmt.Errorf("Failed to convert to FDOProfile object: %s", obj)
|
||||
}
|
||||
fdoProfiles = append(fdoProfiles, *fdoProfile)
|
||||
return &portainer.FDOProfile{}, nil
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -33,7 +34,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
//HelmUserRepository returns an array of all HelmUserRepository
|
||||
// HelmUserRepository returns an array of all HelmUserRepository
|
||||
func (service *Service) HelmUserRepositories() ([]portainer.HelmUserRepository, error) {
|
||||
var repos = make([]portainer.HelmUserRepository, 0)
|
||||
|
||||
@@ -43,10 +44,12 @@ func (service *Service) HelmUserRepositories() ([]portainer.HelmUserRepository,
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
r, ok := obj.(*portainer.HelmUserRepository)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to HelmUserRepository object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
|
||||
return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
|
||||
}
|
||||
|
||||
repos = append(repos, *r)
|
||||
|
||||
return &portainer.HelmUserRepository{}, nil
|
||||
})
|
||||
|
||||
@@ -63,12 +66,14 @@ func (service *Service) HelmUserRepositoryByUserID(userID portainer.UserID) ([]p
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
record, ok := obj.(*portainer.HelmUserRepository)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to HelmUserRepository object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
|
||||
return nil, fmt.Errorf("Failed to convert to HelmUserRepository object: %s", obj)
|
||||
}
|
||||
|
||||
if record.UserID == userID {
|
||||
result = append(result, *record)
|
||||
}
|
||||
|
||||
return &portainer.HelmUserRepository{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
@@ -23,7 +24,6 @@ type (
|
||||
BackupTo(w io.Writer) error
|
||||
Export(filename string) (err error)
|
||||
IsErrObjectNotFound(err error) bool
|
||||
|
||||
CustomTemplate() CustomTemplateService
|
||||
EdgeGroup() EdgeGroupService
|
||||
EdgeJob() EdgeJobService
|
||||
@@ -38,6 +38,7 @@ type (
|
||||
Role() RoleService
|
||||
APIKeyRepository() APIKeyRepository
|
||||
Settings() SettingsService
|
||||
Snapshot() SnapshotService
|
||||
SSLSettings() SSLSettingsService
|
||||
Stack() StackService
|
||||
Tag() TagService
|
||||
@@ -66,6 +67,7 @@ type (
|
||||
EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error)
|
||||
Create(group *portainer.EdgeGroup) error
|
||||
UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error
|
||||
UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFunc func(group *portainer.EdgeGroup)) error
|
||||
DeleteEdgeGroup(ID portainer.EdgeGroupID) error
|
||||
BucketName() string
|
||||
}
|
||||
@@ -76,6 +78,7 @@ type (
|
||||
EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error)
|
||||
Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
|
||||
UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
|
||||
UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc func(edgeJob *portainer.EdgeJob)) error
|
||||
DeleteEdgeJob(ID portainer.EdgeJobID) error
|
||||
GetNextIdentifier() int
|
||||
BucketName() string
|
||||
@@ -85,8 +88,10 @@ type (
|
||||
EdgeStackService interface {
|
||||
EdgeStacks() ([]portainer.EdgeStack, error)
|
||||
EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error)
|
||||
EdgeStackVersion(ID portainer.EdgeStackID) (int, bool)
|
||||
Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error
|
||||
UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error
|
||||
UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
|
||||
DeleteEdgeStack(ID portainer.EdgeStackID) error
|
||||
GetNextIdentifier() int
|
||||
BucketName() string
|
||||
@@ -95,6 +100,9 @@ type (
|
||||
// EndpointService represents a service for managing environment(endpoint) data
|
||||
EndpointService interface {
|
||||
Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error)
|
||||
EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool)
|
||||
Heartbeat(endpointID portainer.EndpointID) (int64, bool)
|
||||
UpdateHeartbeat(endpointID portainer.EndpointID)
|
||||
Endpoints() ([]portainer.Endpoint, error)
|
||||
Create(endpoint *portainer.Endpoint) error
|
||||
UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error
|
||||
@@ -201,6 +209,15 @@ type (
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
SnapshotService interface {
|
||||
Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error)
|
||||
Snapshots() ([]portainer.Snapshot, error)
|
||||
UpdateSnapshot(snapshot *portainer.Snapshot) error
|
||||
DeleteSnapshot(endpointID portainer.EndpointID) error
|
||||
Create(snapshot *portainer.Snapshot) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// SSLSettingsService represents a service for managing application settings
|
||||
SSLSettingsService interface {
|
||||
Settings() (*portainer.SSLSettings, error)
|
||||
@@ -229,6 +246,7 @@ type (
|
||||
Tag(ID portainer.TagID) (*portainer.Tag, error)
|
||||
Create(tag *portainer.Tag) error
|
||||
UpdateTag(ID portainer.TagID, tag *portainer.Tag) error
|
||||
UpdateTagFunc(ID portainer.TagID, updateFunc func(tag *portainer.Tag)) error
|
||||
DeleteTag(ID portainer.TagID) error
|
||||
BucketName() string
|
||||
}
|
||||
@@ -256,6 +274,7 @@ type (
|
||||
DeleteTeamMembershipByUserID(userID portainer.UserID) error
|
||||
DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error
|
||||
BucketName() string
|
||||
DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.TeamID, userID portainer.UserID) error
|
||||
}
|
||||
|
||||
// TunnelServerService represents a service for managing data associated to the tunnel server
|
||||
@@ -279,12 +298,11 @@ type (
|
||||
|
||||
// VersionService represents a service for managing version data
|
||||
VersionService interface {
|
||||
DBVersion() (int, error)
|
||||
Edition() (portainer.SoftwareEdition, error)
|
||||
InstanceID() (string, error)
|
||||
StoreDBVersion(version int) error
|
||||
StoreInstanceID(ID string) error
|
||||
BucketName() string
|
||||
UpdateInstanceID(ID string) error
|
||||
Version() (*models.Version, error)
|
||||
UpdateVersion(*models.Version) error
|
||||
}
|
||||
|
||||
// WebhookService represents a service for managing webhook data.
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,10 +57,12 @@ func (service *Service) Registries() ([]portainer.Registry, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
registry, ok := obj.(*portainer.Registry)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Registry object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Registry object")
|
||||
return nil, fmt.Errorf("Failed to convert to Registry object: %s", obj)
|
||||
}
|
||||
|
||||
registries = append(registries, *registry)
|
||||
|
||||
return &portainer.Registry{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -58,7 +59,7 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
rc, ok := obj.(*portainer.ResourceControl)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
|
||||
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
|
||||
}
|
||||
|
||||
@@ -73,6 +74,7 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
|
||||
return nil, stop
|
||||
}
|
||||
}
|
||||
|
||||
return &portainer.ResourceControl{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
@@ -92,10 +94,12 @@ func (service *Service) ResourceControls() ([]portainer.ResourceControl, error)
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
rc, ok := obj.(*portainer.ResourceControl)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
|
||||
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
|
||||
}
|
||||
|
||||
rcs = append(rcs, *rc)
|
||||
|
||||
return &portainer.ResourceControl{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,10 +57,12 @@ func (service *Service) Roles() ([]portainer.Role, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
set, ok := obj.(*portainer.Role)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Role object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Role object")
|
||||
return nil, fmt.Errorf("Failed to convert to Role object: %s", obj)
|
||||
}
|
||||
|
||||
sets = append(sets, *set)
|
||||
|
||||
return &portainer.Role{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -68,10 +69,12 @@ func (service *Service) Schedules() ([]portainer.Schedule, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
schedule, ok := obj.(*portainer.Schedule)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Schedule object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Schedule object")
|
||||
return nil, fmt.Errorf("Failed to convert to Schedule object: %s", obj)
|
||||
}
|
||||
|
||||
schedules = append(schedules, *schedule)
|
||||
|
||||
return &portainer.Schedule{}, nil
|
||||
})
|
||||
|
||||
@@ -89,12 +92,14 @@ func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portain
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
schedule, ok := obj.(*portainer.Schedule)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Schedule object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Schedule object")
|
||||
return nil, fmt.Errorf("Failed to convert to Schedule object: %s", obj)
|
||||
}
|
||||
|
||||
if schedule.JobType == jobType {
|
||||
schedules = append(schedules, *schedule)
|
||||
}
|
||||
|
||||
return &portainer.Schedule{}, nil
|
||||
})
|
||||
|
||||
@@ -103,7 +108,7 @@ func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portain
|
||||
|
||||
// Create assign an ID to a new schedule and saves it.
|
||||
func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
|
||||
return service.connection.CreateObjectWithSetSequence(BucketName, int(schedule.ID), schedule)
|
||||
return service.connection.CreateObjectWithId(BucketName, int(schedule.ID), schedule)
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for a schedule.
|
||||
|
||||
77
api/dataservices/snapshot/snapshot.go
Normal file
77
api/dataservices/snapshot/snapshot.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
BucketName = "snapshots"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
connection portainer.Connection
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (service *Service) Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error) {
|
||||
var snapshot portainer.Snapshot
|
||||
identifier := service.connection.ConvertToKey(int(endpointID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &snapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &snapshot, nil
|
||||
}
|
||||
|
||||
func (service *Service) Snapshots() ([]portainer.Snapshot, error) {
|
||||
var snapshots = make([]portainer.Snapshot, 0)
|
||||
|
||||
err := service.connection.GetAllWithJsoniter(
|
||||
BucketName,
|
||||
&portainer.Snapshot{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
snapshot, ok := obj.(*portainer.Snapshot)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Snapshot object")
|
||||
return nil, fmt.Errorf("failed to convert to Snapshot object: %s", obj)
|
||||
}
|
||||
snapshots = append(snapshots, *snapshot)
|
||||
return &portainer.Snapshot{}, nil
|
||||
})
|
||||
|
||||
return snapshots, err
|
||||
}
|
||||
|
||||
func (service *Service) UpdateSnapshot(snapshot *portainer.Snapshot) error {
|
||||
identifier := service.connection.ConvertToKey(int(snapshot.EndpointID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, snapshot)
|
||||
}
|
||||
|
||||
func (service *Service) DeleteSnapshot(endpointID portainer.EndpointID) error {
|
||||
identifier := service.connection.ConvertToKey(int(endpointID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
}
|
||||
|
||||
func (service *Service) Create(snapshot *portainer.Snapshot) error {
|
||||
return service.connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -60,13 +60,15 @@ func (service *Service) StackByName(name string) (*portainer.Stack, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(*portainer.Stack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
|
||||
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
|
||||
}
|
||||
|
||||
if stack.Name == name {
|
||||
s = stack
|
||||
return nil, stop
|
||||
}
|
||||
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
@@ -89,12 +91,14 @@ func (service *Service) StacksByName(name string) ([]portainer.Stack, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(portainer.Stack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
|
||||
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
|
||||
}
|
||||
|
||||
if stack.Name == name {
|
||||
stacks = append(stacks, stack)
|
||||
}
|
||||
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
|
||||
@@ -111,10 +115,12 @@ func (service *Service) Stacks() ([]portainer.Stack, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(*portainer.Stack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
|
||||
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
|
||||
}
|
||||
|
||||
stacks = append(stacks, *stack)
|
||||
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
|
||||
@@ -128,7 +134,7 @@ func (service *Service) GetNextIdentifier() int {
|
||||
|
||||
// CreateStack creates a new stack.
|
||||
func (service *Service) Create(stack *portainer.Stack) error {
|
||||
return service.connection.CreateObjectWithSetSequence(BucketName, int(stack.ID), stack)
|
||||
return service.connection.CreateObjectWithId(BucketName, int(stack.ID), stack)
|
||||
}
|
||||
|
||||
// UpdateStack updates a stack.
|
||||
@@ -156,13 +162,15 @@ func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) {
|
||||
s, ok = obj.(*portainer.Stack)
|
||||
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
|
||||
|
||||
return &portainer.Stack{}, nil
|
||||
}
|
||||
|
||||
if s.AutoUpdate != nil && strings.EqualFold(s.AutoUpdate.Webhook, id) {
|
||||
return nil, stop
|
||||
}
|
||||
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
@@ -186,12 +194,14 @@ func (service *Service) RefreshableStacks() ([]portainer.Stack, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
stack, ok := obj.(*portainer.Stack)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Stack object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
|
||||
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj)
|
||||
}
|
||||
|
||||
if stack.AutoUpdate != nil && stack.AutoUpdate.Interval != "" {
|
||||
stacks = append(stacks, *stack)
|
||||
}
|
||||
|
||||
return &portainer.Stack{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestService_StackByWebhookID(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
|
||||
}
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
b := stackBuilder{t: t, store: store}
|
||||
@@ -87,7 +87,7 @@ func Test_RefreshableStacks(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
|
||||
}
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
staticStack := portainer.Stack{ID: 1}
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,10 +44,12 @@ func (service *Service) Tags() ([]portainer.Tag, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
tag, ok := obj.(*portainer.Tag)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Tag object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Tag object")
|
||||
return nil, fmt.Errorf("Failed to convert to Tag object: %s", obj)
|
||||
}
|
||||
|
||||
tags = append(tags, *tag)
|
||||
|
||||
return &portainer.Tag{}, nil
|
||||
})
|
||||
|
||||
@@ -77,12 +80,22 @@ func (service *Service) Create(tag *portainer.Tag) error {
|
||||
)
|
||||
}
|
||||
|
||||
// UpdateTag updates a tag.
|
||||
// Deprecated: Use UpdateTagFunc instead.
|
||||
func (service *Service) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, tag)
|
||||
}
|
||||
|
||||
// UpdateTagFunc updates a tag inside a transaction avoiding data races.
|
||||
func (service *Service) UpdateTagFunc(ID portainer.TagID, updateFunc func(tag *portainer.Tag)) error {
|
||||
id := service.connection.ConvertToKey(int(ID))
|
||||
tag := &portainer.Tag{}
|
||||
|
||||
return service.connection.UpdateObjectFunc(BucketName, id, tag, func() {
|
||||
updateFunc(tag)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteTag deletes a tag.
|
||||
func (service *Service) DeleteTag(ID portainer.TagID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -60,13 +60,15 @@ func (service *Service) TeamByName(name string) (*portainer.Team, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
team, ok := obj.(*portainer.Team)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Team object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Team object")
|
||||
return nil, fmt.Errorf("Failed to convert to Team object: %s", obj)
|
||||
}
|
||||
|
||||
if strings.EqualFold(team.Name, name) {
|
||||
t = team
|
||||
return nil, stop
|
||||
}
|
||||
|
||||
return &portainer.Team{}, nil
|
||||
})
|
||||
if err == stop {
|
||||
@@ -89,10 +91,12 @@ func (service *Service) Teams() ([]portainer.Team, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
team, ok := obj.(*portainer.Team)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Team object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Team object")
|
||||
return nil, fmt.Errorf("Failed to convert to Team object: %s", obj)
|
||||
}
|
||||
|
||||
teams = append(teams, *team)
|
||||
|
||||
return &portainer.Team{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func Test_teamByName(t *testing.T) {
|
||||
t.Run("When store is empty should return ErrObjectNotFound", func(t *testing.T) {
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
_, err := store.Team().TeamByName("name")
|
||||
@@ -19,7 +19,7 @@ func Test_teamByName(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When there is no object with the same name should return ErrObjectNotFound", func(t *testing.T) {
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
teamBuilder := teamBuilder{
|
||||
@@ -35,7 +35,7 @@ func Test_teamByName(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When there is an object with the same name should return the object", func(t *testing.T) {
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
_, store, teardown := datastore.MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
teamBuilder := teamBuilder{
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,10 +57,12 @@ func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
}
|
||||
|
||||
memberships = append(memberships, *membership)
|
||||
|
||||
return &portainer.TeamMembership{}, nil
|
||||
})
|
||||
|
||||
@@ -76,12 +79,14 @@ func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]port
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
}
|
||||
|
||||
if membership.UserID == userID {
|
||||
memberships = append(memberships, *membership)
|
||||
}
|
||||
|
||||
return &portainer.TeamMembership{}, nil
|
||||
})
|
||||
|
||||
@@ -98,12 +103,14 @@ func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]port
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
membership, ok := obj.(*portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
}
|
||||
|
||||
if membership.TeamID == teamID {
|
||||
memberships = append(memberships, *membership)
|
||||
}
|
||||
|
||||
return &portainer.TeamMembership{}, nil
|
||||
})
|
||||
|
||||
@@ -137,16 +144,19 @@ func (service *Service) DeleteTeamMembership(ID portainer.TeamMembershipID) erro
|
||||
func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) error {
|
||||
return service.connection.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
return -1, false
|
||||
}
|
||||
|
||||
if membership.UserID == userID {
|
||||
return int(membership.ID), true
|
||||
}
|
||||
|
||||
return -1, false
|
||||
})
|
||||
}
|
||||
@@ -155,16 +165,39 @@ func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) er
|
||||
func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error {
|
||||
return service.connection.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to TeamMembership object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
return -1, false
|
||||
}
|
||||
|
||||
if membership.TeamID == teamID {
|
||||
return int(membership.ID), true
|
||||
}
|
||||
|
||||
return -1, false
|
||||
})
|
||||
}
|
||||
|
||||
func (service *Service) DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.TeamID, userID portainer.UserID) error {
|
||||
return service.connection.DeleteAllObjects(
|
||||
BucketName,
|
||||
&portainer.TeamMembership{},
|
||||
func(obj interface{}) (id int, ok bool) {
|
||||
membership, ok := obj.(portainer.TeamMembership)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
|
||||
//return fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
|
||||
return -1, false
|
||||
}
|
||||
|
||||
if membership.TeamID == teamID && membership.UserID == userID {
|
||||
return int(membership.ID), true
|
||||
}
|
||||
|
||||
return -1, false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -59,18 +59,23 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
user, ok := obj.(*portainer.User)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
|
||||
|
||||
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
|
||||
}
|
||||
|
||||
if strings.EqualFold(user.Username, username) {
|
||||
u = user
|
||||
return nil, stop
|
||||
}
|
||||
|
||||
return &portainer.User{}, nil
|
||||
})
|
||||
|
||||
if err == stop {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
@@ -88,10 +93,13 @@ func (service *Service) Users() ([]portainer.User, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
user, ok := obj.(*portainer.User)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
|
||||
|
||||
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
|
||||
}
|
||||
|
||||
users = append(users, *user)
|
||||
|
||||
return &portainer.User{}, nil
|
||||
})
|
||||
|
||||
@@ -108,12 +116,15 @@ func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User,
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
user, ok := obj.(*portainer.User)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to User object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
|
||||
|
||||
return nil, fmt.Errorf("Failed to convert to User object: %s", obj)
|
||||
}
|
||||
|
||||
if user.Role == role {
|
||||
users = append(users, *user)
|
||||
}
|
||||
|
||||
return &portainer.User{}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"errors"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "version"
|
||||
versionKey = "DB_VERSION"
|
||||
instanceKey = "INSTANCE_ID"
|
||||
editionKey = "EDITION"
|
||||
versionKey = "VERSION"
|
||||
updatingKey = "DB_UPDATING"
|
||||
)
|
||||
|
||||
@@ -20,10 +21,6 @@ type Service struct {
|
||||
connection portainer.Connection
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
@@ -36,56 +33,87 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DBVersion retrieves the stored database version.
|
||||
func (service *Service) DBVersion() (int, error) {
|
||||
var version string
|
||||
err := service.connection.GetObject(BucketName, []byte(versionKey), &version)
|
||||
func (service *Service) SchemaVersion() (string, error) {
|
||||
v, err := service.Version()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return "", err
|
||||
}
|
||||
return strconv.Atoi(version)
|
||||
|
||||
return v.SchemaVersion, nil
|
||||
}
|
||||
|
||||
func (service *Service) UpdateSchemaVersion(version string) error {
|
||||
v, err := service.Version()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.SchemaVersion = version
|
||||
return service.UpdateVersion(v)
|
||||
}
|
||||
|
||||
// Edition retrieves the stored portainer edition.
|
||||
func (service *Service) Edition() (portainer.SoftwareEdition, error) {
|
||||
var edition string
|
||||
err := service.connection.GetObject(BucketName, []byte(editionKey), &edition)
|
||||
v, err := service.Version()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
e, err := strconv.Atoi(edition)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return portainer.SoftwareEdition(e), nil
|
||||
}
|
||||
|
||||
// StoreDBVersion store the database version.
|
||||
func (service *Service) StoreDBVersion(version int) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(versionKey), strconv.Itoa(version))
|
||||
return portainer.SoftwareEdition(v.Edition), nil
|
||||
}
|
||||
|
||||
// IsUpdating retrieves the database updating status.
|
||||
func (service *Service) IsUpdating() (bool, error) {
|
||||
var isUpdating bool
|
||||
err := service.connection.GetObject(BucketName, []byte(updatingKey), &isUpdating)
|
||||
if err != nil && errors.Is(err, dserrors.ErrObjectNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
return isUpdating, err
|
||||
}
|
||||
|
||||
// StoreIsUpdating store the database updating status.
|
||||
func (service *Service) StoreIsUpdating(isUpdating bool) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(updatingKey), isUpdating)
|
||||
return service.connection.DeleteObject(BucketName, []byte(updatingKey))
|
||||
}
|
||||
|
||||
// InstanceID retrieves the stored instance ID.
|
||||
func (service *Service) InstanceID() (string, error) {
|
||||
var id string
|
||||
err := service.connection.GetObject(BucketName, []byte(instanceKey), &id)
|
||||
return id, err
|
||||
v, err := service.Version()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return v.InstanceID, nil
|
||||
}
|
||||
|
||||
// StoreInstanceID store the instance ID.
|
||||
func (service *Service) StoreInstanceID(ID string) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(instanceKey), ID)
|
||||
func (service *Service) UpdateInstanceID(id string) error {
|
||||
v, err := service.Version()
|
||||
if err != nil {
|
||||
if !dataservices.IsErrObjectNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
v = &models.Version{}
|
||||
}
|
||||
|
||||
v.InstanceID = id
|
||||
return service.UpdateVersion(v)
|
||||
}
|
||||
|
||||
// Version retrieve the version object.
|
||||
func (service *Service) Version() (*models.Version, error) {
|
||||
var v models.Version
|
||||
|
||||
err := service.connection.GetObject(BucketName, []byte(versionKey), &v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
// UpdateVersion persists a Version object.
|
||||
func (service *Service) UpdateVersion(version *models.Version) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(versionKey), version)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,7 +35,7 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
//Webhooks returns an array of all webhooks
|
||||
// Webhooks returns an array of all webhooks
|
||||
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
|
||||
var webhooks = make([]portainer.Webhook, 0)
|
||||
|
||||
@@ -44,10 +45,12 @@ func (service *Service) Webhooks() ([]portainer.Webhook, error) {
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
webhook, ok := obj.(*portainer.Webhook)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
|
||||
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
|
||||
}
|
||||
|
||||
webhooks = append(webhooks, *webhook)
|
||||
|
||||
return &portainer.Webhook{}, nil
|
||||
})
|
||||
|
||||
@@ -77,18 +80,23 @@ func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, erro
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
webhook, ok := obj.(*portainer.Webhook)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
|
||||
|
||||
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
|
||||
}
|
||||
|
||||
if webhook.ResourceID == ID {
|
||||
w = webhook
|
||||
return nil, stop
|
||||
}
|
||||
|
||||
return &portainer.Webhook{}, nil
|
||||
})
|
||||
|
||||
if err == stop {
|
||||
return w, nil
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
@@ -106,18 +114,23 @@ func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error)
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
webhook, ok := obj.(*portainer.Webhook)
|
||||
if !ok {
|
||||
logrus.WithField("obj", obj).Errorf("Failed to convert to Webhook object")
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
|
||||
|
||||
return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
|
||||
}
|
||||
|
||||
if webhook.Token == token {
|
||||
w = webhook
|
||||
return nil, stop
|
||||
}
|
||||
|
||||
return &portainer.Webhook{}, nil
|
||||
})
|
||||
|
||||
if err == stop {
|
||||
return w, nil
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return nil, errors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
plog "github.com/portainer/portainer/api/datastore/log"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var backupDefaults = struct {
|
||||
@@ -17,8 +18,6 @@ var backupDefaults = struct {
|
||||
"common",
|
||||
}
|
||||
|
||||
var backupLog = plog.NewScopedLog("database, backup")
|
||||
|
||||
//
|
||||
// Backup Helpers
|
||||
//
|
||||
@@ -29,7 +28,7 @@ func (store *Store) createBackupFolders() {
|
||||
commonDir := store.commonBackupDir()
|
||||
if exists, _ := store.fileService.FileExists(commonDir); !exists {
|
||||
if err := os.MkdirAll(commonDir, 0700); err != nil {
|
||||
backupLog.Error("Error while creating common backup folder", err)
|
||||
log.Error().Err(err).Msg("error while creating common backup folder")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,17 +42,19 @@ func (store *Store) commonBackupDir() string {
|
||||
}
|
||||
|
||||
func (store *Store) copyDBFile(from string, to string) error {
|
||||
backupLog.Info(fmt.Sprintf("Copying db file from %s to %s", from, to))
|
||||
log.Info().Str("from", from).Str("to", to).Msg("copying DB file")
|
||||
|
||||
err := store.fileService.Copy(from, to, true)
|
||||
if err != nil {
|
||||
backupLog.Error("Failed", err)
|
||||
log.Error().Err(err).Msg("failed")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// BackupOptions provide a helper to inject backup options
|
||||
type BackupOptions struct {
|
||||
Version int // I can't find this used for anything other than a filename
|
||||
Version string
|
||||
BackupDir string
|
||||
BackupFileName string
|
||||
BackupPath string
|
||||
@@ -70,26 +71,32 @@ func getBackupRestoreOptions(backupDir string) *BackupOptions {
|
||||
}
|
||||
|
||||
// Backup current database with default options
|
||||
func (store *Store) Backup() (string, error) {
|
||||
return store.backupWithOptions(nil)
|
||||
func (store *Store) Backup(version *models.Version) (string, error) {
|
||||
if version == nil {
|
||||
return store.backupWithOptions(nil)
|
||||
}
|
||||
|
||||
return store.backupWithOptions(&BackupOptions{
|
||||
Version: version.SchemaVersion,
|
||||
})
|
||||
}
|
||||
|
||||
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
|
||||
if options == nil {
|
||||
options = &BackupOptions{}
|
||||
}
|
||||
if options.Version == 0 {
|
||||
version, err := store.version()
|
||||
if options.Version == "" {
|
||||
v, err := store.VersionService.Version()
|
||||
if err != nil {
|
||||
version = 0
|
||||
options.Version = ""
|
||||
}
|
||||
options.Version = version
|
||||
options.Version = v.SchemaVersion
|
||||
}
|
||||
if options.BackupDir == "" {
|
||||
options.BackupDir = store.commonBackupDir()
|
||||
}
|
||||
if options.BackupFileName == "" {
|
||||
options.BackupFileName = fmt.Sprintf("%s.%s.%s", store.connection.GetDatabaseFileName(), fmt.Sprintf("%03d", options.Version), time.Now().Format("20060102150405"))
|
||||
options.BackupFileName = fmt.Sprintf("%s.%s.%s", store.connection.GetDatabaseFileName(), options.Version, time.Now().Format("20060102150405"))
|
||||
}
|
||||
if options.BackupPath == "" {
|
||||
options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
|
||||
@@ -99,7 +106,8 @@ func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
|
||||
|
||||
// BackupWithOptions backup current database with options
|
||||
func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
||||
backupLog.Info("creating db backup")
|
||||
log.Info().Msg("creating DB backup")
|
||||
|
||||
store.createBackupFolders()
|
||||
|
||||
options = store.setupOptions(options)
|
||||
@@ -122,6 +130,7 @@ func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return options.BackupPath, nil
|
||||
}
|
||||
|
||||
@@ -135,17 +144,19 @@ func (store *Store) restoreWithOptions(options *BackupOptions) error {
|
||||
// Check if backup file exist before restoring
|
||||
_, err := os.Stat(options.BackupPath)
|
||||
if os.IsNotExist(err) {
|
||||
backupLog.Error(fmt.Sprintf("Backup file to restore does not exist %s", options.BackupPath), err)
|
||||
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to restore does not exist %s")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
backupLog.Error("Error while closing store before restore", err)
|
||||
log.Error().Err(err).Msg("error while closing store before restore")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
backupLog.Info("Restoring db backup")
|
||||
log.Info().Msg("restoring DB backup")
|
||||
err = store.copyDBFile(options.BackupPath, store.databasePath())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -157,20 +168,20 @@ func (store *Store) restoreWithOptions(options *BackupOptions) error {
|
||||
|
||||
// RemoveWithOptions removes backup database based on supplied options
|
||||
func (store *Store) removeWithOptions(options *BackupOptions) error {
|
||||
backupLog.Info("Removing db backup")
|
||||
log.Info().Msg("removing DB backup")
|
||||
|
||||
options = store.setupOptions(options)
|
||||
_, err := os.Stat(options.BackupPath)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
backupLog.Error(fmt.Sprintf("Backup file to remove does not exist %s", options.BackupPath), err)
|
||||
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to remove does not exist")
|
||||
return err
|
||||
}
|
||||
|
||||
backupLog.Info(fmt.Sprintf("Removing db file at %s", options.BackupPath))
|
||||
log.Info().Str("path", options.BackupPath).Msg("removing DB file")
|
||||
err = os.Remove(options.BackupPath)
|
||||
if err != nil {
|
||||
backupLog.Error("Failed", err)
|
||||
log.Error().Err(err).Msg("failed")
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
)
|
||||
|
||||
func TestCreateBackupFolders(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
_, store, teardown := MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
connection := store.GetConnection()
|
||||
@@ -27,7 +28,7 @@ func TestCreateBackupFolders(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStoreCreation(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(true, true)
|
||||
_, store, teardown := MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
if store == nil {
|
||||
@@ -40,15 +41,18 @@ func TestStoreCreation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackup(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(true, true)
|
||||
_, store, teardown := MustNewTestStore(t, true, true)
|
||||
connection := store.GetConnection()
|
||||
defer teardown()
|
||||
|
||||
t.Run("Backup should create default db backup", func(t *testing.T) {
|
||||
store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
v := models.Version{
|
||||
SchemaVersion: portainer.APIVersion,
|
||||
}
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
store.backupWithOptions(nil)
|
||||
|
||||
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", fmt.Sprintf("portainer.edb.%03d.*", portainer.DBVersion))
|
||||
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", fmt.Sprintf("portainer.edb.%s.*", portainer.APIVersion))
|
||||
if !isFileExist(backupFileName) {
|
||||
t.Errorf("Expect backup file to be created %s", backupFileName)
|
||||
}
|
||||
@@ -67,7 +71,7 @@ func TestBackup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemoveWithOptions(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(true, true)
|
||||
_, store, teardown := MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
t.Run("successfully removes file if existent", func(t *testing.T) {
|
||||
|
||||
@@ -9,25 +9,10 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
portainerErrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (store *Store) version() (int, error) {
|
||||
version, err := store.VersionService.DBVersion()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
version = 0
|
||||
}
|
||||
return version, err
|
||||
}
|
||||
|
||||
func (store *Store) edition() portainer.SoftwareEdition {
|
||||
edition, err := store.VersionService.Edition()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
edition = portainer.PortainerCE
|
||||
}
|
||||
return edition
|
||||
}
|
||||
|
||||
// NewStore initializes a new Store and the associated services
|
||||
func NewStore(storePath string, fileService portainer.FileService, connection portainer.Connection) *Store {
|
||||
return &Store{
|
||||
@@ -38,8 +23,6 @@ func NewStore(storePath string, fileService portainer.FileService, connection po
|
||||
|
||||
// Open opens and initializes the BoltDB database.
|
||||
func (store *Store) Open() (newStore bool, err error) {
|
||||
newStore = true
|
||||
|
||||
encryptionReq, err := store.connection.NeedsEncryptionMigration()
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -54,30 +37,24 @@ func (store *Store) Open() (newStore bool, err error) {
|
||||
|
||||
err = store.connection.Open()
|
||||
if err != nil {
|
||||
return newStore, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = store.initServices()
|
||||
if err != nil {
|
||||
return newStore, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
// if we have DBVersion in the database then ensure we flag this as NOT a new store
|
||||
version, err := store.VersionService.DBVersion()
|
||||
// If no settings object exists then assume we have a new store
|
||||
_, err = store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
return newStore, nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return newStore, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
if version > 0 {
|
||||
logrus.WithField("version", version).Infof("Opened existing store")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return newStore, nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (store *Store) Close() error {
|
||||
@@ -92,17 +69,29 @@ func (store *Store) BackupTo(w io.Writer) error {
|
||||
|
||||
// CheckCurrentEdition checks if current edition is community edition
|
||||
func (store *Store) CheckCurrentEdition() error {
|
||||
if store.edition() != portainer.PortainerCE {
|
||||
if store.edition() != portainer.Edition {
|
||||
return portainerErrors.ErrWrongDBEdition
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) edition() portainer.SoftwareEdition {
|
||||
edition, err := store.VersionService.Edition()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
edition = portainer.PortainerCE
|
||||
}
|
||||
return edition
|
||||
}
|
||||
|
||||
// TODO: move the use of this to dataservices.IsErrObjectNotFound()?
|
||||
func (store *Store) IsErrObjectNotFound(e error) bool {
|
||||
return e == portainerErrors.ErrObjectNotFound
|
||||
}
|
||||
|
||||
func (store *Store) Connection() portainer.Connection {
|
||||
return store.connection
|
||||
}
|
||||
|
||||
func (store *Store) Rollback(force bool) error {
|
||||
return store.connectionRollback(force)
|
||||
}
|
||||
@@ -121,19 +110,21 @@ func (store *Store) encryptDB() error {
|
||||
|
||||
// The DB is not currently encrypted. First save the encrypted db filename
|
||||
oldFilename := store.connection.GetDatabaseFilePath()
|
||||
logrus.Infof("Encrypting database")
|
||||
log.Info().Msg("encrypting database")
|
||||
|
||||
// export file path for backup
|
||||
exportFilename := path.Join(store.databasePath() + "." + fmt.Sprintf("backup-%d.json", time.Now().Unix()))
|
||||
|
||||
logrus.Infof("Exporting database backup to %s", exportFilename)
|
||||
log.Info().Str("filename", exportFilename).Msg("exporting database backup")
|
||||
|
||||
err = store.Export(exportFilename)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Debugf("Failed to export to %s", exportFilename)
|
||||
log.Error().Str("filename", exportFilename).Err(err).Msg("failed to export")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("Database backup exported")
|
||||
log.Info().Msg("database backup exported")
|
||||
|
||||
// Close existing un-encrypted db so that we can delete the file later
|
||||
store.connection.Close()
|
||||
@@ -152,22 +143,24 @@ func (store *Store) encryptDB() error {
|
||||
if err != nil {
|
||||
// Remove the new encrypted file that we failed to import
|
||||
os.Remove(store.connection.GetDatabaseFilePath())
|
||||
logrus.Fatal(portainerErrors.ErrDBImportFailed.Error())
|
||||
|
||||
log.Fatal().Err(portainerErrors.ErrDBImportFailed).Msg("")
|
||||
}
|
||||
|
||||
err = os.Remove(oldFilename)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to remove the un-encrypted db file")
|
||||
log.Error().Msg("failed to remove the un-encrypted db file")
|
||||
}
|
||||
|
||||
err = os.Remove(exportFilename)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to remove the json backup file")
|
||||
log.Error().Msg("failed to remove the json backup file")
|
||||
}
|
||||
|
||||
// Close db connection
|
||||
store.connection.Close()
|
||||
|
||||
logrus.Info("Database successfully encrypted")
|
||||
log.Info().Msg("database successfully encrypted")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const (
|
||||
// TestStoreFull an eventually comprehensive set of tests for the Store.
|
||||
// The idea is what we write to the store, we should read back.
|
||||
func TestStoreFull(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(true, true)
|
||||
_, store, teardown := MustNewTestStore(t, true, true)
|
||||
defer teardown()
|
||||
|
||||
testCases := map[string]func(t *testing.T){
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// Init creates the default data set.
|
||||
func (store *Store) Init() error {
|
||||
err := store.checkOrCreateInstanceID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.checkOrCreateDefaultSettings()
|
||||
err := store.checkOrCreateDefaultSettings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -25,26 +19,12 @@ func (store *Store) Init() error {
|
||||
return store.checkOrCreateDefaultData()
|
||||
}
|
||||
|
||||
func (store *Store) checkOrCreateInstanceID() error {
|
||||
_, err := store.VersionService.InstanceID()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
uid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instanceID := uid.String()
|
||||
return store.VersionService.StoreInstanceID(instanceID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *Store) checkOrCreateDefaultSettings() error {
|
||||
// TODO: these need to also be applied when importing
|
||||
settings, err := store.SettingsService.Settings()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
defaultSettings := &portainer.Settings{
|
||||
EnableTelemetry: true,
|
||||
EnableTelemetry: false,
|
||||
AuthenticationMethod: portainer.AuthenticationInternal,
|
||||
BlackListedLabels: make([]portainer.Pair, 0),
|
||||
InternalAuthSettings: portainer.InternalAuthSettings{
|
||||
@@ -88,7 +68,6 @@ func (store *Store) checkOrCreateDefaultSettings() error {
|
||||
|
||||
func (store *Store) checkOrCreateDefaultSSLSettings() error {
|
||||
_, err := store.SSLSettings().Settings()
|
||||
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
defaultSSLSettings := &portainer.SSLSettings{
|
||||
HTTPEnabled: true,
|
||||
@@ -96,6 +75,7 @@ func (store *Store) checkOrCreateDefaultSSLSettings() error {
|
||||
|
||||
return store.SSLSettings().UpdateSettings(defaultSSLSettings)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
INFO = "INFO"
|
||||
ERROR = "ERROR"
|
||||
DEBUG = "DEBUG"
|
||||
FATAL = "FATAL"
|
||||
)
|
||||
|
||||
type ScopedLog struct {
|
||||
scope string
|
||||
}
|
||||
|
||||
func NewScopedLog(scope string) *ScopedLog {
|
||||
return &ScopedLog{scope: scope}
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) print(kind string, message string) {
|
||||
log.Printf("[%s] [%s] %s", kind, slog.scope, message)
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) Debug(message string) {
|
||||
slog.print(DEBUG, fmt.Sprintf("[message: %s]", message))
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) Debugf(message string, vars ...interface{}) {
|
||||
message = fmt.Sprintf(message, vars...)
|
||||
slog.print(DEBUG, fmt.Sprintf("[message: %s]", message))
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) Info(message string) {
|
||||
slog.print(INFO, fmt.Sprintf("[message: %s]", message))
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) Infof(message string, vars ...interface{}) {
|
||||
message = fmt.Sprintf(message, vars...)
|
||||
slog.print(INFO, fmt.Sprintf("[message: %s]", message))
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) Error(message string, err error) {
|
||||
slog.print(ERROR, fmt.Sprintf("[message: %s] [error: %s]", message, err))
|
||||
}
|
||||
|
||||
func (slog *ScopedLog) NotImplemented(method string) {
|
||||
log.Fatalf("[%s] [%s] [%s]", FATAL, slog.scope, fmt.Sprintf("%s is not yet implemented", method))
|
||||
}
|
||||
@@ -4,45 +4,75 @@ import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
portaineree "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
plog "github.com/portainer/portainer/api/datastore/log"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/datastore/migrator"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
werrors "github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
|
||||
|
||||
var migrateLog = plog.NewScopedLog("database, migrate")
|
||||
|
||||
func (store *Store) MigrateData() error {
|
||||
version, err := store.version()
|
||||
updating, err := store.VersionService.IsUpdating()
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "while checking if the store is updating")
|
||||
}
|
||||
|
||||
// Backup Database
|
||||
backupPath, err := store.Backup()
|
||||
if err != nil {
|
||||
return werrors.Wrap(err, "while backing up db before migration")
|
||||
if updating {
|
||||
return dserrors.ErrDatabaseIsUpdating
|
||||
}
|
||||
|
||||
migratorParams := &migrator.MigratorParameters{
|
||||
DatabaseVersion: version,
|
||||
// migrate new version bucket if required (doesn't write anything to db yet)
|
||||
version, err := store.getOrMigrateLegacyVersion()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while migrating legacy version")
|
||||
}
|
||||
|
||||
migratorParams := store.newMigratorParameters(version)
|
||||
migrator := migrator.NewMigrator(migratorParams)
|
||||
|
||||
if !migrator.NeedsMigration() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// before we alter anything in the DB, create a backup
|
||||
backupPath, err := store.Backup(version)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while backing up database")
|
||||
}
|
||||
|
||||
err = store.FailSafeMigrate(migrator, version)
|
||||
if err != nil {
|
||||
err = store.restoreWithOptions(&BackupOptions{BackupPath: backupPath})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to restore database")
|
||||
}
|
||||
|
||||
log.Info().Msg("database restored to previous version")
|
||||
return errors.Wrap(err, "failed to migrate database")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) newMigratorParameters(version *models.Version) *migrator.MigratorParameters {
|
||||
return &migrator.MigratorParameters{
|
||||
CurrentDBVersion: version,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointRelationService: store.EndpointRelationService,
|
||||
ExtensionService: store.ExtensionService,
|
||||
FDOProfilesService: store.FDOProfilesService,
|
||||
RegistryService: store.RegistryService,
|
||||
ResourceControlService: store.ResourceControlService,
|
||||
RoleService: store.RoleService,
|
||||
ScheduleService: store.ScheduleService,
|
||||
SettingsService: store.SettingsService,
|
||||
SnapshotService: store.SnapshotService,
|
||||
StackService: store.StackService,
|
||||
TagService: store.TagService,
|
||||
TeamMembershipService: store.TeamMembershipService,
|
||||
@@ -51,93 +81,42 @@ func (store *Store) MigrateData() error {
|
||||
FileService: store.fileService,
|
||||
DockerhubService: store.DockerHubService,
|
||||
AuthorizationService: authorization.NewService(store),
|
||||
EdgeStackService: store.EdgeStackService,
|
||||
}
|
||||
|
||||
// restore on error
|
||||
err = store.connectionMigrateData(migratorParams)
|
||||
if err != nil {
|
||||
logrus.Errorf("While DB migration %v. Restoring DB", err)
|
||||
// Restore options
|
||||
options := BackupOptions{
|
||||
BackupPath: backupPath,
|
||||
}
|
||||
err := store.restoreWithOptions(&options)
|
||||
if err != nil {
|
||||
logrus.Fatalf(
|
||||
"Failed restoring the backup. portainer database file needs to restored manually by "+
|
||||
"replacing %s database file with recent backup %s. Error %v",
|
||||
store.databasePath(),
|
||||
options.BackupPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// FailSafeMigrate backup and restore DB if migration fail
|
||||
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator) (err error) {
|
||||
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version *models.Version) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
store.Rollback(true)
|
||||
// return error with cause and stacktrace (recover() doesn't include a stacktrace)
|
||||
err = fmt.Errorf("%v %s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
// !Important: we must use a named return value in the function definition and not a local
|
||||
// !variable referenced from the closure or else the return value will be incorrectly set
|
||||
return migrator.Migrate()
|
||||
}
|
||||
|
||||
// MigrateData automatically migrate the data based on the DBVersion.
|
||||
// This process is only triggered on an existing database, not if the database was just created.
|
||||
// if force is true, then migrate regardless.
|
||||
func (store *Store) connectionMigrateData(migratorParams *migrator.MigratorParameters) error {
|
||||
migrator := migrator.NewMigrator(migratorParams)
|
||||
|
||||
// backup db file before upgrading DB to support rollback
|
||||
isUpdating, err := migratorParams.VersionService.IsUpdating()
|
||||
if err != nil && err != errors.ErrObjectNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isUpdating && migrator.Version() != portainer.DBVersion {
|
||||
err = store.backupVersion(migrator)
|
||||
if err != nil {
|
||||
return werrors.Wrapf(err, "failed to backup database")
|
||||
}
|
||||
}
|
||||
|
||||
if migrator.Version() < portainer.DBVersion {
|
||||
migrateLog.Info(fmt.Sprintf("Migrating database from version %v to %v.\n", migrator.Version(), portainer.DBVersion))
|
||||
err = store.FailSafeMigrate(migrator)
|
||||
if err != nil {
|
||||
migrateLog.Error("An error occurred during database migration", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// backupVersion will backup the database or panic if any errors occur
|
||||
func (store *Store) backupVersion(migrator *migrator.Migrator) error {
|
||||
migrateLog.Info("Backing up database prior to version upgrade...")
|
||||
|
||||
options := getBackupRestoreOptions(store.commonBackupDir())
|
||||
|
||||
_, err := store.backupWithOptions(options)
|
||||
err = store.VersionService.StoreIsUpdating(true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while updating the store")
|
||||
}
|
||||
|
||||
// now update the version to the new struct (if required)
|
||||
err = store.finishMigrateLegacyVersion(version)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while updating version")
|
||||
}
|
||||
|
||||
log.Info().Msg("migrating database from version " + version.SchemaVersion + " to " + portaineree.APIVersion)
|
||||
|
||||
err = migrator.Migrate()
|
||||
if err != nil {
|
||||
migrateLog.Error("An error occurred during database backup", err)
|
||||
removalErr := store.removeWithOptions(options)
|
||||
if removalErr != nil {
|
||||
migrateLog.Error("An error occurred during store removal prior to backup", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.VersionService.StoreIsUpdating(false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update the store")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,43 +5,46 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// testVersion is a helper which tests current store version against wanted version
|
||||
func testVersion(store *Store, versionWant int, t *testing.T) {
|
||||
v, err := store.VersionService.DBVersion()
|
||||
func testVersion(store *Store, versionWant string, t *testing.T) {
|
||||
v, err := store.VersionService.Version()
|
||||
if err != nil {
|
||||
t.Errorf("Expect store version to be %d but was %d with error: %s", versionWant, v, err)
|
||||
t.Errorf("Expect store version to be %s but was %s with error: %s", versionWant, v.SchemaVersion, err)
|
||||
}
|
||||
if v != versionWant {
|
||||
t.Errorf("Expect store version to be %d but was %d", versionWant, v)
|
||||
if v.SchemaVersion != versionWant {
|
||||
t.Errorf("Expect store version to be %s but was %s", versionWant, v.SchemaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateData(t *testing.T) {
|
||||
snapshotTests := []struct {
|
||||
testName string
|
||||
srcPath string
|
||||
wantPath string
|
||||
testName string
|
||||
srcPath string
|
||||
wantPath string
|
||||
overrideInstanceId bool
|
||||
}{
|
||||
{
|
||||
testName: "migrate version 24 to latest",
|
||||
srcPath: "test_data/input_24.json",
|
||||
wantPath: "test_data/output_24_to_latest.json",
|
||||
testName: "migrate version 24 to latest",
|
||||
srcPath: "test_data/input_24.json",
|
||||
wantPath: "test_data/output_24_to_latest.json",
|
||||
overrideInstanceId: true,
|
||||
},
|
||||
}
|
||||
for _, test := range snapshotTests {
|
||||
t.Run(test.testName, func(t *testing.T) {
|
||||
err := migrateDBTestHelper(t, test.srcPath, test.wantPath)
|
||||
err := migrateDBTestHelper(t, test.srcPath, test.wantPath, test.overrideInstanceId)
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"Failed migrating mock database %v: %v",
|
||||
@@ -52,147 +55,151 @@ func TestMigrateData(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
|
||||
newStore, store, teardown := MustNewTestStore(false, true)
|
||||
defer teardown()
|
||||
// t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
|
||||
// newStore, store, teardown := MustNewTestStore(t, true, false)
|
||||
// defer teardown()
|
||||
|
||||
if !newStore {
|
||||
t.Error("Expect a new DB")
|
||||
}
|
||||
// if !newStore {
|
||||
// t.Error("Expect a new DB")
|
||||
// }
|
||||
|
||||
// not called for new stores
|
||||
//store.MigrateData()
|
||||
// testVersion(store, portainer.APIVersion, t)
|
||||
// store.Close()
|
||||
|
||||
testVersion(store, portainer.DBVersion, t)
|
||||
store.Close()
|
||||
// newStore, _ = store.Open()
|
||||
// if newStore {
|
||||
// t.Error("Expect store to NOT be new DB")
|
||||
// }
|
||||
// })
|
||||
|
||||
newStore, _ = store.Open()
|
||||
if newStore {
|
||||
t.Error("Expect store to NOT be new DB")
|
||||
}
|
||||
})
|
||||
// tests := []struct {
|
||||
// version string
|
||||
// expectedVersion string
|
||||
// }{
|
||||
// {version: "1.24.1", expectedVersion: portainer.APIVersion},
|
||||
// {version: "2.0.0", expectedVersion: portainer.APIVersion},
|
||||
// }
|
||||
// for _, tc := range tests {
|
||||
// _, store, teardown := MustNewTestStore(t, true, true)
|
||||
// defer teardown()
|
||||
|
||||
tests := []struct {
|
||||
version int
|
||||
expectedVersion int
|
||||
}{
|
||||
{version: 17, expectedVersion: portainer.DBVersion},
|
||||
{version: 21, expectedVersion: portainer.DBVersion},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
_, store, teardown := MustNewTestStore(true, true)
|
||||
defer teardown()
|
||||
// // Setup data
|
||||
// v := models.Version{SchemaVersion: tc.version}
|
||||
// store.VersionService.UpdateVersion(&v)
|
||||
|
||||
// Setup data
|
||||
store.VersionService.StoreDBVersion(tc.version)
|
||||
// // Required roles by migrations 22.2
|
||||
// store.RoleService.Create(&portainer.Role{ID: 1})
|
||||
// store.RoleService.Create(&portainer.Role{ID: 2})
|
||||
// store.RoleService.Create(&portainer.Role{ID: 3})
|
||||
// store.RoleService.Create(&portainer.Role{ID: 4})
|
||||
|
||||
// Required roles by migrations 22.2
|
||||
store.RoleService.Create(&portainer.Role{ID: 1})
|
||||
store.RoleService.Create(&portainer.Role{ID: 2})
|
||||
store.RoleService.Create(&portainer.Role{ID: 3})
|
||||
store.RoleService.Create(&portainer.Role{ID: 4})
|
||||
// t.Run(fmt.Sprintf("MigrateData for version %s", tc.version), func(t *testing.T) {
|
||||
// store.MigrateData()
|
||||
// testVersion(store, tc.expectedVersion, t)
|
||||
// })
|
||||
|
||||
t.Run(fmt.Sprintf("MigrateData for version %d", tc.version), func(t *testing.T) {
|
||||
store.MigrateData()
|
||||
testVersion(store, tc.expectedVersion, t)
|
||||
})
|
||||
// t.Run(fmt.Sprintf("Restoring DB after migrateData for version %s", tc.version), func(t *testing.T) {
|
||||
// store.Rollback(true)
|
||||
// store.Open()
|
||||
// testVersion(store, tc.version, t)
|
||||
// })
|
||||
// }
|
||||
|
||||
t.Run(fmt.Sprintf("Restoring DB after migrateData for version %d", tc.version), func(t *testing.T) {
|
||||
store.Rollback(true)
|
||||
store.Open()
|
||||
testVersion(store, tc.version, t)
|
||||
})
|
||||
}
|
||||
// t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
|
||||
// _, store, teardown := MustNewTestStore(t, false, true)
|
||||
// defer teardown()
|
||||
|
||||
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
defer teardown()
|
||||
// v := models.Version{SchemaVersion: "1.24.1"}
|
||||
// store.VersionService.UpdateVersion(&v)
|
||||
|
||||
version := 17
|
||||
store.VersionService.StoreDBVersion(version)
|
||||
// store.MigrateData()
|
||||
|
||||
store.MigrateData()
|
||||
// testVersion(store, v.SchemaVersion, t)
|
||||
// })
|
||||
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
// t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
|
||||
// _, store, teardown := MustNewTestStore(t, false, true)
|
||||
// defer teardown()
|
||||
|
||||
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
defer teardown()
|
||||
store.VersionService.StoreDBVersion(0)
|
||||
// v := models.Version{SchemaVersion: "0.0.0"}
|
||||
// store.VersionService.UpdateVersion(&v)
|
||||
|
||||
store.MigrateData()
|
||||
// store.MigrateData()
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
if !isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should exist; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
// if !isFileExist(options.BackupPath) {
|
||||
// t.Errorf("Backup file should exist; file=%s", options.BackupPath)
|
||||
// }
|
||||
// })
|
||||
|
||||
t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
defer teardown()
|
||||
// t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
|
||||
// _, store, teardown := MustNewTestStore(t, false, true)
|
||||
// defer teardown()
|
||||
|
||||
store.VersionService.StoreIsUpdating(true)
|
||||
// store.VersionService.StoreIsUpdating(true)
|
||||
|
||||
store.MigrateData()
|
||||
// store.MigrateData()
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
if isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
// if isFileExist(options.BackupPath) {
|
||||
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
// }
|
||||
// })
|
||||
|
||||
t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
defer teardown()
|
||||
// t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
|
||||
// _, store, teardown := MustNewTestStore(t, false, true)
|
||||
// defer teardown()
|
||||
|
||||
store.MigrateData()
|
||||
// store.MigrateData()
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
if isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
// if isFileExist(options.BackupPath) {
|
||||
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
func Test_getBackupRestoreOptions(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
_, store, teardown := MustNewTestStore(t, false, true)
|
||||
defer teardown()
|
||||
|
||||
options := getBackupRestoreOptions(store.commonBackupDir())
|
||||
|
||||
wantDir := store.commonBackupDir()
|
||||
if !strings.HasSuffix(options.BackupDir, wantDir) {
|
||||
log.Fatalf("incorrect backup dir; got=%s, want=%s", options.BackupDir, wantDir)
|
||||
log.Fatal().Str("got", options.BackupDir).Str("want", wantDir).Msg("incorrect backup dir")
|
||||
}
|
||||
|
||||
wantFilename := "portainer.db.bak"
|
||||
if options.BackupFileName != wantFilename {
|
||||
log.Fatalf("incorrect backup file; got=%s, want=%s", options.BackupFileName, wantFilename)
|
||||
log.Fatal().Str("got", options.BackupFileName).Str("want", wantFilename).Msg("incorrect backup file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollback(t *testing.T) {
|
||||
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
|
||||
version := 21
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
version := models.Version{SchemaVersion: "2.4.0"}
|
||||
_, store, teardown := MustNewTestStore(t, true, false)
|
||||
defer teardown()
|
||||
store.VersionService.StoreDBVersion(version)
|
||||
|
||||
_, err := store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
err := store.VersionService.UpdateVersion(&version)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Errorf("Failed updating version: %v", err)
|
||||
}
|
||||
|
||||
// Change the current edition
|
||||
err = store.VersionService.StoreDBVersion(version + 10)
|
||||
_, err = store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
// Change the current version
|
||||
version2 := models.Version{SchemaVersion: "2.6.0"}
|
||||
err = store.VersionService.UpdateVersion(&version2)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
err = store.Rollback(true)
|
||||
@@ -202,8 +209,14 @@ func TestRollback(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
store.Open()
|
||||
testVersion(store, version, t)
|
||||
_, err = store.Open()
|
||||
if err != nil {
|
||||
t.Logf("Open failed: %s", err)
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
testVersion(store, version.SchemaVersion, t)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -219,15 +232,20 @@ func isFileExist(path string) bool {
|
||||
// migrateDBTestHelper loads a json representation of a bolt database from srcPath,
|
||||
// parses it into a database, runs a migration on that database, and then
|
||||
// compares it with an expected output database.
|
||||
func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
|
||||
func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanceId bool) error {
|
||||
srcJSON, err := os.ReadFile(srcPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed loading source JSON file %v: %v", srcPath, err)
|
||||
}
|
||||
|
||||
// Parse source json to db.
|
||||
_, store, teardown := MustNewTestStore(true, false)
|
||||
defer teardown()
|
||||
// When we create a new test store, it sets its version field automatically to latest.
|
||||
_, store, _ := MustNewTestStore(t, true, false)
|
||||
|
||||
fmt.Println("store.path=", store.GetConnection().GetDatabaseFilePath())
|
||||
store.connection.DeleteObject("version", []byte("VERSION"))
|
||||
|
||||
// defer teardown()
|
||||
err = importJSON(t, bytes.NewReader(srcJSON), store)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -239,6 +257,21 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if overrideInstanceId {
|
||||
// old versions of portainer did not have instance-id. Because this gets generated
|
||||
// we need to override the expected output to match the expected value to pass the test
|
||||
v, err := store.VersionService.Version()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.InstanceID = "463d5c47-0ea5-4aca-85b1-405ceefee254"
|
||||
err = store.VersionService.UpdateVersion(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that our database connection is using bolt so we can call
|
||||
// exportJson rather than ExportRaw. The exportJson function allows us to
|
||||
// strip out the metadata which we don't want for our tests.
|
||||
@@ -258,7 +291,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
|
||||
return fmt.Errorf("stat on %s failed: %s", databasePath, err)
|
||||
}
|
||||
|
||||
gotJSON, err := con.ExportJson(databasePath, false)
|
||||
gotJSON, err := con.ExportJSON(databasePath, false)
|
||||
if err != nil {
|
||||
t.Logf(
|
||||
"failed re-exporting database %s to JSON: %v",
|
||||
@@ -315,42 +348,65 @@ func importJSON(t *testing.T, r io.Reader, store *Store) error {
|
||||
t.Logf("failed casting %s to map[string]interface{}", k)
|
||||
}
|
||||
|
||||
// New format db
|
||||
version, ok := versions["VERSION"]
|
||||
if ok {
|
||||
err := con.CreateObjectWithStringId(
|
||||
k,
|
||||
[]byte("VERSION"),
|
||||
version,
|
||||
)
|
||||
if err != nil {
|
||||
t.Logf("failed writing VERSION in %s: %v", k, err)
|
||||
}
|
||||
}
|
||||
|
||||
// old format db
|
||||
|
||||
dbVersion, ok := versions["DB_VERSION"]
|
||||
if !ok {
|
||||
t.Logf("failed getting DB_VERSION from %s", k)
|
||||
}
|
||||
if ok {
|
||||
numDBVersion, ok := dbVersion.(json.Number)
|
||||
if !ok {
|
||||
t.Logf("failed parsing DB_VERSION as json number from %s", k)
|
||||
}
|
||||
|
||||
numDBVersion, ok := dbVersion.(json.Number)
|
||||
if !ok {
|
||||
t.Logf("failed parsing DB_VERSION as json number from %s", k)
|
||||
}
|
||||
intDBVersion, err := numDBVersion.Int64()
|
||||
if err != nil {
|
||||
t.Logf("failed casting %v to int: %v", numDBVersion, intDBVersion)
|
||||
}
|
||||
|
||||
intDBVersion, err := numDBVersion.Int64()
|
||||
if err != nil {
|
||||
t.Logf("failed casting %v to int: %v", numDBVersion, intDBVersion)
|
||||
}
|
||||
|
||||
err = con.CreateObjectWithStringId(
|
||||
k,
|
||||
[]byte("DB_VERSION"),
|
||||
int(intDBVersion),
|
||||
)
|
||||
if err != nil {
|
||||
t.Logf("failed writing DB_VERSION in %s: %v", k, err)
|
||||
err = con.CreateObjectWithStringId(
|
||||
k,
|
||||
[]byte("DB_VERSION"),
|
||||
int(intDBVersion),
|
||||
)
|
||||
if err != nil {
|
||||
t.Logf("failed writing DB_VERSION in %s: %v", k, err)
|
||||
}
|
||||
}
|
||||
|
||||
instanceID, ok := versions["INSTANCE_ID"]
|
||||
if !ok {
|
||||
t.Logf("failed getting INSTANCE_ID from %s", k)
|
||||
if ok {
|
||||
err = con.CreateObjectWithStringId(
|
||||
k,
|
||||
[]byte("INSTANCE_ID"),
|
||||
instanceID,
|
||||
)
|
||||
if err != nil {
|
||||
t.Logf("failed writing INSTANCE_ID in %s: %v", k, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = con.CreateObjectWithStringId(
|
||||
k,
|
||||
[]byte("INSTANCE_ID"),
|
||||
instanceID,
|
||||
)
|
||||
if err != nil {
|
||||
t.Logf("failed writing INSTANCE_ID in %s: %v", k, err)
|
||||
edition, ok := versions["EDITION"]
|
||||
if ok {
|
||||
err = con.CreateObjectWithStringId(
|
||||
k,
|
||||
[]byte("EDITION"),
|
||||
edition,
|
||||
)
|
||||
if err != nil {
|
||||
t.Logf("failed writing EDITION in %s: %v", k, err)
|
||||
}
|
||||
}
|
||||
|
||||
case "dockerhub":
|
||||
|
||||
@@ -33,7 +33,7 @@ func setup(store *Store) error {
|
||||
}
|
||||
|
||||
func TestMigrateSettings(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
_, store, teardown := MustNewTestStore(t, false, true)
|
||||
defer teardown()
|
||||
|
||||
err := setup(store)
|
||||
@@ -54,7 +54,6 @@ func TestMigrateSettings(t *testing.T) {
|
||||
}
|
||||
|
||||
m := migrator.NewMigrator(&migrator.MigratorParameters{
|
||||
DatabaseVersion: 29,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointRelationService: store.EndpointRelationService,
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMigrateStackEntryPoint(t *testing.T) {
|
||||
_, store, teardown := MustNewTestStore(false, true)
|
||||
_, store, teardown := MustNewTestStore(t, false, true)
|
||||
defer teardown()
|
||||
|
||||
stackService := store.Stack()
|
||||
|
||||
115
api/datastore/migrate_legacyversion.go
Normal file
115
api/datastore/migrate_legacyversion.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
portaineree "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
)
|
||||
|
||||
const (
|
||||
bucketName = "version"
|
||||
legacyDBVersionKey = "DB_VERSION"
|
||||
legacyInstanceKey = "INSTANCE_ID"
|
||||
legacyEditionKey = "EDITION"
|
||||
)
|
||||
|
||||
var dbVerToSemVerMap = map[int]string{
|
||||
18: "1.21",
|
||||
19: "1.22",
|
||||
20: "1.22.1",
|
||||
21: "1.22.2",
|
||||
22: "1.23",
|
||||
23: "1.24",
|
||||
24: "1.24.1",
|
||||
25: "2.0",
|
||||
26: "2.1",
|
||||
27: "2.2",
|
||||
28: "2.4",
|
||||
29: "2.4",
|
||||
30: "2.6",
|
||||
31: "2.7",
|
||||
32: "2.9",
|
||||
33: "2.9.1",
|
||||
34: "2.10",
|
||||
35: "2.9.3",
|
||||
36: "2.11",
|
||||
40: "2.13",
|
||||
50: "2.14",
|
||||
51: "2.14.1",
|
||||
52: "2.14.2",
|
||||
60: "2.15",
|
||||
61: "2.15.1",
|
||||
70: "2.16",
|
||||
80: "2.17",
|
||||
}
|
||||
|
||||
func dbVersionToSemanticVersion(dbVersion int) string {
|
||||
if dbVersion < 18 {
|
||||
return "1.0.0"
|
||||
}
|
||||
|
||||
ver, ok := dbVerToSemVerMap[dbVersion]
|
||||
if ok {
|
||||
return ver
|
||||
}
|
||||
|
||||
// We should always return something sensible
|
||||
switch {
|
||||
case dbVersion < 40:
|
||||
return "2.11"
|
||||
case dbVersion < 50:
|
||||
return "2.13"
|
||||
case dbVersion < 60:
|
||||
return "2.14.2"
|
||||
case dbVersion < 70:
|
||||
return "2.15.1"
|
||||
}
|
||||
|
||||
return "2.16.0"
|
||||
}
|
||||
|
||||
// getOrMigrateLegacyVersion to new Version struct
|
||||
func (store *Store) getOrMigrateLegacyVersion() (*models.Version, error) {
|
||||
// Very old versions of portainer did not have a version bucket, lets set some defaults
|
||||
dbVersion := 24
|
||||
edition := int(portaineree.PortainerCE)
|
||||
instanceId := ""
|
||||
|
||||
// If we already have a version key, we don't need to migrate
|
||||
v, err := store.VersionService.Version()
|
||||
if err == nil || !dataservices.IsErrObjectNotFound(err) {
|
||||
return v, err
|
||||
}
|
||||
|
||||
err = store.connection.GetObject(bucketName, []byte(legacyDBVersionKey), &dbVersion)
|
||||
if err != nil && !dataservices.IsErrObjectNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = store.connection.GetObject(bucketName, []byte(legacyEditionKey), &edition)
|
||||
if err != nil && !dataservices.IsErrObjectNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = store.connection.GetObject(bucketName, []byte(legacyInstanceKey), &instanceId)
|
||||
if err != nil && !dataservices.IsErrObjectNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.Version{
|
||||
SchemaVersion: dbVersionToSemanticVersion(dbVersion),
|
||||
Edition: edition,
|
||||
InstanceID: string(instanceId),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// finishMigrateLegacyVersion writes the new version to the DB and removes the old version keys from the DB
|
||||
func (store *Store) finishMigrateLegacyVersion(versionToWrite *models.Version) error {
|
||||
err := store.VersionService.UpdateVersion(versionToWrite)
|
||||
|
||||
// Remove legacy keys if present
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyDBVersionKey))
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyEditionKey))
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyInstanceKey))
|
||||
return err
|
||||
}
|
||||
@@ -1,32 +1,18 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
werrors "github.com/pkg/errors"
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type migration struct {
|
||||
dbversion int
|
||||
migrate func() error
|
||||
}
|
||||
|
||||
func migrationError(err error, context string) error {
|
||||
return werrors.Wrap(err, "failed in "+context)
|
||||
}
|
||||
|
||||
func newMigration(dbversion int, migrate func() error) migration {
|
||||
return migration{
|
||||
dbversion: dbversion,
|
||||
migrate: migrate,
|
||||
}
|
||||
}
|
||||
|
||||
func dbTooOldError() error {
|
||||
return errors.New("migrating from less than Portainer 1.21.0 is not supported, please contact Portainer support.")
|
||||
return errors.Wrap(err, "failed in "+context)
|
||||
}
|
||||
|
||||
func GetFunctionName(i interface{}) string {
|
||||
@@ -35,103 +21,105 @@ func GetFunctionName(i interface{}) string {
|
||||
|
||||
// Migrate checks the database version and migrate the existing data to the most recent data model.
|
||||
func (m *Migrator) Migrate() error {
|
||||
// set DB to updating status
|
||||
err := m.versionService.StoreIsUpdating(true)
|
||||
version, err := m.versionService.Version()
|
||||
if err != nil {
|
||||
return migrationError(err, "StoreIsUpdating")
|
||||
return migrationError(err, "get version service")
|
||||
}
|
||||
|
||||
migrations := []migration{
|
||||
// Portainer < 1.21.0
|
||||
newMigration(17, dbTooOldError),
|
||||
|
||||
// Portainer 1.21.0
|
||||
newMigration(18, m.updateUsersToDBVersion18),
|
||||
newMigration(18, m.updateEndpointsToDBVersion18),
|
||||
newMigration(18, m.updateEndpointGroupsToDBVersion18),
|
||||
newMigration(18, m.updateRegistriesToDBVersion18),
|
||||
|
||||
// 1.22.0
|
||||
newMigration(19, m.updateSettingsToDBVersion19),
|
||||
|
||||
// 1.22.1
|
||||
newMigration(20, m.updateUsersToDBVersion20),
|
||||
newMigration(20, m.updateSettingsToDBVersion20),
|
||||
newMigration(20, m.updateSchedulesToDBVersion20),
|
||||
|
||||
// Portainer 1.23.0
|
||||
// DBVersion 21 is missing as it was shipped as via hotfix 1.22.2
|
||||
newMigration(22, m.updateResourceControlsToDBVersion22),
|
||||
newMigration(22, m.updateUsersAndRolesToDBVersion22),
|
||||
|
||||
// Portainer 1.24.0
|
||||
newMigration(23, m.updateTagsToDBVersion23),
|
||||
newMigration(23, m.updateEndpointsAndEndpointGroupsToDBVersion23),
|
||||
|
||||
// Portainer 1.24.1
|
||||
newMigration(24, m.updateSettingsToDB24),
|
||||
|
||||
// Portainer 2.0.0
|
||||
newMigration(25, m.updateSettingsToDB25),
|
||||
newMigration(25, m.updateStacksToDB24), // yes this looks odd. Don't be tempted to move it
|
||||
|
||||
// Portainer 2.1.0
|
||||
newMigration(26, m.updateEndpointSettingsToDB25),
|
||||
|
||||
// Portainer 2.2.0
|
||||
newMigration(27, m.updateStackResourceControlToDB27),
|
||||
|
||||
// Portainer 2.6.0
|
||||
newMigration(30, m.migrateDBVersionToDB30),
|
||||
|
||||
// Portainer 2.9.0
|
||||
newMigration(32, m.migrateDBVersionToDB32),
|
||||
|
||||
// Portainer 2.9.1, 2.9.2
|
||||
newMigration(33, m.migrateDBVersionToDB33),
|
||||
|
||||
// Portainer 2.10
|
||||
newMigration(34, m.migrateDBVersionToDB34),
|
||||
|
||||
// Portainer 2.9.3 (yep out of order, but 2.10 is EE only)
|
||||
newMigration(35, m.migrateDBVersionToDB35),
|
||||
|
||||
newMigration(36, m.migrateDBVersionToDB36),
|
||||
|
||||
// Portainer 2.13
|
||||
newMigration(40, m.migrateDBVersionToDB40),
|
||||
|
||||
// Portainer 2.14
|
||||
newMigration(50, m.migrateDBVersionToDB50),
|
||||
|
||||
// Portainer 2.15
|
||||
newMigration(60, m.migrateDBVersionToDB60),
|
||||
schemaVersion, err := semver.NewVersion(version.SchemaVersion)
|
||||
if err != nil {
|
||||
return migrationError(err, "invalid db schema version")
|
||||
}
|
||||
|
||||
var lastDbVersion int
|
||||
for _, migration := range migrations {
|
||||
if m.currentDBVersion < migration.dbversion {
|
||||
newMigratorCount := 0
|
||||
apiVersion := semver.MustParse(portainer.APIVersion)
|
||||
if schemaVersion.Equal(apiVersion) {
|
||||
// detect and run migrations when the versions are the same.
|
||||
// e.g. development builds
|
||||
latestMigrations := m.LatestMigrations()
|
||||
if latestMigrations.Version.Equal(schemaVersion) &&
|
||||
version.MigratorCount != len(latestMigrations.MigrationFuncs) {
|
||||
err := runMigrations(latestMigrations.MigrationFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newMigratorCount = len(latestMigrations.MigrationFuncs)
|
||||
}
|
||||
} else {
|
||||
// regular path when major/minor/patch versions differ
|
||||
for _, migration := range m.migrations {
|
||||
if schemaVersion.LessThan(migration.Version) {
|
||||
|
||||
// Print the next line only when the version changes
|
||||
if migration.dbversion > lastDbVersion {
|
||||
migrateLog.Infof("Migrating DB to version %d", migration.dbversion)
|
||||
log.Info().Msgf("migrating data to %s", migration.Version.String())
|
||||
err := runMigrations(migration.MigrationFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := migration.migrate()
|
||||
if err != nil {
|
||||
return migrationError(err, GetFunctionName(migration.migrate))
|
||||
if apiVersion.Equal(migration.Version) {
|
||||
newMigratorCount = len(migration.MigrationFuncs)
|
||||
}
|
||||
}
|
||||
lastDbVersion = migration.dbversion
|
||||
}
|
||||
|
||||
migrateLog.Infof("Setting DB version to %d", portainer.DBVersion)
|
||||
err = m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
err = m.Always()
|
||||
if err != nil {
|
||||
return migrationError(err, "Always migrations returned error")
|
||||
}
|
||||
|
||||
version.SchemaVersion = portainer.APIVersion
|
||||
version.MigratorCount = newMigratorCount
|
||||
|
||||
err = m.versionService.UpdateVersion(version)
|
||||
if err != nil {
|
||||
return migrationError(err, "StoreDBVersion")
|
||||
}
|
||||
migrateLog.Infof("Updated DB version to %d", portainer.DBVersion)
|
||||
|
||||
// reset DB updating status
|
||||
return m.versionService.StoreIsUpdating(false)
|
||||
log.Info().Msgf("db migrated to %s", portainer.APIVersion)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runMigrations(migrationFuncs []func() error) error {
|
||||
for _, migrationFunc := range migrationFuncs {
|
||||
err := migrationFunc()
|
||||
if err != nil {
|
||||
return migrationError(err, GetFunctionName(migrationFunc))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) NeedsMigration() bool {
|
||||
// we need to migrate if anything changes with the version in the DB vs what our software version is.
|
||||
// If the version matches, then it's all down to the number of migration funcs we have for the current version
|
||||
// i.e. the MigratorCount
|
||||
|
||||
// In this particular instance we should log a fatal error
|
||||
if m.CurrentDBEdition() != portainer.PortainerCE {
|
||||
log.Fatal().Msg("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
|
||||
return false
|
||||
}
|
||||
|
||||
if m.CurrentSemanticDBVersion().LessThan(semver.MustParse(portainer.APIVersion)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if we have any migrations for the current version
|
||||
latestMigrations := m.LatestMigrations()
|
||||
if latestMigrations.Version.Equal(semver.MustParse(portainer.APIVersion)) {
|
||||
if m.currentDBVersion.MigratorCount != len(latestMigrations.MigrationFuncs) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// One remaining possibility if we get here. If our migrator count > 0 and we have no migration funcs
|
||||
// for the current version (i.e. they were deleted during development). Then we we need to migrate.
|
||||
// This is to reset the migrator count back to 0
|
||||
if m.currentDBVersion.MigratorCount > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateUsersToDBVersion18() error {
|
||||
migrateLog.Info("- updating users")
|
||||
log.Info().Msg("updating users")
|
||||
|
||||
legacyUsers, err := m.userService.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -40,7 +43,8 @@ func (m *Migrator) updateUsersToDBVersion18() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointsToDBVersion18() error {
|
||||
migrateLog.Info("- updating endpoints")
|
||||
log.Info().Msg("updating endpoints")
|
||||
|
||||
legacyEndpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -71,7 +75,8 @@ func (m *Migrator) updateEndpointsToDBVersion18() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
|
||||
migrateLog.Info("- updating endpoint groups")
|
||||
log.Info().Msg("updating endpoint groups")
|
||||
|
||||
legacyEndpointGroups, err := m.endpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -102,7 +107,8 @@ func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateRegistriesToDBVersion18() error {
|
||||
migrateLog.Info("- updating registries")
|
||||
log.Info().Msg("updating registries")
|
||||
|
||||
legacyRegistries, err := m.registryService.Registries()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion19() error {
|
||||
migrateLog.Info("- updating settings")
|
||||
log.Info().Msg("updating settings")
|
||||
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,16 +2,20 @@ package migrator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const scheduleScriptExecutionJobType = 1
|
||||
|
||||
func (m *Migrator) updateUsersToDBVersion20() error {
|
||||
migrateLog.Info("- updating user authentication")
|
||||
log.Info().Msg("updating user authentication")
|
||||
|
||||
return m.authorizationService.UpdateUsersAuthorizations()
|
||||
}
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion20() error {
|
||||
log.Info().Msg("updating settings")
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -23,7 +27,8 @@ func (m *Migrator) updateSettingsToDBVersion20() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateSchedulesToDBVersion20() error {
|
||||
migrateLog.Info("- updating schedules")
|
||||
log.Info().Msg("updating schedules")
|
||||
|
||||
legacySchedules, err := m.scheduleService.Schedules()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -3,10 +3,13 @@ package migrator
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateResourceControlsToDBVersion22() error {
|
||||
migrateLog.Info("- updating resource controls")
|
||||
log.Info().Msg("updating resource controls")
|
||||
|
||||
legacyResourceControls, err := m.resourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -25,7 +28,8 @@ func (m *Migrator) updateResourceControlsToDBVersion22() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateUsersAndRolesToDBVersion22() error {
|
||||
migrateLog.Info("- updating users and roles")
|
||||
log.Info().Msg("updating users and roles")
|
||||
|
||||
legacyUsers, err := m.userService.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateTagsToDBVersion23() error {
|
||||
migrateLog.Info("- Updating tags")
|
||||
log.Info().Msg("updating tags")
|
||||
|
||||
tags, err := m.tagService.Tags()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -17,11 +22,13 @@ func (m *Migrator) updateTagsToDBVersion23() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
|
||||
migrateLog.Info("- updating endpoints and endpoint groups")
|
||||
log.Info().Msg("updating endpoints and endpoint groups")
|
||||
|
||||
tags, err := m.tagService.Tags()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -90,5 +97,6 @@ func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateSettingsToDB24() error {
|
||||
migrateLog.Info("- updating Settings")
|
||||
log.Info().Msg("updating Settings")
|
||||
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
@@ -18,7 +22,8 @@ func (m *Migrator) updateSettingsToDB24() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateStacksToDB24() error {
|
||||
migrateLog.Info("- updating stacks")
|
||||
log.Info().Msg("updating stacks")
|
||||
|
||||
stacks, err := m.stackService.Stacks()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,10 +2,12 @@ package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateSettingsToDB25() error {
|
||||
migrateLog.Info("- updating settings")
|
||||
log.Info().Msg("updating settings")
|
||||
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
|
||||
@@ -2,10 +2,13 @@ package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateEndpointSettingsToDB25() error {
|
||||
migrateLog.Info("- updating endpoint settings")
|
||||
log.Info().Msg("updating endpoint settings")
|
||||
|
||||
settings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -3,11 +3,14 @@ package migrator
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/internal/stackutils"
|
||||
"github.com/portainer/portainer/api/stacks/stackutils"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) updateStackResourceControlToDB27() error {
|
||||
migrateLog.Info("- updating stack resource controls")
|
||||
log.Info().Msg("updating stack resource controls")
|
||||
|
||||
resourceControls, err := m.resourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB30() error {
|
||||
migrateLog.Info("- updating legacy settings")
|
||||
if err := m.MigrateSettingsToDB30(); err != nil {
|
||||
return err
|
||||
}
|
||||
import "github.com/rs/zerolog/log"
|
||||
|
||||
return nil
|
||||
func (m *Migrator) migrateDBVersionToDB30() error {
|
||||
log.Info().Msg("updating legacy settings")
|
||||
|
||||
return m.MigrateSettingsToDB30()
|
||||
}
|
||||
|
||||
// so setting to false and "", is what would happen without this code
|
||||
@@ -20,5 +19,6 @@ func (m *Migrator) MigrateSettingsToDB30() error {
|
||||
|
||||
legacySettings.OAuthSettings.SSO = false
|
||||
legacySettings.OAuthSettings.LogoutURI = ""
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ package migrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
snapshotutils "github.com/portainer/portainer/api/internal/snapshot"
|
||||
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB32() error {
|
||||
@@ -39,7 +39,8 @@ func (m *Migrator) migrateDBVersionToDB32() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateRegistriesToDB32() error {
|
||||
migrateLog.Info("- updating registries")
|
||||
log.Info().Msg("updating registries")
|
||||
|
||||
registries, err := m.registryService.Registries()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -82,7 +83,8 @@ func (m *Migrator) updateRegistriesToDB32() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateDockerhubToDB32() error {
|
||||
migrateLog.Info("- updating dockerhub")
|
||||
log.Info().Msg("updating dockerhub")
|
||||
|
||||
dockerhub, err := m.dockerhubService.DockerHub()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
return nil
|
||||
@@ -171,7 +173,8 @@ func (m *Migrator) updateDockerhubToDB32() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) updateVolumeResourceControlToDB32() error {
|
||||
migrateLog.Info("- updating resource controls")
|
||||
log.Info().Msg("updating resource controls")
|
||||
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed fetching environments: %w", err)
|
||||
@@ -199,7 +202,7 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
|
||||
|
||||
totalSnapshots := len(endpoint.Snapshots)
|
||||
if totalSnapshots == 0 {
|
||||
log.Println("[DEBUG] [volume migration] [message: no snapshot found]")
|
||||
log.Debug().Msg("no snapshot found")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -207,13 +210,13 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
|
||||
|
||||
endpointDockerID, err := snapshotutils.FetchDockerID(snapshot)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] [database,migrator,v31] [message: failed fetching environment docker id] [err: %s]", err)
|
||||
log.Warn().Err(err).Msg("failed fetching environment docker id")
|
||||
continue
|
||||
}
|
||||
|
||||
volumesData := snapshot.SnapshotRaw.Volumes
|
||||
if volumesData.Volumes == nil {
|
||||
log.Println("[DEBUG] [volume migration] [message: no volume data found]")
|
||||
log.Debug().Msg("no volume data found")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -224,17 +227,18 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
|
||||
for _, resourceControl := range volumeResourceControls {
|
||||
if newResourceID, ok := toUpdate[resourceControl.ID]; ok {
|
||||
resourceControl.ResourceID = newResourceID
|
||||
|
||||
err := m.resourceControlService.UpdateResourceControl(resourceControl.ID, resourceControl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed updating resource control %d: %w", resourceControl.ID, err)
|
||||
}
|
||||
|
||||
} else {
|
||||
err := m.resourceControlService.DeleteResourceControl(resourceControl.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed deleting resource control %d: %w", resourceControl.ID, err)
|
||||
}
|
||||
log.Printf("[DEBUG] [volume migration] [message: legacy resource control(%s) has been deleted]", resourceControl.ResourceID)
|
||||
|
||||
log.Debug().Str("resource_id", resourceControl.ResourceID).Msg("legacy resource control has been deleted")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,21 +261,25 @@ func findResourcesToUpdateForDB32(dockerID string, volumesData volume.VolumeList
|
||||
}
|
||||
|
||||
func (m *Migrator) kubeconfigExpiryToDB32() error {
|
||||
migrateLog.Info("- updating kubeconfig expiry")
|
||||
log.Info().Msg("updating kubeconfig expiry")
|
||||
|
||||
settings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.KubeconfigExpiry = portainer.DefaultKubeconfigExpiry
|
||||
return m.settingsService.UpdateSettings(settings)
|
||||
}
|
||||
|
||||
func (m *Migrator) helmRepositoryURLToDB32() error {
|
||||
migrateLog.Info("- setting default helm repository URL")
|
||||
log.Info().Msg("setting default helm repository URL")
|
||||
|
||||
settings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.HelmRepositoryURL = portainer.DefaultHelmRepositoryURL
|
||||
return m.settingsService.UpdateSettings(settings)
|
||||
}
|
||||
|
||||
@@ -2,24 +2,25 @@ package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB33() error {
|
||||
migrateLog.Info("- updating settings")
|
||||
if err := m.migrateSettingsToDB33(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("updating settings")
|
||||
|
||||
return nil
|
||||
return m.migrateSettingsToDB33()
|
||||
}
|
||||
|
||||
func (m *Migrator) migrateSettingsToDB33() error {
|
||||
log.Info().Msg("setting default kubctl shell")
|
||||
settings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrateLog.Info("- setting default kubectl shell image")
|
||||
log.Info().Msg("setting default kubectl shell image")
|
||||
settings.KubectlShellImage = portainer.DefaultKubectlShellImage
|
||||
|
||||
return m.settingsService.UpdateSettings(settings)
|
||||
}
|
||||
|
||||
@@ -2,33 +2,34 @@ package migrator
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB34() error {
|
||||
migrateLog.Info("- updating stacks")
|
||||
err := MigrateStackEntryPoint(m.stackService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("updating stacks")
|
||||
|
||||
return nil
|
||||
return MigrateStackEntryPoint(m.stackService)
|
||||
}
|
||||
|
||||
// MigrateStackEntryPoint exported for testing (blah.)
|
||||
// MigrateStackEntryPoint exported for testing
|
||||
func MigrateStackEntryPoint(stackService dataservices.StackService) error {
|
||||
stacks, err := stackService.Stacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range stacks {
|
||||
stack := &stacks[i]
|
||||
if stack.GitConfig == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
stack.GitConfig.ConfigFilePath = stack.EntryPoint
|
||||
if err := stackService.UpdateStack(stack.ID, stack); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,10 +3,5 @@ package migrator
|
||||
func (m *Migrator) migrateDBVersionToDB35() error {
|
||||
// These should have been migrated already, but due to an earlier bug and a bunch of duplicates,
|
||||
// calling it again will now fix the issue as the function has been repaired.
|
||||
migrateLog.Info("- updating dockerhub registries")
|
||||
err := m.updateDockerhubToDB32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return m.updateDockerhubToDB32()
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package migrator
|
||||
|
||||
const (
|
||||
db35TestFile = "portainer-mig-35.db"
|
||||
username = "portainer"
|
||||
password = "password"
|
||||
)
|
||||
|
||||
// TODO: this is exactly the kind of reaching into the internals of the store we should not do
|
||||
// func setupDB35Test(t *testing.T) *Migrator {
|
||||
// is := assert.New(t)
|
||||
// dbConn, err := bolt.Open(path.Join(t.TempDir(), db35TestFile), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
// is.NoError(err, "failed to init testing DB connection")
|
||||
|
||||
// // Create an old style dockerhub authenticated account
|
||||
// dockerhubService, err := dockerhub.NewService(&database.DbConnection{DB: dbConn})
|
||||
// is.NoError(err, "failed to init testing registry service")
|
||||
// err = dockerhubService.UpdateDockerHub(&portainer.DockerHub{true, username, password})
|
||||
// is.NoError(err, "failed to create dockerhub account")
|
||||
|
||||
// registryService, err := registry.NewService(&database.DbConnection{DB: dbConn})
|
||||
// is.NoError(err, "failed to init testing registry service")
|
||||
|
||||
// endpointService, err := endpoint.NewService(&database.DbConnection{DB: dbConn})
|
||||
// is.NoError(err, "failed to init endpoint service")
|
||||
|
||||
// m := &Migrator{
|
||||
// db: dbConn,
|
||||
// dockerhubService: dockerhubService,
|
||||
// registryService: registryService,
|
||||
// endpointService: endpointService,
|
||||
// }
|
||||
|
||||
// return m
|
||||
// }
|
||||
|
||||
// // TestUpdateDockerhubToDB32 tests a normal upgrade
|
||||
// func TestUpdateDockerhubToDB32(t *testing.T) {
|
||||
// is := assert.New(t)
|
||||
// m := setupDB35Test(t)
|
||||
// defer m.db.Close()
|
||||
// defer os.Remove(db35TestFile)
|
||||
|
||||
// if err := m.updateDockerhubToDB32(); err != nil {
|
||||
// t.Errorf("failed to update settings: %v", err)
|
||||
// }
|
||||
|
||||
// // Verify we have a single registry were created
|
||||
// registries, err := m.registryService.Registries()
|
||||
// is.NoError(err, "failed to read registries from the RegistryService")
|
||||
// is.Equal(len(registries), 1, "only one migrated registry expected")
|
||||
// }
|
||||
|
||||
// // TestUpdateDockerhubToDB32_with_duplicate_migrations tests an upgrade where in earlier versions a broken migration
|
||||
// // created a large number of duplicate "dockerhub migrated" registry entries.
|
||||
// func TestUpdateDockerhubToDB32_with_duplicate_migrations(t *testing.T) {
|
||||
// is := assert.New(t)
|
||||
// m := setupDB35Test(t)
|
||||
// defer m.db.Close()
|
||||
// defer os.Remove(db35TestFile)
|
||||
|
||||
// // Create lots of duplicate entries...
|
||||
// registry := &portainer.Registry{
|
||||
// Type: portainer.DockerHubRegistry,
|
||||
// Name: "Dockerhub (authenticated - migrated)",
|
||||
// URL: "docker.io",
|
||||
// Authentication: true,
|
||||
// Username: "portainer",
|
||||
// Password: "password",
|
||||
// RegistryAccesses: portainer.RegistryAccesses{},
|
||||
// }
|
||||
|
||||
// for i := 1; i < 150; i++ {
|
||||
// err := m.registryService.CreateRegistry(registry)
|
||||
// assert.NoError(t, err, "create registry failed")
|
||||
// }
|
||||
|
||||
// // Verify they were created
|
||||
// registries, err := m.registryService.Registries()
|
||||
// is.NoError(err, "failed to read registries from the RegistryService")
|
||||
// is.Condition(func() bool {
|
||||
// return len(registries) > 1
|
||||
// }, "expected multiple duplicate registry entries")
|
||||
|
||||
// // Now run the migrator
|
||||
// if err := m.updateDockerhubToDB32(); err != nil {
|
||||
// t.Errorf("failed to update settings: %v", err)
|
||||
// }
|
||||
|
||||
// // Verify we have a single registry were created
|
||||
// registries, err = m.registryService.Registries()
|
||||
// is.NoError(err, "failed to read registries from the RegistryService")
|
||||
// is.Equal(len(registries), 1, "only one migrated registry expected")
|
||||
// }
|
||||
@@ -3,18 +3,19 @@ package migrator
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB36() error {
|
||||
migrateLog.Info("Updating user authorizations")
|
||||
if err := m.migrateUsersToDB36(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("updating user authorizations")
|
||||
|
||||
return nil
|
||||
return m.migrateUsersToDB36()
|
||||
}
|
||||
|
||||
func (m *Migrator) migrateUsersToDB36() error {
|
||||
log.Info().Msg("updating user authorizations")
|
||||
|
||||
users, err := m.userService.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package migrator
|
||||
|
||||
import "github.com/portainer/portainer/api/internal/endpointutils"
|
||||
import (
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB40() error {
|
||||
if err := m.trustCurrentEdgeEndpointsDB40(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return m.trustCurrentEdgeEndpointsDB40()
|
||||
}
|
||||
|
||||
func (m *Migrator) trustCurrentEdgeEndpointsDB40() error {
|
||||
migrateLog.Info("- trusting current edge endpoints")
|
||||
log.Info().Msg("trusting current edge endpoints")
|
||||
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,6 +2,7 @@ package migrator
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB50() error {
|
||||
@@ -9,7 +10,8 @@ func (m *Migrator) migrateDBVersionToDB50() error {
|
||||
}
|
||||
|
||||
func (m *Migrator) migratePasswordLengthSettings() error {
|
||||
migrateLog.Info("Updating required password length")
|
||||
log.Info().Msg("updating required password length")
|
||||
|
||||
s, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to retrieve settings")
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB60() error {
|
||||
if err := m.addGpuInputFieldDB60(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return m.addGpuInputFieldDB60()
|
||||
}
|
||||
|
||||
func (m *Migrator) addGpuInputFieldDB60() error {
|
||||
migrateLog.Info("- add gpu input field")
|
||||
log.Info().Msg("add gpu input field")
|
||||
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpoint.Gpus = []portainer.Pair{}
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
if endpoint.Gpus == nil {
|
||||
endpoint.Gpus = []portainer.Pair{}
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
71
api/datastore/migrator/migrate_dbversion70.go
Normal file
71
api/datastore/migrator/migrate_dbversion70.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB70() error {
|
||||
log.Info().Msg("add IngressAvailabilityPerNamespace field")
|
||||
if err := m.updateIngressFieldsForEnvDB70(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
// copy snapshots to new object
|
||||
log.Info().Msg("moving snapshots from endpoint to new object")
|
||||
snapshot := portainer.Snapshot{EndpointID: endpoint.ID}
|
||||
|
||||
if len(endpoint.Snapshots) > 0 {
|
||||
snapshot.Docker = &endpoint.Snapshots[len(endpoint.Snapshots)-1]
|
||||
}
|
||||
|
||||
if len(endpoint.Kubernetes.Snapshots) > 0 {
|
||||
snapshot.Kubernetes = &endpoint.Kubernetes.Snapshots[len(endpoint.Kubernetes.Snapshots)-1]
|
||||
}
|
||||
|
||||
// save new object
|
||||
err = m.snapshotService.Create(&snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set to nil old fields
|
||||
log.Info().Msg("deleting snapshot from endpoint")
|
||||
endpoint.Snapshots = []portainer.DockerSnapshot{}
|
||||
endpoint.Kubernetes.Snapshots = []portainer.KubernetesSnapshot{}
|
||||
|
||||
// update endpoint
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateIngressFieldsForEnvDB70() error {
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = true
|
||||
endpoint.Kubernetes.Configuration.AllowNoneIngressClass = false
|
||||
endpoint.PostInitMigrations.MigrateIngresses = true
|
||||
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
36
api/datastore/migrator/migrate_dbversion71.go
Normal file
36
api/datastore/migrator/migrate_dbversion71.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB71() error {
|
||||
log.Info().Msg("removing orphaned snapshots")
|
||||
|
||||
snapshots, err := m.snapshotService.Snapshots()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range snapshots {
|
||||
_, err := m.endpointService.Endpoint(s.EndpointID)
|
||||
if err == nil {
|
||||
log.Debug().Int("endpoint_id", int(s.EndpointID)).Msg("keeping snapshot")
|
||||
continue
|
||||
} else if err != errors.ErrObjectNotFound {
|
||||
log.Debug().Int("endpoint_id", int(s.EndpointID)).Err(err).Msg("database error")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug().Int("endpoint_id", int(s.EndpointID)).Msg("removing snapshot")
|
||||
|
||||
err = m.snapshotService.DeleteSnapshot(s.EndpointID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
77
api/datastore/migrator/migrate_dbversion80.go
Normal file
77
api/datastore/migrator/migrate_dbversion80.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB80() error {
|
||||
if err := m.updateEdgeStackStatusForDB80(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.updateExistingEndpointsToNotDetectMetricsAPIForDB80(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateExistingEndpointsToNotDetectMetricsAPIForDB80() error {
|
||||
log.Info().Msg("updating existing endpoints to not detect metrics API for existing endpoints (k8s)")
|
||||
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if endpointutils.IsKubernetesEndpoint(&endpoint) {
|
||||
endpoint.Kubernetes.Flags.IsServerMetricsDetected = true
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateEdgeStackStatusForDB80() error {
|
||||
log.Info().Msg("transfer type field to details field for edge stack status")
|
||||
|
||||
edgeStacks, err := m.edgeStackService.EdgeStacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, edgeStack := range edgeStacks {
|
||||
for endpointId, status := range edgeStack.Status {
|
||||
switch status.Type {
|
||||
case portainer.EdgeStackStatusPending:
|
||||
status.Details.Pending = true
|
||||
case portainer.EdgeStackStatusOk:
|
||||
status.Details.Ok = true
|
||||
case portainer.EdgeStackStatusError:
|
||||
status.Details.Error = true
|
||||
case portainer.EdgeStackStatusAcknowledged:
|
||||
status.Details.Acknowledged = true
|
||||
case portainer.EdgeStackStatusRemove:
|
||||
status.Details.Remove = true
|
||||
case portainer.EdgeStackStatusRemoteUpdateSuccess:
|
||||
status.Details.RemoteUpdateSuccess = true
|
||||
}
|
||||
|
||||
edgeStack.Status[endpointId] = status
|
||||
}
|
||||
|
||||
err = m.edgeStackService.UpdateEdgeStack(edgeStack.ID, &edgeStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices/edgestack"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/dataservices/dockerhub"
|
||||
"github.com/portainer/portainer/api/dataservices/endpoint"
|
||||
"github.com/portainer/portainer/api/dataservices/endpointgroup"
|
||||
@@ -13,21 +21,21 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices/role"
|
||||
"github.com/portainer/portainer/api/dataservices/schedule"
|
||||
"github.com/portainer/portainer/api/dataservices/settings"
|
||||
"github.com/portainer/portainer/api/dataservices/snapshot"
|
||||
"github.com/portainer/portainer/api/dataservices/stack"
|
||||
"github.com/portainer/portainer/api/dataservices/tag"
|
||||
"github.com/portainer/portainer/api/dataservices/teammembership"
|
||||
"github.com/portainer/portainer/api/dataservices/user"
|
||||
"github.com/portainer/portainer/api/dataservices/version"
|
||||
plog "github.com/portainer/portainer/api/datastore/log"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
var migrateLog = plog.NewScopedLog("database, migrate")
|
||||
|
||||
type (
|
||||
// Migrator defines a service to migrate data after a Portainer version update.
|
||||
Migrator struct {
|
||||
currentDBVersion int
|
||||
currentDBVersion *models.Version
|
||||
migrations []Migrations
|
||||
|
||||
endpointGroupService *endpointgroup.Service
|
||||
endpointService *endpoint.Service
|
||||
endpointRelationService *endpointrelation.Service
|
||||
@@ -38,6 +46,7 @@ type (
|
||||
roleService *role.Service
|
||||
scheduleService *schedule.Service
|
||||
settingsService *settings.Service
|
||||
snapshotService *snapshot.Service
|
||||
stackService *stack.Service
|
||||
tagService *tag.Service
|
||||
teamMembershipService *teammembership.Service
|
||||
@@ -46,11 +55,12 @@ type (
|
||||
fileService portainer.FileService
|
||||
authorizationService *authorization.Service
|
||||
dockerhubService *dockerhub.Service
|
||||
edgeStackService *edgestack.Service
|
||||
}
|
||||
|
||||
// MigratorParameters represents the required parameters to create a new Migrator instance.
|
||||
MigratorParameters struct {
|
||||
DatabaseVersion int
|
||||
CurrentDBVersion *models.Version
|
||||
EndpointGroupService *endpointgroup.Service
|
||||
EndpointService *endpoint.Service
|
||||
EndpointRelationService *endpointrelation.Service
|
||||
@@ -61,6 +71,7 @@ type (
|
||||
RoleService *role.Service
|
||||
ScheduleService *schedule.Service
|
||||
SettingsService *settings.Service
|
||||
SnapshotService *snapshot.Service
|
||||
StackService *stack.Service
|
||||
TagService *tag.Service
|
||||
TeamMembershipService *teammembership.Service
|
||||
@@ -69,13 +80,14 @@ type (
|
||||
FileService portainer.FileService
|
||||
AuthorizationService *authorization.Service
|
||||
DockerhubService *dockerhub.Service
|
||||
EdgeStackService *edgestack.Service
|
||||
}
|
||||
)
|
||||
|
||||
// NewMigrator creates a new Migrator.
|
||||
func NewMigrator(parameters *MigratorParameters) *Migrator {
|
||||
return &Migrator{
|
||||
currentDBVersion: parameters.DatabaseVersion,
|
||||
migrator := &Migrator{
|
||||
currentDBVersion: parameters.CurrentDBVersion,
|
||||
endpointGroupService: parameters.EndpointGroupService,
|
||||
endpointService: parameters.EndpointService,
|
||||
endpointRelationService: parameters.EndpointRelationService,
|
||||
@@ -86,6 +98,7 @@ func NewMigrator(parameters *MigratorParameters) *Migrator {
|
||||
roleService: parameters.RoleService,
|
||||
scheduleService: parameters.ScheduleService,
|
||||
settingsService: parameters.SettingsService,
|
||||
snapshotService: parameters.SnapshotService,
|
||||
tagService: parameters.TagService,
|
||||
teamMembershipService: parameters.TeamMembershipService,
|
||||
stackService: parameters.StackService,
|
||||
@@ -94,10 +107,116 @@ func NewMigrator(parameters *MigratorParameters) *Migrator {
|
||||
fileService: parameters.FileService,
|
||||
authorizationService: parameters.AuthorizationService,
|
||||
dockerhubService: parameters.DockerhubService,
|
||||
edgeStackService: parameters.EdgeStackService,
|
||||
}
|
||||
|
||||
migrator.initMigrations()
|
||||
return migrator
|
||||
}
|
||||
|
||||
// Version exposes version of database
|
||||
func (migrator *Migrator) Version() int {
|
||||
return migrator.currentDBVersion
|
||||
func (m *Migrator) CurrentDBVersion() string {
|
||||
return m.currentDBVersion.SchemaVersion
|
||||
}
|
||||
|
||||
func (m *Migrator) CurrentDBEdition() portainer.SoftwareEdition {
|
||||
return portainer.SoftwareEdition(m.currentDBVersion.Edition)
|
||||
}
|
||||
|
||||
func (m *Migrator) CurrentSemanticDBVersion() *semver.Version {
|
||||
v, err := semver.NewVersion(m.currentDBVersion.SchemaVersion)
|
||||
if err != nil {
|
||||
log.Fatal().Stack().Err(err).Msg("failed to parse current version")
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (m *Migrator) addMigrations(v string, funcs ...func() error) {
|
||||
m.migrations = append(m.migrations, Migrations{
|
||||
Version: semver.MustParse(v),
|
||||
MigrationFuncs: funcs,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Migrator) LatestMigrations() Migrations {
|
||||
return m.migrations[len(m.migrations)-1]
|
||||
}
|
||||
|
||||
// !NOTE: Migration funtions should ideally be idempotent.
|
||||
// ! Which simply means the function can run over the same data many times but only transform it once.
|
||||
// ! In practice this really just means an extra check or two to ensure we're not destroying valid data.
|
||||
// ! This is not a hard rule though. Understand the limitations. A migration function may only run over
|
||||
// ! the data more than once if a new migration function is added and the version of your database schema is
|
||||
// ! the same. e.g. two developers working on the same version add two different functions for different things.
|
||||
// ! This increases the migration funcs count and so they all run again.
|
||||
|
||||
type Migrations struct {
|
||||
Version *semver.Version
|
||||
MigrationFuncs MigrationFuncs
|
||||
}
|
||||
|
||||
type MigrationFuncs []func() error
|
||||
|
||||
func (m *Migrator) initMigrations() {
|
||||
// !IMPORTANT: Do not be tempted to alter the order of these migrations.
|
||||
// ! Even though one of them looks out of order. Caused by history related
|
||||
// ! to maintaining two versions and releasing at different times
|
||||
|
||||
m.addMigrations("1.0.0", dbTooOldError) // default version found after migration
|
||||
|
||||
m.addMigrations("1.21",
|
||||
m.updateUsersToDBVersion18,
|
||||
m.updateEndpointsToDBVersion18,
|
||||
m.updateEndpointGroupsToDBVersion18,
|
||||
m.updateRegistriesToDBVersion18)
|
||||
|
||||
m.addMigrations("1.22", m.updateSettingsToDBVersion19)
|
||||
|
||||
m.addMigrations("1.22.1",
|
||||
m.updateUsersToDBVersion20,
|
||||
m.updateSettingsToDBVersion20,
|
||||
m.updateSchedulesToDBVersion20)
|
||||
|
||||
m.addMigrations("1.23",
|
||||
m.updateResourceControlsToDBVersion22,
|
||||
m.updateUsersAndRolesToDBVersion22)
|
||||
|
||||
m.addMigrations("1.24",
|
||||
m.updateTagsToDBVersion23,
|
||||
m.updateEndpointsAndEndpointGroupsToDBVersion23)
|
||||
|
||||
m.addMigrations("1.24.1", m.updateSettingsToDB24)
|
||||
|
||||
m.addMigrations("2.0",
|
||||
m.updateSettingsToDB25,
|
||||
m.updateStacksToDB24)
|
||||
|
||||
m.addMigrations("2.1", m.updateEndpointSettingsToDB25)
|
||||
m.addMigrations("2.2", m.updateStackResourceControlToDB27)
|
||||
m.addMigrations("2.6", m.migrateDBVersionToDB30)
|
||||
m.addMigrations("2.9", m.migrateDBVersionToDB32)
|
||||
m.addMigrations("2.9.2", m.migrateDBVersionToDB33)
|
||||
m.addMigrations("2.10.0", m.migrateDBVersionToDB34)
|
||||
m.addMigrations("2.9.3", m.migrateDBVersionToDB35)
|
||||
m.addMigrations("2.12", m.migrateDBVersionToDB36)
|
||||
m.addMigrations("2.13", m.migrateDBVersionToDB40)
|
||||
m.addMigrations("2.14", m.migrateDBVersionToDB50)
|
||||
m.addMigrations("2.15", m.migrateDBVersionToDB60)
|
||||
m.addMigrations("2.16", m.migrateDBVersionToDB70)
|
||||
m.addMigrations("2.16.1", m.migrateDBVersionToDB71)
|
||||
m.addMigrations("2.17", m.migrateDBVersionToDB80)
|
||||
m.addMigrations("2.18")
|
||||
|
||||
// Add new migrations below...
|
||||
// One function per migration, each versions migration funcs in the same file.
|
||||
}
|
||||
|
||||
// Always is always run at the end of migrations
|
||||
func (m *Migrator) Always() error {
|
||||
// currently nothing to be done in CE... yet
|
||||
return nil
|
||||
}
|
||||
|
||||
func dbTooOldError() error {
|
||||
return errors.New("migrating from less than Portainer 1.21.0 is not supported, please contact Portainer support")
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ package datastore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/dataservices/apikeyrepository"
|
||||
"github.com/portainer/portainer/api/dataservices/customtemplate"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices/role"
|
||||
"github.com/portainer/portainer/api/dataservices/schedule"
|
||||
"github.com/portainer/portainer/api/dataservices/settings"
|
||||
"github.com/portainer/portainer/api/dataservices/snapshot"
|
||||
"github.com/portainer/portainer/api/dataservices/ssl"
|
||||
"github.com/portainer/portainer/api/dataservices/stack"
|
||||
"github.com/portainer/portainer/api/dataservices/tag"
|
||||
@@ -33,7 +35,8 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices/user"
|
||||
"github.com/portainer/portainer/api/dataservices/version"
|
||||
"github.com/portainer/portainer/api/dataservices/webhook"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Store defines the implementation of portainer.DataStore using
|
||||
@@ -59,6 +62,7 @@ type Store struct {
|
||||
APIKeyRepositoryService *apikeyrepository.Service
|
||||
ScheduleService *schedule.Service
|
||||
SettingsService *settings.Service
|
||||
SnapshotService *snapshot.Service
|
||||
SSLSettingsService *ssl.Service
|
||||
StackService *stack.Service
|
||||
TagService *tag.Service
|
||||
@@ -161,6 +165,12 @@ func (store *Store) initServices() error {
|
||||
}
|
||||
store.SettingsService = settingsService
|
||||
|
||||
snapshotService, err := snapshot.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.SnapshotService = snapshotService
|
||||
|
||||
sslSettingsService, err := ssl.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -300,6 +310,10 @@ func (store *Store) Settings() dataservices.SettingsService {
|
||||
return store.SettingsService
|
||||
}
|
||||
|
||||
func (store *Store) Snapshot() dataservices.SnapshotService {
|
||||
return store.SnapshotService
|
||||
}
|
||||
|
||||
// SSLSettings gives access to the SSL Settings data management layer
|
||||
func (store *Store) SSLSettings() dataservices.SSLSettingsService {
|
||||
return store.SSLSettingsService
|
||||
@@ -360,6 +374,7 @@ type storeExport struct {
|
||||
Role []portainer.Role `json:"roles,omitempty"`
|
||||
Schedules []portainer.Schedule `json:"schedules,omitempty"`
|
||||
Settings portainer.Settings `json:"settings,omitempty"`
|
||||
Snapshot []portainer.Snapshot `json:"snapshots,omitempty"`
|
||||
SSLSettings portainer.SSLSettings `json:"ssl,omitempty"`
|
||||
Stack []portainer.Stack `json:"stacks,omitempty"`
|
||||
Tag []portainer.Tag `json:"tags,omitempty"`
|
||||
@@ -367,7 +382,7 @@ type storeExport struct {
|
||||
Team []portainer.Team `json:"teams,omitempty"`
|
||||
TunnelServer portainer.TunnelServerInfo `json:"tunnel_server,omitempty"`
|
||||
User []portainer.User `json:"users,omitempty"`
|
||||
Version map[string]string `json:"version,omitempty"`
|
||||
Version models.Version `json:"version,omitempty"`
|
||||
Webhook []portainer.Webhook `json:"webhooks,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
@@ -378,7 +393,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if c, err := store.CustomTemplate().CustomTemplates(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Custom Templates")
|
||||
log.Error().Err(err).Msg("exporting Custom Templates")
|
||||
}
|
||||
} else {
|
||||
backup.CustomTemplate = c
|
||||
@@ -386,7 +401,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if e, err := store.EdgeGroup().EdgeGroups(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Edge Groups")
|
||||
log.Error().Err(err).Msg("exporting Edge Groups")
|
||||
}
|
||||
} else {
|
||||
backup.EdgeGroup = e
|
||||
@@ -394,7 +409,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if e, err := store.EdgeJob().EdgeJobs(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Edge Jobs")
|
||||
log.Error().Err(err).Msg("exporting Edge Jobs")
|
||||
}
|
||||
} else {
|
||||
backup.EdgeJob = e
|
||||
@@ -402,7 +417,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if e, err := store.EdgeStack().EdgeStacks(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Edge Stacks")
|
||||
log.Error().Err(err).Msg("exporting Edge Stacks")
|
||||
}
|
||||
} else {
|
||||
backup.EdgeStack = e
|
||||
@@ -410,7 +425,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if e, err := store.Endpoint().Endpoints(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Endpoints")
|
||||
log.Error().Err(err).Msg("exporting Endpoints")
|
||||
}
|
||||
} else {
|
||||
backup.Endpoint = e
|
||||
@@ -418,7 +433,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if e, err := store.EndpointGroup().EndpointGroups(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Endpoint Groups")
|
||||
log.Error().Err(err).Msg("exporting Endpoint Groups")
|
||||
}
|
||||
} else {
|
||||
backup.EndpointGroup = e
|
||||
@@ -426,7 +441,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if r, err := store.EndpointRelation().EndpointRelations(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Endpoint Relations")
|
||||
log.Error().Err(err).Msg("exporting Endpoint Relations")
|
||||
}
|
||||
} else {
|
||||
backup.EndpointRelation = r
|
||||
@@ -434,7 +449,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if r, err := store.ExtensionService.Extensions(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Extensions")
|
||||
log.Error().Err(err).Msg("exporting Extensions")
|
||||
}
|
||||
} else {
|
||||
backup.Extensions = r
|
||||
@@ -442,7 +457,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if r, err := store.HelmUserRepository().HelmUserRepositories(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Helm User Repositories")
|
||||
log.Error().Err(err).Msg("exporting Helm User Repositories")
|
||||
}
|
||||
} else {
|
||||
backup.HelmUserRepository = r
|
||||
@@ -450,7 +465,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if r, err := store.Registry().Registries(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Registries")
|
||||
log.Error().Err(err).Msg("exporting Registries")
|
||||
}
|
||||
} else {
|
||||
backup.Registry = r
|
||||
@@ -458,7 +473,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if c, err := store.ResourceControl().ResourceControls(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Resource Controls")
|
||||
log.Error().Err(err).Msg("exporting Resource Controls")
|
||||
}
|
||||
} else {
|
||||
backup.ResourceControl = c
|
||||
@@ -466,7 +481,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if role, err := store.Role().Roles(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Roles")
|
||||
log.Error().Err(err).Msg("exporting Roles")
|
||||
}
|
||||
} else {
|
||||
backup.Role = role
|
||||
@@ -474,7 +489,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if r, err := store.ScheduleService.Schedules(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Schedules")
|
||||
log.Error().Err(err).Msg("exporting Schedules")
|
||||
}
|
||||
} else {
|
||||
backup.Schedules = r
|
||||
@@ -482,15 +497,23 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if settings, err := store.Settings().Settings(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Settings")
|
||||
log.Error().Err(err).Msg("exporting Settings")
|
||||
}
|
||||
} else {
|
||||
backup.Settings = *settings
|
||||
}
|
||||
|
||||
if snapshot, err := store.Snapshot().Snapshots(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
log.Error().Err(err).Msg("exporting Snapshots")
|
||||
}
|
||||
} else {
|
||||
backup.Snapshot = snapshot
|
||||
}
|
||||
|
||||
if settings, err := store.SSLSettings().Settings(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting SSL Settings")
|
||||
log.Error().Err(err).Msg("exporting SSL Settings")
|
||||
}
|
||||
} else {
|
||||
backup.SSLSettings = *settings
|
||||
@@ -498,7 +521,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if t, err := store.Stack().Stacks(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Stacks")
|
||||
log.Error().Err(err).Msg("exporting Stacks")
|
||||
}
|
||||
} else {
|
||||
backup.Stack = t
|
||||
@@ -506,7 +529,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if t, err := store.Tag().Tags(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Tags")
|
||||
log.Error().Err(err).Msg("exporting Tags")
|
||||
}
|
||||
} else {
|
||||
backup.Tag = t
|
||||
@@ -514,7 +537,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if t, err := store.TeamMembership().TeamMemberships(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Team Memberships")
|
||||
log.Error().Err(err).Msg("exporting Team Memberships")
|
||||
}
|
||||
} else {
|
||||
backup.TeamMembership = t
|
||||
@@ -522,7 +545,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if t, err := store.Team().Teams(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Teams")
|
||||
log.Error().Err(err).Msg("exporting Teams")
|
||||
}
|
||||
} else {
|
||||
backup.Team = t
|
||||
@@ -530,7 +553,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if info, err := store.TunnelServer().Info(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Tunnel Server")
|
||||
log.Error().Err(err).Msg("exporting Tunnel Server")
|
||||
}
|
||||
} else {
|
||||
backup.TunnelServer = *info
|
||||
@@ -538,7 +561,7 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if users, err := store.User().Users(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Users")
|
||||
log.Error().Err(err).Msg("exporting Users")
|
||||
}
|
||||
} else {
|
||||
backup.User = users
|
||||
@@ -546,39 +569,36 @@ func (store *Store) Export(filename string) (err error) {
|
||||
|
||||
if webhooks, err := store.Webhook().Webhooks(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting Webhooks")
|
||||
log.Error().Err(err).Msg("exporting Webhooks")
|
||||
}
|
||||
} else {
|
||||
backup.Webhook = webhooks
|
||||
}
|
||||
|
||||
v, err := store.Version().DBVersion()
|
||||
if err != nil && !store.IsErrObjectNotFound(err) {
|
||||
logrus.WithError(err).Errorf("Exporting DB version")
|
||||
}
|
||||
instance, _ := store.Version().InstanceID()
|
||||
backup.Version = map[string]string{
|
||||
"DB_VERSION": strconv.Itoa(v),
|
||||
"INSTANCE_ID": instance,
|
||||
if version, err := store.Version().Version(); err != nil {
|
||||
if !store.IsErrObjectNotFound(err) {
|
||||
log.Error().Err(err).Msg("exporting Version")
|
||||
}
|
||||
} else {
|
||||
backup.Version = *version
|
||||
}
|
||||
|
||||
backup.Metadata, err = store.connection.BackupMetadata()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("Exporting Metadata")
|
||||
log.Error().Err(err).Msg("exporting Metadata")
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(backup, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filename, b, 0600)
|
||||
return os.WriteFile(filename, b, 0600)
|
||||
}
|
||||
|
||||
func (store *Store) Import(filename string) (err error) {
|
||||
|
||||
backup := storeExport{}
|
||||
|
||||
s, err := ioutil.ReadFile(filename)
|
||||
s, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -587,19 +607,7 @@ func (store *Store) Import(filename string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: yup, this is bad, and should be in a version struct...
|
||||
if dbversion, ok := backup.Version["DB_VERSION"]; ok {
|
||||
if v, err := strconv.Atoi(dbversion); err == nil {
|
||||
if err := store.Version().StoreDBVersion(v); err != nil {
|
||||
logrus.WithError(err).Errorf("DB_VERSION import issue")
|
||||
}
|
||||
}
|
||||
}
|
||||
if instanceID, ok := backup.Version["INSTANCE_ID"]; ok {
|
||||
if err := store.Version().StoreInstanceID(instanceID); err != nil {
|
||||
logrus.WithError(err).Errorf("INSTANCE_ID import issue")
|
||||
}
|
||||
}
|
||||
store.Version().UpdateVersion(&backup.Version)
|
||||
|
||||
for _, v := range backup.CustomTemplate {
|
||||
store.CustomTemplate().UpdateCustomTemplate(v.ID, &v)
|
||||
@@ -648,6 +656,10 @@ func (store *Store) Import(filename string) (err error) {
|
||||
store.Settings().UpdateSettings(&backup.Settings)
|
||||
store.SSLSettings().UpdateSettings(&backup.SSLSettings)
|
||||
|
||||
for _, v := range backup.Snapshot {
|
||||
store.Snapshot().UpdateSnapshot(&v)
|
||||
}
|
||||
|
||||
for _, v := range backup.Stack {
|
||||
store.Stack().UpdateStack(v.ID, &v)
|
||||
}
|
||||
@@ -668,7 +680,7 @@ func (store *Store) Import(filename string) (err error) {
|
||||
|
||||
for _, user := range backup.User {
|
||||
if err := store.User().UpdateUser(user.ID, &user); err != nil {
|
||||
logrus.WithField("user", user).WithError(err).Errorf("User: Failed to Update Database")
|
||||
log.Debug().Str("user", fmt.Sprintf("%+v", user)).Err(err).Msg("failed to update the user in the database")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
],
|
||||
"endpoints": [
|
||||
{
|
||||
"Agent": {
|
||||
"Version": ""
|
||||
},
|
||||
"AuthorizedTeams": null,
|
||||
"AuthorizedUsers": null,
|
||||
"AzureCredentials": {
|
||||
@@ -49,16 +52,26 @@
|
||||
"IsEdgeDevice": false,
|
||||
"Kubernetes": {
|
||||
"Configuration": {
|
||||
"AllowNoneIngressClass": false,
|
||||
"EnableResourceOverCommit": false,
|
||||
"IngressAvailabilityPerNamespace": true,
|
||||
"IngressClasses": null,
|
||||
"ResourceOverCommitPercentage": 0,
|
||||
"RestrictDefaultNamespace": false,
|
||||
"StorageClasses": null,
|
||||
"UseLoadBalancer": false,
|
||||
"UseServerMetrics": false
|
||||
},
|
||||
"Snapshots": null
|
||||
"Flags": {
|
||||
"IsServerMetricsDetected": false
|
||||
},
|
||||
"Snapshots": []
|
||||
},
|
||||
"LastCheckInDate": 0,
|
||||
"Name": "local",
|
||||
"PostInitMigrations": {
|
||||
"MigrateIngresses": true
|
||||
},
|
||||
"PublicURL": "",
|
||||
"QueryDate": 0,
|
||||
"SecuritySettings": {
|
||||
@@ -72,127 +85,7 @@
|
||||
"allowVolumeBrowserForRegularUsers": false,
|
||||
"enableHostManagementFeatures": false
|
||||
},
|
||||
"Snapshots": [
|
||||
{
|
||||
"DockerSnapshotRaw": {
|
||||
"Containers": null,
|
||||
"Images": null,
|
||||
"Info": {
|
||||
"Architecture": "",
|
||||
"BridgeNfIp6tables": false,
|
||||
"BridgeNfIptables": false,
|
||||
"CPUSet": false,
|
||||
"CPUShares": false,
|
||||
"CgroupDriver": "",
|
||||
"ContainerdCommit": {
|
||||
"Expected": "",
|
||||
"ID": ""
|
||||
},
|
||||
"Containers": 0,
|
||||
"ContainersPaused": 0,
|
||||
"ContainersRunning": 0,
|
||||
"ContainersStopped": 0,
|
||||
"CpuCfsPeriod": false,
|
||||
"CpuCfsQuota": false,
|
||||
"Debug": false,
|
||||
"DefaultRuntime": "",
|
||||
"DockerRootDir": "",
|
||||
"Driver": "",
|
||||
"DriverStatus": null,
|
||||
"ExperimentalBuild": false,
|
||||
"GenericResources": null,
|
||||
"HttpProxy": "",
|
||||
"HttpsProxy": "",
|
||||
"ID": "",
|
||||
"IPv4Forwarding": false,
|
||||
"Images": 0,
|
||||
"IndexServerAddress": "",
|
||||
"InitBinary": "",
|
||||
"InitCommit": {
|
||||
"Expected": "",
|
||||
"ID": ""
|
||||
},
|
||||
"Isolation": "",
|
||||
"KernelMemory": false,
|
||||
"KernelMemoryTCP": false,
|
||||
"KernelVersion": "",
|
||||
"Labels": null,
|
||||
"LiveRestoreEnabled": false,
|
||||
"LoggingDriver": "",
|
||||
"MemTotal": 0,
|
||||
"MemoryLimit": false,
|
||||
"NCPU": 0,
|
||||
"NEventsListener": 0,
|
||||
"NFd": 0,
|
||||
"NGoroutines": 0,
|
||||
"Name": "",
|
||||
"NoProxy": "",
|
||||
"OSType": "",
|
||||
"OSVersion": "",
|
||||
"OomKillDisable": false,
|
||||
"OperatingSystem": "",
|
||||
"PidsLimit": false,
|
||||
"Plugins": {
|
||||
"Authorization": null,
|
||||
"Log": null,
|
||||
"Network": null,
|
||||
"Volume": null
|
||||
},
|
||||
"RegistryConfig": null,
|
||||
"RuncCommit": {
|
||||
"Expected": "",
|
||||
"ID": ""
|
||||
},
|
||||
"Runtimes": null,
|
||||
"SecurityOptions": null,
|
||||
"ServerVersion": "",
|
||||
"SwapLimit": false,
|
||||
"Swarm": {
|
||||
"ControlAvailable": false,
|
||||
"Error": "",
|
||||
"LocalNodeState": "",
|
||||
"NodeAddr": "",
|
||||
"NodeID": "",
|
||||
"RemoteManagers": null
|
||||
},
|
||||
"SystemTime": "",
|
||||
"Warnings": null
|
||||
},
|
||||
"Networks": null,
|
||||
"Version": {
|
||||
"ApiVersion": "",
|
||||
"Arch": "",
|
||||
"GitCommit": "",
|
||||
"GoVersion": "",
|
||||
"Os": "",
|
||||
"Platform": {
|
||||
"Name": ""
|
||||
},
|
||||
"Version": ""
|
||||
},
|
||||
"Volumes": {
|
||||
"Volumes": null,
|
||||
"Warnings": null
|
||||
}
|
||||
},
|
||||
"DockerVersion": "20.10.13",
|
||||
"GpuUseAll": false,
|
||||
"GpuUseList": null,
|
||||
"HealthyContainerCount": 0,
|
||||
"ImageCount": 9,
|
||||
"NodeCount": 0,
|
||||
"RunningContainerCount": 5,
|
||||
"ServiceCount": 0,
|
||||
"StackCount": 2,
|
||||
"StoppedContainerCount": 0,
|
||||
"Swarm": false,
|
||||
"Time": 1648610112,
|
||||
"TotalCPU": 8,
|
||||
"TotalMemory": 25098706944,
|
||||
"UnhealthyContainerCount": 0,
|
||||
"VolumeCount": 10
|
||||
}
|
||||
],
|
||||
"Snapshots": [],
|
||||
"Status": 1,
|
||||
"TLSConfig": {
|
||||
"TLS": false,
|
||||
@@ -750,6 +643,7 @@
|
||||
"Scopes": "",
|
||||
"UserIdentifier": ""
|
||||
},
|
||||
"ShowKomposeBuildOption": false,
|
||||
"SnapshotInterval": "5m",
|
||||
"TemplatesURL": "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json",
|
||||
"TrustOnFirstConnect": false,
|
||||
@@ -772,6 +666,131 @@
|
||||
"mpsUser": ""
|
||||
}
|
||||
},
|
||||
"snapshots": [
|
||||
{
|
||||
"Docker": {
|
||||
"DockerSnapshotRaw": {
|
||||
"Containers": null,
|
||||
"Images": null,
|
||||
"Info": {
|
||||
"Architecture": "",
|
||||
"BridgeNfIp6tables": false,
|
||||
"BridgeNfIptables": false,
|
||||
"CPUSet": false,
|
||||
"CPUShares": false,
|
||||
"CgroupDriver": "",
|
||||
"ContainerdCommit": {
|
||||
"Expected": "",
|
||||
"ID": ""
|
||||
},
|
||||
"Containers": 0,
|
||||
"ContainersPaused": 0,
|
||||
"ContainersRunning": 0,
|
||||
"ContainersStopped": 0,
|
||||
"CpuCfsPeriod": false,
|
||||
"CpuCfsQuota": false,
|
||||
"Debug": false,
|
||||
"DefaultRuntime": "",
|
||||
"DockerRootDir": "",
|
||||
"Driver": "",
|
||||
"DriverStatus": null,
|
||||
"ExperimentalBuild": false,
|
||||
"GenericResources": null,
|
||||
"HttpProxy": "",
|
||||
"HttpsProxy": "",
|
||||
"ID": "",
|
||||
"IPv4Forwarding": false,
|
||||
"Images": 0,
|
||||
"IndexServerAddress": "",
|
||||
"InitBinary": "",
|
||||
"InitCommit": {
|
||||
"Expected": "",
|
||||
"ID": ""
|
||||
},
|
||||
"Isolation": "",
|
||||
"KernelMemory": false,
|
||||
"KernelMemoryTCP": false,
|
||||
"KernelVersion": "",
|
||||
"Labels": null,
|
||||
"LiveRestoreEnabled": false,
|
||||
"LoggingDriver": "",
|
||||
"MemTotal": 0,
|
||||
"MemoryLimit": false,
|
||||
"NCPU": 0,
|
||||
"NEventsListener": 0,
|
||||
"NFd": 0,
|
||||
"NGoroutines": 0,
|
||||
"Name": "",
|
||||
"NoProxy": "",
|
||||
"OSType": "",
|
||||
"OSVersion": "",
|
||||
"OomKillDisable": false,
|
||||
"OperatingSystem": "",
|
||||
"PidsLimit": false,
|
||||
"Plugins": {
|
||||
"Authorization": null,
|
||||
"Log": null,
|
||||
"Network": null,
|
||||
"Volume": null
|
||||
},
|
||||
"RegistryConfig": null,
|
||||
"RuncCommit": {
|
||||
"Expected": "",
|
||||
"ID": ""
|
||||
},
|
||||
"Runtimes": null,
|
||||
"SecurityOptions": null,
|
||||
"ServerVersion": "",
|
||||
"SwapLimit": false,
|
||||
"Swarm": {
|
||||
"ControlAvailable": false,
|
||||
"Error": "",
|
||||
"LocalNodeState": "",
|
||||
"NodeAddr": "",
|
||||
"NodeID": "",
|
||||
"RemoteManagers": null
|
||||
},
|
||||
"SystemTime": "",
|
||||
"Warnings": null
|
||||
},
|
||||
"Networks": null,
|
||||
"Version": {
|
||||
"ApiVersion": "",
|
||||
"Arch": "",
|
||||
"GitCommit": "",
|
||||
"GoVersion": "",
|
||||
"Os": "",
|
||||
"Platform": {
|
||||
"Name": ""
|
||||
},
|
||||
"Version": ""
|
||||
},
|
||||
"Volumes": {
|
||||
"Volumes": null,
|
||||
"Warnings": null
|
||||
}
|
||||
},
|
||||
"DockerVersion": "20.10.13",
|
||||
"GpuUseAll": false,
|
||||
"GpuUseList": null,
|
||||
"HealthyContainerCount": 0,
|
||||
"ImageCount": 9,
|
||||
"NodeCount": 0,
|
||||
"RunningContainerCount": 5,
|
||||
"ServiceCount": 0,
|
||||
"StackCount": 2,
|
||||
"StoppedContainerCount": 0,
|
||||
"Swarm": false,
|
||||
"Time": 1648610112,
|
||||
"TotalCPU": 8,
|
||||
"TotalMemory": 25098706944,
|
||||
"UnhealthyContainerCount": 0,
|
||||
"VolumeCount": 10
|
||||
},
|
||||
"EndpointId": 1,
|
||||
"Kubernetes": null
|
||||
}
|
||||
],
|
||||
"ssl": {
|
||||
"certPath": "",
|
||||
"httpEnabled": true,
|
||||
@@ -915,8 +934,6 @@
|
||||
}
|
||||
],
|
||||
"version": {
|
||||
"DB_UPDATING": "false",
|
||||
"DB_VERSION": "60",
|
||||
"INSTANCE_ID": "null"
|
||||
"VERSION": "{\"SchemaVersion\":\"2.18.0\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,32 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
)
|
||||
|
||||
var errTempDir = errors.New("can't create a temp dir")
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (store *Store) GetConnection() portainer.Connection {
|
||||
return store.connection
|
||||
}
|
||||
|
||||
func MustNewTestStore(init, secure bool) (bool, *Store, func()) {
|
||||
newStore, store, teardown, err := NewTestStore(init, secure)
|
||||
func MustNewTestStore(t testing.TB, init, secure bool) (bool, *Store, func()) {
|
||||
newStore, store, teardown, err := NewTestStore(t, init, secure)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errTempDir) {
|
||||
teardown()
|
||||
}
|
||||
log.Fatal(err)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
return newStore, store, teardown
|
||||
}
|
||||
|
||||
func NewTestStore(init, secure bool) (bool, *Store, func(), error) {
|
||||
func NewTestStore(t testing.TB, init, secure bool) (bool, *Store, func(), error) {
|
||||
// Creates unique temp directory in a concurrency friendly manner.
|
||||
storePath, err := ioutil.TempDir("", "test-store")
|
||||
if err != nil {
|
||||
return false, nil, nil, errors.Wrap(errTempDir, err.Error())
|
||||
}
|
||||
storePath := t.TempDir()
|
||||
|
||||
fileService, err := filesystem.NewService(storePath, "")
|
||||
if err != nil {
|
||||
@@ -51,6 +42,7 @@ func NewTestStore(init, secure bool) (bool, *Store, func(), error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
store := NewStore(storePath, fileService, connection)
|
||||
newStore, err := store.Open()
|
||||
if err != nil {
|
||||
@@ -66,27 +58,22 @@ func NewTestStore(init, secure bool) (bool, *Store, func(), error) {
|
||||
|
||||
if newStore {
|
||||
// from MigrateData
|
||||
store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
v := models.Version{
|
||||
SchemaVersion: portainer.APIVersion,
|
||||
Edition: int(portainer.PortainerCE),
|
||||
}
|
||||
err = store.VersionService.UpdateVersion(&v)
|
||||
if err != nil {
|
||||
return newStore, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
teardown := func() {
|
||||
teardown(store, storePath)
|
||||
err := store.Close()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
}
|
||||
|
||||
return newStore, store, teardown, nil
|
||||
}
|
||||
|
||||
func teardown(store *Store, storePath string) {
|
||||
err := store.Close()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = os.RemoveAll(storePath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type EnvironmentDetails struct {
|
||||
@@ -27,7 +27,7 @@ func (service *Service) Details() EnvironmentDetails {
|
||||
}
|
||||
|
||||
func (service *Service) Init(store dataservices.DataStore, cryptoService portainer.CryptoService) error {
|
||||
log.Print("[INFO] [main] Starting demo environment")
|
||||
log.Info().Msg("starting demo environment")
|
||||
|
||||
isClean, err := isCleanStore(store)
|
||||
if err != nil {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user