Compare commits

..

26 Commits

Author SHA1 Message Date
Prabhat Khera
cf5bec7f2d ssl dataservice fixed 2023-08-01 18:01:57 +12:00
Prabhat Khera
5ce938123d Fix settings update 2023-06-02 17:21:28 +12:00
Prabhat Khera
8d7fcde4cb fix errors 2023-06-02 15:54:33 +12:00
Prabhat Khera
b5a05a2e12 tidy go mod 2023-06-02 15:29:53 +12:00
Prabhat Khera
5acb8edbd0 add init in test data store 2023-06-02 15:29:12 +12:00
Prabhat Khera
ee32f513e1 add stack dataservice 2023-06-02 15:29:12 +12:00
Matt Hook
e819a6ad74 fix startup/init sequence 2023-06-02 15:29:12 +12:00
Prabhat Khera
7647feb395 revert some code 2023-06-02 15:29:12 +12:00
Prabhat Khera
d60b187d6c fix team create 2023-06-02 15:28:53 +12:00
Prabhat Khera
1e9250eccf some more fixes 2023-06-02 15:28:53 +12:00
Matt Hook
1f38623b60 attempt to capture errors and init the db in a way that's compatible with the rest of our codebase 2023-06-02 15:28:32 +12:00
Prabhat Khera
4205d871de fix ssl settings issue 2023-06-02 15:28:32 +12:00
Matt Hook
357cb66fbf add sslsettings 2023-06-02 15:28:32 +12:00
Prabhat Khera
3396f1ca91 fixed structs and auto migrate is working 2023-06-02 15:28:32 +12:00
Prabhat Khera
4ff7135b73 fixed 2023-06-02 15:28:32 +12:00
Prabhat Khera
479842d5ef fix create endpoint 2023-06-02 15:28:32 +12:00
Prabhat Khera
6ab6ffa68e fixed issues with serielizer gorm tag 2023-06-02 15:28:32 +12:00
Prabhat Khera
e5af8cb58a fix opening a new store 2023-06-02 15:28:32 +12:00
Prabhat Khera
cc1b8c05d4 migrate user dataservice 2023-06-02 15:28:32 +12:00
Prabhat Khera
74615bff91 fix libhelm 2023-06-02 15:28:32 +12:00
Prabhat Khera
22d960e51b WIP: models 2023-06-02 15:28:32 +12:00
Prabhat Khera
8fa1875891 version and settings bucket done 2023-06-02 15:27:47 +12:00
Prabhat Khera
3d313be013 WIP: version bucket 2023-06-02 15:27:47 +12:00
Prabhat Khera
52142c3151 WIP: commented error code and set CGO_ENABLED to 1 2023-06-02 15:27:47 +12:00
Prabhat Khera
8fd67d8320 WIP 2023-06-02 15:27:22 +12:00
Prabhat Khera
525efdc035 WIP 2023-06-02 15:27:22 +12:00
994 changed files with 22243 additions and 31022 deletions

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Portainer Business Edition - Get 3 nodes free
url: https://www.portainer.io/take-3
about: Portainer Business Edition has more features, more support and you can now get 3 nodes free for as long as you want.
- name: Portainer Business Edition - Get 5 nodes free
url: https://portainer.io/pricing/take5
about: Portainer Business Edition has more features, more support and you can now get 5 nodes free for as long as you want.

View File

@@ -41,6 +41,6 @@ jobs:
- name: GolangCI-Lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.54.1
version: latest
working-directory: api
args: --timeout=10m -c .golangci.yaml

View File

@@ -14,12 +14,16 @@ jobs:
- name: Run tests
run: yarn jest --maxWorkers=2
test-server:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: 1.19.5
- name: Run tests
run: make test-server
# test-server:
# runs-on: ubuntu-latest
# env:
# GOPRIVATE: "github.com/portainer"
# steps:
# - uses: actions/checkout@v3
# - uses: actions/setup-go@v3
# with:
# go-version: '1.18'
# - name: Run tests
# run: |
# cd api
# go test ./...

View File

@@ -21,7 +21,7 @@ jobs:
run: cd ./api && go get -t -v -d ./...
- uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '14'
cache: 'yarn'
- run: yarn --frozen-lockfile

2
.gitignore vendored
View File

@@ -11,7 +11,7 @@ storybook-static
*.DS_Store
.eslintcache
__debug_bin*
__debug_bin
api/docs
.idea

58
.storybook/main.js Normal file
View File

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

View File

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

View File

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

View File

@@ -10,17 +10,15 @@ linters:
- exportloopref
linters-settings:
depguard:
rules:
main:
deny:
- pkg: 'github.com/sirupsen/logrus'
desc: 'logging is allowed only by github.com/rs/zerolog'
- pkg: 'golang.org/x/exp'
desc: 'exp is not allowed'
files:
- '!**/*_test.go'
- '!**/base.go'
- '!**/base_tx.go'
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'
# errorlint is causing a typecheck error for some reason. The go compiler will report these
# anyway, so ignore them from the linter

View File

@@ -1,6 +1,9 @@
package apikey
import (
"crypto/rand"
"io"
portainer "github.com/portainer/portainer/api"
)
@@ -15,3 +18,13 @@ type APIKeyService interface {
DeleteAPIKey(apiKeyID portainer.APIKeyID) error
InvalidateUserKeyCache(userId portainer.UserID) bool
}
// generateRandomKey generates a random key of specified length
// source: https://github.com/gorilla/securecookie/blob/master/securecookie.go#L515
func generateRandomKey(length int) []byte {
k := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, k); err != nil {
return nil
}
return k
}

View File

@@ -3,7 +3,6 @@ package apikey
import (
"testing"
"github.com/portainer/portainer/api/internal/securecookie"
"github.com/stretchr/testify/assert"
)
@@ -34,7 +33,7 @@ func Test_generateRandomKey(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := securecookie.GenerateRandomKey(tt.wantLenth)
got := generateRandomKey(tt.wantLenth)
is.Equal(tt.wantLenth, len(got))
})
}
@@ -42,7 +41,7 @@ func Test_generateRandomKey(t *testing.T) {
t.Run("Generated keys are unique", func(t *testing.T) {
keys := make(map[string]bool)
for i := 0; i < 100; i++ {
key := securecookie.GenerateRandomKey(8)
key := generateRandomKey(8)
_, ok := keys[string(key)]
is.False(ok)
keys[string(key)] = true

View File

@@ -8,7 +8,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/securecookie"
"github.com/pkg/errors"
)
@@ -40,7 +39,7 @@ func (a *apiKeyService) HashRaw(rawKey string) []byte {
// GenerateApiKey generates a raw API key for a user (for one-time display).
// The generated API key is stored in the cache and database.
func (a *apiKeyService) GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error) {
randKey := securecookie.GenerateRandomKey(32)
randKey := generateRandomKey(32)
encodedRawAPIKey := base64.StdEncoding.EncodeToString(randKey)
prefixedAPIKey := portainerAPIKeyPrefix + encodedRawAPIKey
@@ -54,7 +53,7 @@ func (a *apiKeyService) GenerateApiKey(user portainer.User, description string)
Digest: hashDigest,
}
err := a.apiKeyRepository.Create(apiKey)
err := a.apiKeyRepository.CreateAPIKey(apiKey)
if err != nil {
return "", nil, errors.Wrap(err, "Unable to create API key")
}
@@ -67,7 +66,7 @@ func (a *apiKeyService) GenerateApiKey(user portainer.User, description string)
// GetAPIKey returns an API key by its ID.
func (a *apiKeyService) GetAPIKey(apiKeyID portainer.APIKeyID) (*portainer.APIKey, error) {
return a.apiKeyRepository.Read(apiKeyID)
return a.apiKeyRepository.GetAPIKey(apiKeyID)
}
// GetAPIKeys returns all the API keys associated to a user.
@@ -89,7 +88,7 @@ func (a *apiKeyService) GetDigestUserAndKey(digest []byte) (portainer.User, port
return portainer.User{}, portainer.APIKey{}, errors.Wrap(err, "Unable to retrieve API key")
}
user, err := a.userRepository.Read(apiKey.UserID)
user, err := a.userRepository.User(apiKey.UserID)
if err != nil {
return portainer.User{}, portainer.APIKey{}, errors.Wrap(err, "Unable to retrieve digest user")
}
@@ -107,20 +106,20 @@ func (a *apiKeyService) UpdateAPIKey(apiKey *portainer.APIKey) error {
return errors.Wrap(err, "Unable to retrieve API key")
}
a.cache.Set(apiKey.Digest, user, *apiKey)
return a.apiKeyRepository.Update(apiKey.ID, apiKey)
return a.apiKeyRepository.UpdateAPIKey(apiKey)
}
// DeleteAPIKey deletes an API key and removes the digest/api-key entry from the cache.
func (a *apiKeyService) DeleteAPIKey(apiKeyID portainer.APIKeyID) error {
// get api-key digest to remove from cache
apiKey, err := a.apiKeyRepository.Read(apiKeyID)
apiKey, err := a.apiKeyRepository.GetAPIKey(apiKeyID)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Unable to retrieve API key: %d", apiKeyID))
}
// delete the user/api-key from cache
a.cache.Delete(apiKey.Digest)
return a.apiKeyRepository.Delete(apiKeyID)
return a.apiKeyRepository.DeleteAPIKey(apiKeyID)
}
func (a *apiKeyService) InvalidateUserKeyCache(userId portainer.UserID) bool {

View File

@@ -30,7 +30,6 @@ var filesToBackup = []string{
"portainer.key",
"portainer.pub",
"tls",
"chisel",
}
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
@@ -86,9 +85,9 @@ func backupDb(backupDirPath string, datastore dataservices.DataStore) error {
if err != nil {
return err
}
if err = datastore.BackupTo(backupWriter); err != nil {
return err
}
// if err = datastore.BackupTo(backupWriter); err != nil {
// return err
// }
return backupWriter.Close()
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/pkg/errors"
"github.com/portainer/portainer/api/archive"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/database/boltdb"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/offlinegate"
@@ -98,16 +97,17 @@ func restoreFiles(srcDir string, destinationDir string) error {
// TODO: This is very boltdb module specific once again due to the filename. Move to bolt module? Refactor for another day
// Prevent the possibility of having both databases. Remove any default new instance
os.Remove(filepath.Join(destinationDir, boltdb.DatabaseFileName))
os.Remove(filepath.Join(destinationDir, boltdb.EncryptedDatabaseFileName))
// os.Remove(filepath.Join(destinationDir, boltdb.DatabaseFileName))
// os.Remove(filepath.Join(destinationDir, boltdb.EncryptedDatabaseFileName))
// Now copy the database. It'll be either portainer.db or portainer.edb
// // Now copy the database. It'll be either portainer.db or portainer.edb
// Note: CopyPath does not return an error if the source file doesn't exist
err := filesystem.CopyPath(filepath.Join(srcDir, boltdb.EncryptedDatabaseFileName), destinationDir)
if err != nil {
return err
}
// // Note: CopyPath does not return an error if the source file doesn't exist
// err := filesystem.CopyPath(filepath.Join(srcDir, boltdb.EncryptedDatabaseFileName), destinationDir)
// if err != nil {
// return err
// }
return filesystem.CopyPath(filepath.Join(srcDir, boltdb.DatabaseFileName), destinationDir)
// return filesystem.CopyPath(filepath.Join(srcDir, boltdb.DatabaseFileName), destinationDir)
return nil
}

View File

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

View File

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

View File

@@ -18,7 +18,6 @@ func (service *Service) AddEdgeJob(endpoint *portainer.Endpoint, edgeJob *portai
for idx, existingJob := range tunnel.Jobs {
if existingJob.ID == edgeJob.ID {
existingJobIndex = idx
break
}
}

View File

@@ -12,8 +12,8 @@ import (
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/proxy"
"github.com/dchest/uniuri"
chserver "github.com/jpillora/chisel/server"
"github.com/jpillora/chisel/share/ccrypto"
"github.com/rs/zerolog/log"
)
@@ -36,16 +36,14 @@ type Service struct {
shutdownCtx context.Context
ProxyManager *proxy.Manager
mu sync.Mutex
fileService portainer.FileService
}
// NewService returns a pointer to a new instance of Service
func NewService(dataStore dataservices.DataStore, shutdownCtx context.Context, fileService portainer.FileService) *Service {
func NewService(dataStore dataservices.DataStore, shutdownCtx context.Context) *Service {
return &Service{
tunnelDetailsMap: make(map[portainer.EndpointID]*portainer.TunnelDetails),
dataStore: dataStore,
shutdownCtx: shutdownCtx,
fileService: fileService,
}
}
@@ -75,11 +73,10 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("max_alive_minutes", maxAlive.Minutes()).
Msg("KeepTunnelAlive: start")
Msg("start")
maxAliveTicker := time.NewTicker(maxAlive)
defer maxAliveTicker.Stop()
pingTicker := time.NewTicker(tunnelCleanupInterval)
defer pingTicker.Stop()
@@ -92,13 +89,13 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("KeepTunnelAlive: ping agent")
Msg("ping agent")
}
case <-maxAliveTicker.C:
log.Debug().
Int("endpoint_id", int(endpointID)).
Float64("timeout_minutes", maxAlive.Minutes()).
Msg("KeepTunnelAlive: tunnel keep alive timeout")
Msg("tunnel keep alive timeout")
return
case <-ctx.Done():
@@ -106,7 +103,7 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
log.Debug().
Int("endpoint_id", int(endpointID)).
Err(err).
Msg("KeepTunnelAlive: tunnel stop")
Msg("tunnel stop")
return
}
@@ -120,15 +117,14 @@ func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx con
// It starts the tunnel status verification process in the background.
// The snapshotter is used in the tunnel status verification process.
func (service *Service) StartTunnelServer(addr, port string, snapshotService portainer.SnapshotService) error {
privateKeyFile, err := service.retrievePrivateKeyFile()
keySeed, err := service.retrievePrivateKeySeed()
if err != nil {
return err
}
config := &chserver.Config{
Reverse: true,
PrivateKeyFile: privateKeyFile,
Reverse: true,
KeySeed: keySeed,
}
chiselServer, err := chserver.NewServer(config)
@@ -164,41 +160,26 @@ func (service *Service) StopTunnelServer() error {
return service.chiselServer.Close()
}
func (service *Service) retrievePrivateKeyFile() (string, error) {
privateKeyFile := service.fileService.GetDefaultChiselPrivateKeyPath()
func (service *Service) retrievePrivateKeySeed() (string, error) {
var serverInfo *portainer.TunnelServerInfo
exist, _ := service.fileService.FileExists(privateKeyFile)
if !exist {
log.Debug().
Str("private-key", privateKeyFile).
Msg("Chisel private key file does not exist")
serverInfo, err := service.dataStore.TunnelServer().Info()
if service.dataStore.IsErrObjectNotFound(err) {
keySeed := uniuri.NewLen(16)
privateKey, err := ccrypto.GenerateKey("")
if err != nil {
log.Error().
Err(err).
Msg("Failed to generate chisel private key")
return "", err
serverInfo = &portainer.TunnelServerInfo{
PrivateKeySeed: keySeed,
}
err = service.fileService.StoreChiselPrivateKey(privateKey)
err := service.dataStore.TunnelServer().UpdateInfo(serverInfo)
if err != nil {
log.Error().
Err(err).
Msg("Failed to save Chisel private key to disk")
return "", err
} else {
log.Info().
Str("private-key", privateKeyFile).
Msg("Generated a new Chisel private key file")
}
} else {
log.Info().
Str("private-key", privateKeyFile).
Msg("Found Chisel private key file on disk")
} else if err != nil {
return "", err
}
return privateKeyFile, nil
return serverInfo.PrivateKeySeed, nil
}
func (service *Service) startTunnelVerificationLoop() {
@@ -290,7 +271,14 @@ func (service *Service) snapshotEnvironment(endpointID portainer.EndpointID, tun
return err
}
endpoint.URL = fmt.Sprintf("tcp://127.0.0.1:%d", tunnelPort)
endpointURL := endpoint.URL
return service.snapshotService.SnapshotEndpoint(endpoint)
endpoint.URL = fmt.Sprintf("tcp://127.0.0.1:%d", tunnelPort)
err = service.snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
return err
}
endpoint.URL = endpointURL
return service.dataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint)
}

View File

@@ -126,10 +126,7 @@ func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
credentials := tunnel.Credentials
if credentials != "" {
tunnel.Credentials = ""
if service.chiselServer != nil {
service.chiselServer.DeleteUser(strings.Split(credentials, ":")[0])
}
service.chiselServer.DeleteUser(strings.Split(credentials, ":")[0])
}
service.ProxyManager.DeleteEndpointProxy(endpointID)
@@ -164,12 +161,9 @@ func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointI
username, password := generateRandomCredentials()
authorizedRemote := fmt.Sprintf("^R:0.0.0.0:%d$", tunnel.Port)
if service.chiselServer != nil {
err = service.chiselServer.AddUser(username, password, authorizedRemote)
if err != nil {
return err
}
err = service.chiselServer.AddUser(username, password, authorizedRemote)
if err != nil {
return err
}
credentials, err := encryptCredentials(username, password, endpoint.EdgeID)

View File

@@ -49,7 +49,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
Rollback: kingpin.Flag("rollback", "Rollback the database to the previous backup").Bool(),
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").String(),
AdminPassword: kingpin.Flag("admin-password", "Set admin password with provided hash").String(),
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),

View File

@@ -9,7 +9,7 @@ import (
// Confirm starts a rollback db cli application
func Confirm(message string) (bool, error) {
fmt.Printf("%s [y/N] ", message)
fmt.Printf("%s [y/N]", message)
reader := bufio.NewReader(os.Stdin)

View File

@@ -9,7 +9,7 @@ import (
"gopkg.in/alecthomas/kingpin.v2"
)
type pairList []portainer.Pair
type pairList portainer.MultiPair
// Set implementation for a list of portainer.Pair
func (l *pairList) Set(value string) error {
@@ -19,7 +19,7 @@ func (l *pairList) Set(value string) error {
}
p := new(portainer.Pair)
p.Name = parts[0]
p.Value = parts[1]
p.Value1 = parts[1]
*l = append(*l, *p)
return nil
}
@@ -34,8 +34,8 @@ func (l *pairList) IsCumulative() bool {
return true
}
func pairs(s kingpin.Settings) (target *[]portainer.Pair) {
target = new([]portainer.Pair)
func pairs(s kingpin.Settings) (target *portainer.MultiPair) {
target = new(portainer.MultiPair)
s.SetValue((*pairList)(target))
return
}

View File

@@ -8,7 +8,7 @@ import (
"gopkg.in/alecthomas/kingpin.v2"
)
type pairListBool []portainer.Pair
type pairListBool portainer.MultiPair
// Set implementation for a list of portainer.Pair
func (l *pairListBool) Set(value string) error {
@@ -18,10 +18,10 @@ func (l *pairListBool) Set(value string) error {
parts := strings.SplitN(value, "=", 2)
if len(parts) != 2 {
p.Name = parts[0]
p.Value = "true"
p.Value1 = "true"
} else {
p.Name = parts[0]
p.Value = parts[1]
p.Value1 = parts[1]
}
*l = append(*l, *p)
@@ -38,8 +38,8 @@ func (l *pairListBool) IsCumulative() bool {
return true
}
func BoolPairs(s kingpin.Settings) (target *[]portainer.Pair) {
target = new([]portainer.Pair)
func BoolPairs(s kingpin.Settings) (target *portainer.MultiPair) {
target = new(portainer.MultiPair)
s.SetValue((*pairListBool)(target))
return
}

View File

@@ -9,6 +9,8 @@ import (
"strings"
"time"
libstack "github.com/portainer/docker-compose-wrapper"
"github.com/portainer/docker-compose-wrapper/compose"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/build"
@@ -16,11 +18,9 @@ import (
"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/datastore/migrator"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/docker"
dockerclient "github.com/portainer/portainer/api/docker/client"
@@ -29,6 +29,7 @@ import (
"github.com/portainer/portainer/api/git"
"github.com/portainer/portainer/api/hostmanagement/openamt"
"github.com/portainer/portainer/api/http"
"github.com/portainer/portainer/api/http/client"
"github.com/portainer/portainer/api/http/proxy"
kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/internal/authorization"
@@ -47,8 +48,6 @@ import (
"github.com/portainer/portainer/api/stacks/deployments"
"github.com/portainer/portainer/pkg/featureflags"
"github.com/portainer/portainer/pkg/libhelm"
"github.com/portainer/portainer/pkg/libstack"
"github.com/portainer/portainer/pkg/libstack/compose"
"github.com/gofrs/uuid"
"github.com/rs/zerolog/log"
@@ -79,19 +78,11 @@ func initFileService(dataStorePath string) portainer.FileService {
}
func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService portainer.FileService, shutdownCtx context.Context) dataservices.DataStore {
connection, err := database.NewDatabase("boltdb", *flags.Data, secretKey)
connection, err := database.NewDatabase("sqlite", *flags.Data, secretKey)
if err != nil {
log.Fatal().Err(err).Msg("failed creating database connection")
}
if bconn, ok := connection.(*boltdb.DbConnection); ok {
bconn.MaxBatchSize = *flags.MaxBatchSize
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")
}
store := datastore.NewStore(*flags.Data, fileService, connection)
isNew, err := store.Open()
if err != nil {
@@ -120,26 +111,30 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
log.Fatal().Err(err).Msg("failed generating instance id")
}
migratorInstance := migrator.NewMigrator(&migrator.MigratorParameters{})
migratorCount := migratorInstance.GetMigratorCountOfCurrentAPIVersion()
// from MigrateData
v := models.Version{
SchemaVersion: portainer.APIVersion,
Edition: int(portainer.PortainerCE),
InstanceID: instanceId.String(),
MigratorCount: migratorCount,
v := map[string]interface{}{
models.SchemaVersionKey: portainer.APIVersion,
models.EditionKey: int(portainer.PortainerCE),
models.InstanceKey: instanceId.String(),
}
err = store.VersionService.UpdateAll(v)
if err != nil {
log.Fatal().Err(err).Msg("failed inserting version bucket")
}
store.VersionService.UpdateVersion(&v)
err = updateSettingsFromFlags(store, flags)
if err != nil {
log.Fatal().Err(err).Msg("failed updating settings from flags")
}
} else {
err = store.MigrateData()
// err = store.MigrateData()
// if err != nil {
// log.Fatal().Err(err).Msg("failed migration")
// }
err := store.PostInit()
if err != nil {
log.Fatal().Err(err).Msg("failed migration")
log.Fatal().Err(err).Msg("postinit failed")
}
}
@@ -157,17 +152,7 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
return store
}
// checkDBSchemaServerVersionMatch checks if the server version matches the db scehma version
func checkDBSchemaServerVersionMatch(dbStore dataservices.DataStore, serverVersion string, serverEdition int) bool {
v, err := dbStore.Version().Version()
if err != nil {
return false
}
return v.SchemaVersion == serverVersion && v.Edition == serverEdition
}
func initComposeStackManager(composeDeployer libstack.Deployer, proxyManager *proxy.Manager) portainer.ComposeStackManager {
func initComposeStackManager(composeDeployer libstack.Deployer, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
composeWrapper, err := exec.NewComposeStackManager(composeDeployer, proxyManager)
if err != nil {
log.Fatal().Err(err).Msg("failed creating compose manager")
@@ -362,6 +347,143 @@ func initKeyPair(fileService portainer.FileService, signatureService portainer.D
return generateAndStoreKeyPair(fileService, signatureService)
}
func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
tlsConfiguration := portainer.TLSConfiguration{
TLS: *flags.TLS,
TLSSkipVerify: *flags.TLSSkipVerify,
}
if *flags.TLS {
tlsConfiguration.TLSCACertPath = *flags.TLSCacert
tlsConfiguration.TLSCertPath = *flags.TLSCert
tlsConfiguration.TLSKeyPath = *flags.TLSKey
} else if !*flags.TLS && *flags.TLSSkipVerify {
tlsConfiguration.TLS = true
}
endpoint := &portainer.Endpoint{
Name: "primary",
URL: *flags.EndpointURL,
GroupID: portainer.EndpointGroupID(1),
Type: portainer.DockerEnvironment,
TLSConfig: tlsConfiguration,
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
TagIDs: []portainer.TagID{},
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.DockerSnapshot{},
Kubernetes: portainer.KubernetesDefault(),
SecuritySettings: portainer.EndpointSecuritySettings{
AllowVolumeBrowserForRegularUsers: false,
EnableHostManagementFeatures: false,
AllowSysctlSettingForRegularUsers: true,
AllowBindMountsForRegularUsers: true,
AllowPrivilegedModeForRegularUsers: true,
AllowHostNamespaceForRegularUsers: true,
AllowContainerCapabilitiesForRegularUsers: true,
AllowDeviceMappingForRegularUsers: true,
AllowStackManagementForRegularUsers: true,
},
}
if strings.HasPrefix(endpoint.URL, "tcp://") {
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(tlsConfiguration.TLSCACertPath, tlsConfiguration.TLSCertPath, tlsConfiguration.TLSKeyPath, tlsConfiguration.TLSSkipVerify)
if err != nil {
return err
}
agentOnDockerEnvironment, err := client.ExecutePingOperation(endpoint.URL, tlsConfig)
if err != nil {
return err
}
if agentOnDockerEnvironment {
endpoint.Type = portainer.AgentOnDockerEnvironment
}
}
err := snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
log.Error().
Str("endpoint", endpoint.Name).
Str("URL", endpoint.URL).
Err(err).
Msg("environment snapshot error")
}
return dataStore.Endpoint().Create(endpoint)
}
func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
if strings.HasPrefix(endpointURL, "tcp://") {
_, err := client.ExecutePingOperation(endpointURL, nil)
if err != nil {
return err
}
}
endpoint := &portainer.Endpoint{
Name: "primary",
URL: endpointURL,
GroupID: portainer.EndpointGroupID(1),
Type: portainer.DockerEnvironment,
TLSConfig: portainer.TLSConfiguration{},
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
TagIDs: []portainer.TagID{},
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.DockerSnapshot{},
Kubernetes: portainer.KubernetesDefault(),
SecuritySettings: portainer.EndpointSecuritySettings{
AllowVolumeBrowserForRegularUsers: false,
EnableHostManagementFeatures: false,
AllowSysctlSettingForRegularUsers: true,
AllowBindMountsForRegularUsers: true,
AllowPrivilegedModeForRegularUsers: true,
AllowHostNamespaceForRegularUsers: true,
AllowContainerCapabilitiesForRegularUsers: true,
AllowDeviceMappingForRegularUsers: true,
AllowStackManagementForRegularUsers: true,
},
}
err := snapshotService.SnapshotEndpoint(endpoint)
if err != nil {
log.Error().
Str("endpoint", endpoint.Name).
Str("URL", endpoint.URL).Err(err).
Msg("environment snapshot error")
}
return dataStore.Endpoint().Create(endpoint)
}
func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
if *flags.EndpointURL == "" {
return nil
}
endpoints, err := dataStore.Endpoint().Endpoints()
if err != nil {
return err
}
if len(endpoints) > 0 {
log.Info().Msg("instance already has defined environments, skipping the environment defined via CLI")
return nil
}
if *flags.TLS || *flags.TLSSkipVerify {
return createTLSSecuredEndpoint(flags, dataStore, snapshotService)
}
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
}
func loadEncryptionSecretKey(keyfilename string) []byte {
content, err := os.ReadFile(path.Join("/run/secrets", keyfilename))
if err != nil {
@@ -398,11 +520,6 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatal().Err(err).Msg("")
}
// check if the db schema version matches with server version
if !checkDBSchemaServerVersionMatch(dataStore, portainer.APIVersion, int(portainer.Edition)) {
log.Fatal().Msg("The database schema version does not align with the server version. Please consider reverting to the previous server version or addressing the database migration issue.")
}
instanceID, err := dataStore.Version().InstanceID()
if err != nil {
log.Fatal().Err(err).Msg("failed getting instance id")
@@ -449,7 +566,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatal().Err(err).Msg("failed initializing key pair")
}
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx, fileService)
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
@@ -478,7 +595,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
log.Fatal().Err(err).Msg("failed initializing compose deployer")
}
composeStackManager := initComposeStackManager(composeDeployer, proxyManager)
composeStackManager := initComposeStackManager(composeDeployer, reverseTunnelService, proxyManager)
swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, digitalSignatureService, fileService, reverseTunnelService, dataStore)
if err != nil {

View File

@@ -1,8 +1,6 @@
package portainer
import (
"io"
)
import "gorm.io/gorm"
type ReadTransaction interface {
GetObject(bucketName string, key []byte, object interface{}) error
@@ -25,19 +23,15 @@ type Transaction interface {
}
type Connection interface {
Transaction
Open() error
Close() error
Init() error
GetDB() *gorm.DB
UpdateTx(fn func(Transaction) error) error
ViewTx(fn func(Transaction) error) error
// write the db contents to filename as json (the schema needs defining)
ExportRaw(filename string) error
GetByID(ID int, obj interface{}) error
DeleteByID(ID int, obj interface{}) error
// TODO: this one is very database specific atm
BackupTo(w io.Writer) error
GetDatabaseFileName() string
GetDatabaseFilePath() string
GetStorePath() string
@@ -45,10 +39,4 @@ type Connection interface {
IsEncryptedStore() bool
NeedsEncryptionMigration() (bool, error)
SetEncrypted(encrypted bool)
BackupMetadata() (map[string]interface{}, error)
RestoreMetadata(s map[string]interface{}) error
UpdateObjectFunc(bucketName string, key []byte, object any, updateFn func()) error
ConvertToKey(v int) []byte
}

View File

@@ -28,7 +28,10 @@ func (tx *DbTransaction) GetObject(bucketName string, key []byte, object interfa
return fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
}
return tx.conn.UnmarshalObjectWithJsoniter(value, object)
data := make([]byte, len(value))
copy(data, value)
return tx.conn.UnmarshalObjectWithJsoniter(data, object)
}
func (tx *DbTransaction) UpdateObject(bucketName string, key []byte, object interface{}) error {
@@ -46,9 +49,7 @@ func (tx *DbTransaction) DeleteObject(bucketName string, key []byte) error {
return bucket.Delete(key)
}
func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj interface{}, matchingFn func(o interface{}) (id int, ok bool)) error {
var ids []int
func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj interface{}, matching func(o interface{}) (id int, ok bool)) error {
bucket := tx.tx.Bucket([]byte(bucketName))
cursor := bucket.Cursor()
@@ -58,14 +59,11 @@ func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj interface{}, ma
return err
}
if id, ok := matchingFn(obj); ok {
ids = append(ids, id)
}
}
for _, id := range ids {
if err := bucket.Delete(tx.conn.ConvertToKey(id)); err != nil {
return err
if id, ok := matching(obj); ok {
err := bucket.Delete(tx.conn.ConvertToKey(id))
if err != nil {
return err
}
}
}
@@ -117,33 +115,45 @@ func (tx *DbTransaction) CreateObjectWithStringId(bucketName string, id []byte,
return bucket.Put(id, data)
}
func (tx *DbTransaction) GetAll(bucketName string, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
func (tx *DbTransaction) GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
bucket := tx.tx.Bucket([]byte(bucketName))
return bucket.ForEach(func(k []byte, v []byte) error {
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
err := tx.conn.UnmarshalObject(v, obj)
if err == nil {
obj, err = appendFn(obj)
if err != nil {
return err
}
return err
})
obj, err = append(obj)
if err != nil {
return err
}
}
return nil
}
func (tx *DbTransaction) GetAllWithJsoniter(bucketName string, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
func (tx *DbTransaction) GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
bucket := tx.tx.Bucket([]byte(bucketName))
return bucket.ForEach(func(k []byte, v []byte) error {
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
err := tx.conn.UnmarshalObjectWithJsoniter(v, obj)
if err == nil {
obj, err = appendFn(obj)
if err != nil {
return err
}
return err
})
obj, err = append(obj)
if err != nil {
return err
}
}
return nil
}
func (tx *DbTransaction) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
func (tx *DbTransaction) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error {
cursor := tx.tx.Bucket([]byte(bucketName)).Cursor()
for k, v := cursor.Seek(keyPrefix); k != nil && bytes.HasPrefix(k, keyPrefix); k, v = cursor.Next() {
@@ -152,7 +162,7 @@ func (tx *DbTransaction) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte
return err
}
obj, err = appendFn(obj)
obj, err = append(obj)
if err != nil {
return err
}

View File

@@ -1,112 +0,0 @@
package boltdb
import (
"encoding/json"
"time"
"github.com/rs/zerolog/log"
bolt "go.etcd.io/bbolt"
)
func backupMetadata(connection *bolt.DB) (map[string]interface{}, error) {
buckets := map[string]interface{}{}
err := connection.View(func(tx *bolt.Tx) error {
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
bucketName := string(name)
seqId := bucket.Sequence()
buckets[bucketName] = int(seqId)
return nil
})
return err
})
return buckets, err
}
// 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.
// 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")
connection, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second, ReadOnly: true})
if err != nil {
return []byte("{}"), err
}
defer connection.Close()
backup := make(map[string]interface{})
if metadata {
meta, err := backupMetadata(connection)
if err != nil {
log.Error().Err(err).Msg("failed exporting metadata")
}
backup["__metadata"] = meta
}
err = connection.View(func(tx *bolt.Tx) error {
err = tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
bucketName := string(name)
var list []interface{}
version := make(map[string]string)
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
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")
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" ||
bucketName == "tunnel_server" {
backup[bucketName] = nil
if len(list) > 0 {
backup[bucketName] = list[0]
}
return nil
}
backup[bucketName] = list
return nil
}
return nil
})
return err
})
if err != nil {
return []byte("{}"), err
}
return json.MarshalIndent(backup, "", " ")
}

View File

@@ -1,133 +0,0 @@
package boltdb
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"fmt"
"io"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
)
var errEncryptedStringTooShort = fmt.Errorf("encrypted string too short")
// MarshalObject encodes an object to binary format
func (connection *DbConnection) MarshalObject(object interface{}) (data []byte, err error) {
// Special case for the VERSION bucket. Here we're not using json
if v, ok := object.(string); ok {
data = []byte(v)
} else {
data, err = json.Marshal(object)
if err != nil {
return data, err
}
}
if connection.getEncryptionKey() == nil {
return data, nil
}
return encrypt(data, connection.getEncryptionKey())
}
// UnmarshalObject decodes an object from binary data
func (connection *DbConnection) UnmarshalObject(data []byte, object interface{}) error {
var err error
if connection.getEncryptionKey() != nil {
data, err = decrypt(data, connection.getEncryptionKey())
if err != nil {
return errors.Wrap(err, "Failed decrypting object")
}
}
e := json.Unmarshal(data, object)
if e != nil {
// Special case for the VERSION bucket. Here we're not using json
// So we need to return it as a string
s, ok := object.(*string)
if !ok {
return errors.Wrap(err, e.Error())
}
*s = string(data)
}
return err
}
// UnmarshalObjectWithJsoniter decodes an object from binary data
// using the jsoniter library. It is mainly used to accelerate environment(endpoint)
// decoding at the moment.
func (connection *DbConnection) UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
if connection.getEncryptionKey() != nil {
var err error
data, err = decrypt(data, connection.getEncryptionKey())
if err != nil {
return err
}
}
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary
err := jsoni.Unmarshal(data, &object)
if err != nil {
if s, ok := object.(*string); ok {
*s = string(data)
return nil
}
return err
}
return nil
}
// mmm, don't have a KMS .... aes GCM seems the most likely from
// https://gist.github.com/atoponce/07d8d4c833873be2f68c34f9afc5a78a#symmetric-encryption
func encrypt(plaintext []byte, passphrase []byte) (encrypted []byte, err error) {
block, _ := aes.NewCipher(passphrase)
gcm, err := cipher.NewGCM(block)
if err != nil {
return encrypted, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return encrypted, err
}
ciphertextByte := gcm.Seal(
nonce,
nonce,
plaintext,
nil)
return ciphertextByte, nil
}
func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err error) {
if string(encrypted) == "false" {
return []byte("false"), nil
}
block, err := aes.NewCipher(passphrase)
if err != nil {
return encrypted, errors.Wrap(err, "Error creating cypher block")
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return encrypted, errors.Wrap(err, "Error creating GCM")
}
nonceSize := gcm.NonceSize()
if len(encrypted) < nonceSize {
return encrypted, errEncryptedStringTooShort
}
nonce, ciphertextByteClean := encrypted[:nonceSize], encrypted[nonceSize:]
plaintextByte, err = gcm.Open(
nil,
nonce,
ciphertextByteClean,
nil)
if err != nil {
return encrypted, errors.Wrap(err, "Error decrypting text")
}
return plaintextByte, err
}

View File

@@ -1,177 +0,0 @@
package boltdb
import (
"crypto/sha256"
"fmt"
"testing"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
)
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}`
passphrase = "my secret key"
)
func secretToEncryptionKey(passphrase string) []byte {
hash := sha256.Sum256([]byte(passphrase))
return hash[:]
}
func Test_MarshalObjectUnencrypted(t *testing.T) {
is := assert.New(t)
uuid := uuid.Must(uuid.NewV4())
tests := []struct {
object interface{}
expected string
}{
{
object: nil,
expected: `null`,
},
{
object: true,
expected: `true`,
},
{
object: false,
expected: `false`,
},
{
object: 123,
expected: `123`,
},
{
object: "456",
expected: "456",
},
{
object: uuid,
expected: "\"" + uuid.String() + "\"",
},
{
object: uuid.String(),
expected: uuid.String(),
},
{
object: map[string]interface{}{"key": "value"},
expected: `{"key":"value"}`,
},
{
object: []bool{true, false},
expected: `[true,false]`,
},
{
object: []int{1, 2, 3},
expected: `[1,2,3]`,
},
{
object: []string{"1", "2", "3"},
expected: `["1","2","3"]`,
},
{
object: []map[string]interface{}{{"key1": "value1"}, {"key2": "value2"}},
expected: `[{"key1":"value1"},{"key2":"value2"}]`,
},
{
object: []interface{}{1, "2", false, map[string]interface{}{"key1": "value1"}},
expected: `[1,"2",false,{"key1":"value1"}]`,
},
}
conn := DbConnection{}
for _, test := range tests {
t.Run(fmt.Sprintf("%s -> %s", test.object, test.expected), func(t *testing.T) {
data, err := conn.MarshalObject(test.object)
is.NoError(err)
is.Equal(test.expected, string(data))
})
}
}
func Test_UnMarshalObjectUnencrypted(t *testing.T) {
is := assert.New(t)
// Based on actual data entering and what we expect out of the function
tests := []struct {
object []byte
expected string
}{
{
object: []byte(""),
expected: "",
},
{
object: []byte("35"),
expected: "35",
},
{
// An unmarshalled byte string should return the same without error
object: []byte("9ca4a1dd-a439-4593-b386-a7dfdc2e9fc6"),
expected: "9ca4a1dd-a439-4593-b386-a7dfdc2e9fc6",
},
{
// An un-marshalled json object string should return the same as a string without error also
object: []byte(jsonobject),
expected: jsonobject,
},
}
conn := DbConnection{}
for _, test := range tests {
t.Run(fmt.Sprintf("%s -> %s", test.object, test.expected), func(t *testing.T) {
var object string
err := conn.UnmarshalObject(test.object, &object)
is.NoError(err)
is.Equal(test.expected, object)
})
}
}
func Test_ObjectMarshallingEncrypted(t *testing.T) {
is := assert.New(t)
// Based on actual data entering and what we expect out of the function
tests := []struct {
object []byte
expected string
}{
{
object: []byte(""),
},
{
object: []byte("35"),
},
{
// An unmarshalled byte string should return the same without error
object: []byte("9ca4a1dd-a439-4593-b386-a7dfdc2e9fc6"),
},
{
// An un-marshalled json object string should return the same as a string without error also
object: []byte(jsonobject),
},
}
key := secretToEncryptionKey(passphrase)
conn := DbConnection{EncryptionKey: key}
for _, test := range tests {
t.Run(fmt.Sprintf("%s -> %s", test.object, test.expected), func(t *testing.T) {
data, err := conn.MarshalObject(test.object)
is.NoError(err)
var object []byte
err = conn.UnmarshalObject(data, &object)
is.NoError(err)
is.Equal(test.object, object)
})
}
}

View File

@@ -4,13 +4,13 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/boltdb"
"github.com/portainer/portainer/api/database/sqlite"
)
// NewDatabase should use config options to return a connection to the requested database
func NewDatabase(storeType, storePath string, encryptionKey []byte) (connection portainer.Connection, err error) {
if storeType == "boltdb" {
return &boltdb.DbConnection{
if storeType == "sqlite" {
return &sqlite.DbConnection{
Path: storePath,
EncryptionKey: encryptionKey,
}, nil

View File

@@ -1,8 +1,23 @@
package models
import (
"net/http"
)
const (
VersionKey string = "DB_VERSION"
InstanceKey string = "INSTANCE_ID"
EditionKey string = "EDITION"
UpdatingKey string = "DB_UPDATING"
MigratorCountKey string = "MIGRATOR_COUNT"
SchemaVersionKey string = "SCHEMA_VERSION"
)
type Version struct {
SchemaVersion string
MigratorCount int
Edition int
InstanceID string
Key string `json:"Key" gorm:"unique,primaryKey"`
Value string `json:"Value"`
}
func (r *Version) Validate(request *http.Request) error {
return nil
}

291
api/database/sqlite/db.go Normal file
View File

@@ -0,0 +1,291 @@
package sqlite
import (
"errors"
"os"
"path"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/rs/zerolog/log"
)
const (
DatabaseFileName = "portainer.db"
EncryptedDatabaseFileName = "portainer.edb"
)
var (
ErrHaveEncryptedAndUnencrypted = errors.New("Portainer has detected both an encrypted and un-encrypted database and cannot start. Only one database should exist")
ErrHaveEncryptedWithNoKey = errors.New("The portainer database is encrypted, but no secret was loaded")
)
type DbConnection struct {
Path string
EncryptionKey []byte
isEncrypted bool
*gorm.DB
}
func (connection *DbConnection) GetDB() *gorm.DB {
if connection.DB == nil {
err := connection.Open()
if err != nil {
panic(err)
}
}
return connection.DB
}
// GetDatabaseFileName get the database filename
func (connection *DbConnection) GetDatabaseFileName() string {
if connection.IsEncryptedStore() {
return EncryptedDatabaseFileName
}
return DatabaseFileName
}
// GetDataseFilePath get the path + filename for the database file
func (connection *DbConnection) GetDatabaseFilePath() string {
if connection.IsEncryptedStore() {
return path.Join(connection.Path, EncryptedDatabaseFileName)
}
return path.Join(connection.Path, DatabaseFileName)
}
// GetStorePath get the filename and path for the database file
func (connection *DbConnection) GetStorePath() string {
return connection.Path
}
// Return true if the database is encrypted
func (connection *DbConnection) IsEncryptedStore() bool {
return connection.getEncryptionKey() != nil
}
func (connection *DbConnection) SetEncrypted(encrypted bool) {
connection.isEncrypted = encrypted
}
// NeedsEncryptionMigration returns true if database encryption is enabled and
// we have an un-encrypted DB that requires migration to an encrypted DB
func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
// Cases: Note, we need to check both portainer.db and portainer.edb
// to determine if it's a new store. We only need to differentiate between cases 2,3 and 5
// 1) portainer.edb + key => False
// 2) portainer.edb + no key => ERROR Fatal!
// 3) portainer.db + key => True (needs migration)
// 4) portainer.db + no key => False
// 5) NoDB (new) + key => False
// 6) NoDB (new) + no key => False
// 7) portainer.db & portainer.edb => ERROR Fatal!
// If we have a loaded encryption key, always set encrypted
if connection.EncryptionKey != nil {
connection.SetEncrypted(true)
}
// Check for portainer.db
dbFile := path.Join(connection.Path, DatabaseFileName)
_, err := os.Stat(dbFile)
haveDbFile := err == nil
// Check for portainer.edb
edbFile := path.Join(connection.Path, EncryptedDatabaseFileName)
_, err = os.Stat(edbFile)
haveEdbFile := err == nil
if haveDbFile && haveEdbFile {
// 7 - encrypted and unencrypted db?
return false, ErrHaveEncryptedAndUnencrypted
}
if haveDbFile && connection.EncryptionKey != nil {
// 3 - needs migration
return true, nil
}
if haveEdbFile && connection.EncryptionKey == nil {
// 2 - encrypted db, but no key?
return false, ErrHaveEncryptedWithNoKey
}
// 1, 4, 5, 6
return false, nil
}
// Open opens and initializes the BoltDB database.
func (connection *DbConnection) Open() error {
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
// Now we open the db
databasePath := connection.GetDatabaseFilePath()
db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{})
if err != nil {
return err
}
sqlDB, err := db.DB()
if err != nil {
return err
}
sqlDB.SetMaxOpenConns(5)
sqlDB.SetMaxOpenConns(10)
connection.DB = db
return nil
}
func (connection *DbConnection) Close() error {
sqlDB, err := connection.DB.DB()
if err != nil {
return err
}
connection.DB = nil
return sqlDB.Close()
}
func (connection *DbConnection) getEncryptionKey() []byte {
if !connection.isEncrypted {
return nil
}
return connection.EncryptionKey
}
func (connection *DbConnection) GetByID(ID int, obj interface{}) error {
tx := connection.DB.First(obj, `id = ?`, ID)
if tx.Error != nil {
return tx.Error
}
return nil
}
func (connection *DbConnection) DeleteByID(ID int, obj interface{}) error {
tx := connection.DB.Model(obj).Delete(`id = ?`, ID)
if tx.Error != nil {
return tx.Error
}
return nil
}
func (connection *DbConnection) Init() error {
err := connection.DB.AutoMigrate(&models.Version{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Version")
}
err = connection.DB.AutoMigrate(&portainer.Settings{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Settings")
}
err = connection.DB.AutoMigrate(&portainer.APIKey{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate APIKey")
}
err = connection.DB.AutoMigrate(&portainer.User{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate User")
}
err = connection.DB.AutoMigrate(&portainer.CustomTemplate{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate CustomTemplate")
}
err = connection.DB.AutoMigrate(&portainer.DockerHub{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate DockerHub")
}
err = connection.DB.AutoMigrate(&portainer.EdgeGroup{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate EdgeGroup")
}
err = connection.DB.AutoMigrate(&portainer.EdgeJob{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate EdgeJob")
}
err = connection.DB.AutoMigrate(&portainer.EdgeStack{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate EdgeStack")
}
err = connection.DB.AutoMigrate(&portainer.Endpoint{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Endpoint")
}
err = connection.DB.AutoMigrate(&portainer.EndpointGroup{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate EndpointGroup")
}
err = connection.DB.AutoMigrate(&portainer.EndpointRelation{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate EndpointRelation")
}
err = connection.DB.AutoMigrate(&portainer.Extension{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Extension")
}
err = connection.DB.AutoMigrate(&portainer.FDOProfile{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate FDOProfile")
}
err = connection.DB.AutoMigrate(&portainer.HelmUserRepository{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate HelmUserRepository")
}
err = connection.DB.AutoMigrate(&portainer.Registry{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Registry")
}
err = connection.DB.AutoMigrate(&portainer.ResourceControl{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate ResourceControl")
}
err = connection.DB.AutoMigrate(&portainer.Role{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Role")
}
err = connection.DB.AutoMigrate(&portainer.Schedule{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Schedule")
}
err = connection.DB.AutoMigrate(&portainer.Snapshot{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Snapshot")
}
err = connection.DB.AutoMigrate(&portainer.SSLSettings{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate SSLSettings")
}
err = connection.DB.AutoMigrate(&portainer.Stack{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Stack")
}
err = connection.DB.AutoMigrate(&portainer.Tag{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Tag")
}
err = connection.DB.AutoMigrate(&portainer.Team{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Team")
}
err = connection.DB.AutoMigrate(&portainer.TeamMembership{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate TeamMembership")
}
err = connection.DB.AutoMigrate(&portainer.TunnelServerInfo{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate TunnelServerInfo")
}
err = connection.DB.AutoMigrate(&portainer.Webhook{})
if err != nil {
log.Err(err).Msgf("failed to auto migrate Webhook")
}
return nil
}

View File

@@ -1,37 +1,28 @@
package apikeyrepository
import (
"bytes"
"errors"
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "api_key"
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "api_key"
)
// Service represents a service for managing api-key data.
type Service struct {
dataservices.BaseDataService[portainer.APIKey, portainer.APIKeyID]
connection portainer.Connection
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
// err := connection.SetServiceName(BucketName)
// if err != nil {
// return nil, err
// }
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.APIKey, portainer.APIKeyID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
@@ -39,67 +30,91 @@ func NewService(connection portainer.Connection) (*Service, error) {
func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error) {
var result = make([]portainer.APIKey, 0)
err := service.Connection.GetAll(
BucketName,
&portainer.APIKey{},
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")
return nil, fmt.Errorf("failed to convert to APIKey object: %s", obj)
}
// err := service.connection.GetAll(
// BucketName,
// &portainer.APIKey{},
// 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")
// return nil, fmt.Errorf("Failed to convert to APIKey object: %s", obj)
// }
if record.UserID == userID {
result = append(result, *record)
}
// if record.UserID == userID {
// result = append(result, *record)
// }
return &portainer.APIKey{}, nil
})
// return &portainer.APIKey{}, nil
// })
return result, err
return result, nil
}
// GetAPIKeyByDigest returns the API key for the associated digest.
// Note: there is a 1-to-1 mapping of api-key and digest
func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error) {
var k *portainer.APIKey
stop := fmt.Errorf("ok")
err := service.Connection.GetAll(
BucketName,
&portainer.APIKey{},
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")
return nil, fmt.Errorf("failed to convert to APIKey object: %s", obj)
}
if bytes.Equal(key.Digest, digest) {
k = key
return nil, stop
}
// var k *portainer.APIKey
// stop := fmt.Errorf("ok")
// err := service.connection.GetAll(
// BucketName,
// &portainer.APIKey{},
// 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")
// 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
})
// return &portainer.APIKey{}, nil
// })
if errors.Is(err, stop) {
return k, nil
}
// if errors.Is(err, stop) {
// return k, nil
// }
if err == nil {
return nil, dserrors.ErrObjectNotFound
}
// if err == nil {
// return nil, dserrors.ErrObjectNotFound
// }
return nil, err
return nil, nil
}
// Create creates a new APIKey object.
func (service *Service) Create(record *portainer.APIKey) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
record.ID = portainer.APIKeyID(id)
// CreateAPIKey creates a new APIKey object.
func (service *Service) CreateAPIKey(record *portainer.APIKey) error {
// return service.connection.CreateObject(
// BucketName,
// func(id uint64) (int, interface{}) {
// record.ID = portainer.APIKeyID(id)
return int(record.ID), record
},
)
// return int(record.ID), record
// },
// )
return nil
}
// GetAPIKey retrieves an existing APIKey object by api key ID.
func (service *Service) GetAPIKey(keyID portainer.APIKeyID) (*portainer.APIKey, error) {
var key portainer.APIKey
// identifier := service.connection.ConvertToKey(int(keyID))
// err := service.connection.GetObject(BucketName, identifier, &key)
// if err != nil {
// return nil, err
// }
return &key, nil
}
func (service *Service) UpdateAPIKey(key *portainer.APIKey) error {
// identifier := service.connection.ConvertToKey(int(key.ID))
// return service.connection.UpdateObject(BucketName, identifier, key)
return nil
}
func (service *Service) DeleteAPIKey(ID portainer.APIKeyID) error {
return nil
}

View File

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

View File

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

View File

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

View File

@@ -21,11 +21,6 @@ func (service *Service) BucketName() string {
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
}, nil
@@ -35,15 +30,10 @@ func NewService(connection portainer.Connection) (*Service, error) {
func (service *Service) DockerHub() (*portainer.DockerHub, error) {
var dockerhub portainer.DockerHub
err := service.connection.GetObject(BucketName, []byte(dockerHubKey), &dockerhub)
if err != nil {
return nil, err
}
return &dockerhub, nil
}
// UpdateDockerHub updates a DockerHub object.
func (service *Service) UpdateDockerHub(dockerhub *portainer.DockerHub) error {
return service.connection.UpdateObject(BucketName, []byte(dockerHubKey), dockerhub)
return nil
}

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ package edgejob
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
@@ -10,61 +9,56 @@ const BucketName = "edgejobs"
// Service represents a service for managing edge jobs data.
type Service struct {
dataservices.BaseDataService[portainer.EdgeJob, portainer.EdgeJobID]
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.EdgeJob, portainer.EdgeJobID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.EdgeJob, portainer.EdgeJobID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
service: service,
tx: tx,
}
}
// Create creates a new EdgeJob
func (service *Service) Create(edgeJob *portainer.EdgeJob) error {
return service.CreateWithID(portainer.EdgeJobID(service.GetNextIdentifier()), edgeJob)
// EdgeJobs returns a list of Edge jobs
func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
var edgeJobs = make([]portainer.EdgeJob, 0)
return edgeJobs, nil
}
// CreateWithID creates a new EdgeJob
func (service *Service) CreateWithID(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
edgeJob.ID = ID
// EdgeJob returns an Edge job by ID
func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
var edgeJob portainer.EdgeJob
return service.Connection.CreateObjectWithId(
BucketName,
int(edgeJob.ID),
edgeJob,
)
return &edgeJob, nil
}
// Create creates a new EdgeJob
func (service *Service) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
return nil
}
// Deprecated: use UpdateEdgeJobFunc instead
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
return nil
}
// 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)
})
return nil
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service *Service) GetNextIdentifier() int {
return service.Connection.GetNextIdentifier(BucketName)
// DeleteEdgeJob deletes an Edge job
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
return nil
}

View File

@@ -4,23 +4,41 @@ import (
"errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.EdgeJob, portainer.EdgeJobID]
service *Service
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// EdgeJobs returns a list of Edge jobs
func (service ServiceTx) EdgeJobs() ([]portainer.EdgeJob, error) {
var edgeJobs = make([]portainer.EdgeJob, 0)
return edgeJobs, nil
}
// EdgeJob returns an Edge job by ID
func (service ServiceTx) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
var edgeJob portainer.EdgeJob
return &edgeJob, nil
}
// Create creates a new EdgeJob
func (service ServiceTx) Create(edgeJob *portainer.EdgeJob) error {
return service.CreateWithID(portainer.EdgeJobID(service.GetNextIdentifier()), edgeJob)
}
// CreateWithID creates a new EdgeJob
func (service ServiceTx) CreateWithID(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
func (service ServiceTx) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
edgeJob.ID = ID
return service.Tx.CreateObjectWithId(BucketName, int(edgeJob.ID), edgeJob)
return service.tx.CreateObjectWithId(BucketName, int(edgeJob.ID), edgeJob)
}
// UpdateEdgeJob updates an edge job
func (service ServiceTx) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
return nil
}
// UpdateEdgeJobFunc is a no-op inside a transaction.
@@ -28,7 +46,7 @@ func (service ServiceTx) UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc fu
return errors.New("cannot be called inside a transaction")
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service ServiceTx) GetNextIdentifier() int {
return service.Tx.GetNextIdentifier(BucketName)
// DeleteEdgeJob deletes an Edge job
func (service ServiceTx) DeleteEdgeJob(ID portainer.EdgeJobID) error {
return nil
}

View File

@@ -4,7 +4,6 @@ import (
"sync"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
@@ -24,11 +23,6 @@ func (service *Service) BucketName() string {
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection, cacheInvalidationFn func(portainer.EdgeStackID)) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
s := &Service{
connection: connection,
idxVersion: make(map[portainer.EdgeStackID]int),
@@ -61,24 +55,12 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
// EdgeStacks returns an array containing all edge stacks
func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
var stacks = make([]portainer.EdgeStack, 0)
return stacks, service.connection.GetAll(
BucketName,
&portainer.EdgeStack{},
dataservices.AppendFn(&stacks),
)
return stacks, nil
}
// EdgeStack returns an Edge stack by ID.
func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error) {
var stack portainer.EdgeStack
identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &stack)
if err != nil {
return nil, err
}
return &stack, nil
}
@@ -95,15 +77,6 @@ func (service *Service) EdgeStackVersion(ID portainer.EdgeStackID) (int, bool) {
func (service *Service) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
edgeStack.ID = id
err := service.connection.CreateObjectWithId(
BucketName,
int(edgeStack.ID),
edgeStack,
)
if err != nil {
return err
}
service.mu.Lock()
service.idxVersion[id] = edgeStack.Version
service.cacheInvalidationFn(id)
@@ -117,13 +90,6 @@ func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *por
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)
@@ -132,18 +98,7 @@ func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *por
// 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 nil
}
// UpdateEdgeStackFuncTx is a helper function used to call UpdateEdgeStackFunc inside a transaction.
@@ -156,21 +111,7 @@ 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
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service *Service) GetNextIdentifier() int {
return service.connection.GetNextIdentifier(BucketName)
}

View File

@@ -1,11 +1,7 @@
package edgestack
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
type ServiceTx struct {
@@ -21,33 +17,12 @@ func (service ServiceTx) BucketName() string {
func (service ServiceTx) EdgeStacks() ([]portainer.EdgeStack, error) {
var stacks = make([]portainer.EdgeStack, 0)
err := service.tx.GetAll(
BucketName,
&portainer.EdgeStack{},
func(obj interface{}) (interface{}, error) {
stack, ok := obj.(*portainer.EdgeStack)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object")
return nil, fmt.Errorf("failed to convert to EdgeStack object: %s", obj)
}
stacks = append(stacks, *stack)
return &portainer.EdgeStack{}, nil
})
return stacks, err
return stacks, nil
}
// EdgeStack returns an Edge stack by ID.
func (service ServiceTx) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error) {
var stack portainer.EdgeStack
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.GetObject(BucketName, identifier, &stack)
if err != nil {
return nil, err
}
return &stack, nil
}
@@ -65,15 +40,6 @@ func (service ServiceTx) EdgeStackVersion(ID portainer.EdgeStackID) (int, bool)
func (service ServiceTx) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
edgeStack.ID = id
err := service.tx.CreateObjectWithId(
BucketName,
int(edgeStack.ID),
edgeStack,
)
if err != nil {
return err
}
service.service.mu.Lock()
service.service.idxVersion[id] = edgeStack.Version
service.service.cacheInvalidationFn(id)
@@ -87,13 +53,6 @@ func (service ServiceTx) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *po
service.service.mu.Lock()
defer service.service.mu.Unlock()
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.UpdateObject(BucketName, identifier, edgeStack)
if err != nil {
return err
}
service.service.idxVersion[ID] = edgeStack.Version
service.service.cacheInvalidationFn(ID)
@@ -116,22 +75,5 @@ func (service ServiceTx) UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFun
func (service ServiceTx) DeleteEdgeStack(ID portainer.EdgeStackID) error {
service.service.mu.Lock()
defer service.service.mu.Unlock()
identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.DeleteObject(BucketName, identifier)
if err != nil {
return err
}
delete(service.service.idxVersion, ID)
service.service.cacheInvalidationFn(ID)
return nil
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service ServiceTx) GetNextIdentifier() int {
return service.tx.GetNextIdentifier(BucketName)
}

View File

@@ -5,12 +5,8 @@ import (
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "endpoints"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
connection portainer.Connection
@@ -19,36 +15,31 @@ type Service struct {
heartbeats sync.Map
}
func (service *Service) BucketName() string {
return BucketName
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
s := &Service{
connection: connection,
idxEdgeID: make(map[string]portainer.EndpointID),
}
es, err := s.endpoints()
return s, nil
}
func (service *Service) Init() error {
es, err := service.endpoints()
if err != nil {
return nil, err
return err
}
for _, e := range es {
if len(e.EdgeID) > 0 {
s.idxEdgeID[e.EdgeID] = e.ID
service.idxEdgeID[e.EdgeID] = e.ID
}
s.heartbeats.Store(e.ID, e.LastCheckInDate)
service.heartbeats.Store(e.ID, e.LastCheckInDate)
}
return s, nil
return nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
@@ -60,44 +51,48 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
// Endpoint returns an environment(endpoint) by ID.
func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
var endpoint *portainer.Endpoint
var err error
var obj portainer.Endpoint
err = service.connection.ViewTx(func(tx portainer.Transaction) error {
endpoint, err = service.Tx(tx).Endpoint(ID)
return err
})
err := service.connection.GetByID(int(ID), &obj)
if err != nil {
return nil, err
}
endpoint.LastCheckInDate, _ = service.Heartbeat(ID)
return endpoint, nil
return &obj, nil
}
// UpdateEndpoint updates an environment(endpoint).
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Tx(tx).UpdateEndpoint(ID, endpoint)
})
db := service.connection.GetDB()
endpoint.ID = ID
tx := db.Save(&endpoint)
if tx.Error != nil {
return tx.Error
}
return nil
}
// DeleteEndpoint deletes an environment(endpoint).
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Tx(tx).DeleteEndpoint(ID)
})
db := service.connection.GetDB()
tx := db.Model(&portainer.Endpoint{}).Delete("id = ?", ID)
if tx.Error != nil {
return tx.Error
}
return nil
}
func (service *Service) endpoints() ([]portainer.Endpoint, error) {
var endpoints []portainer.Endpoint
var err error
err = service.connection.ViewTx(func(tx portainer.Transaction) error {
endpoints, err = service.Tx(tx).Endpoints()
return err
})
db := service.connection.GetDB()
tx := db.Find(&endpoints)
if tx.Error != nil {
return nil, tx.Error
}
return endpoints, err
}
@@ -140,37 +135,10 @@ func (service *Service) UpdateHeartbeat(endpointID portainer.EndpointID) {
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
func (service *Service) Create(endpoint *portainer.Endpoint) error {
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Tx(tx).Create(endpoint)
})
}
func (service *Service) EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.Endpoint, error) {
var endpoints = make([]portainer.Endpoint, 0)
return endpoints, service.connection.GetAll(
BucketName,
&portainer.Endpoint{},
dataservices.FilterFn(&endpoints, func(e portainer.Endpoint) bool {
for t := range e.TeamAccessPolicies {
if t == teamID {
return true
}
}
return false
}),
)
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service *Service) GetNextIdentifier() int {
var identifier int
service.connection.UpdateTx(func(tx portainer.Transaction) error {
identifier = service.Tx(tx).GetNextIdentifier()
return nil
})
return identifier
db := service.connection.GetDB()
tx := db.Model(&portainer.Endpoint{}).Create(&endpoint)
if tx.Error != nil {
return tx.Error
}
return nil
}

View File

@@ -2,7 +2,6 @@ package endpoint
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
@@ -13,33 +12,27 @@ type ServiceTx struct {
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// Endpoint returns an environment(endpoint) by ID.
func (service ServiceTx) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
var endpoint portainer.Endpoint
identifier := service.service.connection.ConvertToKey(int(ID))
// identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.GetObject(BucketName, identifier, &endpoint)
if err != nil {
return nil, err
}
endpoint.LastCheckInDate, _ = service.service.Heartbeat(ID)
// err := service.tx.GetObject(BucketName, identifier, &endpoint)
// if err != nil {
// return nil, err
// }
return &endpoint, nil
}
// UpdateEndpoint updates an environment(endpoint).
func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
identifier := service.service.connection.ConvertToKey(int(ID))
// identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.UpdateObject(BucketName, identifier, endpoint)
if err != nil {
return err
}
// err := service.tx.UpdateObject(BucketName, identifier, endpoint)
// if err != nil {
// return err
// }
service.service.mu.Lock()
if len(endpoint.EdgeID) > 0 {
@@ -55,18 +48,17 @@ func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *porta
// DeleteEndpoint deletes an environment(endpoint).
func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
identifier := service.service.connection.ConvertToKey(int(ID))
// identifier := service.service.connection.ConvertToKey(int(ID))
err := service.tx.DeleteObject(BucketName, identifier)
if err != nil {
return err
}
// err := service.tx.DeleteObject(BucketName, identifier)
// if err != nil {
// return err
// }
service.service.mu.Lock()
for edgeID, endpointID := range service.service.idxEdgeID {
if endpointID == ID {
delete(service.service.idxEdgeID, edgeID)
break
}
}
@@ -82,11 +74,22 @@ func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
func (service ServiceTx) Endpoints() ([]portainer.Endpoint, error) {
var endpoints = make([]portainer.Endpoint, 0)
return endpoints, service.tx.GetAllWithJsoniter(
BucketName,
&portainer.Endpoint{},
dataservices.AppendFn(&endpoints),
)
// err := service.tx.GetAllWithJsoniter(
// BucketName,
// &portainer.Endpoint{},
// func(obj interface{}) (interface{}, error) {
// endpoint, ok := obj.(*portainer.Endpoint)
// if !ok {
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Endpoint object")
// return nil, fmt.Errorf("failed to convert to Endpoint object: %s", obj)
// }
// endpoints = append(endpoints, *endpoint)
// return &portainer.Endpoint{}, nil
// })
return endpoints, nil
}
func (service ServiceTx) EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool) {
@@ -107,39 +110,17 @@ func (service ServiceTx) UpdateHeartbeat(endpointID portainer.EndpointID) {
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
func (service ServiceTx) Create(endpoint *portainer.Endpoint) error {
err := service.tx.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint)
if err != nil {
return err
}
// err := service.tx.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint)
// if err != nil {
// return err
// }
service.service.mu.Lock()
if len(endpoint.EdgeID) > 0 {
service.service.idxEdgeID[endpoint.EdgeID] = endpoint.ID
}
service.service.heartbeats.Store(endpoint.ID, endpoint.LastCheckInDate)
service.service.mu.Unlock()
// service.service.mu.Lock()
// if len(endpoint.EdgeID) > 0 {
// service.service.idxEdgeID[endpoint.EdgeID] = endpoint.ID
// }
// service.service.heartbeats.Store(endpoint.ID, endpoint.LastCheckInDate)
// service.service.mu.Unlock()
return nil
}
func (service ServiceTx) EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.Endpoint, error) {
var endpoints = make([]portainer.Endpoint, 0)
return endpoints, service.tx.GetAll(
BucketName,
&portainer.Endpoint{},
dataservices.FilterFn(&endpoints, func(e portainer.Endpoint) bool {
for t := range e.TeamAccessPolicies {
if t == teamID {
return true
}
}
return false
}),
)
}
// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service ServiceTx) GetNextIdentifier() int {
return service.tx.GetNextIdentifier(BucketName)
}

View File

@@ -2,51 +2,62 @@ package endpointgroup
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "endpoint_groups"
)
// Service represents a service for managing environment(endpoint) data.
type Service struct {
dataservices.BaseDataService[portainer.EndpointGroup, portainer.EndpointGroupID]
connection portainer.Connection
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.EndpointGroup, portainer.EndpointGroupID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.EndpointGroup, portainer.EndpointGroupID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
service: service,
tx: tx,
}
}
// EndpointGroup returns an environment(endpoint) group by ID.
func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
var endpointGroup portainer.EndpointGroup
// identifier := service.connection.ConvertToKey(int(ID))
// err := service.connection.GetObject(BucketName, identifier, &endpointGroup)
// if err != nil {
// return nil, err
// }
return &endpointGroup, nil
}
// UpdateEndpointGroup updates an environment(endpoint) group.
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.UpdateObject(BucketName, identifier, endpointGroup)
return nil
}
// DeleteEndpointGroup deletes an environment(endpoint) group.
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.DeleteObject(BucketName, identifier)
return nil
}
// EndpointGroups return an array containing all the environment(endpoint) groups.
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
var endpointGroups = make([]portainer.EndpointGroup, 0)
return endpointGroups, nil
}
// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it.
func (service *Service) Create(endpointGroup *portainer.EndpointGroup) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
endpointGroup.ID = portainer.EndpointGroupID(id)
return int(endpointGroup.ID), endpointGroup
},
)
return nil
}

View File

@@ -2,20 +2,48 @@ package endpointgroup
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.EndpointGroup, portainer.EndpointGroupID]
service *Service
tx portainer.Transaction
}
// EndpointGroup returns an environment(endpoint) group by ID.
func (service ServiceTx) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
var endpointGroup portainer.EndpointGroup
// identifier := service.service.connection.ConvertToKey(int(ID))
// err := service.tx.GetObject(BucketName, identifier, &endpointGroup)
// if err != nil {
// return nil, err
// }
return &endpointGroup, nil
}
// UpdateEndpointGroup updates an environment(endpoint) group.
func (service ServiceTx) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.UpdateObject(BucketName, identifier, endpointGroup)
return nil
}
// DeleteEndpointGroup deletes an environment(endpoint) group.
func (service ServiceTx) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.DeleteObject(BucketName, identifier)
return nil
}
// EndpointGroups return an array containing all the environment(endpoint) groups.
func (service ServiceTx) EndpointGroups() ([]portainer.EndpointGroup, error) {
var endpointGroups = make([]portainer.EndpointGroup, 0)
return endpointGroups, nil
}
// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it.
func (service ServiceTx) Create(endpointGroup *portainer.EndpointGroup) error {
return service.Tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
endpointGroup.ID = portainer.EndpointGroupID(id)
return int(endpointGroup.ID), endpointGroup
},
)
return nil
}

View File

@@ -2,15 +2,11 @@ package endpointrelation
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "endpoint_relations"
// Service represents a service for managing environment(endpoint) relation data.
type Service struct {
connection portainer.Connection
@@ -18,10 +14,6 @@ type Service struct {
updateStackFnTx func(tx portainer.Transaction, ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error
}
func (service *Service) BucketName() string {
return BucketName
}
func (service *Service) RegisterUpdateStackFunction(
updateFunc func(portainer.EdgeStackID, func(*portainer.EdgeStack)) error,
updateFuncTx func(portainer.Transaction, portainer.EdgeStackID, func(*portainer.EdgeStack)) error,
@@ -32,10 +24,6 @@ func (service *Service) RegisterUpdateStackFunction(
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
@@ -53,44 +41,32 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error) {
var all = make([]portainer.EndpointRelation, 0)
return all, service.connection.GetAll(
BucketName,
&portainer.EndpointRelation{},
dataservices.AppendFn(&all),
)
return all, nil
}
// EndpointRelation returns a Environment(Endpoint) relation object by EndpointID
func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*portainer.EndpointRelation, error) {
var endpointRelation portainer.EndpointRelation
identifier := service.connection.ConvertToKey(int(endpointID))
err := service.connection.GetObject(BucketName, identifier, &endpointRelation)
if err != nil {
return nil, err
}
return &endpointRelation, nil
}
// 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 nil
}
// UpdateEndpointRelation updates an Environment(Endpoint) relation object
func (service *Service) UpdateEndpointRelation(endpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
previousRelationState, _ := service.EndpointRelation(endpointID)
identifier := service.connection.ConvertToKey(int(endpointID))
err := service.connection.UpdateObject(BucketName, identifier, endpointRelation)
cache.Del(endpointID)
if err != nil {
return err
}
// identifier := service.connection.ConvertToKey(int(endpointID))
// err := service.connection.UpdateObject(BucketName, identifier, endpointRelation)
// cache.Del(endpointID)
// if err != nil {
// return err
// }
updatedRelationState, _ := service.EndpointRelation(endpointID)
@@ -103,12 +79,12 @@ func (service *Service) UpdateEndpointRelation(endpointID portainer.EndpointID,
func (service *Service) DeleteEndpointRelation(endpointID portainer.EndpointID) error {
deletedRelation, _ := service.EndpointRelation(endpointID)
identifier := service.connection.ConvertToKey(int(endpointID))
err := service.connection.DeleteObject(BucketName, identifier)
cache.Del(endpointID)
if err != nil {
return err
}
// identifier := service.connection.ConvertToKey(int(endpointID))
// err := service.connection.DeleteObject(BucketName, identifier)
// cache.Del(endpointID)
// if err != nil {
// return err
// }
service.updateEdgeStacksAfterRelationChange(deletedRelation, nil)

View File

@@ -2,7 +2,6 @@ package endpointrelation
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/edge/cache"
"github.com/rs/zerolog/log"
@@ -13,52 +12,59 @@ type ServiceTx struct {
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// EndpointRelations returns an array of all EndpointRelations
func (service ServiceTx) EndpointRelations() ([]portainer.EndpointRelation, error) {
var all = make([]portainer.EndpointRelation, 0)
return all, service.tx.GetAll(
BucketName,
&portainer.EndpointRelation{},
dataservices.AppendFn(&all),
)
// err := service.tx.GetAll(
// BucketName,
// &portainer.EndpointRelation{},
// func(obj interface{}) (interface{}, error) {
// r, ok := obj.(*portainer.EndpointRelation)
// if !ok {
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointRelation object")
// return nil, fmt.Errorf("failed to convert to EndpointRelation object: %s", obj)
// }
// all = append(all, *r)
// return &portainer.EndpointRelation{}, nil
// })
return all, nil
}
// EndpointRelation returns an Environment(Endpoint) relation object by EndpointID
func (service ServiceTx) EndpointRelation(endpointID portainer.EndpointID) (*portainer.EndpointRelation, error) {
var endpointRelation portainer.EndpointRelation
identifier := service.service.connection.ConvertToKey(int(endpointID))
// identifier := service.service.connection.ConvertToKey(int(endpointID))
err := service.tx.GetObject(BucketName, identifier, &endpointRelation)
if err != nil {
return nil, err
}
// err := service.tx.GetObject(BucketName, identifier, &endpointRelation)
// if err != nil {
// return nil, err
// }
return &endpointRelation, nil
}
// CreateEndpointRelation saves endpointRelation
func (service ServiceTx) Create(endpointRelation *portainer.EndpointRelation) error {
err := service.tx.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
cache.Del(endpointRelation.EndpointID)
// err := service.tx.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation)
// cache.Del(endpointRelation.EndpointID)
return err
return nil
}
// UpdateEndpointRelation updates an Environment(Endpoint) relation object
func (service ServiceTx) UpdateEndpointRelation(endpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
previousRelationState, _ := service.EndpointRelation(endpointID)
identifier := service.service.connection.ConvertToKey(int(endpointID))
err := service.tx.UpdateObject(BucketName, identifier, endpointRelation)
cache.Del(endpointID)
if err != nil {
return err
}
// identifier := service.service.connection.ConvertToKey(int(endpointID))
// err := service.tx.UpdateObject(BucketName, identifier, endpointRelation)
// cache.Del(endpointID)
// if err != nil {
// return err
// }
updatedRelationState, _ := service.EndpointRelation(endpointID)
@@ -71,12 +77,12 @@ func (service ServiceTx) UpdateEndpointRelation(endpointID portainer.EndpointID,
func (service ServiceTx) DeleteEndpointRelation(endpointID portainer.EndpointID) error {
deletedRelation, _ := service.EndpointRelation(endpointID)
identifier := service.service.connection.ConvertToKey(int(endpointID))
err := service.tx.DeleteObject(BucketName, identifier)
cache.Del(endpointID)
if err != nil {
return err
}
// identifier := service.service.connection.ConvertToKey(int(endpointID))
// err := service.tx.DeleteObject(BucketName, identifier)
// cache.Del(endpointID)
// if err != nil {
// return err
// }
service.updateEdgeStacksAfterRelationChange(deletedRelation, nil)

View File

@@ -5,6 +5,7 @@ import (
)
var (
ErrNoSuchTable = errors.New("no such table")
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

@@ -2,11 +2,12 @@ package extension
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "extension"
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "extension"
)
// Service represents a service for managing environment(endpoint) data.
type Service struct {
@@ -19,10 +20,10 @@ func (service *Service) BucketName() string {
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
// err := connection.SetServiceName(BucketName)
// if err != nil {
// return nil, err
// }
return &Service{
connection: connection,
@@ -32,12 +33,12 @@ func NewService(connection portainer.Connection) (*Service, error) {
// Extension returns a extension by ID
func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extension, error) {
var extension portainer.Extension
identifier := service.connection.ConvertToKey(int(ID))
// identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &extension)
if err != nil {
return nil, err
}
// err := service.connection.GetObject(BucketName, identifier, &extension)
// if err != nil {
// return nil, err
// }
return &extension, nil
}
@@ -46,21 +47,33 @@ func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extensio
func (service *Service) Extensions() ([]portainer.Extension, error) {
var extensions = make([]portainer.Extension, 0)
return extensions, service.connection.GetAll(
BucketName,
&portainer.Extension{},
dataservices.AppendFn(&extensions),
)
// err := service.connection.GetAll(
// BucketName,
// &portainer.Extension{},
// func(obj interface{}) (interface{}, error) {
// extension, ok := obj.(*portainer.Extension)
// if !ok {
// log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Extension object")
// return nil, fmt.Errorf("Failed to convert to Extension object: %s", obj)
// }
// extensions = append(extensions, *extension)
// return &portainer.Extension{}, nil
// })
return extensions, nil
}
// Persist persists a extension inside the database.
func (service *Service) Persist(extension *portainer.Extension) error {
return service.connection.CreateObjectWithId(BucketName, int(extension.ID), extension)
// return service.connection.CreateObjectWithId(BucketName, int(extension.ID), extension)
return nil
}
// DeleteExtension deletes a Extension.
func (service *Service) DeleteExtension(ID portainer.ExtensionID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.DeleteObject(BucketName, identifier)
return nil
}

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,13 @@
package dataservices
import (
"io"
"errors"
"time"
"github.com/mattn/go-sqlite3"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"gorm.io/gorm"
)
type (
@@ -37,16 +39,13 @@ type (
Webhook() WebhookService
}
// DataStore defines the interface to manage the data
DataStore interface {
Open() (newStore bool, err error)
Init() error
Close() error
UpdateTx(func(DataStoreTx) error) error
ViewTx(func(DataStoreTx) error) error
MigrateData() error
Rollback(force bool) error
CheckCurrentEdition() error
BackupTo(w io.Writer) error
Export(filename string) (err error)
DataStoreTx
@@ -54,22 +53,31 @@ type (
// CustomTemplateService represents a service to manage custom templates
CustomTemplateService interface {
BaseCRUD[portainer.CustomTemplate, portainer.CustomTemplateID]
GetNextIdentifier() int
CustomTemplates() ([]portainer.CustomTemplate, error)
CustomTemplate(ID portainer.CustomTemplateID) (*portainer.CustomTemplate, error)
Create(customTemplate *portainer.CustomTemplate) error
UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error
DeleteCustomTemplate(ID portainer.CustomTemplateID) error
}
// EdgeGroupService represents a service to manage Edge groups
EdgeGroupService interface {
BaseCRUD[portainer.EdgeGroup, portainer.EdgeGroupID]
EdgeGroups() ([]portainer.EdgeGroup, error)
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
}
// EdgeJobService represents a service to manage Edge jobs
EdgeJobService interface {
BaseCRUD[portainer.EdgeJob, portainer.EdgeJobID]
CreateWithID(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
EdgeJobs() ([]portainer.EdgeJob, error)
EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error)
Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error
UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc func(edgeJob *portainer.EdgeJob)) error
GetNextIdentifier() int
DeleteEdgeJob(ID portainer.EdgeJobID) error
}
// EdgeStackService represents a service to manage Edge stacks
@@ -78,31 +86,31 @@ type (
EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error)
EdgeStackVersion(ID portainer.EdgeStackID) (int, bool)
Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error
// Deprecated: Use UpdateEdgeStackFunc instead.
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
}
// EndpointService represents a service for managing environment(endpoint) data
EndpointService interface {
Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error)
EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool)
EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.Endpoint, error)
Heartbeat(endpointID portainer.EndpointID) (int64, bool)
UpdateHeartbeat(endpointID portainer.EndpointID)
Endpoints() ([]portainer.Endpoint, error)
Create(endpoint *portainer.Endpoint) error
UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error
DeleteEndpoint(ID portainer.EndpointID) error
GetNextIdentifier() int
BucketName() string
}
// EndpointGroupService represents a service for managing environment(endpoint) group data
EndpointGroupService interface {
BaseCRUD[portainer.EndpointGroup, portainer.EndpointGroupID]
EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error)
EndpointGroups() ([]portainer.EndpointGroup, error)
Create(group *portainer.EndpointGroup) error
UpdateEndpointGroup(ID portainer.EndpointGroupID, group *portainer.EndpointGroup) error
DeleteEndpointGroup(ID portainer.EndpointGroupID) error
}
// EndpointRelationService represents a service for managing environment(endpoint) relations data
@@ -112,19 +120,24 @@ type (
Create(endpointRelation *portainer.EndpointRelation) error
UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error
DeleteEndpointRelation(EndpointID portainer.EndpointID) error
BucketName() string
}
// FDOProfileService represents a service to manage FDO Profiles
FDOProfileService interface {
BaseCRUD[portainer.FDOProfile, portainer.FDOProfileID]
GetNextIdentifier() int
FDOProfiles() ([]portainer.FDOProfile, error)
FDOProfile(ID portainer.FDOProfileID) (*portainer.FDOProfile, error)
Create(FDOProfile *portainer.FDOProfile) error
Update(ID portainer.FDOProfileID, FDOProfile *portainer.FDOProfile) error
Delete(ID portainer.FDOProfileID) error
}
// HelmUserRepositoryService represents a service to manage HelmUserRepositories
HelmUserRepositoryService interface {
BaseCRUD[portainer.HelmUserRepository, portainer.HelmUserRepositoryID]
HelmUserRepositories() ([]portainer.HelmUserRepository, error)
HelmUserRepositoryByUserID(userID portainer.UserID) ([]portainer.HelmUserRepository, error)
Create(record *portainer.HelmUserRepository) error
UpdateHelmUserRepository(ID portainer.HelmUserRepositoryID, repository *portainer.HelmUserRepository) error
DeleteHelmUserRepository(ID portainer.HelmUserRepositoryID) error
}
// JWTService represents a service for managing JWT tokens
@@ -138,23 +151,37 @@ type (
// RegistryService represents a service for managing registry data
RegistryService interface {
BaseCRUD[portainer.Registry, portainer.RegistryID]
Registry(ID portainer.RegistryID) (*portainer.Registry, error)
Registries() ([]portainer.Registry, error)
Create(registry *portainer.Registry) error
UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error
DeleteRegistry(ID portainer.RegistryID) error
}
// ResourceControlService represents a service for managing resource control data
ResourceControlService interface {
BaseCRUD[portainer.ResourceControl, portainer.ResourceControlID]
ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error)
ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error)
ResourceControls() ([]portainer.ResourceControl, error)
Create(rc *portainer.ResourceControl) error
UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error
DeleteResourceControl(ID portainer.ResourceControlID) error
}
// RoleService represents a service for managing user roles
RoleService interface {
BaseCRUD[portainer.Role, portainer.RoleID]
Role(ID portainer.RoleID) (*portainer.Role, error)
Roles() ([]portainer.Role, error)
Create(role *portainer.Role) error
UpdateRole(ID portainer.RoleID, role *portainer.Role) error
}
// APIKeyRepositoryService
APIKeyRepository interface {
BaseCRUD[portainer.APIKey, portainer.APIKeyID]
CreateAPIKey(key *portainer.APIKey) error
GetAPIKey(keyID portainer.APIKeyID) (*portainer.APIKey, error)
UpdateAPIKey(key *portainer.APIKey) error
DeleteAPIKey(ID portainer.APIKeyID) error
GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error)
GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error)
}
@@ -163,47 +190,64 @@ type (
SettingsService interface {
Settings() (*portainer.Settings, error)
UpdateSettings(settings *portainer.Settings) error
BucketName() string
}
SnapshotService interface {
BaseCRUD[portainer.Snapshot, portainer.EndpointID]
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
}
// SSLSettingsService represents a service for managing application settings
SSLSettingsService interface {
Settings() (*portainer.SSLSettings, error)
UpdateSettings(settings *portainer.SSLSettings) error
BucketName() string
}
// StackService represents a service for managing stack data
StackService interface {
BaseCRUD[portainer.Stack, portainer.StackID]
Stack(ID portainer.StackID) (*portainer.Stack, error)
StackByName(name string) (*portainer.Stack, error)
StacksByName(name string) ([]portainer.Stack, error)
GetNextIdentifier() int
Stacks() ([]portainer.Stack, error)
Create(stack *portainer.Stack) error
UpdateStack(ID portainer.StackID, stack *portainer.Stack) error
DeleteStack(ID portainer.StackID) error
StackByWebhookID(ID string) (*portainer.Stack, error)
RefreshableStacks() ([]portainer.Stack, error)
}
// TagService represents a service for managing tag data
TagService interface {
BaseCRUD[portainer.Tag, portainer.TagID]
Tags() ([]portainer.Tag, error)
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
}
// TeamService represents a service for managing user data
TeamService interface {
BaseCRUD[portainer.Team, portainer.TeamID]
Team(ID portainer.TeamID) (*portainer.Team, error)
TeamByName(name string) (*portainer.Team, error)
Teams() ([]portainer.Team, error)
Create(team *portainer.Team) error
UpdateTeam(ID portainer.TeamID, team *portainer.Team) error
DeleteTeam(ID portainer.TeamID) error
}
// TeamMembershipService represents a service for managing team membership data
TeamMembershipService interface {
BaseCRUD[portainer.TeamMembership, portainer.TeamMembershipID]
TeamMembership(ID portainer.TeamMembershipID) (*portainer.TeamMembership, error)
TeamMemberships() ([]portainer.TeamMembership, error)
TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error)
TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error)
Create(membership *portainer.TeamMembership) error
UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error
DeleteTeamMembership(ID portainer.TeamMembershipID) error
DeleteTeamMembershipByUserID(userID portainer.UserID) error
DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error
DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.TeamID, userID portainer.UserID) error
@@ -213,29 +257,48 @@ type (
TunnelServerService interface {
Info() (*portainer.TunnelServerInfo, error)
UpdateInfo(info *portainer.TunnelServerInfo) error
BucketName() string
}
// UserService represents a service for managing user data
UserService interface {
BaseCRUD[portainer.User, portainer.UserID]
User(ID portainer.UserID) (*portainer.User, error)
UserByUsername(username string) (*portainer.User, error)
Users() ([]portainer.User, error)
UsersByRole(role portainer.UserRole) ([]portainer.User, error)
Create(user *portainer.User) error
UpdateUser(ID portainer.UserID, user *portainer.User) error
DeleteUser(ID portainer.UserID) error
}
// VersionService represents a service for managing version data
VersionService interface {
InstanceID() (string, error)
UpdateInstanceID(ID string) error
Edition() (portainer.SoftwareEdition, error)
InstanceID() (string, error)
// UpdateInstanceID(ID string) error
Version() (*models.Version, error)
UpdateVersion(*models.Version) error
GetAll() (map[string]interface{}, error)
UpdateAll(map[string]interface{}) error
}
// WebhookService represents a service for managing webhook data.
WebhookService interface {
BaseCRUD[portainer.Webhook, portainer.WebhookID]
Webhooks() ([]portainer.Webhook, error)
Webhook(ID portainer.WebhookID) (*portainer.Webhook, error)
Create(portainer *portainer.Webhook) error
UpdateWebhook(ID portainer.WebhookID, webhook *portainer.Webhook) error
WebhookByResourceID(resourceID string) (*portainer.Webhook, error)
WebhookByToken(token string) (*portainer.Webhook, error)
DeleteWebhook(ID portainer.WebhookID) error
}
)
func IsErrObjectNotFound(e error) bool {
var sqliteErr sqlite3.Error
errNotFound := false
if errors.As(e, &sqliteErr) {
errNotFound = sqliteErr.Code == sqlite3.ErrError
}
return errNotFound || errors.Is(e, gorm.ErrRecordNotFound)
}

View File

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

View File

@@ -1,17 +1,59 @@
package registry
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.Registry, portainer.RegistryID]
service *Service
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// Registry returns a registry by ID.
func (service ServiceTx) Registry(ID portainer.RegistryID) (*portainer.Registry, error) {
var registry portainer.Registry
// identifier := service.service.connection.ConvertToKey(int(ID))
// err := service.tx.GetObject(BucketName, identifier, &registry)
// if err != nil {
// return nil, err
// }
return &registry, nil
}
// Registries returns an array containing all the registries.
func (service ServiceTx) Registries() ([]portainer.Registry, error) {
var registries = make([]portainer.Registry, 0)
err := service.tx.GetAll(
BucketName,
&portainer.Registry{},
func(obj interface{}) (interface{}, error) {
registry, ok := obj.(*portainer.Registry)
if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Registry object")
return nil, fmt.Errorf("Failed to convert to Registry object: %s", obj)
}
registries = append(registries, *registry)
return &portainer.Registry{}, nil
})
return registries, err
}
// Create creates a new registry.
func (service ServiceTx) Create(registry *portainer.Registry) error {
return service.Tx.CreateObject(
return service.tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
registry.ID = portainer.RegistryID(id)
@@ -19,3 +61,17 @@ func (service ServiceTx) Create(registry *portainer.Registry) error {
},
)
}
// UpdateRegistry updates a registry.
func (service ServiceTx) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.UpdateObject(BucketName, identifier, registry)
return nil
}
// DeleteRegistry deletes a registry.
func (service ServiceTx) DeleteRegistry(ID portainer.RegistryID) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.DeleteObject(BucketName, identifier)
return nil
}

View File

@@ -1,13 +1,7 @@
package resourcecontrol
import (
"errors"
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
// BucketName represents the name of the bucket where this service stores data.
@@ -15,78 +9,126 @@ const BucketName = "resource_control"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
dataservices.BaseDataService[portainer.ResourceControl, portainer.ResourceControlID]
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
// err := connection.SetServiceName(BucketName)
// if err != nil {
// return nil, err
// }
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.ResourceControl, portainer.ResourceControlID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.ResourceControl, portainer.ResourceControlID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
service: service,
tx: tx,
}
}
// ResourceControl returns a ResourceControl object by ID
func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error) {
var resourceControl portainer.ResourceControl
// identifier := service.connection.ConvertToKey(int(ID))
// err := service.connection.GetObject(BucketName, identifier, &resourceControl)
// if err != nil {
// return nil, err
// }
return &resourceControl, nil
}
// ResourceControlByResourceIDAndType returns a ResourceControl object by checking if the resourceID is equal
// to the main ResourceID or in SubResourceIDs. It also performs a check on the resource type. Return nil
// if no ResourceControl was found.
func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
var resourceControl *portainer.ResourceControl
stop := fmt.Errorf("ok")
err := service.Connection.GetAll(
BucketName,
&portainer.ResourceControl{},
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")
return nil, fmt.Errorf("failed to convert to ResourceControl object: %s", obj)
}
// var resourceControl *portainer.ResourceControl
// stop := fmt.Errorf("ok")
// err := service.connection.GetAll(
// BucketName,
// &portainer.ResourceControl{},
// 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")
// return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
// }
if rc.ResourceID == resourceID && rc.Type == resourceType {
resourceControl = rc
return nil, stop
}
// if rc.ResourceID == resourceID && rc.Type == resourceType {
// resourceControl = rc
// return nil, stop
// }
for _, subResourceID := range rc.SubResourceIDs {
if subResourceID == resourceID {
resourceControl = rc
return nil, stop
}
}
// for _, subResourceID := range rc.SubResourceIDs {
// if subResourceID == resourceID {
// resourceControl = rc
// return nil, stop
// }
// }
return &portainer.ResourceControl{}, nil
})
if errors.Is(err, stop) {
return resourceControl, nil
}
// return &portainer.ResourceControl{}, nil
// })
// if errors.Is(err, stop) {
// return resourceControl, nil
// }
return nil, err
return nil, nil
}
// ResourceControls returns all the ResourceControl objects
func (service *Service) ResourceControls() ([]portainer.ResourceControl, error) {
var rcs = make([]portainer.ResourceControl, 0)
// err := service.connection.GetAll(
// BucketName,
// &portainer.ResourceControl{},
// 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")
// return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
// }
// rcs = append(rcs, *rc)
// return &portainer.ResourceControl{}, nil
// })
return rcs, nil
}
// CreateResourceControl creates a new ResourceControl object
func (service *Service) Create(resourceControl *portainer.ResourceControl) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
resourceControl.ID = portainer.ResourceControlID(id)
return int(resourceControl.ID), resourceControl
},
)
// return service.connection.CreateObject(
// BucketName,
// func(id uint64) (int, interface{}) {
// resourceControl.ID = portainer.ResourceControlID(id)
// return int(resourceControl.ID), resourceControl
// },
// )
return nil
}
// UpdateResourceControl saves a ResourceControl object.
func (service *Service) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.UpdateObject(BucketName, identifier, resourceControl)
return nil
}
// DeleteResourceControl deletes a ResourceControl object by ID
func (service *Service) DeleteResourceControl(ID portainer.ResourceControlID) error {
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.DeleteObject(BucketName, identifier)
return nil
}

View File

@@ -5,13 +5,30 @@ import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.ResourceControl, portainer.ResourceControlID]
service *Service
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// ResourceControl returns a ResourceControl object by ID
func (service ServiceTx) ResourceControl(ID portainer.ResourceControlID) (*portainer.ResourceControl, error) {
var resourceControl portainer.ResourceControl
// identifier := service.service.connection.ConvertToKey(int(ID))
// err := service.tx.GetObject(BucketName, identifier, &resourceControl)
// if err != nil {
// return nil, err
// }
return &resourceControl, nil
}
// ResourceControlByResourceIDAndType returns a ResourceControl object by checking if the resourceID is equal
@@ -20,14 +37,14 @@ type ServiceTx struct {
func (service ServiceTx) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
var resourceControl *portainer.ResourceControl
stop := fmt.Errorf("ok")
err := service.Tx.GetAll(
err := service.tx.GetAll(
BucketName,
&portainer.ResourceControl{},
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")
return nil, fmt.Errorf("failed to convert to ResourceControl object: %s", obj)
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
}
if rc.ResourceID == resourceID && rc.Type == resourceType {
@@ -51,9 +68,31 @@ func (service ServiceTx) ResourceControlByResourceIDAndType(resourceID string, r
return nil, err
}
// ResourceControls returns all the ResourceControl objects
func (service ServiceTx) ResourceControls() ([]portainer.ResourceControl, error) {
var rcs = make([]portainer.ResourceControl, 0)
err := service.tx.GetAll(
BucketName,
&portainer.ResourceControl{},
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")
return nil, fmt.Errorf("Failed to convert to ResourceControl object: %s", obj)
}
rcs = append(rcs, *rc)
return &portainer.ResourceControl{}, nil
})
return rcs, err
}
// CreateResourceControl creates a new ResourceControl object
func (service ServiceTx) Create(resourceControl *portainer.ResourceControl) error {
return service.Tx.CreateObject(
return service.tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
resourceControl.ID = portainer.ResourceControlID(id)
@@ -61,3 +100,17 @@ func (service ServiceTx) Create(resourceControl *portainer.ResourceControl) erro
},
)
}
// UpdateResourceControl saves a ResourceControl object.
func (service ServiceTx) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.UpdateObject(BucketName, identifier, resourceControl)
return nil
}
// DeleteResourceControl deletes a ResourceControl object by ID
func (service ServiceTx) DeleteResourceControl(ID portainer.ResourceControlID) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.DeleteObject(BucketName, identifier)
return nil
}

View File

@@ -2,49 +2,70 @@ package role
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "roles"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
dataservices.BaseDataService[portainer.Role, portainer.RoleID]
connection portainer.Connection
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.Role, portainer.RoleID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.Role, portainer.RoleID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
service: service,
tx: tx,
}
}
// Role returns a Role by ID
func (service *Service) Role(ID portainer.RoleID) (*portainer.Role, error) {
var set portainer.Role
db := service.connection.GetDB()
tx := db.First(&set, `id = ?`, ID)
if tx.Error != nil {
return nil, tx.Error
}
return &set, nil
}
// Roles returns an array containing all the sets.
func (service *Service) Roles() ([]portainer.Role, error) {
var sets = make([]portainer.Role, 0)
db := service.connection.GetDB()
tx := db.Find(&sets)
if tx.Error != nil {
return nil, tx.Error
}
return sets, nil
}
// CreateRole creates a new Role.
func (service *Service) Create(role *portainer.Role) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
role.ID = portainer.RoleID(id)
return int(role.ID), role
},
)
db := service.connection.GetDB()
tx := db.Create(&role)
if tx.Error != nil {
return tx.Error
}
return nil
}
// UpdateRole updates a role.
func (service *Service) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
db := service.connection.GetDB()
role.ID = ID
tx := db.Save(&role)
if tx.Error != nil {
return tx.Error
}
return nil
}

View File

@@ -2,20 +2,64 @@ package role
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// Service represents a service for managing environment(endpoint) data.
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.Role, portainer.RoleID]
service *Service
tx portainer.Transaction
}
// Role returns a Role by ID
func (service ServiceTx) Role(ID portainer.RoleID) (*portainer.Role, error) {
var set portainer.Role
// identifier := service.service.connection.ConvertToKey(int(ID))
// err := service.tx.GetObject(BucketName, identifier, &set)
// if err != nil {
// return nil, err
// }
return &set, nil
}
// Roles returns an array containing all the sets.
func (service ServiceTx) Roles() ([]portainer.Role, error) {
var sets = make([]portainer.Role, 0)
// err := service.tx.GetAll(
// BucketName,
// &portainer.Role{},
// 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")
// return nil, fmt.Errorf("failed to convert to Role object: %s", obj)
// }
// sets = append(sets, *set)
// return &portainer.Role{}, nil
// })
return sets, nil
}
// CreateRole creates a new Role.
func (service ServiceTx) Create(role *portainer.Role) error {
return service.Tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
role.ID = portainer.RoleID(id)
return int(role.ID), role
},
)
// return service.tx.CreateObject(
// BucketName,
// func(id uint64) (int, interface{}) {
// role.ID = portainer.RoleID(id)
// return int(role.ID), role
// },
// )
return nil
}
// UpdateRole updates a role.
func (service ServiceTx) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.UpdateObject(BucketName, identifier, role)
return nil
}

View File

@@ -2,11 +2,12 @@ package schedule
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "schedules"
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "schedules"
)
// Service represents a service for managing schedule data.
type Service struct {
@@ -19,10 +20,10 @@ func (service *Service) BucketName() string {
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
// err := connection.SetServiceName(BucketName)
// if err != nil {
// return nil, err
// }
return &Service{
connection: connection,
@@ -32,37 +33,50 @@ func NewService(connection portainer.Connection) (*Service, error) {
// Schedule returns a schedule by ID.
func (service *Service) Schedule(ID portainer.ScheduleID) (*portainer.Schedule, error) {
var schedule portainer.Schedule
identifier := service.connection.ConvertToKey(int(ID))
// identifier := service.connection.ConvertToKey(int(ID))
err := service.connection.GetObject(BucketName, identifier, &schedule)
if err != nil {
return nil, err
}
// err := service.connection.GetObject(BucketName, identifier, &schedule)
// if err != nil {
// return nil, err
// }
return &schedule, nil
}
// UpdateSchedule updates a schedule.
func (service *Service) UpdateSchedule(ID portainer.ScheduleID, schedule *portainer.Schedule) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.UpdateObject(BucketName, identifier, schedule)
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.UpdateObject(BucketName, identifier, schedule)
return nil
}
// DeleteSchedule deletes a schedule.
func (service *Service) DeleteSchedule(ID portainer.ScheduleID) error {
identifier := service.connection.ConvertToKey(int(ID))
return service.connection.DeleteObject(BucketName, identifier)
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.DeleteObject(BucketName, identifier)
return nil
}
// Schedules return a array containing all the schedules.
func (service *Service) Schedules() ([]portainer.Schedule, error) {
var schedules = make([]portainer.Schedule, 0)
return schedules, service.connection.GetAll(
BucketName,
&portainer.Schedule{},
dataservices.AppendFn(&schedules),
)
// err := service.connection.GetAll(
// BucketName,
// &portainer.Schedule{},
// 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")
// return nil, fmt.Errorf("Failed to convert to Schedule object: %s", obj)
// }
// schedules = append(schedules, *schedule)
// return &portainer.Schedule{}, nil
// })
return schedules, nil
}
// SchedulesByJobType return a array containing all the schedules
@@ -70,21 +84,28 @@ func (service *Service) Schedules() ([]portainer.Schedule, error) {
func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portainer.Schedule, error) {
var schedules = make([]portainer.Schedule, 0)
return schedules, service.connection.GetAll(
BucketName,
&portainer.Schedule{},
dataservices.FilterFn(&schedules, func(e portainer.Schedule) bool {
return e.JobType == jobType
}),
)
// err := service.connection.GetAll(
// BucketName,
// &portainer.Schedule{},
// 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")
// return nil, fmt.Errorf("Failed to convert to Schedule object: %s", obj)
// }
// if schedule.JobType == jobType {
// schedules = append(schedules, *schedule)
// }
// return &portainer.Schedule{}, nil
// })
return schedules, nil
}
// 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)
}
// GetNextIdentifier returns the next identifier for a schedule.
func (service *Service) GetNextIdentifier() int {
return service.connection.GetNextIdentifier(BucketName)
// return service.connection.CreateObjectWithId(BucketName, int(schedule.ID), schedule)
return nil
}

View File

@@ -15,17 +15,8 @@ type Service struct {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
}, nil
@@ -40,9 +31,9 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
// Settings retrieve the settings object.
func (service *Service) Settings() (*portainer.Settings, error) {
var settings portainer.Settings
settings := portainer.Settings{ID: 1}
err := service.connection.GetObject(BucketName, []byte(settingsKey), &settings)
err := service.connection.GetByID(1, &settings)
if err != nil {
return nil, err
}
@@ -52,5 +43,10 @@ func (service *Service) Settings() (*portainer.Settings, error) {
// UpdateSettings persists a Settings object.
func (service *Service) UpdateSettings(settings *portainer.Settings) error {
return service.connection.UpdateObject(BucketName, []byte(settingsKey), settings)
db := service.connection.GetDB()
tx := db.Model(&portainer.Settings{}).Where(portainer.Settings{ID: 1}).Save(settings)
if tx.Error != nil {
return tx.Error
}
return nil
}

View File

@@ -2,7 +2,6 @@ package snapshot
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
const (
@@ -10,33 +9,51 @@ const (
)
type Service struct {
dataservices.BaseDataService[portainer.Snapshot, portainer.EndpointID]
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{
BaseDataService: dataservices.BaseDataService[portainer.Snapshot, portainer.EndpointID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.Snapshot, portainer.EndpointID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
service: service,
tx: tx,
}
}
func (service *Service) Create(snapshot *portainer.Snapshot) error {
return service.Connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
func (service *Service) Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error) {
var snapshot portainer.Snapshot
return &snapshot, nil
}
func (service *Service) Snapshots() ([]portainer.Snapshot, error) {
var snapshots = make([]portainer.Snapshot, 0)
return snapshots, nil
}
func (service *Service) UpdateSnapshot(snapshot *portainer.Snapshot) error {
// identifier := service.connection.ConvertToKey(int(snapshot.EndpointID))
// return service.connection.UpdateObject(BucketName, identifier, snapshot)
return nil
}
func (service *Service) DeleteSnapshot(endpointID portainer.EndpointID) error {
// identifier := service.connection.ConvertToKey(int(endpointID))
// return service.connection.DeleteObject(BucketName, identifier)
return nil
}
func (service *Service) Create(snapshot *portainer.Snapshot) error {
// return service.connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
return nil
}

View File

@@ -2,13 +2,61 @@ package snapshot
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.Snapshot, portainer.EndpointID]
service *Service
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
func (service ServiceTx) Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error) {
var snapshot portainer.Snapshot
// identifier := service.service.connection.ConvertToKey(int(endpointID))
// err := service.tx.GetObject(BucketName, identifier, &snapshot)
// if err != nil {
// return nil, err
// }
return &snapshot, nil
}
func (service ServiceTx) Snapshots() ([]portainer.Snapshot, error) {
var snapshots = make([]portainer.Snapshot, 0)
// err := service.tx.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, nil
}
func (service ServiceTx) UpdateSnapshot(snapshot *portainer.Snapshot) error {
// identifier := service.service.connection.ConvertToKey(int(snapshot.EndpointID))
// return service.tx.UpdateObject(BucketName, identifier, snapshot)
return nil
}
func (service ServiceTx) DeleteSnapshot(endpointID portainer.EndpointID) error {
// identifier := service.service.connection.ConvertToKey(int(endpointID))
// return service.tx.DeleteObject(BucketName, identifier)
return nil
}
func (service ServiceTx) Create(snapshot *portainer.Snapshot) error {
return service.Tx.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
// return service.tx.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
return nil
}

View File

@@ -4,28 +4,13 @@ import (
portainer "github.com/portainer/portainer/api"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "ssl"
key = "SSL"
)
// Service represents a service for managing ssl data.
type Service struct {
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
}, nil
@@ -33,17 +18,22 @@ func NewService(connection portainer.Connection) (*Service, error) {
// Settings retrieve the ssl settings object.
func (service *Service) Settings() (*portainer.SSLSettings, error) {
var settings portainer.SSLSettings
err := service.connection.GetObject(BucketName, []byte(key), &settings)
var obj portainer.SSLSettings
err := service.connection.GetByID(1, &obj)
if err != nil {
return nil, err
}
return &settings, nil
return &obj, nil
}
// UpdateSettings persists a SSLSettings object.
func (service *Service) UpdateSettings(settings *portainer.SSLSettings) error {
return service.connection.UpdateObject(BucketName, []byte(key), settings)
db := service.connection.GetDB()
settings.ID = 1
tx := db.Save(settings)
if tx.Error != nil {
return tx.Error
}
return nil
}

View File

@@ -1,123 +1,136 @@
package stack
import (
"errors"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "stacks"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
dataservices.BaseDataService[portainer.Stack, portainer.StackID]
connection portainer.Connection
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
return &Service{
connection: connection,
}, nil
}
// Stack returns a stack object by ID.
func (service *Service) Stack(ID portainer.StackID) (*portainer.Stack, error) {
var obj portainer.Stack
err := service.connection.GetByID(int(ID), &obj)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.Stack, portainer.StackID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: service.BaseDataService.Tx(tx),
}
return &obj, nil
}
// StackByName returns a stack object by name.
func (service *Service) StackByName(name string) (*portainer.Stack, error) {
var s portainer.Stack
err := service.Connection.GetAll(
BucketName,
&portainer.Stack{},
dataservices.FirstFn(&s, func(e portainer.Stack) bool {
return e.Name == name
}),
)
db := service.connection.GetDB()
tx := db.First(&s, `name = ?`, name)
if errors.Is(err, dataservices.ErrStop) {
return &s, nil
if tx.Error != nil {
return nil, tx.Error
}
if err == nil {
return nil, dserrors.ErrObjectNotFound
}
return nil, err
return &s, nil
}
// Stacks returns an array containing all the stacks with same name
func (service *Service) StacksByName(name string) ([]portainer.Stack, error) {
var stacks = make([]portainer.Stack, 0)
return stacks, service.Connection.GetAll(
BucketName,
&portainer.Stack{},
dataservices.FilterFn(&stacks, func(e portainer.Stack) bool {
return e.Name == name
}),
)
db := service.connection.GetDB()
tx := db.Find(&stacks, `name = ?`, name)
if tx.Error != nil {
return nil, tx.Error
}
return stacks, nil
}
// GetNextIdentifier returns the next identifier for a stack.
func (service *Service) GetNextIdentifier() int {
return service.Connection.GetNextIdentifier(BucketName)
// Stacks returns an array containing all the stacks.
func (service *Service) Stacks() ([]portainer.Stack, error) {
var stacks = make([]portainer.Stack, 0)
db := service.connection.GetDB()
tx := db.Find(&stacks)
if tx.Error != nil {
return nil, tx.Error
}
return stacks, nil
}
// CreateStack creates a new stack.
func (service *Service) Create(stack *portainer.Stack) error {
return service.Connection.CreateObjectWithId(BucketName, int(stack.ID), stack)
db := service.connection.GetDB()
tx := db.Create(&stack)
if tx.Error != nil {
return tx.Error
}
return nil
}
// UpdateStack updates a stack.
func (service *Service) UpdateStack(ID portainer.StackID, stack *portainer.Stack) error {
db := service.connection.GetDB()
stack.ID = ID
tx := db.Save(&stack)
if tx.Error != nil {
return tx.Error
}
return nil
}
// DeleteStack deletes a stack.
func (service *Service) DeleteStack(ID portainer.StackID) error {
return service.connection.DeleteByID(int(ID), &portainer.Stack{})
}
// StackByWebhookID returns a pointer to a stack object by webhook ID.
// It returns nil, errors.ErrObjectNotFound if there's no stack associated with the webhook ID.
func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) {
var s portainer.Stack
db := service.connection.GetDB()
err := service.Connection.GetAll(
BucketName,
&portainer.Stack{},
dataservices.FirstFn(&s, func(e portainer.Stack) bool {
return e.AutoUpdate != nil && strings.EqualFold(e.AutoUpdate.Webhook, id)
}),
)
if errors.Is(err, dataservices.ErrStop) {
return &s, nil
var stacks = make([]portainer.Stack, 0)
tx := db.Find(&stacks)
if tx.Error != nil {
return nil, tx.Error
}
if err == nil {
return nil, dserrors.ErrObjectNotFound
for _, stack := range stacks {
if stack.AutoUpdate != nil && stack.AutoUpdate.Webhook != "" && stack.AutoUpdate.Webhook == id {
return &stack, nil
}
}
return nil, err
return nil, nil
}
// RefreshableStacks returns stacks that are configured for a periodic update
func (service *Service) RefreshableStacks() ([]portainer.Stack, error) {
stacks := make([]portainer.Stack, 0)
return stacks, service.Connection.GetAll(
BucketName,
&portainer.Stack{},
dataservices.FilterFn(&stacks, func(e portainer.Stack) bool {
return e.AutoUpdate != nil && e.AutoUpdate.Interval != ""
}),
)
db := service.connection.GetDB()
var stacks = make([]portainer.Stack, 0)
var stacksRes = make([]portainer.Stack, 0)
tx := db.Find(&stacks)
if tx.Error != nil {
return nil, tx.Error
}
for _, stack := range stacks {
if stack.AutoUpdate != nil && stack.AutoUpdate.Interval != "" {
stacksRes = append(stacksRes, stack)
}
}
return stacksRes, nil
}

View File

@@ -58,7 +58,7 @@ func (b *stackBuilder) createNewStack(webhookID string) portainer.Stack {
Type: portainer.DockerComposeStack,
EndpointID: 2,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: []portainer.Pair{{Name: "Name1", Value: "Value1"}},
Env: portainer.MultiPair{{Name: "Name1", Value1: "Value1"}},
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
ProjectPath: "/tmp/project",

View File

@@ -1,98 +0,0 @@
package stack
import (
"errors"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.Stack, portainer.StackID]
}
// StackByName returns a stack object by name.
func (service ServiceTx) StackByName(name string) (*portainer.Stack, error) {
var s portainer.Stack
err := service.Tx.GetAll(
BucketName,
&portainer.Stack{},
dataservices.FirstFn(&s, func(e portainer.Stack) bool {
return e.Name == name
}),
)
if errors.Is(err, dataservices.ErrStop) {
return &s, nil
}
if err == nil {
return nil, dserrors.ErrObjectNotFound
}
return nil, err
}
// Stacks returns an array containing all the stacks with same name
func (service ServiceTx) StacksByName(name string) ([]portainer.Stack, error) {
var stacks = make([]portainer.Stack, 0)
return stacks, service.Tx.GetAll(
BucketName,
&portainer.Stack{},
dataservices.FilterFn(&stacks, func(e portainer.Stack) bool {
return e.Name == name
}),
)
}
// GetNextIdentifier returns the next identifier for a stack.
func (service ServiceTx) GetNextIdentifier() int {
return service.Tx.GetNextIdentifier(BucketName)
}
// CreateStack creates a new stack.
func (service ServiceTx) Create(stack *portainer.Stack) error {
return service.Tx.CreateObjectWithId(BucketName, int(stack.ID), stack)
}
// StackByWebhookID returns a pointer to a stack object by webhook ID.
// It returns nil, errors.ErrObjectNotFound if there's no stack associated with the webhook ID.
func (service ServiceTx) StackByWebhookID(id string) (*portainer.Stack, error) {
var s portainer.Stack
err := service.Tx.GetAll(
BucketName,
&portainer.Stack{},
dataservices.FirstFn(&s, func(e portainer.Stack) bool {
return e.AutoUpdate != nil && strings.EqualFold(e.AutoUpdate.Webhook, id)
}),
)
if errors.Is(err, dataservices.ErrStop) {
return &s, nil
}
if err == nil {
return nil, dserrors.ErrObjectNotFound
}
return nil, err
}
// RefreshableStacks returns stacks that are configured for a periodic update
func (service ServiceTx) RefreshableStacks() ([]portainer.Stack, error) {
stacks := make([]portainer.Stack, 0)
return stacks, service.Tx.GetAll(
BucketName,
&portainer.Stack{},
dataservices.FilterFn(&stacks, func(e portainer.Stack) bool {
return e.AutoUpdate != nil && e.AutoUpdate.Interval != ""
}),
)
}

View File

@@ -2,7 +2,6 @@ package tag
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
const (
@@ -12,51 +11,96 @@ const (
// Service represents a service for managing environment(endpoint) data.
type Service struct {
dataservices.BaseDataService[portainer.Tag, portainer.TagID]
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.Tag, portainer.TagID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.Tag, portainer.TagID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
service: service,
tx: tx,
}
}
// Tags return an array containing all the tags.
func (service *Service) Tags() ([]portainer.Tag, error) {
var tags = make([]portainer.Tag, 0)
// err := service.connection.GetAll(
// BucketName,
// &portainer.Tag{},
// 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")
// return nil, fmt.Errorf("Failed to convert to Tag object: %s", obj)
// }
// tags = append(tags, *tag)
// return &portainer.Tag{}, nil
// })
return tags, nil
}
// Tag returns a tag by ID.
func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) {
var tag portainer.Tag
// identifier := service.connection.ConvertToKey(int(ID))
// err := service.connection.GetObject(BucketName, identifier, &tag)
// if err != nil {
// return nil, err
// }
return &tag, nil
}
// CreateTag creates a new tag.
func (service *Service) Create(tag *portainer.Tag) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
tag.ID = portainer.TagID(id)
return int(tag.ID), tag
},
)
// return service.connection.CreateObject(
// BucketName,
// func(id uint64) (int, interface{}) {
// tag.ID = portainer.TagID(id)
// return int(tag.ID), tag
// },
// )
return nil
}
// Deprecated: Use UpdateTagFunc instead.
func (service *Service) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.UpdateObject(BucketName, identifier, tag)
return nil
}
// 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{}
// id := service.connection.ConvertToKey(int(ID))
// tag := &portainer.Tag{}
return service.Connection.UpdateObjectFunc(BucketName, id, tag, func() {
updateFunc(tag)
})
// return service.connection.UpdateObjectFunc(BucketName, id, tag, func() {
// updateFunc(tag)
// })
return nil
}
// DeleteTag deletes a tag.
func (service *Service) DeleteTag(ID portainer.TagID) error {
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.DeleteObject(BucketName, identifier)
return nil
}

View File

@@ -2,18 +2,60 @@ package tag
import (
"errors"
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.Tag, portainer.TagID]
service *Service
tx portainer.Transaction
}
func (service ServiceTx) BucketName() string {
return BucketName
}
// Tags return an array containing all the tags.
func (service ServiceTx) Tags() ([]portainer.Tag, error) {
var tags = make([]portainer.Tag, 0)
err := service.tx.GetAll(
BucketName,
&portainer.Tag{},
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")
return nil, fmt.Errorf("failed to convert to Tag object: %s", obj)
}
tags = append(tags, *tag)
return &portainer.Tag{}, nil
})
return tags, err
}
// Tag returns a tag by ID.
func (service ServiceTx) Tag(ID portainer.TagID) (*portainer.Tag, error) {
var tag portainer.Tag
// identifier := service.service.connection.ConvertToKey(int(ID))
// err := service.tx.GetObject(BucketName, identifier, &tag)
// if err != nil {
// return nil, err
// }
return &tag, nil
}
// CreateTag creates a new tag.
func (service ServiceTx) Create(tag *portainer.Tag) error {
return service.Tx.CreateObject(
return service.tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
tag.ID = portainer.TagID(id)
@@ -22,7 +64,21 @@ func (service ServiceTx) Create(tag *portainer.Tag) error {
)
}
// UpdateTag updates a tag
func (service ServiceTx) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.UpdateObject(BucketName, identifier, tag)
return nil
}
// UpdateTagFunc is a no-op inside a transaction
func (service ServiceTx) UpdateTagFunc(ID portainer.TagID, updateFunc func(tag *portainer.Tag)) error {
return errors.New("cannot be called inside a transaction")
}
// DeleteTag deletes a tag.
func (service ServiceTx) DeleteTag(ID portainer.TagID) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.DeleteObject(BucketName, identifier)
return nil
}

View File

@@ -1,67 +1,91 @@
package team
import (
"errors"
"strings"
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "teams"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
dataservices.BaseDataService[portainer.Team, portainer.TeamID]
connection portainer.Connection
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
return &Service{
connection: connection,
}, nil
}
// Team returns a Team by ID
func (service *Service) Team(ID portainer.TeamID) (*portainer.Team, error) {
var obj portainer.Team
err := service.connection.GetByID(int(ID), &obj)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.Team, portainer.TeamID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
return &obj, nil
}
// TeamByName returns a team by name.
func (service *Service) TeamByName(name string) (*portainer.Team, error) {
var t portainer.Team
var team portainer.Team
err := service.Connection.GetAll(
BucketName,
&portainer.Team{},
dataservices.FirstFn(&t, func(e portainer.Team) bool {
return strings.EqualFold(e.Name, name)
}),
)
if errors.Is(err, dataservices.ErrStop) {
return &t, nil
db := service.connection.GetDB()
tx := db.First(&team, `name = ?`, name)
if tx.Error != nil {
return nil, tx.Error
}
if err == nil {
return nil, dserrors.ErrObjectNotFound
return &team, nil
}
// Teams return an array containing all the teams.
func (service *Service) Teams() ([]portainer.Team, error) {
var teams = make([]portainer.Team, 0)
db := service.connection.GetDB()
tx := db.Find(&teams)
if tx.Error != nil {
return nil, tx.Error
}
return nil, err
return teams, nil
}
// UpdateTeam saves a Team.
func (service *Service) UpdateTeam(ID portainer.TeamID, team *portainer.Team) error {
db := service.connection.GetDB()
team.ID = ID
tx := db.Save(&team)
if tx.Error != nil {
return tx.Error
}
return nil
}
// CreateTeam creates a new Team.
func (service *Service) Create(team *portainer.Team) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
team.ID = portainer.TeamID(id)
return int(team.ID), team
},
)
db := service.connection.GetDB()
tx := db.Create(&team)
if tx.Error != nil {
fmt.Println(tx.Error)
return tx.Error
}
return nil
}
// DeleteTeam deletes a Team.
func (service *Service) DeleteTeam(ID portainer.TeamID) error {
db := service.connection.GetDB()
tx := db.Model(&portainer.Team{}).Delete("id = ?", ID)
if tx.Error != nil {
return tx.Error
}
return nil
}

View File

@@ -1,142 +1,120 @@
package teammembership
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "team_membership"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
dataservices.BaseDataService[portainer.TeamMembership, portainer.TeamMembershipID]
connection portainer.Connection
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.TeamMembership, portainer.TeamMembershipID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.TeamMembership, portainer.TeamMembershipID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
service: service,
tx: tx,
}
}
// TeamMembership returns a TeamMembership object by ID
func (service *Service) TeamMembership(ID portainer.TeamMembershipID) (*portainer.TeamMembership, error) {
var obj portainer.TeamMembership
err := service.connection.GetByID(int(ID), &obj)
if err != nil {
return nil, err
}
return &obj, nil
}
// TeamMemberships return an array containing all the TeamMembership objects.
func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
var memberships = make([]portainer.TeamMembership, 0)
db := service.connection.GetDB()
tx := db.Find(&memberships)
if tx.Error != nil {
return nil, tx.Error
}
return memberships, nil
}
// TeamMembershipsByUserID return an array containing all the TeamMembership objects where the specified userID is present.
func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error) {
var memberships = make([]portainer.TeamMembership, 0)
return memberships, service.Connection.GetAll(
BucketName,
&portainer.TeamMembership{},
dataservices.FilterFn(&memberships, func(e portainer.TeamMembership) bool {
return e.UserID == userID
}),
)
db := service.connection.GetDB()
tx := db.Find(&memberships, `user_id = ?`, userID)
if tx.Error != nil {
return nil, tx.Error
}
return memberships, nil
}
// TeamMembershipsByTeamID return an array containing all the TeamMembership objects where the specified teamID is present.
func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error) {
var memberships = make([]portainer.TeamMembership, 0)
return memberships, service.Connection.GetAll(
BucketName,
&portainer.TeamMembership{},
dataservices.FilterFn(&memberships, func(e portainer.TeamMembership) bool {
return e.TeamID == teamID
}),
)
db := service.connection.GetDB()
tx := db.Find(&memberships, `team_id = ?`, teamID)
if tx.Error != nil {
return nil, tx.Error
}
return memberships, nil
}
// UpdateTeamMembership saves a TeamMembership object.
func (service *Service) UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error {
db := service.connection.GetDB()
membership.ID = ID
tx := db.Save(&membership)
if tx.Error != nil {
return tx.Error
}
return nil
}
// CreateTeamMembership creates a new TeamMembership object.
func (service *Service) Create(membership *portainer.TeamMembership) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
membership.ID = portainer.TeamMembershipID(id)
return int(membership.ID), membership
},
)
db := service.connection.GetDB()
tx := db.Create(&membership)
if tx.Error != nil {
return tx.Error
}
return nil
}
// DeleteTeamMembership deletes a TeamMembership object.
func (service *Service) DeleteTeamMembership(ID portainer.TeamMembershipID) error {
return service.connection.DeleteByID(int(ID), &portainer.TeamMembership{})
}
// DeleteTeamMembershipByUserID deletes all the TeamMembership object associated to a UserID.
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")
//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
})
db := service.connection.GetDB()
return db.Model(&portainer.TeamMembership{}).Delete("user_id = ?", userID).Error
}
// DeleteTeamMembershipByTeamID deletes all the TeamMembership object associated to a TeamID.
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")
//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
})
db := service.connection.GetDB()
return db.Model(&portainer.TeamMembership{}).Delete("team_id = ?", teamID).Error
}
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
})
db := service.connection.GetDB()
return db.Model(&portainer.TeamMembership{}).Delete("team_id = ? AND user_id = ?", teamID, userID).Error
}

View File

@@ -1,113 +1,184 @@
package teammembership
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.TeamMembership, portainer.TeamMembershipID]
service *Service
tx portainer.Transaction
}
// TeamMembership returns a TeamMembership object by ID
func (service ServiceTx) TeamMembership(ID portainer.TeamMembershipID) (*portainer.TeamMembership, error) {
var membership portainer.TeamMembership
// identifier := service.service.connection.ConvertToKey(int(ID))
// err := service.tx.GetObject(BucketName, identifier, &membership)
// if err != nil {
// return nil, err
// }
return &membership, nil
}
// TeamMemberships return an array containing all the TeamMembership objects.
func (service ServiceTx) TeamMemberships() ([]portainer.TeamMembership, error) {
var memberships = make([]portainer.TeamMembership, 0)
// err := service.tx.GetAll(
// BucketName,
// &portainer.TeamMembership{},
// 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")
// return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
// }
// memberships = append(memberships, *membership)
// return &portainer.TeamMembership{}, nil
// })
return memberships, nil
}
// TeamMembershipsByUserID return an array containing all the TeamMembership objects where the specified userID is present.
func (service ServiceTx) TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error) {
var memberships = make([]portainer.TeamMembership, 0)
return memberships, service.Tx.GetAll(
BucketName,
&portainer.TeamMembership{},
dataservices.FilterFn(&memberships, func(e portainer.TeamMembership) bool {
return e.UserID == userID
}),
)
// err := service.tx.GetAll(
// BucketName,
// &portainer.TeamMembership{},
// 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")
// return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
// }
// if membership.UserID == userID {
// memberships = append(memberships, *membership)
// }
// return &portainer.TeamMembership{}, nil
// })
return memberships, nil
}
// TeamMembershipsByTeamID return an array containing all the TeamMembership objects where the specified teamID is present.
func (service ServiceTx) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error) {
var memberships = make([]portainer.TeamMembership, 0)
return memberships, service.Tx.GetAll(
BucketName,
&portainer.TeamMembership{},
dataservices.FilterFn(&memberships, func(e portainer.TeamMembership) bool {
return e.TeamID == teamID
}),
)
// err := service.tx.GetAll(
// BucketName,
// &portainer.TeamMembership{},
// 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")
// return nil, fmt.Errorf("Failed to convert to TeamMembership object: %s", obj)
// }
// if membership.TeamID == teamID {
// memberships = append(memberships, *membership)
// }
// return &portainer.TeamMembership{}, nil
// })
return memberships, nil
}
// UpdateTeamMembership saves a TeamMembership object.
func (service ServiceTx) UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.UpdateObject(BucketName, identifier, membership)
return nil
}
// CreateTeamMembership creates a new TeamMembership object.
func (service ServiceTx) Create(membership *portainer.TeamMembership) error {
return service.Tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
membership.ID = portainer.TeamMembershipID(id)
return int(membership.ID), membership
},
)
// return service.tx.CreateObject(
// BucketName,
// func(id uint64) (int, interface{}) {
// membership.ID = portainer.TeamMembershipID(id)
// return int(membership.ID), membership
// },
// )
return nil
}
// DeleteTeamMembership deletes a TeamMembership object.
func (service ServiceTx) DeleteTeamMembership(ID portainer.TeamMembershipID) error {
// identifier := service.service.connection.ConvertToKey(int(ID))
// return service.tx.DeleteObject(BucketName, identifier)
return nil
}
// DeleteTeamMembershipByUserID deletes all the TeamMembership object associated to a UserID.
func (service ServiceTx) DeleteTeamMembershipByUserID(userID portainer.UserID) error {
return service.Tx.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
}
// return service.tx.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.UserID == userID {
return int(membership.ID), true
}
// if membership.UserID == userID {
// return int(membership.ID), true
// }
return -1, false
})
// return -1, false
// })
return nil
}
// DeleteTeamMembershipByTeamID deletes all the TeamMembership object associated to a TeamID.
func (service ServiceTx) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error {
return service.Tx.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
}
// return service.tx.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 {
return int(membership.ID), true
}
// if membership.TeamID == teamID {
// return int(membership.ID), true
// }
return -1, false
})
// return -1, false
// })
return nil
}
func (service ServiceTx) DeleteTeamMembershipByTeamIDAndUserID(teamID portainer.TeamID, userID portainer.UserID) error {
return service.Tx.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
}
// return service.tx.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
}
// if membership.TeamID == teamID && membership.UserID == userID {
// return int(membership.ID), true
// }
return -1, false
})
// return -1, false
// })
return nil
}

View File

@@ -21,11 +21,6 @@ func (service *Service) BucketName() string {
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
}, nil
@@ -35,15 +30,16 @@ func NewService(connection portainer.Connection) (*Service, error) {
func (service *Service) Info() (*portainer.TunnelServerInfo, error) {
var info portainer.TunnelServerInfo
err := service.connection.GetObject(BucketName, []byte(infoKey), &info)
if err != nil {
return nil, err
}
// err := service.connection.GetObject(BucketName, []byte(infoKey), &info)
// if err != nil {
// return nil, err
// }
return &info, nil
}
// UpdateInfo persists a TunnelServerInfo object.
func (service *Service) UpdateInfo(settings *portainer.TunnelServerInfo) error {
return service.connection.UpdateObject(BucketName, []byte(infoKey), settings)
// return service.connection.UpdateObject(BucketName, []byte(infoKey), settings)
return nil
}

View File

@@ -1,63 +0,0 @@
package user
import (
"errors"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
)
type ServiceTx struct {
dataservices.BaseDataServiceTx[portainer.User, portainer.UserID]
}
// UserByUsername returns a user by username.
func (service ServiceTx) UserByUsername(username string) (*portainer.User, error) {
var u portainer.User
err := service.Tx.GetAll(
BucketName,
&portainer.User{},
dataservices.FirstFn(&u, func(e portainer.User) bool {
return strings.EqualFold(e.Username, username)
}),
)
if errors.Is(err, dataservices.ErrStop) {
return &u, nil
}
if err == nil {
return nil, dserrors.ErrObjectNotFound
}
return nil, err
}
// UsersByRole return an array containing all the users with the specified role.
func (service ServiceTx) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
var users = make([]portainer.User, 0)
return users, service.Tx.GetAll(
BucketName,
&portainer.User{},
dataservices.FilterFn(&users, func(e portainer.User) bool {
return e.Role == role
}),
)
}
// CreateUser creates a new user.
func (service ServiceTx) Create(user *portainer.User) error {
return service.Tx.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
user.ID = portainer.UserID(id)
user.Username = strings.ToLower(user.Username)
return int(user.ID), user
},
)
}

View File

@@ -1,92 +1,96 @@
package user
import (
"errors"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "users"
// Service represents a service for managing environment(endpoint) data.
type Service struct {
dataservices.BaseDataService[portainer.User, portainer.UserID]
connection portainer.Connection
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
return &Service{
connection: connection,
}, nil
}
// User returns a user by ID
func (service *Service) User(ID portainer.UserID) (*portainer.User, error) {
var obj portainer.User
err := service.connection.GetByID(int(ID), &obj)
if err != nil {
return nil, err
}
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.User, portainer.UserID]{
Bucket: BucketName,
Connection: connection,
},
}, nil
}
func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
return ServiceTx{
BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.User, portainer.UserID]{
Bucket: BucketName,
Connection: service.Connection,
Tx: tx,
},
}
return &obj, nil
}
// UserByUsername returns a user by username.
func (service *Service) UserByUsername(username string) (*portainer.User, error) {
var u portainer.User
err := service.Connection.GetAll(
BucketName,
&portainer.User{},
dataservices.FirstFn(&u, func(e portainer.User) bool {
return strings.EqualFold(e.Username, username)
}),
)
db := service.connection.GetDB()
tx := db.First(&u, `username = ?`, username)
if errors.Is(err, dataservices.ErrStop) {
return &u, nil
if tx.Error != nil {
return nil, tx.Error
}
if err == nil {
return nil, dserrors.ErrObjectNotFound
return &u, nil
}
// Users return an array containing all the users.
func (service *Service) Users() ([]portainer.User, error) {
var users = make([]portainer.User, 0)
db := service.connection.GetDB()
tx := db.Find(&users)
if tx.Error != nil {
return nil, tx.Error
}
return nil, err
return users, nil
}
// UsersByRole return an array containing all the users with the specified role.
func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
var users = make([]portainer.User, 0)
return users, service.Connection.GetAll(
BucketName,
&portainer.User{},
dataservices.FilterFn(&users, func(e portainer.User) bool {
return e.Role == role
}),
)
db := service.connection.GetDB()
tx := db.Find(&users, `role = ?`, role)
if tx.Error != nil {
return nil, tx.Error
}
return users, nil
}
// UpdateUser saves a user.
func (service *Service) UpdateUser(ID portainer.UserID, user *portainer.User) error {
db := service.connection.GetDB()
user.ID = ID
tx := db.Save(&user)
if tx.Error != nil {
return tx.Error
}
return nil
}
// CreateUser creates a new user.
func (service *Service) Create(user *portainer.User) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
user.ID = portainer.UserID(id)
user.Username = strings.ToLower(user.Username)
return int(user.ID), user
},
)
db := service.connection.GetDB()
tx := db.Create(&user)
if tx.Error != nil {
return tx.Error
}
return nil
}
// DeleteUser deletes a user.
func (service *Service) DeleteUser(ID portainer.UserID) error {
return service.connection.DeleteByID(int(ID), &portainer.User{})
}

View File

@@ -1,19 +1,12 @@
package version
import (
"errors"
"fmt"
"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"
updatingKey = "DB_UPDATING"
"gorm.io/gorm"
)
// Service represents a service to manage stored versions.
@@ -23,97 +16,136 @@ type Service struct {
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
return &Service{
connection: connection,
}, nil
}
func (service *Service) SchemaVersion() (string, error) {
v, err := service.Version()
if err != nil {
return "", err
// DBVersion retrieves the stored database version.
func (service *Service) DBVersion() (int, error) {
db := service.connection.GetDB()
var version models.Version
tx := db.First(&version, `key = ?`, models.VersionKey)
if tx.Error != nil {
return 0, tx.Error
}
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)
return strconv.Atoi(version.Value)
}
// Edition retrieves the stored portainer edition.
func (service *Service) Edition() (portainer.SoftwareEdition, error) {
v, err := service.Version()
db := service.connection.GetDB()
var version models.Version
tx := db.First(&version, `key = ?`, models.EditionKey)
if tx.Error != nil {
return 0, tx.Error
}
e, err := strconv.Atoi(version.Value)
if err != nil {
return 0, err
}
fmt.Println(portainer.SoftwareEdition(e))
return portainer.SoftwareEdition(e), nil
}
return portainer.SoftwareEdition(v.Edition), nil
// StoreDBVersion store the database version.
func (service *Service) StoreDBVersion(v int) error {
db := service.connection.GetDB()
tx := db.Model(&models.Version{}).Where("key = ?", models.VersionKey).Update("value", strconv.FormatInt(int64(v), 10)).Limit(1)
if tx.Error != nil {
return tx.Error
}
return nil
}
// IsUpdating retrieves the database updating status.
func (service *Service) IsUpdating() (bool, error) {
var isUpdating bool
err := service.connection.GetObject(BucketName, []byte(updatingKey), &isUpdating)
if err != nil && errors.Is(err, dserrors.ErrObjectNotFound) {
return false, nil
db := service.connection.GetDB()
var version models.Version
tx := db.First(&version, `key = ?`, models.UpdatingKey)
if tx.Error != nil {
return false, tx.Error
}
return isUpdating, err
return version.Value == "true", nil
}
// StoreIsUpdating store the database updating status.
func (service *Service) StoreIsUpdating(isUpdating bool) error {
return service.connection.DeleteObject(BucketName, []byte(updatingKey))
db := service.connection.GetDB()
tx := db.Model(&models.Version{}).Where("key = ?", models.UpdatingKey).Update("value", strconv.FormatBool(isUpdating)).Limit(1)
if tx.Error != nil {
return tx.Error
}
return nil
}
// InstanceID retrieves the stored instance ID.
func (service *Service) InstanceID() (string, error) {
v, err := service.Version()
if err != nil {
return "", err
db := service.connection.GetDB()
var version models.Version
tx := db.First(&version, `key = ?`, models.InstanceKey)
if tx.Error != nil {
return "", tx.Error
}
return v.InstanceID, nil
return version.Value, nil
}
// 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
}
v = &models.Version{}
func (service *Service) StoreInstanceID(ID string) error {
db := service.connection.GetDB()
tx := db.FirstOrCreate(&models.Version{Key: models.InstanceKey, Value: ID})
if tx.Error != nil {
return tx.Error
}
v.InstanceID = id
return service.UpdateVersion(v)
return nil
}
// 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
db := service.connection.GetDB()
var version models.Version
tx := db.First(&version, `key = ?`, models.VersionKey)
if tx.Error != nil {
return nil, tx.Error
}
return &v, nil
return &version, nil
}
// UpdateVersion persists a Version object.
func (service *Service) UpdateVersion(version *models.Version) error {
return service.connection.UpdateObject(BucketName, []byte(versionKey), version)
db := service.connection.GetDB()
tx := db.Model(&models.Version{}).Where("key = ?", models.VersionKey).Update("value", version).Limit(1)
if tx.Error != nil {
return tx.Error
}
return nil
}
// Version retrieve the version object.
func (service *Service) GetAll() (map[string]interface{}, error) {
db := service.connection.GetDB()
var all map[string]interface{}
tx := db.Find(&all)
if tx.Error != nil {
return nil, tx.Error
}
return all, nil
}
// Version retrieve the version object.
func (service *Service) UpdateAll(all map[string]interface{}) error {
db := service.connection.GetDB()
db.Transaction(func(tx *gorm.DB) error {
for k, v := range all {
tx := db.Model(&models.Version{}).Where(models.Version{Key: k}).FirstOrCreate(&models.Version{Key: k, Value: fmt.Sprintf("%v", v)})
if tx.Error != nil {
tx.Rollback()
return tx.Error
}
}
tx.Commit()
return nil
})
return nil
}

View File

@@ -1,89 +1,160 @@
package webhook
import (
"errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
)
// BucketName represents the name of the bucket where this service stores data.
const BucketName = "webhooks"
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "webhooks"
)
// Service represents a service for managing webhook data.
type Service struct {
dataservices.BaseDataService[portainer.Webhook, portainer.WebhookID]
connection portainer.Connection
}
func (service *Service) BucketName() string {
return BucketName
}
// NewService creates a new instance of a service.
func NewService(connection portainer.Connection) (*Service, error) {
err := connection.SetServiceName(BucketName)
if err != nil {
return nil, err
}
// err := connection.SetServiceName(BucketName)
// if err != nil {
// return nil, err
// }
return &Service{
BaseDataService: dataservices.BaseDataService[portainer.Webhook, portainer.WebhookID]{
Bucket: BucketName,
Connection: connection,
},
connection: connection,
}, nil
}
// Webhooks returns an array of all webhooks
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
var webhooks = make([]portainer.Webhook, 0)
// err := service.connection.GetAll(
// BucketName,
// &portainer.Webhook{},
// 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")
// return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
// }
// webhooks = append(webhooks, *webhook)
// return &portainer.Webhook{}, nil
// })
return webhooks, nil
}
// Webhook returns a webhook by ID.
func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, error) {
var webhook portainer.Webhook
// identifier := service.connection.ConvertToKey(int(ID))
// err := service.connection.GetObject(BucketName, identifier, &webhook)
// if err != nil {
// return nil, err
// }
return &webhook, nil
}
// WebhookByResourceID returns a webhook by the ResourceID it is associated with.
func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) {
var w portainer.Webhook
// var w *portainer.Webhook
// stop := fmt.Errorf("ok")
// err := service.connection.GetAll(
// BucketName,
// &portainer.Webhook{},
// 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")
err := service.Connection.GetAll(
BucketName,
&portainer.Webhook{},
dataservices.FirstFn(&w, func(e portainer.Webhook) bool {
return e.ResourceID == ID
}),
)
// return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
// }
if errors.Is(err, dataservices.ErrStop) {
return &w, nil
}
// if webhook.ResourceID == ID {
// w = webhook
// return nil, stop
// }
if err == nil {
return nil, dserrors.ErrObjectNotFound
}
// return &portainer.Webhook{}, nil
// })
return nil, err
// if errors.Is(err, stop) {
// return w, nil
// }
// if err == nil {
// return nil, dserrors.ErrObjectNotFound
// }
return nil, nil
}
// WebhookByToken returns a webhook by the random token it is associated with.
func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) {
var w portainer.Webhook
// var w *portainer.Webhook
// stop := fmt.Errorf("ok")
// err := service.connection.GetAll(
// BucketName,
// &portainer.Webhook{},
// 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")
err := service.Connection.GetAll(
BucketName,
&portainer.Webhook{},
dataservices.FirstFn(&w, func(e portainer.Webhook) bool {
return e.Token == token
}),
)
// return nil, fmt.Errorf("Failed to convert to Webhook object: %s", obj)
// }
if errors.Is(err, dataservices.ErrStop) {
return &w, nil
}
// if webhook.Token == token {
// w = webhook
// return nil, stop
// }
if err == nil {
return nil, dserrors.ErrObjectNotFound
}
// return &portainer.Webhook{}, nil
// })
return nil, err
// if errors.Is(err, stop) {
// return w, nil
// }
// if err == nil {
// return nil, dserrors.ErrObjectNotFound
// }
return nil, nil
}
// DeleteWebhook deletes a webhook.
func (service *Service) DeleteWebhook(ID portainer.WebhookID) error {
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.DeleteObject(BucketName, identifier)
return nil
}
// CreateWebhook assign an ID to a new webhook and saves it.
func (service *Service) Create(webhook *portainer.Webhook) error {
return service.Connection.CreateObject(
BucketName,
func(id uint64) (int, interface{}) {
webhook.ID = portainer.WebhookID(id)
return int(webhook.ID), webhook
},
)
// return service.connection.CreateObject(
// BucketName,
// func(id uint64) (int, interface{}) {
// webhook.ID = portainer.WebhookID(id)
// return int(webhook.ID), webhook
// },
// )
return nil
}
// UpdateWebhook update a webhook.
func (service *Service) UpdateWebhook(ID portainer.WebhookID, webhook *portainer.Webhook) error {
// identifier := service.connection.ConvertToKey(int(ID))
// return service.connection.UpdateObject(BucketName, identifier, webhook)
return nil
}

View File

@@ -1,77 +1,189 @@
package datastore
import (
"os"
"path"
// import (
// "fmt"
// "os"
// "path"
// "time"
"github.com/rs/zerolog/log"
)
// "github.com/portainer/portainer/api/database/models"
// "github.com/rs/zerolog/log"
// )
func (store *Store) Backup() (string, error) {
if err := store.createBackupPath(); err != nil {
return "", err
}
// var backupDefaults = struct {
// backupDir string
// commonDir string
// }{
// "backups",
// "common",
// }
backupFilename := store.backupFilename()
log.Info().Str("from", store.connection.GetDatabaseFilePath()).Str("to", backupFilename).Msgf("Backing up database")
err := store.fileService.Copy(store.connection.GetDatabaseFilePath(), backupFilename, true)
if err != nil {
log.Warn().Err(err).Msg("failed to create backup file")
return "", err
}
// //
// // Backup Helpers
// //
return backupFilename, nil
}
// // createBackupFolders create initial folders for backups
// func (store *Store) createBackupFolders() {
// // create common dir
// commonDir := store.commonBackupDir()
// if exists, _ := store.fileService.FileExists(commonDir); !exists {
// if err := os.MkdirAll(commonDir, 0700); err != nil {
// log.Error().Err(err).Msg("error while creating common backup folder")
// }
// }
// }
func (store *Store) Restore() error {
backupFilename := store.backupFilename()
return store.RestoreFromFile(backupFilename)
}
// func (store *Store) databasePath() string {
// return store.connection.GetDatabaseFilePath()
// }
func (store *Store) RestoreFromFile(backupFilename string) error {
if exists, _ := store.fileService.FileExists(backupFilename); !exists {
log.Error().Str("backupFilename", backupFilename).Msg("backup file does not exist")
return os.ErrNotExist
}
// func (store *Store) commonBackupDir() string {
// return path.Join(store.connection.GetStorePath(), backupDefaults.backupDir, backupDefaults.commonDir)
// }
if err := store.fileService.Copy(backupFilename, store.connection.GetDatabaseFilePath(), true); err != nil {
log.Error().Err(err).Msg("error while restoring backup.")
return err
}
// func (store *Store) copyDBFile(from string, to string) error {
// log.Info().Str("from", from).Str("to", to).Msg("copying DB file")
log.Info().Str("from", store.connection.GetDatabaseFilePath()).Str("to", backupFilename).Msgf("database restored")
// err := store.fileService.Copy(from, to, true)
// if err != nil {
// log.Error().Err(err).Msg("failed")
// }
// determine the db version
store.Open()
version, err := store.VersionService.Version()
// return err
// }
edition := "CE"
if version.Edition == 2 {
edition = "EE"
}
// // BackupOptions provide a helper to inject backup options
// type BackupOptions struct {
// Version string
// BackupDir string
// BackupFileName string
// BackupPath string
// }
if err == nil {
log.Info().Str("version", version.SchemaVersion).Msgf("Restored database version: Portainer %s %s", edition, version.SchemaVersion)
}
// // getBackupRestoreOptions returns options to store db at common backup dir location; used by:
// // - db backup prior to version upgrade
// // - db rollback
// func getBackupRestoreOptions(backupDir string) *BackupOptions {
// return &BackupOptions{
// BackupDir: backupDir, //connection.commonBackupDir(),
// BackupFileName: beforePortainerVersionUpgradeBackup,
// }
// }
return nil
}
// // Backup current database with default options
// func (store *Store) Backup(version *models.Version) (string, error) {
// if version == nil {
// return store.backupWithOptions(nil)
// }
func (store *Store) createBackupPath() error {
backupDir := path.Join(store.connection.GetStorePath(), "backups")
if exists, _ := store.fileService.FileExists(backupDir); !exists {
if err := os.MkdirAll(backupDir, 0700); err != nil {
log.Error().Err(err).Msg("error while creating backup folder")
return err
}
}
return nil
}
// return store.backupWithOptions(&BackupOptions{
// Version: version.SchemaVersion,
// })
// }
func (store *Store) backupFilename() string {
return path.Join(store.connection.GetStorePath(), "backups", store.connection.GetDatabaseFileName()+".bak")
}
// func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
// if options == nil {
// options = &BackupOptions{}
// }
// if options.Version == "" {
// v, err := store.VersionService.Version()
// if err != nil {
// options.Version = ""
// }
// options.Version = v.SchemaVersion
// }
// if options.BackupDir == "" {
// options.BackupDir = store.commonBackupDir()
// }
// if options.BackupFileName == "" {
// options.BackupFileName = fmt.Sprintf("%s.%s.%s", store.connection.GetDatabaseFileName(), options.Version, time.Now().Format("20060102150405"))
// }
// if options.BackupPath == "" {
// options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
// }
// return options
// }
func (store *Store) databasePath() string {
return store.connection.GetDatabaseFilePath()
}
// // BackupWithOptions backup current database with options
// func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
// log.Info().Msg("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: %w",
// 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: %w",
// err,
// )
// }
// return options.BackupPath, nil
// }
// // RestoreWithOptions previously saved backup for the current Edition with options
// // Restore strategies:
// // - default: restore latest from current edition
// // - restore a specific
// func (store *Store) restoreWithOptions(options *BackupOptions) error {
// options = store.setupOptions(options)
// // Check if backup file exist before restoring
// _, err := os.Stat(options.BackupPath)
// if os.IsNotExist(err) {
// log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to restore does not exist %s")
// return err
// }
// err = store.Close()
// if err != nil {
// log.Error().Err(err).Msg("error while closing store before restore")
// return err
// }
// log.Info().Msg("restoring DB backup")
// err = store.copyDBFile(options.BackupPath, store.databasePath())
// if err != nil {
// return err
// }
// _, err = store.Open()
// return err
// }
// // RemoveWithOptions removes backup database based on supplied options
// func (store *Store) removeWithOptions(options *BackupOptions) error {
// log.Info().Msg("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")
// return err
// }
// log.Info().Str("path", options.BackupPath).Msg("removing DB file")
// err = os.Remove(options.BackupPath)
// if err != nil {
// log.Error().Err(err).Msg("failed")
// return err
// }
// return nil
// }

View File

@@ -1,80 +1,107 @@
package datastore
import (
"fmt"
"testing"
// import (
// "fmt"
// "os"
// "path"
// "testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
// portainer "github.com/portainer/portainer/api"
// "github.com/portainer/portainer/api/database/models"
// )
"github.com/rs/zerolog/log"
)
// func TestCreateBackupFolders(t *testing.T) {
// _, store := MustNewTestStore(t, true, true)
func TestStoreCreation(t *testing.T) {
_, store := MustNewTestStore(t, true, true)
if store == nil {
t.Fatal("Expect to create a store")
}
// connection := store.GetConnection()
// backupPath := path.Join(connection.GetStorePath(), backupDefaults.backupDir)
v, err := store.VersionService.Version()
if err != nil {
log.Fatal().Err(err).Msg("")
}
// if isFileExist(backupPath) {
// t.Error("Expect backups folder to not exist")
// }
if portainer.SoftwareEdition(v.Edition) != portainer.PortainerCE {
t.Error("Expect to get CE Edition")
}
// store.createBackupFolders()
// if !isFileExist(backupPath) {
// t.Error("Expect backups folder to exist")
// }
// }
if v.SchemaVersion != portainer.APIVersion {
t.Error("Expect to get APIVersion")
}
}
// func TestStoreCreation(t *testing.T) {
// _, store := MustNewTestStore(t, true, true)
// if store == nil {
// t.Error("Expect to create a store")
// }
func TestBackup(t *testing.T) {
_, store := MustNewTestStore(t, true, true)
backupFileName := store.backupFilename()
t.Run(fmt.Sprintf("Backup should create %s", backupFileName), func(t *testing.T) {
v := models.Version{
Edition: int(portainer.PortainerCE),
SchemaVersion: portainer.APIVersion,
}
store.VersionService.UpdateVersion(&v)
store.Backup()
// if store.CheckCurrentEdition() != nil {
// t.Error("Expect to get CE Edition")
// }
// }
if !isFileExist(backupFileName) {
t.Errorf("Expect backup file to be created %s", backupFileName)
}
})
}
// func TestBackup(t *testing.T) {
// _, store := MustNewTestStore(t, true, true)
// connection := store.GetConnection()
func TestRestore(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
// t.Run("Backup should create default db backup", func(t *testing.T) {
// v := models.Version{
// SchemaVersion: portainer.APIVersion,
// }
// store.VersionService.UpdateVersion(&v)
// store.backupWithOptions(nil)
t.Run(fmt.Sprintf("Basic Restore"), func(t *testing.T) {
// override and set initial db version and edition
updateEdition(store, portainer.PortainerCE)
updateVersion(store, "2.4")
// backupFileName := path.Join(connection.GetStorePath(), "backups", "common", fmt.Sprintf("portainer.edb.%s.*", portainer.APIVersion))
// if !isFileExist(backupFileName) {
// t.Errorf("Expect backup file to be created %s", backupFileName)
// }
// })
store.Backup()
updateVersion(store, "2.16")
testVersion(store, "2.16", t)
store.Restore()
// t.Run("BackupWithOption should create a name specific backup at common path", func(t *testing.T) {
// store.backupWithOptions(&BackupOptions{
// BackupFileName: beforePortainerVersionUpgradeBackup,
// BackupDir: store.commonBackupDir(),
// })
// backupFileName := path.Join(connection.GetStorePath(), "backups", "common", beforePortainerVersionUpgradeBackup)
// if !isFileExist(backupFileName) {
// t.Errorf("Expect backup file to be created %s", backupFileName)
// }
// })
// }
// check if the restore is successful and the version is correct
testVersion(store, "2.4", t)
})
// func TestRemoveWithOptions(t *testing.T) {
// _, store := MustNewTestStore(t, true, true)
t.Run(fmt.Sprintf("Basic Restore After Multiple Backups"), func(t *testing.T) {
// override and set initial db version and edition
updateEdition(store, portainer.PortainerCE)
updateVersion(store, "2.4")
store.Backup()
updateVersion(store, "2.14")
updateVersion(store, "2.16")
testVersion(store, "2.16", t)
store.Restore()
// t.Run("successfully removes file if existent", func(t *testing.T) {
// store.createBackupFolders()
// options := &BackupOptions{
// BackupDir: store.commonBackupDir(),
// BackupFileName: "test.txt",
// }
// check if the restore is successful and the version is correct
testVersion(store, "2.4", t)
})
}
// filePath := path.Join(options.BackupDir, options.BackupFileName)
// f, err := os.Create(filePath)
// if err != nil {
// t.Fatalf("file should be created; err=%s", err)
// }
// f.Close()
// err = store.removeWithOptions(options)
// if err != nil {
// t.Errorf("RemoveWithOptions should successfully remove file; err=%v", err)
// }
// if isFileExist(f.Name()) {
// t.Errorf("RemoveWithOptions should successfully remove file; file=%s", f.Name())
// }
// })
// t.Run("fails to removes file if non-existent", func(t *testing.T) {
// options := &BackupOptions{
// BackupDir: store.commonBackupDir(),
// BackupFileName: "test.txt",
// }
// err := store.removeWithOptions(options)
// if err == nil {
// t.Error("RemoveWithOptions should fail for non-existent file")
// }
// })
// }

View File

@@ -3,14 +3,14 @@ package datastore
import (
"errors"
"fmt"
"io"
"os"
"path"
"time"
"github.com/mattn/go-sqlite3"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
portainerErrors "github.com/portainer/portainer/api/dataservices/errors"
"gorm.io/gorm"
"github.com/rs/zerolog/log"
)
@@ -25,26 +25,25 @@ func NewStore(storePath string, fileService portainer.FileService, connection po
// Open opens and initializes the BoltDB database.
func (store *Store) Open() (newStore bool, err error) {
encryptionReq, err := store.connection.NeedsEncryptionMigration()
if err != nil {
return false, err
}
if encryptionReq {
backupFilename, err := store.Backup()
if err != nil {
return false, fmt.Errorf("failed to backup database prior to encrypting: %w", err)
}
err = store.encryptDB()
if err != nil {
store.RestoreFromFile(backupFilename) // restore from backup if encryption fails
return false, err
}
}
err = store.connection.Open()
if err != nil {
fmt.Printf("connection open returned error: %s\n", err.Error())
if store.IsErrObjectNotFound(err) || store.IsErrNoSuchTable(err) {
return true, nil
}
return false, err
}
@@ -56,7 +55,7 @@ func (store *Store) Open() (newStore bool, err error) {
// If no settings object exists then assume we have a new store
_, err = store.SettingsService.Settings()
if err != nil {
if store.IsErrObjectNotFound(err) {
if store.IsErrObjectNotFound(err) || store.IsErrNoSuchTable(err) {
return true, nil
}
return false, err
@@ -69,32 +68,9 @@ func (store *Store) Close() error {
return store.connection.Close()
}
func (store *Store) UpdateTx(fn func(dataservices.DataStoreTx) error) error {
return store.connection.UpdateTx(func(tx portainer.Transaction) error {
return fn(&StoreTx{
store: store,
tx: tx,
})
})
}
func (store *Store) ViewTx(fn func(dataservices.DataStoreTx) error) error {
return store.connection.ViewTx(func(tx portainer.Transaction) error {
return fn(&StoreTx{
store: store,
tx: tx,
})
})
}
// BackupTo backs up db to a provided writer.
// It does hot backup and doesn't block other database reads and writes
func (store *Store) BackupTo(w io.Writer) error {
return store.connection.BackupTo(w)
}
// CheckCurrentEdition checks if current edition is community edition
func (store *Store) CheckCurrentEdition() error {
fmt.Println("Edition:", store.edition(), portainer.Edition)
if store.edition() != portainer.Edition {
return portainerErrors.ErrWrongDBEdition
}
@@ -109,9 +85,19 @@ func (store *Store) edition() portainer.SoftwareEdition {
return edition
}
func (store *Store) IsErrNoSuchTable(e error) bool {
return errors.Is(e, portainerErrors.ErrNoSuchTable)
}
// TODO: move the use of this to dataservices.IsErrObjectNotFound()?
func (store *Store) IsErrObjectNotFound(e error) bool {
return errors.Is(e, portainerErrors.ErrObjectNotFound)
var sqliteErr sqlite3.Error
errNotFound := false
if errors.As(e, &sqliteErr) {
errNotFound = sqliteErr.Code == sqlite3.ErrError
}
return errNotFound || errors.Is(e, gorm.ErrRecordNotFound)
}
func (store *Store) Connection() portainer.Connection {
@@ -119,7 +105,7 @@ func (store *Store) Connection() portainer.Connection {
}
func (store *Store) Rollback(force bool) error {
return store.connectionRollback(force)
return nil
}
func (store *Store) encryptDB() error {
@@ -139,7 +125,7 @@ func (store *Store) encryptDB() error {
log.Info().Msg("encrypting database")
// export file path for backup
exportFilename := path.Join(store.databasePath() + "." + fmt.Sprintf("backup-%d.json", time.Now().Unix()))
exportFilename := path.Join(store.connection.GetDatabaseFilePath() + "." + fmt.Sprintf("backup-%d.json", time.Now().Unix()))
log.Info().Str("filename", exportFilename).Msg("exporting database backup")

View File

@@ -1,416 +1,416 @@
package datastore
import (
"fmt"
"runtime"
"strings"
"testing"
"github.com/dchest/uniuri"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/chisel"
"github.com/portainer/portainer/api/crypto"
"github.com/stretchr/testify/assert"
)
const (
adminUsername = "admin"
adminPassword = "password"
standardUsername = "standard"
standardPassword = "password"
agentOnDockerEnvironmentUrl = "tcp://192.168.167.207:30775"
edgeAgentOnKubernetesEnvironmentUrl = "tcp://192.168.167.207"
kubernetesLocalEnvironmentUrl = "https://kubernetes.default.svc"
)
// 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 := MustNewTestStore(t, true, true)
testCases := map[string]func(t *testing.T){
"User Accounts": func(t *testing.T) {
store.testUserAccounts(t)
},
"Environments": func(t *testing.T) {
store.testEnvironments(t)
},
"Settings": func(t *testing.T) {
store.testSettings(t)
},
"SSL Settings": func(t *testing.T) {
store.testSSLSettings(t)
},
"Tunnel Server": func(t *testing.T) {
store.testTunnelServer(t)
},
"Custom Templates": func(t *testing.T) {
store.testCustomTemplates(t)
},
"Registries": func(t *testing.T) {
store.testRegistries(t)
},
"Resource Control": func(t *testing.T) {
store.testResourceControl(t)
},
"Schedules": func(t *testing.T) {
store.testSchedules(t)
},
"Tags": func(t *testing.T) {
store.testTags(t)
},
// "Test Title": func(t *testing.T) {
// },
}
for name, test := range testCases {
t.Run(name, test)
}
}
func (store *Store) testEnvironments(t *testing.T) {
id := store.CreateEndpoint(t, "local", portainer.KubernetesLocalEnvironment, "", true)
store.CreateEndpointRelation(id)
id = store.CreateEndpoint(t, "agent", portainer.AgentOnDockerEnvironment, agentOnDockerEnvironmentUrl, true)
store.CreateEndpointRelation(id)
id = store.CreateEndpoint(t, "edge", portainer.EdgeAgentOnKubernetesEnvironment, edgeAgentOnKubernetesEnvironmentUrl, true)
store.CreateEndpointRelation(id)
}
func newEndpoint(endpointType portainer.EndpointType, id portainer.EndpointID, name, URL string, TLS bool) *portainer.Endpoint {
endpoint := &portainer.Endpoint{
ID: id,
Name: name,
URL: URL,
Type: endpointType,
GroupID: portainer.EndpointGroupID(1),
PublicURL: "",
TLSConfig: portainer.TLSConfiguration{
TLS: false,
},
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
TagIDs: []portainer.TagID{},
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.DockerSnapshot{},
Kubernetes: portainer.KubernetesDefault(),
}
if TLS {
endpoint.TLSConfig = portainer.TLSConfiguration{
TLS: true,
TLSSkipVerify: true,
}
}
return endpoint
}
func setEndpointAuthorizations(endpoint *portainer.Endpoint) {
endpoint.SecuritySettings = portainer.EndpointSecuritySettings{
AllowVolumeBrowserForRegularUsers: false,
EnableHostManagementFeatures: false,
AllowSysctlSettingForRegularUsers: true,
AllowBindMountsForRegularUsers: true,
AllowPrivilegedModeForRegularUsers: true,
AllowHostNamespaceForRegularUsers: true,
AllowContainerCapabilitiesForRegularUsers: true,
AllowDeviceMappingForRegularUsers: true,
AllowStackManagementForRegularUsers: true,
}
}
func (store *Store) CreateEndpoint(t *testing.T, name string, endpointType portainer.EndpointType, URL string, tls bool) portainer.EndpointID {
is := assert.New(t)
var expectedEndpoint *portainer.Endpoint
id := portainer.EndpointID(store.Endpoint().GetNextIdentifier())
switch endpointType {
case portainer.DockerEnvironment:
if URL == "" {
URL = "unix:///var/run/docker.sock"
if runtime.GOOS == "windows" {
URL = "npipe:////./pipe/docker_engine"
}
}
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
case portainer.AgentOnDockerEnvironment:
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
case portainer.AgentOnKubernetesEnvironment:
URL = strings.TrimPrefix(URL, "tcp://")
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
case portainer.EdgeAgentOnKubernetesEnvironment:
cs := chisel.NewService(store, nil, nil)
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
edgeKey := cs.GenerateEdgeKey(URL, "", int(id))
expectedEndpoint.EdgeKey = edgeKey
store.testTunnelServer(t)
case portainer.KubernetesLocalEnvironment:
if URL == "" {
URL = kubernetesLocalEnvironmentUrl
}
expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
}
setEndpointAuthorizations(expectedEndpoint)
store.Endpoint().Create(expectedEndpoint)
endpoint, err := store.Endpoint().Endpoint(id)
is.NoError(err, "Endpoint() should not return an error")
is.Equal(expectedEndpoint, endpoint, "endpoint should be the same")
return endpoint.ID
}
func (store *Store) CreateEndpointRelation(id portainer.EndpointID) {
relation := &portainer.EndpointRelation{
EndpointID: id,
EdgeStacks: map[portainer.EdgeStackID]bool{},
}
store.EndpointRelation().Create(relation)
}
func (store *Store) testSSLSettings(t *testing.T) {
is := assert.New(t)
ssl := &portainer.SSLSettings{
CertPath: "/data/certs/cert.pem",
HTTPEnabled: true,
KeyPath: "/data/certs/key.pem",
SelfSigned: true,
}
store.SSLSettings().UpdateSettings(ssl)
settings, err := store.SSLSettings().Settings()
is.NoError(err, "Get sslsettings should succeed")
is.Equal(ssl, settings, "Stored SSLSettings should be the same as what is read out")
}
func (store *Store) testTunnelServer(t *testing.T) {
is := assert.New(t)
expectPrivateKeySeed := uniuri.NewLen(16)
err := store.TunnelServer().UpdateInfo(&portainer.TunnelServerInfo{PrivateKeySeed: expectPrivateKeySeed})
is.NoError(err, "UpdateInfo should have succeeded")
serverInfo, err := store.TunnelServer().Info()
is.NoError(err, "Info should have succeeded")
is.Equal(expectPrivateKeySeed, serverInfo.PrivateKeySeed, "hashed passwords should not differ")
}
// add users, read them back and check the details are unchanged
func (store *Store) testUserAccounts(t *testing.T) {
is := assert.New(t)
err := store.createAccount(adminUsername, adminPassword, portainer.AdministratorRole)
is.NoError(err, "CreateAccount should succeed")
store.checkAccount(adminUsername, adminPassword, portainer.AdministratorRole)
is.NoError(err, "Account failure")
err = store.createAccount(standardUsername, standardPassword, portainer.StandardUserRole)
is.NoError(err, "CreateAccount should succeed")
store.checkAccount(standardUsername, standardPassword, portainer.StandardUserRole)
is.NoError(err, "Account failure")
}
// create an account with the provided details
func (store *Store) createAccount(username, password string, role portainer.UserRole) error {
var err error
user := &portainer.User{Username: username, Role: role}
// encrypt the password
cs := &crypto.Service{}
user.Password, err = cs.Hash(password)
if err != nil {
return err
}
err = store.User().Create(user)
if err != nil {
return err
}
return nil
}
func (store *Store) checkAccount(username, expectPassword string, expectRole portainer.UserRole) error {
// Read the account for username. Check password and role is what we expect
user, err := store.User().UserByUsername(username)
if err != nil {
return errors.Wrap(err, "failed to find user")
}
if user.Username != username || user.Role != expectRole {
return fmt.Errorf("%s user details do not match", user.Username)
}
// Check the password
cs := &crypto.Service{}
expectPasswordHash, err := cs.Hash(expectPassword)
if err != nil {
return errors.Wrap(err, "hash failed")
}
if user.Password != expectPasswordHash {
return fmt.Errorf("%s user password hash failure", user.Username)
}
return nil
}
func (store *Store) testSettings(t *testing.T) {
is := assert.New(t)
// since many settings are default and basically nil, I'm going to update some and read them back
expectedSettings, err := store.Settings().Settings()
is.NoError(err, "Settings() should not return an error")
expectedSettings.TemplatesURL = "http://portainer.io/application-templates"
expectedSettings.HelmRepositoryURL = "http://portainer.io/helm-repository"
expectedSettings.EdgeAgentCheckinInterval = 60
expectedSettings.AuthenticationMethod = portainer.AuthenticationLDAP
expectedSettings.LDAPSettings = portainer.LDAPSettings{
AnonymousMode: true,
StartTLS: true,
AutoCreateUsers: true,
Password: "random",
}
expectedSettings.SnapshotInterval = "10m"
err = store.Settings().UpdateSettings(expectedSettings)
is.NoError(err, "UpdateSettings() should succeed")
settings, err := store.Settings().Settings()
is.NoError(err, "Settings() should not return an error")
is.Equal(expectedSettings, settings, "stored settings should match")
}
func (store *Store) testCustomTemplates(t *testing.T) {
is := assert.New(t)
customTemplate := store.CustomTemplate()
is.NotNil(customTemplate, "customTemplate Service shouldn't be nil")
expectedTemplate := &portainer.CustomTemplate{
ID: portainer.CustomTemplateID(customTemplate.GetNextIdentifier()),
Title: "Custom Title",
Description: "Custom Template Description",
ProjectPath: "/data/custom_template/1",
Note: "A note about this custom template",
EntryPoint: "docker-compose.yaml",
CreatedByUserID: 10,
}
customTemplate.Create(expectedTemplate)
actualTemplate, err := customTemplate.Read(expectedTemplate.ID)
is.NoError(err, "CustomTemplate should not return an error")
is.Equal(expectedTemplate, actualTemplate, "expected and actual template do not match")
}
func (store *Store) testRegistries(t *testing.T) {
is := assert.New(t)
regService := store.RegistryService
is.NotNil(regService, "RegistryService shouldn't be nil")
reg1 := &portainer.Registry{
ID: 1,
Type: portainer.DockerHubRegistry,
Name: "Dockerhub Registry Test",
}
reg2 := &portainer.Registry{
ID: 2,
Type: portainer.GitlabRegistry,
Name: "Gitlab Registry Test",
Gitlab: portainer.GitlabRegistryData{
ProjectID: 12345,
InstanceURL: "http://gitlab.com/12345",
ProjectPath: "mytestproject",
},
}
err := regService.Create(reg1)
is.NoError(err)
err = regService.Create(reg2)
is.NoError(err)
actualReg1, err := regService.Read(reg1.ID)
is.NoError(err)
is.Equal(reg1, actualReg1, "registries differ")
actualReg2, err := regService.Read(reg2.ID)
is.NoError(err)
is.Equal(reg2, actualReg2, "registries differ")
}
func (store *Store) testResourceControl(t *testing.T) {
// is := assert.New(t)
// resControl := store.ResourceControl()
// ctrl := &portainer.ResourceControl{
// }
// resControl().Create()
}
func (store *Store) testSchedules(t *testing.T) {
is := assert.New(t)
schedule := store.ScheduleService
s := &portainer.Schedule{
ID: portainer.ScheduleID(schedule.GetNextIdentifier()),
Name: "My Custom Schedule 1",
CronExpression: "*/5 * * * * portainer /bin/sh -c echo 'hello world'",
}
err := schedule.CreateSchedule(s)
is.NoError(err, "CreateSchedule should succeed")
actual, err := schedule.Schedule(s.ID)
is.NoError(err, "schedule should be found")
is.Equal(s, actual, "schedules differ")
}
func (store *Store) testTags(t *testing.T) {
is := assert.New(t)
tags := store.TagService
tag1 := &portainer.Tag{
ID: 1,
Name: "Tag 1",
}
tag2 := &portainer.Tag{
ID: 2,
Name: "Tag 1",
}
err := tags.Create(tag1)
is.NoError(err, "Tags.Create should succeed")
err = tags.Create(tag2)
is.NoError(err, "Tags.Create should succeed")
actual, err := tags.Read(tag1.ID)
is.NoError(err, "tag1 should be found")
is.Equal(tag1, actual, "tags differ")
actual, err = tags.Read(tag2.ID)
is.NoError(err, "tag2 should be found")
is.Equal(tag2, actual, "tags differ")
}
// import (
// "fmt"
// "runtime"
// "strings"
// "testing"
// "github.com/dchest/uniuri"
// "github.com/pkg/errors"
// portainer "github.com/portainer/portainer/api"
// "github.com/portainer/portainer/api/chisel"
// "github.com/portainer/portainer/api/crypto"
// "github.com/stretchr/testify/assert"
// )
// const (
// adminUsername = "admin"
// adminPassword = "password"
// standardUsername = "standard"
// standardPassword = "password"
// agentOnDockerEnvironmentUrl = "tcp://192.168.167.207:30775"
// edgeAgentOnKubernetesEnvironmentUrl = "tcp://192.168.167.207"
// kubernetesLocalEnvironmentUrl = "https://kubernetes.default.svc"
// )
// // 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 := MustNewTestStore(t, true, true)
// testCases := map[string]func(t *testing.T){
// "User Accounts": func(t *testing.T) {
// store.testUserAccounts(t)
// },
// "Environments": func(t *testing.T) {
// store.testEnvironments(t)
// },
// "Settings": func(t *testing.T) {
// store.testSettings(t)
// },
// "SSL Settings": func(t *testing.T) {
// store.testSSLSettings(t)
// },
// "Tunnel Server": func(t *testing.T) {
// store.testTunnelServer(t)
// },
// "Custom Templates": func(t *testing.T) {
// store.testCustomTemplates(t)
// },
// "Registries": func(t *testing.T) {
// store.testRegistries(t)
// },
// "Resource Control": func(t *testing.T) {
// store.testResourceControl(t)
// },
// "Schedules": func(t *testing.T) {
// store.testSchedules(t)
// },
// "Tags": func(t *testing.T) {
// store.testTags(t)
// },
// // "Test Title": func(t *testing.T) {
// // },
// }
// for name, test := range testCases {
// t.Run(name, test)
// }
// }
// func (store *Store) testEnvironments(t *testing.T) {
// id := store.CreateEndpoint(t, "local", portainer.KubernetesLocalEnvironment, "", true)
// store.CreateEndpointRelation(id)
// id = store.CreateEndpoint(t, "agent", portainer.AgentOnDockerEnvironment, agentOnDockerEnvironmentUrl, true)
// store.CreateEndpointRelation(id)
// id = store.CreateEndpoint(t, "edge", portainer.EdgeAgentOnKubernetesEnvironment, edgeAgentOnKubernetesEnvironmentUrl, true)
// store.CreateEndpointRelation(id)
// }
// func newEndpoint(endpointType portainer.EndpointType, id portainer.EndpointID, name, URL string, TLS bool) *portainer.Endpoint {
// endpoint := &portainer.Endpoint{
// ID: id,
// Name: name,
// URL: URL,
// Type: endpointType,
// GroupID: portainer.EndpointGroupID(1),
// PublicURL: "",
// TLSConfig: portainer.TLSConfiguration{
// TLS: false,
// },
// UserAccessPolicies: portainer.UserAccessPolicies{},
// TeamAccessPolicies: portainer.TeamAccessPolicies{},
// TagIDs: []portainer.TagID{},
// Status: portainer.EndpointStatusUp,
// Snapshots: []portainer.DockerSnapshot{},
// Kubernetes: portainer.KubernetesDefault(),
// }
// if TLS {
// endpoint.TLSConfig = portainer.TLSConfiguration{
// TLS: true,
// TLSSkipVerify: true,
// }
// }
// return endpoint
// }
// func setEndpointAuthorizations(endpoint *portainer.Endpoint) {
// endpoint.SecuritySettings = portainer.EndpointSecuritySettings{
// AllowVolumeBrowserForRegularUsers: false,
// EnableHostManagementFeatures: false,
// AllowSysctlSettingForRegularUsers: true,
// AllowBindMountsForRegularUsers: true,
// AllowPrivilegedModeForRegularUsers: true,
// AllowHostNamespaceForRegularUsers: true,
// AllowContainerCapabilitiesForRegularUsers: true,
// AllowDeviceMappingForRegularUsers: true,
// AllowStackManagementForRegularUsers: true,
// }
// }
// func (store *Store) CreateEndpoint(t *testing.T, name string, endpointType portainer.EndpointType, URL string, tls bool) portainer.EndpointID {
// is := assert.New(t)
// var expectedEndpoint *portainer.Endpoint
// id := portainer.EndpointID(store.Endpoint().GetNextIdentifier())
// switch endpointType {
// case portainer.DockerEnvironment:
// if URL == "" {
// URL = "unix:///var/run/docker.sock"
// if runtime.GOOS == "windows" {
// URL = "npipe:////./pipe/docker_engine"
// }
// }
// expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
// case portainer.AgentOnDockerEnvironment:
// expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
// case portainer.AgentOnKubernetesEnvironment:
// URL = strings.TrimPrefix(URL, "tcp://")
// expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
// case portainer.EdgeAgentOnKubernetesEnvironment:
// cs := chisel.NewService(store, nil)
// expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
// edgeKey := cs.GenerateEdgeKey(URL, "", int(id))
// expectedEndpoint.EdgeKey = edgeKey
// store.testTunnelServer(t)
// case portainer.KubernetesLocalEnvironment:
// if URL == "" {
// URL = kubernetesLocalEnvironmentUrl
// }
// expectedEndpoint = newEndpoint(endpointType, id, name, URL, tls)
// }
// setEndpointAuthorizations(expectedEndpoint)
// store.Endpoint().Create(expectedEndpoint)
// endpoint, err := store.Endpoint().Endpoint(id)
// is.NoError(err, "Endpoint() should not return an error")
// is.Equal(expectedEndpoint, endpoint, "endpoint should be the same")
// return endpoint.ID
// }
// func (store *Store) CreateEndpointRelation(id portainer.EndpointID) {
// relation := &portainer.EndpointRelation{
// EndpointID: id,
// EdgeStacks: map[portainer.EdgeStackID]bool{},
// }
// store.EndpointRelation().Create(relation)
// }
// func (store *Store) testSSLSettings(t *testing.T) {
// is := assert.New(t)
// ssl := &portainer.SSLSettings{
// CertPath: "/data/certs/cert.pem",
// HTTPEnabled: true,
// KeyPath: "/data/certs/key.pem",
// SelfSigned: true,
// }
// store.SSLSettings().UpdateSettings(ssl)
// settings, err := store.SSLSettings().Settings()
// is.NoError(err, "Get sslsettings should succeed")
// is.Equal(ssl, settings, "Stored SSLSettings should be the same as what is read out")
// }
// func (store *Store) testTunnelServer(t *testing.T) {
// is := assert.New(t)
// expectPrivateKeySeed := uniuri.NewLen(16)
// err := store.TunnelServer().UpdateInfo(&portainer.TunnelServerInfo{PrivateKeySeed: expectPrivateKeySeed})
// is.NoError(err, "UpdateInfo should have succeeded")
// serverInfo, err := store.TunnelServer().Info()
// is.NoError(err, "Info should have succeeded")
// is.Equal(expectPrivateKeySeed, serverInfo.PrivateKeySeed, "hashed passwords should not differ")
// }
// // add users, read them back and check the details are unchanged
// func (store *Store) testUserAccounts(t *testing.T) {
// is := assert.New(t)
// err := store.createAccount(adminUsername, adminPassword, portainer.AdministratorRole)
// is.NoError(err, "CreateAccount should succeed")
// store.checkAccount(adminUsername, adminPassword, portainer.AdministratorRole)
// is.NoError(err, "Account failure")
// err = store.createAccount(standardUsername, standardPassword, portainer.StandardUserRole)
// is.NoError(err, "CreateAccount should succeed")
// store.checkAccount(standardUsername, standardPassword, portainer.StandardUserRole)
// is.NoError(err, "Account failure")
// }
// // create an account with the provided details
// func (store *Store) createAccount(username, password string, role portainer.UserRole) error {
// var err error
// user := &portainer.User{Username: username, Role: role}
// // encrypt the password
// cs := &crypto.Service{}
// user.Password, err = cs.Hash(password)
// if err != nil {
// return err
// }
// err = store.User().Create(user)
// if err != nil {
// return err
// }
// return nil
// }
// func (store *Store) checkAccount(username, expectPassword string, expectRole portainer.UserRole) error {
// // Read the account for username. Check password and role is what we expect
// user, err := store.User().UserByUsername(username)
// if err != nil {
// return errors.Wrap(err, "failed to find user")
// }
// if user.Username != username || user.Role != expectRole {
// return fmt.Errorf("%s user details do not match", user.Username)
// }
// // Check the password
// cs := &crypto.Service{}
// expectPasswordHash, err := cs.Hash(expectPassword)
// if err != nil {
// return errors.Wrap(err, "hash failed")
// }
// if user.Password != expectPasswordHash {
// return fmt.Errorf("%s user password hash failure", user.Username)
// }
// return nil
// }
// func (store *Store) testSettings(t *testing.T) {
// is := assert.New(t)
// // since many settings are default and basically nil, I'm going to update some and read them back
// expectedSettings, err := store.Settings().Settings()
// is.NoError(err, "Settings() should not return an error")
// expectedSettings.TemplatesURL = "http://portainer.io/application-templates"
// expectedSettings.HelmRepositoryURL = "http://portainer.io/helm-repository"
// expectedSettings.EdgeAgentCheckinInterval = 60
// expectedSettings.AuthenticationMethod = portainer.AuthenticationLDAP
// expectedSettings.LDAPSettings = portainer.LDAPSettings{
// AnonymousMode: true,
// StartTLS: true,
// AutoCreateUsers: true,
// Password: "random",
// }
// expectedSettings.SnapshotInterval = "10m"
// err = store.Settings().UpdateSettings(expectedSettings)
// is.NoError(err, "UpdateSettings() should succeed")
// settings, err := store.Settings().Settings()
// is.NoError(err, "Settings() should not return an error")
// is.Equal(expectedSettings, settings, "stored settings should match")
// }
// func (store *Store) testCustomTemplates(t *testing.T) {
// is := assert.New(t)
// customTemplate := store.CustomTemplate()
// is.NotNil(customTemplate, "customTemplate Service shouldn't be nil")
// expectedTemplate := &portainer.CustomTemplate{
// ID: portainer.CustomTemplateID(customTemplate.GetNextIdentifier()),
// Title: "Custom Title",
// Description: "Custom Template Description",
// ProjectPath: "/data/custom_template/1",
// Note: "A note about this custom template",
// EntryPoint: "docker-compose.yaml",
// CreatedByUserID: 10,
// }
// customTemplate.Create(expectedTemplate)
// actualTemplate, err := customTemplate.CustomTemplate(expectedTemplate.ID)
// is.NoError(err, "CustomTemplate should not return an error")
// is.Equal(expectedTemplate, actualTemplate, "expected and actual template do not match")
// }
// func (store *Store) testRegistries(t *testing.T) {
// is := assert.New(t)
// regService := store.RegistryService
// is.NotNil(regService, "RegistryService shouldn't be nil")
// reg1 := &portainer.Registry{
// ID: 1,
// Type: portainer.DockerHubRegistry,
// Name: "Dockerhub Registry Test",
// }
// reg2 := &portainer.Registry{
// ID: 2,
// Type: portainer.GitlabRegistry,
// Name: "Gitlab Registry Test",
// Gitlab: portainer.GitlabRegistryData{
// ProjectID: 12345,
// InstanceURL: "http://gitlab.com/12345",
// ProjectPath: "mytestproject",
// },
// }
// err := regService.Create(reg1)
// is.NoError(err)
// err = regService.Create(reg2)
// is.NoError(err)
// actualReg1, err := regService.Registry(reg1.ID)
// is.NoError(err)
// is.Equal(reg1, actualReg1, "registries differ")
// actualReg2, err := regService.Registry(reg2.ID)
// is.NoError(err)
// is.Equal(reg2, actualReg2, "registries differ")
// }
// func (store *Store) testResourceControl(t *testing.T) {
// // is := assert.New(t)
// // resControl := store.ResourceControl()
// // ctrl := &portainer.ResourceControl{
// // }
// // resControl().Create()
// }
// func (store *Store) testSchedules(t *testing.T) {
// is := assert.New(t)
// schedule := store.ScheduleService
// s := &portainer.Schedule{
// ID: portainer.ScheduleID(schedule.GetNextIdentifier()),
// Name: "My Custom Schedule 1",
// CronExpression: "*/5 * * * * portainer /bin/sh -c echo 'hello world'",
// }
// err := schedule.CreateSchedule(s)
// is.NoError(err, "CreateSchedule should succeed")
// actual, err := schedule.Schedule(s.ID)
// is.NoError(err, "schedule should be found")
// is.Equal(s, actual, "schedules differ")
// }
// func (store *Store) testTags(t *testing.T) {
// is := assert.New(t)
// tags := store.TagService
// tag1 := &portainer.Tag{
// ID: 1,
// Name: "Tag 1",
// }
// tag2 := &portainer.Tag{
// ID: 2,
// Name: "Tag 1",
// }
// err := tags.Create(tag1)
// is.NoError(err, "Tags.Create should succeed")
// err = tags.Create(tag2)
// is.NoError(err, "Tags.Create should succeed")
// actual, err := tags.Tag(tag1.ID)
// is.NoError(err, "tag1 should be found")
// is.Equal(tag1, actual, "tags differ")
// actual, err = tags.Tag(tag2.ID)
// is.NoError(err, "tag2 should be found")
// is.Equal(tag2, actual, "tags differ")
// }

View File

@@ -1,68 +0,0 @@
package datastore
import (
"path/filepath"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
)
// isFileExist is helper function to check for file existence
func isFileExist(path string) bool {
matches, err := filepath.Glob(path)
if err != nil {
return false
}
return len(matches) > 0
}
func updateVersion(store *Store, v string) {
version, err := store.VersionService.Version()
if err != nil {
log.Fatal().Err(err).Msg("")
}
version.SchemaVersion = v
err = store.VersionService.UpdateVersion(version)
if err != nil {
log.Fatal().Err(err).Msg("")
}
}
func updateEdition(store *Store, edition portainer.SoftwareEdition) {
version, err := store.VersionService.Version()
if err != nil {
log.Fatal().Err(err).Msg("")
}
version.Edition = int(edition)
err = store.VersionService.UpdateVersion(version)
if err != nil {
log.Fatal().Err(err).Msg("")
}
}
// 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()
if err != nil {
log.Fatal().Err(err).Msg("")
}
if v.SchemaVersion != versionWant {
t.Errorf("Expect store version to be %s but was %s", versionWant, v.SchemaVersion)
}
}
func testEdition(store *Store, editionWant portainer.SoftwareEdition, t *testing.T) {
v, err := store.VersionService.Version()
if err != nil {
log.Fatal().Err(err).Msg("")
}
if portainer.SoftwareEdition(v.Edition) != editionWant {
t.Errorf("Expect store edition to be %s but was %s", editionWant.GetEditionLabel(), portainer.SoftwareEdition(v.Edition).GetEditionLabel())
}
}

View File

@@ -1,6 +1,7 @@
package datastore
import (
"fmt"
"os"
portainer "github.com/portainer/portainer/api"
@@ -8,6 +9,11 @@ import (
// Init creates the default data set.
func (store *Store) Init() error {
if err := store.connection.Init(); err != nil {
fmt.Errorf("connection init returned error: %s\n", err.Error())
}
err := store.checkOrCreateDefaultSettings()
if err != nil {
return err
@@ -21,7 +27,13 @@ func (store *Store) Init() error {
return store.checkOrCreateDefaultData()
}
func (store *Store) PostInit() error {
store.EndpointService.Init()
return nil
}
func (store *Store) checkOrCreateDefaultSettings() error {
isDDExtention := false
if _, ok := os.LookupEnv("DOCKER_EXTENSION"); ok {
isDDExtention = true
@@ -33,7 +45,7 @@ func (store *Store) checkOrCreateDefaultSettings() error {
defaultSettings := &portainer.Settings{
EnableTelemetry: false,
AuthenticationMethod: portainer.AuthenticationInternal,
BlackListedLabels: make([]portainer.Pair, 0),
BlackListedLabels: make(portainer.MultiPair, 0),
InternalAuthSettings: portainer.InternalAuthSettings{
RequiredPasswordLength: 12,
},
@@ -72,7 +84,6 @@ func (store *Store) checkOrCreateDefaultSettings() error {
settings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
return store.Settings().UpdateSettings(settings)
}
return nil
}
@@ -90,7 +101,7 @@ func (store *Store) checkOrCreateDefaultSSLSettings() error {
}
func (store *Store) checkOrCreateDefaultData() error {
groups, err := store.EndpointGroupService.ReadAll()
groups, err := store.EndpointGroupService.EndpointGroups()
if err != nil {
return err
}
@@ -99,7 +110,7 @@ func (store *Store) checkOrCreateDefaultData() error {
unassignedGroup := &portainer.EndpointGroup{
Name: "Unassigned",
Description: "Unassigned environments",
Labels: []portainer.Pair{},
Labels: portainer.MultiPair{},
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
TagIDs: []portainer.TagID{},
@@ -110,6 +121,5 @@ func (store *Store) checkOrCreateDefaultData() error {
return err
}
}
return nil
}

View File

@@ -1,148 +1,145 @@
package datastore
import (
"fmt"
"os"
"runtime/debug"
// import (
// "fmt"
// "runtime/debug"
portainer "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/datastore/migrator"
"github.com/portainer/portainer/api/internal/authorization"
// portainer "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/datastore/migrator"
// "github.com/portainer/portainer/api/internal/authorization"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// "github.com/pkg/errors"
// "github.com/rs/zerolog/log"
// )
func (store *Store) MigrateData() error {
updating, err := store.VersionService.IsUpdating()
if err != nil {
return errors.Wrap(err, "while checking if the store is updating")
}
// const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
if updating {
return dserrors.ErrDatabaseIsUpdating
}
// func (store *Store) MigrateData() error {
// updating, err := store.VersionService.IsUpdating()
// if err != nil {
// return errors.Wrap(err, "while checking if the store is updating")
// }
// migrate new version bucket if required (doesn't write anything to db yet)
version, err := store.getOrMigrateLegacyVersion()
if err != nil {
return errors.Wrap(err, "while migrating legacy version")
}
// if updating {
// return dserrors.ErrDatabaseIsUpdating
// }
migratorParams := store.newMigratorParameters(version)
migrator := migrator.NewMigrator(migratorParams)
// // migrate new version bucket if required (doesn't write anything to db yet)
// version, err := store.getOrMigrateLegacyVersion()
// if err != nil {
// return errors.Wrap(err, "while migrating legacy version")
// }
if !migrator.NeedsMigration() {
return nil
}
// migratorParams := store.newMigratorParameters(version)
// migrator := migrator.NewMigrator(migratorParams)
// before we alter anything in the DB, create a backup
_, err = store.Backup()
if err != nil {
return errors.Wrap(err, "while backing up database")
}
// if !migrator.NeedsMigration() {
// return nil
// }
err = store.FailSafeMigrate(migrator, version)
if err != nil {
err = errors.Wrap(err, "failed to migrate database")
// // 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")
// }
log.Warn().Err(err).Msg("migration failed, restoring database to previous version")
restoreErr := store.Restore()
if restoreErr != nil {
return errors.Wrap(restoreErr, "failed to restore database")
}
// err = store.FailSafeMigrate(migrator, version)
// if err != nil {
// err = errors.Wrap(err, "failed to migrate database")
log.Info().Msg("database restored to previous version")
return err
}
// log.Warn().Msg("migration failed, restoring database to previous version")
// err = store.restoreWithOptions(&BackupOptions{BackupPath: backupPath})
// if err != nil {
// return errors.Wrap(err, "failed to restore database")
// }
return nil
}
// log.Info().Msg("database restored to previous version")
// return err
// }
func (store *Store) newMigratorParameters(version *models.Version) *migrator.MigratorParameters {
return &migrator.MigratorParameters{
CurrentDBVersion: version,
EndpointGroupService: store.EndpointGroupService,
EndpointService: store.EndpointService,
EndpointRelationService: store.EndpointRelationService,
ExtensionService: store.ExtensionService,
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,
UserService: store.UserService,
VersionService: store.VersionService,
FileService: store.fileService,
DockerhubService: store.DockerHubService,
AuthorizationService: authorization.NewService(store),
EdgeStackService: store.EdgeStackService,
EdgeJobService: store.EdgeJobService,
TunnelServerService: store.TunnelServerService,
}
}
// return nil
// }
// FailSafeMigrate backup and restore DB if migration fail
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version *models.Version) (err error) {
defer func() {
if e := recover(); e != nil {
// return error with cause and stacktrace (recover() doesn't include a stacktrace)
err = fmt.Errorf("%v %s", e, string(debug.Stack()))
}
}()
// func (store *Store) newMigratorParameters(version *models.Version) *migrator.MigratorParameters {
// return &migrator.MigratorParameters{
// CurrentDBVersion: version,
// EndpointGroupService: store.EndpointGroupService,
// EndpointService: store.EndpointService,
// EndpointRelationService: store.EndpointRelationService,
// ExtensionService: store.ExtensionService,
// 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,
// UserService: store.UserService,
// VersionService: store.VersionService,
// FileService: store.fileService,
// DockerhubService: store.DockerHubService,
// AuthorizationService: authorization.NewService(store),
// EdgeStackService: store.EdgeStackService,
// EdgeJobService: store.EdgeJobService,
// }
// }
err = store.VersionService.StoreIsUpdating(true)
if err != nil {
return errors.Wrap(err, "while updating the store")
}
// // FailSafeMigrate backup and restore DB if migration fail
// func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version *models.Version) (err error) {
// defer func() {
// if e := recover(); e != nil {
// // return error with cause and stacktrace (recover() doesn't include a stacktrace)
// err = fmt.Errorf("%v %s", e, string(debug.Stack()))
// }
// }()
// now update the version to the new struct (if required)
err = store.finishMigrateLegacyVersion(version)
if err != nil {
return errors.Wrap(err, "while updating version")
}
// err = store.VersionService.StoreIsUpdating(true)
// if err != nil {
// return errors.Wrap(err, "while updating the store")
// }
log.Info().Msg("migrating database from version " + version.SchemaVersion + " to " + portainer.APIVersion)
// // now update the version to the new struct (if required)
// err = store.finishMigrateLegacyVersion(version)
// if err != nil {
// return errors.Wrap(err, "while updating version")
// }
err = migrator.Migrate()
if err != nil {
return err
}
// log.Info().Msg("migrating database from version " + version.SchemaVersion + " to " + portainer.APIVersion)
// Special test code to simulate a failure (used by migrate_data_test.go). Do not remove...
if os.Getenv("PORTAINER_TEST_MIGRATE_FAIL") == "FAIL" {
panic("test migration failure")
}
// err = migrator.Migrate()
// if err != nil {
// return err
// }
err = store.VersionService.StoreIsUpdating(false)
if err != nil {
return errors.Wrap(err, "failed to update the store")
}
// err = store.VersionService.StoreIsUpdating(false)
// if err != nil {
// return errors.Wrap(err, "failed to update the store")
// }
return nil
}
// return nil
// }
// Rollback to a pre-upgrade backup copy/snapshot of portainer.db
func (store *Store) connectionRollback(force bool) error {
// // Rollback to a pre-upgrade backup copy/snapshot of portainer.db
// func (store *Store) connectionRollback(force bool) error {
if !force {
confirmed, err := cli.Confirm("Are you sure you want to rollback your database to the previous backup?")
if err != nil || !confirmed {
return err
}
}
// if !force {
// confirmed, err := cli.Confirm("Are you sure you want to rollback your database to the previous backup?")
// if err != nil || !confirmed {
// return err
// }
// }
err := store.Restore()
if err != nil {
return err
}
// options := getBackupRestoreOptions(store.commonBackupDir())
return store.connection.Close()
}
// err := store.restoreWithOptions(options)
// if err != nil {
// return err
// }
// return store.connection.Close()
// }

View File

@@ -1,514 +1,507 @@
package datastore
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"github.com/Masterminds/semver"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/boltdb"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/datastore/migrator"
"github.com/rs/zerolog/log"
"github.com/google/go-cmp/cmp"
)
// 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()
// if err != nil {
// t.Errorf("Expect store version to be %s but was %s with error: %s", versionWant, v.SchemaVersion, err)
// }
// if v.SchemaVersion != versionWant {
// t.Errorf("Expect store version to be %s but was %s", versionWant, v.SchemaVersion)
// }
}
func TestMigrateData(t *testing.T) {
tests := []struct {
testName string
srcPath string
wantPath string
overrideInstanceId bool
}{
{
testName: "migrate version 24 to latest",
srcPath: "test_data/input_24.json",
wantPath: "test_data/output_24_to_latest.json",
overrideInstanceId: true,
},
}
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
err := migrateDBTestHelper(t, test.srcPath, test.wantPath, test.overrideInstanceId)
if err != nil {
t.Errorf(
"Failed migrating mock database %v: %v",
test.srcPath,
err,
)
}
})
}
// snapshotTests := []struct {
// testName string
// srcPath string
// wantPath string
// overrideInstanceId bool
// }{
// {
// testName: "migrate version 24 to latest",
// srcPath: "test_data/input_24.json",
// wantPath: "test_data/output_24_to_latest.json",
// overrideInstanceId: true,
// },
// }
// for _, test := range snapshotTests {
// t.Run(test.testName, func(t *testing.T) {
// err := migrateDBTestHelper(t, test.srcPath, test.wantPath, test.overrideInstanceId)
// if err != nil {
// t.Errorf(
// "Failed migrating mock database %v: %v",
// test.srcPath,
// err,
// )
// }
// })
// }
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
newStore, store := MustNewTestStore(t, true, false)
if !newStore {
t.Error("Expect a new DB")
}
// t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
// newStore, store, teardown := MustNewTestStore(t, true, false)
// defer teardown()
testVersion(store, portainer.APIVersion, t)
store.Close()
// if !newStore {
// t.Error("Expect a new DB")
// }
newStore, _ = store.Open()
if newStore {
t.Error("Expect store to NOT be new DB")
}
})
// testVersion(store, portainer.APIVersion, t)
// store.Close()
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
store.VersionService.UpdateVersion(&models.Version{SchemaVersion: "1.0", Edition: int(portainer.PortainerCE)})
store.MigrateData()
// newStore, _ = store.Open()
// if newStore {
// t.Error("Expect store to NOT be new DB")
// }
// })
backupfilename := store.backupFilename()
if exists, _ := store.fileService.FileExists(backupfilename); !exists {
t.Errorf("Expect backup file to be created %s", backupfilename)
}
})
// 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()
t.Run("MigrateData should recover and restore backup during migration critical failure", func(t *testing.T) {
os.Setenv("PORTAINER_TEST_MIGRATE_FAIL", "FAIL")
// // Setup data
// v := models.Version{SchemaVersion: tc.version}
// store.VersionService.UpdateVersion(&v)
version := "2.15"
_, store := MustNewTestStore(t, true, false)
store.VersionService.UpdateVersion(&models.Version{SchemaVersion: version, Edition: int(portainer.PortainerCE)})
store.MigrateData()
// // 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})
store.Open()
testVersion(store, version, t)
})
// t.Run(fmt.Sprintf("MigrateData for version %s", tc.version), func(t *testing.T) {
// store.MigrateData()
// testVersion(store, tc.expectedVersion, t)
// })
t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
store.VersionService.StoreIsUpdating(true)
store.MigrateData()
// 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)
// })
// }
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
// If the backup file is not blank, then it means a backup was created. We don't want that because we
// only create a backup when the version changes.
backupfilename := store.backupFilename()
if exists, _ := store.fileService.FileExists(backupfilename); exists {
t.Errorf("Backup file should not exist for dirty database")
}
})
// t.Run("Error in MigrateData should restore backup before MigrateData", 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 := MustNewTestStore(t, true, false)
// v := models.Version{SchemaVersion: "1.24.1"}
// store.VersionService.UpdateVersion(&v)
// Set migrator the count to match our migrations array (simulate no changes).
// Should not create a backup
v, err := store.VersionService.Version()
if err != nil {
t.Errorf("Unable to read version from db: %s", err)
t.FailNow()
}
// store.MigrateData()
migratorParams := store.newMigratorParameters(v)
m := migrator.NewMigrator(migratorParams)
latestMigrations := m.LatestMigrations()
// testVersion(store, v.SchemaVersion, t)
// })
if latestMigrations.Version.Equal(semver.MustParse(portainer.APIVersion)) {
v.MigratorCount = len(latestMigrations.MigrationFuncs)
store.VersionService.UpdateVersion(v)
}
// t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
store.MigrateData()
// v := models.Version{SchemaVersion: "0.0.0"}
// store.VersionService.UpdateVersion(&v)
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
// If the backup file is not blank, then it means a backup was created. We don't want that because we
// only create a backup when the version changes.
backupfilename := store.backupFilename()
if exists, _ := store.fileService.FileExists(backupfilename); exists {
t.Errorf("Backup file should not exist for dirty database")
}
})
// store.MigrateData()
t.Run("MigrateData should create backup on startup if portainer version matches db and migrationFuncs counts differ", func(t *testing.T) {
_, store := MustNewTestStore(t, true, false)
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// Set migrator count very large to simulate changes
// Should not create a backup
v, err := store.VersionService.Version()
if err != nil {
t.Errorf("Unable to read version from db: %s", err)
t.FailNow()
}
// if !isFileExist(options.BackupPath) {
// t.Errorf("Backup file should exist; file=%s", options.BackupPath)
// }
// })
v.MigratorCount = 1000
store.VersionService.UpdateVersion(v)
store.MigrateData()
// 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()
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
// If the backup file is not blank, then it means a backup was created. We don't want that because we
// only create a backup when the version changes.
backupfilename := store.backupFilename()
if exists, _ := store.fileService.FileExists(backupfilename); !exists {
t.Errorf("DB backup should exist and there should be no error")
}
})
// store.VersionService.StoreIsUpdating(true)
// store.MigrateData()
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// if isFileExist(options.BackupPath) {
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
// }
// })
// t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
// store.MigrateData()
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// if isFileExist(options.BackupPath) {
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
// }
// })
}
func Test_getBackupRestoreOptions(t *testing.T) {
// _, store := MustNewTestStore(t, false, true)
// 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")
// }
// wantFilename := "portainer.db.bak"
// if options.BackupFileName != wantFilename {
// log.Fatal().Str("got", options.BackupFileName).Str("want", wantFilename).Msg("incorrect backup file")
// }
}
func TestRollback(t *testing.T) {
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
version := "2.11"
// t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
// version := models.Version{SchemaVersion: "2.4.0"}
// _, store := MustNewTestStore(t, true, false)
v := models.Version{
SchemaVersion: version,
}
// err := store.VersionService.UpdateVersion(&version)
// if err != nil {
// t.Errorf("Failed updating version: %v", err)
// }
_, store := MustNewTestStore(t, false, false)
store.VersionService.UpdateVersion(&v)
// _, err = store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
// if err != nil {
// log.Fatal().Err(err).Msg("")
// }
_, err := store.Backup()
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("")
// }
v.SchemaVersion = "2.14"
// Change the current edition
err = store.VersionService.UpdateVersion(&v)
if err != nil {
log.Fatal().Err(err).Msg("")
}
// err = store.Rollback(true)
// if err != nil {
// t.Logf("Rollback failed: %s", err)
// t.Fail()
// return
// }
err = store.Rollback(true)
if err != nil {
t.Logf("Rollback failed: %s", err)
t.Fail()
return
}
// _, err = store.Open()
// if err != nil {
// t.Logf("Open failed: %s", err)
// t.Fail()
// return
// }
store.Open()
testVersion(store, version, t)
})
// testVersion(store, version.SchemaVersion, t)
// })
}
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
version := "2.15"
v := models.Version{
SchemaVersion: version,
Edition: int(portainer.PortainerCE),
}
_, store := MustNewTestStore(t, true, false)
store.VersionService.UpdateVersion(&v)
_, err := store.Backup()
if err != nil {
log.Fatal().Err(err).Msg("")
}
v.SchemaVersion = "2.14"
// Change the current edition
err = store.VersionService.UpdateVersion(&v)
if err != nil {
log.Fatal().Err(err).Msg("")
}
err = store.Rollback(true)
if err != nil {
t.Logf("Rollback failed: %s", err)
t.Fail()
return
}
store.Open()
testVersion(store, version, t)
})
// isFileExist is helper function to check for file existence
func isFileExist(path string) bool {
matches, err := filepath.Glob(path)
if err != nil {
return false
}
return len(matches) > 0
}
// 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 {
srcJSON, err := os.ReadFile(srcPath)
if err != nil {
t.Fatalf("failed loading source JSON file %v: %v", srcPath, err)
}
// 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)
// // 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"))
// fmt.Println("store.path=", store.GetConnection().GetDatabaseFilePath())
// store.connection.DeleteObject("version", []byte("VERSION"))
// defer teardown()
err = importJSON(t, bytes.NewReader(srcJSON), store)
if err != nil {
return err
}
// // defer teardown()
// err = importJSON(t, bytes.NewReader(srcJSON), store)
// if err != nil {
// return err
// }
// Run the actual migrations on our input database.
err = store.MigrateData()
if err != nil {
return err
}
// // Run the actual migrations on our input database.
// err = store.MigrateData()
// if err != nil {
// 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
}
// 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
}
}
// 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.
// TODO: update connection interface in CE to allow us to use ExportRaw and pass meta false
err = store.connection.Close()
if err != nil {
t.Fatalf("err closing bolt connection: %v", err)
}
con, ok := store.connection.(*boltdb.DbConnection)
if !ok {
t.Fatalf("backing database is not using boltdb, but the migrations test requires it")
}
// // 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.
// // TODO: update connection interface in CE to allow us to use ExportRaw and pass meta false
// err = store.connection.Close()
// if err != nil {
// t.Fatalf("err closing bolt connection: %v", err)
// }
// con, ok := store.connection.(*boltdb.DbConnection)
// if !ok {
// t.Fatalf("backing database is not using boltdb, but the migrations test requires it")
// }
// Convert database back to json.
databasePath := con.GetDatabaseFilePath()
if _, err := os.Stat(databasePath); err != nil {
return fmt.Errorf("stat on %s failed: %w", databasePath, err)
}
// // Convert database back to json.
// databasePath := con.GetDatabaseFilePath()
// if _, err := os.Stat(databasePath); err != nil {
// return fmt.Errorf("stat on %s failed: %w", databasePath, err)
// }
gotJSON, err := con.ExportJSON(databasePath, false)
if err != nil {
t.Logf(
"failed re-exporting database %s to JSON: %v",
databasePath,
err,
)
}
// gotJSON, err := con.ExportJSON(databasePath, false)
// if err != nil {
// t.Logf(
// "failed re-exporting database %s to JSON: %v",
// databasePath,
// err,
// )
// }
wantJSON, err := os.ReadFile(wantPath)
if err != nil {
t.Fatalf("failed loading want JSON file %v: %v", wantPath, err)
}
// wantJSON, err := os.ReadFile(wantPath)
// if err != nil {
// t.Fatalf("failed loading want JSON file %v: %v", wantPath, err)
// }
// Compare the result we got with the one we wanted.
if diff := cmp.Diff(wantJSON, gotJSON); diff != "" {
gotPath := filepath.Join(os.TempDir(), "portainer-migrator-test-fail.json")
os.WriteFile(
gotPath,
gotJSON,
0600,
)
t.Errorf(
"migrate data from %s to %s failed\nwrote migrated input to %s\nmismatch (-want +got):\n%s",
srcPath,
wantPath,
gotPath,
diff,
)
}
// // Compare the result we got with the one we wanted.
// if diff := cmp.Diff(wantJSON, gotJSON); diff != "" {
// gotPath := filepath.Join(os.TempDir(), "portainer-migrator-test-fail.json")
// os.WriteFile(
// gotPath,
// gotJSON,
// 0600,
// )
// t.Errorf(
// "migrate data from %s to %s failed\nwrote migrated input to %s\nmismatch (-want +got):\n%s",
// srcPath,
// wantPath,
// gotPath,
// diff,
// )
// }
return nil
}
// importJSON reads input JSON and commits it to a portainer datastore.Store.
// Errors are logged with the testing package.
func importJSON(t *testing.T, r io.Reader, store *Store) error {
objects := make(map[string]interface{})
// objects := make(map[string]interface{})
// Parse json into map of objects.
d := json.NewDecoder(r)
d.UseNumber()
err := d.Decode(&objects)
if err != nil {
return err
}
// // Parse json into map of objects.
// d := json.NewDecoder(r)
// d.UseNumber()
// err := d.Decode(&objects)
// if err != nil {
// return err
// }
// Get database connection from store.
con := store.connection
// // Get database connection from store.
// con := store.connection
for k, v := range objects {
switch k {
case "version":
versions, ok := v.(map[string]interface{})
if !ok {
t.Logf("failed casting %s to map[string]interface{}", k)
}
// for k, v := range objects {
// switch k {
// case "version":
// versions, ok := v.(map[string]interface{})
// if !ok {
// 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)
}
}
// // New format db
// version, ok := versions["VERSION"]
// if ok {
// err := con.CreateObjectWithStringId(
// k,
// []byte("VERSION"),
// version,
// )
// if err != nil {
// t.Logf("failed writing VERSION in %s: %v", k, err)
// }
// }
// old format db
// // old format db
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)
}
// 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)
}
}
// 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)
// }
// }
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)
}
}
// edition, ok := versions["EDITION"]
// if ok {
// err = con.CreateObjectWithStringId(
// k,
// []byte("EDITION"),
// edition,
// )
// if err != nil {
// t.Logf("failed writing EDITION in %s: %v", k, err)
// }
// }
case "dockerhub":
obj, ok := v.([]interface{})
if !ok {
t.Logf("failed to cast %s to []interface{}", k)
}
err := con.CreateObjectWithStringId(
k,
[]byte("DOCKERHUB"),
obj[0],
)
if err != nil {
t.Logf("failed writing DOCKERHUB in %s: %v", k, err)
}
// case "dockerhub":
// obj, ok := v.([]interface{})
// if !ok {
// t.Logf("failed to cast %s to []interface{}", k)
// }
// err := con.CreateObjectWithStringId(
// k,
// []byte("DOCKERHUB"),
// obj[0],
// )
// if err != nil {
// t.Logf("failed writing DOCKERHUB in %s: %v", k, err)
// }
case "ssl":
obj, ok := v.(map[string]interface{})
if !ok {
t.Logf("failed to case %s to map[string]interface{}", k)
}
err := con.CreateObjectWithStringId(
k,
[]byte("SSL"),
obj,
)
if err != nil {
t.Logf("failed writing SSL in %s: %v", k, err)
}
// case "ssl":
// obj, ok := v.(map[string]interface{})
// if !ok {
// t.Logf("failed to case %s to map[string]interface{}", k)
// }
// err := con.CreateObjectWithStringId(
// k,
// []byte("SSL"),
// obj,
// )
// if err != nil {
// t.Logf("failed writing SSL in %s: %v", k, err)
// }
case "settings":
obj, ok := v.(map[string]interface{})
if !ok {
t.Logf("failed to case %s to map[string]interface{}", k)
}
err := con.CreateObjectWithStringId(
k,
[]byte("SETTINGS"),
obj,
)
if err != nil {
t.Logf("failed writing SETTINGS in %s: %v", k, err)
}
// case "settings":
// obj, ok := v.(map[string]interface{})
// if !ok {
// t.Logf("failed to case %s to map[string]interface{}", k)
// }
// err := con.CreateObjectWithStringId(
// k,
// []byte("SETTINGS"),
// obj,
// )
// if err != nil {
// t.Logf("failed writing SETTINGS in %s: %v", k, err)
// }
case "tunnel_server":
obj, ok := v.(map[string]interface{})
if !ok {
t.Logf("failed to case %s to map[string]interface{}", k)
}
err := con.CreateObjectWithStringId(
k,
[]byte("INFO"),
obj,
)
if err != nil {
t.Logf("failed writing INFO in %s: %v", k, err)
}
case "templates":
continue
// case "tunnel_server":
// obj, ok := v.(map[string]interface{})
// if !ok {
// t.Logf("failed to case %s to map[string]interface{}", k)
// }
// err := con.CreateObjectWithStringId(
// k,
// []byte("INFO"),
// obj,
// )
// if err != nil {
// t.Logf("failed writing INFO in %s: %v", k, err)
// }
// case "templates":
// continue
default:
objlist, ok := v.([]interface{})
if !ok {
t.Logf("failed to cast %s to []interface{}", k)
}
// default:
// objlist, ok := v.([]interface{})
// if !ok {
// t.Logf("failed to cast %s to []interface{}", k)
// }
for _, obj := range objlist {
value, ok := obj.(map[string]interface{})
if !ok {
t.Logf("failed to cast %v to map[string]interface{}", obj)
} else {
var ok bool
var id interface{}
switch k {
case "endpoint_relations":
// TODO: need to make into an int, then do that weird
// stringification
id, ok = value["EndpointID"]
default:
id, ok = value["Id"]
}
if !ok {
// endpoint_relations: EndpointID
t.Logf("missing Id field: %s", k)
id = "error"
}
n, ok := id.(json.Number)
if !ok {
t.Logf("failed to cast %v to json.Number in %s", id, k)
} else {
key, err := n.Int64()
if err != nil {
t.Logf("failed to cast %v to int in %s", n, k)
} else {
err := con.CreateObjectWithId(
k,
int(key),
value,
)
if err != nil {
t.Logf("failed writing %v in %s: %v", key, k, err)
}
}
}
}
}
}
}
// for _, obj := range objlist {
// value, ok := obj.(map[string]interface{})
// if !ok {
// t.Logf("failed to cast %v to map[string]interface{}", obj)
// } else {
// var ok bool
// var id interface{}
// switch k {
// case "endpoint_relations":
// // TODO: need to make into an int, then do that weird
// // stringification
// id, ok = value["EndpointID"]
// default:
// id, ok = value["Id"]
// }
// if !ok {
// // endpoint_relations: EndpointID
// t.Logf("missing Id field: %s", k)
// id = "error"
// }
// n, ok := id.(json.Number)
// if !ok {
// t.Logf("failed to cast %v to json.Number in %s", id, k)
// } else {
// key, err := n.Int64()
// if err != nil {
// t.Logf("failed to cast %v to int in %s", n, k)
// } else {
// err := con.CreateObjectWithId(
// k,
// int(key),
// value,
// )
// if err != nil {
// t.Logf("failed writing %v in %s: %v", key, k, err)
// }
// }
// }
// }
// }
// }
// }
return nil
}

View File

@@ -1,89 +1,89 @@
package datastore
import (
"testing"
// import (
// "testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore/migrator"
"github.com/portainer/portainer/api/internal/authorization"
)
// portainer "github.com/portainer/portainer/api"
// "github.com/portainer/portainer/api/datastore/migrator"
// "github.com/portainer/portainer/api/internal/authorization"
// )
const dummyLogoURL = "example.com"
// const dummyLogoURL = "example.com"
// initTestingDBConn creates a settings service with raw database DB connection
// for unit testing usage only since using NewStore will cause cycle import inside migrator pkg
func initTestingSettingsService(dbConn portainer.Connection, preSetObj map[string]interface{}) error {
//insert a obj
return dbConn.UpdateObject("settings", []byte("SETTINGS"), preSetObj)
}
// func initTestingSettingsService(dbConn portainer.Connection, preSetObj map[string]interface{}) error {
// //insert a obj
// return dbConn.UpdateObject("settings", []byte("SETTINGS"), preSetObj)
// }
func setup(store *Store) error {
dummySettingsObj := map[string]interface{}{
"LogoURL": dummyLogoURL,
}
// func setup(store *Store) error {
// dummySettingsObj := map[string]interface{}{
// "LogoURL": dummyLogoURL,
// }
return initTestingSettingsService(store.connection, dummySettingsObj)
}
// return initTestingSettingsService(store.connection, dummySettingsObj)
// }
func TestMigrateSettings(t *testing.T) {
_, store := MustNewTestStore(t, false, true)
// func TestMigrateSettings(t *testing.T) {
// _, store := MustNewTestStore(t, false, true)
err := setup(store)
if err != nil {
t.Errorf("failed to complete testing setups, err: %v", err)
}
// // err := setup(store)
// // if err != nil {
// // t.Errorf("failed to complete testing setups, err: %v", err)
// // }
updatedSettings, err := store.SettingsService.Settings()
// SO -basically, this test _isn't_ testing migration, its testing golang type defaults.
if updatedSettings.LogoURL != dummyLogoURL { // ensure a pre-migrate setting isn't unset
t.Errorf("unexpected value changes in the updated settings, want LogoURL value: %s, got LogoURL value: %s", dummyLogoURL, updatedSettings.LogoURL)
}
// updatedSettings, err := store.SettingsService.Settings()
// // SO -basically, this test _isn't_ testing migration, its testing golang type defaults.
// if updatedSettings.LogoURL != dummyLogoURL { // ensure a pre-migrate setting isn't unset
// t.Errorf("unexpected value changes in the updated settings, want LogoURL value: %s, got LogoURL value: %s", dummyLogoURL, updatedSettings.LogoURL)
// }
if updatedSettings.OAuthSettings.SSO != false { // I recon golang defaulting will make this false
t.Errorf("unexpected default OAuth SSO setting, want: false, got: %t", updatedSettings.OAuthSettings.SSO)
}
// if updatedSettings.OAuthSettings.SSO != false { // I recon golang defaulting will make this false
// t.Errorf("unexpected default OAuth SSO setting, want: false, got: %t", updatedSettings.OAuthSettings.SSO)
// }
if updatedSettings.OAuthSettings.LogoutURI != "" {
t.Errorf("unexpected default OAuth HideInternalAuth setting, want:, got: %s", updatedSettings.OAuthSettings.LogoutURI)
}
// if updatedSettings.OAuthSettings.LogoutURI != "" {
// t.Errorf("unexpected default OAuth HideInternalAuth setting, want:, got: %s", updatedSettings.OAuthSettings.LogoutURI)
// }
m := migrator.NewMigrator(&migrator.MigratorParameters{
EndpointGroupService: store.EndpointGroupService,
EndpointService: store.EndpointService,
EndpointRelationService: store.EndpointRelationService,
ExtensionService: store.ExtensionService,
RegistryService: store.RegistryService,
ResourceControlService: store.ResourceControlService,
RoleService: store.RoleService,
ScheduleService: store.ScheduleService,
SettingsService: store.SettingsService,
StackService: store.StackService,
TagService: store.TagService,
TeamMembershipService: store.TeamMembershipService,
UserService: store.UserService,
VersionService: store.VersionService,
FileService: store.fileService,
DockerhubService: store.DockerHubService,
AuthorizationService: authorization.NewService(store),
})
// m := migrator.NewMigrator(&migrator.MigratorParameters{
// EndpointGroupService: store.EndpointGroupService,
// EndpointService: store.EndpointService,
// EndpointRelationService: store.EndpointRelationService,
// ExtensionService: store.ExtensionService,
// RegistryService: store.RegistryService,
// ResourceControlService: store.ResourceControlService,
// RoleService: store.RoleService,
// ScheduleService: store.ScheduleService,
// SettingsService: store.SettingsService,
// StackService: store.StackService,
// TagService: store.TagService,
// TeamMembershipService: store.TeamMembershipService,
// UserService: store.UserService,
// VersionService: store.VersionService,
// FileService: store.fileService,
// DockerhubService: store.DockerHubService,
// AuthorizationService: authorization.NewService(store),
// })
if err := m.MigrateSettingsToDB30(); err != nil {
t.Errorf("failed to update settings: %v", err)
}
// if err := m.MigrateSettingsToDB30(); err != nil {
// t.Errorf("failed to update settings: %v", err)
// }
if err != nil {
t.Errorf("failed to retrieve the updated settings: %v", err)
}
// if err != nil {
// t.Errorf("failed to retrieve the updated settings: %v", err)
// }
if updatedSettings.LogoURL != dummyLogoURL {
t.Errorf("unexpected value changes in the updated settings, want LogoURL value: %s, got LogoURL value: %s", dummyLogoURL, updatedSettings.LogoURL)
}
// if updatedSettings.LogoURL != dummyLogoURL {
// t.Errorf("unexpected value changes in the updated settings, want LogoURL value: %s, got LogoURL value: %s", dummyLogoURL, updatedSettings.LogoURL)
// }
if updatedSettings.OAuthSettings.SSO != false {
t.Errorf("unexpected default OAuth SSO setting, want: false, got: %t", updatedSettings.OAuthSettings.SSO)
}
// if updatedSettings.OAuthSettings.SSO != false {
// t.Errorf("unexpected default OAuth SSO setting, want: false, got: %t", updatedSettings.OAuthSettings.SSO)
// }
if updatedSettings.OAuthSettings.LogoutURI != "" {
t.Errorf("unexpected default OAuth HideInternalAuth setting, want:, got: %s", updatedSettings.OAuthSettings.LogoutURI)
}
}
// if updatedSettings.OAuthSettings.LogoutURI != "" {
// t.Errorf("unexpected default OAuth HideInternalAuth setting, want:, got: %s", updatedSettings.OAuthSettings.LogoutURI)
// }
// }

View File

@@ -31,22 +31,22 @@ func TestMigrateStackEntryPoint(t *testing.T) {
assert.NoError(t, err, "failed to create stack")
}
s, err := stackService.Read(1)
s, err := stackService.Stack(1)
assert.NoError(t, err)
assert.Nil(t, s.GitConfig, "first stack should not have git config")
s, err = stackService.Read(2)
s, err = stackService.Stack(2)
assert.NoError(t, err)
assert.Equal(t, "", s.GitConfig.ConfigFilePath, "not migrated yet migrated")
err = migrator.MigrateStackEntryPoint(stackService)
assert.NoError(t, err, "failed to migrate entry point to Git ConfigFilePath")
s, err = stackService.Read(1)
s, err = stackService.Stack(1)
assert.NoError(t, err)
assert.Nil(t, s.GitConfig, "first stack should not have git config")
s, err = stackService.Read(2)
s, err = stackService.Stack(2)
assert.NoError(t, err)
assert.Equal(t, "dir/sub/compose.yml", s.GitConfig.ConfigFilePath, "second stack should have config file path migrated")
}

View File

@@ -1,115 +1,115 @@
package datastore
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices"
)
// 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"
)
// 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",
}
// 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"
}
// func dbVersionToSemanticVersion(dbVersion int) string {
// if dbVersion < 18 {
// return "1.0.0"
// }
ver, ok := dbVerToSemVerMap[dbVersion]
if ok {
return ver
}
// 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"
}
// // 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"
}
// 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(portainer.PortainerCE)
instanceId := ""
// // 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
}
// // 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(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(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
}
// 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
}
// 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)
// // 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
}
// // 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,11 +1,13 @@
package migrator
import (
"fmt"
"reflect"
"runtime"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
"github.com/Masterminds/semver"
"github.com/rs/zerolog/log"
@@ -21,16 +23,28 @@ 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()
versions, err := m.versionService.GetAll()
if err != nil {
return migrationError(err, "get version service")
}
schemaVersion, err := semver.NewVersion(version.SchemaVersion)
version, ok := versions[models.VersionKey]
if !ok {
return migrationError(err, "no version specified in the DB")
}
schemaVersion, err := semver.NewVersion(fmt.Sprintf("%v", version))
if err != nil {
return migrationError(err, "invalid db schema version")
}
migratorCount, ok := versions[models.MigratorCountKey]
if ok {
migratorCount = migratorCount.(int)
} else {
migratorCount = 0
}
newMigratorCount := 0
apiVersion := semver.MustParse(portainer.APIVersion)
if schemaVersion.Equal(apiVersion) {
@@ -38,7 +52,7 @@ func (m *Migrator) Migrate() error {
// e.g. development builds
latestMigrations := m.LatestMigrations()
if latestMigrations.Version.Equal(schemaVersion) &&
version.MigratorCount != len(latestMigrations.MigrationFuncs) {
migratorCount != len(latestMigrations.MigrationFuncs) {
err := runMigrations(latestMigrations.MigrationFuncs)
if err != nil {
return err
@@ -68,10 +82,10 @@ func (m *Migrator) Migrate() error {
return migrationError(err, "Always migrations returned error")
}
version.SchemaVersion = portainer.APIVersion
version.MigratorCount = newMigratorCount
versions[models.SchemaVersionKey] = portainer.APIVersion
versions[models.MigratorCountKey] = newMigratorCount
err = m.versionService.UpdateVersion(version)
err = m.versionService.UpdateAll(versions)
if err != nil {
return migrationError(err, "StoreDBVersion")
}
@@ -91,35 +105,35 @@ func runMigrations(migrationFuncs []func() error) error {
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
// 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
}
// // 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
}
// 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
}
}
// // 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
}
// return false
// }

View File

@@ -2,11 +2,7 @@ package migrator
import (
"os"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/chisel/crypto"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
@@ -24,133 +20,10 @@ func (m *Migrator) migrateDockerDesktopExtentionSetting() error {
}
settings.IsDockerDesktopExtension = isDDExtension
return m.settingsService.UpdateSettings(settings)
}
func (m *Migrator) convertSeedToPrivateKeyForDB100() error {
var serverInfo *portainer.TunnelServerInfo
serverInfo, err := m.TunnelServerService.Info()
if err != nil {
if dataservices.IsErrObjectNotFound(err) {
log.Info().Msg("ServerInfo object not found")
return nil
}
log.Error().
Err(err).
Msg("Failed to read ServerInfo from DB")
return err
}
if serverInfo.PrivateKeySeed != "" {
key, err := crypto.GenerateGo119CompatibleKey(serverInfo.PrivateKeySeed)
if err != nil {
log.Error().
Err(err).
Msg("Failed to read ServerInfo from DB")
return err
}
err = m.fileService.StoreChiselPrivateKey(key)
if err != nil {
log.Error().
Err(err).
Msg("Failed to save Chisel private key to disk")
return err
}
} else {
log.Info().Msg("PrivateKeySeed is blank")
}
serverInfo.PrivateKeySeed = ""
err = m.TunnelServerService.UpdateInfo(serverInfo)
if err != nil {
log.Error().
Err(err).
Msg("Failed to clean private key seed in DB")
} else {
log.Info().Msg("Success to migrate private key seed to private key file")
}
return err
}
func (m *Migrator) updateEdgeStackStatusForDB100() error {
log.Info().Msg("update edge stack status to have deployment steps")
edgeStacks, err := m.edgeStackService.EdgeStacks()
err = m.settingsService.UpdateSettings(settings)
if err != nil {
return err
}
for _, edgeStack := range edgeStacks {
for environmentID, environmentStatus := range edgeStack.Status {
// skip if status is already updated
if len(environmentStatus.Status) > 0 {
continue
}
statusArray := []portainer.EdgeStackDeploymentStatus{}
if environmentStatus.Details.Pending {
statusArray = append(statusArray, portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusPending,
Time: time.Now().Unix(),
})
}
if environmentStatus.Details.Acknowledged {
statusArray = append(statusArray, portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusAcknowledged,
Time: time.Now().Unix(),
})
}
if environmentStatus.Details.Error {
statusArray = append(statusArray, portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusError,
Error: environmentStatus.Error,
Time: time.Now().Unix(),
})
}
if environmentStatus.Details.Ok {
statusArray = append(statusArray,
portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusDeploymentReceived,
Time: time.Now().Unix(),
},
portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusRunning,
Time: time.Now().Unix(),
},
)
}
if environmentStatus.Details.ImagesPulled {
statusArray = append(statusArray, portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusImagesPulled,
Time: time.Now().Unix(),
})
}
if environmentStatus.Details.Remove {
statusArray = append(statusArray, portainer.EdgeStackDeploymentStatus{
Type: portainer.EdgeStackStatusRemoving,
Time: time.Now().Unix(),
})
}
environmentStatus.Status = statusArray
edgeStack.Status[environmentID] = environmentStatus
}
err = m.edgeStackService.UpdateEdgeStack(edgeStack.ID, &edgeStack)
if err != nil {
return err
}
}
return nil
}

View File

@@ -9,7 +9,7 @@ import (
func (m *Migrator) updateUsersToDBVersion18() error {
log.Info().Msg("updating users")
legacyUsers, err := m.userService.ReadAll()
legacyUsers, err := m.userService.Users()
if err != nil {
return err
}
@@ -33,7 +33,7 @@ func (m *Migrator) updateUsersToDBVersion18() error {
portainer.OperationPortainerUserMemberships: true,
}
err = m.userService.Update(user.ID, &user)
err = m.userService.UpdateUser(user.ID, &user)
if err != nil {
return err
}
@@ -77,7 +77,7 @@ func (m *Migrator) updateEndpointsToDBVersion18() error {
func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
log.Info().Msg("updating endpoint groups")
legacyEndpointGroups, err := m.endpointGroupService.ReadAll()
legacyEndpointGroups, err := m.endpointGroupService.EndpointGroups()
if err != nil {
return err
}
@@ -97,7 +97,7 @@ func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
}
}
err = m.endpointGroupService.Update(endpointGroup.ID, &endpointGroup)
err = m.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
if err != nil {
return err
}
@@ -109,7 +109,7 @@ func (m *Migrator) updateEndpointGroupsToDBVersion18() error {
func (m *Migrator) updateRegistriesToDBVersion18() error {
log.Info().Msg("updating registries")
legacyRegistries, err := m.registryService.ReadAll()
legacyRegistries, err := m.registryService.Registries()
if err != nil {
return err
}
@@ -125,7 +125,7 @@ func (m *Migrator) updateRegistriesToDBVersion18() error {
registry.TeamAccessPolicies[teamID] = portainer.AccessPolicy{}
}
err = m.registryService.Update(registry.ID, &registry)
err = m.registryService.UpdateRegistry(registry.ID, &registry)
if err != nil {
return err
}

View File

@@ -10,7 +10,7 @@ import (
func (m *Migrator) updateResourceControlsToDBVersion22() error {
log.Info().Msg("updating resource controls")
legacyResourceControls, err := m.resourceControlService.ReadAll()
legacyResourceControls, err := m.resourceControlService.ResourceControls()
if err != nil {
return err
}
@@ -18,7 +18,7 @@ func (m *Migrator) updateResourceControlsToDBVersion22() error {
for _, resourceControl := range legacyResourceControls {
resourceControl.AdministratorsOnly = false
err := m.resourceControlService.Update(resourceControl.ID, &resourceControl)
err := m.resourceControlService.UpdateResourceControl(resourceControl.ID, &resourceControl)
if err != nil {
return err
}
@@ -30,7 +30,7 @@ func (m *Migrator) updateResourceControlsToDBVersion22() error {
func (m *Migrator) updateUsersAndRolesToDBVersion22() error {
log.Info().Msg("updating users and roles")
legacyUsers, err := m.userService.ReadAll()
legacyUsers, err := m.userService.Users()
if err != nil {
return err
}
@@ -42,47 +42,47 @@ func (m *Migrator) updateUsersAndRolesToDBVersion22() error {
for _, user := range legacyUsers {
user.PortainerAuthorizations = authorization.DefaultPortainerAuthorizations()
err = m.userService.Update(user.ID, &user)
err = m.userService.UpdateUser(user.ID, &user)
if err != nil {
return err
}
}
endpointAdministratorRole, err := m.roleService.Read(portainer.RoleID(1))
endpointAdministratorRole, err := m.roleService.Role(portainer.RoleID(1))
if err != nil {
return err
}
endpointAdministratorRole.Priority = 1
endpointAdministratorRole.Authorizations = authorization.DefaultEndpointAuthorizationsForEndpointAdministratorRole()
err = m.roleService.Update(endpointAdministratorRole.ID, endpointAdministratorRole)
err = m.roleService.UpdateRole(endpointAdministratorRole.ID, endpointAdministratorRole)
helpDeskRole, err := m.roleService.Read(portainer.RoleID(2))
helpDeskRole, err := m.roleService.Role(portainer.RoleID(2))
if err != nil {
return err
}
helpDeskRole.Priority = 2
helpDeskRole.Authorizations = authorization.DefaultEndpointAuthorizationsForHelpDeskRole(settings.AllowVolumeBrowserForRegularUsers)
err = m.roleService.Update(helpDeskRole.ID, helpDeskRole)
err = m.roleService.UpdateRole(helpDeskRole.ID, helpDeskRole)
standardUserRole, err := m.roleService.Read(portainer.RoleID(3))
standardUserRole, err := m.roleService.Role(portainer.RoleID(3))
if err != nil {
return err
}
standardUserRole.Priority = 3
standardUserRole.Authorizations = authorization.DefaultEndpointAuthorizationsForStandardUserRole(settings.AllowVolumeBrowserForRegularUsers)
err = m.roleService.Update(standardUserRole.ID, standardUserRole)
err = m.roleService.UpdateRole(standardUserRole.ID, standardUserRole)
readOnlyUserRole, err := m.roleService.Read(portainer.RoleID(4))
readOnlyUserRole, err := m.roleService.Role(portainer.RoleID(4))
if err != nil {
return err
}
readOnlyUserRole.Priority = 4
readOnlyUserRole.Authorizations = authorization.DefaultEndpointAuthorizationsForReadOnlyUserRole(settings.AllowVolumeBrowserForRegularUsers)
err = m.roleService.Update(readOnlyUserRole.ID, readOnlyUserRole)
err = m.roleService.UpdateRole(readOnlyUserRole.ID, readOnlyUserRole)
if err != nil {
return err
}

View File

@@ -9,7 +9,7 @@ import (
func (m *Migrator) updateTagsToDBVersion23() error {
log.Info().Msg("updating tags")
tags, err := m.tagService.ReadAll()
tags, err := m.tagService.Tags()
if err != nil {
return err
}
@@ -17,7 +17,7 @@ func (m *Migrator) updateTagsToDBVersion23() error {
for _, tag := range tags {
tag.EndpointGroups = make(map[portainer.EndpointGroupID]bool)
tag.Endpoints = make(map[portainer.EndpointID]bool)
err = m.tagService.Update(tag.ID, &tag)
err = m.tagService.UpdateTag(tag.ID, &tag)
if err != nil {
return err
}
@@ -29,7 +29,7 @@ func (m *Migrator) updateTagsToDBVersion23() error {
func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
log.Info().Msg("updating endpoints and endpoint groups")
tags, err := m.tagService.ReadAll()
tags, err := m.tagService.Tags()
if err != nil {
return err
}
@@ -70,7 +70,7 @@ func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
}
}
endpointGroups, err := m.endpointGroupService.ReadAll()
endpointGroups, err := m.endpointGroupService.EndpointGroups()
if err != nil {
return err
}
@@ -85,14 +85,14 @@ func (m *Migrator) updateEndpointsAndEndpointGroupsToDBVersion23() error {
}
}
endpointGroup.TagIDs = endpointGroupTags
err = m.endpointGroupService.Update(endpointGroup.ID, &endpointGroup)
err = m.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
if err != nil {
return err
}
}
for _, tag := range tagsNameMap {
err = m.tagService.Update(tag.ID, &tag)
err = m.tagService.UpdateTag(tag.ID, &tag)
if err != nil {
return err
}

View File

@@ -24,7 +24,7 @@ func (m *Migrator) updateSettingsToDB24() error {
func (m *Migrator) updateStacksToDB24() error {
log.Info().Msg("updating stacks")
stacks, err := m.stackService.ReadAll()
stacks, err := m.stackService.Stacks()
if err != nil {
return err
}
@@ -32,7 +32,7 @@ func (m *Migrator) updateStacksToDB24() error {
for idx := range stacks {
stack := &stacks[idx]
stack.Status = portainer.StackStatusActive
err := m.stackService.Update(stack.ID, stack)
err := m.stackService.UpdateStack(stack.ID, stack)
if err != nil {
return err
}

View File

@@ -11,7 +11,7 @@ import (
func (m *Migrator) updateStackResourceControlToDB27() error {
log.Info().Msg("updating stack resource controls")
resourceControls, err := m.resourceControlService.ReadAll()
resourceControls, err := m.resourceControlService.ResourceControls()
if err != nil {
return err
}
@@ -34,7 +34,7 @@ func (m *Migrator) updateStackResourceControlToDB27() error {
resource.ResourceID = stackutils.ResourceControlID(stack.EndpointID, stack.Name)
err = m.resourceControlService.Update(resource.ID, &resource)
err = m.resourceControlService.UpdateResourceControl(resource.ID, &resource)
if err != nil {
return err
}

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