mirror of
https://github.com/portainer/portainer.git
synced 2025-08-09 15:55:23 +02:00
WIP
This commit is contained in:
parent
ecf7f7ec14
commit
525efdc035
29 changed files with 1563 additions and 1749 deletions
|
@ -1,9 +1,5 @@
|
|||
package portainer
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type ReadTransaction interface {
|
||||
GetObject(bucketName string, key []byte, object interface{}) error
|
||||
GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error
|
||||
|
@ -25,19 +21,11 @@ type Transaction interface {
|
|||
}
|
||||
|
||||
type Connection interface {
|
||||
Transaction
|
||||
|
||||
Open() error
|
||||
Close() error
|
||||
|
||||
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
|
||||
Init() error
|
||||
|
||||
// TODO: this one is very database specific atm
|
||||
BackupTo(w io.Writer) error
|
||||
GetDatabaseFileName() string
|
||||
GetDatabaseFilePath() string
|
||||
GetStorePath() string
|
||||
|
@ -45,10 +33,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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
return &sqlite.DbConnection{
|
||||
Path: storePath,
|
||||
EncryptionKey: encryptionKey,
|
||||
}, nil
|
||||
|
|
169
api/database/sqlite/db.go
Normal file
169
api/database/sqlite/db.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
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) Init() error {
|
||||
connection.DB.AutoMigrate(&models.Version{})
|
||||
connection.DB.AutoMigrate(&portainer.Settings{})
|
||||
return nil
|
||||
}
|
|
@ -1,14 +1,7 @@
|
|||
package apikeyrepository
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -23,10 +16,10 @@ 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
|
||||
}
|
||||
// err := connection.SetServiceName(BucketName)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
|
@ -37,90 +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
|
||||
}
|
||||
|
||||
// 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 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))
|
||||
// identifier := service.connection.ConvertToKey(int(keyID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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)
|
||||
// identifier := service.connection.ConvertToKey(int(key.ID))
|
||||
// return service.connection.UpdateObject(BucketName, identifier, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) DeleteAPIKey(ID portainer.APIKeyID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package customtemplate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -24,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,
|
||||
|
@ -38,56 +34,49 @@ func NewService(connection portainer.Connection) (*Service, error) {
|
|||
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)
|
||||
// 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 &portainer.CustomTemplate{}, nil
|
||||
// })
|
||||
|
||||
return customTemplates, err
|
||||
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))
|
||||
// identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &customTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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 {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, customTemplate)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCustomTemplate deletes an custom template.
|
||||
func (service *Service) DeleteCustomTemplate(ID portainer.CustomTemplateID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -18,11 +18,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
|
||||
|
@ -40,10 +35,10 @@ 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
|
||||
})
|
||||
// err = service.connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
// groups, err = service.Tx(tx).EdgeGroups()
|
||||
// return err
|
||||
// })
|
||||
|
||||
return groups, err
|
||||
}
|
||||
|
@ -53,40 +48,30 @@ func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGrou
|
|||
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
|
||||
})
|
||||
// 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 {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, group)
|
||||
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 service.connection.UpdateObjectFunc(BucketName, id, edgeGroup, func() {
|
||||
updateFunc(edgeGroup)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEdgeGroup deletes an Edge group.
|
||||
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
|
||||
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return service.Tx(tx).DeleteEdgeGroup(ID)
|
||||
})
|
||||
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
|
||||
}
|
||||
|
|
|
@ -2,11 +2,8 @@ package edgegroup
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ServiceTx struct {
|
||||
|
@ -22,40 +19,33 @@ func (service ServiceTx) BucketName() string {
|
|||
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)
|
||||
// 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 &portainer.EdgeGroup{}, nil
|
||||
// })
|
||||
|
||||
return groups, err
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// EdgeGroup returns an Edge group by ID.
|
||||
func (service ServiceTx) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error) {
|
||||
var group portainer.EdgeGroup
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.tx.GetObject(BucketName, identifier, &group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &group, nil
|
||||
}
|
||||
|
||||
// UpdateEdgeGroup updates an edge group.
|
||||
func (service ServiceTx) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
return service.tx.UpdateObject(BucketName, identifier, group)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateEdgeGroupFunc is a no-op inside a transaction.
|
||||
|
@ -65,16 +55,9 @@ func (service ServiceTx) UpdateEdgeGroupFunc(ID portainer.EdgeGroupID, updateFun
|
|||
|
||||
// DeleteEdgeGroup deletes an Edge group.
|
||||
func (service ServiceTx) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
return service.tx.DeleteObject(BucketName, identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package edgejob
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
|
@ -22,11 +18,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
|
||||
|
@ -42,72 +33,32 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
|
|||
// EdgeJobs returns a list of Edge jobs
|
||||
func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
|
||||
var edgeJobs = make([]portainer.EdgeJob, 0)
|
||||
|
||||
err := service.connection.GetAll(
|
||||
BucketName,
|
||||
&portainer.EdgeJob{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
job, ok := obj.(*portainer.EdgeJob)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object")
|
||||
return nil, fmt.Errorf("Failed to convert to EdgeJob object: %s", obj)
|
||||
}
|
||||
|
||||
edgeJobs = append(edgeJobs, *job)
|
||||
|
||||
return &portainer.EdgeJob{}, nil
|
||||
})
|
||||
|
||||
return edgeJobs, err
|
||||
return edgeJobs, nil
|
||||
}
|
||||
|
||||
// EdgeJob returns an Edge job by ID
|
||||
func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
|
||||
var edgeJob portainer.EdgeJob
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.connection.GetObject(BucketName, identifier, &edgeJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &edgeJob, nil
|
||||
}
|
||||
|
||||
// Create creates a new EdgeJob
|
||||
func (service *Service) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||
edgeJob.ID = ID
|
||||
|
||||
return service.connection.CreateObjectWithId(
|
||||
BucketName,
|
||||
int(edgeJob.ID),
|
||||
edgeJob,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: use UpdateEdgeJobFunc instead
|
||||
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.UpdateObject(BucketName, identifier, edgeJob)
|
||||
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
|
||||
}
|
||||
|
||||
// DeleteEdgeJob deletes an Edge job
|
||||
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
||||
identifier := service.connection.ConvertToKey(int(ID))
|
||||
return service.connection.DeleteObject(BucketName, identifier)
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return service.connection.GetNextIdentifier(BucketName)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,11 +2,8 @@ package edgejob
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ServiceTx struct {
|
||||
|
@ -22,33 +19,12 @@ func (service ServiceTx) BucketName() string {
|
|||
func (service ServiceTx) EdgeJobs() ([]portainer.EdgeJob, error) {
|
||||
var edgeJobs = make([]portainer.EdgeJob, 0)
|
||||
|
||||
err := service.tx.GetAll(
|
||||
BucketName,
|
||||
&portainer.EdgeJob{},
|
||||
func(obj interface{}) (interface{}, error) {
|
||||
job, ok := obj.(*portainer.EdgeJob)
|
||||
if !ok {
|
||||
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object")
|
||||
return nil, fmt.Errorf("failed to convert to EdgeJob object: %s", obj)
|
||||
}
|
||||
|
||||
edgeJobs = append(edgeJobs, *job)
|
||||
|
||||
return &portainer.EdgeJob{}, nil
|
||||
})
|
||||
|
||||
return edgeJobs, err
|
||||
return edgeJobs, nil
|
||||
}
|
||||
|
||||
// EdgeJob returns an Edge job by ID
|
||||
func (service ServiceTx) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
|
||||
var edgeJob portainer.EdgeJob
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
err := service.tx.GetObject(BucketName, identifier, &edgeJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &edgeJob, nil
|
||||
}
|
||||
|
@ -62,8 +38,7 @@ func (service ServiceTx) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJ
|
|||
|
||||
// UpdateEdgeJob updates an edge job
|
||||
func (service ServiceTx) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
return service.tx.UpdateObject(BucketName, identifier, edgeJob)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateEdgeJobFunc is a no-op inside a transaction.
|
||||
|
@ -73,12 +48,5 @@ func (service ServiceTx) UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc fu
|
|||
|
||||
// DeleteEdgeJob deletes an Edge job
|
||||
func (service ServiceTx) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
||||
identifier := service.service.connection.ConvertToKey(int(ID))
|
||||
|
||||
return service.tx.DeleteObject(BucketName, identifier)
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
func (service ServiceTx) GetNextIdentifier() int {
|
||||
return service.tx.GetNextIdentifier(BucketName)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package edgestack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
|
@ -26,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),
|
||||
|
@ -63,35 +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)
|
||||
|
||||
err := service.connection.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 *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
|
||||
}
|
||||
|
||||
|
@ -108,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)
|
||||
|
@ -130,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)
|
||||
|
||||
|
@ -145,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.
|
||||
|
@ -169,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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -60,33 +60,18 @@ 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
|
||||
|
||||
err = service.connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
endpoint, err = service.Tx(tx).Endpoint(ID)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoint.LastCheckInDate, _ = service.Heartbeat(ID)
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// UpdateEndpoint updates an environment(endpoint).
|
||||
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||
return service.connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return service.Tx(tx).UpdateEndpoint(ID, endpoint)
|
||||
})
|
||||
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)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) endpoints() ([]portainer.Endpoint, error) {
|
||||
|
@ -139,20 +124,5 @@ 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)
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,189 +1,189 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
// import (
|
||||
// "fmt"
|
||||
// "os"
|
||||
// "path"
|
||||
// "time"
|
||||
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
// "github.com/portainer/portainer/api/database/models"
|
||||
// "github.com/rs/zerolog/log"
|
||||
// )
|
||||
|
||||
var backupDefaults = struct {
|
||||
backupDir string
|
||||
commonDir string
|
||||
}{
|
||||
"backups",
|
||||
"common",
|
||||
}
|
||||
// var backupDefaults = struct {
|
||||
// backupDir string
|
||||
// commonDir string
|
||||
// }{
|
||||
// "backups",
|
||||
// "common",
|
||||
// }
|
||||
|
||||
//
|
||||
// Backup Helpers
|
||||
//
|
||||
// //
|
||||
// // Backup Helpers
|
||||
// //
|
||||
|
||||
// createBackupFolders create initial folders for backups
|
||||
func (store *Store) createBackupFolders() {
|
||||
// create common dir
|
||||
commonDir := store.commonBackupDir()
|
||||
if exists, _ := store.fileService.FileExists(commonDir); !exists {
|
||||
if err := os.MkdirAll(commonDir, 0700); err != nil {
|
||||
log.Error().Err(err).Msg("error while creating common backup folder")
|
||||
}
|
||||
}
|
||||
}
|
||||
// // 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) databasePath() string {
|
||||
return store.connection.GetDatabaseFilePath()
|
||||
}
|
||||
// func (store *Store) databasePath() string {
|
||||
// return store.connection.GetDatabaseFilePath()
|
||||
// }
|
||||
|
||||
func (store *Store) commonBackupDir() string {
|
||||
return path.Join(store.connection.GetStorePath(), backupDefaults.backupDir, backupDefaults.commonDir)
|
||||
}
|
||||
// func (store *Store) commonBackupDir() string {
|
||||
// return path.Join(store.connection.GetStorePath(), backupDefaults.backupDir, backupDefaults.commonDir)
|
||||
// }
|
||||
|
||||
func (store *Store) copyDBFile(from string, to string) error {
|
||||
log.Info().Str("from", from).Str("to", to).Msg("copying DB file")
|
||||
// func (store *Store) copyDBFile(from string, to string) error {
|
||||
// log.Info().Str("from", from).Str("to", to).Msg("copying DB file")
|
||||
|
||||
err := store.fileService.Copy(from, to, true)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed")
|
||||
}
|
||||
// err := store.fileService.Copy(from, to, true)
|
||||
// if err != nil {
|
||||
// log.Error().Err(err).Msg("failed")
|
||||
// }
|
||||
|
||||
return err
|
||||
}
|
||||
// return err
|
||||
// }
|
||||
|
||||
// BackupOptions provide a helper to inject backup options
|
||||
type BackupOptions struct {
|
||||
Version string
|
||||
BackupDir string
|
||||
BackupFileName string
|
||||
BackupPath string
|
||||
}
|
||||
// // BackupOptions provide a helper to inject backup options
|
||||
// type BackupOptions struct {
|
||||
// Version string
|
||||
// BackupDir string
|
||||
// BackupFileName string
|
||||
// BackupPath string
|
||||
// }
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
// // 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,
|
||||
// }
|
||||
// }
|
||||
|
||||
// Backup current database with default options
|
||||
func (store *Store) Backup(version *models.Version) (string, error) {
|
||||
if version == nil {
|
||||
return store.backupWithOptions(nil)
|
||||
}
|
||||
// // Backup current database with default options
|
||||
// func (store *Store) Backup(version *models.Version) (string, error) {
|
||||
// if version == nil {
|
||||
// return store.backupWithOptions(nil)
|
||||
// }
|
||||
|
||||
return store.backupWithOptions(&BackupOptions{
|
||||
Version: version.SchemaVersion,
|
||||
})
|
||||
}
|
||||
// return store.backupWithOptions(&BackupOptions{
|
||||
// Version: version.SchemaVersion,
|
||||
// })
|
||||
// }
|
||||
|
||||
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) 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
|
||||
// }
|
||||
|
||||
// BackupWithOptions backup current database with options
|
||||
func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
||||
log.Info().Msg("creating DB backup")
|
||||
// // BackupWithOptions backup current database with options
|
||||
// func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
||||
// log.Info().Msg("creating DB backup")
|
||||
|
||||
store.createBackupFolders()
|
||||
// store.createBackupFolders()
|
||||
|
||||
options = store.setupOptions(options)
|
||||
dbPath := store.databasePath()
|
||||
// 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.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.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,
|
||||
)
|
||||
}
|
||||
// if _, err := store.Open(); err != nil {
|
||||
// return options.BackupPath, fmt.Errorf(
|
||||
// "error opening datastore after creating backup: %w",
|
||||
// err,
|
||||
// )
|
||||
// }
|
||||
|
||||
return options.BackupPath, nil
|
||||
}
|
||||
// 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)
|
||||
// // 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")
|
||||
// // 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
|
||||
}
|
||||
// return err
|
||||
// }
|
||||
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while closing store before restore")
|
||||
// err = store.Close()
|
||||
// if err != nil {
|
||||
// log.Error().Err(err).Msg("error while closing store before restore")
|
||||
|
||||
return err
|
||||
}
|
||||
// return err
|
||||
// }
|
||||
|
||||
log.Info().Msg("restoring DB backup")
|
||||
err = store.copyDBFile(options.BackupPath, store.databasePath())
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
// _, 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")
|
||||
// // 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)
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
|
|
@ -1,107 +1,107 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"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"
|
||||
// )
|
||||
|
||||
func TestCreateBackupFolders(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, true, true)
|
||||
// func TestCreateBackupFolders(t *testing.T) {
|
||||
// _, store := MustNewTestStore(t, true, true)
|
||||
|
||||
connection := store.GetConnection()
|
||||
backupPath := path.Join(connection.GetStorePath(), backupDefaults.backupDir)
|
||||
// connection := store.GetConnection()
|
||||
// backupPath := path.Join(connection.GetStorePath(), backupDefaults.backupDir)
|
||||
|
||||
if isFileExist(backupPath) {
|
||||
t.Error("Expect backups folder to not exist")
|
||||
}
|
||||
// if isFileExist(backupPath) {
|
||||
// t.Error("Expect backups folder to not exist")
|
||||
// }
|
||||
|
||||
store.createBackupFolders()
|
||||
if !isFileExist(backupPath) {
|
||||
t.Error("Expect backups folder to exist")
|
||||
}
|
||||
}
|
||||
// store.createBackupFolders()
|
||||
// if !isFileExist(backupPath) {
|
||||
// t.Error("Expect backups folder to exist")
|
||||
// }
|
||||
// }
|
||||
|
||||
func TestStoreCreation(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, true, true)
|
||||
if store == nil {
|
||||
t.Error("Expect to create a store")
|
||||
}
|
||||
// func TestStoreCreation(t *testing.T) {
|
||||
// _, store := MustNewTestStore(t, true, true)
|
||||
// if store == nil {
|
||||
// t.Error("Expect to create a store")
|
||||
// }
|
||||
|
||||
if store.CheckCurrentEdition() != nil {
|
||||
t.Error("Expect to get CE Edition")
|
||||
}
|
||||
}
|
||||
// if store.CheckCurrentEdition() != nil {
|
||||
// t.Error("Expect to get CE Edition")
|
||||
// }
|
||||
// }
|
||||
|
||||
func TestBackup(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, true, true)
|
||||
connection := store.GetConnection()
|
||||
// func TestBackup(t *testing.T) {
|
||||
// _, store := MustNewTestStore(t, true, true)
|
||||
// connection := store.GetConnection()
|
||||
|
||||
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("Backup should create default db backup", func(t *testing.T) {
|
||||
// v := models.Version{
|
||||
// SchemaVersion: portainer.APIVersion,
|
||||
// }
|
||||
// store.VersionService.UpdateVersion(&v)
|
||||
// store.backupWithOptions(nil)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
// 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)
|
||||
// }
|
||||
// })
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 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)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
func TestRemoveWithOptions(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, true, true)
|
||||
// func TestRemoveWithOptions(t *testing.T) {
|
||||
// _, store := MustNewTestStore(t, true, true)
|
||||
|
||||
t.Run("successfully removes file if existent", func(t *testing.T) {
|
||||
store.createBackupFolders()
|
||||
options := &BackupOptions{
|
||||
BackupDir: store.commonBackupDir(),
|
||||
BackupFileName: "test.txt",
|
||||
}
|
||||
// t.Run("successfully removes file if existent", func(t *testing.T) {
|
||||
// store.createBackupFolders()
|
||||
// options := &BackupOptions{
|
||||
// BackupDir: store.commonBackupDir(),
|
||||
// BackupFileName: "test.txt",
|
||||
// }
|
||||
|
||||
filePath := path.Join(options.BackupDir, options.BackupFileName)
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("file should be created; err=%s", err)
|
||||
}
|
||||
f.Close()
|
||||
// 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)
|
||||
}
|
||||
// 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())
|
||||
}
|
||||
})
|
||||
// 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",
|
||||
}
|
||||
// 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
// err := store.removeWithOptions(options)
|
||||
// if err == nil {
|
||||
// t.Error("RemoveWithOptions should fail for non-existent file")
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
|
|
@ -3,13 +3,11 @@ package datastore
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
portainerErrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -63,30 +61,6 @@ 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 {
|
||||
if store.edition() != portainer.Edition {
|
||||
|
@ -113,7 +87,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 {
|
||||
|
@ -133,7 +107,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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
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")
|
||||
}
|
||||
// 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")
|
||||
// }
|
||||
|
|
|
@ -1,145 +1,145 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
// )
|
||||
|
||||
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
|
||||
// const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
|
||||
|
||||
func (store *Store) MigrateData() error {
|
||||
updating, err := store.VersionService.IsUpdating()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while checking if the store is updating")
|
||||
}
|
||||
// func (store *Store) MigrateData() error {
|
||||
// updating, err := store.VersionService.IsUpdating()
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "while checking if the store is updating")
|
||||
// }
|
||||
|
||||
if updating {
|
||||
return dserrors.ErrDatabaseIsUpdating
|
||||
}
|
||||
// if updating {
|
||||
// return dserrors.ErrDatabaseIsUpdating
|
||||
// }
|
||||
|
||||
// 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")
|
||||
}
|
||||
// // migrate new version bucket if required (doesn't write anything to db yet)
|
||||
// version, err := store.getOrMigrateLegacyVersion()
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "while migrating legacy version")
|
||||
// }
|
||||
|
||||
migratorParams := store.newMigratorParameters(version)
|
||||
migrator := migrator.NewMigrator(migratorParams)
|
||||
// migratorParams := store.newMigratorParameters(version)
|
||||
// migrator := migrator.NewMigrator(migratorParams)
|
||||
|
||||
if !migrator.NeedsMigration() {
|
||||
return nil
|
||||
}
|
||||
// if !migrator.NeedsMigration() {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// before we alter anything in the DB, create a backup
|
||||
backupPath, err := store.Backup(version)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while backing up database")
|
||||
}
|
||||
// // before we alter anything in the DB, create a backup
|
||||
// backupPath, err := store.Backup(version)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "while backing up database")
|
||||
// }
|
||||
|
||||
err = store.FailSafeMigrate(migrator, version)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to migrate database")
|
||||
// err = store.FailSafeMigrate(migrator, version)
|
||||
// if err != nil {
|
||||
// err = errors.Wrap(err, "failed to migrate database")
|
||||
|
||||
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")
|
||||
}
|
||||
// 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")
|
||||
// }
|
||||
|
||||
log.Info().Msg("database restored to previous version")
|
||||
return err
|
||||
}
|
||||
// log.Info().Msg("database restored to previous version")
|
||||
// return err
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (store *Store) newMigratorParameters(version *models.Version) *migrator.MigratorParameters {
|
||||
return &migrator.MigratorParameters{
|
||||
CurrentDBVersion: version,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointRelationService: store.EndpointRelationService,
|
||||
ExtensionService: store.ExtensionService,
|
||||
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,
|
||||
}
|
||||
}
|
||||
// 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,
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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()))
|
||||
}
|
||||
}()
|
||||
// // 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()))
|
||||
// }
|
||||
// }()
|
||||
|
||||
err = store.VersionService.StoreIsUpdating(true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while updating the store")
|
||||
}
|
||||
// err = store.VersionService.StoreIsUpdating(true)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "while updating the store")
|
||||
// }
|
||||
|
||||
// now update the version to the new struct (if required)
|
||||
err = store.finishMigrateLegacyVersion(version)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while updating version")
|
||||
}
|
||||
// // now update the version to the new struct (if required)
|
||||
// err = store.finishMigrateLegacyVersion(version)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "while updating version")
|
||||
// }
|
||||
|
||||
log.Info().Msg("migrating database from version " + version.SchemaVersion + " to " + portainer.APIVersion)
|
||||
// log.Info().Msg("migrating database from version " + version.SchemaVersion + " to " + portainer.APIVersion)
|
||||
|
||||
err = migrator.Migrate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
|
||||
options := getBackupRestoreOptions(store.commonBackupDir())
|
||||
// options := getBackupRestoreOptions(store.commonBackupDir())
|
||||
|
||||
err := store.restoreWithOptions(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// err := store.restoreWithOptions(options)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return store.connection.Close()
|
||||
}
|
||||
// return store.connection.Close()
|
||||
// }
|
||||
|
|
|
@ -1,20 +1,9 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// testVersion is a helper which tests current store version against wanted version
|
||||
|
@ -29,31 +18,31 @@ func testVersion(store *Store, versionWant string, t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMigrateData(t *testing.T) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 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, teardown := MustNewTestStore(t, true, false)
|
||||
|
@ -163,59 +152,59 @@ func TestMigrateData(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_getBackupRestoreOptions(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, false, true)
|
||||
// _, store := MustNewTestStore(t, false, true)
|
||||
|
||||
options := getBackupRestoreOptions(store.commonBackupDir())
|
||||
// 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")
|
||||
}
|
||||
// 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")
|
||||
}
|
||||
// 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 := models.Version{SchemaVersion: "2.4.0"}
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
// t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
|
||||
// version := models.Version{SchemaVersion: "2.4.0"}
|
||||
// _, store := MustNewTestStore(t, true, false)
|
||||
|
||||
err := store.VersionService.UpdateVersion(&version)
|
||||
if err != nil {
|
||||
t.Errorf("Failed updating version: %v", err)
|
||||
}
|
||||
// err := store.VersionService.UpdateVersion(&version)
|
||||
// if err != nil {
|
||||
// t.Errorf("Failed updating version: %v", err)
|
||||
// }
|
||||
|
||||
_, err = store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
// _, err = store.backupWithOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
// 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("")
|
||||
}
|
||||
// // Change the current version
|
||||
// version2 := models.Version{SchemaVersion: "2.6.0"}
|
||||
// err = store.VersionService.UpdateVersion(&version2)
|
||||
// if err != nil {
|
||||
// log.Fatal().Err(err).Msg("")
|
||||
// }
|
||||
|
||||
err = store.Rollback(true)
|
||||
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
|
||||
}
|
||||
// _, err = store.Open()
|
||||
// if err != nil {
|
||||
// t.Logf("Open failed: %s", err)
|
||||
// t.Fail()
|
||||
// return
|
||||
// }
|
||||
|
||||
testVersion(store, version.SchemaVersion, t)
|
||||
})
|
||||
// testVersion(store, version.SchemaVersion, t)
|
||||
// })
|
||||
}
|
||||
|
||||
// isFileExist is helper function to check for file existence
|
||||
|
@ -231,288 +220,288 @@ func isFileExist(path string) bool {
|
|||
// 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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,115 +1,115 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
portaineree "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(portaineree.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
|
||||
// }
|
||||
|
|
|
@ -584,10 +584,10 @@ func (store *Store) Export(filename string) (err error) {
|
|||
backup.Version = *version
|
||||
}
|
||||
|
||||
backup.Metadata, err = store.connection.BackupMetadata()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("exporting Metadata")
|
||||
}
|
||||
// backup.Metadata, err = store.connection.BackupMetadata()
|
||||
// if err != nil {
|
||||
// log.Error().Err(err).Msg("exporting Metadata")
|
||||
// }
|
||||
|
||||
b, err := json.MarshalIndent(backup, "", " ")
|
||||
if err != nil {
|
||||
|
@ -689,5 +689,5 @@ func (store *Store) Import(filename string) (err error) {
|
|||
store.Webhook().UpdateWebhook(v.ID, &v)
|
||||
}
|
||||
|
||||
return store.connection.RestoreMetadata(backup.Metadata)
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue