mirror of https://github.com/portainer/portainer
feat(backup): Add backup/restore to the server
parent
c04bbb5775
commit
a3ec2f8e85
|
@ -0,0 +1,69 @@
|
||||||
|
package adminmonitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logFatalf = log.Fatalf
|
||||||
|
|
||||||
|
type Monitor struct {
|
||||||
|
timeout time.Duration
|
||||||
|
datastore portainer.DataStore
|
||||||
|
shutdownCtx context.Context
|
||||||
|
cancellationFunc context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a monitor that when started will wait for the timeout duration and then shutdown the application unless it has been initialized.
|
||||||
|
func New(timeout time.Duration, datastore portainer.DataStore, shutdownCtx context.Context) *Monitor {
|
||||||
|
return &Monitor{
|
||||||
|
timeout: timeout,
|
||||||
|
datastore: datastore,
|
||||||
|
shutdownCtx: shutdownCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts starts the monitor. Active monitor could be stopped or shuttted down by cancelling the shutdown context.
|
||||||
|
func (m *Monitor) Start() {
|
||||||
|
cancellationCtx, cancellationFunc := context.WithCancel(context.Background())
|
||||||
|
m.cancellationFunc = cancellationFunc
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Println("[DEBUG] [internal,init] [message: start initialization monitor ]")
|
||||||
|
select {
|
||||||
|
case <-time.After(m.timeout):
|
||||||
|
initialized, err := m.WasInitialized()
|
||||||
|
if err != nil {
|
||||||
|
logFatalf("%s", err)
|
||||||
|
}
|
||||||
|
if !initialized {
|
||||||
|
logFatalf("[FATAL] [internal,init] No administrator account was created in %f mins. Shutting down the Portainer instance for security reasons", m.timeout.Minutes())
|
||||||
|
}
|
||||||
|
case <-cancellationCtx.Done():
|
||||||
|
log.Println("[DEBUG] [internal,init] [message: canceling initialization monitor]")
|
||||||
|
case <-m.shutdownCtx.Done():
|
||||||
|
log.Println("[DEBUG] [internal,init] [message: shutting down initialization monitor]")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops monitor. Safe to call even if monitor wasn't started.
|
||||||
|
func (m *Monitor) Stop() {
|
||||||
|
if m.cancellationFunc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.cancellationFunc()
|
||||||
|
m.cancellationFunc = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WasInitialized is a system initialization check
|
||||||
|
func (m *Monitor) WasInitialized() (bool, error) {
|
||||||
|
users, err := m.datastore.User().UsersByRole(portainer.AdministratorRole)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return len(users) > 0, nil
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package adminmonitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
i "github.com/portainer/portainer/api/internal/testhelpers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_stopWithoutStarting(t *testing.T) {
|
||||||
|
monitor := New(1*time.Minute, nil, nil)
|
||||||
|
monitor.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_stopCouldBeCalledMultipleTimes(t *testing.T) {
|
||||||
|
monitor := New(1*time.Minute, nil, nil)
|
||||||
|
monitor.Stop()
|
||||||
|
monitor.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_canStopStartedMonitor(t *testing.T) {
|
||||||
|
monitor := New(1*time.Minute, nil, context.Background())
|
||||||
|
monitor.Start()
|
||||||
|
assert.NotNil(t, monitor.cancellationFunc, "cancellation function is missing in started monitor")
|
||||||
|
|
||||||
|
monitor.Stop()
|
||||||
|
assert.Nil(t, monitor.cancellationFunc, "cancellation function should absent in stopped monitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_start_shouldFatalAfterTimeout_ifNotInitialized(t *testing.T) {
|
||||||
|
timeout := 10 * time.Millisecond
|
||||||
|
|
||||||
|
datastore := i.NewDatastore(i.WithUsers([]portainer.User{}))
|
||||||
|
|
||||||
|
var fataled bool
|
||||||
|
origLogFatalf := logFatalf
|
||||||
|
logFatalf = func(s string, v ...interface{}) { fataled = true }
|
||||||
|
defer func() {
|
||||||
|
logFatalf = origLogFatalf
|
||||||
|
}()
|
||||||
|
|
||||||
|
monitor := New(timeout, datastore, context.Background())
|
||||||
|
monitor.Start()
|
||||||
|
<-time.After(2 * timeout)
|
||||||
|
|
||||||
|
assert.True(t, fataled, "monitor should been timeout and fatal")
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TarGzDir creates a tar.gz archive and returns it's path.
|
||||||
|
// abosolutePath should be an absolute path to a directory.
|
||||||
|
// Archive name will be <directoryName>.tar.gz and will be placed next to the directory.
|
||||||
|
func TarGzDir(absolutePath string) (string, error) {
|
||||||
|
targzPath := filepath.Join(absolutePath, fmt.Sprintf("%s.tar.gz", filepath.Base(absolutePath)))
|
||||||
|
outFile, err := os.Create(targzPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
zipWriter := gzip.NewWriter(outFile)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
tarWriter := tar.NewWriter(zipWriter)
|
||||||
|
defer tarWriter.Close()
|
||||||
|
|
||||||
|
err = filepath.Walk(absolutePath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == targzPath {
|
||||||
|
return nil // skip archive file
|
||||||
|
}
|
||||||
|
|
||||||
|
pathInArchive := filepath.Clean(strings.TrimPrefix(path, absolutePath))
|
||||||
|
if pathInArchive == "" {
|
||||||
|
return nil // skip root dir
|
||||||
|
}
|
||||||
|
|
||||||
|
return addToArchive(tarWriter, pathInArchive, path, info)
|
||||||
|
})
|
||||||
|
|
||||||
|
return targzPath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToArchive(tarWriter *tar.Writer, pathInArchive string, path string, info os.FileInfo) error {
|
||||||
|
header, err := tar.FileInfoHeader(info, info.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
header.Name = pathInArchive // use relative paths in archive
|
||||||
|
|
||||||
|
err = tarWriter.WriteHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(tarWriter, file)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractTarGz reads a .tar.gz archive from the reader and extracts it into outputDirPath directory
|
||||||
|
func ExtractTarGz(r io.Reader, outputDirPath string) error {
|
||||||
|
zipReader, err := gzip.NewReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer zipReader.Close()
|
||||||
|
|
||||||
|
tarReader := tar.NewReader(zipReader)
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tarReader.Next()
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
// skip, dir will be created with a file
|
||||||
|
case tar.TypeReg:
|
||||||
|
p := filepath.Clean(filepath.Join(outputDirPath, header.Name))
|
||||||
|
if err := os.MkdirAll(filepath.Dir(p), 0744); err != nil {
|
||||||
|
return fmt.Errorf("Failed to extract dir %s", filepath.Dir(p))
|
||||||
|
}
|
||||||
|
outFile, err := os.Create(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to create file %s", header.Name)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||||
|
return fmt.Errorf("Failed to extract file %s", header.Name)
|
||||||
|
}
|
||||||
|
outFile.Close()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Tar: uknown type: %v in %s",
|
||||||
|
header.Typeflag,
|
||||||
|
header.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listFiles(dir string) []string {
|
||||||
|
items := make([]string, 0)
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if path == dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
items = append(items, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_shouldCreateArhive(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "backup")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
content := []byte("content")
|
||||||
|
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||||
|
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||||
|
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||||
|
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
|
||||||
|
|
||||||
|
gzPath, err := TarGzDir(tmpdir)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||||
|
|
||||||
|
extractionDir, _ := os.MkdirTemp("", "extract")
|
||||||
|
defer os.RemoveAll(extractionDir)
|
||||||
|
|
||||||
|
cmd := exec.Command("tar", "-xzf", gzPath, "-C", extractionDir)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to extract archive: ", err)
|
||||||
|
}
|
||||||
|
extractedFiles := listFiles(extractionDir)
|
||||||
|
|
||||||
|
wasExtracted := func(p string) {
|
||||||
|
fullpath := path.Join(extractionDir, p)
|
||||||
|
assert.Contains(t, extractedFiles, fullpath)
|
||||||
|
copyContent, _ := ioutil.ReadFile(fullpath)
|
||||||
|
assert.Equal(t, content, copyContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
wasExtracted("outer")
|
||||||
|
wasExtracted("dir/inner")
|
||||||
|
wasExtracted("dir/.dotfile")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_shouldCreateArhiveXXXXX(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "backup")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
content := []byte("content")
|
||||||
|
ioutil.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||||
|
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||||
|
ioutil.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||||
|
ioutil.WriteFile(path.Join(tmpdir, "dir", "inner"), content, 0600)
|
||||||
|
|
||||||
|
gzPath, err := TarGzDir(tmpdir)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, filepath.Join(tmpdir, fmt.Sprintf("%s.tar.gz", filepath.Base(tmpdir))), gzPath)
|
||||||
|
|
||||||
|
extractionDir, _ := os.MkdirTemp("", "extract")
|
||||||
|
defer os.RemoveAll(extractionDir)
|
||||||
|
|
||||||
|
r, _ := os.Open(gzPath)
|
||||||
|
ExtractTarGz(r, extractionDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to extract archive: ", err)
|
||||||
|
}
|
||||||
|
extractedFiles := listFiles(extractionDir)
|
||||||
|
|
||||||
|
wasExtracted := func(p string) {
|
||||||
|
fullpath := path.Join(extractionDir, p)
|
||||||
|
assert.Contains(t, extractedFiles, fullpath)
|
||||||
|
copyContent, _ := ioutil.ReadFile(fullpath)
|
||||||
|
assert.Equal(t, content, copyContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
wasExtracted("outer")
|
||||||
|
wasExtracted("dir/inner")
|
||||||
|
wasExtracted("dir/.dotfile")
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/archive"
|
||||||
|
"github.com/portainer/portainer/api/crypto"
|
||||||
|
"github.com/portainer/portainer/api/http/offlinegate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rwxr__r__ fs.FileMode = 0744
|
||||||
|
|
||||||
|
var filesToBackup = []string{"compose", "config.json", "custom_templates", "edge_jobs", "edge_stacks", "extensions", "portainer.key", "portainer.pub", "tls"}
|
||||||
|
|
||||||
|
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
|
||||||
|
func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore portainer.DataStore, filestorePath string) (string, error) {
|
||||||
|
unlock := gate.Lock()
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
|
||||||
|
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
|
||||||
|
return "", errors.Wrap(err, "Failed to create backup dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := backupDb(backupDirPath, datastore); err != nil {
|
||||||
|
return "", errors.Wrap(err, "Failed to backup database")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, filename := range filesToBackup {
|
||||||
|
err := copyPath(filepath.Join(filestorePath, filename), backupDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "Failed to create backup file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
archivePath, err := archive.TarGzDir(backupDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "Failed to make an archive")
|
||||||
|
}
|
||||||
|
|
||||||
|
if password != "" {
|
||||||
|
archivePath, err = encrypt(archivePath, password)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "Failed to encrypt backup with the password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return archivePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupDb(backupDirPath string, datastore portainer.DataStore) error {
|
||||||
|
backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = datastore.BackupTo(backupWriter); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return backupWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func encrypt(path string, passphrase string) (string, error) {
|
||||||
|
in, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
outFileName := fmt.Sprintf("%s.encrypted", path)
|
||||||
|
out, err := os.Create(outFileName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = crypto.AesEncrypt(in, out, []byte(passphrase))
|
||||||
|
|
||||||
|
return outFileName, err
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyPath(path string, toDir string) error {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||||
|
// skip copy if file does not exist
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
destination := filepath.Join(toDir, info.Name())
|
||||||
|
return copyFile(path, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyDir(path, toDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDir(fromDir, toDir string) error {
|
||||||
|
cleanedSourcePath := filepath.Clean(fromDir)
|
||||||
|
parentDirectory := filepath.Dir(cleanedSourcePath)
|
||||||
|
err := filepath.Walk(cleanedSourcePath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
destination := filepath.Join(toDir, strings.TrimPrefix(path, parentDirectory))
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil // skip directory creations
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 { // entry is a symlink
|
||||||
|
return nil // don't copy symlinks
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyFile(path, destination)
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// copies regular a file from src to dst
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
from, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer from.Close()
|
||||||
|
|
||||||
|
// has to include 'execute' bit, otherwise fails. MkdirAll follows `mkdir -m` restrictions
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0744); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
to, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer to.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(to, from)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listFiles(dir string) []string {
|
||||||
|
items := make([]string, 0)
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if path == dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
items = append(items, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(t *testing.T, list []string, path string) {
|
||||||
|
assert.Contains(t, list, path)
|
||||||
|
copyContent, _ := ioutil.ReadFile(path)
|
||||||
|
assert.Equal(t, "content\n", string(copyContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_copyFile_returnsError_whenSourceDoesNotExist(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "backup")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
err := copyFile("does-not-exist", tmpdir)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_copyFile_shouldMakeAbackup(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "backup")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
content := []byte("content")
|
||||||
|
ioutil.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
|
||||||
|
|
||||||
|
err := copyFile(path.Join(tmpdir, "origin"), path.Join(tmpdir, "copy"))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
copyContent, _ := ioutil.ReadFile(path.Join(tmpdir, "copy"))
|
||||||
|
assert.Equal(t, content, copyContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_copyDir_shouldCopyAllFilesAndDirectories(t *testing.T) {
|
||||||
|
destination, _ := os.MkdirTemp("", "destination")
|
||||||
|
defer os.RemoveAll(destination)
|
||||||
|
err := copyDir("./test_assets/copy_test", destination)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
createdFiles := listFiles(destination)
|
||||||
|
|
||||||
|
contains(t, createdFiles, filepath.Join(destination, "copy_test", "outer"))
|
||||||
|
contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", ".dotfile"))
|
||||||
|
contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", "inner"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_backupPath_shouldSkipWhenNotExist(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "backup")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
err := copyPath("does-not-exists", tmpdir)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Empty(t, listFiles(tmpdir))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_backupPath_shouldCopyFile(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "backup")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
content := []byte("content")
|
||||||
|
ioutil.WriteFile(path.Join(tmpdir, "file"), content, 0600)
|
||||||
|
|
||||||
|
os.MkdirAll(path.Join(tmpdir, "backup"), 0700)
|
||||||
|
err := copyPath(path.Join(tmpdir, "file"), path.Join(tmpdir, "backup"))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
copyContent, err := ioutil.ReadFile(path.Join(tmpdir, "backup", "file"))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, content, copyContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_backupPath_shouldCopyDir(t *testing.T) {
|
||||||
|
destination, _ := os.MkdirTemp("", "destination")
|
||||||
|
defer os.RemoveAll(destination)
|
||||||
|
err := copyPath("./test_assets/copy_test", destination)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
createdFiles := listFiles(destination)
|
||||||
|
|
||||||
|
contains(t, createdFiles, filepath.Join(destination, "copy_test", "outer"))
|
||||||
|
contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", ".dotfile"))
|
||||||
|
contains(t, createdFiles, filepath.Join(destination, "copy_test", "dir", "inner"))
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/archive"
|
||||||
|
"github.com/portainer/portainer/api/crypto"
|
||||||
|
"github.com/portainer/portainer/api/http/offlinegate"
|
||||||
|
)
|
||||||
|
|
||||||
|
var filesToRestore = append(filesToBackup, "portainer.db")
|
||||||
|
|
||||||
|
// Restores system state from backup archive, will trigger system shutdown, when finished.
|
||||||
|
func RestoreArchive(archive io.Reader, password string, filestorePath string, gate *offlinegate.OfflineGate, datastore portainer.DataStore, shutdownTrigger context.CancelFunc) error {
|
||||||
|
var err error
|
||||||
|
if password != "" {
|
||||||
|
archive, err = decrypt(archive, password)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to decrypt the archive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restorePath := filepath.Join(filestorePath, "restore", time.Now().Format("20060102150405"))
|
||||||
|
defer os.RemoveAll(filepath.Dir(restorePath))
|
||||||
|
|
||||||
|
err = extractArchive(archive, restorePath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "cannot extract files from the archive. Please ensure the password is correct and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock := gate.Lock()
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
if err = datastore.Close(); err != nil {
|
||||||
|
return errors.Wrap(err, "Failed to stop db")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = restoreFiles(restorePath, filestorePath); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to restore the system state")
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdownTrigger()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt(r io.Reader, password string) (io.Reader, error) {
|
||||||
|
return crypto.AesDecrypt(r, []byte(password))
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractArchive(r io.Reader, destinationDirPath string) error {
|
||||||
|
return archive.ExtractTarGz(r, destinationDirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreFiles(srcDir string, destinationDir string) error {
|
||||||
|
for _, filename := range filesToRestore {
|
||||||
|
err := copyPath(filepath.Join(srcDir, filename), destinationDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -2,7 +2,7 @@ package customtemplate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing custom template data.
|
// Service represents a service for managing custom template data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
|
func (service *Service) CustomTemplates() ([]portainer.CustomTemplate, error) {
|
||||||
var customTemplates = make([]portainer.CustomTemplate, 0)
|
var customTemplates = make([]portainer.CustomTemplate, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -56,7 +56,7 @@ func (service *Service) CustomTemplate(ID portainer.CustomTemplateID) (*portaine
|
||||||
var customTemplate portainer.CustomTemplate
|
var customTemplate portainer.CustomTemplate
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &customTemplate)
|
err := internal.GetObject(service.connection, BucketName, identifier, &customTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -67,18 +67,18 @@ func (service *Service) CustomTemplate(ID portainer.CustomTemplateID) (*portaine
|
||||||
// UpdateCustomTemplate updates an custom template.
|
// UpdateCustomTemplate updates an custom template.
|
||||||
func (service *Service) UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error {
|
func (service *Service) UpdateCustomTemplate(ID portainer.CustomTemplateID, customTemplate *portainer.CustomTemplate) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, customTemplate)
|
return internal.UpdateObject(service.connection, BucketName, identifier, customTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCustomTemplate deletes an custom template.
|
// DeleteCustomTemplate deletes an custom template.
|
||||||
func (service *Service) DeleteCustomTemplate(ID portainer.CustomTemplateID) error {
|
func (service *Service) DeleteCustomTemplate(ID portainer.CustomTemplateID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateCustomTemplate assign an ID to a new custom template and saves it.
|
// CreateCustomTemplate assign an ID to a new custom template and saves it.
|
||||||
func (service *Service) CreateCustomTemplate(customTemplate *portainer.CustomTemplate) error {
|
func (service *Service) CreateCustomTemplate(customTemplate *portainer.CustomTemplate) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
data, err := internal.MarshalObject(customTemplate)
|
data, err := internal.MarshalObject(customTemplate)
|
||||||
|
@ -92,5 +92,5 @@ func (service *Service) CreateCustomTemplate(customTemplate *portainer.CustomTem
|
||||||
|
|
||||||
// GetNextIdentifier returns the next identifier for a custom template.
|
// GetNextIdentifier returns the next identifier for a custom template.
|
||||||
func (service *Service) GetNextIdentifier() int {
|
func (service *Service) GetNextIdentifier() int {
|
||||||
return internal.GetNextIdentifier(service.db, BucketName)
|
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
||||||
"github.com/portainer/portainer/api/bolt/errors"
|
"github.com/portainer/portainer/api/bolt/errors"
|
||||||
"github.com/portainer/portainer/api/bolt/extension"
|
"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/migrator"
|
||||||
"github.com/portainer/portainer/api/bolt/registry"
|
"github.com/portainer/portainer/api/bolt/registry"
|
||||||
"github.com/portainer/portainer/api/bolt/resourcecontrol"
|
"github.com/portainer/portainer/api/bolt/resourcecontrol"
|
||||||
|
@ -42,7 +44,7 @@ const (
|
||||||
// BoltDB as the storage system.
|
// BoltDB as the storage system.
|
||||||
type Store struct {
|
type Store struct {
|
||||||
path string
|
path string
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
isNew bool
|
isNew bool
|
||||||
fileService portainer.FileService
|
fileService portainer.FileService
|
||||||
CustomTemplateService *customtemplate.Service
|
CustomTemplateService *customtemplate.Service
|
||||||
|
@ -83,6 +85,7 @@ func NewStore(storePath string, fileService portainer.FileService) (*Store, erro
|
||||||
path: storePath,
|
path: storePath,
|
||||||
fileService: fileService,
|
fileService: fileService,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
|
connection: &internal.DbConnection{},
|
||||||
}
|
}
|
||||||
|
|
||||||
databasePath := path.Join(storePath, databaseFileName)
|
databasePath := path.Join(storePath, databaseFileName)
|
||||||
|
@ -105,15 +108,15 @@ func (store *Store) Open() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
store.db = db
|
store.connection.DB = db
|
||||||
|
|
||||||
return store.initServices()
|
return store.initServices()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the BoltDB database.
|
// Close closes the BoltDB database.
|
||||||
func (store *Store) Close() error {
|
func (store *Store) Close() error {
|
||||||
if store.db != nil {
|
if store.connection.DB != nil {
|
||||||
return store.db.Close()
|
return store.connection.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -134,8 +137,9 @@ func (store *Store) CheckCurrentEdition() error {
|
||||||
|
|
||||||
// MigrateData automatically migrate the data based on the DBVersion.
|
// 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.
|
// This process is only triggered on an existing database, not if the database was just created.
|
||||||
func (store *Store) MigrateData() error {
|
// if force is true, then migrate regardless.
|
||||||
if store.isNew {
|
func (store *Store) MigrateData(force bool) error {
|
||||||
|
if store.isNew && !force {
|
||||||
return store.VersionService.StoreDBVersion(portainer.DBVersion)
|
return store.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +152,7 @@ func (store *Store) MigrateData() error {
|
||||||
|
|
||||||
if version < portainer.DBVersion {
|
if version < portainer.DBVersion {
|
||||||
migratorParams := &migrator.Parameters{
|
migratorParams := &migrator.Parameters{
|
||||||
DB: store.db,
|
DB: store.connection.DB,
|
||||||
DatabaseVersion: version,
|
DatabaseVersion: version,
|
||||||
EndpointGroupService: store.EndpointGroupService,
|
EndpointGroupService: store.EndpointGroupService,
|
||||||
EndpointService: store.EndpointService,
|
EndpointService: store.EndpointService,
|
||||||
|
@ -180,238 +184,11 @@ func (store *Store) MigrateData() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) initServices() error {
|
// BackupTo backs up db to a provided writer.
|
||||||
authorizationsetService, err := role.NewService(store.db)
|
// It does hot backup and doesn't block other database reads and writes
|
||||||
if err != nil {
|
func (store *Store) BackupTo(w io.Writer) error {
|
||||||
|
return store.connection.View(func(tx *bolt.Tx) error {
|
||||||
|
_, err := tx.WriteTo(w)
|
||||||
return err
|
return err
|
||||||
}
|
})
|
||||||
store.RoleService = authorizationsetService
|
|
||||||
|
|
||||||
customTemplateService, err := customtemplate.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.CustomTemplateService = customTemplateService
|
|
||||||
|
|
||||||
dockerhubService, err := dockerhub.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.DockerHubService = dockerhubService
|
|
||||||
|
|
||||||
edgeStackService, err := edgestack.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.EdgeStackService = edgeStackService
|
|
||||||
|
|
||||||
edgeGroupService, err := edgegroup.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.EdgeGroupService = edgeGroupService
|
|
||||||
|
|
||||||
edgeJobService, err := edgejob.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.EdgeJobService = edgeJobService
|
|
||||||
|
|
||||||
endpointgroupService, err := endpointgroup.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.EndpointGroupService = endpointgroupService
|
|
||||||
|
|
||||||
endpointService, err := endpoint.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.EndpointService = endpointService
|
|
||||||
|
|
||||||
endpointRelationService, err := endpointrelation.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.EndpointRelationService = endpointRelationService
|
|
||||||
|
|
||||||
extensionService, err := extension.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.ExtensionService = extensionService
|
|
||||||
|
|
||||||
registryService, err := registry.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.RegistryService = registryService
|
|
||||||
|
|
||||||
resourcecontrolService, err := resourcecontrol.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.ResourceControlService = resourcecontrolService
|
|
||||||
|
|
||||||
settingsService, err := settings.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.SettingsService = settingsService
|
|
||||||
|
|
||||||
stackService, err := stack.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.StackService = stackService
|
|
||||||
|
|
||||||
tagService, err := tag.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.TagService = tagService
|
|
||||||
|
|
||||||
teammembershipService, err := teammembership.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.TeamMembershipService = teammembershipService
|
|
||||||
|
|
||||||
teamService, err := team.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.TeamService = teamService
|
|
||||||
|
|
||||||
tunnelServerService, err := tunnelserver.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.TunnelServerService = tunnelServerService
|
|
||||||
|
|
||||||
userService, err := user.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.UserService = userService
|
|
||||||
|
|
||||||
versionService, err := version.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.VersionService = versionService
|
|
||||||
|
|
||||||
webhookService, err := webhook.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.WebhookService = webhookService
|
|
||||||
|
|
||||||
scheduleService, err := schedule.NewService(store.db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store.ScheduleService = scheduleService
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomTemplate gives access to the CustomTemplate data management layer
|
|
||||||
func (store *Store) CustomTemplate() portainer.CustomTemplateService {
|
|
||||||
return store.CustomTemplateService
|
|
||||||
}
|
|
||||||
|
|
||||||
// DockerHub gives access to the DockerHub data management layer
|
|
||||||
func (store *Store) DockerHub() portainer.DockerHubService {
|
|
||||||
return store.DockerHubService
|
|
||||||
}
|
|
||||||
|
|
||||||
// EdgeGroup gives access to the EdgeGroup data management layer
|
|
||||||
func (store *Store) EdgeGroup() portainer.EdgeGroupService {
|
|
||||||
return store.EdgeGroupService
|
|
||||||
}
|
|
||||||
|
|
||||||
// EdgeJob gives access to the EdgeJob data management layer
|
|
||||||
func (store *Store) EdgeJob() portainer.EdgeJobService {
|
|
||||||
return store.EdgeJobService
|
|
||||||
}
|
|
||||||
|
|
||||||
// EdgeStack gives access to the EdgeStack data management layer
|
|
||||||
func (store *Store) EdgeStack() portainer.EdgeStackService {
|
|
||||||
return store.EdgeStackService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endpoint gives access to the Endpoint data management layer
|
|
||||||
func (store *Store) Endpoint() portainer.EndpointService {
|
|
||||||
return store.EndpointService
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndpointGroup gives access to the EndpointGroup data management layer
|
|
||||||
func (store *Store) EndpointGroup() portainer.EndpointGroupService {
|
|
||||||
return store.EndpointGroupService
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndpointRelation gives access to the EndpointRelation data management layer
|
|
||||||
func (store *Store) EndpointRelation() portainer.EndpointRelationService {
|
|
||||||
return store.EndpointRelationService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registry gives access to the Registry data management layer
|
|
||||||
func (store *Store) Registry() portainer.RegistryService {
|
|
||||||
return store.RegistryService
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceControl gives access to the ResourceControl data management layer
|
|
||||||
func (store *Store) ResourceControl() portainer.ResourceControlService {
|
|
||||||
return store.ResourceControlService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Role gives access to the Role data management layer
|
|
||||||
func (store *Store) Role() portainer.RoleService {
|
|
||||||
return store.RoleService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settings gives access to the Settings data management layer
|
|
||||||
func (store *Store) Settings() portainer.SettingsService {
|
|
||||||
return store.SettingsService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stack gives access to the Stack data management layer
|
|
||||||
func (store *Store) Stack() portainer.StackService {
|
|
||||||
return store.StackService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tag gives access to the Tag data management layer
|
|
||||||
func (store *Store) Tag() portainer.TagService {
|
|
||||||
return store.TagService
|
|
||||||
}
|
|
||||||
|
|
||||||
// TeamMembership gives access to the TeamMembership data management layer
|
|
||||||
func (store *Store) TeamMembership() portainer.TeamMembershipService {
|
|
||||||
return store.TeamMembershipService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Team gives access to the Team data management layer
|
|
||||||
func (store *Store) Team() portainer.TeamService {
|
|
||||||
return store.TeamService
|
|
||||||
}
|
|
||||||
|
|
||||||
// TunnelServer gives access to the TunnelServer data management layer
|
|
||||||
func (store *Store) TunnelServer() portainer.TunnelServerService {
|
|
||||||
return store.TunnelServerService
|
|
||||||
}
|
|
||||||
|
|
||||||
// User gives access to the User data management layer
|
|
||||||
func (store *Store) User() portainer.UserService {
|
|
||||||
return store.UserService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version gives access to the Version data management layer
|
|
||||||
func (store *Store) Version() portainer.VersionService {
|
|
||||||
return store.VersionService
|
|
||||||
}
|
|
||||||
|
|
||||||
// Webhook gives access to the Webhook data management layer
|
|
||||||
func (store *Store) Webhook() portainer.WebhookService {
|
|
||||||
return store.WebhookService
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package dockerhub
|
package dockerhub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -15,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing Dockerhub data.
|
// Service represents a service for managing Dockerhub data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) DockerHub() (*portainer.DockerHub, error) {
|
func (service *Service) DockerHub() (*portainer.DockerHub, error) {
|
||||||
var dockerhub portainer.DockerHub
|
var dockerhub portainer.DockerHub
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, []byte(dockerHubKey), &dockerhub)
|
err := internal.GetObject(service.connection, BucketName, []byte(dockerHubKey), &dockerhub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -44,5 +42,5 @@ func (service *Service) DockerHub() (*portainer.DockerHub, error) {
|
||||||
|
|
||||||
// UpdateDockerHub updates a DockerHub object.
|
// UpdateDockerHub updates a DockerHub object.
|
||||||
func (service *Service) UpdateDockerHub(dockerhub *portainer.DockerHub) error {
|
func (service *Service) UpdateDockerHub(dockerhub *portainer.DockerHub) error {
|
||||||
return internal.UpdateObject(service.db, BucketName, []byte(dockerHubKey), dockerhub)
|
return internal.UpdateObject(service.connection, BucketName, []byte(dockerHubKey), dockerhub)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package edgegroup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing Edge group data.
|
// Service represents a service for managing Edge group data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
|
func (service *Service) EdgeGroups() ([]portainer.EdgeGroup, error) {
|
||||||
var groups = make([]portainer.EdgeGroup, 0)
|
var groups = make([]portainer.EdgeGroup, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -56,7 +56,7 @@ func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGrou
|
||||||
var group portainer.EdgeGroup
|
var group portainer.EdgeGroup
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &group)
|
err := internal.GetObject(service.connection, BucketName, identifier, &group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -67,18 +67,18 @@ func (service *Service) EdgeGroup(ID portainer.EdgeGroupID) (*portainer.EdgeGrou
|
||||||
// UpdateEdgeGroup updates an Edge group.
|
// UpdateEdgeGroup updates an Edge group.
|
||||||
func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
|
func (service *Service) UpdateEdgeGroup(ID portainer.EdgeGroupID, group *portainer.EdgeGroup) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, group)
|
return internal.UpdateObject(service.connection, BucketName, identifier, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteEdgeGroup deletes an Edge group.
|
// DeleteEdgeGroup deletes an Edge group.
|
||||||
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
|
func (service *Service) DeleteEdgeGroup(ID portainer.EdgeGroupID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateEdgeGroup assign an ID to a new Edge group and saves it.
|
// CreateEdgeGroup assign an ID to a new Edge group and saves it.
|
||||||
func (service *Service) CreateEdgeGroup(group *portainer.EdgeGroup) error {
|
func (service *Service) CreateEdgeGroup(group *portainer.EdgeGroup) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
|
|
@ -2,7 +2,7 @@ package edgejob
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing edge jobs data.
|
// Service represents a service for managing edge jobs data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
|
func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) {
|
||||||
var edgeJobs = make([]portainer.EdgeJob, 0)
|
var edgeJobs = make([]portainer.EdgeJob, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -56,7 +56,7 @@ func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, err
|
||||||
var edgeJob portainer.EdgeJob
|
var edgeJob portainer.EdgeJob
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &edgeJob)
|
err := internal.GetObject(service.connection, BucketName, identifier, &edgeJob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ func (service *Service) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, err
|
||||||
|
|
||||||
// CreateEdgeJob creates a new Edge job
|
// CreateEdgeJob creates a new Edge job
|
||||||
func (service *Service) CreateEdgeJob(edgeJob *portainer.EdgeJob) error {
|
func (service *Service) CreateEdgeJob(edgeJob *portainer.EdgeJob) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
if edgeJob.ID == 0 {
|
if edgeJob.ID == 0 {
|
||||||
|
@ -86,16 +86,16 @@ func (service *Service) CreateEdgeJob(edgeJob *portainer.EdgeJob) error {
|
||||||
// UpdateEdgeJob updates an Edge job by ID
|
// UpdateEdgeJob updates an Edge job by ID
|
||||||
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
func (service *Service) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, edgeJob)
|
return internal.UpdateObject(service.connection, BucketName, identifier, edgeJob)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteEdgeJob deletes an Edge job
|
// DeleteEdgeJob deletes an Edge job
|
||||||
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
func (service *Service) DeleteEdgeJob(ID portainer.EdgeJobID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||||
func (service *Service) GetNextIdentifier() int {
|
func (service *Service) GetNextIdentifier() int {
|
||||||
return internal.GetNextIdentifier(service.db, BucketName)
|
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package edgestack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing Edge stack data.
|
// Service represents a service for managing Edge stack data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
|
func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) {
|
||||||
var stacks = make([]portainer.EdgeStack, 0)
|
var stacks = make([]portainer.EdgeStack, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -56,7 +56,7 @@ func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStac
|
||||||
var stack portainer.EdgeStack
|
var stack portainer.EdgeStack
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &stack)
|
err := internal.GetObject(service.connection, BucketName, identifier, &stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ func (service *Service) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStac
|
||||||
|
|
||||||
// CreateEdgeStack assign an ID to a new Edge stack and saves it.
|
// CreateEdgeStack assign an ID to a new Edge stack and saves it.
|
||||||
func (service *Service) CreateEdgeStack(edgeStack *portainer.EdgeStack) error {
|
func (service *Service) CreateEdgeStack(edgeStack *portainer.EdgeStack) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
if edgeStack.ID == 0 {
|
if edgeStack.ID == 0 {
|
||||||
|
@ -86,16 +86,16 @@ func (service *Service) CreateEdgeStack(edgeStack *portainer.EdgeStack) error {
|
||||||
// UpdateEdgeStack updates an Edge stack.
|
// UpdateEdgeStack updates an Edge stack.
|
||||||
func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
func (service *Service) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, edgeStack)
|
return internal.UpdateObject(service.connection, BucketName, identifier, edgeStack)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteEdgeStack deletes an Edge stack.
|
// DeleteEdgeStack deletes an Edge stack.
|
||||||
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
func (service *Service) DeleteEdgeStack(ID portainer.EdgeStackID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||||
func (service *Service) GetNextIdentifier() int {
|
func (service *Service) GetNextIdentifier() int {
|
||||||
return internal.GetNextIdentifier(service.db, BucketName)
|
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package endpoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
|
||||||
var endpoint portainer.Endpoint
|
var endpoint portainer.Endpoint
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &endpoint)
|
err := internal.GetObject(service.connection, BucketName, identifier, &endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -44,20 +44,20 @@ func (service *Service) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint,
|
||||||
// UpdateEndpoint updates an endpoint.
|
// UpdateEndpoint updates an endpoint.
|
||||||
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
func (service *Service) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, endpoint)
|
return internal.UpdateObject(service.connection, BucketName, identifier, endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteEndpoint deletes an endpoint.
|
// DeleteEndpoint deletes an endpoint.
|
||||||
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
|
func (service *Service) DeleteEndpoint(ID portainer.EndpointID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoints return an array containing all the endpoints.
|
// Endpoints return an array containing all the endpoints.
|
||||||
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
||||||
var endpoints = make([]portainer.Endpoint, 0)
|
var endpoints = make([]portainer.Endpoint, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -78,7 +78,7 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
|
||||||
|
|
||||||
// CreateEndpoint assign an ID to a new endpoint and saves it.
|
// CreateEndpoint assign an ID to a new endpoint and saves it.
|
||||||
func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
// We manually manage sequences for endpoints
|
// We manually manage sequences for endpoints
|
||||||
|
@ -98,12 +98,12 @@ func (service *Service) CreateEndpoint(endpoint *portainer.Endpoint) error {
|
||||||
|
|
||||||
// GetNextIdentifier returns the next identifier for an endpoint.
|
// GetNextIdentifier returns the next identifier for an endpoint.
|
||||||
func (service *Service) GetNextIdentifier() int {
|
func (service *Service) GetNextIdentifier() int {
|
||||||
return internal.GetNextIdentifier(service.db, BucketName)
|
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronize creates, updates and deletes 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 {
|
func (service *Service) Synchronize(toCreate, toUpdate, toDelete []*portainer.Endpoint) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
for _, endpoint := range toCreate {
|
for _, endpoint := range toCreate {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package endpointgroup
|
package endpointgroup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -14,18 +14,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.
|
||||||
var endpointGroup portainer.EndpointGroup
|
var endpointGroup portainer.EndpointGroup
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &endpointGroup)
|
err := internal.GetObject(service.connection, BucketName, identifier, &endpointGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -45,20 +45,20 @@ func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.
|
||||||
// UpdateEndpointGroup updates an endpoint group.
|
// UpdateEndpointGroup updates an endpoint group.
|
||||||
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
|
func (service *Service) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, endpointGroup)
|
return internal.UpdateObject(service.connection, BucketName, identifier, endpointGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteEndpointGroup deletes an endpoint group.
|
// DeleteEndpointGroup deletes an endpoint group.
|
||||||
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
|
func (service *Service) DeleteEndpointGroup(ID portainer.EndpointGroupID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndpointGroups return an array containing all the endpoint groups.
|
// EndpointGroups return an array containing all the endpoint groups.
|
||||||
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||||
var endpointGroups = make([]portainer.EndpointGroup, 0)
|
var endpointGroups = make([]portainer.EndpointGroup, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -79,7 +79,7 @@ func (service *Service) EndpointGroups() ([]portainer.EndpointGroup, error) {
|
||||||
|
|
||||||
// CreateEndpointGroup assign an ID to a new 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 {
|
func (service *Service) CreateEndpointGroup(endpointGroup *portainer.EndpointGroup) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
|
|
@ -13,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint relation data.
|
// Service represents a service for managing endpoint relation data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
|
||||||
var endpointRelation portainer.EndpointRelation
|
var endpointRelation portainer.EndpointRelation
|
||||||
identifier := internal.Itob(int(endpointID))
|
identifier := internal.Itob(int(endpointID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &endpointRelation)
|
err := internal.GetObject(service.connection, BucketName, identifier, &endpointRelation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ func (service *Service) EndpointRelation(endpointID portainer.EndpointID) (*port
|
||||||
|
|
||||||
// CreateEndpointRelation saves endpointRelation
|
// CreateEndpointRelation saves endpointRelation
|
||||||
func (service *Service) CreateEndpointRelation(endpointRelation *portainer.EndpointRelation) error {
|
func (service *Service) CreateEndpointRelation(endpointRelation *portainer.EndpointRelation) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
data, err := internal.MarshalObject(endpointRelation)
|
data, err := internal.MarshalObject(endpointRelation)
|
||||||
|
@ -58,11 +58,11 @@ func (service *Service) CreateEndpointRelation(endpointRelation *portainer.Endpo
|
||||||
// UpdateEndpointRelation updates an Endpoint relation object
|
// UpdateEndpointRelation updates an Endpoint relation object
|
||||||
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
|
func (service *Service) UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error {
|
||||||
identifier := internal.Itob(int(EndpointID))
|
identifier := internal.Itob(int(EndpointID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, endpointRelation)
|
return internal.UpdateObject(service.connection, BucketName, identifier, endpointRelation)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteEndpointRelation deletes an Endpoint relation object
|
// DeleteEndpointRelation deletes an Endpoint relation object
|
||||||
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
|
func (service *Service) DeleteEndpointRelation(EndpointID portainer.EndpointID) error {
|
||||||
identifier := internal.Itob(int(EndpointID))
|
identifier := internal.Itob(int(EndpointID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package extension
|
package extension
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -14,18 +14,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extensio
|
||||||
var extension portainer.Extension
|
var extension portainer.Extension
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &extension)
|
err := internal.GetObject(service.connection, BucketName, identifier, &extension)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ func (service *Service) Extension(ID portainer.ExtensionID) (*portainer.Extensio
|
||||||
func (service *Service) Extensions() ([]portainer.Extension, error) {
|
func (service *Service) Extensions() ([]portainer.Extension, error) {
|
||||||
var extensions = make([]portainer.Extension, 0)
|
var extensions = make([]portainer.Extension, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -67,7 +67,7 @@ func (service *Service) Extensions() ([]portainer.Extension, error) {
|
||||||
|
|
||||||
// Persist persists a extension inside the database.
|
// Persist persists a extension inside the database.
|
||||||
func (service *Service) Persist(extension *portainer.Extension) error {
|
func (service *Service) Persist(extension *portainer.Extension) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
data, err := internal.MarshalObject(extension)
|
data, err := internal.MarshalObject(extension)
|
||||||
|
@ -82,5 +82,5 @@ func (service *Service) Persist(extension *portainer.Extension) error {
|
||||||
// DeleteExtension deletes a Extension.
|
// DeleteExtension deletes a Extension.
|
||||||
func (service *Service) DeleteExtension(ID portainer.ExtensionID) error {
|
func (service *Service) DeleteExtension(ID portainer.ExtensionID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@ import (
|
||||||
"github.com/portainer/portainer/api/bolt/errors"
|
"github.com/portainer/portainer/api/bolt/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DbConnection struct {
|
||||||
|
*bolt.DB
|
||||||
|
}
|
||||||
|
|
||||||
// Itob returns an 8-byte big endian representation of v.
|
// Itob returns an 8-byte big endian representation of v.
|
||||||
// This function is typically used for encoding integer IDs to byte slices
|
// This function is typically used for encoding integer IDs to byte slices
|
||||||
// so that they can be used as BoltDB keys.
|
// so that they can be used as BoltDB keys.
|
||||||
|
@ -17,8 +21,8 @@ func Itob(v int) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBucket is a generic function used to create a bucket inside a bolt database.
|
// CreateBucket is a generic function used to create a bucket inside a bolt database.
|
||||||
func CreateBucket(db *bolt.DB, bucketName string) error {
|
func CreateBucket(connection *DbConnection, bucketName string) error {
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
return connection.Update(func(tx *bolt.Tx) error {
|
||||||
_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -28,10 +32,10 @@ func CreateBucket(db *bolt.DB, bucketName string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObject is a generic function used to retrieve an unmarshalled object from a bolt database.
|
// GetObject is a generic function used to retrieve an unmarshalled object from a bolt database.
|
||||||
func GetObject(db *bolt.DB, bucketName string, key []byte, object interface{}) error {
|
func GetObject(connection *DbConnection, bucketName string, key []byte, object interface{}) error {
|
||||||
var data []byte
|
var data []byte
|
||||||
|
|
||||||
err := db.View(func(tx *bolt.Tx) error {
|
err := connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(bucketName))
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
|
|
||||||
value := bucket.Get(key)
|
value := bucket.Get(key)
|
||||||
|
@ -52,8 +56,8 @@ func GetObject(db *bolt.DB, bucketName string, key []byte, object interface{}) e
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateObject is a generic function used to update an object inside a bolt database.
|
// UpdateObject is a generic function used to update an object inside a bolt database.
|
||||||
func UpdateObject(db *bolt.DB, bucketName string, key []byte, object interface{}) error {
|
func UpdateObject(connection *DbConnection, bucketName string, key []byte, object interface{}) error {
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
return connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(bucketName))
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
|
|
||||||
data, err := MarshalObject(object)
|
data, err := MarshalObject(object)
|
||||||
|
@ -71,18 +75,18 @@ func UpdateObject(db *bolt.DB, bucketName string, key []byte, object interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObject is a generic function used to delete an object inside a bolt database.
|
// DeleteObject is a generic function used to delete an object inside a bolt database.
|
||||||
func DeleteObject(db *bolt.DB, bucketName string, key []byte) error {
|
func DeleteObject(connection *DbConnection, bucketName string, key []byte) error {
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
return connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(bucketName))
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
return bucket.Delete(key)
|
return bucket.Delete(key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNextIdentifier is a generic function that returns the specified bucket identifier incremented by 1.
|
// GetNextIdentifier is a generic function that returns the specified bucket identifier incremented by 1.
|
||||||
func GetNextIdentifier(db *bolt.DB, bucketName string) int {
|
func GetNextIdentifier(connection *DbConnection, bucketName string) int {
|
||||||
var identifier int
|
var identifier int
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(bucketName))
|
bucket := tx.Bucket([]byte(bucketName))
|
||||||
id, err := bucket.NextSequence()
|
id, err := bucket.NextSequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -14,18 +14,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func (service *Service) Registry(ID portainer.RegistryID) (*portainer.Registry,
|
||||||
var registry portainer.Registry
|
var registry portainer.Registry
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, ®istry)
|
err := internal.GetObject(service.connection, BucketName, identifier, ®istry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ func (service *Service) Registry(ID portainer.RegistryID) (*portainer.Registry,
|
||||||
func (service *Service) Registries() ([]portainer.Registry, error) {
|
func (service *Service) Registries() ([]portainer.Registry, error) {
|
||||||
var registries = make([]portainer.Registry, 0)
|
var registries = make([]portainer.Registry, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -67,7 +67,7 @@ func (service *Service) Registries() ([]portainer.Registry, error) {
|
||||||
|
|
||||||
// CreateRegistry creates a new registry.
|
// CreateRegistry creates a new registry.
|
||||||
func (service *Service) CreateRegistry(registry *portainer.Registry) error {
|
func (service *Service) CreateRegistry(registry *portainer.Registry) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
@ -85,11 +85,11 @@ func (service *Service) CreateRegistry(registry *portainer.Registry) error {
|
||||||
// UpdateRegistry updates an registry.
|
// UpdateRegistry updates an registry.
|
||||||
func (service *Service) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
|
func (service *Service) UpdateRegistry(ID portainer.RegistryID, registry *portainer.Registry) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, registry)
|
return internal.UpdateObject(service.connection, BucketName, identifier, registry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRegistry deletes an registry.
|
// DeleteRegistry deletes an registry.
|
||||||
func (service *Service) DeleteRegistry(ID portainer.RegistryID) error {
|
func (service *Service) DeleteRegistry(ID portainer.RegistryID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package resourcecontrol
|
package resourcecontrol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -14,18 +14,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portai
|
||||||
var resourceControl portainer.ResourceControl
|
var resourceControl portainer.ResourceControl
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &resourceControl)
|
err := internal.GetObject(service.connection, BucketName, identifier, &resourceControl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func (service *Service) ResourceControl(ID portainer.ResourceControlID) (*portai
|
||||||
func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
func (service *Service) ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
||||||
var resourceControl *portainer.ResourceControl
|
var resourceControl *portainer.ResourceControl
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
|
||||||
func (service *Service) ResourceControls() ([]portainer.ResourceControl, error) {
|
func (service *Service) ResourceControls() ([]portainer.ResourceControl, error) {
|
||||||
var rcs = make([]portainer.ResourceControl, 0)
|
var rcs = make([]portainer.ResourceControl, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -103,7 +103,7 @@ func (service *Service) ResourceControls() ([]portainer.ResourceControl, error)
|
||||||
|
|
||||||
// CreateResourceControl creates a new ResourceControl object
|
// CreateResourceControl creates a new ResourceControl object
|
||||||
func (service *Service) CreateResourceControl(resourceControl *portainer.ResourceControl) error {
|
func (service *Service) CreateResourceControl(resourceControl *portainer.ResourceControl) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
@ -121,11 +121,11 @@ func (service *Service) CreateResourceControl(resourceControl *portainer.Resourc
|
||||||
// UpdateResourceControl saves a ResourceControl object.
|
// UpdateResourceControl saves a ResourceControl object.
|
||||||
func (service *Service) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
|
func (service *Service) UpdateResourceControl(ID portainer.ResourceControlID, resourceControl *portainer.ResourceControl) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, resourceControl)
|
return internal.UpdateObject(service.connection, BucketName, identifier, resourceControl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteResourceControl deletes a ResourceControl object by ID
|
// DeleteResourceControl deletes a ResourceControl object by ID
|
||||||
func (service *Service) DeleteResourceControl(ID portainer.ResourceControlID) error {
|
func (service *Service) DeleteResourceControl(ID portainer.ResourceControlID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package role
|
package role
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -14,18 +14,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func (service *Service) Role(ID portainer.RoleID) (*portainer.Role, error) {
|
||||||
var set portainer.Role
|
var set portainer.Role
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &set)
|
err := internal.GetObject(service.connection, BucketName, identifier, &set)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ func (service *Service) Role(ID portainer.RoleID) (*portainer.Role, error) {
|
||||||
func (service *Service) Roles() ([]portainer.Role, error) {
|
func (service *Service) Roles() ([]portainer.Role, error) {
|
||||||
var sets = make([]portainer.Role, 0)
|
var sets = make([]portainer.Role, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -67,7 +67,7 @@ func (service *Service) Roles() ([]portainer.Role, error) {
|
||||||
|
|
||||||
// CreateRole creates a new Role.
|
// CreateRole creates a new Role.
|
||||||
func (service *Service) CreateRole(role *portainer.Role) error {
|
func (service *Service) CreateRole(role *portainer.Role) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
@ -85,5 +85,5 @@ func (service *Service) CreateRole(role *portainer.Role) error {
|
||||||
// UpdateRole updates a role.
|
// UpdateRole updates a role.
|
||||||
func (service *Service) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
|
func (service *Service) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, role)
|
return internal.UpdateObject(service.connection, BucketName, identifier, role)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package schedule
|
package schedule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -14,18 +14,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing schedule data.
|
// Service represents a service for managing schedule data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func (service *Service) Schedule(ID portainer.ScheduleID) (*portainer.Schedule,
|
||||||
var schedule portainer.Schedule
|
var schedule portainer.Schedule
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &schedule)
|
err := internal.GetObject(service.connection, BucketName, identifier, &schedule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -45,20 +45,20 @@ func (service *Service) Schedule(ID portainer.ScheduleID) (*portainer.Schedule,
|
||||||
// UpdateSchedule updates a schedule.
|
// UpdateSchedule updates a schedule.
|
||||||
func (service *Service) UpdateSchedule(ID portainer.ScheduleID, schedule *portainer.Schedule) error {
|
func (service *Service) UpdateSchedule(ID portainer.ScheduleID, schedule *portainer.Schedule) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, schedule)
|
return internal.UpdateObject(service.connection, BucketName, identifier, schedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSchedule deletes a schedule.
|
// DeleteSchedule deletes a schedule.
|
||||||
func (service *Service) DeleteSchedule(ID portainer.ScheduleID) error {
|
func (service *Service) DeleteSchedule(ID portainer.ScheduleID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedules return a array containing all the schedules.
|
// Schedules return a array containing all the schedules.
|
||||||
func (service *Service) Schedules() ([]portainer.Schedule, error) {
|
func (service *Service) Schedules() ([]portainer.Schedule, error) {
|
||||||
var schedules = make([]portainer.Schedule, 0)
|
var schedules = make([]portainer.Schedule, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -82,7 +82,7 @@ func (service *Service) Schedules() ([]portainer.Schedule, error) {
|
||||||
func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portainer.Schedule, error) {
|
func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portainer.Schedule, error) {
|
||||||
var schedules = make([]portainer.Schedule, 0)
|
var schedules = make([]portainer.Schedule, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -105,7 +105,7 @@ func (service *Service) SchedulesByJobType(jobType portainer.JobType) ([]portain
|
||||||
|
|
||||||
// CreateSchedule assign an ID to a new schedule and saves it.
|
// CreateSchedule assign an ID to a new schedule and saves it.
|
||||||
func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
|
func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
// We manually manage sequences for schedules
|
// We manually manage sequences for schedules
|
||||||
|
@ -125,5 +125,5 @@ func (service *Service) CreateSchedule(schedule *portainer.Schedule) error {
|
||||||
|
|
||||||
// GetNextIdentifier returns the next identifier for a schedule.
|
// GetNextIdentifier returns the next identifier for a schedule.
|
||||||
func (service *Service) GetNextIdentifier() int {
|
func (service *Service) GetNextIdentifier() int {
|
||||||
return internal.GetNextIdentifier(service.db, BucketName)
|
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,263 @@
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/bolt/customtemplate"
|
||||||
|
"github.com/portainer/portainer/api/bolt/dockerhub"
|
||||||
|
"github.com/portainer/portainer/api/bolt/edgegroup"
|
||||||
|
"github.com/portainer/portainer/api/bolt/edgejob"
|
||||||
|
"github.com/portainer/portainer/api/bolt/edgestack"
|
||||||
|
"github.com/portainer/portainer/api/bolt/endpoint"
|
||||||
|
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
||||||
|
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
||||||
|
"github.com/portainer/portainer/api/bolt/extension"
|
||||||
|
"github.com/portainer/portainer/api/bolt/registry"
|
||||||
|
"github.com/portainer/portainer/api/bolt/resourcecontrol"
|
||||||
|
"github.com/portainer/portainer/api/bolt/role"
|
||||||
|
"github.com/portainer/portainer/api/bolt/schedule"
|
||||||
|
"github.com/portainer/portainer/api/bolt/settings"
|
||||||
|
"github.com/portainer/portainer/api/bolt/stack"
|
||||||
|
"github.com/portainer/portainer/api/bolt/tag"
|
||||||
|
"github.com/portainer/portainer/api/bolt/team"
|
||||||
|
"github.com/portainer/portainer/api/bolt/teammembership"
|
||||||
|
"github.com/portainer/portainer/api/bolt/tunnelserver"
|
||||||
|
"github.com/portainer/portainer/api/bolt/user"
|
||||||
|
"github.com/portainer/portainer/api/bolt/version"
|
||||||
|
"github.com/portainer/portainer/api/bolt/webhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (store *Store) initServices() error {
|
||||||
|
authorizationsetService, err := role.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.RoleService = authorizationsetService
|
||||||
|
|
||||||
|
customTemplateService, err := customtemplate.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.CustomTemplateService = customTemplateService
|
||||||
|
|
||||||
|
dockerhubService, err := dockerhub.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.DockerHubService = dockerhubService
|
||||||
|
|
||||||
|
edgeStackService, err := edgestack.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.EdgeStackService = edgeStackService
|
||||||
|
|
||||||
|
edgeGroupService, err := edgegroup.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.EdgeGroupService = edgeGroupService
|
||||||
|
|
||||||
|
edgeJobService, err := edgejob.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.EdgeJobService = edgeJobService
|
||||||
|
|
||||||
|
endpointgroupService, err := endpointgroup.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.EndpointGroupService = endpointgroupService
|
||||||
|
|
||||||
|
endpointService, err := endpoint.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.EndpointService = endpointService
|
||||||
|
|
||||||
|
endpointRelationService, err := endpointrelation.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.EndpointRelationService = endpointRelationService
|
||||||
|
|
||||||
|
extensionService, err := extension.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.ExtensionService = extensionService
|
||||||
|
|
||||||
|
registryService, err := registry.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.RegistryService = registryService
|
||||||
|
|
||||||
|
resourcecontrolService, err := resourcecontrol.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.ResourceControlService = resourcecontrolService
|
||||||
|
|
||||||
|
settingsService, err := settings.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.SettingsService = settingsService
|
||||||
|
|
||||||
|
stackService, err := stack.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.StackService = stackService
|
||||||
|
|
||||||
|
tagService, err := tag.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.TagService = tagService
|
||||||
|
|
||||||
|
teammembershipService, err := teammembership.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.TeamMembershipService = teammembershipService
|
||||||
|
|
||||||
|
teamService, err := team.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.TeamService = teamService
|
||||||
|
|
||||||
|
tunnelServerService, err := tunnelserver.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.TunnelServerService = tunnelServerService
|
||||||
|
|
||||||
|
userService, err := user.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.UserService = userService
|
||||||
|
|
||||||
|
versionService, err := version.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.VersionService = versionService
|
||||||
|
|
||||||
|
webhookService, err := webhook.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.WebhookService = webhookService
|
||||||
|
|
||||||
|
scheduleService, err := schedule.NewService(store.connection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.ScheduleService = scheduleService
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomTemplate gives access to the CustomTemplate data management layer
|
||||||
|
func (store *Store) CustomTemplate() portainer.CustomTemplateService {
|
||||||
|
return store.CustomTemplateService
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerHub gives access to the DockerHub data management layer
|
||||||
|
func (store *Store) DockerHub() portainer.DockerHubService {
|
||||||
|
return store.DockerHubService
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeGroup gives access to the EdgeGroup data management layer
|
||||||
|
func (store *Store) EdgeGroup() portainer.EdgeGroupService {
|
||||||
|
return store.EdgeGroupService
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeJob gives access to the EdgeJob data management layer
|
||||||
|
func (store *Store) EdgeJob() portainer.EdgeJobService {
|
||||||
|
return store.EdgeJobService
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeStack gives access to the EdgeStack data management layer
|
||||||
|
func (store *Store) EdgeStack() portainer.EdgeStackService {
|
||||||
|
return store.EdgeStackService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint gives access to the Endpoint data management layer
|
||||||
|
func (store *Store) Endpoint() portainer.EndpointService {
|
||||||
|
return store.EndpointService
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointGroup gives access to the EndpointGroup data management layer
|
||||||
|
func (store *Store) EndpointGroup() portainer.EndpointGroupService {
|
||||||
|
return store.EndpointGroupService
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointRelation gives access to the EndpointRelation data management layer
|
||||||
|
func (store *Store) EndpointRelation() portainer.EndpointRelationService {
|
||||||
|
return store.EndpointRelationService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registry gives access to the Registry data management layer
|
||||||
|
func (store *Store) Registry() portainer.RegistryService {
|
||||||
|
return store.RegistryService
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceControl gives access to the ResourceControl data management layer
|
||||||
|
func (store *Store) ResourceControl() portainer.ResourceControlService {
|
||||||
|
return store.ResourceControlService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role gives access to the Role data management layer
|
||||||
|
func (store *Store) Role() portainer.RoleService {
|
||||||
|
return store.RoleService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings gives access to the Settings data management layer
|
||||||
|
func (store *Store) Settings() portainer.SettingsService {
|
||||||
|
return store.SettingsService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack gives access to the Stack data management layer
|
||||||
|
func (store *Store) Stack() portainer.StackService {
|
||||||
|
return store.StackService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag gives access to the Tag data management layer
|
||||||
|
func (store *Store) Tag() portainer.TagService {
|
||||||
|
return store.TagService
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamMembership gives access to the TeamMembership data management layer
|
||||||
|
func (store *Store) TeamMembership() portainer.TeamMembershipService {
|
||||||
|
return store.TeamMembershipService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Team gives access to the Team data management layer
|
||||||
|
func (store *Store) Team() portainer.TeamService {
|
||||||
|
return store.TeamService
|
||||||
|
}
|
||||||
|
|
||||||
|
// TunnelServer gives access to the TunnelServer data management layer
|
||||||
|
func (store *Store) TunnelServer() portainer.TunnelServerService {
|
||||||
|
return store.TunnelServerService
|
||||||
|
}
|
||||||
|
|
||||||
|
// User gives access to the User data management layer
|
||||||
|
func (store *Store) User() portainer.UserService {
|
||||||
|
return store.UserService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version gives access to the Version data management layer
|
||||||
|
func (store *Store) Version() portainer.VersionService {
|
||||||
|
return store.VersionService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webhook gives access to the Webhook data management layer
|
||||||
|
func (store *Store) Webhook() portainer.WebhookService {
|
||||||
|
return store.WebhookService
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -15,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) Settings() (*portainer.Settings, error) {
|
func (service *Service) Settings() (*portainer.Settings, error) {
|
||||||
var settings portainer.Settings
|
var settings portainer.Settings
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, []byte(settingsKey), &settings)
|
err := internal.GetObject(service.connection, BucketName, []byte(settingsKey), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -44,5 +42,5 @@ func (service *Service) Settings() (*portainer.Settings, error) {
|
||||||
|
|
||||||
// UpdateSettings persists a Settings object.
|
// UpdateSettings persists a Settings object.
|
||||||
func (service *Service) UpdateSettings(settings *portainer.Settings) error {
|
func (service *Service) UpdateSettings(settings *portainer.Settings) error {
|
||||||
return internal.UpdateObject(service.db, BucketName, []byte(settingsKey), settings)
|
return internal.UpdateObject(service.connection, BucketName, []byte(settingsKey), settings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package stack
|
package stack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/errors"
|
"github.com/portainer/portainer/api/bolt/errors"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
|
@ -15,18 +15,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ func (service *Service) Stack(ID portainer.StackID) (*portainer.Stack, error) {
|
||||||
var stack portainer.Stack
|
var stack portainer.Stack
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &stack)
|
err := internal.GetObject(service.connection, BucketName, identifier, &stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ func (service *Service) Stack(ID portainer.StackID) (*portainer.Stack, error) {
|
||||||
func (service *Service) StackByName(name string) (*portainer.Stack, error) {
|
func (service *Service) StackByName(name string) (*portainer.Stack, error) {
|
||||||
var stack *portainer.Stack
|
var stack *portainer.Stack
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ func (service *Service) StackByName(name string) (*portainer.Stack, error) {
|
||||||
func (service *Service) Stacks() ([]portainer.Stack, error) {
|
func (service *Service) Stacks() ([]portainer.Stack, error) {
|
||||||
var stacks = make([]portainer.Stack, 0)
|
var stacks = make([]portainer.Stack, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -99,12 +99,12 @@ func (service *Service) Stacks() ([]portainer.Stack, error) {
|
||||||
|
|
||||||
// GetNextIdentifier returns the next identifier for a stack.
|
// GetNextIdentifier returns the next identifier for a stack.
|
||||||
func (service *Service) GetNextIdentifier() int {
|
func (service *Service) GetNextIdentifier() int {
|
||||||
return internal.GetNextIdentifier(service.db, BucketName)
|
return internal.GetNextIdentifier(service.connection, BucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStack creates a new stack.
|
// CreateStack creates a new stack.
|
||||||
func (service *Service) CreateStack(stack *portainer.Stack) error {
|
func (service *Service) CreateStack(stack *portainer.Stack) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
// We manually manage sequences for stacks
|
// We manually manage sequences for stacks
|
||||||
|
@ -125,11 +125,11 @@ func (service *Service) CreateStack(stack *portainer.Stack) error {
|
||||||
// UpdateStack updates a stack.
|
// UpdateStack updates a stack.
|
||||||
func (service *Service) UpdateStack(ID portainer.StackID, stack *portainer.Stack) error {
|
func (service *Service) UpdateStack(ID portainer.StackID, stack *portainer.Stack) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, stack)
|
return internal.UpdateObject(service.connection, BucketName, identifier, stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteStack deletes a stack.
|
// DeleteStack deletes a stack.
|
||||||
func (service *Service) DeleteStack(ID portainer.StackID) error {
|
func (service *Service) DeleteStack(ID portainer.StackID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package tag
|
package tag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -14,18 +14,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) Tags() ([]portainer.Tag, error) {
|
func (service *Service) Tags() ([]portainer.Tag, error) {
|
||||||
var tags = make([]portainer.Tag, 0)
|
var tags = make([]portainer.Tag, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -57,7 +57,7 @@ func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) {
|
||||||
var tag portainer.Tag
|
var tag portainer.Tag
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &tag)
|
err := internal.GetObject(service.connection, BucketName, identifier, &tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) {
|
||||||
|
|
||||||
// CreateTag creates a new tag.
|
// CreateTag creates a new tag.
|
||||||
func (service *Service) CreateTag(tag *portainer.Tag) error {
|
func (service *Service) CreateTag(tag *portainer.Tag) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
@ -85,11 +85,11 @@ func (service *Service) CreateTag(tag *portainer.Tag) error {
|
||||||
// UpdateTag updates a tag.
|
// UpdateTag updates a tag.
|
||||||
func (service *Service) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
|
func (service *Service) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, tag)
|
return internal.UpdateObject(service.connection, BucketName, identifier, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTag deletes a tag.
|
// DeleteTag deletes a tag.
|
||||||
func (service *Service) DeleteTag(ID portainer.TagID) error {
|
func (service *Service) DeleteTag(ID portainer.TagID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ func (service *Service) Team(ID portainer.TeamID) (*portainer.Team, error) {
|
||||||
var team portainer.Team
|
var team portainer.Team
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &team)
|
err := internal.GetObject(service.connection, BucketName, identifier, &team)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ func (service *Service) Team(ID portainer.TeamID) (*portainer.Team, error) {
|
||||||
func (service *Service) TeamByName(name string) (*portainer.Team, error) {
|
func (service *Service) TeamByName(name string) (*portainer.Team, error) {
|
||||||
var team *portainer.Team
|
var team *portainer.Team
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -80,7 +80,7 @@ func (service *Service) TeamByName(name string) (*portainer.Team, error) {
|
||||||
func (service *Service) Teams() ([]portainer.Team, error) {
|
func (service *Service) Teams() ([]portainer.Team, error) {
|
||||||
var teams = make([]portainer.Team, 0)
|
var teams = make([]portainer.Team, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -102,12 +102,12 @@ func (service *Service) Teams() ([]portainer.Team, error) {
|
||||||
// UpdateTeam saves a Team.
|
// UpdateTeam saves a Team.
|
||||||
func (service *Service) UpdateTeam(ID portainer.TeamID, team *portainer.Team) error {
|
func (service *Service) UpdateTeam(ID portainer.TeamID, team *portainer.Team) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, team)
|
return internal.UpdateObject(service.connection, BucketName, identifier, team)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTeam creates a new Team.
|
// CreateTeam creates a new Team.
|
||||||
func (service *Service) CreateTeam(team *portainer.Team) error {
|
func (service *Service) CreateTeam(team *portainer.Team) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
@ -125,5 +125,5 @@ func (service *Service) CreateTeam(team *portainer.Team) error {
|
||||||
// DeleteTeam deletes a Team.
|
// DeleteTeam deletes a Team.
|
||||||
func (service *Service) DeleteTeam(ID portainer.TeamID) error {
|
func (service *Service) DeleteTeam(ID portainer.TeamID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package teammembership
|
package teammembership
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -14,18 +14,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func (service *Service) TeamMembership(ID portainer.TeamMembershipID) (*portaine
|
||||||
var membership portainer.TeamMembership
|
var membership portainer.TeamMembership
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &membership)
|
err := internal.GetObject(service.connection, BucketName, identifier, &membership)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ func (service *Service) TeamMembership(ID portainer.TeamMembershipID) (*portaine
|
||||||
func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
|
func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
|
||||||
var memberships = make([]portainer.TeamMembership, 0)
|
var memberships = make([]portainer.TeamMembership, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -69,7 +69,7 @@ func (service *Service) TeamMemberships() ([]portainer.TeamMembership, error) {
|
||||||
func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error) {
|
func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]portainer.TeamMembership, error) {
|
||||||
var memberships = make([]portainer.TeamMembership, 0)
|
var memberships = make([]portainer.TeamMembership, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -95,7 +95,7 @@ func (service *Service) TeamMembershipsByUserID(userID portainer.UserID) ([]port
|
||||||
func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error) {
|
func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]portainer.TeamMembership, error) {
|
||||||
var memberships = make([]portainer.TeamMembership, 0)
|
var memberships = make([]portainer.TeamMembership, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -120,12 +120,12 @@ func (service *Service) TeamMembershipsByTeamID(teamID portainer.TeamID) ([]port
|
||||||
// UpdateTeamMembership saves a TeamMembership object.
|
// UpdateTeamMembership saves a TeamMembership object.
|
||||||
func (service *Service) UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error {
|
func (service *Service) UpdateTeamMembership(ID portainer.TeamMembershipID, membership *portainer.TeamMembership) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, membership)
|
return internal.UpdateObject(service.connection, BucketName, identifier, membership)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTeamMembership creates a new TeamMembership object.
|
// CreateTeamMembership creates a new TeamMembership object.
|
||||||
func (service *Service) CreateTeamMembership(membership *portainer.TeamMembership) error {
|
func (service *Service) CreateTeamMembership(membership *portainer.TeamMembership) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
@ -143,12 +143,12 @@ func (service *Service) CreateTeamMembership(membership *portainer.TeamMembershi
|
||||||
// DeleteTeamMembership deletes a TeamMembership object.
|
// DeleteTeamMembership deletes a TeamMembership object.
|
||||||
func (service *Service) DeleteTeamMembership(ID portainer.TeamMembershipID) error {
|
func (service *Service) DeleteTeamMembership(ID portainer.TeamMembershipID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTeamMembershipByUserID deletes all the TeamMembership object associated to a UserID.
|
// DeleteTeamMembershipByUserID deletes all the TeamMembership object associated to a UserID.
|
||||||
func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) error {
|
func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -173,7 +173,7 @@ func (service *Service) DeleteTeamMembershipByUserID(userID portainer.UserID) er
|
||||||
|
|
||||||
// DeleteTeamMembershipByTeamID deletes all the TeamMembership object associated to a TeamID.
|
// DeleteTeamMembershipByTeamID deletes all the TeamMembership object associated to a TeamID.
|
||||||
func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error {
|
func (service *Service) DeleteTeamMembershipByTeamID(teamID portainer.TeamID) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package tunnelserver
|
package tunnelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -15,18 +13,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +32,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) Info() (*portainer.TunnelServerInfo, error) {
|
func (service *Service) Info() (*portainer.TunnelServerInfo, error) {
|
||||||
var info portainer.TunnelServerInfo
|
var info portainer.TunnelServerInfo
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, []byte(infoKey), &info)
|
err := internal.GetObject(service.connection, BucketName, []byte(infoKey), &info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -44,5 +42,5 @@ func (service *Service) Info() (*portainer.TunnelServerInfo, error) {
|
||||||
|
|
||||||
// UpdateInfo persists a TunnelServerInfo object.
|
// UpdateInfo persists a TunnelServerInfo object.
|
||||||
func (service *Service) UpdateInfo(settings *portainer.TunnelServerInfo) error {
|
func (service *Service) UpdateInfo(settings *portainer.TunnelServerInfo) error {
|
||||||
return internal.UpdateObject(service.db, BucketName, []byte(infoKey), settings)
|
return internal.UpdateObject(service.connection, BucketName, []byte(infoKey), settings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing endpoint data.
|
// Service represents a service for managing endpoint data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ func (service *Service) User(ID portainer.UserID) (*portainer.User, error) {
|
||||||
var user portainer.User
|
var user portainer.User
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &user)
|
err := internal.GetObject(service.connection, BucketName, identifier, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
|
||||||
|
|
||||||
username = strings.ToLower(username)
|
username = strings.ToLower(username)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
|
||||||
func (service *Service) Users() ([]portainer.User, error) {
|
func (service *Service) Users() ([]portainer.User, error) {
|
||||||
var users = make([]portainer.User, 0)
|
var users = make([]portainer.User, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -103,7 +103,7 @@ func (service *Service) Users() ([]portainer.User, error) {
|
||||||
// UsersByRole return an array containing all the users with the specified role.
|
// UsersByRole return an array containing all the users with the specified role.
|
||||||
func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
|
func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
|
||||||
var users = make([]portainer.User, 0)
|
var users = make([]portainer.User, 0)
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -128,12 +128,12 @@ func (service *Service) UsersByRole(role portainer.UserRole) ([]portainer.User,
|
||||||
func (service *Service) UpdateUser(ID portainer.UserID, user *portainer.User) error {
|
func (service *Service) UpdateUser(ID portainer.UserID, user *portainer.User) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
user.Username = strings.ToLower(user.Username)
|
user.Username = strings.ToLower(user.Username)
|
||||||
return internal.UpdateObject(service.db, BucketName, identifier, user)
|
return internal.UpdateObject(service.connection, BucketName, identifier, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a new user.
|
// CreateUser creates a new user.
|
||||||
func (service *Service) CreateUser(user *portainer.User) error {
|
func (service *Service) CreateUser(user *portainer.User) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
@ -152,5 +152,5 @@ func (service *Service) CreateUser(user *portainer.User) error {
|
||||||
// DeleteUser deletes a user.
|
// DeleteUser deletes a user.
|
||||||
func (service *Service) DeleteUser(ID portainer.UserID) error {
|
func (service *Service) DeleteUser(ID portainer.UserID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,18 +19,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service to manage stored versions.
|
// Service represents a service to manage stored versions.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) DBVersion() (int, error) {
|
func (service *Service) DBVersion() (int, error) {
|
||||||
var data []byte
|
var data []byte
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
value := bucket.Get([]byte(versionKey))
|
value := bucket.Get([]byte(versionKey))
|
||||||
|
@ -75,7 +75,7 @@ func (service *Service) Edition() (portainer.SoftwareEdition, error) {
|
||||||
|
|
||||||
// StoreDBVersion store the database version.
|
// StoreDBVersion store the database version.
|
||||||
func (service *Service) StoreDBVersion(version int) error {
|
func (service *Service) StoreDBVersion(version int) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
data := []byte(strconv.Itoa(version))
|
data := []byte(strconv.Itoa(version))
|
||||||
|
@ -87,7 +87,7 @@ func (service *Service) StoreDBVersion(version int) error {
|
||||||
func (service *Service) InstanceID() (string, error) {
|
func (service *Service) InstanceID() (string, error) {
|
||||||
var data []byte
|
var data []byte
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
value := bucket.Get([]byte(instanceKey))
|
value := bucket.Get([]byte(instanceKey))
|
||||||
|
@ -109,7 +109,7 @@ func (service *Service) InstanceID() (string, error) {
|
||||||
|
|
||||||
// StoreInstanceID store the instance ID.
|
// StoreInstanceID store the instance ID.
|
||||||
func (service *Service) StoreInstanceID(ID string) error {
|
func (service *Service) StoreInstanceID(ID string) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
data := []byte(ID)
|
data := []byte(ID)
|
||||||
|
@ -120,7 +120,7 @@ func (service *Service) StoreInstanceID(ID string) error {
|
||||||
func (service *Service) getKey(key string) ([]byte, error) {
|
func (service *Service) getKey(key string) ([]byte, error) {
|
||||||
var data []byte
|
var data []byte
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
value := bucket.Get([]byte(key))
|
value := bucket.Get([]byte(key))
|
||||||
|
@ -142,7 +142,7 @@ func (service *Service) getKey(key string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *Service) setKey(key string, value string) error {
|
func (service *Service) setKey(key string, value string) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
data := []byte(value)
|
data := []byte(value)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/errors"
|
"github.com/portainer/portainer/api/bolt/errors"
|
||||||
"github.com/portainer/portainer/api/bolt/internal"
|
"github.com/portainer/portainer/api/bolt/internal"
|
||||||
|
|
||||||
|
@ -15,18 +15,18 @@ const (
|
||||||
|
|
||||||
// Service represents a service for managing webhook data.
|
// Service represents a service for managing webhook data.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *bolt.DB
|
connection *internal.DbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service.
|
// NewService creates a new instance of a service.
|
||||||
func NewService(db *bolt.DB) (*Service, error) {
|
func NewService(connection *internal.DbConnection) (*Service, error) {
|
||||||
err := internal.CreateBucket(db, BucketName)
|
err := internal.CreateBucket(connection, BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
connection: connection,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func NewService(db *bolt.DB) (*Service, error) {
|
||||||
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
|
func (service *Service) Webhooks() ([]portainer.Webhook, error) {
|
||||||
var webhooks = make([]portainer.Webhook, 0)
|
var webhooks = make([]portainer.Webhook, 0)
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
@ -58,7 +58,7 @@ func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, err
|
||||||
var webhook portainer.Webhook
|
var webhook portainer.Webhook
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
|
|
||||||
err := internal.GetObject(service.db, BucketName, identifier, &webhook)
|
err := internal.GetObject(service.connection, BucketName, identifier, &webhook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, err
|
||||||
func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) {
|
func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) {
|
||||||
var webhook *portainer.Webhook
|
var webhook *portainer.Webhook
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, erro
|
||||||
func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) {
|
func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) {
|
||||||
var webhook *portainer.Webhook
|
var webhook *portainer.Webhook
|
||||||
|
|
||||||
err := service.db.View(func(tx *bolt.Tx) error {
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
cursor := bucket.Cursor()
|
cursor := bucket.Cursor()
|
||||||
|
|
||||||
|
@ -131,12 +131,12 @@ func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error)
|
||||||
// DeleteWebhook deletes a webhook.
|
// DeleteWebhook deletes a webhook.
|
||||||
func (service *Service) DeleteWebhook(ID portainer.WebhookID) error {
|
func (service *Service) DeleteWebhook(ID portainer.WebhookID) error {
|
||||||
identifier := internal.Itob(int(ID))
|
identifier := internal.Itob(int(ID))
|
||||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
return internal.DeleteObject(service.connection, BucketName, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateWebhook assign an ID to a new webhook and saves it.
|
// CreateWebhook assign an ID to a new webhook and saves it.
|
||||||
func (service *Service) CreateWebhook(webhook *portainer.Webhook) error {
|
func (service *Service) CreateWebhook(webhook *portainer.Webhook) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) error {
|
return service.connection.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(BucketName))
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
|
||||||
id, _ := bucket.NextSequence()
|
id, _ := bucket.NextSequence()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package chisel
|
package chisel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -9,7 +10,7 @@ import (
|
||||||
"github.com/dchest/uniuri"
|
"github.com/dchest/uniuri"
|
||||||
chserver "github.com/jpillora/chisel/server"
|
chserver "github.com/jpillora/chisel/server"
|
||||||
cmap "github.com/orcaman/concurrent-map"
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/errors"
|
"github.com/portainer/portainer/api/bolt/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,13 +30,15 @@ type Service struct {
|
||||||
dataStore portainer.DataStore
|
dataStore portainer.DataStore
|
||||||
snapshotService portainer.SnapshotService
|
snapshotService portainer.SnapshotService
|
||||||
chiselServer *chserver.Server
|
chiselServer *chserver.Server
|
||||||
|
shutdownCtx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a pointer to a new instance of Service
|
// NewService returns a pointer to a new instance of Service
|
||||||
func NewService(dataStore portainer.DataStore) *Service {
|
func NewService(dataStore portainer.DataStore, shutdownCtx context.Context) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
tunnelDetailsMap: cmap.New(),
|
tunnelDetailsMap: cmap.New(),
|
||||||
dataStore: dataStore,
|
dataStore: dataStore,
|
||||||
|
shutdownCtx: shutdownCtx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +86,11 @@ func (service *Service) StartTunnelServer(addr, port string, snapshotService por
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopTunnelServer stops tunnel http server
|
||||||
|
func (service *Service) StopTunnelServer() error {
|
||||||
|
return service.chiselServer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (service *Service) retrievePrivateKeySeed() (string, error) {
|
func (service *Service) retrievePrivateKeySeed() (string, error) {
|
||||||
var serverInfo *portainer.TunnelServerInfo
|
var serverInfo *portainer.TunnelServerInfo
|
||||||
|
|
||||||
|
@ -108,13 +116,16 @@ func (service *Service) retrievePrivateKeySeed() (string, error) {
|
||||||
func (service *Service) startTunnelVerificationLoop() {
|
func (service *Service) startTunnelVerificationLoop() {
|
||||||
log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
|
log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
|
||||||
ticker := time.NewTicker(tunnelCleanupInterval)
|
ticker := time.NewTicker(tunnelCleanupInterval)
|
||||||
stopSignal := make(chan struct{})
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
service.checkTunnels()
|
service.checkTunnels()
|
||||||
case <-stopSignal:
|
case <-service.shutdownCtx.Done():
|
||||||
|
log.Println("[DEBUG] Shutting down tunnel service")
|
||||||
|
if err := service.StopTunnelServer(); err != nil {
|
||||||
|
log.Printf("Stopped tunnel service: %s", err)
|
||||||
|
}
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt"
|
"github.com/portainer/portainer/api/bolt"
|
||||||
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/portainer/portainer/api/http/client"
|
"github.com/portainer/portainer/api/http/client"
|
||||||
"github.com/portainer/portainer/api/http/proxy"
|
"github.com/portainer/portainer/api/http/proxy"
|
||||||
kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||||
|
"github.com/portainer/portainer/api/internal/edge"
|
||||||
"github.com/portainer/portainer/api/internal/snapshot"
|
"github.com/portainer/portainer/api/internal/snapshot"
|
||||||
"github.com/portainer/portainer/api/jwt"
|
"github.com/portainer/portainer/api/jwt"
|
||||||
"github.com/portainer/portainer/api/kubernetes"
|
"github.com/portainer/portainer/api/kubernetes"
|
||||||
|
@ -67,7 +68,7 @@ func initDataStore(dataStorePath string, fileService portainer.FileService) port
|
||||||
log.Fatalf("failed initializing data store: %v", err)
|
log.Fatalf("failed initializing data store: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.MigrateData()
|
err = store.MigrateData(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed migration: %v", err)
|
log.Fatalf("failed migration: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -136,11 +137,11 @@ func initKubernetesClientFactory(signatureService portainer.DigitalSignatureServ
|
||||||
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID)
|
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSnapshotService(snapshotInterval string, dataStore portainer.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory) (portainer.SnapshotService, error) {
|
func initSnapshotService(snapshotInterval string, dataStore portainer.DataStore, dockerClientFactory *docker.ClientFactory, kubernetesClientFactory *kubecli.ClientFactory, shutdownCtx context.Context) (portainer.SnapshotService, error) {
|
||||||
dockerSnapshotter := docker.NewSnapshotter(dockerClientFactory)
|
dockerSnapshotter := docker.NewSnapshotter(dockerClientFactory)
|
||||||
kubernetesSnapshotter := kubernetes.NewSnapshotter(kubernetesClientFactory)
|
kubernetesSnapshotter := kubernetes.NewSnapshotter(kubernetesClientFactory)
|
||||||
|
|
||||||
snapshotService, err := snapshot.NewService(snapshotInterval, dataStore, dockerSnapshotter, kubernetesSnapshotter)
|
snapshotService, err := snapshot.NewService(snapshotInterval, dataStore, dockerSnapshotter, kubernetesSnapshotter, shutdownCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -148,21 +149,6 @@ func initSnapshotService(snapshotInterval string, dataStore portainer.DataStore,
|
||||||
return snapshotService, nil
|
return snapshotService, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEdgeJobsFromDatabase(dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService) error {
|
|
||||||
edgeJobs, err := dataStore.EdgeJob().EdgeJobs()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, edgeJob := range edgeJobs {
|
|
||||||
for endpointID := range edgeJob.Endpoints {
|
|
||||||
reverseTunnelService.AddEdgeJob(endpointID, &edgeJob)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initStatus(flags *portainer.CLIFlags) *portainer.Status {
|
func initStatus(flags *portainer.CLIFlags) *portainer.Status {
|
||||||
return &portainer.Status{
|
return &portainer.Status{
|
||||||
Version: portainer.APIVersion,
|
Version: portainer.APIVersion,
|
||||||
|
@ -353,28 +339,12 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore portainer.DataStore, snap
|
||||||
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
|
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func terminateIfNoAdminCreated(dataStore portainer.DataStore) {
|
func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||||
timer1 := time.NewTimer(5 * time.Minute)
|
shutdownCtx, shutdownTrigger := context.WithCancel(context.Background())
|
||||||
<-timer1.C
|
|
||||||
|
|
||||||
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed getting admin user: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(users) == 0 {
|
|
||||||
log.Fatal("No administrator account was created after 5 min. Shutting down the Portainer instance for security reasons.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flags := initCLI()
|
|
||||||
|
|
||||||
fileService := initFileService(*flags.Data)
|
fileService := initFileService(*flags.Data)
|
||||||
|
|
||||||
dataStore := initDataStore(*flags.Data, fileService)
|
dataStore := initDataStore(*flags.Data, fileService)
|
||||||
defer dataStore.Close()
|
|
||||||
|
|
||||||
if err := dataStore.CheckCurrentEdition(); err != nil {
|
if err := dataStore.CheckCurrentEdition(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -400,7 +370,7 @@ func main() {
|
||||||
log.Fatalf("failed initializing key pai: %v", err)
|
log.Fatalf("failed initializing key pai: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reverseTunnelService := chisel.NewService(dataStore)
|
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
|
||||||
|
|
||||||
instanceID, err := dataStore.Version().InstanceID()
|
instanceID, err := dataStore.Version().InstanceID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -410,7 +380,7 @@ func main() {
|
||||||
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
|
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
|
||||||
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID)
|
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID)
|
||||||
|
|
||||||
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory)
|
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing snapshot service: %v", err)
|
log.Fatalf("failed initializing snapshot service: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -434,7 +404,7 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = loadEdgeJobsFromDatabase(dataStore, reverseTunnelService)
|
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed loading edge jobs from database: %v", err)
|
log.Fatalf("failed loading edge jobs from database: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -482,14 +452,12 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go terminateIfNoAdminCreated(dataStore)
|
|
||||||
|
|
||||||
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
|
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed starting tunnel server: %v", err)
|
log.Fatalf("failed starting license service: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var server portainer.Server = &http.Server{
|
return &http.Server{
|
||||||
ReverseTunnelService: reverseTunnelService,
|
ReverseTunnelService: reverseTunnelService,
|
||||||
Status: applicationStatus,
|
Status: applicationStatus,
|
||||||
BindAddress: *flags.Addr,
|
BindAddress: *flags.Addr,
|
||||||
|
@ -513,11 +481,18 @@ func main() {
|
||||||
SSLKey: *flags.SSLKey,
|
SSLKey: *flags.SSLKey,
|
||||||
DockerClientFactory: dockerClientFactory,
|
DockerClientFactory: dockerClientFactory,
|
||||||
KubernetesClientFactory: kubernetesClientFactory,
|
KubernetesClientFactory: kubernetesClientFactory,
|
||||||
}
|
ShutdownCtx: shutdownCtx,
|
||||||
|
ShutdownTrigger: shutdownTrigger,
|
||||||
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
|
}
|
||||||
err = server.Start()
|
}
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed starting server: %v", err)
|
func main() {
|
||||||
|
flags := initCLI()
|
||||||
|
|
||||||
|
for {
|
||||||
|
server := buildServer(flags)
|
||||||
|
log.Printf("Starting Portainer %s on %s\n", portainer.APIVersion, *flags.Addr)
|
||||||
|
err := server.Start()
|
||||||
|
log.Printf("Http server exited: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: has to go with what is considered to be a simplistic in that it omits any
|
||||||
|
// authentication of the encrypted data.
|
||||||
|
// Person with better knowledge is welcomed to improve it.
|
||||||
|
// sourced from https://golang.org/src/crypto/cipher/example_test.go
|
||||||
|
|
||||||
|
var emptySalt []byte = make([]byte, 0, 0)
|
||||||
|
|
||||||
|
// AesEncrypt reads from input, encrypts with AES-256 and writes to the output.
|
||||||
|
// passphrase is used to generate an encryption key.
|
||||||
|
func AesEncrypt(input io.Reader, output io.Writer, passphrase []byte) error {
|
||||||
|
// making a 32 bytes key that would correspond to AES-256
|
||||||
|
// don't necessarily need a salt, so just kept in empty
|
||||||
|
key, err := scrypt.Key(passphrase, emptySalt, 32768, 8, 1, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the key is unique for each ciphertext, then it's ok to use a zero
|
||||||
|
// IV.
|
||||||
|
var iv [aes.BlockSize]byte
|
||||||
|
stream := cipher.NewOFB(block, iv[:])
|
||||||
|
|
||||||
|
writer := &cipher.StreamWriter{S: stream, W: output}
|
||||||
|
// Copy the input to the output, encrypting as we go.
|
||||||
|
if _, err := io.Copy(writer, input); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AesDecrypt reads from input, decrypts with AES-256 and returns the reader to a read decrypted content from.
|
||||||
|
// passphrase is used to generate an encryption key.
|
||||||
|
func AesDecrypt(input io.Reader, passphrase []byte) (io.Reader, error) {
|
||||||
|
// making a 32 bytes key that would correspond to AES-256
|
||||||
|
// don't necessarily need a salt, so just kept in empty
|
||||||
|
key, err := scrypt.Key(passphrase, emptySalt, 32768, 8, 1, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the key is unique for each ciphertext, then it's ok to use a zero
|
||||||
|
// IV.
|
||||||
|
var iv [aes.BlockSize]byte
|
||||||
|
stream := cipher.NewOFB(block, iv[:])
|
||||||
|
|
||||||
|
reader := &cipher.StreamReader{S: stream, R: input}
|
||||||
|
|
||||||
|
return reader, nil
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "encrypt")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
var (
|
||||||
|
originFilePath = filepath.Join(tmpdir, "origin")
|
||||||
|
encryptedFilePath = filepath.Join(tmpdir, "encrypted")
|
||||||
|
decryptedFilePath = filepath.Join(tmpdir, "decrypted")
|
||||||
|
)
|
||||||
|
|
||||||
|
content := []byte("content")
|
||||||
|
ioutil.WriteFile(originFilePath, content, 0600)
|
||||||
|
|
||||||
|
originFile, _ := os.Open(originFilePath)
|
||||||
|
defer originFile.Close()
|
||||||
|
|
||||||
|
encryptedFileWriter, _ := os.Create(encryptedFilePath)
|
||||||
|
defer encryptedFileWriter.Close()
|
||||||
|
|
||||||
|
err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
|
||||||
|
assert.Nil(t, err, "Failed to encrypt a file")
|
||||||
|
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
|
||||||
|
assert.Nil(t, err, "Couldn't read encrypted file")
|
||||||
|
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
|
||||||
|
|
||||||
|
encryptedFileReader, _ := os.Open(encryptedFilePath)
|
||||||
|
defer encryptedFileReader.Close()
|
||||||
|
|
||||||
|
decryptedFileWriter, _ := os.Create(decryptedFilePath)
|
||||||
|
defer decryptedFileWriter.Close()
|
||||||
|
|
||||||
|
decryptedReader, err := AesDecrypt(encryptedFileReader, []byte("passphrase"))
|
||||||
|
assert.Nil(t, err, "Failed to decrypt file")
|
||||||
|
|
||||||
|
io.Copy(decryptedFileWriter, decryptedReader)
|
||||||
|
|
||||||
|
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
|
||||||
|
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "encrypt")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
var (
|
||||||
|
originFilePath = filepath.Join(tmpdir, "origin")
|
||||||
|
encryptedFilePath = filepath.Join(tmpdir, "encrypted")
|
||||||
|
decryptedFilePath = filepath.Join(tmpdir, "decrypted")
|
||||||
|
)
|
||||||
|
|
||||||
|
content := []byte("content")
|
||||||
|
ioutil.WriteFile(originFilePath, content, 0600)
|
||||||
|
|
||||||
|
originFile, _ := os.Open(originFilePath)
|
||||||
|
defer originFile.Close()
|
||||||
|
|
||||||
|
encryptedFileWriter, _ := os.Create(encryptedFilePath)
|
||||||
|
defer encryptedFileWriter.Close()
|
||||||
|
|
||||||
|
err := AesEncrypt(originFile, encryptedFileWriter, []byte(""))
|
||||||
|
assert.Nil(t, err, "Failed to encrypt a file")
|
||||||
|
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
|
||||||
|
assert.Nil(t, err, "Couldn't read encrypted file")
|
||||||
|
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
|
||||||
|
|
||||||
|
encryptedFileReader, _ := os.Open(encryptedFilePath)
|
||||||
|
defer encryptedFileReader.Close()
|
||||||
|
|
||||||
|
decryptedFileWriter, _ := os.Create(decryptedFilePath)
|
||||||
|
defer decryptedFileWriter.Close()
|
||||||
|
|
||||||
|
decryptedReader, err := AesDecrypt(encryptedFileReader, []byte(""))
|
||||||
|
assert.Nil(t, err, "Failed to decrypt file")
|
||||||
|
|
||||||
|
io.Copy(decryptedFileWriter, decryptedReader)
|
||||||
|
|
||||||
|
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
|
||||||
|
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T) {
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "encrypt")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
var (
|
||||||
|
originFilePath = filepath.Join(tmpdir, "origin")
|
||||||
|
encryptedFilePath = filepath.Join(tmpdir, "encrypted")
|
||||||
|
decryptedFilePath = filepath.Join(tmpdir, "decrypted")
|
||||||
|
)
|
||||||
|
|
||||||
|
content := []byte("content")
|
||||||
|
ioutil.WriteFile(originFilePath, content, 0600)
|
||||||
|
|
||||||
|
originFile, _ := os.Open(originFilePath)
|
||||||
|
defer originFile.Close()
|
||||||
|
|
||||||
|
encryptedFileWriter, _ := os.Create(encryptedFilePath)
|
||||||
|
defer encryptedFileWriter.Close()
|
||||||
|
|
||||||
|
err := AesEncrypt(originFile, encryptedFileWriter, []byte("passphrase"))
|
||||||
|
assert.Nil(t, err, "Failed to encrypt a file")
|
||||||
|
encryptedContent, err := ioutil.ReadFile(encryptedFilePath)
|
||||||
|
assert.Nil(t, err, "Couldn't read encrypted file")
|
||||||
|
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
|
||||||
|
|
||||||
|
encryptedFileReader, _ := os.Open(encryptedFilePath)
|
||||||
|
defer encryptedFileReader.Close()
|
||||||
|
|
||||||
|
decryptedFileWriter, _ := os.Create(decryptedFilePath)
|
||||||
|
defer decryptedFileWriter.Close()
|
||||||
|
|
||||||
|
decryptedReader, err := AesDecrypt(encryptedFileReader, []byte("garbage"))
|
||||||
|
assert.Nil(t, err, "Should allow to decrypt with wrong passphrase")
|
||||||
|
|
||||||
|
io.Copy(decryptedFileWriter, decryptedReader)
|
||||||
|
|
||||||
|
decryptedContent, _ := ioutil.ReadFile(decryptedFilePath)
|
||||||
|
assert.NotEqual(t, content, decryptedContent, "Original and decrypted content should NOT match")
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -505,3 +505,8 @@ func (service *Service) GetTemporaryPath() (string, error) {
|
||||||
|
|
||||||
return path.Join(service.fileStorePath, TempPath, uid.String()), nil
|
return path.Join(service.fileStorePath, TempPath, uid.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDataStorePath returns path to data folder
|
||||||
|
func (service *Service) GetDatastorePath() string {
|
||||||
|
return service.dataStorePath
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ require (
|
||||||
github.com/mattn/go-shellwords v1.0.6 // indirect
|
github.com/mattn/go-shellwords v1.0.6 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
|
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/portainer/libcompose v0.5.3
|
github.com/portainer/libcompose v0.5.3
|
||||||
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2
|
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2
|
||||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
|
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
|
||||||
|
|
|
@ -223,6 +223,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
"github.com/portainer/libhttp/request"
|
||||||
|
operations "github.com/portainer/portainer/api/backup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
backupPayload struct {
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *backupPayload) Validate(r *http.Request) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// @id Backup
|
||||||
|
// @summary Creates an archive with a system data snapshot that could be used to restore the system.
|
||||||
|
// @description Creates an archive with a system data snapshot that could be used to restore the system.
|
||||||
|
// @description **Access policy**: admin
|
||||||
|
// @tags backup
|
||||||
|
// @security jwt
|
||||||
|
// @produce octet-stream
|
||||||
|
// @param Password body string false "Password to encrypt the backup with"
|
||||||
|
// @success 200 "Success"
|
||||||
|
// @failure 400 "Invalid request"
|
||||||
|
// @failure 500 "Server error"
|
||||||
|
// @router /backup [post]
|
||||||
|
func (h *Handler) backup(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
|
var payload backupPayload
|
||||||
|
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
archivePath, err := operations.CreateBackupArchive(payload.Password, h.gate, h.dataStore, h.filestorePath)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to create backup", Err: err}
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(filepath.Dir(archivePath))
|
||||||
|
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fmt.Sprintf("portainer-backup_%s", filepath.Base(archivePath))))
|
||||||
|
http.ServeFile(w, r, archivePath)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/portainer/portainer/api/adminmonitor"
|
||||||
|
"github.com/portainer/portainer/api/crypto"
|
||||||
|
"github.com/portainer/portainer/api/http/offlinegate"
|
||||||
|
i "github.com/portainer/portainer/api/internal/testhelpers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listFiles(dir string) []string {
|
||||||
|
items := make([]string, 0)
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if path == dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
items = append(items, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(t *testing.T, list []string, path string) {
|
||||||
|
assert.Contains(t, list, path)
|
||||||
|
copyContent, _ := ioutil.ReadFile(path)
|
||||||
|
assert.Equal(t, "content\n", string(copyContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_backupHandlerWithoutPassword_shouldCreateATarballArchive(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"password":""}`))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
gate := offlinegate.NewOfflineGate()
|
||||||
|
adminMonitor := adminmonitor.New(time.Hour, nil, context.Background())
|
||||||
|
|
||||||
|
handlerErr := NewHandler(nil, i.NewDatastore(), gate, "./test_assets/handler_test", func() {}, adminMonitor).backup(w, r)
|
||||||
|
assert.Nil(t, handlerErr, "Handler should not fail")
|
||||||
|
|
||||||
|
response := w.Result()
|
||||||
|
body, _ := io.ReadAll(response.Body)
|
||||||
|
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "backup")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
archivePath := filepath.Join(tmpdir, "archive.tar.gz")
|
||||||
|
err := ioutil.WriteFile(archivePath, body, 0600)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to save downloaded .tar.gz archive: ", err)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("tar", "-xzf", archivePath, "-C", tmpdir)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to extract archive: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdFiles := listFiles(tmpdir)
|
||||||
|
|
||||||
|
contains(t, createdFiles, path.Join(tmpdir, "portainer.key"))
|
||||||
|
contains(t, createdFiles, path.Join(tmpdir, "portainer.pub"))
|
||||||
|
contains(t, createdFiles, path.Join(tmpdir, "tls", "file1"))
|
||||||
|
contains(t, createdFiles, path.Join(tmpdir, "tls", "file2"))
|
||||||
|
assert.NotContains(t, createdFiles, path.Join(tmpdir, "extra_file"))
|
||||||
|
assert.NotContains(t, createdFiles, path.Join(tmpdir, "extra_folder", "file1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_backupHandlerWithPassword_shouldCreateEncryptedATarballArchive(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"password":"secret"}`))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
gate := offlinegate.NewOfflineGate()
|
||||||
|
adminMonitor := adminmonitor.New(time.Hour, nil, nil)
|
||||||
|
|
||||||
|
handlerErr := NewHandler(nil, i.NewDatastore(), gate, "./test_assets/handler_test", func() {}, adminMonitor).backup(w, r)
|
||||||
|
assert.Nil(t, handlerErr, "Handler should not fail")
|
||||||
|
|
||||||
|
response := w.Result()
|
||||||
|
body, _ := io.ReadAll(response.Body)
|
||||||
|
|
||||||
|
tmpdir, _ := os.MkdirTemp("", "backup")
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
dr, err := crypto.AesDecrypt(bytes.NewReader(body), []byte("secret"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to decrypt archive")
|
||||||
|
}
|
||||||
|
|
||||||
|
archivePath := filepath.Join(tmpdir, "archive.tag.gz")
|
||||||
|
archive, _ := os.Create(archivePath)
|
||||||
|
defer archive.Close()
|
||||||
|
io.Copy(archive, dr)
|
||||||
|
|
||||||
|
cmd := exec.Command("tar", "-xzf", archivePath, "-C", tmpdir)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to extract archive: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdFiles := listFiles(tmpdir)
|
||||||
|
|
||||||
|
contains(t, createdFiles, path.Join(tmpdir, "portainer.key"))
|
||||||
|
contains(t, createdFiles, path.Join(tmpdir, "portainer.pub"))
|
||||||
|
contains(t, createdFiles, path.Join(tmpdir, "tls", "file1"))
|
||||||
|
contains(t, createdFiles, path.Join(tmpdir, "tls", "file2"))
|
||||||
|
assert.NotContains(t, createdFiles, path.Join(tmpdir, "extra_file"))
|
||||||
|
assert.NotContains(t, createdFiles, path.Join(tmpdir, "extra_folder", "file1"))
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/adminmonitor"
|
||||||
|
"github.com/portainer/portainer/api/http/offlinegate"
|
||||||
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler is an http handler responsible for backup and restore portainer state
|
||||||
|
type Handler struct {
|
||||||
|
*mux.Router
|
||||||
|
bouncer *security.RequestBouncer
|
||||||
|
dataStore portainer.DataStore
|
||||||
|
gate *offlinegate.OfflineGate
|
||||||
|
filestorePath string
|
||||||
|
shutdownTrigger context.CancelFunc
|
||||||
|
adminMonitor *adminmonitor.Monitor
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler creates an new instance of backup handler
|
||||||
|
func NewHandler(bouncer *security.RequestBouncer, dataStore portainer.DataStore, gate *offlinegate.OfflineGate, filestorePath string, shutdownTrigger context.CancelFunc, adminMonitor *adminmonitor.Monitor) *Handler {
|
||||||
|
h := &Handler{
|
||||||
|
Router: mux.NewRouter(),
|
||||||
|
bouncer: bouncer,
|
||||||
|
dataStore: dataStore,
|
||||||
|
gate: gate,
|
||||||
|
filestorePath: filestorePath,
|
||||||
|
shutdownTrigger: shutdownTrigger,
|
||||||
|
adminMonitor: adminMonitor,
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Handle("/backup", bouncer.RestrictedAccess(adminAccess(httperror.LoggerHandler(h.backup)))).Methods(http.MethodPost)
|
||||||
|
h.Handle("/restore", bouncer.PublicAccess(httperror.LoggerHandler(h.restore))).Methods(http.MethodPost)
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func adminAccess(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||||
|
if err != nil {
|
||||||
|
httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user info from request context", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !securityContext.IsAdmin {
|
||||||
|
httperror.WriteError(w, http.StatusUnauthorized, "User is not authorized to perfom the action", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func systemWasInitialized(dataStore portainer.DataStore) (bool, error) {
|
||||||
|
users, err := dataStore.User().UsersByRole(portainer.AdministratorRole)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return len(users) > 0, nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
"github.com/portainer/libhttp/request"
|
||||||
|
operations "github.com/portainer/portainer/api/backup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type restorePayload struct {
|
||||||
|
FileContent []byte
|
||||||
|
FileName string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// @id Restore
|
||||||
|
// @summary Triggers a system restore using provided backup file
|
||||||
|
// @description Triggers a system restore using provided backup file
|
||||||
|
// @description **Access policy**: public
|
||||||
|
// @tags backup
|
||||||
|
// @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]
|
||||||
|
func (h *Handler) restore(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
|
initialized, err := h.adminMonitor.WasInitialized()
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to check system initialization", Err: err}
|
||||||
|
}
|
||||||
|
if initialized {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Cannot restore already initialized instance", Err: errors.New("system already initialized")}
|
||||||
|
}
|
||||||
|
h.adminMonitor.Stop()
|
||||||
|
defer h.adminMonitor.Start()
|
||||||
|
|
||||||
|
var payload restorePayload
|
||||||
|
err = decodeForm(r, &payload)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var archiveReader io.Reader = bytes.NewReader(payload.FileContent)
|
||||||
|
err = operations.RestoreArchive(archiveReader, payload.Password, h.filestorePath, h.gate, h.dataStore, h.shutdownTrigger)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Failed to restore the backup", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeForm(r *http.Request, p *restorePayload) error {
|
||||||
|
content, name, err := request.RetrieveMultiPartFormFile(r, "file")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.FileContent = content
|
||||||
|
p.FileName = name
|
||||||
|
|
||||||
|
password, _ := request.RetrieveMultiPartFormValue(r, "password", true)
|
||||||
|
p.Password = password
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/adminmonitor"
|
||||||
|
"github.com/portainer/portainer/api/http/offlinegate"
|
||||||
|
i "github.com/portainer/portainer/api/internal/testhelpers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_restoreArchive_usingCombinationOfPasswords(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
backupPassword string
|
||||||
|
restorePassword string
|
||||||
|
fails bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty password to both encrypt and decrypt",
|
||||||
|
backupPassword: "",
|
||||||
|
restorePassword: "",
|
||||||
|
fails: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same password to encrypt and decrypt",
|
||||||
|
backupPassword: "secret",
|
||||||
|
restorePassword: "secret",
|
||||||
|
fails: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different passwords to encrypt and decrypt",
|
||||||
|
backupPassword: "secret",
|
||||||
|
restorePassword: "terces",
|
||||||
|
fails: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
datastore := i.NewDatastore(i.WithUsers([]portainer.User{}), i.WithEdgeJobs([]portainer.EdgeJob{}))
|
||||||
|
adminMonitor := adminmonitor.New(time.Hour, datastore, context.Background())
|
||||||
|
|
||||||
|
h := NewHandler(nil, datastore, offlinegate.NewOfflineGate(), "./test_assets/handler_test", func() {}, adminMonitor)
|
||||||
|
|
||||||
|
//backup
|
||||||
|
archive := backup(t, h, test.backupPassword)
|
||||||
|
|
||||||
|
//restore
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r, err := prepareMultipartRequest(test.restorePassword, archive)
|
||||||
|
assert.Nil(t, err, "Shouldn't fail to write multipart form")
|
||||||
|
|
||||||
|
restoreErr := h.restore(w, r)
|
||||||
|
assert.Equal(t, test.fails, restoreErr != nil, "Didn't meet expectation of failing restore handler")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_restoreArchive_shouldFailIfSystemWasAlreadyInitialized(t *testing.T) {
|
||||||
|
admin := portainer.User{
|
||||||
|
Role: portainer.AdministratorRole,
|
||||||
|
}
|
||||||
|
datastore := i.NewDatastore(i.WithUsers([]portainer.User{admin}), i.WithEdgeJobs([]portainer.EdgeJob{}))
|
||||||
|
adminMonitor := adminmonitor.New(time.Hour, datastore, context.Background())
|
||||||
|
|
||||||
|
h := NewHandler(nil, datastore, offlinegate.NewOfflineGate(), "./test_assets/handler_test", func() {}, adminMonitor)
|
||||||
|
|
||||||
|
//backup
|
||||||
|
archive := backup(t, h, "password")
|
||||||
|
|
||||||
|
//restore
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r, err := prepareMultipartRequest("password", archive)
|
||||||
|
assert.Nil(t, err, "Shouldn't fail to write multipart form")
|
||||||
|
|
||||||
|
restoreErr := h.restore(w, r)
|
||||||
|
assert.NotNil(t, restoreErr, "Should fail, because system it already initialized")
|
||||||
|
assert.Equal(t, "Cannot restore already initialized instance", restoreErr.Message, "Should fail with certain error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func backup(t *testing.T, h *Handler, password string) []byte {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(fmt.Sprintf(`{"password":"%s"}`, password)))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
backupErr := h.backup(w, r)
|
||||||
|
assert.Nil(t, backupErr, "Backup should not fail")
|
||||||
|
|
||||||
|
response := w.Result()
|
||||||
|
archive, _ := io.ReadAll(response.Body)
|
||||||
|
return archive
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareMultipartRequest(password string, file []byte) (*http.Request, error) {
|
||||||
|
var body bytes.Buffer
|
||||||
|
w := multipart.NewWriter(&body)
|
||||||
|
err := w.WriteField("password", password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fw, err := w.CreateFormFile("file", "filename")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
io.Copy(fw, bytes.NewReader(file))
|
||||||
|
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "http://localhost/", &body)
|
||||||
|
r.Header.Set("Content-Type", w.FormDataContentType())
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api/http/handler/auth"
|
"github.com/portainer/portainer/api/http/handler/auth"
|
||||||
|
"github.com/portainer/portainer/api/http/handler/backup"
|
||||||
"github.com/portainer/portainer/api/http/handler/customtemplates"
|
"github.com/portainer/portainer/api/http/handler/customtemplates"
|
||||||
"github.com/portainer/portainer/api/http/handler/dockerhub"
|
"github.com/portainer/portainer/api/http/handler/dockerhub"
|
||||||
"github.com/portainer/portainer/api/http/handler/edgegroups"
|
"github.com/portainer/portainer/api/http/handler/edgegroups"
|
||||||
|
@ -36,6 +37,7 @@ import (
|
||||||
// Handler is a collection of all the service handlers.
|
// Handler is a collection of all the service handlers.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
AuthHandler *auth.Handler
|
AuthHandler *auth.Handler
|
||||||
|
BackupHandler *backup.Handler
|
||||||
CustomTemplatesHandler *customtemplates.Handler
|
CustomTemplatesHandler *customtemplates.Handler
|
||||||
DockerHubHandler *dockerhub.Handler
|
DockerHubHandler *dockerhub.Handler
|
||||||
EdgeGroupsHandler *edgegroups.Handler
|
EdgeGroupsHandler *edgegroups.Handler
|
||||||
|
@ -140,6 +142,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(r.URL.Path, "/api/auth"):
|
case strings.HasPrefix(r.URL.Path, "/api/auth"):
|
||||||
http.StripPrefix("/api", h.AuthHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api", h.AuthHandler).ServeHTTP(w, r)
|
||||||
|
case strings.HasPrefix(r.URL.Path, "/api/backup"):
|
||||||
|
http.StripPrefix("/api", h.BackupHandler).ServeHTTP(w, r)
|
||||||
|
case strings.HasPrefix(r.URL.Path, "/api/restore"):
|
||||||
|
http.StripPrefix("/api", h.BackupHandler).ServeHTTP(w, r)
|
||||||
case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
|
case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
|
||||||
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
|
||||||
case strings.HasPrefix(r.URL.Path, "/api/custom_templates"):
|
case strings.HasPrefix(r.URL.Path, "/api/custom_templates"):
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package offlinegate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OfflineGate is a entity that works similar to a mutex with a signaling
|
||||||
|
// Only the caller that have Locked an gate can unlock it, otherw will be blocked with a call to Lock.
|
||||||
|
// Gate provides a passthrough http middleware that will wait for a locked gate to be unlocked.
|
||||||
|
// For a safety reasons, middleware will timeout
|
||||||
|
type OfflineGate struct {
|
||||||
|
lock *sync.Mutex
|
||||||
|
signalingCh chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOfflineGate creates a new gate
|
||||||
|
func NewOfflineGate() *OfflineGate {
|
||||||
|
return &OfflineGate{
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock locks readonly gate and returns a function to unlock
|
||||||
|
func (o *OfflineGate) Lock() func() {
|
||||||
|
o.lock.Lock()
|
||||||
|
o.signalingCh = make(chan interface{})
|
||||||
|
return o.unlock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OfflineGate) unlock() {
|
||||||
|
if o.signalingCh == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
close(o.signalingCh)
|
||||||
|
o.signalingCh = nil
|
||||||
|
o.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch returns a signaling channel.
|
||||||
|
// Unless channel is nil, client needs to watch for a signal on a channel to know when gate is unlocked.
|
||||||
|
// Signal channel is disposable: onced signaled, has to be disposed and acquired again.
|
||||||
|
func (o *OfflineGate) Watch() chan interface{} {
|
||||||
|
return o.signalingCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitingMiddleware returns an http handler that waits for the gate to be unlocked before continuing
|
||||||
|
func (o *OfflineGate) WaitingMiddleware(timeout time.Duration, next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
signalingCh := o.Watch()
|
||||||
|
|
||||||
|
if signalingCh != nil {
|
||||||
|
if r.Method != "GET" && r.Method != "HEAD" && r.Method != "OPTIONS" {
|
||||||
|
select {
|
||||||
|
case <-signalingCh:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
log.Println("error: Timeout waiting for the offline gate to signal")
|
||||||
|
httperror.WriteError(w, http.StatusRequestTimeout, "Timeout waiting for the offline gate to signal", http.ErrHandlerTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
package offlinegate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_canLockAndUnlock(t *testing.T) {
|
||||||
|
o := NewOfflineGate()
|
||||||
|
|
||||||
|
unlock := o.Lock()
|
||||||
|
unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_hasToBeUnlockedToLockAgain(t *testing.T) {
|
||||||
|
// scenario:
|
||||||
|
// 1. first routine starts and locks the gate
|
||||||
|
// 2. first routine starts a second and wait for the second to start
|
||||||
|
// 3. second start but waits for the gate to be released
|
||||||
|
// 4. first continues and unlocks the gate, when done
|
||||||
|
// 5. second be able to continue
|
||||||
|
// 6. second lock the gate, does the job and unlocks it
|
||||||
|
|
||||||
|
o := NewOfflineGate()
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
result := make([]string, 0, 2)
|
||||||
|
go func() {
|
||||||
|
unlock := o.Lock()
|
||||||
|
defer unlock()
|
||||||
|
waitForSecondToStart := sync.WaitGroup{}
|
||||||
|
waitForSecondToStart.Add(1)
|
||||||
|
go func() {
|
||||||
|
waitForSecondToStart.Done()
|
||||||
|
unlock := o.Lock()
|
||||||
|
defer unlock()
|
||||||
|
result = append(result, "second")
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
waitForSecondToStart.Wait()
|
||||||
|
result = append(result, "first")
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if len(result) != 2 || result[0] != "first" || result[1] != "second" {
|
||||||
|
t.Error("Second call have disresregarded a raised lock")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_waitChannelWillBeEmpty_ifGateIsUnlocked(t *testing.T) {
|
||||||
|
o := NewOfflineGate()
|
||||||
|
|
||||||
|
signalingCh := o.Watch()
|
||||||
|
if signalingCh != nil {
|
||||||
|
t.Error("Signaling channel should be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_startWaitingForSignal_beforeGateGetsUnlocked(t *testing.T) {
|
||||||
|
// scenario:
|
||||||
|
// 1. main routing locks the gate and waits for a consumer to start up
|
||||||
|
// 2. consumer starts up, notifies main and begins waiting for the gate to be unlocked
|
||||||
|
// 3. main unlocks the gate
|
||||||
|
// 4. consumer be able to continue
|
||||||
|
|
||||||
|
o := NewOfflineGate()
|
||||||
|
unlock := o.Lock()
|
||||||
|
|
||||||
|
signalingCh := o.Watch()
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
readerIsReady := sync.WaitGroup{}
|
||||||
|
readerIsReady.Add(1)
|
||||||
|
|
||||||
|
go func(t *testing.T) {
|
||||||
|
readerIsReady.Done()
|
||||||
|
|
||||||
|
// either wait for a signal or timeout
|
||||||
|
select {
|
||||||
|
case <-signalingCh:
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Error("Failed to wait for a signal, exit by timeout")
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}(t)
|
||||||
|
|
||||||
|
readerIsReady.Wait()
|
||||||
|
unlock()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_startWaitingForSignal_afterGateGetsUnlocked(t *testing.T) {
|
||||||
|
// scenario:
|
||||||
|
// 1. main routing locks, gets waiting channel and unlocks
|
||||||
|
// 2. consumer starts up and begins waiting for the gate to be unlocked
|
||||||
|
// 3. consumer gets signal immediately and continues
|
||||||
|
|
||||||
|
o := NewOfflineGate()
|
||||||
|
unlock := o.Lock()
|
||||||
|
signalingCh := o.Watch()
|
||||||
|
unlock()
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(t *testing.T) {
|
||||||
|
// either wait for a signal or timeout
|
||||||
|
select {
|
||||||
|
case <-signalingCh:
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Error("Failed to wait for a signal, exit by timeout")
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}(t)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_waitingMiddleware_executesImmediately_whenNotLocked(t *testing.T) {
|
||||||
|
// scenario:
|
||||||
|
// 1. create an gate
|
||||||
|
// 2. kick off a waiting middleware that will release immediately as gate wasn't locked
|
||||||
|
// 3. middleware shouldn't timeout
|
||||||
|
|
||||||
|
o := NewOfflineGate()
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
|
timeout := 2 * time.Second
|
||||||
|
start := time.Now()
|
||||||
|
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
if elapsed >= timeout {
|
||||||
|
t.Error("WaitingMiddleware had likely timeout, when it shouldn't")
|
||||||
|
}
|
||||||
|
w.Write([]byte("success"))
|
||||||
|
})).ServeHTTP(response, request)
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(response.Body)
|
||||||
|
if string(body) != "success" {
|
||||||
|
t.Error("Didn't receive expected result from the hanlder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_waitingMiddleware_waitsForTheLockToBeReleased(t *testing.T) {
|
||||||
|
// scenario:
|
||||||
|
// 1. create an gate and lock it
|
||||||
|
// 2. kick off a routing that will unlock the gate after 1 second
|
||||||
|
// 3. kick off a waiting middleware that will wait for lock to be eventually released
|
||||||
|
// 4. middleware shouldn't timeout
|
||||||
|
|
||||||
|
o := NewOfflineGate()
|
||||||
|
unlock := o.Lock()
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
timeout := 10 * time.Second
|
||||||
|
start := time.Now()
|
||||||
|
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
if elapsed >= timeout {
|
||||||
|
t.Error("WaitingMiddleware had likely timeout, when it shouldn't")
|
||||||
|
}
|
||||||
|
w.Write([]byte("success"))
|
||||||
|
})).ServeHTTP(response, request)
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(response.Body)
|
||||||
|
if string(body) != "success" {
|
||||||
|
t.Error("Didn't receive expected result from the hanlder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_waitingMiddleware_mayTimeout_whenLockedForTooLong(t *testing.T) {
|
||||||
|
/*
|
||||||
|
scenario:
|
||||||
|
1. create an gate and lock it
|
||||||
|
2. kick off a waiting middleware that will wait for lock to be eventually released
|
||||||
|
3. because we never unlocked the gate, middleware suppose to timeout
|
||||||
|
*/
|
||||||
|
o := NewOfflineGate()
|
||||||
|
o.Lock()
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
|
timeout := 1 * time.Second
|
||||||
|
start := time.Now()
|
||||||
|
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
if elapsed < timeout {
|
||||||
|
t.Error("WaitingMiddleware suppose to timeout, but it didnt")
|
||||||
|
}
|
||||||
|
w.Write([]byte("success"))
|
||||||
|
})).ServeHTTP(response, request)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusRequestTimeout, response.Result().StatusCode, "Request support to timeout waiting for the gate")
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||||
)
|
)
|
||||||
|
@ -153,6 +153,9 @@ func (bouncer *RequestBouncer) RegistryAccess(r *http.Request, registry *portain
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handlers are applied backwards to the incoming request:
|
||||||
|
// - add secure handlers to the response
|
||||||
|
// - parse the JWT token and put it into the http context.
|
||||||
func (bouncer *RequestBouncer) mwAuthenticatedUser(h http.Handler) http.Handler {
|
func (bouncer *RequestBouncer) mwAuthenticatedUser(h http.Handler) http.Handler {
|
||||||
h = bouncer.mwCheckAuthentication(h)
|
h = bouncer.mwCheckAuthentication(h)
|
||||||
h = mwSecureHeaders(h)
|
h = mwSecureHeaders(h)
|
||||||
|
@ -216,6 +219,8 @@ func (bouncer *RequestBouncer) mwUpgradeToRestrictedRequest(next http.Handler) h
|
||||||
}
|
}
|
||||||
|
|
||||||
// mwCheckAuthentication provides Authentication middleware for handlers
|
// mwCheckAuthentication provides Authentication middleware for handlers
|
||||||
|
//
|
||||||
|
// It parses the JWT token and adds the parsed token data to the http context
|
||||||
func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Handler {
|
func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var tokenData *portainer.TokenData
|
var tokenData *portainer.TokenData
|
||||||
|
@ -269,30 +274,31 @@ func mwSecureHeaders(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bouncer *RequestBouncer) newRestrictedContextRequest(userID portainer.UserID, userRole portainer.UserRole) (*RestrictedRequestContext, error) {
|
func (bouncer *RequestBouncer) newRestrictedContextRequest(userID portainer.UserID, userRole portainer.UserRole) (*RestrictedRequestContext, error) {
|
||||||
requestContext := &RestrictedRequestContext{
|
if userRole == portainer.AdministratorRole {
|
||||||
IsAdmin: true,
|
return &RestrictedRequestContext{
|
||||||
UserID: userID,
|
IsAdmin: true,
|
||||||
|
UserID: userID,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if userRole != portainer.AdministratorRole {
|
memberships, err := bouncer.dataStore.TeamMembership().TeamMembershipsByUserID(userID)
|
||||||
requestContext.IsAdmin = false
|
if err != nil {
|
||||||
memberships, err := bouncer.dataStore.TeamMembership().TeamMembershipsByUserID(userID)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
isTeamLeader := false
|
|
||||||
for _, membership := range memberships {
|
|
||||||
if membership.Role == portainer.TeamLeader {
|
|
||||||
isTeamLeader = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestContext.IsTeamLeader = isTeamLeader
|
|
||||||
requestContext.UserMemberships = memberships
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return requestContext, nil
|
isTeamLeader := false
|
||||||
|
for _, membership := range memberships {
|
||||||
|
if membership.Role == portainer.TeamLeader {
|
||||||
|
isTeamLeader = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RestrictedRequestContext{
|
||||||
|
IsAdmin: false,
|
||||||
|
UserID: userID,
|
||||||
|
IsTeamLeader: isTeamLeader,
|
||||||
|
UserMemberships: memberships,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EdgeComputeOperation defines a restriced edge compute operation.
|
// EdgeComputeOperation defines a restriced edge compute operation.
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/adminmonitor"
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
"github.com/portainer/portainer/api/docker"
|
"github.com/portainer/portainer/api/docker"
|
||||||
"github.com/portainer/portainer/api/http/handler"
|
"github.com/portainer/portainer/api/http/handler"
|
||||||
"github.com/portainer/portainer/api/http/handler/auth"
|
"github.com/portainer/portainer/api/http/handler/auth"
|
||||||
|
"github.com/portainer/portainer/api/http/handler/backup"
|
||||||
"github.com/portainer/portainer/api/http/handler/customtemplates"
|
"github.com/portainer/portainer/api/http/handler/customtemplates"
|
||||||
"github.com/portainer/portainer/api/http/handler/dockerhub"
|
"github.com/portainer/portainer/api/http/handler/dockerhub"
|
||||||
"github.com/portainer/portainer/api/http/handler/edgegroups"
|
"github.com/portainer/portainer/api/http/handler/edgegroups"
|
||||||
|
@ -36,10 +41,10 @@ import (
|
||||||
"github.com/portainer/portainer/api/http/handler/users"
|
"github.com/portainer/portainer/api/http/handler/users"
|
||||||
"github.com/portainer/portainer/api/http/handler/webhooks"
|
"github.com/portainer/portainer/api/http/handler/webhooks"
|
||||||
"github.com/portainer/portainer/api/http/handler/websocket"
|
"github.com/portainer/portainer/api/http/handler/websocket"
|
||||||
|
"github.com/portainer/portainer/api/http/offlinegate"
|
||||||
"github.com/portainer/portainer/api/http/proxy"
|
"github.com/portainer/portainer/api/http/proxy"
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,6 +74,8 @@ type Server struct {
|
||||||
DockerClientFactory *docker.ClientFactory
|
DockerClientFactory *docker.ClientFactory
|
||||||
KubernetesClientFactory *cli.ClientFactory
|
KubernetesClientFactory *cli.ClientFactory
|
||||||
KubernetesDeployer portainer.KubernetesDeployer
|
KubernetesDeployer portainer.KubernetesDeployer
|
||||||
|
ShutdownCtx context.Context
|
||||||
|
ShutdownTrigger context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the HTTP server
|
// Start starts the HTTP server
|
||||||
|
@ -78,6 +85,7 @@ func (server *Server) Start() error {
|
||||||
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService)
|
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService)
|
||||||
|
|
||||||
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
|
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
|
||||||
|
offlineGate := offlinegate.NewOfflineGate()
|
||||||
|
|
||||||
var authHandler = auth.NewHandler(requestBouncer, rateLimiter)
|
var authHandler = auth.NewHandler(requestBouncer, rateLimiter)
|
||||||
authHandler.DataStore = server.DataStore
|
authHandler.DataStore = server.DataStore
|
||||||
|
@ -88,6 +96,11 @@ func (server *Server) Start() error {
|
||||||
authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager
|
authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager
|
||||||
authHandler.OAuthService = server.OAuthService
|
authHandler.OAuthService = server.OAuthService
|
||||||
|
|
||||||
|
adminMonitor := adminmonitor.New(5*time.Minute, server.DataStore, server.ShutdownCtx)
|
||||||
|
adminMonitor.Start()
|
||||||
|
|
||||||
|
var backupHandler = backup.NewHandler(requestBouncer, server.DataStore, offlineGate, server.FileService.GetDatastorePath(), server.ShutdownTrigger, adminMonitor)
|
||||||
|
|
||||||
var roleHandler = roles.NewHandler(requestBouncer)
|
var roleHandler = roles.NewHandler(requestBouncer)
|
||||||
roleHandler.DataStore = server.DataStore
|
roleHandler.DataStore = server.DataStore
|
||||||
|
|
||||||
|
@ -200,6 +213,7 @@ func (server *Server) Start() error {
|
||||||
server.Handler = &handler.Handler{
|
server.Handler = &handler.Handler{
|
||||||
RoleHandler: roleHandler,
|
RoleHandler: roleHandler,
|
||||||
AuthHandler: authHandler,
|
AuthHandler: authHandler,
|
||||||
|
BackupHandler: backupHandler,
|
||||||
CustomTemplatesHandler: customTemplatesHandler,
|
CustomTemplatesHandler: customTemplatesHandler,
|
||||||
DockerHubHandler: dockerHubHandler,
|
DockerHubHandler: dockerHubHandler,
|
||||||
EdgeGroupsHandler: edgeGroupsHandler,
|
EdgeGroupsHandler: edgeGroupsHandler,
|
||||||
|
@ -231,10 +245,27 @@ func (server *Server) Start() error {
|
||||||
Addr: server.BindAddress,
|
Addr: server.BindAddress,
|
||||||
Handler: server.Handler,
|
Handler: server.Handler,
|
||||||
}
|
}
|
||||||
|
httpServer.Handler = offlineGate.WaitingMiddleware(time.Minute, httpServer.Handler)
|
||||||
|
|
||||||
if server.SSL {
|
if server.SSL {
|
||||||
httpServer.TLSConfig = crypto.CreateServerTLSConfiguration()
|
httpServer.TLSConfig = crypto.CreateServerTLSConfiguration()
|
||||||
return httpServer.ListenAndServeTLS(server.SSLCert, server.SSLKey)
|
return httpServer.ListenAndServeTLS(server.SSLCert, server.SSLKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go server.shutdown(httpServer)
|
||||||
|
|
||||||
return httpServer.ListenAndServe()
|
return httpServer.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server *Server) shutdown(httpServer *http.Server) {
|
||||||
|
<-server.ShutdownCtx.Done()
|
||||||
|
|
||||||
|
log.Println("[DEBUG] Shutting down http server")
|
||||||
|
shutdownTimeout, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := httpServer.Shutdown(shutdownTimeout)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed shutdown http server: %s \n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package edge
|
||||||
|
|
||||||
|
import portainer "github.com/portainer/portainer/api"
|
||||||
|
|
||||||
|
// LoadEdgeJobs registers all edge jobs inside corresponding endpoint tunnel
|
||||||
|
func LoadEdgeJobs(dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService) error {
|
||||||
|
edgeJobs, err := dataStore.EdgeJob().EdgeJobs()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, edgeJob := range edgeJobs {
|
||||||
|
for endpointID := range edgeJob.Endpoints {
|
||||||
|
reverseTunnelService.AddEdgeJob(endpointID, &edgeJob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package snapshot
|
package snapshot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -16,10 +17,11 @@ type Service struct {
|
||||||
snapshotIntervalInSeconds float64
|
snapshotIntervalInSeconds float64
|
||||||
dockerSnapshotter portainer.DockerSnapshotter
|
dockerSnapshotter portainer.DockerSnapshotter
|
||||||
kubernetesSnapshotter portainer.KubernetesSnapshotter
|
kubernetesSnapshotter portainer.KubernetesSnapshotter
|
||||||
|
shutdownCtx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new instance of a service
|
// NewService creates a new instance of a service
|
||||||
func NewService(snapshotInterval string, dataStore portainer.DataStore, dockerSnapshotter portainer.DockerSnapshotter, kubernetesSnapshotter portainer.KubernetesSnapshotter) (*Service, error) {
|
func NewService(snapshotInterval string, dataStore portainer.DataStore, dockerSnapshotter portainer.DockerSnapshotter, kubernetesSnapshotter portainer.KubernetesSnapshotter, shutdownCtx context.Context) (*Service, error) {
|
||||||
snapshotFrequency, err := time.ParseDuration(snapshotInterval)
|
snapshotFrequency, err := time.ParseDuration(snapshotInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -30,6 +32,7 @@ func NewService(snapshotInterval string, dataStore portainer.DataStore, dockerSn
|
||||||
snapshotIntervalInSeconds: snapshotFrequency.Seconds(),
|
snapshotIntervalInSeconds: snapshotFrequency.Seconds(),
|
||||||
dockerSnapshotter: dockerSnapshotter,
|
dockerSnapshotter: dockerSnapshotter,
|
||||||
kubernetesSnapshotter: kubernetesSnapshotter,
|
kubernetesSnapshotter: kubernetesSnapshotter,
|
||||||
|
shutdownCtx: shutdownCtx,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +46,7 @@ func (service *Service) Start() {
|
||||||
service.startSnapshotLoop()
|
service.startSnapshotLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *Service) stop() {
|
func (service *Service) Stop() {
|
||||||
if service.refreshSignal == nil {
|
if service.refreshSignal == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -55,7 +58,7 @@ func (service *Service) stop() {
|
||||||
|
|
||||||
// SetSnapshotInterval sets the snapshot interval and resets the service
|
// SetSnapshotInterval sets the snapshot interval and resets the service
|
||||||
func (service *Service) SetSnapshotInterval(snapshotInterval string) error {
|
func (service *Service) SetSnapshotInterval(snapshotInterval string) error {
|
||||||
service.stop()
|
service.Stop()
|
||||||
|
|
||||||
snapshotFrequency, err := time.ParseDuration(snapshotInterval)
|
snapshotFrequency, err := time.ParseDuration(snapshotInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -132,9 +135,12 @@ func (service *Service) startSnapshotLoop() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] [internal,snapshot] [message: background schedule error (endpoint snapshot).] [error: %s]", err)
|
log.Printf("[ERROR] [internal,snapshot] [message: background schedule error (endpoint snapshot).] [error: %s]", err)
|
||||||
}
|
}
|
||||||
|
case <-service.shutdownCtx.Done():
|
||||||
|
log.Println("[DEBUG] [internal,snapshot] [message: shutting down snapshotting]")
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
case <-service.refreshSignal:
|
case <-service.refreshSignal:
|
||||||
log.Println("[DEBUG] [internal,snapshot] [message: shutting down Snapshot service]")
|
log.Println("[DEBUG] [internal,snapshot] [message: shutting down snapshotting]")
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
package testhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type datastore struct {
|
||||||
|
dockerHub portainer.DockerHubService
|
||||||
|
customTemplate portainer.CustomTemplateService
|
||||||
|
edgeGroup portainer.EdgeGroupService
|
||||||
|
edgeJob portainer.EdgeJobService
|
||||||
|
edgeStack portainer.EdgeStackService
|
||||||
|
endpoint portainer.EndpointService
|
||||||
|
endpointGroup portainer.EndpointGroupService
|
||||||
|
endpointRelation portainer.EndpointRelationService
|
||||||
|
registry portainer.RegistryService
|
||||||
|
resourceControl portainer.ResourceControlService
|
||||||
|
role portainer.RoleService
|
||||||
|
settings portainer.SettingsService
|
||||||
|
stack portainer.StackService
|
||||||
|
tag portainer.TagService
|
||||||
|
teamMembership portainer.TeamMembershipService
|
||||||
|
team portainer.TeamService
|
||||||
|
tunnelServer portainer.TunnelServerService
|
||||||
|
user portainer.UserService
|
||||||
|
version portainer.VersionService
|
||||||
|
webhook portainer.WebhookService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) BackupTo(io.Writer) error { return nil }
|
||||||
|
func (d *datastore) Open() error { return nil }
|
||||||
|
func (d *datastore) Init() error { return nil }
|
||||||
|
func (d *datastore) Close() error { return nil }
|
||||||
|
func (d *datastore) CheckCurrentEdition() error { return nil }
|
||||||
|
func (d *datastore) IsNew() bool { return false }
|
||||||
|
func (d *datastore) MigrateData(force bool) error { return nil }
|
||||||
|
func (d *datastore) RollbackToCE() error { return nil }
|
||||||
|
func (d *datastore) DockerHub() portainer.DockerHubService { return d.dockerHub }
|
||||||
|
func (d *datastore) CustomTemplate() portainer.CustomTemplateService { return d.customTemplate }
|
||||||
|
func (d *datastore) EdgeGroup() portainer.EdgeGroupService { return d.edgeGroup }
|
||||||
|
func (d *datastore) EdgeJob() portainer.EdgeJobService { return d.edgeJob }
|
||||||
|
func (d *datastore) EdgeStack() portainer.EdgeStackService { return d.edgeStack }
|
||||||
|
func (d *datastore) Endpoint() portainer.EndpointService { return d.endpoint }
|
||||||
|
func (d *datastore) EndpointGroup() portainer.EndpointGroupService { return d.endpointGroup }
|
||||||
|
func (d *datastore) EndpointRelation() portainer.EndpointRelationService { return d.endpointRelation }
|
||||||
|
func (d *datastore) Registry() portainer.RegistryService { return d.registry }
|
||||||
|
func (d *datastore) ResourceControl() portainer.ResourceControlService { return d.resourceControl }
|
||||||
|
func (d *datastore) Role() portainer.RoleService { return d.role }
|
||||||
|
func (d *datastore) Settings() portainer.SettingsService { return d.settings }
|
||||||
|
func (d *datastore) Stack() portainer.StackService { return d.stack }
|
||||||
|
func (d *datastore) Tag() portainer.TagService { return d.tag }
|
||||||
|
func (d *datastore) TeamMembership() portainer.TeamMembershipService { return d.teamMembership }
|
||||||
|
func (d *datastore) Team() portainer.TeamService { return d.team }
|
||||||
|
func (d *datastore) TunnelServer() portainer.TunnelServerService { return d.tunnelServer }
|
||||||
|
func (d *datastore) User() portainer.UserService { return d.user }
|
||||||
|
func (d *datastore) Version() portainer.VersionService { return d.version }
|
||||||
|
func (d *datastore) Webhook() portainer.WebhookService { return d.webhook }
|
||||||
|
|
||||||
|
type datastoreOption = func(d *datastore)
|
||||||
|
|
||||||
|
// NewDatastore creates new instance of datastore.
|
||||||
|
// Will apply options before returning, opts will be applied from left to right.
|
||||||
|
func NewDatastore(options ...datastoreOption) *datastore {
|
||||||
|
d := datastore{}
|
||||||
|
for _, o := range options {
|
||||||
|
o(&d)
|
||||||
|
}
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubUserService struct {
|
||||||
|
users []portainer.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubUserService) User(ID portainer.UserID) (*portainer.User, error) { return nil, nil }
|
||||||
|
func (s *stubUserService) UserByUsername(username string) (*portainer.User, error) { return nil, nil }
|
||||||
|
func (s *stubUserService) Users() ([]portainer.User, error) { return s.users, nil }
|
||||||
|
func (s *stubUserService) UsersByRole(role portainer.UserRole) ([]portainer.User, error) {
|
||||||
|
return s.users, nil
|
||||||
|
}
|
||||||
|
func (s *stubUserService) CreateUser(user *portainer.User) error { return nil }
|
||||||
|
func (s *stubUserService) UpdateUser(ID portainer.UserID, user *portainer.User) error { return nil }
|
||||||
|
func (s *stubUserService) DeleteUser(ID portainer.UserID) error { return nil }
|
||||||
|
|
||||||
|
// WithUsers datastore option that will instruct datastore to return provided users
|
||||||
|
func WithUsers(us []portainer.User) datastoreOption {
|
||||||
|
return func(d *datastore) {
|
||||||
|
d.user = &stubUserService{users: us}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubEdgeJobService struct {
|
||||||
|
jobs []portainer.EdgeJob
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubEdgeJobService) EdgeJobs() ([]portainer.EdgeJob, error) { return s.jobs, nil }
|
||||||
|
func (s *stubEdgeJobService) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (s *stubEdgeJobService) CreateEdgeJob(edgeJob *portainer.EdgeJob) error { return nil }
|
||||||
|
func (s *stubEdgeJobService) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *stubEdgeJobService) DeleteEdgeJob(ID portainer.EdgeJobID) error { return nil }
|
||||||
|
func (s *stubEdgeJobService) GetNextIdentifier() int { return 0 }
|
||||||
|
|
||||||
|
// WithEdgeJobs option will instruct datastore to return provided jobs
|
||||||
|
func WithEdgeJobs(js []portainer.EdgeJob) datastoreOption {
|
||||||
|
return func(d *datastore) {
|
||||||
|
d.edgeJob = &stubEdgeJobService{jobs: js}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package testhelpers
|
||||||
|
|
||||||
|
import portainer "github.com/portainer/portainer/api"
|
||||||
|
|
||||||
|
type ReverseTunnelService struct{}
|
||||||
|
|
||||||
|
func (r ReverseTunnelService) StartTunnelServer(addr, port string, snapshotService portainer.SnapshotService) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (r ReverseTunnelService) GenerateEdgeKey(url, host string, endpointIdentifier int) string {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
func (r ReverseTunnelService) SetTunnelStatusToActive(endpointID portainer.EndpointID) {}
|
||||||
|
func (r ReverseTunnelService) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (r ReverseTunnelService) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {}
|
||||||
|
func (r ReverseTunnelService) GetTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (r ReverseTunnelService) AddEdgeJob(endpointID portainer.EndpointID, edgeJob *portainer.EdgeJob) {
|
||||||
|
}
|
||||||
|
func (r ReverseTunnelService) RemoveEdgeJob(edgeJobID portainer.EdgeJobID) {}
|
|
@ -381,8 +381,8 @@ type (
|
||||||
|
|
||||||
// QuayRegistryData represents data required for Quay registry to work
|
// QuayRegistryData represents data required for Quay registry to work
|
||||||
QuayRegistryData struct {
|
QuayRegistryData struct {
|
||||||
UseOrganisation bool `json:"UseOrganisation"`
|
UseOrganisation bool `json:"UseOrganisation"`
|
||||||
OrganisationName string `json:"OrganisationName"`
|
OrganisationName string `json:"OrganisationName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobType represents a job type
|
// JobType represents a job type
|
||||||
|
@ -1000,8 +1000,9 @@ type (
|
||||||
Init() error
|
Init() error
|
||||||
Close() error
|
Close() error
|
||||||
IsNew() bool
|
IsNew() bool
|
||||||
MigrateData() error
|
MigrateData(force bool) error
|
||||||
CheckCurrentEdition() error
|
CheckCurrentEdition() error
|
||||||
|
BackupTo(w io.Writer) error
|
||||||
|
|
||||||
DockerHub() DockerHubService
|
DockerHub() DockerHubService
|
||||||
CustomTemplate() CustomTemplateService
|
CustomTemplate() CustomTemplateService
|
||||||
|
@ -1130,6 +1131,7 @@ type (
|
||||||
StoreCustomTemplateFileFromBytes(identifier, fileName string, data []byte) (string, error)
|
StoreCustomTemplateFileFromBytes(identifier, fileName string, data []byte) (string, error)
|
||||||
GetCustomTemplateProjectPath(identifier string) string
|
GetCustomTemplateProjectPath(identifier string) string
|
||||||
GetTemporaryPath() (string, error)
|
GetTemporaryPath() (string, error)
|
||||||
|
GetDatastorePath() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitService represents a service for managing Git
|
// GitService represents a service for managing Git
|
||||||
|
@ -1196,6 +1198,7 @@ type (
|
||||||
// ReverseTunnelService represents a service used to manage reverse tunnel connections.
|
// ReverseTunnelService represents a service used to manage reverse tunnel connections.
|
||||||
ReverseTunnelService interface {
|
ReverseTunnelService interface {
|
||||||
StartTunnelServer(addr, port string, snapshotService SnapshotService) error
|
StartTunnelServer(addr, port string, snapshotService SnapshotService) error
|
||||||
|
StopTunnelServer() error
|
||||||
GenerateEdgeKey(url, host string, endpointIdentifier int) string
|
GenerateEdgeKey(url, host string, endpointIdentifier int) string
|
||||||
SetTunnelStatusToActive(endpointID EndpointID)
|
SetTunnelStatusToActive(endpointID EndpointID)
|
||||||
SetTunnelStatusToRequired(endpointID EndpointID) error
|
SetTunnelStatusToRequired(endpointID EndpointID) error
|
||||||
|
@ -1238,6 +1241,7 @@ type (
|
||||||
// SnapshotService represents a service for managing endpoint snapshots
|
// SnapshotService represents a service for managing endpoint snapshots
|
||||||
SnapshotService interface {
|
SnapshotService interface {
|
||||||
Start()
|
Start()
|
||||||
|
Stop()
|
||||||
SetSnapshotInterval(snapshotInterval string) error
|
SetSnapshotInterval(snapshotInterval string) error
|
||||||
SnapshotEndpoint(endpoint *Endpoint) error
|
SnapshotEndpoint(endpoint *Endpoint) error
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue