Compare commits
1 Commits
fix/EE-187
...
fix/EE-171
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00ac9e6777 |
2
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -30,7 +30,7 @@ A clear and concise description of what you expected to happen.
|
||||
|
||||
**Portainer Logs**
|
||||
Provide the logs of your Portainer container or Service.
|
||||
You can see how [here](https://documentation.portainer.io/r/portainer-logs)
|
||||
You can see how [here](https://documentation.portainer.io/archive/1.23.2/faq/#how-do-i-get-the-logs-from-portainer)
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Portainer Business
|
||||
url: https://www.portainer.io/portainerbusiness
|
||||
about: Would you and your co-workers benefit from our enterprise edition which provides functionality to deploy Portainer at scale?
|
||||
@@ -44,7 +44,7 @@ Portainer CE is an open source project and is supported by the community. You ca
|
||||
Learn more about Portainers community support channels [here.](https://www.portainer.io/help_about)
|
||||
|
||||
- Issues: https://github.com/portainer/portainer/issues
|
||||
- Slack (chat): [https://portainer.slack.com/](https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA)
|
||||
- Slack (chat): https://portainer.io/slack/
|
||||
|
||||
You can join the Portainer Community by visiting community.portainer.io. This will give you advance notice of events, content and other related Portainer content.
|
||||
|
||||
@@ -59,7 +59,7 @@ You can join the Portainer Community by visiting community.portainer.io. This wi
|
||||
|
||||
## WORK FOR US
|
||||
|
||||
If you are a developer, and our code in this repo makes sense to you, we would love to hear from you. We are always on the hunt for awesome devs, either freelance or employed. Drop us a line to info@portainer.io with your details and we will be in touch.
|
||||
If you are a developer, and our code in this repo makes sense to you, we would love to hear from you. We are always on the hunt for awesome devs, either freelance or employed. Drop us a line to info@portainer.io with your details and we will be in touch.
|
||||
|
||||
## Privacy
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ You can find out more about Portainer at [http://portainer.io](http://portainer.
|
||||
|
||||
# Authentication
|
||||
|
||||
Most of the API environments(endpoints) require to be authenticated as well as some level of authorization to be used.
|
||||
Most of the API endpoints require to be authenticated as well as some level of authorization to be used.
|
||||
Portainer API uses JSON Web Token to manage authentication and thus requires you to provide a token in the **Authorization** header of each request
|
||||
with the **Bearer** authentication mechanism.
|
||||
|
||||
@@ -16,7 +16,7 @@ Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIs
|
||||
|
||||
# Security
|
||||
|
||||
Each API environment(endpoint) has an associated access policy, it is documented in the description of each environment(endpoint).
|
||||
Each API endpoint has an associated access policy, it is documented in the description of each endpoint.
|
||||
|
||||
Different access policies are available:
|
||||
|
||||
@@ -27,27 +27,27 @@ Different access policies are available:
|
||||
|
||||
### Public access
|
||||
|
||||
No authentication is required to access the environments(endpoints) with this access policy.
|
||||
No authentication is required to access the endpoints with this access policy.
|
||||
|
||||
### Authenticated access
|
||||
|
||||
Authentication is required to access the environments(endpoints) with this access policy.
|
||||
Authentication is required to access the endpoints with this access policy.
|
||||
|
||||
### Restricted access
|
||||
|
||||
Authentication is required to access the environments(endpoints) with this access policy.
|
||||
Authentication is required to access the endpoints with this access policy.
|
||||
Extra-checks might be added to ensure access to the resource is granted. Returned data might also be filtered.
|
||||
|
||||
### Administrator access
|
||||
|
||||
Authentication as well as an administrator role are required to access the environments(endpoints) with this access policy.
|
||||
Authentication as well as an administrator role are required to access the endpoints with this access policy.
|
||||
|
||||
# Execute Docker requests
|
||||
|
||||
Portainer **DO NOT** expose specific environments(endpoints) to manage your Docker resources (create a container, remove a volume, etc...).
|
||||
Portainer **DO NOT** expose specific endpoints to manage your Docker resources (create a container, remove a volume, etc...).
|
||||
|
||||
Instead, it acts as a reverse-proxy to the Docker HTTP API. This means that you can execute Docker requests **via** the Portainer HTTP API.
|
||||
|
||||
To do so, you can use the `/endpoints/{id}/docker` Portainer API environment(endpoint) (which is not documented below due to Swagger limitations). This environment(endpoint) has a restricted access policy so you still need to be authenticated to be able to query this environment(endpoint). Any query on this environment(endpoint) will be proxied to the Docker API of the associated environment(endpoint) (requests and responses objects are the same as documented in the Docker API).
|
||||
To do so, you can use the `/endpoints/{id}/docker` Portainer API endpoint (which is not documented below due to Swagger limitations). This endpoint has a restricted access policy so you still need to be authenticated to be able to query this endpoint. Any query on this endpoint will be proxied to the Docker API of the associated endpoint (requests and responses objects are the same as documented in the Docker API).
|
||||
|
||||
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://documentation.portainer.io/api/api-examples/).
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
plog "github.com/portainer/portainer/api/bolt/log"
|
||||
)
|
||||
|
||||
var backupDefaults = struct {
|
||||
backupDir string
|
||||
commonDir string
|
||||
databaseFileName string
|
||||
}{
|
||||
"backups",
|
||||
"common",
|
||||
databaseFileName,
|
||||
}
|
||||
|
||||
var backupLog = plog.NewScopedLog("bolt, backup")
|
||||
|
||||
//
|
||||
// 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 {
|
||||
backupLog.Error("Error while creating common backup folder", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (store *Store) databasePath() string {
|
||||
return path.Join(store.path, databaseFileName)
|
||||
}
|
||||
|
||||
func (store *Store) commonBackupDir() string {
|
||||
return path.Join(store.path, backupDefaults.backupDir, backupDefaults.commonDir)
|
||||
}
|
||||
|
||||
func (store *Store) copyDBFile(from string, to string) error {
|
||||
backupLog.Info(fmt.Sprintf("Copying db file from %s to %s", from, to))
|
||||
err := store.fileService.Copy(from, to, true)
|
||||
if err != nil {
|
||||
backupLog.Error("Failed", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// BackupOptions provide a helper to inject backup options
|
||||
type BackupOptions struct {
|
||||
Version int
|
||||
BackupDir string
|
||||
BackupFileName string
|
||||
BackupPath string
|
||||
}
|
||||
|
||||
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
|
||||
if options == nil {
|
||||
options = &BackupOptions{}
|
||||
}
|
||||
if options.Version == 0 {
|
||||
options.Version, _ = store.version()
|
||||
}
|
||||
if options.BackupDir == "" {
|
||||
options.BackupDir = store.commonBackupDir()
|
||||
}
|
||||
if options.BackupFileName == "" {
|
||||
options.BackupFileName = fmt.Sprintf("%s.%s.%s", backupDefaults.databaseFileName, fmt.Sprintf("%03d", 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) {
|
||||
backupLog.Info("creating db backup")
|
||||
store.createBackupFolders()
|
||||
|
||||
options = store.setupOptions(options)
|
||||
|
||||
return options.BackupPath, store.copyDBFile(store.databasePath(), options.BackupPath)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
backupLog.Error(fmt.Sprintf("Backup file to restore does not exist %s", options.BackupPath), err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
backupLog.Error("Error while closing store before restore", err)
|
||||
return err
|
||||
}
|
||||
|
||||
backupLog.Info("Restoring db backup")
|
||||
err = store.copyDBFile(options.BackupPath, store.databasePath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.Open()
|
||||
}
|
||||
|
||||
// RemoveWithOptions removes backup database based on supplied options
|
||||
func (store *Store) RemoveWithOptions(options *BackupOptions) error {
|
||||
backupLog.Info("Removing db backup")
|
||||
|
||||
options = store.setupOptions(options)
|
||||
_, err := os.Stat(options.BackupPath)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
backupLog.Error(fmt.Sprintf("Backup file to remove does not exist %s", options.BackupPath), err)
|
||||
return err
|
||||
}
|
||||
|
||||
backupLog.Info(fmt.Sprintf("Removing db file at %s", options.BackupPath))
|
||||
err = os.Remove(options.BackupPath)
|
||||
if err != nil {
|
||||
backupLog.Error("Failed", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// isFileExist is helper function to check for file existence
|
||||
func isFileExist(path string) bool {
|
||||
matches, err := filepath.Glob(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(matches) > 0
|
||||
}
|
||||
|
||||
func TestCreateBackupFolders(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
backupPath := path.Join(store.path, backupDefaults.backupDir)
|
||||
|
||||
if isFileExist(backupPath) {
|
||||
t.Error("Expect backups folder to not exist")
|
||||
}
|
||||
|
||||
store.createBackupFolders()
|
||||
if !isFileExist(backupPath) {
|
||||
t.Error("Expect backups folder to exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreCreation(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
if store == nil {
|
||||
t.Error("Expect to create a store")
|
||||
}
|
||||
|
||||
if store.edition() != portainer.PortainerCE {
|
||||
t.Error("Expect to get CE Edition")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackup(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
t.Run("Backup should create default db backup", func(t *testing.T) {
|
||||
store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
store.BackupWithOptions(nil)
|
||||
|
||||
backupFileName := path.Join(store.path, "backups", "common", fmt.Sprintf("portainer.db.%03d.*", portainer.DBVersion))
|
||||
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(store.path, "backups", "common", beforePortainerVersionUpgradeBackup)
|
||||
if !isFileExist(backupFileName) {
|
||||
t.Errorf("Expect backup file to be created %s", backupFileName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveWithOptions(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
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()
|
||||
|
||||
err = store.RemoveWithOptions(options)
|
||||
if err != nil {
|
||||
t.Errorf("RemoveWithOptions should successfully remove file; err=%w", err)
|
||||
}
|
||||
|
||||
if isFileExist(f.Name()) {
|
||||
t.Errorf("RemoveWithOptions should successfully remove file; file=%s", f.Name())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails to removes file if non-existent", func(t *testing.T) {
|
||||
options := &BackupOptions{
|
||||
BackupDir: store.commonBackupDir(),
|
||||
BackupFileName: "test.txt",
|
||||
}
|
||||
|
||||
err := store.RemoveWithOptions(options)
|
||||
if err == nil {
|
||||
t.Error("RemoveWithOptions should fail for non-existent file")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package bolt
|
||||
package bolttest
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@@ -6,12 +6,13 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/bolt"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
)
|
||||
|
||||
var errTempDir = errors.New("can't create a temp dir")
|
||||
|
||||
func MustNewTestStore(init bool) (*Store, func()) {
|
||||
func MustNewTestStore(init bool) (*bolt.Store, func()) {
|
||||
store, teardown, err := NewTestStore(init)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errTempDir) {
|
||||
@@ -23,7 +24,7 @@ func MustNewTestStore(init bool) (*Store, func()) {
|
||||
return store, teardown
|
||||
}
|
||||
|
||||
func NewTestStore(init bool) (*Store, func(), error) {
|
||||
func NewTestStore(init bool) (*bolt.Store, func(), error) {
|
||||
// Creates unique temp directory in a concurrency friendly manner.
|
||||
dataStorePath, err := ioutil.TempDir("", "boltdb")
|
||||
if err != nil {
|
||||
@@ -35,7 +36,11 @@ func NewTestStore(init bool) (*Store, func(), error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
store := NewStore(dataStorePath, fileService)
|
||||
store, err := bolt.NewStore(dataStorePath, fileService)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = store.Open()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -55,7 +60,7 @@ func NewTestStore(init bool) (*Store, func(), error) {
|
||||
return store, teardown, nil
|
||||
}
|
||||
|
||||
func teardown(store *Store, dataStorePath string) {
|
||||
func teardown(store *bolt.Store, dataStorePath string) {
|
||||
err := store.Close()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
@@ -2,6 +2,7 @@ package bolt
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/bolt/extension"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
"github.com/portainer/portainer/api/bolt/migrator"
|
||||
"github.com/portainer/portainer/api/bolt/registry"
|
||||
"github.com/portainer/portainer/api/bolt/resourcecontrol"
|
||||
"github.com/portainer/portainer/api/bolt/role"
|
||||
@@ -34,6 +36,7 @@ import (
|
||||
"github.com/portainer/portainer/api/bolt/user"
|
||||
"github.com/portainer/portainer/api/bolt/version"
|
||||
"github.com/portainer/portainer/api/bolt/webhook"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -73,14 +76,6 @@ type Store struct {
|
||||
WebhookService *webhook.Service
|
||||
}
|
||||
|
||||
func (store *Store) version() (int, error) {
|
||||
version, err := store.VersionService.DBVersion()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
version = 0
|
||||
}
|
||||
return version, err
|
||||
}
|
||||
|
||||
func (store *Store) edition() portainer.SoftwareEdition {
|
||||
edition, err := store.VersionService.Edition()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
@@ -90,13 +85,25 @@ func (store *Store) edition() portainer.SoftwareEdition {
|
||||
}
|
||||
|
||||
// NewStore initializes a new Store and the associated services
|
||||
func NewStore(storePath string, fileService portainer.FileService) *Store {
|
||||
return &Store{
|
||||
func NewStore(storePath string, fileService portainer.FileService) (*Store, error) {
|
||||
store := &Store{
|
||||
path: storePath,
|
||||
fileService: fileService,
|
||||
isNew: true,
|
||||
connection: &internal.DbConnection{},
|
||||
}
|
||||
|
||||
databasePath := path.Join(storePath, databaseFileName)
|
||||
databaseFileExists, err := fileService.FileExists(databasePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if databaseFileExists {
|
||||
store.isNew = false
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// Open opens and initializes the BoltDB database.
|
||||
@@ -108,17 +115,7 @@ func (store *Store) Open() error {
|
||||
}
|
||||
store.connection.DB = db
|
||||
|
||||
err = store.initServices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we have DBVersion in the database then ensure we flag this as NOT a new store
|
||||
if _, err := store.VersionService.DBVersion(); err == nil {
|
||||
store.isNew = false
|
||||
}
|
||||
|
||||
return nil
|
||||
return store.initServices()
|
||||
}
|
||||
|
||||
// Close closes the BoltDB database.
|
||||
@@ -136,6 +133,64 @@ func (store *Store) IsNew() bool {
|
||||
return store.isNew
|
||||
}
|
||||
|
||||
// CheckCurrentEdition checks if current edition is community edition
|
||||
func (store *Store) CheckCurrentEdition() error {
|
||||
if store.edition() != portainer.PortainerCE {
|
||||
return errors.ErrWrongDBEdition
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MigrateData automatically migrate the data based on the DBVersion.
|
||||
// This process is only triggered on an existing database, not if the database was just created.
|
||||
// if force is true, then migrate regardless.
|
||||
func (store *Store) MigrateData(force bool) error {
|
||||
if store.isNew && !force {
|
||||
return store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
}
|
||||
|
||||
version, err := store.VersionService.DBVersion()
|
||||
if err == errors.ErrObjectNotFound {
|
||||
version = 0
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if version < portainer.DBVersion {
|
||||
migratorParams := &migrator.Parameters{
|
||||
DB: store.connection.DB,
|
||||
DatabaseVersion: 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,
|
||||
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),
|
||||
}
|
||||
migrator := migrator.NewMigrator(migratorParams)
|
||||
|
||||
log.Printf("Migrating database from version %v to %v.\n", version, portainer.DBVersion)
|
||||
err = migrator.Migrate()
|
||||
if err != nil {
|
||||
log.Printf("An error occurred during database migration: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -144,11 +199,3 @@ func (store *Store) BackupTo(w io.Writer) error {
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// CheckCurrentEdition checks if current edition is community edition
|
||||
func (store *Store) CheckCurrentEdition() error {
|
||||
if store.edition() != portainer.PortainerCE {
|
||||
return errors.ErrWrongDBEdition
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
BucketName = "endpoints"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Endpoint returns an environment(endpoint) by ID.
|
||||
// Endpoint returns an endpoint by ID.
|
||||
func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
|
||||
var endpoint portainer.Endpoint
|
||||
identifier := internal.Itob(int(ID))
|
||||
@@ -41,19 +41,19 @@ func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
|
||||
return &endpoint, nil
|
||||
}
|
||||
|
||||
// UpdateEndpoint updates an environment(endpoint).
|
||||
// UpdateEndpoint updates an endpoint.
|
||||
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, endpoint)
|
||||
}
|
||||
|
||||
// DeleteEndpoint deletes an environment(endpoint).
|
||||
// DeleteEndpoint deletes an endpoint.
|
||||
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// Endpoints return an array containing all the environments(endpoints).
|
||||
// Endpoints return an array containing all the endpoints.
|
||||
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
||||
var endpoints = make([]portainer.Endpoint, 0)
|
||||
|
||||
@@ -76,12 +76,12 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
||||
return endpoints, err
|
||||
}
|
||||
|
||||
// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
|
||||
// CreateEndpoint assign an ID to a new endpoint and saves it.
|
||||
func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
// We manually manage sequences for environments(endpoints)
|
||||
// We manually manage sequences for endpoints
|
||||
err := bucket.SetSequence(uint64(endpoint.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -96,12 +96,12 @@ func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetNextIdentifier returns the next identifier for an environment(endpoint).
|
||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||
func (service *Service) GetNextIdentifier() int {
|
||||
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||
}
|
||||
|
||||
// Synchronize creates, updates and deletes environments(endpoints) inside a single transaction.
|
||||
// Synchronize creates, updates and deletes endpoints inside a single transaction.
|
||||
func (service *Service) Synchronize(toCreate, toUpdate, toDelete []*portainer.Endpoint) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
BucketName = "endpoint_groups"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EndpointGroup returns an environment(endpoint) group by ID.
|
||||
// EndpointGroup returns an endpoint group by ID.
|
||||
func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) {
|
||||
var endpointGroup portainer.EndpointGroup
|
||||
identifier := internal.Itob(int(ID))
|
||||
@@ -42,19 +42,19 @@ func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.
|
||||
return &endpointGroup, nil
|
||||
}
|
||||
|
||||
// UpdateEndpointGroup updates an environment(endpoint) group.
|
||||
// UpdateEndpointGroup updates an endpoint group.
|
||||
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, endpointGroup)
|
||||
}
|
||||
|
||||
// DeleteEndpointGroup deletes an environment(endpoint) group.
|
||||
// DeleteEndpointGroup deletes an endpoint group.
|
||||
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
}
|
||||
|
||||
// EndpointGroups return an array containing all the environment(endpoint) groups.
|
||||
// EndpointGroups return an array containing all the endpoint groups.
|
||||
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||
var endpointGroups = make([]portainer.EndpointGroup, 0)
|
||||
|
||||
@@ -77,7 +77,7 @@ func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||
return endpointGroups, err
|
||||
}
|
||||
|
||||
// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it.
|
||||
// CreateEndpointGroup assign an ID to a new endpoint group and saves it.
|
||||
func (service *Service) CreateEndpointGroup(endpointGroup *portainer.EndpointGroup) error {
|
||||
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
BucketName = "endpoint_relations"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) relation data.
|
||||
// Service represents a service for managing endpoint relation data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EndpointRelation returns a Environment(Endpoint) relation object by EndpointID
|
||||
// EndpointRelation returns a Endpoint relation object by EndpointID
|
||||
func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*portainer.EndpointRelation, error) {
|
||||
var endpointRelation portainer.EndpointRelation
|
||||
identifier := internal.Itob(int(endpointID))
|
||||
@@ -55,13 +55,13 @@ func (service *Service) CreateEndpointRelation(endpointRelation *portainer.Endpo
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateEndpointRelation updates an Environment(Endpoint) relation object
|
||||
// UpdateEndpointRelation updates an Endpoint relation object
|
||||
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
|
||||
identifier := internal.Itob(int(EndpointID))
|
||||
return internal.UpdateObject(service.connection, BucketName, identifier, endpointRelation)
|
||||
}
|
||||
|
||||
// DeleteEndpointRelation deletes an Environment(Endpoint) relation object
|
||||
// DeleteEndpointRelation deletes an Endpoint relation object
|
||||
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
|
||||
identifier := internal.Itob(int(EndpointID))
|
||||
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
BucketName = "extension"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
BucketName = "helm_user_repository"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ func (store *Store) Init() error {
|
||||
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
|
||||
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
||||
KubeconfigExpiry: portainer.DefaultKubeconfigExpiry,
|
||||
KubectlShellImage: portainer.DefaultKubectlShellImage,
|
||||
}
|
||||
|
||||
err = store.SettingsService.UpdateSettings(defaultSettings)
|
||||
|
||||
@@ -17,7 +17,7 @@ func UnmarshalObject(data []byte, object interface{}) error {
|
||||
}
|
||||
|
||||
// UnmarshalObjectWithJsoniter decodes an object from binary data
|
||||
// using the jsoniter library. It is mainly used to accelerate environment(endpoint)
|
||||
// using the jsoniter library. It is mainly used to accelerate endpoint
|
||||
// decoding at the moment.
|
||||
func UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
|
||||
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
|
||||
werrors "github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
plog "github.com/portainer/portainer/api/bolt/log"
|
||||
"github.com/portainer/portainer/api/bolt/migrator"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
const beforePortainerVersionUpgradeBackup = "portainer.db.bak"
|
||||
|
||||
var migrateLog = plog.NewScopedLog("bolt, migrate")
|
||||
|
||||
// FailSafeMigrate backup and restore DB if migration fail
|
||||
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
migrateLog.Info(fmt.Sprintf("Error during migration, recovering [%v]", err))
|
||||
store.Rollback(true)
|
||||
}
|
||||
}()
|
||||
return migrator.Migrate()
|
||||
}
|
||||
|
||||
// MigrateData automatically migrate the data based on the DBVersion.
|
||||
// This process is only triggered on an existing database, not if the database was just created.
|
||||
// if force is true, then migrate regardless.
|
||||
func (store *Store) MigrateData(force bool) error {
|
||||
if store.isNew && !force {
|
||||
return store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
}
|
||||
|
||||
migrator, err := store.newMigrator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// backup db file before upgrading DB to support rollback
|
||||
isUpdating, err := store.VersionService.IsUpdating()
|
||||
if err != nil && err != errors.ErrObjectNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isUpdating && migrator.Version() != portainer.DBVersion {
|
||||
err = store.backupVersion(migrator)
|
||||
if err != nil {
|
||||
return werrors.Wrapf(err, "failed to backup database")
|
||||
}
|
||||
}
|
||||
|
||||
if migrator.Version() < portainer.DBVersion {
|
||||
migrateLog.Info(fmt.Sprintf("Migrating database from version %v to %v.\n", migrator.Version(), portainer.DBVersion))
|
||||
err = store.FailSafeMigrate(migrator)
|
||||
if err != nil {
|
||||
migrateLog.Error("An error occurred during database migration", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) newMigrator() (*migrator.Migrator, error) {
|
||||
version, err := store.version()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
migratorParams := &migrator.Parameters{
|
||||
DB: store.connection.DB,
|
||||
DatabaseVersion: 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,
|
||||
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),
|
||||
}
|
||||
return migrator.NewMigrator(migratorParams), nil
|
||||
}
|
||||
|
||||
// getBackupRestoreOptions returns options to store db at common backup dir location; used by:
|
||||
// - db backup prior to version upgrade
|
||||
// - db rollback
|
||||
func getBackupRestoreOptions(store *Store) *BackupOptions {
|
||||
return &BackupOptions{
|
||||
BackupDir: store.commonBackupDir(),
|
||||
BackupFileName: beforePortainerVersionUpgradeBackup,
|
||||
}
|
||||
}
|
||||
|
||||
// backupVersion will backup the database or panic if any errors occur
|
||||
func (store *Store) backupVersion(migrator *migrator.Migrator) error {
|
||||
migrateLog.Info("Backing up database prior to version upgrade...")
|
||||
|
||||
options := getBackupRestoreOptions(store)
|
||||
|
||||
_, err := store.BackupWithOptions(options)
|
||||
if err != nil {
|
||||
migrateLog.Error("An error occurred during database backup", err)
|
||||
removalErr := store.RemoveWithOptions(options)
|
||||
if removalErr != nil {
|
||||
migrateLog.Error("An error occurred during store removal prior to backup", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rollback to a pre-upgrade backup copy/snapshot of portainer.db
|
||||
func (store *Store) Rollback(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
|
||||
}
|
||||
}
|
||||
|
||||
options := getBackupRestoreOptions(store)
|
||||
|
||||
err := store.RestoreWithOptions(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.Close()
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// testVersion is a helper which tests current store version against wanted version
|
||||
func testVersion(store *Store, versionWant int, t *testing.T) {
|
||||
if v, _ := store.version(); v != versionWant {
|
||||
t.Errorf("Expect store version to be %d but was %d", versionWant, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateData(t *testing.T) {
|
||||
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
if !store.IsNew() {
|
||||
t.Error("Expect a new DB")
|
||||
}
|
||||
|
||||
store.MigrateData(false)
|
||||
|
||||
testVersion(store, portainer.DBVersion, t)
|
||||
store.Close()
|
||||
|
||||
store.Open()
|
||||
if store.IsNew() {
|
||||
t.Error("Expect store to NOT be new DB")
|
||||
}
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
version int
|
||||
expectedVersion int
|
||||
}{
|
||||
{version: 2, expectedVersion: portainer.DBVersion},
|
||||
{version: 21, expectedVersion: portainer.DBVersion},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
store, teardown := MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
// Setup data
|
||||
store.VersionService.StoreDBVersion(tc.version)
|
||||
|
||||
// Required roles by migrations 22.2
|
||||
store.RoleService.CreateRole(&portainer.Role{ID: 1})
|
||||
store.RoleService.CreateRole(&portainer.Role{ID: 2})
|
||||
store.RoleService.CreateRole(&portainer.Role{ID: 3})
|
||||
store.RoleService.CreateRole(&portainer.Role{ID: 4})
|
||||
|
||||
t.Run(fmt.Sprintf("MigrateData for version %d", tc.version), func(t *testing.T) {
|
||||
store.MigrateData(true)
|
||||
testVersion(store, tc.expectedVersion, t)
|
||||
})
|
||||
|
||||
t.Run(fmt.Sprintf("Restoring DB after migrateData for version %d", tc.version), func(t *testing.T) {
|
||||
store.Rollback(true)
|
||||
store.Open()
|
||||
testVersion(store, tc.version, t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
version := 2
|
||||
store.VersionService.StoreDBVersion(version)
|
||||
|
||||
store.MigrateData(true)
|
||||
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
|
||||
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
store.VersionService.StoreDBVersion(0)
|
||||
|
||||
store.MigrateData(true)
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store))
|
||||
|
||||
if !isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should exist; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
store.VersionService.StoreIsUpdating(true)
|
||||
|
||||
store.MigrateData(true)
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store))
|
||||
|
||||
if isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
store.MigrateData(true)
|
||||
|
||||
options := store.setupOptions(getBackupRestoreOptions(store))
|
||||
|
||||
if isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_getBackupRestoreOptions(t *testing.T) {
|
||||
store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
|
||||
options := getBackupRestoreOptions(store)
|
||||
|
||||
wantDir := store.commonBackupDir()
|
||||
if !strings.HasSuffix(options.BackupDir, wantDir) {
|
||||
log.Fatalf("incorrect backup dir; got=%s, want=%s", options.BackupDir, wantDir)
|
||||
}
|
||||
|
||||
wantFilename := "portainer.db.bak"
|
||||
if options.BackupFileName != wantFilename {
|
||||
log.Fatalf("incorrect backup file; got=%s, want=%s", options.BackupFileName, wantFilename)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollback(t *testing.T) {
|
||||
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
|
||||
version := 21
|
||||
store, teardown := MustNewTestStore(false)
|
||||
defer teardown()
|
||||
store.VersionService.StoreDBVersion(version)
|
||||
|
||||
_, err := store.BackupWithOptions(getBackupRestoreOptions(store))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Change the current edition
|
||||
err = store.VersionService.StoreDBVersion(version + 10)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = store.Rollback(true)
|
||||
if err != nil {
|
||||
t.Logf("Rollback failed: %s", err)
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
store.Open()
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
werrors "github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func migrationError(err error, context string) error {
|
||||
return werrors.Wrap(err, "failed in "+context)
|
||||
}
|
||||
|
||||
// Migrate checks the database version and migrate the existing data to the most recent data model.
|
||||
func (m *Migrator) Migrate() error {
|
||||
// set DB to updating status
|
||||
err := m.versionService.StoreIsUpdating(true)
|
||||
if err != nil {
|
||||
return migrationError(err, "StoreIsUpdating")
|
||||
}
|
||||
|
||||
// Portainer < 1.12
|
||||
if m.currentDBVersion < 1 {
|
||||
err := m.updateAdminUserToDBVersion1()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateAdminUserToDBVersion1")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.12.x
|
||||
if m.currentDBVersion < 2 {
|
||||
err := m.updateResourceControlsToDBVersion2()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateResourceControlsToDBVersion2")
|
||||
}
|
||||
err = m.updateEndpointsToDBVersion2()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToDBVersion2")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.13.x
|
||||
if m.currentDBVersion < 3 {
|
||||
err := m.updateSettingsToDBVersion3()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDBVersion3")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.14.0
|
||||
if m.currentDBVersion < 4 {
|
||||
err := m.updateEndpointsToDBVersion4()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToDBVersion4")
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1235
|
||||
if m.currentDBVersion < 5 {
|
||||
err := m.updateSettingsToVersion5()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToVersion5")
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1236
|
||||
if m.currentDBVersion < 6 {
|
||||
err := m.updateSettingsToVersion6()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToVersion6")
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1449
|
||||
if m.currentDBVersion < 7 {
|
||||
err := m.updateSettingsToVersion7()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToVersion7")
|
||||
}
|
||||
}
|
||||
|
||||
if m.currentDBVersion < 8 {
|
||||
err := m.updateEndpointsToVersion8()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToVersion8")
|
||||
}
|
||||
}
|
||||
|
||||
// https: //github.com/portainer/portainer/issues/1396
|
||||
if m.currentDBVersion < 9 {
|
||||
err := m.updateEndpointsToVersion9()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToVersion9")
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/461
|
||||
if m.currentDBVersion < 10 {
|
||||
err := m.updateEndpointsToVersion10()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToVersion10")
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1906
|
||||
if m.currentDBVersion < 11 {
|
||||
err := m.updateEndpointsToVersion11()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToVersion11")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.18.0
|
||||
if m.currentDBVersion < 12 {
|
||||
err := m.updateEndpointsToVersion12()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToVersion12")
|
||||
}
|
||||
|
||||
err = m.updateEndpointGroupsToVersion12()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointGroupsToVersion12")
|
||||
}
|
||||
|
||||
err = m.updateStacksToVersion12()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateStacksToVersion12")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.19.0
|
||||
if m.currentDBVersion < 13 {
|
||||
err := m.updateSettingsToVersion13()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToVersion13")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.19.2
|
||||
if m.currentDBVersion < 14 {
|
||||
err := m.updateResourceControlsToDBVersion14()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateResourceControlsToDBVersion14")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.20.0
|
||||
if m.currentDBVersion < 15 {
|
||||
err := m.updateSettingsToDBVersion15()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDBVersion15")
|
||||
}
|
||||
|
||||
err = m.updateTemplatesToVersion15()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateTemplatesToVersion15")
|
||||
}
|
||||
}
|
||||
|
||||
if m.currentDBVersion < 16 {
|
||||
err := m.updateSettingsToDBVersion16()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDBVersion16")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.20.1
|
||||
if m.currentDBVersion < 17 {
|
||||
err := m.updateExtensionsToDBVersion17()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateExtensionsToDBVersion17")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.21.0
|
||||
if m.currentDBVersion < 18 {
|
||||
err := m.updateUsersToDBVersion18()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateUsersToDBVersion18")
|
||||
}
|
||||
|
||||
err = m.updateEndpointsToDBVersion18()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsToDBVersion18")
|
||||
}
|
||||
|
||||
err = m.updateEndpointGroupsToDBVersion18()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointGroupsToDBVersion18")
|
||||
}
|
||||
|
||||
err = m.updateRegistriesToDBVersion18()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateRegistriesToDBVersion18")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.22.0
|
||||
if m.currentDBVersion < 19 {
|
||||
err := m.updateSettingsToDBVersion19()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDBVersion19")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.22.1
|
||||
if m.currentDBVersion < 20 {
|
||||
err := m.updateUsersToDBVersion20()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateUsersToDBVersion20")
|
||||
}
|
||||
|
||||
err = m.updateSettingsToDBVersion20()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDBVersion20")
|
||||
}
|
||||
|
||||
err = m.updateSchedulesToDBVersion20()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSchedulesToDBVersion20")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.23.0
|
||||
// DBVersion 21 is missing as it was shipped as via hotfix 1.22.2
|
||||
if m.currentDBVersion < 22 {
|
||||
err := m.updateResourceControlsToDBVersion22()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateResourceControlsToDBVersion22")
|
||||
}
|
||||
|
||||
err = m.updateUsersAndRolesToDBVersion22()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateUsersAndRolesToDBVersion22")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.0
|
||||
if m.currentDBVersion < 23 {
|
||||
err := m.updateTagsToDBVersion23()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateTagsToDBVersion23")
|
||||
}
|
||||
|
||||
err = m.updateEndpointsAndEndpointGroupsToDBVersion23()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointsAndEndpointGroupsToDBVersion23")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.1
|
||||
if m.currentDBVersion < 24 {
|
||||
err := m.updateSettingsToDB24()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDB24")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.0.0
|
||||
if m.currentDBVersion < 25 {
|
||||
err := m.updateSettingsToDB25()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateSettingsToDB25")
|
||||
}
|
||||
|
||||
err = m.updateStacksToDB24()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateStacksToDB24")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.1.0
|
||||
if m.currentDBVersion < 26 {
|
||||
err := m.updateEndpointSettingsToDB25()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateEndpointSettingsToDB25")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.2.0
|
||||
if m.currentDBVersion < 27 {
|
||||
err := m.updateStackResourceControlToDB27()
|
||||
if err != nil {
|
||||
return migrationError(err, "updateStackResourceControlToDB27")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.6.0
|
||||
if m.currentDBVersion < 30 {
|
||||
err := m.migrateDBVersionToDB30()
|
||||
if err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB30")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.9.0
|
||||
if m.currentDBVersion < 32 {
|
||||
err := m.migrateDBVersionToDB32()
|
||||
if err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB32")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.9.1
|
||||
if m.currentDBVersion < 33 {
|
||||
err := m.migrateDBVersionToDB33()
|
||||
if err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB33")
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.10
|
||||
if m.currentDBVersion < 34 {
|
||||
if err := m.migrateDBVersionToDB34(); err != nil {
|
||||
return migrationError(err, "migrateDBVersionToDB34")
|
||||
}
|
||||
}
|
||||
|
||||
err = m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
if err != nil {
|
||||
return migrationError(err, "StoreDBVersion")
|
||||
}
|
||||
migrateLog.Info(fmt.Sprintf("Updated DB version to %d", portainer.DBVersion))
|
||||
|
||||
// reset DB updating status
|
||||
return m.versionService.StoreIsUpdating(false)
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package migrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
@@ -168,7 +167,6 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
|
||||
|
||||
totalSnapshots := len(endpoint.Snapshots)
|
||||
if totalSnapshots == 0 {
|
||||
log.Println("[DEBUG] [volume migration] [message: no snapshot found]")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -176,13 +174,11 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
|
||||
|
||||
endpointDockerID, err := snapshotutils.FetchDockerID(snapshot)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] [bolt,migrator,v31] [message: failed fetching environment docker id] [err: %s]", err)
|
||||
continue
|
||||
return fmt.Errorf("failed fetching environment docker id: %w", err)
|
||||
}
|
||||
|
||||
if volumesData, done := snapshot.SnapshotRaw.Volumes.(map[string]interface{}); done {
|
||||
if volumesData["Volumes"] == nil {
|
||||
log.Println("[DEBUG] [volume migration] [message: no volume data found]")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -203,7 +199,7 @@ func (m *Migrator) updateVolumeResourceControlToDB32() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed deleting resource control %d: %w", resourceControl.ID, err)
|
||||
}
|
||||
log.Printf("[DEBUG] [volume migration] [message: legacy resource control(%s) has been deleted]", resourceControl.ResourceID)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,11 +210,7 @@ func findResourcesToUpdateForDB32(dockerID string, volumesData map[string]interf
|
||||
volumes := volumesData["Volumes"].([]interface{})
|
||||
for _, volumeMeta := range volumes {
|
||||
volume := volumeMeta.(map[string]interface{})
|
||||
volumeName, nameExist := volume["Name"].(string)
|
||||
if !nameExist {
|
||||
continue
|
||||
}
|
||||
|
||||
volumeName := volume["Name"].(string)
|
||||
oldResourceID := fmt.Sprintf("%s%s", volumeName, volume["CreatedAt"].(string))
|
||||
resourceControl, ok := volumeResourceControls[oldResourceID]
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB33() error {
|
||||
if err := m.migrateSettingsToDB33(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) migrateSettingsToDB33() error {
|
||||
settings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.KubectlShellImage = portainer.DefaultKubectlShellImage
|
||||
return m.settingsService.UpdateSettings(settings)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB34() error {
|
||||
func (m *Migrator) migrateDBVersionTo33() error {
|
||||
err := migrateStackEntryPoint(m.stackService)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMigrateStackEntryPoint(t *testing.T) {
|
||||
dbConn, err := bolt.Open(path.Join(t.TempDir(), "portainer-ee-mig-34.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
dbConn, err := bolt.Open(path.Join(t.TempDir(), "portainer-ee-mig-33.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
assert.NoError(t, err, "failed to init testing DB connection")
|
||||
defer dbConn.Close()
|
||||
|
||||
|
||||
@@ -27,9 +27,8 @@ var migrateLog = plog.NewScopedLog("bolt, migrate")
|
||||
type (
|
||||
// Migrator defines a service to migrate data after a Portainer version update.
|
||||
Migrator struct {
|
||||
db *bolt.DB
|
||||
currentDBVersion int
|
||||
|
||||
currentDBVersion int
|
||||
db *bolt.DB
|
||||
endpointGroupService *endpointgroup.Service
|
||||
endpointService *endpoint.Service
|
||||
endpointRelationService *endpointrelation.Service
|
||||
@@ -98,7 +97,295 @@ func NewMigrator(parameters *Parameters) *Migrator {
|
||||
}
|
||||
}
|
||||
|
||||
// Version exposes version of database
|
||||
func (migrator *Migrator) Version() int {
|
||||
return migrator.currentDBVersion
|
||||
// Migrate checks the database version and migrate the existing data to the most recent data model.
|
||||
func (m *Migrator) Migrate() error {
|
||||
// Portainer < 1.12
|
||||
if m.currentDBVersion < 1 {
|
||||
err := m.updateAdminUserToDBVersion1()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.12.x
|
||||
if m.currentDBVersion < 2 {
|
||||
err := m.updateResourceControlsToDBVersion2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.updateEndpointsToDBVersion2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.13.x
|
||||
if m.currentDBVersion < 3 {
|
||||
err := m.updateSettingsToDBVersion3()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.14.0
|
||||
if m.currentDBVersion < 4 {
|
||||
err := m.updateEndpointsToDBVersion4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1235
|
||||
if m.currentDBVersion < 5 {
|
||||
err := m.updateSettingsToVersion5()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1236
|
||||
if m.currentDBVersion < 6 {
|
||||
err := m.updateSettingsToVersion6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1449
|
||||
if m.currentDBVersion < 7 {
|
||||
err := m.updateSettingsToVersion7()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m.currentDBVersion < 8 {
|
||||
err := m.updateEndpointsToVersion8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https: //github.com/portainer/portainer/issues/1396
|
||||
if m.currentDBVersion < 9 {
|
||||
err := m.updateEndpointsToVersion9()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/461
|
||||
if m.currentDBVersion < 10 {
|
||||
err := m.updateEndpointsToVersion10()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/portainer/portainer/issues/1906
|
||||
if m.currentDBVersion < 11 {
|
||||
err := m.updateEndpointsToVersion11()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.18.0
|
||||
if m.currentDBVersion < 12 {
|
||||
err := m.updateEndpointsToVersion12()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateEndpointGroupsToVersion12()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateStacksToVersion12()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.19.0
|
||||
if m.currentDBVersion < 13 {
|
||||
err := m.updateSettingsToVersion13()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.19.2
|
||||
if m.currentDBVersion < 14 {
|
||||
err := m.updateResourceControlsToDBVersion14()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.20.0
|
||||
if m.currentDBVersion < 15 {
|
||||
err := m.updateSettingsToDBVersion15()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateTemplatesToVersion15()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m.currentDBVersion < 16 {
|
||||
err := m.updateSettingsToDBVersion16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.20.1
|
||||
if m.currentDBVersion < 17 {
|
||||
err := m.updateExtensionsToDBVersion17()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.21.0
|
||||
if m.currentDBVersion < 18 {
|
||||
err := m.updateUsersToDBVersion18()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateEndpointsToDBVersion18()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateEndpointGroupsToDBVersion18()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateRegistriesToDBVersion18()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.22.0
|
||||
if m.currentDBVersion < 19 {
|
||||
err := m.updateSettingsToDBVersion19()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.22.1
|
||||
if m.currentDBVersion < 20 {
|
||||
err := m.updateUsersToDBVersion20()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateSettingsToDBVersion20()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateSchedulesToDBVersion20()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.23.0
|
||||
// DBVersion 21 is missing as it was shipped as via hotfix 1.22.2
|
||||
if m.currentDBVersion < 22 {
|
||||
err := m.updateResourceControlsToDBVersion22()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateUsersAndRolesToDBVersion22()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.0
|
||||
if m.currentDBVersion < 23 {
|
||||
err := m.updateTagsToDBVersion23()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateEndpointsAndEndpointGroupsToDBVersion23()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.1
|
||||
if m.currentDBVersion < 24 {
|
||||
err := m.updateSettingsToDB24()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.0.0
|
||||
if m.currentDBVersion < 25 {
|
||||
err := m.updateSettingsToDB25()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateStacksToDB24()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.1.0
|
||||
if m.currentDBVersion < 26 {
|
||||
err := m.updateEndpointSettingsToDB25()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.2.0
|
||||
if m.currentDBVersion < 27 {
|
||||
err := m.updateStackResourceControlToDB27()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.6.0
|
||||
if m.currentDBVersion < 30 {
|
||||
err := m.migrateDBVersionToDB30()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 2.9.0
|
||||
if m.currentDBVersion < 32 {
|
||||
err := m.migrateDBVersionToDB32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m.currentDBVersion < 33 {
|
||||
if err := m.migrateDBVersionTo33(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
BucketName = "registries"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
BucketName = "resource_control"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
BucketName = "roles"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ func (store *Store) EdgeStack() portainer.EdgeStackService {
|
||||
return store.EdgeStackService
|
||||
}
|
||||
|
||||
// Environment(Endpoint) gives access to the Environment(Endpoint) data management layer
|
||||
// Endpoint gives access to the Endpoint data management layer
|
||||
func (store *Store) Endpoint() portainer.EndpointService {
|
||||
return store.EndpointService
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
settingsKey = "SETTINGS"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const (
|
||||
BucketName = "stacks"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -4,12 +4,18 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt"
|
||||
|
||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
|
||||
"github.com/portainer/portainer/api/bolt/bolttest"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
)
|
||||
|
||||
func newGuidString(t *testing.T) string {
|
||||
@@ -29,7 +35,7 @@ func TestService_StackByWebhookID(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
|
||||
}
|
||||
store, teardown := bolt.MustNewTestStore(true)
|
||||
store, teardown := bolttest.MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
b := stackBuilder{t: t, store: store}
|
||||
@@ -87,7 +93,7 @@ func Test_RefreshableStacks(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode. Normally takes ~1s to run.")
|
||||
}
|
||||
store, teardown := bolt.MustNewTestStore(true)
|
||||
store, teardown := bolttest.MustNewTestStore(true)
|
||||
defer teardown()
|
||||
|
||||
staticStack := portainer.Stack{ID: 1}
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
BucketName = "tags"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const (
|
||||
BucketName = "teams"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
BucketName = "team_membership"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
infoKey = "INFO"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const (
|
||||
BucketName = "users"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
// Service represents a service for managing endpoint data.
|
||||
type Service struct {
|
||||
connection *internal.DbConnection
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ const (
|
||||
versionKey = "DB_VERSION"
|
||||
instanceKey = "INSTANCE_ID"
|
||||
editionKey = "EDITION"
|
||||
updatingKey = "DB_UPDATING"
|
||||
)
|
||||
|
||||
// Service represents a service to manage stored versions.
|
||||
@@ -84,21 +83,6 @@ func (service *Service) StoreDBVersion(version int) error {
|
||||
})
|
||||
}
|
||||
|
||||
// IsUpdating retrieves the database updating status.
|
||||
func (service *Service) IsUpdating() (bool, error) {
|
||||
isUpdating, err := service.getKey(updatingKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return strconv.ParseBool(string(isUpdating))
|
||||
}
|
||||
|
||||
// StoreIsUpdating store the database updating status.
|
||||
func (service *Service) StoreIsUpdating(isUpdating bool) error {
|
||||
return service.setKey(updatingKey, strconv.FormatBool(isUpdating))
|
||||
}
|
||||
|
||||
// InstanceID retrieves the stored instance ID.
|
||||
func (service *Service) InstanceID() (string, error) {
|
||||
var data []byte
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an environment(endpoint).
|
||||
// AddEdgeJob register an EdgeJob inside the tunnel details associated to an endpoint.
|
||||
func (service *Service) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *portainer.EdgeJob) {
|
||||
tunnel := service.GetTunnelDetails(endpointID)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -43,55 +42,6 @@ func NewService(dataStore portainer.DataStore, shutdownCtx context.Context) *Ser
|
||||
}
|
||||
}
|
||||
|
||||
// pingAgent ping the given agent so that the agent can keep the tunnel alive
|
||||
func (service *Service) pingAgent(endpointID portainer.EndpointID) error{
|
||||
tunnel := service.GetTunnelDetails(endpointID)
|
||||
requestURL := fmt.Sprintf("http://127.0.0.1:%d/ping", tunnel.Port)
|
||||
req, err := http.NewRequest(http.MethodHead, requestURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
_, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeepTunnelAlive keeps the tunnel of the given environment for maxAlive duration, or until ctx is done
|
||||
func (service *Service) KeepTunnelAlive(endpointID portainer.EndpointID, ctx context.Context, maxAlive time.Duration) {
|
||||
go func() {
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: start for %.0f minutes]\n", endpointID, maxAlive.Minutes())
|
||||
maxAliveTicker := time.NewTicker(maxAlive)
|
||||
defer maxAliveTicker.Stop()
|
||||
pingTicker := time.NewTicker(tunnelCleanupInterval)
|
||||
defer pingTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-pingTicker.C:
|
||||
service.SetTunnelStatusToActive(endpointID)
|
||||
err := service.pingAgent(endpointID)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [warning: ping agent err=%s]\n", endpointID, err)
|
||||
}
|
||||
case <-maxAliveTicker.C:
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as %.0f minutes timeout]\n", endpointID, maxAlive.Minutes())
|
||||
return
|
||||
case <-ctx.Done():
|
||||
err := ctx.Err()
|
||||
log.Printf("[DEBUG] [chisel,KeepTunnelAlive] [endpoint_id: %d] [message: stop as err=%s]\n", endpointID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StartTunnelServer starts a tunnel server on the specified addr and port.
|
||||
// It uses a seed to generate a new private/public key pair. If the seed cannot
|
||||
// be found inside the database, it will generate a new one randomly and persist it.
|
||||
|
||||
@@ -38,7 +38,7 @@ func randomInt(min, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
// GetTunnelDetails returns information about the tunnel associated to an environment(endpoint).
|
||||
// GetTunnelDetails returns information about the tunnel associated to an endpoint.
|
||||
func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
|
||||
key := strconv.Itoa(int(endpointID))
|
||||
|
||||
@@ -56,33 +56,7 @@ func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) *porta
|
||||
}
|
||||
}
|
||||
|
||||
// GetActiveTunnel retrieves an active tunnel which allows communicating with edge agent
|
||||
func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (*portainer.TunnelDetails, error) {
|
||||
tunnel := service.GetTunnelDetails(endpoint.ID)
|
||||
if tunnel.Status == portainer.EdgeAgentIdle || tunnel.Status == portainer.EdgeAgentManagementRequired {
|
||||
err := service.SetTunnelStatusToRequired(endpoint.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed opening tunnel to endpoint: %w", err)
|
||||
}
|
||||
|
||||
if endpoint.EdgeCheckinInterval == 0 {
|
||||
settings, err := service.dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed fetching settings from db: %w", err)
|
||||
}
|
||||
|
||||
endpoint.EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||
}
|
||||
|
||||
waitForAgentToConnect := time.Duration(endpoint.EdgeCheckinInterval) * time.Second
|
||||
time.Sleep(waitForAgentToConnect * 2)
|
||||
}
|
||||
tunnel = service.GetTunnelDetails(endpoint.ID)
|
||||
|
||||
return tunnel, nil
|
||||
}
|
||||
|
||||
// SetTunnelStatusToActive update the status of the tunnel associated to the specified environment(endpoint).
|
||||
// SetTunnelStatusToActive update the status of the tunnel associated to the specified endpoint.
|
||||
// It sets the status to ACTIVE.
|
||||
func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID) {
|
||||
tunnel := service.GetTunnelDetails(endpointID)
|
||||
@@ -94,7 +68,7 @@ func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID)
|
||||
service.tunnelDetailsMap.Set(key, tunnel)
|
||||
}
|
||||
|
||||
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified environment(endpoint).
|
||||
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified endpoint.
|
||||
// It sets the status to IDLE.
|
||||
// It removes any existing credentials associated to the tunnel.
|
||||
func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
|
||||
@@ -114,11 +88,11 @@ func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
|
||||
service.tunnelDetailsMap.Set(key, tunnel)
|
||||
}
|
||||
|
||||
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified environment(endpoint).
|
||||
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified endpoint.
|
||||
// It sets the status to REQUIRED.
|
||||
// If no port is currently associated to the tunnel, it will associate a random unused port to the tunnel
|
||||
// and generate temporary credentials that can be used to establish a reverse tunnel on that port.
|
||||
// Credentials are encrypted using the Edge ID associated to the environment(endpoint).
|
||||
// Credentials are encrypted using the Edge ID associated to the endpoint.
|
||||
func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
|
||||
tunnel := service.GetTunnelDetails(endpointID)
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
|
||||
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
|
||||
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
|
||||
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
|
||||
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").Default(defaultSnapshotInterval).String(),
|
||||
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
|
||||
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Confirm starts a rollback db cli application
|
||||
func Confirm(message string) (bool, error) {
|
||||
log.Printf("%s [y/N]", message)
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
answer, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
answer = strings.Replace(answer, "\n", "", -1)
|
||||
answer = strings.ToLower(answer)
|
||||
|
||||
return answer == "y" || answer == "yes", nil
|
||||
|
||||
}
|
||||
@@ -56,22 +56,15 @@ func initFileService(dataStorePath string) portainer.FileService {
|
||||
return fileService
|
||||
}
|
||||
|
||||
func initDataStore(dataStorePath string, rollback bool, fileService portainer.FileService, shutdownCtx context.Context) portainer.DataStore {
|
||||
store := bolt.NewStore(dataStorePath, fileService)
|
||||
err := store.Open()
|
||||
func initDataStore(dataStorePath string, fileService portainer.FileService, shutdownCtx context.Context) portainer.DataStore {
|
||||
store, err := bolt.NewStore(dataStorePath, fileService)
|
||||
if err != nil {
|
||||
log.Fatalf("failed opening store: %v", err)
|
||||
log.Fatalf("failed creating data store: %v", err)
|
||||
}
|
||||
|
||||
if rollback {
|
||||
err := store.Rollback(false)
|
||||
if err != nil {
|
||||
log.Fatalf("failed rolling back: %s", err)
|
||||
}
|
||||
|
||||
log.Println("Exiting rollback")
|
||||
os.Exit(0)
|
||||
return nil
|
||||
err = store.Open()
|
||||
if err != nil {
|
||||
log.Fatalf("failed opening store: %v", err)
|
||||
}
|
||||
|
||||
err = store.Init()
|
||||
@@ -106,8 +99,8 @@ func initSwarmStackManager(assetsPath string, configPath string, signatureServic
|
||||
return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService)
|
||||
}
|
||||
|
||||
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, assetsPath string) portainer.KubernetesDeployer {
|
||||
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager, assetsPath)
|
||||
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, assetsPath string) portainer.KubernetesDeployer {
|
||||
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, assetsPath)
|
||||
}
|
||||
|
||||
func initHelmPackageManager(assetsPath string) (libhelm.HelmPackageManager, error) {
|
||||
@@ -406,7 +399,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
fileService := initFileService(*flags.Data)
|
||||
|
||||
dataStore := initDataStore(*flags.Data, *flags.Rollback, fileService, shutdownCtx)
|
||||
dataStore := initDataStore(*flags.Data, fileService, shutdownCtx)
|
||||
|
||||
if err := dataStore.CheckCurrentEdition(); err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -476,7 +469,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
log.Fatalf("failed initializing swarm stack manager: %s", err)
|
||||
}
|
||||
|
||||
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
|
||||
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, *flags.Assets)
|
||||
|
||||
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
|
||||
if err != nil {
|
||||
@@ -549,7 +542,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
}
|
||||
|
||||
scheduler := scheduler.NewScheduler(shutdownCtx)
|
||||
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer)
|
||||
stackDeployer := stacks.NewStackDeployer(swarmStackManager, composeStackManager)
|
||||
stacks.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
|
||||
|
||||
return &http.Server{
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
)
|
||||
|
||||
// ECDSAService is a service used to create digital signatures when communicating with
|
||||
// an agent based environment(endpoint). It will automatically generates a key pair using ECDSA or
|
||||
// an agent based environment. It will automatically generates a key pair using ECDSA or
|
||||
// can also reuse an existing ECDSA key pair.
|
||||
type ECDSAService struct {
|
||||
privateKey *ecdsa.PrivateKey
|
||||
|
||||
@@ -34,8 +34,8 @@ func NewClientFactory(signatureService portainer.DigitalSignatureService, revers
|
||||
}
|
||||
|
||||
// createClient is a generic function to create a Docker client based on
|
||||
// a specific environment(endpoint) configuration. The nodeName parameter can be used
|
||||
// with an agent enabled environment(endpoint) to target a specific node in an agent cluster.
|
||||
// a specific endpoint configuration. The nodeName parameter can be used
|
||||
// with an agent enabled endpoint to target a specific node in an agent cluster.
|
||||
func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint, nodeName string) (*client.Client, error) {
|
||||
if endpoint.Type == portainer.AzureEnvironment {
|
||||
return nil, errUnsupportedEnvironmentType
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// Snapshotter represents a service used to create environment(endpoint) snapshots
|
||||
// Snapshotter represents a service used to create endpoint snapshots
|
||||
type Snapshotter struct {
|
||||
clientFactory *ClientFactory
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func NewSnapshotter(clientFactory *ClientFactory) *Snapshotter {
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSnapshot creates a snapshot of a specific Docker environment(endpoint)
|
||||
// CreateSnapshot creates a snapshot of a specific Docker endpoint
|
||||
func (snapshotter *Snapshotter) CreateSnapshot(endpoint *portainer.Endpoint) (*portainer.DockerSnapshot, error) {
|
||||
cli, err := snapshotter.clientFactory.CreateClient(endpoint, "")
|
||||
if err != nil {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package exec
|
||||
|
||||
import "regexp"
|
||||
|
||||
var stackNameNormalizeRegex = regexp.MustCompile("[^-_a-z0-9]+")
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -46,7 +47,7 @@ func (manager *ComposeStackManager) ComposeSyntaxMaxVersion() string {
|
||||
func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint) error {
|
||||
url, proxy, err := manager.fetchEndpointProxy(endpoint)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to fetch environment proxy")
|
||||
return errors.Wrap(err, "failed to featch environment proxy")
|
||||
}
|
||||
|
||||
if proxy != nil {
|
||||
@@ -79,8 +80,9 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
|
||||
}
|
||||
|
||||
// NormalizeStackName returns a new stack name with unsupported characters replaced
|
||||
func (manager *ComposeStackManager) NormalizeStackName(name string) string {
|
||||
return stackNameNormalizeRegex.ReplaceAllString(strings.ToLower(name), "")
|
||||
func (w *ComposeStackManager) NormalizeStackName(name string) string {
|
||||
r := regexp.MustCompile("[^a-z0-9]+")
|
||||
return r.ReplaceAllString(strings.ToLower(name), "")
|
||||
}
|
||||
|
||||
func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpoint) (string, *factory.ProxyServer, error) {
|
||||
@@ -88,7 +90,7 @@ func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpo
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
proxy, err := manager.proxyManager.CreateAgentProxyServer(endpoint)
|
||||
proxy, err := manager.proxyManager.CreateComposeProxyServer(endpoint)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package exectest
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type kubernetesMockDeployer struct{}
|
||||
|
||||
func NewKubernetesDeployer() portainer.KubernetesDeployer {
|
||||
return &kubernetesMockDeployer{}
|
||||
}
|
||||
|
||||
func (deployer *kubernetesMockDeployer) Deploy(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (deployer *kubernetesMockDeployer) Remove(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (deployer *kubernetesMockDeployer) ConvertCompose(data []byte) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -2,22 +2,27 @@ package exec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
)
|
||||
|
||||
// KubernetesDeployer represents a service to deploy resources inside a Kubernetes environment(endpoint).
|
||||
// KubernetesDeployer represents a service to deploy resources inside a Kubernetes environment.
|
||||
type KubernetesDeployer struct {
|
||||
binaryPath string
|
||||
dataStore portainer.DataStore
|
||||
@@ -25,11 +30,10 @@ type KubernetesDeployer struct {
|
||||
signatureService portainer.DigitalSignatureService
|
||||
kubernetesClientFactory *cli.ClientFactory
|
||||
kubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
||||
proxyManager *proxy.Manager
|
||||
}
|
||||
|
||||
// NewKubernetesDeployer initializes a new KubernetesDeployer service.
|
||||
func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheManager, kubernetesClientFactory *cli.ClientFactory, datastore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, binaryPath string) *KubernetesDeployer {
|
||||
func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheManager, kubernetesClientFactory *cli.ClientFactory, datastore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, binaryPath string) *KubernetesDeployer {
|
||||
return &KubernetesDeployer{
|
||||
binaryPath: binaryPath,
|
||||
dataStore: datastore,
|
||||
@@ -37,33 +41,32 @@ func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheMan
|
||||
signatureService: signatureService,
|
||||
kubernetesClientFactory: kubernetesClientFactory,
|
||||
kubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
||||
proxyManager: proxyManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (deployer *KubernetesDeployer) getToken(userID portainer.UserID, endpoint *portainer.Endpoint, setLocalAdminToken bool) (string, error) {
|
||||
kubeCLI, err := deployer.kubernetesClientFactory.GetKubeClient(endpoint)
|
||||
func (deployer *KubernetesDeployer) getToken(request *http.Request, endpoint *portainer.Endpoint, setLocalAdminToken bool) (string, error) {
|
||||
tokenData, err := security.RetrieveTokenData(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
kubecli, err := deployer.kubernetesClientFactory.GetKubeClient(endpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tokenCache := deployer.kubernetesTokenCacheManager.GetOrCreateTokenCache(int(endpoint.ID))
|
||||
|
||||
tokenManager, err := kubernetes.NewTokenManager(kubeCLI, deployer.dataStore, tokenCache, setLocalAdminToken)
|
||||
tokenManager, err := kubernetes.NewTokenManager(kubecli, deployer.dataStore, tokenCache, setLocalAdminToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
user, err := deployer.dataStore.User().User(userID)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to fetch the user")
|
||||
}
|
||||
|
||||
if user.Role == portainer.AdministratorRole {
|
||||
if tokenData.Role == portainer.AdministratorRole {
|
||||
return tokenManager.GetAdminServiceAccountToken(), nil
|
||||
}
|
||||
|
||||
token, err := tokenManager.GetUserServiceAccountToken(int(user.ID), endpoint.ID)
|
||||
token, err := tokenManager.GetUserServiceAccountToken(int(tokenData.ID), endpoint.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -74,62 +77,156 @@ func (deployer *KubernetesDeployer) getToken(userID portainer.UserID, endpoint *
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Deploy upserts Kubernetes resources defined in manifest(s)
|
||||
func (deployer *KubernetesDeployer) Deploy(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
|
||||
return deployer.command("apply", userID, endpoint, manifestFiles, namespace)
|
||||
}
|
||||
|
||||
// Remove deletes Kubernetes resources defined in manifest(s)
|
||||
func (deployer *KubernetesDeployer) Remove(userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
|
||||
return deployer.command("delete", userID, endpoint, manifestFiles, namespace)
|
||||
}
|
||||
|
||||
func (deployer *KubernetesDeployer) command(operation string, userID portainer.UserID, endpoint *portainer.Endpoint, manifestFiles []string, namespace string) (string, error) {
|
||||
token, err := deployer.getToken(userID, endpoint, endpoint.Type == portainer.KubernetesLocalEnvironment)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed generating a user token")
|
||||
}
|
||||
|
||||
command := path.Join(deployer.binaryPath, "kubectl")
|
||||
if runtime.GOOS == "windows" {
|
||||
command = path.Join(deployer.binaryPath, "kubectl.exe")
|
||||
}
|
||||
|
||||
args := []string{"--token", token}
|
||||
if namespace != "" {
|
||||
args = append(args, "--namespace", namespace)
|
||||
}
|
||||
|
||||
if endpoint.Type == portainer.AgentOnKubernetesEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
|
||||
url, proxy, err := deployer.getAgentURL(endpoint)
|
||||
// Deploy will deploy a Kubernetes manifest inside a specific namespace in a Kubernetes endpoint.
|
||||
// Otherwise it will use kubectl to deploy the manifest.
|
||||
func (deployer *KubernetesDeployer) Deploy(request *http.Request, endpoint *portainer.Endpoint, stackConfig string, namespace string) (string, error) {
|
||||
if endpoint.Type == portainer.KubernetesLocalEnvironment {
|
||||
token, err := deployer.getToken(request, endpoint, true)
|
||||
if err != nil {
|
||||
return "", errors.WithMessage(err, "failed generating endpoint URL")
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer proxy.Close()
|
||||
args = append(args, "--server", url)
|
||||
command := path.Join(deployer.binaryPath, "kubectl")
|
||||
if runtime.GOOS == "windows" {
|
||||
command = path.Join(deployer.binaryPath, "kubectl.exe")
|
||||
}
|
||||
|
||||
args := make([]string, 0)
|
||||
args = append(args, "--server", endpoint.URL)
|
||||
args = append(args, "--insecure-skip-tls-verify")
|
||||
args = append(args, "--token", token)
|
||||
args = append(args, "--namespace", namespace)
|
||||
args = append(args, "apply", "-f", "-")
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdin = strings.NewReader(stackConfig)
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", errors.New(stderr.String())
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
if operation == "delete" {
|
||||
args = append(args, "--ignore-not-found=true")
|
||||
// agent
|
||||
|
||||
endpointURL := endpoint.URL
|
||||
if endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
|
||||
tunnel := deployer.reverseTunnelService.GetTunnelDetails(endpoint.ID)
|
||||
if tunnel.Status == portainer.EdgeAgentIdle {
|
||||
|
||||
err := deployer.reverseTunnelService.SetTunnelStatusToRequired(endpoint.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
settings, err := deployer.dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
waitForAgentToConnect := time.Duration(settings.EdgeAgentCheckinInterval) * time.Second
|
||||
time.Sleep(waitForAgentToConnect * 2)
|
||||
}
|
||||
|
||||
endpointURL = fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
|
||||
}
|
||||
|
||||
args = append(args, operation)
|
||||
for _, path := range manifestFiles {
|
||||
args = append(args, "-f", strings.TrimSpace(path))
|
||||
transport := &http.Transport{}
|
||||
|
||||
if endpoint.TLSConfig.TLS {
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
transport.TLSClientConfig = tlsConfig
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stderr = &stderr
|
||||
httpCli := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
output, err := cmd.Output()
|
||||
if !strings.HasPrefix(endpointURL, "http") {
|
||||
endpointURL = fmt.Sprintf("https://%s", endpointURL)
|
||||
}
|
||||
|
||||
url, err := url.Parse(fmt.Sprintf("%s/v2/kubernetes/stack", endpointURL))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to execute kubectl command: %q", stderr.String())
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
reqPayload, err := json.Marshal(
|
||||
struct {
|
||||
StackConfig string
|
||||
Namespace string
|
||||
}{
|
||||
StackConfig: stackConfig,
|
||||
Namespace: namespace,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, url.String(), bytes.NewReader(reqPayload))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signature, err := deployer.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token, err := deployer.getToken(request, endpoint, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Set(portainer.PortainerAgentPublicKeyHeader, deployer.signatureService.EncodedPublicKey())
|
||||
req.Header.Set(portainer.PortainerAgentSignatureHeader, signature)
|
||||
req.Header.Set(portainer.PortainerAgentKubernetesSATokenHeader, token)
|
||||
|
||||
resp, err := httpCli.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var errorResponseData struct {
|
||||
Message string
|
||||
Details string
|
||||
}
|
||||
err = json.NewDecoder(resp.Body).Decode(&errorResponseData)
|
||||
if err != nil {
|
||||
output, parseStringErr := ioutil.ReadAll(resp.Body)
|
||||
if parseStringErr != nil {
|
||||
return "", parseStringErr
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Failed parsing, body: %s, error: %w", output, err)
|
||||
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Deployment to agent failed: %s", errorResponseData.Details)
|
||||
}
|
||||
|
||||
var responseData struct{ Output string }
|
||||
err = json.NewDecoder(resp.Body).Decode(&responseData)
|
||||
if err != nil {
|
||||
parsedOutput, parseStringErr := ioutil.ReadAll(resp.Body)
|
||||
if parseStringErr != nil {
|
||||
return "", parseStringErr
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Failed decoding, body: %s, err: %w", parsedOutput, err)
|
||||
}
|
||||
|
||||
return responseData.Output, nil
|
||||
|
||||
}
|
||||
|
||||
// ConvertCompose leverages the kompose binary to deploy a compose compliant manifest.
|
||||
@@ -154,12 +251,3 @@ func (deployer *KubernetesDeployer) ConvertCompose(data []byte) ([]byte, error)
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (deployer *KubernetesDeployer) getAgentURL(endpoint *portainer.Endpoint) (string, *factory.ProxyServer, error) {
|
||||
proxy, err := deployer.proxyManager.CreateAgentProxyServer(endpoint)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("http://127.0.0.1:%d/kubernetes", proxy.Port), proxy, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@@ -189,7 +190,8 @@ func (manager *SwarmStackManager) retrieveConfigurationFromDisk(path string) (ma
|
||||
}
|
||||
|
||||
func (manager *SwarmStackManager) NormalizeStackName(name string) string {
|
||||
return stackNameNormalizeRegex.ReplaceAllString(strings.ToLower(name), "")
|
||||
r := regexp.MustCompile("[^a-z0-9]+")
|
||||
return r.ReplaceAllString(strings.ToLower(name), "")
|
||||
}
|
||||
|
||||
func configureFilePaths(args []string, filePaths []string) []string {
|
||||
|
||||
@@ -288,7 +288,7 @@ func (service *Service) StoreTLSFileFromBytes(folder string, fileType portainer.
|
||||
return path.Join(service.fileStorePath, tlsFilePath), nil
|
||||
}
|
||||
|
||||
// GetPathForTLSFile returns the absolute path to a specific TLS file for an environment(endpoint).
|
||||
// GetPathForTLSFile returns the absolute path to a specific TLS file for an endpoint.
|
||||
func (service *Service) GetPathForTLSFile(folder string, fileType portainer.TLSFileType) (string, error) {
|
||||
var fileName string
|
||||
switch fileType {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func WriteToFile(dst string, content []byte) error {
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0744); err != nil {
|
||||
return errors.Wrapf(err, "failed to create filestructure for the path %q", dst)
|
||||
}
|
||||
|
||||
file, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open a file %q", dst)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Write(content)
|
||||
return errors.Wrapf(err, "failed to write a file %q", dst)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_WriteFile_CanStoreContentInANewFile(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
tmpFilePath := path.Join(tmpDir, "dummy")
|
||||
|
||||
content := []byte("content")
|
||||
err := WriteToFile(tmpFilePath, content)
|
||||
assert.NoError(t, err)
|
||||
|
||||
fileContent, _ := ioutil.ReadFile(tmpFilePath)
|
||||
assert.Equal(t, content, fileContent)
|
||||
}
|
||||
|
||||
func Test_WriteFile_CanOverwriteExistingFile(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
tmpFilePath := path.Join(tmpDir, "dummy")
|
||||
|
||||
err := WriteToFile(tmpFilePath, []byte("content"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
content := []byte("new content")
|
||||
err = WriteToFile(tmpFilePath, content)
|
||||
assert.NoError(t, err)
|
||||
|
||||
fileContent, _ := ioutil.ReadFile(tmpFilePath)
|
||||
assert.Equal(t, content, fileContent)
|
||||
}
|
||||
|
||||
func Test_WriteFile_CanWriteANestedPath(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
tmpFilePath := path.Join(tmpDir, "dir", "sub-dir", "dummy")
|
||||
|
||||
content := []byte("content")
|
||||
err := WriteToFile(tmpFilePath, content)
|
||||
assert.NoError(t, err)
|
||||
|
||||
fileContent, _ := ioutil.ReadFile(tmpFilePath)
|
||||
assert.Equal(t, content, fileContent)
|
||||
}
|
||||
41
api/go.mod
41
api/go.mod
@@ -3,48 +3,57 @@ module github.com/portainer/portainer/api
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.17
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.16
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
|
||||
github.com/boltdb/bolt v1.3.1
|
||||
github.com/containerd/containerd v1.5.7 // indirect
|
||||
github.com/containerd/containerd v1.3.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.0
|
||||
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/docker/cli v20.10.9+incompatible
|
||||
github.com/docker/docker v20.10.9+incompatible
|
||||
github.com/docker/cli v0.0.0-20191126203649-54d085b857e9
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
|
||||
github.com/go-git/go-git/v5 v5.3.0
|
||||
github.com/go-ldap/ldap/v3 v3.1.8
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gofrs/uuid v3.2.0+incompatible
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/jpillora/chisel v0.0.0-20190724232113-f3a8df20e389
|
||||
github.com/json-iterator/go v1.1.11
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.6 // indirect
|
||||
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20210909083948-8be0d98451a1
|
||||
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a
|
||||
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108
|
||||
github.com/portainer/libhelm v0.0.0-20210913052337-365741c1c320
|
||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/swaggo/swag v1.7.3
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
k8s.io/api v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
k8s.io/client-go v0.22.2
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
k8s.io/api v0.17.2
|
||||
k8s.io/apimachinery v0.17.2
|
||||
k8s.io/client-go v0.17.2
|
||||
)
|
||||
|
||||
replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20200204220554-5f6d6f3f2203
|
||||
|
||||
replace golang.org/x/sys => golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456
|
||||
|
||||
971
api/go.sum
971
api/go.sum
File diff suppressed because it is too large
Load Diff
@@ -102,7 +102,7 @@ func Get(url string, timeout int) ([]byte, error) {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// ExecutePingOperation will send a SystemPing operation HTTP request to a Docker environment(endpoint)
|
||||
// ExecutePingOperation will send a SystemPing operation HTTP request to a Docker environment
|
||||
// using the specified host and optional TLS configuration.
|
||||
// It uses a new Http.Client for each operation.
|
||||
func ExecutePingOperation(host string, tlsConfig *tls.Config) (bool, error) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package errors
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrEndpointAccessDenied Access denied to environment(endpoint) error
|
||||
// ErrEndpointAccessDenied Access denied to endpoint error
|
||||
ErrEndpointAccessDenied = errors.New("Access denied to environment")
|
||||
// ErrUnauthorized Unauthorized error
|
||||
ErrUnauthorized = errors.New("Unauthorized")
|
||||
|
||||
@@ -39,8 +39,7 @@ func (payload *authenticatePayload) Validate(r *http.Request) error {
|
||||
|
||||
// @id AuthenticateUser
|
||||
// @summary Authenticate
|
||||
// @description **Access policy**: public
|
||||
// @description Use this environment(endpoint) to authenticate against Portainer using a username and password.
|
||||
// @description Use this endpoint to authenticate against Portainer using a username and password.
|
||||
// @tags auth
|
||||
// @accept json
|
||||
// @produce json
|
||||
|
||||
@@ -44,7 +44,6 @@ func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuth
|
||||
|
||||
// @id ValidateOAuth
|
||||
// @summary Authenticate with OAuth
|
||||
// @description **Access policy**: public
|
||||
// @tags auth
|
||||
// @accept json
|
||||
// @produce json
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
// @id Logout
|
||||
// @summary Logout
|
||||
// @description **Access policy**: authenticated
|
||||
// @security jwt
|
||||
// @tags auth
|
||||
// @success 204 "Success"
|
||||
|
||||
@@ -27,9 +27,8 @@ func (p *backupPayload) Validate(r *http.Request) error {
|
||||
// @description **Access policy**: admin
|
||||
// @tags backup
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce octet-stream
|
||||
// @param body body backupPayload false "An object contains the password to encrypt the backup with"
|
||||
// @param Password body string false "Password to encrypt the backup with"
|
||||
// @success 200 "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 500 "Server error"
|
||||
|
||||
@@ -22,9 +22,10 @@ type restorePayload struct {
|
||||
// @description Triggers a system restore using provided backup file
|
||||
// @description **Access policy**: public
|
||||
// @tags backup
|
||||
// @accept json
|
||||
// @param restorePayload body restorePayload true "Restore request payload"
|
||||
// @success 200 "Success"
|
||||
// @param FileContent body []byte true "Content of the backup"
|
||||
// @param FileName body string true "File name"
|
||||
// @param Password body string false "Password to decrypt the backup with"
|
||||
// @success 200 "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 500 "Server error"
|
||||
// @router /restore [post]
|
||||
|
||||
@@ -3,7 +3,6 @@ package customtemplates
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
@@ -130,20 +129,9 @@ func (payload *customTemplateFromFileContentPayload) Validate(r *http.Request) e
|
||||
if payload.Type != portainer.KubernetesStack && payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack {
|
||||
return errors.New("Invalid custom template type")
|
||||
}
|
||||
if !isValidNote(payload.Note) {
|
||||
return errors.New("Invalid note. <img> tag is not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isValidNote(note string) bool {
|
||||
if govalidator.IsNull(note) {
|
||||
return true
|
||||
}
|
||||
match, _ := regexp.MatchString("<img", note)
|
||||
return !match
|
||||
}
|
||||
|
||||
func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*portainer.CustomTemplate, error) {
|
||||
var payload customTemplateFromFileContentPayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
@@ -230,9 +218,6 @@ func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request)
|
||||
if payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack {
|
||||
return errors.New("Invalid custom template type")
|
||||
}
|
||||
if !isValidNote(payload.Note) {
|
||||
return errors.New("Invalid note. <img> tag is not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -300,9 +285,6 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er
|
||||
payload.Logo = logo
|
||||
|
||||
note, _ := request.RetrieveMultiPartFormValue(r, "Note", true)
|
||||
if !isValidNote(note) {
|
||||
return errors.New("Invalid note. <img> tag is not supported")
|
||||
}
|
||||
payload.Note = note
|
||||
|
||||
typeNumeral, _ := request.RetrieveNumericMultiPartFormValue(r, "Type", true)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// @id CustomTemplateDelete
|
||||
// @summary Remove a template
|
||||
// @description Remove a template.
|
||||
// @description **Access policy**: authenticated
|
||||
// @description **Access policy**: authorized
|
||||
// @tags custom_templates
|
||||
// @security jwt
|
||||
// @param id path int true "Template identifier"
|
||||
|
||||
@@ -18,7 +18,7 @@ type fileResponse struct {
|
||||
// @id CustomTemplateFile
|
||||
// @summary Get Template stack file content.
|
||||
// @description Retrieve the content of the Stack file for the specified custom template
|
||||
// @description **Access policy**: authenticated
|
||||
// @description **Access policy**: authorized
|
||||
// @tags custom_templates
|
||||
// @security jwt
|
||||
// @produce json
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
// @description **Access policy**: authenticated
|
||||
// @tags custom_templates
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path int true "Template identifier"
|
||||
// @success 200 {object} portainer.CustomTemplate "Success"
|
||||
|
||||
@@ -51,9 +51,6 @@ func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error {
|
||||
if govalidator.IsNull(payload.Description) {
|
||||
return errors.New("Invalid custom template description")
|
||||
}
|
||||
if !isValidNote(payload.Note) {
|
||||
return errors.New("Invalid note. <img> tag is not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle environment(endpoint) group operations.
|
||||
// Handler is the HTTP handler used to handle endpoint group operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
DataStore portainer.DataStore
|
||||
@@ -18,7 +18,7 @@ type Handler struct {
|
||||
GitService portainer.GitService
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage environment(endpoint) group operations.
|
||||
// NewHandler creates a handler to manage endpoint group operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
|
||||
@@ -34,7 +34,7 @@ func (payload *edgeGroupCreatePayload) Validate(r *http.Request) error {
|
||||
|
||||
// @id EdgeGroupCreate
|
||||
// @summary Create an EdgeGroup
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_groups
|
||||
// @security jwt
|
||||
// @accept json
|
||||
|
||||
@@ -13,9 +13,11 @@ import (
|
||||
|
||||
// @id EdgeGroupDelete
|
||||
// @summary Deletes an EdgeGroup
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_groups
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path int true "EdgeGroup Id"
|
||||
// @success 204
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
|
||||
// @id EdgeGroupInspect
|
||||
// @summary Inspects an EdgeGroup
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_groups
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path int true "EdgeGroup Id"
|
||||
// @success 200 {object} portainer.EdgeGroup
|
||||
|
||||
@@ -17,9 +17,10 @@ type decoratedEdgeGroup struct {
|
||||
|
||||
// @id EdgeGroupList
|
||||
// @summary list EdgeGroups
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_groups
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @success 200 {array} portainer.EdgeGroup{HasEdgeStack=bool} "EdgeGroups"
|
||||
// @failure 500
|
||||
|
||||
@@ -36,7 +36,7 @@ func (payload *edgeGroupUpdatePayload) Validate(r *http.Request) error {
|
||||
|
||||
// @id EgeGroupUpdate
|
||||
// @summary Updates an EdgeGroup
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_groups
|
||||
// @security jwt
|
||||
// @accept json
|
||||
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle environment(endpoint) group operations.
|
||||
// Handler is the HTTP handler used to handle endpoint group operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
DataStore portainer.DataStore
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage environment(endpoint) group operations.
|
||||
// NewHandler creates a handler to manage endpoint group operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
|
||||
@@ -16,9 +16,10 @@ import (
|
||||
|
||||
// @id EdgeJobCreate
|
||||
// @summary Create an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param method query string true "Creation Method" Enums(file, string)
|
||||
// @param body_string body edgeJobCreateFromFileContentPayload true "EdgeGroup data when method is string"
|
||||
|
||||
@@ -13,9 +13,11 @@ import (
|
||||
|
||||
// @id EdgeJobDelete
|
||||
// @summary Delete an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeJob Id"
|
||||
// @success 204
|
||||
// @failure 500
|
||||
|
||||
@@ -16,9 +16,10 @@ type edgeJobFileResponse struct {
|
||||
|
||||
// @id EdgeJobFile
|
||||
// @summary Fetch a file of an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeJob Id"
|
||||
// @success 200 {object} edgeJobFileResponse
|
||||
|
||||
@@ -17,9 +17,10 @@ type edgeJobInspectResponse struct {
|
||||
|
||||
// @id EdgeJobInspect
|
||||
// @summary Inspect an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeJob Id"
|
||||
// @success 200 {object} portainer.EdgeJob
|
||||
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
|
||||
// @id EdgeJobList
|
||||
// @summary Fetch EdgeJobs list
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @success 200 {array} portainer.EdgeJob
|
||||
// @failure 500
|
||||
|
||||
@@ -13,9 +13,10 @@ import (
|
||||
|
||||
// @id EdgeJobTasksClear
|
||||
// @summary Clear the log for a specifc task on an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeJob Id"
|
||||
// @param taskID path string true "Task Id"
|
||||
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
|
||||
// @id EdgeJobTasksCollect
|
||||
// @summary Collect the log for a specifc task on an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeJob Id"
|
||||
// @param taskID path string true "Task Id"
|
||||
|
||||
@@ -15,9 +15,10 @@ type fileResponse struct {
|
||||
|
||||
// @id EdgeJobTaskLogsInspect
|
||||
// @summary Fetch the log for a specifc task on an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeJob Id"
|
||||
// @param taskID path string true "Task Id"
|
||||
|
||||
@@ -19,9 +19,10 @@ type taskContainer struct {
|
||||
|
||||
// @id EdgeJobTasksList
|
||||
// @summary Fetch the list of tasks on an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeJob Id"
|
||||
// @success 200 {array} taskContainer
|
||||
|
||||
@@ -30,7 +30,7 @@ func (payload *edgeJobUpdatePayload) Validate(r *http.Request) error {
|
||||
|
||||
// @id EdgeJobUpdate
|
||||
// @summary Update an EdgeJob
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_jobs
|
||||
// @security jwt
|
||||
// @accept json
|
||||
|
||||
@@ -19,9 +19,10 @@ import (
|
||||
|
||||
// @id EdgeStackCreate
|
||||
// @summary Create an EdgeStack
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_stacks
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param method query string true "Creation Method" Enums(file,string,repository)
|
||||
// @param body_string body swarmStackFromFileContentPayload true "Required when using method=string"
|
||||
@@ -66,8 +67,8 @@ type swarmStackFromFileContentPayload struct {
|
||||
EdgeGroups []portainer.EdgeGroupID `example:"1"`
|
||||
// Deployment type to deploy this stack
|
||||
// Valid values are: 0 - 'compose', 1 - 'kubernetes'
|
||||
// for compose stacks will use kompose to convert to kubernetes manifest for kubernetes environments(endpoints)
|
||||
// kubernetes deploytype is enabled only for kubernetes environments(endpoints)
|
||||
// for compose stacks will use kompose to convert to kubernetes manifest for kubernetes endpoints
|
||||
// kubernetes deploytype is enabled only for kubernetes endpoints
|
||||
DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1"`
|
||||
}
|
||||
|
||||
@@ -184,8 +185,8 @@ type swarmStackFromGitRepositoryPayload struct {
|
||||
EdgeGroups []portainer.EdgeGroupID `example:"1"`
|
||||
// Deployment type to deploy this stack
|
||||
// Valid values are: 0 - 'compose', 1 - 'kubernetes'
|
||||
// for compose stacks will use kompose to convert to kubernetes manifest for kubernetes environments(endpoints)
|
||||
// kubernetes deploytype is enabled only for kubernetes environments(endpoints)
|
||||
// for compose stacks will use kompose to convert to kubernetes manifest for kubernetes endpoints
|
||||
// kubernetes deploytype is enabled only for kubernetes endpoints
|
||||
DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1"`
|
||||
}
|
||||
|
||||
@@ -401,7 +402,7 @@ func (handler *Handler) validateUniqueName(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateEndpointRelations adds a relation between the Edge Stack to the related environments(endpoints)
|
||||
// updateEndpointRelations adds a relation between the Edge Stack to the related endpoints
|
||||
func updateEndpointRelations(endpointRelationService portainer.EndpointRelationService, edgeStackID portainer.EdgeStackID, relatedEndpointIds []portainer.EndpointID) error {
|
||||
for _, endpointID := range relatedEndpointIds {
|
||||
relation, err := endpointRelationService.EndpointRelation(endpointID)
|
||||
|
||||
@@ -13,9 +13,11 @@ import (
|
||||
|
||||
// @id EdgeStackDelete
|
||||
// @summary Delete an EdgeStack
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_stacks
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeStack Id"
|
||||
// @success 204
|
||||
// @failure 500
|
||||
|
||||
@@ -17,9 +17,10 @@ type stackFileResponse struct {
|
||||
|
||||
// @id EdgeStackFile
|
||||
// @summary Fetches the stack file for an EdgeStack
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_stacks
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeStack Id"
|
||||
// @success 200 {object} stackFileResponse
|
||||
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
|
||||
// @id EdgeStackInspect
|
||||
// @summary Inspect an EdgeStack
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_stacks
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "EdgeStack Id"
|
||||
// @success 200 {object} portainer.EdgeStack
|
||||
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
|
||||
// @id EdgeStackList
|
||||
// @summary Fetches the list of EdgeStacks
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_stacks
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @success 200 {array} portainer.EdgeStack
|
||||
// @failure 500
|
||||
|
||||
@@ -33,7 +33,7 @@ func (payload *updateStatusPayload) Validate(r *http.Request) error {
|
||||
|
||||
// @id EdgeStackStatusUpdate
|
||||
// @summary Update an EdgeStack status
|
||||
// @description Authorized only if the request is done by an Edge Environment(Endpoint)
|
||||
// @description Authorized only if the request is done by an Edge Endpoint
|
||||
// @tags edge_stacks
|
||||
// @accept json
|
||||
// @produce json
|
||||
|
||||
@@ -33,7 +33,7 @@ func (payload *updateEdgeStackPayload) Validate(r *http.Request) error {
|
||||
|
||||
// @id EdgeStackUpdate
|
||||
// @summary Update an EdgeStack
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_stacks
|
||||
// @security jwt
|
||||
// @accept json
|
||||
@@ -164,11 +164,11 @@ func (handler *Handler) edgeStackUpdate(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
hasDockerEndpoint, err := hasDockerEndpoint(handler.DataStore.Endpoint(), relatedEndpointIds)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for existence of docker environment", err}
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for existence of docker endpoint", err}
|
||||
}
|
||||
|
||||
if hasDockerEndpoint {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Edge stack with docker environment cannot be deployed with kubernetes config", err}
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Edge stack with docker endpoint cannot be deployed with kubernetes config", err}
|
||||
}
|
||||
|
||||
_, err = handler.FileService.StoreEdgeStackFileFromBytes(stackFolder, stack.ManifestPath, []byte(payload.StackFileContent))
|
||||
|
||||
@@ -19,7 +19,7 @@ func hasEndpointPredicate(endpointService portainer.EndpointService, endpointIDs
|
||||
for _, endpointID := range endpointIDs {
|
||||
endpoint, err := endpointService.Endpoint(endpointID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to retrieve environment from database: %w", err)
|
||||
return false, fmt.Errorf("failed to retrieve endpoint from database: %w", err)
|
||||
}
|
||||
|
||||
if predicate(endpoint) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle environment(endpoint) group operations.
|
||||
// Handler is the HTTP handler used to handle endpoint group operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
requestBouncer *security.RequestBouncer
|
||||
@@ -23,7 +23,7 @@ type Handler struct {
|
||||
KubernetesDeployer portainer.KubernetesDeployer
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage environment(endpoint) group operations.
|
||||
// NewHandler creates a handler to manage endpoint group operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
@@ -49,7 +49,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
func (handler *Handler) convertAndStoreKubeManifestIfNeeded(edgeStack *portainer.EdgeStack, relatedEndpointIds []portainer.EndpointID) error {
|
||||
hasKubeEndpoint, err := hasKubeEndpoint(handler.DataStore.Endpoint(), relatedEndpointIds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check if edge stack has kube environments: %w", err)
|
||||
return fmt.Errorf("unable to check if edge stack has kube endpoints: %w", err)
|
||||
}
|
||||
|
||||
if !hasKubeEndpoint {
|
||||
|
||||
@@ -17,7 +17,7 @@ type templateFileFormat struct {
|
||||
|
||||
// @id EdgeTemplateList
|
||||
// @summary Fetches the list of Edge Templates
|
||||
// @description **Access policy**: administrator
|
||||
// @description
|
||||
// @tags edge_templates
|
||||
// @security jwt
|
||||
// @accept json
|
||||
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle edge environment(endpoint) operations.
|
||||
// Handler is the HTTP handler used to handle edge endpoint operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
requestBouncer *security.RequestBouncer
|
||||
DataStore portainer.DataStore
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage environment(endpoint) operations.
|
||||
// NewHandler creates a handler to manage endpoint operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
|
||||
@@ -21,11 +21,11 @@ func (payload *logsPayload) Validate(r *http.Request) error {
|
||||
|
||||
// endpointEdgeJobsLogs
|
||||
// @summary Inspect an EdgeJob Log
|
||||
// @description **Access policy**: public
|
||||
// @description
|
||||
// @tags edge, endpoints
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "environment(endpoint) Id"
|
||||
// @param id path string true "Endpoint Id"
|
||||
// @param jobID path string true "Job Id"
|
||||
// @success 200
|
||||
// @failure 500
|
||||
|
||||
@@ -18,12 +18,12 @@ type configResponse struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// @summary Inspect an Edge Stack for an Environment(Endpoint)
|
||||
// @description **Access policy**: public
|
||||
// @summary Inspect an Edge Stack for an Endpoint
|
||||
// @description
|
||||
// @tags edge, endpoints, edge_stacks
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path string true "environment(endpoint) Id"
|
||||
// @param id path string true "Endpoint Id"
|
||||
// @param stackId path string true "EdgeStack Id"
|
||||
// @success 200 {object} configResponse
|
||||
// @failure 500
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle edge environment(endpoint) operations.
|
||||
// Handler is the HTTP handler used to handle edge endpoint operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
requestBouncer *security.RequestBouncer
|
||||
@@ -19,7 +19,7 @@ type Handler struct {
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage environment(endpoint) operations.
|
||||
// NewHandler creates a handler to manage endpoint operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user