Compare commits

..

22 Commits

Author SHA1 Message Date
andres-portainer
da3856503f [WIP] Optimize buildEdgeStacks()
Some checks failed
Test / test-client (push) Has been cancelled
2022-05-11 19:38:55 -03:00
andres-portainer
0b6362a4bd Merge branch 'develop' into debug-api-endpoint 2022-05-10 18:00:25 -03:00
andres-portainer
8bbb6e5a6e Revert "Add caching to EdgeStack."
This reverts commit ea71ce44fa.
2022-05-08 16:41:11 -03:00
andres-portainer
ea71ce44fa Add caching to EdgeStack. 2022-05-06 21:45:06 -03:00
matias.spinarolli
a3f2b4b0af feat(ssl): use ECDSA instead of RSA to generate the self-signed certificates EE-3097 2022-05-06 19:35:52 -03:00
andres-portainer
f4c7896046 Add cache invalidation to avoid breaking data migration. 2022-05-05 19:40:40 -03:00
andres-portainer
b1272b9da3 Merge branch 'develop' into debug-api-endpoint 2022-05-05 19:17:57 -03:00
andres-portainer
943c0a6256 Avoid allocating on nil values. 2022-05-05 19:12:06 -03:00
andres-portainer
d245e196c1 Merge branch 'debug-api-endpoint' of github.com:portainer/portainer into debug-api-endpoint 2022-05-05 18:45:47 -03:00
andres-portainer
d6abf03d42 Cache the Settings object. 2022-05-05 18:44:11 -03:00
andres-portainer
f98585d832 Avoid a call to Endpoint().GetNextIdentifier(). 2022-05-05 18:43:16 -03:00
deviantony
bc3e973830 Merge branch 'develop' into debug-api-endpoint 2022-05-05 21:31:20 +00:00
deviantony
0292523855 refactor(edge): rollback to original error message 2022-05-04 18:30:05 +00:00
deviantony
044d756626 feat(edge): remove logrus calls 2022-05-04 18:28:31 +00:00
deviantony
a9c0c5f835 feat(edge): rollback go routine changes 2022-05-04 18:13:58 +00:00
deviantony
2b0a519c36 feat(edge): update edge status logic 2022-05-04 01:42:55 +00:00
yi-portainer
871da94da0 * remove endpoint update for testing 2022-05-04 12:07:27 +12:00
andres-portainer
81faf20f20 Add pprof handlers. 2022-05-03 19:42:26 -03:00
deviantony
b8757ac8eb Merge branch 'develop' into debug-api-endpoint 2022-05-03 04:34:27 +00:00
deviantony
b927e08d5e feat(http): add debug logs 2022-05-03 04:33:50 +00:00
deviantony
e2188edc9d feat(http): update logging format 2022-04-30 20:05:16 +00:00
deviantony
97d2a3bdf3 feat(http): add debug logs in 2022-04-30 19:45:46 +00:00
2705 changed files with 42152 additions and 79477 deletions

View File

@@ -31,12 +31,7 @@ rules:
[
'error',
{
pathGroups:
[
{ pattern: '@@/**', group: 'internal', position: 'after' },
{ pattern: '@/**', group: 'internal' },
{ pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' },
],
pathGroups: [{ pattern: '@/**', group: 'internal' }, { pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' }],
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
pathGroupsExcludedImportTypes: ['internal'],
},
@@ -46,7 +41,6 @@ settings:
'import/resolver':
alias:
map:
- ['@@', './app/react/components']
- ['@', './app']
extensions: ['.js', '.ts', '.tsx']
@@ -58,7 +52,6 @@ overrides:
parser: '@typescript-eslint/parser'
plugins:
- '@typescript-eslint'
- 'regex'
extends:
- airbnb
- airbnb-typescript
@@ -75,15 +68,7 @@ overrides:
version: 'detect'
rules:
import/order:
[
'error',
{
pathGroups: [{ pattern: '@@/**', group: 'internal', position: 'after' }, { pattern: '@/**', group: 'internal' }],
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
},
]
no-plusplus: off
['error', { pathGroups: [{ pattern: '@/**', group: 'internal' }], groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 'newlines-between': 'always' }]
func-style: [error, 'declaration']
import/prefer-default-export: off
no-use-before-define: ['error', { functions: false }]
@@ -100,17 +85,11 @@ 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', controlComponents: ['Input', 'Checkbox'] }]
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either' }]
'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': '<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:

View File

@@ -23,9 +23,6 @@ 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
@@ -39,9 +36,3 @@ 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

View File

@@ -101,15 +101,15 @@ jobs:
- name: Checkout code
uses: actions/checkout@master
- name: Use golang 1.19.4
- name: Use golang 1.18
uses: actions/setup-go@v3
with:
go-version: '1.19.4'
go-version: '1.18'
- name: Use Node.js 18.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 12.x
- name: Install packages and build
run: yarn install && yarn build

View File

@@ -136,15 +136,15 @@ jobs:
- name: Checkout code
uses: actions/checkout@master
- name: Use golang 1.19.4
- name: Use golang 1.18
uses: actions/setup-go@v3
with:
go-version: '1.19.4'
go-version: '1.18'
- name: Use Node.js 18.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 12.x
- name: Install packages and build
run: yarn install && yarn build

1
.gitignore vendored
View File

@@ -7,7 +7,6 @@ storybook-static
.tmp
**/.vscode/settings.json
**/.vscode/tasks.json
.vscode
*.DS_Store
.eslintcache

View File

@@ -3,7 +3,6 @@ import '../app/assets/css';
import { pushStateLocationPlugin, UIRouter } from '@uirouter/react';
import { initialize as initMSW, mswDecorator } from 'msw-storybook-addon';
import { handlers } from '@/setup-tests/server-handlers';
import { QueryClient, QueryClientProvider } from 'react-query';
// Initialize MSW
initMSW({
@@ -32,17 +31,11 @@ export const parameters = {
},
};
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
export const decorators = [
(Story) => (
<QueryClientProvider client={testQueryClient}>
<UIRouter plugins={[pushStateLocationPlugin]}>
<Story />
</UIRouter>
</QueryClientProvider>
<UIRouter plugins={[pushStateLocationPlugin]}>
<Story />
</UIRouter>
),
mswDecorator,
];

View File

@@ -12,15 +12,21 @@ 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.
[![latest version](https://img.shields.io/github/v/release/portainer/portainer?color=%2344cc11&label=Latest%20release&style=for-the-badge)](https://github.com/portainer/portainer/releases/latest)
**The latest version of Portainer is 2.9.x**. Portainer is on version 2, the second number denotes the month of release.
## Getting started
- [Deploy Portainer](https://docs.portainer.io/start/install)
- [Deploy Portainer](https://docs.portainer.io/v/ce-2.9/start/install)
- [Documentation](https://documentation.portainer.io)
- [Contribute to the project](https://documentation.portainer.io/contributing/instructions/)

View File

@@ -1,26 +0,0 @@
linters:
# Disable all linters.
disable-all: true
enable:
- depguard
linters-settings:
depguard:
list-type: denylist
include-go-root: true
packages:
- github.com/sirupsen/logrus
- golang.org/x/exp
packages-with-error-message:
- github.com/sirupsen/logrus: 'logging is allowed only by github.com/rs/zerolog'
ignore-file-rules:
- "**/*_test.go"
# Create additional guards that follow the same configuration pattern.
# Results from all guards are aggregated together.
# additional-guards:
# - list-type: allowlist
# include-go-root: false
# packages:
# - github.com/sirupsen/logrus
# # Specify rules by which the linter ignores certain files for consideration.
# ignore-file-rules:
# - "!**/*_test.go"

View File

@@ -2,6 +2,7 @@ package adminmonitor
import (
"context"
"log"
"net/http"
"strings"
"sync"
@@ -10,10 +11,10 @@ import (
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
var logFatalf = log.Fatalf
const RedirectReasonAdminInitTimeout string = "AdminInitTimeout"
type Monitor struct {
@@ -21,11 +22,11 @@ type Monitor struct {
datastore dataservices.DataStore
shutdownCtx context.Context
cancellationFunc context.CancelFunc
mu sync.RWMutex
mu sync.Mutex
adminInitDisabled bool
}
// New creates a monitor that when started will wait for the timeout duration and then shutdown the application unless it has been initialized.
// New creates a monitor that when started will wait for the timeout duration and then sends the timeout signal to disable the application
func New(timeout time.Duration, datastore dataservices.DataStore, shutdownCtx context.Context) *Monitor {
return &Monitor{
timeout: timeout,
@@ -48,29 +49,24 @@ func (m *Monitor) Start() {
m.cancellationFunc = cancellationFunc
go func() {
log.Debug().Msg("start initialization monitor")
log.Println("[DEBUG] [internal,init] [message: start initialization monitor ]")
select {
case <-time.After(m.timeout):
initialized, err := m.WasInitialized()
if err != nil {
log.Error().Err(err).Msg("AdminMonitor failed to determine if Portainer is Initialized")
return
logFatalf("%s", err)
}
if !initialized {
log.Info().Msg("the Portainer instance timed out for security purposes, to re-enable your Portainer instance, you will need to restart Portainer")
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")
m.mu.Lock()
defer m.mu.Unlock()
m.adminInitDisabled = true
return
}
case <-cancellationCtx.Done():
log.Debug().Msg("canceling initialization monitor")
log.Println("[DEBUG] [internal,init] [message: canceling initialization monitor]")
case <-m.shutdownCtx.Done():
log.Debug().Msg("shutting down initialization monitor")
log.Println("[DEBUG] [internal,init] [message: shutting down initialization monitor]")
}
}()
}
@@ -83,7 +79,6 @@ func (m *Monitor) Stop() {
if m.cancellationFunc == nil {
return
}
m.cancellationFunc()
m.cancellationFunc = nil
}
@@ -94,14 +89,12 @@ func (m *Monitor) WasInitialized() (bool, error) {
if err != nil {
return false, err
}
return len(users) > 0, nil
}
func (m *Monitor) WasInstanceDisabled() bool {
m.mu.RLock()
defer m.mu.RUnlock()
m.mu.Lock()
defer m.mu.Unlock()
return m.adminInitDisabled
}
@@ -109,10 +102,12 @@ 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() && 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() {
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
}
}
next.ServeHTTP(w, r)

View File

@@ -1,71 +0,0 @@
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
}

View File

@@ -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_"

View File

@@ -2,7 +2,7 @@ package apikey
import (
"crypto/sha256"
"fmt"
"log"
"strings"
"testing"
"time"
@@ -10,8 +10,6 @@ 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) {
@@ -22,7 +20,7 @@ func Test_SatisfiesAPIKeyServiceInterface(t *testing.T) {
func Test_GenerateApiKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -76,7 +74,7 @@ func Test_GenerateApiKey(t *testing.T) {
func Test_GetAPIKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -96,7 +94,7 @@ func Test_GetAPIKey(t *testing.T) {
func Test_GetAPIKeys(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -117,7 +115,7 @@ func Test_GetAPIKeys(t *testing.T) {
func Test_GetDigestUserAndKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -153,7 +151,7 @@ func Test_GetDigestUserAndKey(t *testing.T) {
func Test_UpdateAPIKey(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())
@@ -171,9 +169,11 @@ func Test_UpdateAPIKey(t *testing.T) {
_, apiKeyGot, err := service.GetDigestUserAndKey(apiKey.Digest)
is.NoError(err)
log.Debug().Str("wanted", fmt.Sprintf("%+v", apiKey)).Str("got", fmt.Sprintf("%+v", apiKeyGot)).Msg("")
log.Println(apiKey)
log.Println(apiKeyGot)
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(t, true, true)
_, store, teardown := datastore.MustNewTestStore(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(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
service := NewAPIKeyService(store.APIKeyRepository(), store.User())

View File

@@ -34,45 +34,3 @@ 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()
}

View File

@@ -2,12 +2,14 @@ package archive
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"testing"
"github.com/docker/docker/pkg/ioutils"
"github.com/stretchr/testify/assert"
)
@@ -25,18 +27,22 @@ func listFiles(dir string) []string {
}
func Test_shouldCreateArhive(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
content := []byte("content")
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
os.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
os.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
ioutil.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 := t.TempDir()
extractionDir, _ := ioutils.TempDir("", "extract")
defer os.RemoveAll(extractionDir)
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
err = cmd.Run()
if err != nil {
@@ -47,7 +53,7 @@ func Test_shouldCreateArhive(t *testing.T) {
wasExtracted := func(p string) {
fullpath := path.Join(extractionDir, p)
assert.Contains(t, extractedFiles, fullpath)
copyContent, _ := os.ReadFile(fullpath)
copyContent, _ := ioutil.ReadFile(fullpath)
assert.Equal(t, content, copyContent)
}
@@ -57,18 +63,22 @@ func Test_shouldCreateArhive(t *testing.T) {
}
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "backup")
defer os.RemoveAll(tmpdir)
content := []byte("content")
os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
os.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
os.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
ioutil.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 := t.TempDir()
extractionDir, _ := ioutils.TempDir("", "extract")
defer os.RemoveAll(extractionDir)
r, _ := os.Open(gzPath)
ExtractTarGz(r, extractionDir)
if err != nil {
@@ -79,7 +89,7 @@ func Test_shouldCreateArhiveXXXXX(t *testing.T) {
wasExtracted := func(p string) {
fullpath := path.Join(extractionDir, p)
assert.Contains(t, extractedFiles, fullpath)
copyContent, _ := os.ReadFile(fullpath)
copyContent, _ := ioutil.ReadFile(fullpath)
assert.Equal(t, content, copyContent)
}

View File

@@ -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 := io.ReadAll(f)
data, err := ioutil.ReadAll(f)
if err != nil {
return err
}

View File

@@ -1,14 +1,17 @@
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 := t.TempDir()
dir, err := ioutil.TempDir("", "unzip-test-")
assert.NoError(t, err)
defer os.RemoveAll(dir)
/*
Archive structure.
├── 0
@@ -18,7 +21,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"

View File

@@ -7,14 +7,13 @@ 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/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const rwxr__r__ os.FileMode = 0744
@@ -48,9 +47,9 @@ func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datasto
err := datastore.Export(exportFilename)
if err != nil {
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
logrus.WithError(err).Debugf("failed to export to %s", exportFilename)
} else {
log.Debug().Str("filename", exportFilename).Msg("file exported")
logrus.Debugf("exported to %s", exportFilename)
}
}

View File

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

View File

@@ -2,17 +2,12 @@ package chisel
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
)
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
func (service *Service) AddEdgeJob(endpoint *portainer.Endpoint, edgeJob *portainer.EdgeJob) {
if endpoint.Edge.AsyncMode {
return
}
func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *portainer.EdgeJob) {
service.mu.Lock()
tunnel := service.getTunnelDetails(endpoint.ID)
tunnel := service.getTunnelDetails(endpointID)
existingJobIndex := -1
for idx, existingJob := range tunnel.Jobs {
@@ -28,8 +23,6 @@ func (service *Service) AddEdgeJob(endpoint *portainer.Endpoint, edgeJob *portai
tunnel.Jobs[existingJobIndex] = *edgeJob
}
cache.Del(endpoint.ID)
service.mu.Unlock()
}
@@ -37,7 +30,8 @@ func (service *Service) AddEdgeJob(endpoint *portainer.Endpoint, edgeJob *portai
func (service *Service) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {
service.mu.Lock()
for endpointID, tunnel := range service.tunnelDetailsMap {
for _, tunnel := range service.tunnelDetailsMap {
// Filter in-place
n := 0
for _, edgeJob := range tunnel.Jobs {
if edgeJob.ID != edgeJobID {
@@ -47,28 +41,7 @@ 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()
}

View File

@@ -3,17 +3,16 @@ 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 (
@@ -65,11 +64,7 @@ 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.Debug().
Int("endpoint_id", int(endpointID)).
Float64("max_alive_minutes", maxAlive.Minutes()).
Msg("start")
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: start for %.0f minutes]\n", endpointID, maxAlive.Minutes())
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
pingTicker := time.NewTicker(tunnelCleanupInterval)
@@ -81,25 +76,14 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
service.SetTunnelStatusToActive(endpointID)
err := service.pingAgent(endpointID)
if err != nil {
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("ping agent")
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [warning: ping agent err=%s]\n", endpointID, err)
}
case <-maxAliveTicker.C:
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("timeout_minutes", maxAlive.Minutes()).
Msg("tunnel keep alive timeout")
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as %.0f minutes timeout]\n", endpointID, maxAlive.Minutes())
return
case <-ctx.Done():
err := ctx.Err()
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("tunnel stop")
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as err=%s]\n", endpointID, err)
return
}
}
@@ -178,10 +162,7 @@ func (service *Service) retrievePrivateKeySeed() (string, error) {
}
func (service *Service) startTunnelVerificationLoop() {
log.Debug().
Float64("check_interval_seconds", tunnelCleanupInterval.Seconds()).
Msg("starting tunnel management process")
log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
ticker := time.NewTicker(tunnelCleanupInterval)
for {
@@ -189,12 +170,10 @@ func (service *Service) startTunnelVerificationLoop() {
case <-ticker.C:
service.checkTunnels()
case <-service.shutdownCtx.Done():
log.Debug().Msg("shutting down tunnel service")
log.Println("[DEBUG] Shutting down tunnel service")
if err := service.StopTunnelServer(); err != nil {
log.Debug().Err(err).Msg("stopped tunnel service")
log.Printf("Stopped tunnel service: %s", err)
}
ticker.Stop()
return
}
@@ -206,53 +185,32 @@ 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 {
elapsed := time.Since(tunnel.LastActivity)
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 > 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.LastActivity.IsZero() || tunnel.Status == portainer.EdgeAgentIdle {
continue
}
if tunnel.Status == portainer.EdgeAgentActive && elapsed > activeTimeout {
log.Debug().
Int("endpoint_id", int(endpointID)).
Str("status", tunnel.Status).
Float64("status_time_seconds", elapsed.Seconds()).
Float64("timeout_seconds", activeTimeout.Seconds()).
Msg("ACTIVE state timeout exceeded")
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())
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.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())
err := service.snapshotEnvironment(endpointID, tunnel.Port)
if err != nil {
log.Error().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("unable to snapshot Edge environment")
log.Printf("[ERROR] [snapshot] Unable to snapshot Edge environment (id: %d): %s", endpointID, err)
}
}

View File

@@ -2,17 +2,14 @@ package chisel
import (
"encoding/base64"
"errors"
"fmt"
"math/rand"
"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 (
@@ -52,8 +49,6 @@ func (service *Service) getTunnelDetails(endpointID portainer.EndpointID) *porta
service.tunnelDetailsMap[endpointID] = tunnel
cache.Del(endpointID)
return tunnel
}
@@ -67,10 +62,6 @@ func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) portai
// GetActiveTunnel retrieves an active tunnel which allows communicating with edge agent
func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (portainer.TunnelDetails, error) {
if endpoint.Edge.AsyncMode {
return portainer.TunnelDetails{}, errors.New("cannot open tunnel on async endpoint")
}
tunnel := service.GetTunnelDetails(endpoint.ID)
if tunnel.Status == portainer.EdgeAgentActive {
@@ -108,8 +99,6 @@ 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).
@@ -132,8 +121,6 @@ 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).
@@ -142,8 +129,6 @@ 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()

View File

@@ -2,14 +2,15 @@ package cli
import (
"errors"
"os"
"path/filepath"
"strings"
"log"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"os"
"path/filepath"
"strings"
"gopkg.in/alecthomas/kingpin.v2"
)
@@ -34,7 +35,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
DemoEnvironment: kingpin.Flag("demo", "Demo environment").Bool(),
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
FeatureFlags: BoolPairs(kingpin.Flag("feat", "List of feature flags").Hidden()),
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
@@ -61,8 +61,6 @@ 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()
@@ -102,11 +100,11 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
if *flags.NoAnalytics {
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")
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.")
}
if *flags.SSL {
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")
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")
}
}

View File

@@ -2,14 +2,14 @@ package cli
import (
"bufio"
"fmt"
"log"
"os"
"strings"
)
// Confirm starts a rollback db cli application
func Confirm(message string) (bool, error) {
fmt.Printf("%s [y/N]", message)
log.Printf("%s [y/N]", message)
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')

View File

@@ -1,10 +1,10 @@
package cli
import (
"strings"
portainer "github.com/portainer/portainer/api"
"strings"
"gopkg.in/alecthomas/kingpin.v2"
)

View File

@@ -0,0 +1,29 @@
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)
}
}
}

View File

@@ -1,55 +1,20 @@
package main
import (
"fmt"
stdlog "log"
"os"
"log"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
"github.com/sirupsen/logrus"
)
func configureLogger() {
zerolog.ErrorStackFieldName = "stack_trace"
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
logger := logrus.New() // logger is to implicitly substitute stdlib's log
log.SetOutput(logger.Writer())
stdlog.SetFlags(0)
stdlog.SetOutput(log.Logger)
formatter := &logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true}
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)
logger.SetFormatter(formatter)
logrus.SetFormatter(formatter)
logger.SetLevel(logrus.DebugLevel)
logrus.SetLevel(logrus.DebugLevel)
}

View File

@@ -4,27 +4,25 @@ import (
"context"
"crypto/sha256"
"fmt"
"math/rand"
"log"
"os"
"path"
"strconv"
"strings"
"time"
libstack "github.com/portainer/docker-compose-wrapper"
"github.com/portainer/docker-compose-wrapper/compose"
"github.com/sirupsen/logrus"
"github.com/portainer/libhelm"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/build"
"github.com/portainer/portainer/api/chisel"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/crypto"
"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"
"github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/exec"
"github.com/portainer/portainer/api/filesystem"
@@ -36,51 +34,43 @@ 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/deployments"
"github.com/portainer/portainer/pkg/libhelm"
"github.com/gofrs/uuid"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/stacks"
)
func initCLI() *portainer.CLIFlags {
var cliService portainer.CLIService = &cli.Service{}
flags, err := cliService.ParseFlags(portainer.APIVersion)
if err != nil {
log.Fatal().Err(err).Msg("failed parsing flags")
logrus.Fatalf("Failed parsing flags: %v", err)
}
err = cliService.ValidateFlags(flags)
if err != nil {
log.Fatal().Err(err).Msg("failed validating flags")
logrus.Fatalf("Failed validating flags:%v", err)
}
return flags
}
func initFileService(dataStorePath string) portainer.FileService {
fileService, err := filesystem.NewService(dataStorePath, "")
if err != nil {
log.Fatal().Err(err).Msg("failed creating file service")
logrus.Fatalf("Failed creating file service: %v", err)
}
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 {
log.Fatal().Err(err).Msg("failed creating database connection")
logrus.Fatalf("failed creating database connection: %s", err)
}
if bconn, ok := connection.(*boltdb.DbConnection); ok {
@@ -88,74 +78,80 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
bconn.MaxBatchDelay = *flags.MaxBatchDelay
bconn.InitialMmapSize = *flags.InitialMmapSize
} else {
log.Fatal().Msg("failed creating database connection: expecting a boltdb database type but a different one was received")
logrus.Fatalf("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 {
log.Fatal().Err(err).Msg("failed opening store")
logrus.Fatalf("Failed opening store: %v", err)
}
if *flags.Rollback {
err := store.Rollback(false)
if err != nil {
log.Fatal().Err(err).Msg("failed rolling back")
logrus.Fatalf("Failed rolling back: %v", err)
}
log.Info().Msg("exiting rollback")
logrus.Println("Exiting rollback")
os.Exit(0)
return nil
}
// Init sets some defaults - it's basically a migration
err = store.Init()
if err != nil {
log.Fatal().Err(err).Msg("failed initializing data store")
logrus.Fatalf("Failed initializing data store: %v", err)
}
if isNew {
instanceId, err := uuid.NewV4()
if err != nil {
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)
store.VersionService.StoreDBVersion(portainer.DBVersion)
err = updateSettingsFromFlags(store, flags)
err := updateSettingsFromFlags(store, flags)
if err != nil {
log.Fatal().Err(err).Msg("failed updating settings from flags")
logrus.Fatalf("Failed updating settings from flags: %v", err)
}
} else {
err = store.MigrateData()
storedVersion, err := store.VersionService.DBVersion()
if err != nil {
log.Fatal().Err(err).Msg("failed migration")
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)
}
}
}
err = updateSettingsFromFlags(store, flags)
if err != nil {
log.Fatal().Err(err).Msg("failed updating settings from flags")
log.Fatalf("Failed updating settings from flags: %v", err)
}
// 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(composeDeployer libstack.Deployer, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
composeWrapper, err := exec.NewComposeStackManager(composeDeployer, proxyManager)
func initComposeStackManager(assetsPath string, configPath string, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
composeWrapper, err := exec.NewComposeStackManager(assetsPath, configPath, proxyManager)
if err != nil {
log.Fatal().Err(err).Msg("failed creating compose manager")
logrus.Fatalf("Failed creating compose manager: %v", err)
}
return composeWrapper
@@ -185,15 +181,10 @@ 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
}
@@ -213,8 +204,8 @@ func initOAuthService() portainer.OAuthService {
return oauth.NewService()
}
func initGitService(ctx context.Context) portainer.GitService {
return git.NewService(ctx)
func initGitService() portainer.GitService {
return git.NewService()
}
func initSSLService(addr, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) {
@@ -238,17 +229,11 @@ func initDockerClientFactory(signatureService portainer.DigitalSignatureService,
return docker.NewClientFactory(signatureService, reverseTunnelService)
}
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*kubecli.ClientFactory, error) {
return kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, addrHTTPS, userSessionTimeout)
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string, dataStore dataservices.DataStore) *kubecli.ClientFactory {
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID, dataStore)
}
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)
@@ -315,7 +300,12 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
sslSettings.HTTPEnabled = true
}
return dataStore.SSLSettings().UpdateSettings(sslSettings)
err = dataStore.SSLSettings().UpdateSettings(sslSettings)
if err != nil {
return err
}
return nil
}
// enableFeaturesFromFlags turns on or off feature flags
@@ -349,7 +339,11 @@ func enableFeaturesFromFlags(dataStore dataservices.DataStore, flags *portainer.
return fmt.Errorf("feature flag's '%s' value should be true or false", feat.Name)
}
log.Info().Str("feature", string(*correspondingFeature)).Bool("state", featureState).Msg("")
if featureState {
logrus.Printf("Feature %v : on", *correspondingFeature)
} else {
logrus.Printf("Feature %v : off", *correspondingFeature)
}
settings.FeatureFlagSettings[*correspondingFeature] = featureState
}
@@ -377,7 +371,7 @@ func generateAndStoreKeyPair(fileService portainer.FileService, signatureService
func initKeyPair(fileService portainer.FileService, signatureService portainer.DigitalSignatureService) error {
existingKeyPair, err := fileService.KeyPairFilesExist()
if err != nil {
log.Fatal().Err(err).Msg("failed checking for existing key pair")
logrus.Fatalf("Failed checking for existing key pair: %v", err)
}
if existingKeyPair {
@@ -447,11 +441,7 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.
err := snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
log.Error().
Str("endpoint", endpoint.Name).
Str("URL", endpoint.URL).
Err(err).
Msg("environment snapshot error")
logrus.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
}
return dataStore.Endpoint().Create(endpoint)
@@ -496,10 +486,7 @@ func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStor
err := snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
log.Error().
Str("endpoint", endpoint.Name).
Str("URL", endpoint.URL).Err(err).
Msg("environment snapshot error")
logrus.Printf("http error: environment snapshot error (environment=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
}
return dataStore.Endpoint().Create(endpoint)
@@ -516,8 +503,7 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, s
}
if len(endpoints) > 0 {
log.Info().Msg("instance already has defined environments, skipping the environment defined via CLI")
logrus.Println("Instance already has defined environments. Skipping the environment defined via CLI.")
return nil
}
@@ -531,9 +517,9 @@ func loadEncryptionSecretKey(keyfilename string) []byte {
content, err := os.ReadFile(path.Join("/run/secrets", keyfilename))
if err != nil {
if os.IsNotExist(err) {
log.Info().Str("filename", keyfilename).Msg("encryption key file not present")
logrus.Printf("Encryption key file `%s` not present", keyfilename)
} else {
log.Info().Err(err).Msg("error reading encryption key file")
logrus.Printf("Error reading encryption key file: %v", err)
}
return nil
@@ -550,74 +536,67 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
fileService := initFileService(*flags.Data)
encryptionKey := loadEncryptionSecretKey(*flags.SecretKeyName)
if encryptionKey == nil {
log.Info().Msg("proceeding without encryption key")
logrus.Println("Proceeding without encryption key")
}
dataStore := initDataStore(flags, encryptionKey, fileService, shutdownCtx)
if err := dataStore.CheckCurrentEdition(); err != nil {
log.Fatal().Err(err).Msg("")
logrus.Fatal(err)
}
instanceID, err := dataStore.Version().InstanceID()
if err != nil {
log.Fatal().Err(err).Msg("failed getting instance id")
logrus.Fatalf("Failed getting instance id: %v", err)
}
apiKeyService := initAPIKeyService(dataStore)
settings, err := dataStore.Settings().Settings()
if err != nil {
log.Fatal().Err(err).Msg("")
logrus.Fatal(err)
}
jwtService, err := initJWTService(settings.UserSessionTimeout, dataStore)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing JWT service")
logrus.Fatalf("Failed initializing JWT service: %v", err)
}
err = enableFeaturesFromFlags(dataStore, flags)
if err != nil {
log.Fatal().Err(err).Msg("failed enabling feature flag")
logrus.Fatalf("Failed enabling feature flag: %v", err)
}
ldapService := initLDAPService()
oauthService := initOAuthService()
gitService := initGitService(shutdownCtx)
gitService := initGitService()
openAMTService := openamt.NewService()
cryptoService := initCryptoService()
digitalSignatureService := initDigitalSignatureService()
edgeStacksService := edgestacks.NewService(dataStore)
sslService, err := initSSLService(*flags.AddrHTTPS, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger)
if err != nil {
log.Fatal().Err(err).Msg("")
logrus.Fatal(err)
}
sslSettings, err := sslService.GetSSLSettings()
if err != nil {
log.Fatal().Err(err).Msg("failed to get SSL settings")
logrus.Fatalf("Failed to get ssl settings: %s", err)
}
err = initKeyPair(fileService, digitalSignatureService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing key pair")
logrus.Fatalf("Failed initializing key pair: %v", err)
}
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID, dataStore)
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing snapshot service")
logrus.Fatalf("Failed initializing snapshot service: %v", err)
}
snapshotService.Start()
@@ -628,61 +607,47 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService(*flags.BaseURL, *flags.AddrHTTPS, sslSettings.CertPath)
proxyManager := proxy.NewManager(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService)
proxyManager := proxy.NewManager(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager)
reverseTunnelService.ProxyManager = proxyManager
dockerConfigPath := fileService.GetDockerConfigPath()
composeDeployer, err := compose.NewComposeDeployer(*flags.Assets, dockerConfigPath)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing compose deployer")
}
composeStackManager := initComposeStackManager(composeDeployer, reverseTunnelService, proxyManager)
composeStackManager := initComposeStackManager(*flags.Assets, dockerConfigPath, reverseTunnelService, proxyManager)
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing swarm stack manager")
logrus.Fatalf("Failed initializing swarm stack manager: %v", err)
}
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing helm package manager")
logrus.Fatalf("Failed initializing helm package manager: %v", err)
}
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
if err != nil {
log.Fatal().Err(err).Msg("failed loading edge jobs from database")
logrus.Fatalf("Failed loading edge jobs from database: %v", err)
}
applicationStatus := initStatus(instanceID)
demoService := demo.NewService()
if *flags.DemoEnvironment {
err := demoService.Init(dataStore, cryptoService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing demo environment")
}
}
err = initEndpoint(flags, dataStore, snapshotService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing environment")
logrus.Fatalf("Failed initializing environment: %v", err)
}
adminPasswordHash := ""
if *flags.AdminPasswordFile != "" {
content, err := fileService.GetFileContent(*flags.AdminPasswordFile, "")
if err != nil {
log.Fatal().Err(err).Msg("failed getting admin password file")
logrus.Fatalf("Failed getting admin password file: %v", err)
}
adminPasswordHash, err = cryptoService.Hash(strings.TrimSuffix(string(content), "\n"))
if err != nil {
log.Fatal().Err(err).Msg("failed hashing admin password")
logrus.Fatalf("Failed hashing admin password: %v", err)
}
} else if *flags.AdminPassword != "" {
adminPasswordHash = *flags.AdminPassword
@@ -691,61 +656,38 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
if adminPasswordHash != "" {
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
if err != nil {
log.Fatal().Err(err).Msg("failed getting admin user")
logrus.Fatalf("Failed getting admin user: %v", err)
}
if len(users) == 0 {
log.Info().Msg("created admin user with the given password.")
logrus.Println("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 {
log.Fatal().Err(err).Msg("failed creating admin user")
logrus.Fatalf("Failed creating admin user: %v", err)
}
} else {
log.Info().Msg("instance already has an administrator user defined, skipping admin password related flags.")
logrus.Println("Instance already has an administrator user defined. Skipping admin password related flags.")
}
}
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
if err != nil {
log.Fatal().Err(err).Msg("failed starting tunnel server")
logrus.Fatalf("Failed starting tunnel server: %v", err)
}
scheduler := scheduler.NewScheduler(shutdownCtx)
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")
logrus.Fatalf("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")
}
scheduler := scheduler.NewScheduler(shutdownCtx)
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
stacks.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
return &http.Server{
AuthorizationService: authorizationService,
@@ -756,13 +698,12 @@ 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,
APIKeyService: apiKeyService,
CryptoService: cryptoService,
APIKeyService: apiKeyService,
JWTService: jwtService,
FileService: fileService,
LDAPService: ldapService,
@@ -781,35 +722,18 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
ShutdownCtx: shutdownCtx,
ShutdownTrigger: shutdownTrigger,
StackDeployer: stackDeployer,
DemoService: demoService,
UpgradeService: upgradeService,
}
}
func main() {
rand.Seed(time.Now().UnixNano())
configureLogger()
setLoggingMode("PRETTY")
flags := initCLI()
setLoggingLevel(*flags.LogLevel)
setLoggingMode(*flags.LogMode)
configureLogger()
for {
server := buildServer(flags)
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")
logrus.Printf("[INFO] [cmd,main] Starting Portainer version %s\n", portainer.APIVersion)
err := server.Start()
log.Info().Err(err).Msg("HTTP server exited")
logrus.Printf("[INFO] [cmd,main] Http server exited: %v\n", err)
}
}

View File

@@ -8,7 +8,6 @@ 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"
)
@@ -22,7 +21,7 @@ func (m mockKingpinSetting) SetValue(value kingpin.Value) {
func Test_enableFeaturesFromFlags(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
tests := []struct {
@@ -77,7 +76,7 @@ func Test_optionalFeature(t *testing.T) {
is := assert.New(t)
_, store, teardown := datastore.MustNewTestStore(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
// Enable the test feature

View File

@@ -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, obj interface{}, matching func(o interface{}) (id int, ok bool)) error
DeleteAllObjects(bucketName string, 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

View File

@@ -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)
var emptySalt []byte = make([]byte, 0, 0)
// AesEncrypt reads from input, encrypts with AES-256 and writes to the output.
// passphrase is used to generate an encryption key.

View File

@@ -2,15 +2,18 @@ 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 := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "encrypt")
defer os.RemoveAll(tmpdir)
var (
originFilePath = filepath.Join(tmpdir, "origin")
@@ -19,7 +22,7 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
)
content := []byte("content")
os.WriteFile(originFilePath, content, 0600)
ioutil.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
@@ -29,7 +32,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 := os.ReadFile(encryptedFilePath)
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
@@ -44,12 +47,13 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
}
func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "encrypt")
defer os.RemoveAll(tmpdir)
var (
originFilePath = filepath.Join(tmpdir, "origin")
@@ -58,7 +62,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
)
content := []byte("content")
os.WriteFile(originFilePath, content, 0600)
ioutil.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
@@ -68,7 +72,7 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
err := AesEncrypt(originFile, encryptedFileWriter, []byte(""))
assert.Nil(t, err, "Failed to encrypt a file")
encryptedContent, err := os.ReadFile(encryptedFilePath)
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
@@ -83,12 +87,13 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
}
func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T) {
tmpdir := t.TempDir()
tmpdir, _ := ioutils.TempDir("", "encrypt")
defer os.RemoveAll(tmpdir)
var (
originFilePath = filepath.Join(tmpdir, "origin")
@@ -97,7 +102,7 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
)
content := []byte("content")
os.WriteFile(originFilePath, content, 0600)
ioutil.WriteFile(originFilePath, content, 0600)
originFile, _ := os.Open(originFilePath)
defer originFile.Close()
@@ -107,7 +112,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 := os.ReadFile(encryptedFilePath)
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
assert.Nil(t, err, "Couldn't read encrypted file")
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
@@ -122,6 +127,6 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
io.Copy(decryptedFileWriter, decryptedReader)
decryptedContent, _ := os.ReadFile(decryptedFilePath)
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
assert.NotEqual(t, content, decryptedContent, "Original and decrypted content should NOT match")
}

View File

@@ -3,7 +3,7 @@ package crypto
import (
"crypto/tls"
"crypto/x509"
"os"
"io/ioutil"
)
// 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 := os.ReadFile(caCertPath)
caCert, err := ioutil.ReadFile(caCertPath)
if err != nil {
return nil, err
}

View File

@@ -1,18 +1,17 @@
package boltdb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"time"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt"
)
@@ -121,7 +120,7 @@ func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
// Open opens and initializes the BoltDB database.
func (connection *DbConnection) Open() error {
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
logrus.Infof("Loading PortainerDB: %s", connection.GetDatabaseFileName())
// Now we open the db
databasePath := connection.GetDatabaseFilePath()
@@ -162,11 +161,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 os.WriteFile(filename, b, 0600)
return ioutil.WriteFile(filename, b, 0600)
}
// ConvertToKey returns an 8-byte big endian representation of v.
@@ -178,7 +177,7 @@ func (connection *DbConnection) ConvertToKey(v int) []byte {
return b
}
// CreateBucket is a generic function used to create a bucket inside a database.
// CreateBucket is a generic function used to create a bucket inside a database database.
func (connection *DbConnection) SetServiceName(bucketName string) error {
return connection.Batch(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
@@ -186,7 +185,7 @@ func (connection *DbConnection) SetServiceName(bucketName string) error {
})
}
// GetObject is a generic function used to retrieve an unmarshalled object from a database.
// GetObject is a generic function used to retrieve an unmarshalled object from a database database.
func (connection *DbConnection) GetObject(bucketName string, key []byte, object interface{}) error {
var data []byte
@@ -218,7 +217,7 @@ func (connection *DbConnection) getEncryptionKey() []byte {
return connection.EncryptionKey
}
// UpdateObject is a generic function used to update an object inside a database.
// UpdateObject is a generic function used to update an object inside a database database.
func (connection *DbConnection) UpdateObject(bucketName string, key []byte, object interface{}) error {
data, err := connection.MarshalObject(object)
if err != nil {
@@ -231,33 +230,7 @@ func (connection *DbConnection) UpdateObject(bucketName string, key []byte, obje
})
}
// 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.
// DeleteObject is a generic function used to delete an object inside a database database.
func (connection *DbConnection) DeleteObject(bucketName string, key []byte) error {
return connection.Batch(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(bucketName))
@@ -267,12 +240,13 @@ func (connection *DbConnection) DeleteObject(bucketName string, key []byte) erro
// DeleteAllObjects delete all objects where matching() returns (id, ok).
// TODO: think about how to return the error inside (maybe change ok to type err, and use "notfound"?
func (connection *DbConnection) DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error {
func (connection *DbConnection) DeleteAllObjects(bucketName string, 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
@@ -350,10 +324,30 @@ 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)
@@ -368,7 +362,6 @@ func (connection *DbConnection) GetAll(bucketName string, obj interface{}, appen
return nil
})
return err
}
@@ -394,33 +387,13 @@ 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
@@ -432,14 +405,13 @@ 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 {
log.Error().Str("bucket", bucketName).Msg("failed to restore metadata to bucket, skipped")
logrus.Errorf("Failed to restore metadata to bucket %s, skipped", bucketName)
continue
}
@@ -448,7 +420,6 @@ func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error
if err != nil {
return err
}
return bucket.SetSequence(uint64(id))
})
}

View File

@@ -4,7 +4,7 @@ import (
"encoding/json"
"time"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt"
)
@@ -14,6 +14,7 @@ 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
@@ -27,11 +28,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) {
log.Debug().Str("databasePath", databasePath).Msg("exportJson")
func (c *DbConnection) ExportJson(databasePath string, metadata bool) ([]byte, error) {
logrus.WithField("databasePath", databasePath).Infof("exportJson")
connection, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second, ReadOnly: true})
if err != nil {
@@ -43,9 +44,8 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
if metadata {
meta, err := backupMetadata(connection)
if err != nil {
log.Error().Err(err).Msg("failed exporting metadata")
logrus.WithError(err).Errorf("Failed exporting metadata: %v", err)
}
backup["__metadata"] = meta
}
@@ -59,31 +59,22 @@ 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 {
log.Error().
Str("bucket", bucketName).
Str("object", string(v)).
Err(err).
Msg("failed to unmarshal")
logrus.WithError(err).Errorf("Failed to unmarshal (bucket %s): %v", bucketName, string(v))
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" ||
@@ -100,10 +91,8 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
return nil
})
return err
})
if err != nil {
return []byte("{}"), err
}

View File

@@ -10,7 +10,7 @@ import (
)
const (
jsonobject = `{"LogoURL":"","BlackListedLabels":[],"AuthenticationMethod":1,"InternalAuthSettings": {"RequiredPasswordLength": 12}"LDAPSettings":{"AnonymousMode":true,"ReaderDN":"","URL":"","TLSConfig":{"TLS":false,"TLSSkipVerify":false},"StartTLS":false,"SearchSettings":[{"BaseDN":"","Filter":"","UserNameAttribute":""}],"GroupSearchSettings":[{"GroupBaseDN":"","GroupFilter":"","GroupAttribute":""}],"AutoCreateUsers":true},"OAuthSettings":{"ClientID":"","AccessTokenURI":"","AuthorizationURI":"","ResourceURI":"","RedirectURI":"","UserIdentifier":"","Scopes":"","OAuthAutoCreateUsers":false,"DefaultTeamID":0,"SSO":true,"LogoutURI":"","KubeSecretKey":"j0zLVtY/lAWBk62ByyF0uP80SOXaitsABP0TTJX8MhI="},"OpenAMTConfiguration":{"Enabled":false,"MPSServer":"","MPSUser":"","MPSPassword":"","MPSToken":"","CertFileContent":"","CertFileName":"","CertFilePassword":"","DomainName":""},"FeatureFlagSettings":{},"SnapshotInterval":"5m","TemplatesURL":"https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json","EdgeAgentCheckinInterval":5,"EnableEdgeComputeFeatures":false,"UserSessionTimeout":"8h","KubeconfigExpiry":"0","EnableTelemetry":true,"HelmRepositoryURL":"https://charts.bitnami.com/bitnami","KubectlShellImage":"portainer/kubectl-shell","DisplayDonationHeader":false,"DisplayExternalContributors":false,"EnableHostManagementFeatures":false,"AllowVolumeBrowserForRegularUsers":false,"AllowBindMountsForRegularUsers":false,"AllowPrivilegedModeForRegularUsers":false,"AllowHostNamespaceForRegularUsers":false,"AllowStackManagementForRegularUsers":false,"AllowDeviceMappingForRegularUsers":false,"AllowContainerCapabilitiesForRegularUsers":false}`
jsonobject = `{"LogoURL":"","BlackListedLabels":[],"AuthenticationMethod":1,"LDAPSettings":{"AnonymousMode":true,"ReaderDN":"","URL":"","TLSConfig":{"TLS":false,"TLSSkipVerify":false},"StartTLS":false,"SearchSettings":[{"BaseDN":"","Filter":"","UserNameAttribute":""}],"GroupSearchSettings":[{"GroupBaseDN":"","GroupFilter":"","GroupAttribute":""}],"AutoCreateUsers":true},"OAuthSettings":{"ClientID":"","AccessTokenURI":"","AuthorizationURI":"","ResourceURI":"","RedirectURI":"","UserIdentifier":"","Scopes":"","OAuthAutoCreateUsers":false,"DefaultTeamID":0,"SSO":true,"LogoutURI":"","KubeSecretKey":"j0zLVtY/lAWBk62ByyF0uP80SOXaitsABP0TTJX8MhI="},"OpenAMTConfiguration":{"Enabled":false,"MPSServer":"","MPSUser":"","MPSPassword":"","MPSToken":"","CertFileContent":"","CertFileName":"","CertFilePassword":"","DomainName":""},"FeatureFlagSettings":{},"SnapshotInterval":"5m","TemplatesURL":"https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json","EdgeAgentCheckinInterval":5,"EnableEdgeComputeFeatures":false,"UserSessionTimeout":"8h","KubeconfigExpiry":"0","EnableTelemetry":true,"HelmRepositoryURL":"https://charts.bitnami.com/bitnami","KubectlShellImage":"portainer/kubectl-shell","DisplayDonationHeader":false,"DisplayExternalContributors":false,"EnableHostManagementFeatures":false,"AllowVolumeBrowserForRegularUsers":false,"AllowBindMountsForRegularUsers":false,"AllowPrivilegedModeForRegularUsers":false,"AllowHostNamespaceForRegularUsers":false,"AllowStackManagementForRegularUsers":false,"AllowDeviceMappingForRegularUsers":false,"AllowContainerCapabilitiesForRegularUsers":false}`
passphrase = "my secret key"
)

View File

@@ -16,6 +16,5 @@ 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)
}

View File

@@ -1,8 +0,0 @@
package models
type Version struct {
SchemaVersion string
MigratorCount int
Edition int
InstanceID string
}

View File

@@ -6,8 +6,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -42,14 +41,12 @@ func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer
func(obj interface{}) (interface{}, error) {
record, ok := obj.(*portainer.APIKey)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -67,21 +64,18 @@ func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, err
func(obj interface{}) (interface{}, error) {
key, ok := obj.(*portainer.APIKey)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object")
logrus.WithField("obj", obj).Errorf("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
}

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -45,11 +44,10 @@ func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
//var tag portainer.Tag
customTemplate, ok := obj.(*portainer.CustomTemplate)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to CustomTemplate object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -4,12 +4,13 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "edgegroups"
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "edgegroups"
)
// Service represents a service for managing Edge group data.
type Service struct {
@@ -42,11 +43,10 @@ func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
func(obj interface{}) (interface{}, error) {
group, ok := obj.(*portainer.EdgeGroup)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeGroup object")
logrus.WithField("obj", obj).Errorf("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,22 +66,12 @@ func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGrou
return &group, nil
}
// Deprecated: Use UpdateEdgeGroupFunc instead.
// UpdateEdgeGroup updates an Edge group.
func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, group)
}
// 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))

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -42,14 +41,13 @@ 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 {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -80,22 +78,12 @@ func (service *Service) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJo
)
}
// Deprecated: use UpdateEdgeJobFunc instead
// UpdateEdgeJob updates an Edge job by ID
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))

View File

@@ -2,11 +2,9 @@ package edgestack
import (
"fmt"
"sync"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -16,10 +14,7 @@ const (
// Service represents a service for managing Edge stack data.
type Service struct {
connection portainer.Connection
idxVersion map[portainer.EdgeStackID]int
mu sync.RWMutex
cacheInvalidationFn func(portainer.EdgeStackID)
connection portainer.Connection
}
func (service *Service) BucketName() string {
@@ -27,32 +22,15 @@ func (service *Service) BucketName() string {
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection, cacheInvalidationFn func(portainer.EdgeStackID)) (*Service, error) {
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
s := &Service{
connection: connection,
idxVersion: make(map[portainer.EdgeStackID]int),
cacheInvalidationFn: cacheInvalidationFn,
}
if s.cacheInvalidationFn == nil {
s.cacheInvalidationFn = func(portainer.EdgeStackID) {}
}
es, err := s.EdgeStacks()
if err != nil {
return nil, err
}
for _, e := range es {
s.idxVersion[e.ID] = e.Version
}
return s, nil
return &Service{
connection: connection,
}, nil
}
// EdgeStacks returns an array containing all edge stacks
@@ -63,14 +41,13 @@ 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 {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -90,87 +67,28 @@ 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
err := service.connection.CreateObjectWithId(
return service.connection.CreateObjectWithId(
BucketName,
int(edgeStack.ID),
edgeStack,
)
if err != nil {
return err
}
service.mu.Lock()
service.idxVersion[id] = edgeStack.Version
service.cacheInvalidationFn(id)
service.mu.Unlock()
return nil
}
// Deprecated: Use UpdateEdgeStackFunc instead.
// UpdateEdgeStack updates an Edge stack.
func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
service.mu.Lock()
defer service.mu.Unlock()
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.UpdateObject(BucketName, identifier, edgeStack)
if err != nil {
return err
}
service.idxVersion[ID] = edgeStack.Version
service.cacheInvalidationFn(ID)
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() {
updateFunc(edgeStack)
service.idxVersion[ID] = edgeStack.Version
service.cacheInvalidationFn(ID)
})
return service.connection.UpdateObject(BucketName, identifier, edgeStack)
}
// DeleteEdgeStack deletes an Edge stack.
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
service.mu.Lock()
defer service.mu.Unlock()
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.DeleteObject(BucketName, identifier)
if err != nil {
return err
}
delete(service.idxVersion, ID)
service.cacheInvalidationFn(ID)
return nil
return service.connection.DeleteObject(BucketName, identifier)
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).

View File

@@ -1,14 +1,11 @@
package endpoint
import (
"errors"
"fmt"
"sync"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -19,9 +16,6 @@ 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 {
@@ -35,25 +29,9 @@ func NewService(connection portainer.Connection) (*Service, error) {
return nil, err
}
s := &Service{
return &Service{
connection: connection,
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
}, nil
}
// Endpoint returns an environment(endpoint) by ID.
@@ -66,54 +44,19 @@ 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))
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
return service.connection.UpdateObject(BucketName, identifier, endpoint)
}
// DeleteEndpoint deletes an environment(endpoint).
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
identifier := service.connection.ConvertToKey(int(ID))
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
return service.connection.DeleteObject(BucketName, identifier)
}
// Endpoints return an array containing all the environments(endpoints).
@@ -126,63 +69,28 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
func(obj interface{}) (interface{}, error) {
endpoint, ok := obj.(*portainer.Endpoint)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Endpoint object")
logrus.WithField("obj", obj).Errorf("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
})
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())
return endpoints, err
}
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
func (service *Service) Create(endpoint *portainer.Endpoint) error {
err := service.connection.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint)
if err != nil {
return err
return service.connection.CreateObjectWithSetSequence(BucketName, int(endpoint.ID), endpoint)
}
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
func (service *Service) CreateWithCallback(endpoint *portainer.Endpoint, fn func(id uint64) (int, interface{})) error {
if endpoint.ID > 0 {
return errors.New("the endpoint must not have an ID")
}
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
return service.connection.CreateObject(BucketName, fn)
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -67,14 +66,13 @@ 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 {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointGroup object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -4,9 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -35,7 +33,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)
@@ -45,12 +43,10 @@ func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error
func(obj interface{}) (interface{}, error) {
r, ok := obj.(*portainer.EndpointRelation)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointRelation object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -72,42 +68,17 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
// CreateEndpointRelation saves endpointRelation
func (service *Service) Create(endpointRelation *portainer.EndpointRelation) error {
err := service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
cache.Del(endpointRelation.EndpointID)
return err
return service.connection.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
}
// UpdateEndpointRelation updates an Environment(Endpoint) relation object
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
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
identifier := service.connection.ConvertToKey(int(EndpointID))
return service.connection.UpdateObject(BucketName, identifier, endpointRelation)
}
// DeleteEndpointRelation deletes an Environment(Endpoint) relation object
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
}
func (service *Service) InvalidateEdgeCacheForEdgeStack(edgeStackID portainer.EdgeStackID) {
rels, err := service.EndpointRelations()
if err != nil {
log.Error().Err(err).Msg("cannot retrieve endpoint relations")
return
}
for _, rel := range rels {
for id := range rel.EdgeStacks {
if edgeStackID == id {
cache.Del(rel.EndpointID)
}
}
}
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
identifier := service.connection.ConvertToKey(int(EndpointID))
return service.connection.DeleteObject(BucketName, identifier)
}

View File

@@ -4,8 +4,7 @@ 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")
ErrDatabaseIsUpdating = errors.New("database is currently in updating state. Failed prior upgrade. Please restore from backup or delete the database and restart Portainer")
ErrObjectNotFound = errors.New("object not found inside the database")
ErrWrongDBEdition = errors.New("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
ErrDBImportFailed = errors.New("importing backup failed")
)

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -57,12 +56,10 @@ func (service *Service) Extensions() ([]portainer.Extension, error) {
func(obj interface{}) (interface{}, error) {
extension, ok := obj.(*portainer.Extension)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Extension object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -44,9 +43,8 @@ func (service *Service) FDOProfiles() ([]portainer.FDOProfile, error) {
func(obj interface{}) (interface{}, error) {
fdoProfile, ok := obj.(*portainer.FDOProfile)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to FDOProfile object")
return nil, fmt.Errorf("Failed to convert to FDOProfile object: %s", obj)
logrus.WithField("obj", obj).Errorf("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

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -34,7 +33,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)
@@ -44,12 +43,10 @@ func (service *Service) HelmUserRepositories() ([]portainer.HelmUserRepository,
func(obj interface{}) (interface{}, error) {
r, ok := obj.(*portainer.HelmUserRepository)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -66,14 +63,12 @@ func (service *Service) HelmUserRepositoryByUserID(userID portainer.UserID) ([]p
func(obj interface{}) (interface{}, error) {
record, ok := obj.(*portainer.HelmUserRepository)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to HelmUserRepository object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -6,7 +6,6 @@ import (
"io"
"time"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices/errors"
portainer "github.com/portainer/portainer/api"
@@ -24,6 +23,7 @@ type (
BackupTo(w io.Writer) error
Export(filename string) (err error)
IsErrObjectNotFound(err error) bool
CustomTemplate() CustomTemplateService
EdgeGroup() EdgeGroupService
EdgeJob() EdgeJobService
@@ -38,7 +38,6 @@ type (
Role() RoleService
APIKeyRepository() APIKeyRepository
Settings() SettingsService
Snapshot() SnapshotService
SSLSettings() SSLSettingsService
Stack() StackService
Tag() TagService
@@ -67,7 +66,6 @@ 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
}
@@ -78,7 +76,6 @@ 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
@@ -88,10 +85,8 @@ 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
@@ -100,11 +95,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
CreateWithCallback(endpoint *portainer.Endpoint, fn func(uint64) (int, interface{})) error
UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error
DeleteEndpoint(ID portainer.EndpointID) error
GetNextIdentifier() int
@@ -209,15 +202,6 @@ 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)
@@ -246,7 +230,6 @@ 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
}
@@ -274,7 +257,6 @@ 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
@@ -298,11 +280,12 @@ type (
// VersionService represents a service for managing version data
VersionService interface {
DBVersion() (int, error)
Edition() (portainer.SoftwareEdition, error)
InstanceID() (string, error)
UpdateInstanceID(ID string) error
Version() (*models.Version, error)
UpdateVersion(*models.Version) error
StoreDBVersion(version int) error
StoreInstanceID(ID string) error
BucketName() string
}
// WebhookService represents a service for managing webhook data.

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -57,12 +56,10 @@ func (service *Service) Registries() ([]portainer.Registry, error) {
func(obj interface{}) (interface{}, error) {
registry, ok := obj.(*portainer.Registry)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Registry object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -59,7 +58,7 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
func(obj interface{}) (interface{}, error) {
rc, ok := obj.(*portainer.ResourceControl)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
logrus.WithField("obj", obj).Errorf("Failed to convert to ResourceControl object")
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
}
@@ -74,7 +73,6 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
return nil, stop
}
}
return &portainer.ResourceControl{}, nil
})
if err == stop {
@@ -94,12 +92,10 @@ func (service *Service) ResourceControls() ([]portainer.ResourceControl, error)
func(obj interface{}) (interface{}, error) {
rc, ok := obj.(*portainer.ResourceControl)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to ResourceControl object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -57,12 +56,10 @@ func (service *Service) Roles() ([]portainer.Role, error) {
func(obj interface{}) (interface{}, error) {
set, ok := obj.(*portainer.Role)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Role object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -69,12 +68,10 @@ func (service *Service) Schedules() ([]portainer.Schedule, error) {
func(obj interface{}) (interface{}, error) {
schedule, ok := obj.(*portainer.Schedule)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Schedule object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -92,14 +89,12 @@ func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portain
func(obj interface{}) (interface{}, error) {
schedule, ok := obj.(*portainer.Schedule)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Schedule object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -108,7 +103,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.CreateObjectWithId(BucketName, int(schedule.ID), schedule)
return service.connection.CreateObjectWithSetSequence(BucketName, int(schedule.ID), schedule)
}
// GetNextIdentifier returns the next identifier for a schedule.

View File

@@ -1,6 +1,8 @@
package settings
import (
"sync"
portainer "github.com/portainer/portainer/api"
)
@@ -13,6 +15,30 @@ const (
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection portainer.Connection
cache *portainer.Settings
mu sync.RWMutex
}
func cloneSettings(src *portainer.Settings) *portainer.Settings {
if src == nil {
return nil
}
c := *src
if c.BlackListedLabels != nil {
c.BlackListedLabels = make([]portainer.Pair, len(src.BlackListedLabels))
copy(c.BlackListedLabels, src.BlackListedLabels)
}
if src.FeatureFlagSettings != nil {
c.FeatureFlagSettings = make(map[portainer.Feature]bool)
for k, v := range src.FeatureFlagSettings {
c.FeatureFlagSettings[k] = v
}
}
return &c
}
func (service *Service) BucketName() string {
@@ -33,6 +59,18 @@ func NewService(connection portainer.Connection) (*Service, error) {
// Settings retrieve the settings object.
func (service *Service) Settings() (*portainer.Settings, error) {
service.mu.RLock()
if service.cache != nil {
s := cloneSettings(service.cache)
service.mu.RUnlock()
return s, nil
}
service.mu.RUnlock()
service.mu.Lock()
defer service.mu.Unlock()
var settings portainer.Settings
err := service.connection.GetObject(BucketName, []byte(settingsKey), &settings)
@@ -40,12 +78,24 @@ func (service *Service) Settings() (*portainer.Settings, error) {
return nil, err
}
service.cache = cloneSettings(&settings)
return &settings, nil
}
// UpdateSettings persists a Settings object.
func (service *Service) UpdateSettings(settings *portainer.Settings) error {
return service.connection.UpdateObject(BucketName, []byte(settingsKey), settings)
service.mu.Lock()
defer service.mu.Unlock()
err := service.connection.UpdateObject(BucketName, []byte(settingsKey), settings)
if err != nil {
return err
}
service.cache = cloneSettings(settings)
return nil
}
func (service *Service) IsFeatureFlagEnabled(feature portainer.Feature) bool {
@@ -61,3 +111,9 @@ func (service *Service) IsFeatureFlagEnabled(feature portainer.Feature) bool {
return false
}
func (service *Service) InvalidateCache() {
service.mu.Lock()
service.cache = nil
service.mu.Unlock()
}

View File

@@ -1,77 +0,0 @@
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)
}

View File

@@ -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,15 +60,13 @@ func (service *Service) StackByName(name string) (*portainer.Stack, error) {
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(*portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("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 {
@@ -91,14 +89,12 @@ func (service *Service) StacksByName(name string) ([]portainer.Stack, error) {
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -115,12 +111,10 @@ func (service *Service) Stacks() ([]portainer.Stack, error) {
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(*portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -134,7 +128,7 @@ func (service *Service) GetNextIdentifier() int {
// CreateStack creates a new stack.
func (service *Service) Create(stack *portainer.Stack) error {
return service.connection.CreateObjectWithId(BucketName, int(stack.ID), stack)
return service.connection.CreateObjectWithSetSequence(BucketName, int(stack.ID), stack)
}
// UpdateStack updates a stack.
@@ -162,15 +156,13 @@ func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) {
s, ok = obj.(*portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("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 {
@@ -194,14 +186,12 @@ func (service *Service) RefreshableStacks() ([]portainer.Stack, error) {
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(*portainer.Stack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -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(t, true, true)
_, store, teardown := datastore.MustNewTestStore(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(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
staticStack := portainer.Stack{ID: 1}

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -44,12 +43,10 @@ func (service *Service) Tags() ([]portainer.Tag, error) {
func(obj interface{}) (interface{}, error) {
tag, ok := obj.(*portainer.Tag)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Tag object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -80,22 +77,12 @@ func (service *Service) Create(tag *portainer.Tag) error {
)
}
// Deprecated: Use UpdateTagFunc instead.
// UpdateTag updates a tag.
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))

View File

@@ -4,10 +4,10 @@ import (
"fmt"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/sirupsen/logrus"
"github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api"
)
const (
@@ -60,15 +60,13 @@ func (service *Service) TeamByName(name string) (*portainer.Team, error) {
func(obj interface{}) (interface{}, error) {
team, ok := obj.(*portainer.Team)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Team object")
logrus.WithField("obj", obj).Errorf("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 {
@@ -91,12 +89,10 @@ func (service *Service) Teams() ([]portainer.Team, error) {
func(obj interface{}) (interface{}, error) {
team, ok := obj.(*portainer.Team)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Team object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -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(t, true, true)
_, store, teardown := datastore.MustNewTestStore(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(t, true, true)
_, store, teardown := datastore.MustNewTestStore(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(t, true, true)
_, store, teardown := datastore.MustNewTestStore(true, true)
defer teardown()
teamBuilder := teamBuilder{

View File

@@ -4,8 +4,7 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -57,12 +56,10 @@ func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
func(obj interface{}) (interface{}, error) {
membership, ok := obj.(*portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -79,14 +76,12 @@ func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]port
func(obj interface{}) (interface{}, error) {
membership, ok := obj.(*portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -103,14 +98,12 @@ func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]port
func(obj interface{}) (interface{}, error) {
membership, ok := obj.(*portainer.TeamMembership)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -144,19 +137,16 @@ 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 {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("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
})
}
@@ -165,39 +155,16 @@ 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 {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to TeamMembership object")
logrus.WithField("obj", obj).Errorf("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
})
}

View File

@@ -4,10 +4,10 @@ import (
"fmt"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/sirupsen/logrus"
"github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api"
)
const (
@@ -59,23 +59,18 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
func(obj interface{}) (interface{}, error) {
user, ok := obj.(*portainer.User)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
logrus.WithField("obj", obj).Errorf("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
}
@@ -93,13 +88,10 @@ func (service *Service) Users() ([]portainer.User, error) {
func(obj interface{}) (interface{}, error) {
user, ok := obj.(*portainer.User)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -116,15 +108,12 @@ func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User,
func(obj interface{}) (interface{}, error) {
user, ok := obj.(*portainer.User)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to User object")
logrus.WithField("obj", obj).Errorf("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
})

View File

@@ -1,18 +1,17 @@
package version
import (
"errors"
"strconv"
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 = "VERSION"
versionKey = "DB_VERSION"
instanceKey = "INSTANCE_ID"
editionKey = "EDITION"
updatingKey = "DB_UPDATING"
)
@@ -21,6 +20,10 @@ 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)
@@ -33,87 +36,56 @@ func NewService(connection portainer.Connection) (*Service, error) {
}, nil
}
func (service *Service) SchemaVersion() (string, error) {
v, err := service.Version()
if err != nil {
return "", err
}
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)
}
func (service *Service) Edition() (portainer.SoftwareEdition, error) {
v, err := service.Version()
// DBVersion retrieves the stored database version.
func (service *Service) DBVersion() (int, error) {
var version string
err := service.connection.GetObject(BucketName, []byte(versionKey), &version)
if err != nil {
return 0, err
}
return strconv.Atoi(version)
}
return portainer.SoftwareEdition(v.Edition), nil
// Edition retrieves the stored portainer edition.
func (service *Service) Edition() (portainer.SoftwareEdition, error) {
var edition string
err := service.connection.GetObject(BucketName, []byte(editionKey), &edition)
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))
}
// 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.DeleteObject(BucketName, []byte(updatingKey))
return service.connection.UpdateObject(BucketName, []byte(updatingKey), isUpdating)
}
// InstanceID retrieves the stored instance ID.
func (service *Service) InstanceID() (string, error) {
v, err := service.Version()
if err != nil {
return "", err
}
return v.InstanceID, nil
var id string
err := service.connection.GetObject(BucketName, []byte(instanceKey), &id)
return id, err
}
// StoreInstanceID store the instance ID.
func (service *Service) UpdateInstanceID(id string) error {
v, err := service.Version()
if err != nil {
if !dataservices.IsErrObjectNotFound(err) {
return err
}
func (service *Service) StoreInstanceID(ID string) error {
return service.connection.UpdateObject(BucketName, []byte(instanceKey), ID)
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)
}

View File

@@ -5,8 +5,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
const (
@@ -35,7 +34,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)
@@ -45,12 +44,10 @@ func (service *Service) Webhooks() ([]portainer.Webhook, error) {
func(obj interface{}) (interface{}, error) {
webhook, ok := obj.(*portainer.Webhook)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
logrus.WithField("obj", obj).Errorf("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
})
@@ -80,23 +77,18 @@ func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, erro
func(obj interface{}) (interface{}, error) {
webhook, ok := obj.(*portainer.Webhook)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
logrus.WithField("obj", obj).Errorf("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
}
@@ -114,23 +106,18 @@ func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error)
func(obj interface{}) (interface{}, error) {
webhook, ok := obj.(*portainer.Webhook)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Webhook object")
logrus.WithField("obj", obj).Errorf("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
}

View File

@@ -6,8 +6,7 @@ import (
"path"
"time"
"github.com/portainer/portainer/api/database/models"
"github.com/rs/zerolog/log"
plog "github.com/portainer/portainer/api/datastore/log"
)
var backupDefaults = struct {
@@ -18,6 +17,8 @@ var backupDefaults = struct {
"common",
}
var backupLog = plog.NewScopedLog("database, backup")
//
// Backup Helpers
//
@@ -28,7 +29,7 @@ func (store *Store) createBackupFolders() {
commonDir := store.commonBackupDir()
if exists, _ := store.fileService.FileExists(commonDir); !exists {
if err := os.MkdirAll(commonDir, 0700); err != nil {
log.Error().Err(err).Msg("error while creating common backup folder")
backupLog.Error("Error while creating common backup folder", err)
}
}
}
@@ -42,19 +43,17 @@ func (store *Store) commonBackupDir() string {
}
func (store *Store) copyDBFile(from string, to string) error {
log.Info().Str("from", from).Str("to", to).Msg("copying DB file")
backupLog.Info(fmt.Sprintf("Copying db file from %s to %s", from, to))
err := store.fileService.Copy(from, to, true)
if err != nil {
log.Error().Err(err).Msg("failed")
backupLog.Error("Failed", err)
}
return err
}
// BackupOptions provide a helper to inject backup options
type BackupOptions struct {
Version string
Version int // I can't find this used for anything other than a filename
BackupDir string
BackupFileName string
BackupPath string
@@ -71,32 +70,26 @@ func getBackupRestoreOptions(backupDir string) *BackupOptions {
}
// Backup current database with default options
func (store *Store) Backup(version *models.Version) (string, error) {
if version == nil {
return store.backupWithOptions(nil)
}
return store.backupWithOptions(&BackupOptions{
Version: version.SchemaVersion,
})
func (store *Store) Backup() (string, error) {
return store.backupWithOptions(nil)
}
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
if options == nil {
options = &BackupOptions{}
}
if options.Version == "" {
v, err := store.VersionService.Version()
if options.Version == 0 {
version, err := store.version()
if err != nil {
options.Version = ""
version = 0
}
options.Version = v.SchemaVersion
options.Version = version
}
if options.BackupDir == "" {
options.BackupDir = store.commonBackupDir()
}
if options.BackupFileName == "" {
options.BackupFileName = fmt.Sprintf("%s.%s.%s", store.connection.GetDatabaseFileName(), options.Version, time.Now().Format("20060102150405"))
options.BackupFileName = fmt.Sprintf("%s.%s.%s", store.connection.GetDatabaseFileName(), fmt.Sprintf("%03d", options.Version), time.Now().Format("20060102150405"))
}
if options.BackupPath == "" {
options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
@@ -106,32 +99,12 @@ func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
// BackupWithOptions backup current database with options
func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
log.Info().Msg("creating DB backup")
backupLog.Info("creating db backup")
store.createBackupFolders()
options = store.setupOptions(options)
dbPath := store.databasePath()
if err := store.Close(); err != nil {
return options.BackupPath, fmt.Errorf(
"error closing datastore before creating backup: %v",
err,
)
}
if err := store.copyDBFile(dbPath, options.BackupPath); err != nil {
return options.BackupPath, err
}
if _, err := store.Open(); err != nil {
return options.BackupPath, fmt.Errorf(
"error opening datastore after creating backup: %v",
err,
)
}
return options.BackupPath, nil
return options.BackupPath, store.copyDBFile(store.databasePath(), options.BackupPath)
}
// RestoreWithOptions previously saved backup for the current Edition with options
@@ -144,19 +117,17 @@ func (store *Store) restoreWithOptions(options *BackupOptions) error {
// Check if backup file exist before restoring
_, err := os.Stat(options.BackupPath)
if os.IsNotExist(err) {
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to restore does not exist %s")
backupLog.Error(fmt.Sprintf("Backup file to restore does not exist %s", options.BackupPath), err)
return err
}
err = store.Close()
if err != nil {
log.Error().Err(err).Msg("error while closing store before restore")
backupLog.Error("Error while closing store before restore", err)
return err
}
log.Info().Msg("restoring DB backup")
backupLog.Info("Restoring db backup")
err = store.copyDBFile(options.BackupPath, store.databasePath())
if err != nil {
return err
@@ -168,20 +139,20 @@ func (store *Store) restoreWithOptions(options *BackupOptions) error {
// RemoveWithOptions removes backup database based on supplied options
func (store *Store) removeWithOptions(options *BackupOptions) error {
log.Info().Msg("removing DB backup")
backupLog.Info("Removing db backup")
options = store.setupOptions(options)
_, err := os.Stat(options.BackupPath)
if os.IsNotExist(err) {
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to remove does not exist")
backupLog.Error(fmt.Sprintf("Backup file to remove does not exist %s", options.BackupPath), err)
return err
}
log.Info().Str("path", options.BackupPath).Msg("removing DB file")
backupLog.Info(fmt.Sprintf("Removing db file at %s", options.BackupPath))
err = os.Remove(options.BackupPath)
if err != nil {
log.Error().Err(err).Msg("failed")
backupLog.Error("Failed", err)
return err
}

View File

@@ -7,11 +7,10 @@ import (
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
)
func TestCreateBackupFolders(t *testing.T) {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
connection := store.GetConnection()
@@ -28,7 +27,7 @@ func TestCreateBackupFolders(t *testing.T) {
}
func TestStoreCreation(t *testing.T) {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
defer teardown()
if store == nil {
@@ -41,18 +40,15 @@ func TestStoreCreation(t *testing.T) {
}
func TestBackup(t *testing.T) {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
connection := store.GetConnection()
defer teardown()
t.Run("Backup should create default db backup", func(t *testing.T) {
v := models.Version{
SchemaVersion: portainer.APIVersion,
}
store.VersionService.UpdateVersion(&v)
store.VersionService.StoreDBVersion(portainer.DBVersion)
store.backupWithOptions(nil)
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", fmt.Sprintf("portainer.edb.%s.*", portainer.APIVersion))
backupFileName := path.Join(connection.GetStorePath(), "backups", "common", fmt.Sprintf("portainer.edb.%03d.*", portainer.DBVersion))
if !isFileExist(backupFileName) {
t.Errorf("Expect backup file to be created %s", backupFileName)
}
@@ -71,7 +67,7 @@ func TestBackup(t *testing.T) {
}
func TestRemoveWithOptions(t *testing.T) {
_, store, teardown := MustNewTestStore(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
defer teardown()
t.Run("successfully removes file if existent", func(t *testing.T) {

View File

@@ -9,10 +9,25 @@ import (
portainer "github.com/portainer/portainer/api"
portainerErrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
"github.com/sirupsen/logrus"
)
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{
@@ -23,6 +38,8 @@ 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
@@ -37,24 +54,30 @@ func (store *Store) Open() (newStore bool, err error) {
err = store.connection.Open()
if err != nil {
return false, err
return newStore, err
}
err = store.initServices()
if err != nil {
return false, err
return newStore, err
}
// If no settings object exists then assume we have a new store
_, err = store.SettingsService.Settings()
// if we have DBVersion in the database then ensure we flag this as NOT a new store
version, err := store.VersionService.DBVersion()
if err != nil {
if store.IsErrObjectNotFound(err) {
return true, nil
return newStore, nil
}
return false, err
return newStore, err
}
return false, nil
if version > 0 {
logrus.WithField("version", version).Infof("Opened existing store")
return false, nil
}
return newStore, nil
}
func (store *Store) Close() error {
@@ -69,29 +92,17 @@ 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.Edition {
if store.edition() != portainer.PortainerCE {
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)
}
@@ -110,21 +121,19 @@ func (store *Store) encryptDB() error {
// The DB is not currently encrypted. First save the encrypted db filename
oldFilename := store.connection.GetDatabaseFilePath()
log.Info().Msg("encrypting database")
logrus.Infof("Encrypting database")
// export file path for backup
exportFilename := path.Join(store.databasePath() + "." + fmt.Sprintf("backup-%d.json", time.Now().Unix()))
log.Info().Str("filename", exportFilename).Msg("exporting database backup")
logrus.Infof("Exporting database backup to %s", exportFilename)
err = store.Export(exportFilename)
if err != nil {
log.Error().Str("filename", exportFilename).Err(err).Msg("failed to export")
logrus.WithError(err).Debugf("Failed to export to %s", exportFilename)
return err
}
log.Info().Msg("database backup exported")
logrus.Infof("Database backup exported")
// Close existing un-encrypted db so that we can delete the file later
store.connection.Close()
@@ -143,24 +152,22 @@ func (store *Store) encryptDB() error {
if err != nil {
// Remove the new encrypted file that we failed to import
os.Remove(store.connection.GetDatabaseFilePath())
log.Fatal().Err(portainerErrors.ErrDBImportFailed).Msg("")
logrus.Fatal(portainerErrors.ErrDBImportFailed.Error())
}
err = os.Remove(oldFilename)
if err != nil {
log.Error().Msg("failed to remove the un-encrypted db file")
logrus.Errorf("Failed to remove the un-encrypted db file")
}
err = os.Remove(exportFilename)
if err != nil {
log.Error().Msg("failed to remove the json backup file")
logrus.Errorf("Failed to remove the json backup file")
}
// Close db connection
store.connection.Close()
log.Info().Msg("database successfully encrypted")
logrus.Info("Database successfully encrypted")
return nil
}

View File

@@ -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(t, true, true)
_, store, teardown := MustNewTestStore(true, true)
defer teardown()
testCases := map[string]func(t *testing.T){
@@ -177,7 +177,7 @@ func (store *Store) CreateEndpoint(t *testing.T, name string, endpointType porta
func (store *Store) CreateEndpointRelation(id portainer.EndpointID) {
relation := &portainer.EndpointRelation{
EndpointID: id,
EdgeStacks: map[portainer.EdgeStackID]bool{},
EdgeStacks: map[portainer.EdgeStackID]portainer.EdgeStackStatus{},
}
store.EndpointRelation().Create(relation)

View File

@@ -1,12 +1,18 @@
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.checkOrCreateDefaultSettings()
err := store.checkOrCreateInstanceID()
if err != nil {
return err
}
err = store.checkOrCreateDefaultSettings()
if err != nil {
return err
}
@@ -19,17 +25,28 @@ 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: false,
EnableTelemetry: true,
AuthenticationMethod: portainer.AuthenticationInternal,
BlackListedLabels: make([]portainer.Pair, 0),
InternalAuthSettings: portainer.InternalAuthSettings{
RequiredPasswordLength: 12,
},
LDAPSettings: portainer.LDAPSettings{
AnonymousMode: true,
AutoCreateUsers: true,
@@ -68,6 +85,7 @@ func (store *Store) checkOrCreateDefaultSettings() error {
func (store *Store) checkOrCreateDefaultSSLSettings() error {
_, err := store.SSLSettings().Settings()
if store.IsErrObjectNotFound(err) {
defaultSSLSettings := &portainer.SSLSettings{
HTTPEnabled: true,
@@ -75,7 +93,6 @@ func (store *Store) checkOrCreateDefaultSSLSettings() error {
return store.SSLSettings().UpdateSettings(defaultSSLSettings)
}
return err
}

51
api/datastore/log/log.go Normal file
View File

@@ -0,0 +1,51 @@
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))
}

View File

@@ -4,75 +4,47 @@ import (
"fmt"
"runtime/debug"
portaineree "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/database/models"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/dataservices/errors"
plog "github.com/portainer/portainer/api/datastore/log"
"github.com/portainer/portainer/api/datastore/migrator"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/sirupsen/logrus"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
werrors "github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
)
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
var migrateLog = plog.NewScopedLog("database, migrate")
func (store *Store) MigrateData() error {
updating, err := store.VersionService.IsUpdating()
version, err := store.version()
if err != nil {
return errors.Wrap(err, "while checking if the store is updating")
return err
}
if updating {
return dserrors.ErrDatabaseIsUpdating
}
// migrate new version bucket if required (doesn't write anything to db yet)
version, err := store.getOrMigrateLegacyVersion()
// Backup Database
backupPath, err := store.Backup()
if err != nil {
return errors.Wrap(err, "while migrating legacy version")
return werrors.Wrap(err, "while backing up db before migration")
}
migratorParams := store.newMigratorParameters(version)
migrator := migrator.NewMigrator(migratorParams)
store.SettingsService.InvalidateCache()
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,
migratorParams := &migrator.MigratorParameters{
DatabaseVersion: 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,
@@ -81,41 +53,91 @@ func (store *Store) newMigratorParameters(version *models.Version) *migrator.Mig
FileService: store.fileService,
DockerhubService: store.DockerHubService,
AuthorizationService: authorization.NewService(store),
EdgeStackService: store.EdgeStackService,
EdgeJobService: store.EdgeJobService,
}
// 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, version *models.Version) (err error) {
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator) (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()))
}
}()
err = store.VersionService.StoreIsUpdating(true)
if err != nil {
return errors.Wrap(err, "while updating the store")
}
// !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()
}
// now update the version to the new struct (if required)
err = store.finishMigrateLegacyVersion(version)
if err != nil {
return errors.Wrap(err, "while updating version")
}
// 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)
log.Info().Msg("migrating database from version " + version.SchemaVersion + " to " + portaineree.APIVersion)
err = migrator.Migrate()
if err != nil {
// backup db file before upgrading DB to support rollback
isUpdating, err := migratorParams.VersionService.IsUpdating()
if err != nil && err != errors.ErrObjectNotFound {
return err
}
err = store.VersionService.StoreIsUpdating(false)
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)
if err != nil {
return errors.Wrap(err, "failed to update the store")
migrateLog.Error("An error occurred during database backup", err)
removalErr := store.removeWithOptions(options)
if removalErr != nil {
migrateLog.Error("An error occurred during store removal prior to backup", err)
}
return err
}
return nil

View File

@@ -5,46 +5,43 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"testing"
"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"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/boltdb"
)
// testVersion is a helper which tests current store version against wanted version
func testVersion(store *Store, versionWant string, t *testing.T) {
v, err := store.VersionService.Version()
func testVersion(store *Store, versionWant int, t *testing.T) {
v, err := store.VersionService.DBVersion()
if err != nil {
t.Errorf("Expect store version to be %s but was %s with error: %s", versionWant, v.SchemaVersion, err)
t.Errorf("Expect store version to be %d but was %d with error: %s", versionWant, v, err)
}
if v.SchemaVersion != versionWant {
t.Errorf("Expect store version to be %s but was %s", versionWant, v.SchemaVersion)
if v != versionWant {
t.Errorf("Expect store version to be %d but was %d", versionWant, v)
}
}
func TestMigrateData(t *testing.T) {
snapshotTests := []struct {
testName string
srcPath string
wantPath string
overrideInstanceId bool
testName string
srcPath string
wantPath string
}{
{
testName: "migrate version 24 to latest",
srcPath: "test_data/input_24.json",
wantPath: "test_data/output_24_to_latest.json",
overrideInstanceId: true,
testName: "migrate version 24 to 35",
srcPath: "test_data/input_24.json",
wantPath: "test_data/output_35.json",
},
}
for _, test := range snapshotTests {
t.Run(test.testName, func(t *testing.T) {
err := migrateDBTestHelper(t, test.srcPath, test.wantPath, test.overrideInstanceId)
err := migrateDBTestHelper(t, test.srcPath, test.wantPath)
if err != nil {
t.Errorf(
"Failed migrating mock database %v: %v",
@@ -55,151 +52,147 @@ func TestMigrateData(t *testing.T) {
})
}
// t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
// newStore, store, teardown := MustNewTestStore(t, true, false)
// defer teardown()
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
newStore, store, teardown := MustNewTestStore(false, true)
defer teardown()
// if !newStore {
// t.Error("Expect a new DB")
// }
if !newStore {
t.Error("Expect a new DB")
}
// testVersion(store, portainer.APIVersion, t)
// store.Close()
// not called for new stores
//store.MigrateData()
// newStore, _ = store.Open()
// if newStore {
// t.Error("Expect store to NOT be new DB")
// }
// })
testVersion(store, portainer.DBVersion, t)
store.Close()
// 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()
newStore, _ = store.Open()
if newStore {
t.Error("Expect store to NOT be new DB")
}
})
// // Setup data
// v := models.Version{SchemaVersion: tc.version}
// store.VersionService.UpdateVersion(&v)
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()
// // 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})
// Setup data
store.VersionService.StoreDBVersion(tc.version)
// t.Run(fmt.Sprintf("MigrateData for version %s", tc.version), func(t *testing.T) {
// store.MigrateData()
// testVersion(store, tc.expectedVersion, t)
// })
// 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("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("MigrateData for version %d", tc.version), func(t *testing.T) {
store.MigrateData()
testVersion(store, tc.expectedVersion, 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(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)
})
}
// v := models.Version{SchemaVersion: "1.24.1"}
// store.VersionService.UpdateVersion(&v)
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
// store.MigrateData()
version := 17
store.VersionService.StoreDBVersion(version)
// testVersion(store, v.SchemaVersion, t)
// })
store.MigrateData()
// t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
testVersion(store, version, t)
})
// v := models.Version{SchemaVersion: "0.0.0"}
// store.VersionService.UpdateVersion(&v)
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
store.VersionService.StoreDBVersion(0)
// 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(t, 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(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(t, 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(false, true)
defer teardown()
// 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)
// }
// })
}
func Test_getBackupRestoreOptions(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
options := getBackupRestoreOptions(store.commonBackupDir())
wantDir := store.commonBackupDir()
if !strings.HasSuffix(options.BackupDir, wantDir) {
log.Fatal().Str("got", options.BackupDir).Str("want", wantDir).Msg("incorrect backup dir")
log.Fatalf("incorrect backup dir; got=%s, want=%s", options.BackupDir, wantDir)
}
wantFilename := "portainer.db.bak"
if options.BackupFileName != wantFilename {
log.Fatal().Str("got", options.BackupFileName).Str("want", wantFilename).Msg("incorrect backup file")
log.Fatalf("incorrect backup file; got=%s, want=%s", options.BackupFileName, wantFilename)
}
}
func TestRollback(t *testing.T) {
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
version := models.Version{SchemaVersion: "2.4.0"}
_, store, teardown := MustNewTestStore(t, true, false)
version := 21
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
store.VersionService.StoreDBVersion(version)
err := store.VersionService.UpdateVersion(&version)
_, err := store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
if err != nil {
t.Errorf("Failed updating version: %v", err)
log.Fatal(err)
}
_, err = store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
// Change the current edition
err = store.VersionService.StoreDBVersion(version + 10)
if err != nil {
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("")
log.Fatal(err)
}
err = store.Rollback(true)
@@ -209,14 +202,8 @@ func TestRollback(t *testing.T) {
return
}
_, err = store.Open()
if err != nil {
t.Logf("Open failed: %s", err)
t.Fail()
return
}
testVersion(store, version.SchemaVersion, t)
store.Open()
testVersion(store, version, t)
})
}
@@ -232,20 +219,15 @@ 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, overrideInstanceId bool) error {
func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) 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.
// 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()
_, store, teardown := MustNewTestStore(true, false)
defer teardown()
err = importJSON(t, bytes.NewReader(srcJSON), store)
if err != nil {
return err
@@ -257,21 +239,6 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
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.
@@ -291,7 +258,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
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",
@@ -348,65 +315,42 @@ 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)
}
dbVersion, ok := versions["DB_VERSION"]
if !ok {
t.Logf("failed getting DB_VERSION from %s", k)
}
// old format db
numDBVersion, ok := dbVersion.(json.Number)
if !ok {
t.Logf("failed parsing DB_VERSION as json number from %s", k)
}
dbVersion, ok := versions["DB_VERSION"]
if ok {
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 {
err = con.CreateObjectWithStringId(
k,
[]byte("INSTANCE_ID"),
instanceID,
)
if err != nil {
t.Logf("failed writing INSTANCE_ID in %s: %v", k, err)
}
if !ok {
t.Logf("failed getting INSTANCE_ID from %s", k)
}
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)
}
err = con.CreateObjectWithStringId(
k,
[]byte("INSTANCE_ID"),
instanceID,
)
if err != nil {
t.Logf("failed writing INSTANCE_ID in %s: %v", k, err)
}
case "dockerhub":

View File

@@ -33,7 +33,7 @@ func setup(store *Store) error {
}
func TestMigrateSettings(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
err := setup(store)
@@ -54,6 +54,7 @@ func TestMigrateSettings(t *testing.T) {
}
m := migrator.NewMigrator(&migrator.MigratorParameters{
DatabaseVersion: 29,
EndpointGroupService: store.EndpointGroupService,
EndpointService: store.EndpointService,
EndpointRelationService: store.EndpointRelationService,

View File

@@ -10,7 +10,7 @@ import (
)
func TestMigrateStackEntryPoint(t *testing.T) {
_, store, teardown := MustNewTestStore(t, false, true)
_, store, teardown := MustNewTestStore(false, true)
defer teardown()
stackService := store.Stack()

View File

@@ -1,115 +0,0 @@
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
}

View File

@@ -1,18 +1,32 @@
package migrator
import (
"errors"
"reflect"
"runtime"
"github.com/pkg/errors"
werrors "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 errors.Wrap(err, "failed in "+context)
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.")
}
func GetFunctionName(i interface{}) string {
@@ -21,105 +35,97 @@ 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 {
version, err := m.versionService.Version()
// set DB to updating status
err := m.versionService.StoreIsUpdating(true)
if err != nil {
return migrationError(err, "get version service")
return migrationError(err, "StoreIsUpdating")
}
schemaVersion, err := semver.NewVersion(version.SchemaVersion)
if err != nil {
return migrationError(err, "invalid db schema version")
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),
}
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)
var lastDbVersion int
for _, migration := range migrations {
if m.currentDBVersion < migration.dbversion {
// Print the next line only when the version changes
if migration.dbversion > lastDbVersion {
migrateLog.Infof("Migrating DB to version %d", migration.dbversion)
}
err := migration.migrate()
if err != nil {
return err
}
newMigratorCount = len(latestMigrations.MigrationFuncs)
}
} else {
// regular path when major/minor/patch versions differ
for _, migration := range m.migrations {
if schemaVersion.LessThan(migration.Version) {
log.Info().Msgf("migrating data to %s", migration.Version.String())
err := runMigrations(migration.MigrationFuncs)
if err != nil {
return err
}
}
if apiVersion.Equal(migration.Version) {
newMigratorCount = len(migration.MigrationFuncs)
return migrationError(err, GetFunctionName(migration.migrate))
}
}
lastDbVersion = migration.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)
migrateLog.Infof("Setting DB version to %d", portainer.DBVersion)
err = m.versionService.StoreDBVersion(portainer.DBVersion)
if err != nil {
return migrationError(err, "StoreDBVersion")
}
migrateLog.Infof("Updated DB version to %d", portainer.DBVersion)
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
// reset DB updating status
return m.versionService.StoreIsUpdating(false)
}

View File

@@ -2,13 +2,10 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) updateUsersToDBVersion18() error {
log.Info().Msg("updating users")
migrateLog.Info("- updating users")
legacyUsers, err := m.userService.Users()
if err != nil {
return err
@@ -43,8 +40,7 @@ func (m *Migrator) updateUsersToDBVersion18() error {
}
func (m *Migrator) updateEndpointsToDBVersion18() error {
log.Info().Msg("updating endpoints")
migrateLog.Info("- updating endpoints")
legacyEndpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
@@ -75,8 +71,7 @@ func (m *Migrator) updateEndpointsToDBVersion18() error {
}
func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
log.Info().Msg("updating endpoint groups")
migrateLog.Info("- updating endpoint groups")
legacyEndpointGroups, err := m.endpointGroupService.EndpointGroups()
if err != nil {
return err
@@ -107,8 +102,7 @@ func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
}
func (m *Migrator) updateRegistriesToDBVersion18() error {
log.Info().Msg("updating registries")
migrateLog.Info("- updating registries")
legacyRegistries, err := m.registryService.Registries()
if err != nil {
return err

View File

@@ -1,14 +1,9 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
import portainer "github.com/portainer/portainer/api"
func (m *Migrator) updateSettingsToDBVersion19() error {
log.Info().Msg("updating settings")
migrateLog.Info("- updating settings")
legacySettings, err := m.settingsService.Settings()
if err != nil {
return err

View File

@@ -2,20 +2,16 @@ package migrator
import (
"strings"
"github.com/rs/zerolog/log"
)
const scheduleScriptExecutionJobType = 1
func (m *Migrator) updateUsersToDBVersion20() error {
log.Info().Msg("updating user authentication")
migrateLog.Info("- 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
@@ -27,8 +23,7 @@ func (m *Migrator) updateSettingsToDBVersion20() error {
}
func (m *Migrator) updateSchedulesToDBVersion20() error {
log.Info().Msg("updating schedules")
migrateLog.Info("- updating schedules")
legacySchedules, err := m.scheduleService.Schedules()
if err != nil {
return err

View File

@@ -3,13 +3,10 @@ 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 {
log.Info().Msg("updating resource controls")
migrateLog.Info("- updating resource controls")
legacyResourceControls, err := m.resourceControlService.ResourceControls()
if err != nil {
return err
@@ -28,8 +25,7 @@ func (m *Migrator) updateResourceControlsToDBVersion22() error {
}
func (m *Migrator) updateUsersAndRolesToDBVersion22() error {
log.Info().Msg("updating users and roles")
migrateLog.Info("- updating users and roles")
legacyUsers, err := m.userService.Users()
if err != nil {
return err

View File

@@ -1,14 +1,9 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
import portainer "github.com/portainer/portainer/api"
func (m *Migrator) updateTagsToDBVersion23() error {
log.Info().Msg("updating tags")
migrateLog.Info("- Updating tags")
tags, err := m.tagService.Tags()
if err != nil {
return err
@@ -22,13 +17,11 @@ func (m *Migrator) updateTagsToDBVersion23() error {
return err
}
}
return nil
}
func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
log.Info().Msg("updating endpoints and endpoint groups")
migrateLog.Info("- updating endpoints and endpoint groups")
tags, err := m.tagService.Tags()
if err != nil {
return err
@@ -61,7 +54,7 @@ func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
relation := &portainer.EndpointRelation{
EndpointID: endpoint.ID,
EdgeStacks: map[portainer.EdgeStackID]bool{},
EdgeStacks: map[portainer.EdgeStackID]portainer.EdgeStackStatus{},
}
err = m.endpointRelationService.Create(relation)
@@ -97,6 +90,5 @@ func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
return err
}
}
return nil
}

View File

@@ -1,13 +1,9 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
import portainer "github.com/portainer/portainer/api"
func (m *Migrator) updateSettingsToDB24() error {
log.Info().Msg("updating Settings")
migrateLog.Info("- updating Settings")
legacySettings, err := m.settingsService.Settings()
if err != nil {
@@ -22,8 +18,7 @@ func (m *Migrator) updateSettingsToDB24() error {
}
func (m *Migrator) updateStacksToDB24() error {
log.Info().Msg("updating stacks")
migrateLog.Info("- updating stacks")
stacks, err := m.stackService.Stacks()
if err != nil {
return err

View File

@@ -2,12 +2,10 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) updateSettingsToDB25() error {
log.Info().Msg("updating settings")
migrateLog.Info("- updating settings")
legacySettings, err := m.settingsService.Settings()
if err != nil {

View File

@@ -2,13 +2,10 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) updateEndpointSettingsToDB25() error {
log.Info().Msg("updating endpoint settings")
migrateLog.Info("- updating endpoint settings")
settings, err := m.settingsService.Settings()
if err != nil {
return err

View File

@@ -3,14 +3,11 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/stacks/stackutils"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/api/internal/stackutils"
)
func (m *Migrator) updateStackResourceControlToDB27() error {
log.Info().Msg("updating stack resource controls")
migrateLog.Info("- updating stack resource controls")
resourceControls, err := m.resourceControlService.ResourceControls()
if err != nil {
return err

View File

@@ -1,11 +1,12 @@
package migrator
import "github.com/rs/zerolog/log"
func (m *Migrator) migrateDBVersionToDB30() error {
log.Info().Msg("updating legacy settings")
migrateLog.Info("- updating legacy settings")
if err := m.MigrateSettingsToDB30(); err != nil {
return err
}
return m.MigrateSettingsToDB30()
return nil
}
// so setting to false and "", is what would happen without this code
@@ -19,6 +20,5 @@ func (m *Migrator) MigrateSettingsToDB30() error {
legacySettings.OAuthSettings.SSO = false
legacySettings.OAuthSettings.LogoutURI = ""
return m.settingsService.UpdateSettings(legacySettings)
}

View File

@@ -2,14 +2,13 @@ package migrator
import (
"fmt"
"log"
"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,8 +38,7 @@ func (m *Migrator) migrateDBVersionToDB32() error {
}
func (m *Migrator) updateRegistriesToDB32() error {
log.Info().Msg("updating registries")
migrateLog.Info("- updating registries")
registries, err := m.registryService.Registries()
if err != nil {
return err
@@ -83,8 +81,7 @@ func (m *Migrator) updateRegistriesToDB32() error {
}
func (m *Migrator) updateDockerhubToDB32() error {
log.Info().Msg("updating dockerhub")
migrateLog.Info("- updating dockerhub")
dockerhub, err := m.dockerhubService.DockerHub()
if err == errors.ErrObjectNotFound {
return nil
@@ -173,8 +170,7 @@ func (m *Migrator) updateDockerhubToDB32() error {
}
func (m *Migrator) updateVolumeResourceControlToDB32() error {
log.Info().Msg("updating resource controls")
migrateLog.Info("- updating resource controls")
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return fmt.Errorf("failed fetching environments: %w", err)
@@ -202,7 +198,7 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
totalSnapshots := len(endpoint.Snapshots)
if totalSnapshots == 0 {
log.Debug().Msg("no snapshot found")
log.Println("[DEBUG] [volume migration] [message: no snapshot found]")
continue
}
@@ -210,46 +206,52 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
endpointDockerID, err := snapshotutils.FetchDockerID(snapshot)
if err != nil {
log.Warn().Err(err).Msg("failed fetching environment docker id")
log.Printf("[WARN] [database,migrator,v31] [message: failed fetching environment docker id] [err: %s]", err)
continue
}
volumesData := snapshot.SnapshotRaw.Volumes
if volumesData.Volumes == nil {
log.Debug().Msg("no volume data found")
continue
if volumesData, done := snapshot.SnapshotRaw.Volumes.(map[string]interface{}); done {
if volumesData["Volumes"] == nil {
log.Println("[DEBUG] [volume migration] [message: no volume data found]")
continue
}
findResourcesToUpdateForDB32(endpointDockerID, volumesData, toUpdate, volumeResourceControls)
}
findResourcesToUpdateForDB32(endpointDockerID, volumesData, toUpdate, volumeResourceControls)
}
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.Debug().Str("resource_id", resourceControl.ResourceID).Msg("legacy resource control has been deleted")
log.Printf("[DEBUG] [volume migration] [message: legacy resource control(%s) has been deleted]", resourceControl.ResourceID)
}
}
return nil
}
func findResourcesToUpdateForDB32(dockerID string, volumesData volume.VolumeListOKBody, toUpdate map[portainer.ResourceControlID]string, volumeResourceControls map[string]*portainer.ResourceControl) {
volumes := volumesData.Volumes
for _, volume := range volumes {
volumeName := volume.Name
createTime := volume.CreatedAt
func findResourcesToUpdateForDB32(dockerID string, volumesData map[string]interface{}, toUpdate map[portainer.ResourceControlID]string, volumeResourceControls map[string]*portainer.ResourceControl) {
volumes := volumesData["Volumes"].([]interface{})
for _, volumeMeta := range volumes {
volume := volumeMeta.(map[string]interface{})
volumeName, nameExist := volume["Name"].(string)
if !nameExist {
continue
}
createTime, createTimeExist := volume["CreatedAt"].(string)
if !createTimeExist {
continue
}
oldResourceID := fmt.Sprintf("%s%s", volumeName, createTime)
resourceControl, ok := volumeResourceControls[oldResourceID]
@@ -261,25 +263,21 @@ func findResourcesToUpdateForDB32(dockerID string, volumesData volume.VolumeList
}
func (m *Migrator) kubeconfigExpiryToDB32() error {
log.Info().Msg("updating kubeconfig expiry")
migrateLog.Info("- 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 {
log.Info().Msg("setting default helm repository URL")
migrateLog.Info("- setting default helm repository URL")
settings, err := m.settingsService.Settings()
if err != nil {
return err
}
settings.HelmRepositoryURL = portainer.DefaultHelmRepositoryURL
return m.settingsService.UpdateSettings(settings)
}

View File

@@ -2,25 +2,24 @@ package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB33() error {
log.Info().Msg("updating settings")
migrateLog.Info("- updating settings")
if err := m.migrateSettingsToDB33(); err != nil {
return err
}
return m.migrateSettingsToDB33()
return nil
}
func (m *Migrator) migrateSettingsToDB33() error {
log.Info().Msg("setting default kubctl shell")
settings, err := m.settingsService.Settings()
if err != nil {
return err
}
log.Info().Msg("setting default kubectl shell image")
migrateLog.Info("- setting default kubectl shell image")
settings.KubectlShellImage = portainer.DefaultKubectlShellImage
return m.settingsService.UpdateSettings(settings)
}

View File

@@ -2,34 +2,33 @@ package migrator
import (
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB34() error {
log.Info().Msg("updating stacks")
migrateLog.Info("- updating stacks")
err := MigrateStackEntryPoint(m.stackService)
if err != nil {
return err
}
return MigrateStackEntryPoint(m.stackService)
return nil
}
// MigrateStackEntryPoint exported for testing
// MigrateStackEntryPoint exported for testing (blah.)
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
}

View File

@@ -3,5 +3,10 @@ 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.
return m.updateDockerhubToDB32()
migrateLog.Info("- updating dockerhub registries")
err := m.updateDockerhubToDB32()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,94 @@
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")
// }

View File

@@ -3,19 +3,18 @@ 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 {
log.Info().Msg("updating user authorizations")
migrateLog.Info("Updating user authorizations")
if err := m.migrateUsersToDB36(); err != nil {
return err
}
return m.migrateUsersToDB36()
return nil
}
func (m *Migrator) migrateUsersToDB36() error {
log.Info().Msg("updating user authorizations")
users, err := m.userService.Users()
if err != nil {
return err

View File

@@ -1,18 +1,17 @@
package migrator
import (
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/rs/zerolog/log"
)
import "github.com/portainer/portainer/api/internal/endpointutils"
func (m *Migrator) migrateDBVersionToDB40() error {
return m.trustCurrentEdgeEndpointsDB40()
if err := m.trustCurrentEdgeEndpointsDB40(); err != nil {
return err
}
return nil
}
func (m *Migrator) trustCurrentEdgeEndpointsDB40() error {
log.Info().Msg("trusting current edge endpoints")
migrateLog.Info("- trusting current edge endpoints")
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err

View File

@@ -1,22 +0,0 @@
package migrator
import (
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB50() error {
return m.migratePasswordLengthSettings()
}
func (m *Migrator) migratePasswordLengthSettings() error {
log.Info().Msg("updating required password length")
s, err := m.settingsService.Settings()
if err != nil {
return errors.Wrap(err, "unable to retrieve settings")
}
s.InternalAuthSettings.RequiredPasswordLength = 12
return m.settingsService.UpdateSettings(s)
}

View File

@@ -1,32 +0,0 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
func (m *Migrator) migrateDBVersionToDB60() error {
return m.addGpuInputFieldDB60()
}
func (m *Migrator) addGpuInputFieldDB60() error {
log.Info().Msg("add gpu input field")
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
if endpoint.Gpus == nil {
endpoint.Gpus = []portainer.Pair{}
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -1,71 +0,0 @@
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
}

View File

@@ -1,36 +0,0 @@
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
}

View File

@@ -1,102 +0,0 @@
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
}
if err := m.updateExistingEndpointsToNotDetectStorageAPIForDB80(); 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) updateExistingEndpointsToNotDetectStorageAPIForDB80() 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.IsServerStorageDetected = 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
}

View File

@@ -1,36 +0,0 @@
package migrator
import (
"github.com/rs/zerolog/log"
portainerDsErrors "github.com/portainer/portainer/api/dataservices/errors"
)
func (m *Migrator) migrateDBVersionToDB81() error {
return m.updateEdgeStackStatusForDB81()
}
func (m *Migrator) updateEdgeStackStatusForDB81() error {
log.Info().Msg("clean up deleted endpoints from edge jobs")
edgeJobs, err := m.edgeJobService.EdgeJobs()
if err != nil {
return err
}
for _, edgeJob := range edgeJobs {
for endpointId := range edgeJob.Endpoints {
_, err := m.endpointService.Endpoint(endpointId)
if err == portainerDsErrors.ErrObjectNotFound {
delete(edgeJob.Endpoints, endpointId)
err = m.edgeJobService.UpdateEdgeJob(edgeJob.ID, &edgeJob)
if err != nil {
return err
}
}
}
}
return nil
}

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