mirror of
https://github.com/portainer/portainer.git
synced 2026-06-23 04:10:29 +00:00
chore(linters): enforce error checking in CE BE-12527 (#1723)
This commit is contained in:
+5
-1
@@ -1,4 +1,5 @@
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
allow-parallel-runners: true
|
||||
linters:
|
||||
@@ -7,6 +8,7 @@ linters:
|
||||
- bodyclose
|
||||
- copyloopvar
|
||||
- depguard
|
||||
- errcheck
|
||||
- errorlint
|
||||
- forbidigo
|
||||
- govet
|
||||
@@ -19,6 +21,7 @@ linters:
|
||||
- durationcheck
|
||||
- errorlint
|
||||
- govet
|
||||
- usetesting
|
||||
- zerologlint
|
||||
- testifylint
|
||||
- modernize
|
||||
@@ -49,6 +52,8 @@ linters:
|
||||
desc: golang.org/x/crypto is not allowed because of FIPS mode
|
||||
- pkg: github.com/ProtonMail/go-crypto/openpgp
|
||||
desc: github.com/ProtonMail/go-crypto/openpgp is not allowed because of FIPS mode
|
||||
- pkg: github.com/cosi-project/runtime
|
||||
desc: github.com/cosi-project/runtime is not allowed because of FIPS mode
|
||||
- pkg: gopkg.in/yaml.v3
|
||||
desc: use go.yaml.in/yaml/v3 instead
|
||||
forbidigo:
|
||||
@@ -68,7 +73,6 @@ linters:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
|
||||
@@ -11,20 +11,18 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/url"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// GetAgentVersionAndPlatform returns the agent version and platform
|
||||
//
|
||||
// it sends a ping to the agent and parses the version and platform from the headers
|
||||
func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (portainer.AgentPlatform, string, error) { //nolint:forbidigo
|
||||
httpCli := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
httpCli := &http.Client{Timeout: 3 * time.Second}
|
||||
|
||||
if tlsConfig != nil {
|
||||
httpCli.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
httpCli.Transport = &http.Transport{TLSClientConfig: tlsConfig}
|
||||
}
|
||||
|
||||
parsedURL, err := url.ParseURL(endpointUrl + "/ping")
|
||||
@@ -44,8 +42,10 @@ func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (port
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return 0, "", fmt.Errorf("Failed request with status %d", resp.StatusCode)
|
||||
|
||||
@@ -157,7 +157,10 @@ func Test_UpdateAPIKey(t *testing.T) {
|
||||
|
||||
t.Run("Successfully updates the api-key LastUsed time", func(t *testing.T) {
|
||||
user := portainer.User{ID: 1}
|
||||
store.User().Create(&user)
|
||||
|
||||
err := store.User().Create(&user)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, apiKey, err := service.GenerateApiKey(user, "test-x")
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
+6
-14
@@ -17,18 +17,15 @@ func TarFileInBuffer(fileContent []byte, fileName string, mode int64) ([]byte, e
|
||||
Size: int64(len(fileContent)),
|
||||
}
|
||||
|
||||
err := tarWriter.WriteHeader(header)
|
||||
if err != nil {
|
||||
if err := tarWriter.WriteHeader(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = tarWriter.Write(fileContent)
|
||||
if err != nil {
|
||||
if _, err := tarWriter.Write(fileContent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tarWriter.Close()
|
||||
if err != nil {
|
||||
if err := tarWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -43,10 +40,7 @@ type tarFileInBuffer struct {
|
||||
|
||||
func NewTarFileInBuffer() *tarFileInBuffer {
|
||||
var b bytes.Buffer
|
||||
return &tarFileInBuffer{
|
||||
b: &b,
|
||||
w: tar.NewWriter(&b),
|
||||
}
|
||||
return &tarFileInBuffer{b: &b, w: tar.NewWriter(&b)}
|
||||
}
|
||||
|
||||
// Put puts a single file to tar archive buffer.
|
||||
@@ -61,11 +55,9 @@ func (t *tarFileInBuffer) Put(fileContent []byte, fileName string, mode int64) e
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := t.w.Write(fileContent); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := t.w.Write(fileContent)
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Bytes returns the archive as a byte array.
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
)
|
||||
|
||||
// TarGzDir creates a tar.gz archive and returns it's path.
|
||||
@@ -20,12 +22,13 @@ func TarGzDir(absolutePath string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer outFile.Close()
|
||||
defer logs.CloseAndLogErr(outFile)
|
||||
|
||||
zipWriter := gzip.NewWriter(outFile)
|
||||
defer zipWriter.Close()
|
||||
defer logs.CloseAndLogErr(zipWriter)
|
||||
|
||||
tarWriter := tar.NewWriter(zipWriter)
|
||||
defer tarWriter.Close()
|
||||
defer logs.CloseAndLogErr(tarWriter)
|
||||
|
||||
err = filepath.Walk(absolutePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -86,7 +89,7 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipReader.Close()
|
||||
defer logs.CloseAndLogErr(zipReader)
|
||||
|
||||
tarReader := tar.NewReader(zipReader)
|
||||
|
||||
@@ -116,7 +119,7 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
return fmt.Errorf("Failed to extract file %s", header.Name)
|
||||
}
|
||||
outFile.Close()
|
||||
logs.CloseAndLogErr(outFile)
|
||||
default:
|
||||
return fmt.Errorf("tar: unknown type: %v in %s",
|
||||
header.Typeflag,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
func listFiles(dir string) []string {
|
||||
items := make([]string, 0)
|
||||
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
@@ -22,7 +23,9 @@ func listFiles(dir string) []string {
|
||||
items = append(items, path)
|
||||
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to list files in directory")
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
@@ -34,7 +37,7 @@ func Test_shouldCreateArchive(t *testing.T) {
|
||||
err := os.WriteFile(path.Join(tmpdir, "outer"), content, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||
err = os.MkdirAll(path.Join(tmpdir, "dir"), 0700)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = os.WriteFile(path.Join(tmpdir, "dir", ".dotfile"), content, 0600)
|
||||
|
||||
+8
-4
@@ -8,6 +8,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -18,7 +20,7 @@ func UnzipFile(src string, dest string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
defer logs.CloseAndLogErr(r)
|
||||
|
||||
for _, f := range r.File {
|
||||
p := filepath.Join(dest, f.Name)
|
||||
@@ -30,7 +32,9 @@ func UnzipFile(src string, dest string) error {
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
// Make Folder
|
||||
os.MkdirAll(p, os.ModePerm)
|
||||
if err := os.MkdirAll(p, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -53,13 +57,13 @@ func unzipFile(f *zip.File, p string) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't create file %s", p)
|
||||
}
|
||||
defer outFile.Close()
|
||||
defer logs.CloseAndLogErr(outFile)
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't open zip file %s in the archive", f.Name)
|
||||
}
|
||||
defer rc.Close()
|
||||
defer logs.CloseAndLogErr(rc)
|
||||
|
||||
if _, err = io.Copy(outFile, rc); err != nil {
|
||||
return errors.Wrapf(err, "unzipFile: can't copy an archived file content")
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/http/offlinegate"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -97,7 +98,7 @@ func encrypt(path string, passphrase string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer in.Close()
|
||||
defer logs.CloseAndLogErr(in)
|
||||
|
||||
outFileName := path + ".encrypted"
|
||||
out, err := os.Create(outFileName)
|
||||
@@ -105,7 +106,5 @@ func encrypt(path string, passphrase string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = crypto.AesEncrypt(in, out, []byte(passphrase))
|
||||
|
||||
return outFileName, err
|
||||
return outFileName, crypto.AesEncrypt(in, out, []byte(passphrase))
|
||||
}
|
||||
|
||||
+19
-11
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/http/offlinegate"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var filesToRestore = append(filesToBackup, "portainer.db")
|
||||
@@ -31,17 +33,20 @@ func RestoreArchive(archive io.Reader, password string, filestorePath string, ga
|
||||
}
|
||||
|
||||
restorePath := filepath.Join(filestorePath, "restore", time.Now().Format("20060102150405"))
|
||||
defer os.RemoveAll(filepath.Dir(restorePath))
|
||||
defer func() {
|
||||
if err := os.RemoveAll(filepath.Dir(restorePath)); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to clean up restore files")
|
||||
}
|
||||
}()
|
||||
|
||||
err = extractArchive(archive, restorePath)
|
||||
if err != nil {
|
||||
if err := extractArchive(archive, restorePath); 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 {
|
||||
if err := datastore.Close(); err != nil {
|
||||
return errors.Wrap(err, "Failed to stop db")
|
||||
}
|
||||
|
||||
@@ -51,7 +56,7 @@ func RestoreArchive(archive io.Reader, password string, filestorePath string, ga
|
||||
return errors.Wrap(err, "failed to restore from backup. Portainer database missing from backup file")
|
||||
}
|
||||
|
||||
if err = restoreFiles(restorePath, filestorePath); err != nil {
|
||||
if err := restoreFiles(restorePath, filestorePath); err != nil {
|
||||
return errors.Wrap(err, "failed to restore the system state")
|
||||
}
|
||||
|
||||
@@ -89,8 +94,7 @@ func getRestoreSourcePath(dir string) (string, error) {
|
||||
|
||||
func restoreFiles(srcDir string, destinationDir string) error {
|
||||
for _, filename := range filesToRestore {
|
||||
err := filesystem.CopyPath(filepath.Join(srcDir, filename), destinationDir)
|
||||
if err != nil {
|
||||
if err := filesystem.CopyPath(filepath.Join(srcDir, filename), destinationDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -98,14 +102,18 @@ func restoreFiles(srcDir string, destinationDir string) error {
|
||||
// TODO: This is very boltdb module specific once again due to the filename. Move to bolt module? Refactor for another day
|
||||
|
||||
// Prevent the possibility of having both databases. Remove any default new instance
|
||||
os.Remove(filepath.Join(destinationDir, boltdb.DatabaseFileName))
|
||||
os.Remove(filepath.Join(destinationDir, boltdb.EncryptedDatabaseFileName))
|
||||
if err := os.Remove(filepath.Join(destinationDir, boltdb.DatabaseFileName)); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove(filepath.Join(destinationDir, boltdb.EncryptedDatabaseFileName)); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now copy the database. It'll be either portainer.db or portainer.edb
|
||||
|
||||
// Note: CopyPath does not return an error if the source file doesn't exist
|
||||
err := filesystem.CopyPath(filepath.Join(srcDir, boltdb.EncryptedDatabaseFileName), destinationDir)
|
||||
if err != nil {
|
||||
if err := filesystem.CopyPath(filepath.Join(srcDir, boltdb.EncryptedDatabaseFileName), destinationDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -89,10 +89,8 @@ func (service *Service) pingAgent(endpointID portainer.EndpointID) error {
|
||||
return err
|
||||
}
|
||||
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
return nil
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
return resp.Body.Close()
|
||||
}
|
||||
|
||||
// KeepTunnelAlive keeps the tunnel of the given environment for maxAlive duration, or until ctx is done
|
||||
|
||||
@@ -142,7 +142,9 @@ func (s *Service) TunnelAddr(endpoint *portainer.Endpoint) (string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close tcp connection")
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -134,15 +134,16 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
InstanceID: instanceId.String(),
|
||||
MigratorCount: migratorCount,
|
||||
}
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
|
||||
if err := store.VersionService.UpdateVersion(&v); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed to update version")
|
||||
}
|
||||
|
||||
if err := updateSettingsFromFlags(store, flags); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed updating settings from flags")
|
||||
}
|
||||
} else {
|
||||
if err := store.MigrateData(); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed migration")
|
||||
}
|
||||
} else if err := store.MigrateData(); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed migration")
|
||||
}
|
||||
|
||||
if err := updateSettingsFromFlags(store, flags); err != nil {
|
||||
@@ -153,7 +154,7 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
go func() {
|
||||
<-shutdownCtx.Done()
|
||||
|
||||
defer connection.Close()
|
||||
defer logs.CloseAndLogErr(connection)
|
||||
}()
|
||||
|
||||
return store
|
||||
@@ -529,7 +530,9 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||
|
||||
scheduler := scheduler.NewScheduler(shutdownCtx)
|
||||
stackDeployer := deployments.NewStackDeployer(swarmStackManager, composeStackManager, kubernetesDeployer, dockerClientFactory, dataStore)
|
||||
deployments.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService)
|
||||
if err := deployments.StartStackSchedules(scheduler, stackDeployer, dataStore, gitService); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed to start stack scheduler")
|
||||
}
|
||||
|
||||
sslDBSettings, err := dataStore.SSLSettings().Settings()
|
||||
if err != nil {
|
||||
|
||||
+6
-2
@@ -164,7 +164,9 @@ func aesEncryptGCM(input io.Reader, output io.Writer, passphrase []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
nonce.Increment()
|
||||
if err := nonce.Increment(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -235,7 +237,9 @@ func aesDecryptGCM(input io.Reader, passphrase []byte) (io.Reader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce.Increment()
|
||||
if err := nonce.Increment(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &buf, nil
|
||||
|
||||
+60
-41
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/pkg/fips"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -47,16 +48,17 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
)
|
||||
|
||||
content := randBytes(1024*1024*100 + 523)
|
||||
os.WriteFile(originFilePath, content, 0600)
|
||||
err := os.WriteFile(originFilePath, content, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
originFile, _ := os.Open(originFilePath)
|
||||
defer originFile.Close()
|
||||
defer logs.CloseAndLogErr(originFile)
|
||||
|
||||
encryptedFileWriter, _ := os.Create(encryptedFilePath)
|
||||
|
||||
err := encrypt(originFile, encryptedFileWriter, []byte(passphrase))
|
||||
err = encrypt(originFile, encryptedFileWriter, []byte(passphrase))
|
||||
require.NoError(t, err, "Failed to encrypt a file")
|
||||
encryptedFileWriter.Close()
|
||||
logs.CloseAndLogErr(encryptedFileWriter)
|
||||
|
||||
encryptedContent, err := os.ReadFile(encryptedFilePath)
|
||||
require.NoError(t, err, "Couldn't read encrypted file")
|
||||
@@ -64,11 +66,11 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
|
||||
encryptedFileReader, err := os.Open(encryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer encryptedFileReader.Close()
|
||||
defer logs.CloseAndLogErr(encryptedFileReader)
|
||||
|
||||
decryptedFileWriter, err := os.Create(decryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer decryptedFileWriter.Close()
|
||||
defer logs.CloseAndLogErr(decryptedFileWriter)
|
||||
|
||||
decryptedReader, err := decrypt(encryptedFileReader, []byte(passphrase))
|
||||
if !decryptShouldSucceed {
|
||||
@@ -76,9 +78,11 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
|
||||
} else {
|
||||
require.NoError(t, err, "Failed to decrypt file indicated by decryptShouldSucceed")
|
||||
|
||||
io.Copy(decryptedFileWriter, decryptedReader)
|
||||
_, err = io.Copy(decryptedFileWriter, decryptedReader)
|
||||
require.NoError(t, err)
|
||||
|
||||
decryptedContent, _ := os.ReadFile(decryptedFilePath)
|
||||
decryptedContent, err := os.ReadFile(decryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
|
||||
}
|
||||
}
|
||||
@@ -149,33 +153,40 @@ func Test_encryptAndDecrypt_withStrongPassphrase(t *testing.T) {
|
||||
)
|
||||
|
||||
content := randBytes(500)
|
||||
os.WriteFile(originFilePath, content, 0600)
|
||||
|
||||
originFile, _ := os.Open(originFilePath)
|
||||
defer originFile.Close()
|
||||
err := os.WriteFile(originFilePath, content, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
originFile, err := os.Open(originFilePath)
|
||||
require.NoError(t, err)
|
||||
defer logs.CloseAndLogErr(originFile)
|
||||
|
||||
encryptedFileWriter, _ := os.Create(encryptedFilePath)
|
||||
|
||||
err := encrypt(originFile, encryptedFileWriter, []byte(passphrase))
|
||||
err = encrypt(originFile, encryptedFileWriter, []byte(passphrase))
|
||||
require.NoError(t, err, "Failed to encrypt a file")
|
||||
encryptedFileWriter.Close()
|
||||
logs.CloseAndLogErr(encryptedFileWriter)
|
||||
|
||||
encryptedContent, err := os.ReadFile(encryptedFilePath)
|
||||
require.NoError(t, err, "Couldn't read encrypted file")
|
||||
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
|
||||
|
||||
encryptedFileReader, _ := os.Open(encryptedFilePath)
|
||||
defer encryptedFileReader.Close()
|
||||
encryptedFileReader, err := os.Open(encryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer logs.CloseAndLogErr(encryptedFileReader)
|
||||
|
||||
decryptedFileWriter, _ := os.Create(decryptedFilePath)
|
||||
defer decryptedFileWriter.Close()
|
||||
decryptedFileWriter, err := os.Create(decryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer logs.CloseAndLogErr(decryptedFileWriter)
|
||||
|
||||
decryptedReader, err := decrypt(encryptedFileReader, []byte(passphrase))
|
||||
require.NoError(t, err, "Failed to decrypt file")
|
||||
|
||||
io.Copy(decryptedFileWriter, decryptedReader)
|
||||
_, err = io.Copy(decryptedFileWriter, decryptedReader)
|
||||
require.NoError(t, err)
|
||||
|
||||
decryptedContent, _ := os.ReadFile(decryptedFilePath)
|
||||
decryptedContent, err := os.ReadFile(decryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, content, decryptedContent, "Original and decrypted content should match")
|
||||
}
|
||||
|
||||
@@ -199,16 +210,19 @@ func Test_encryptAndDecrypt_withTheSamePasswordSmallFile(t *testing.T) {
|
||||
)
|
||||
|
||||
content := randBytes(500)
|
||||
os.WriteFile(originFilePath, content, 0600)
|
||||
err := os.WriteFile(originFilePath, content, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
originFile, _ := os.Open(originFilePath)
|
||||
defer originFile.Close()
|
||||
originFile, err := os.Open(originFilePath)
|
||||
require.NoError(t, err)
|
||||
defer logs.CloseAndLogErr(originFile)
|
||||
|
||||
encryptedFileWriter, _ := os.Create(encryptedFilePath)
|
||||
encryptedFileWriter, err := os.Create(encryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
err := encrypt(originFile, encryptedFileWriter, []byte("passphrase"))
|
||||
err = encrypt(originFile, encryptedFileWriter, []byte("passphrase"))
|
||||
require.NoError(t, err, "Failed to encrypt a file")
|
||||
encryptedFileWriter.Close()
|
||||
logs.CloseAndLogErr(encryptedFileWriter)
|
||||
|
||||
encryptedContent, err := os.ReadFile(encryptedFilePath)
|
||||
require.NoError(t, err, "Couldn't read encrypted file")
|
||||
@@ -216,11 +230,11 @@ func Test_encryptAndDecrypt_withTheSamePasswordSmallFile(t *testing.T) {
|
||||
|
||||
encryptedFileReader, err := os.Open(encryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer encryptedFileReader.Close()
|
||||
defer logs.CloseAndLogErr(encryptedFileReader)
|
||||
|
||||
decryptedFileWriter, err := os.Create(decryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer decryptedFileWriter.Close()
|
||||
defer logs.CloseAndLogErr(decryptedFileWriter)
|
||||
|
||||
decryptedReader, err := decrypt(encryptedFileReader, []byte("passphrase"))
|
||||
require.NoError(t, err, "Failed to decrypt file")
|
||||
@@ -258,11 +272,11 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||
|
||||
originFile, err := os.Open(originFilePath)
|
||||
require.NoError(t, err)
|
||||
defer originFile.Close()
|
||||
defer logs.CloseAndLogErr(originFile)
|
||||
|
||||
encryptedFileWriter, err := os.Create(encryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer encryptedFileWriter.Close()
|
||||
defer logs.CloseAndLogErr(encryptedFileWriter)
|
||||
|
||||
err = encrypt(originFile, encryptedFileWriter, []byte(""))
|
||||
require.NoError(t, err, "Failed to encrypt a file")
|
||||
@@ -273,11 +287,11 @@ func Test_encryptAndDecrypt_withEmptyPassword(t *testing.T) {
|
||||
|
||||
encryptedFileReader, err := os.Open(encryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer encryptedFileReader.Close()
|
||||
defer logs.CloseAndLogErr(encryptedFileReader)
|
||||
|
||||
decryptedFileWriter, err := os.Create(decryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer decryptedFileWriter.Close()
|
||||
defer logs.CloseAndLogErr(decryptedFileWriter)
|
||||
|
||||
decryptedReader, err := decrypt(encryptedFileReader, []byte(""))
|
||||
require.NoError(t, err, "Failed to decrypt file")
|
||||
@@ -310,25 +324,30 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
|
||||
)
|
||||
|
||||
content := randBytes(1034)
|
||||
os.WriteFile(originFilePath, content, 0600)
|
||||
err := os.WriteFile(originFilePath, content, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
originFile, _ := os.Open(originFilePath)
|
||||
defer originFile.Close()
|
||||
originFile, err := os.Open(originFilePath)
|
||||
require.NoError(t, err)
|
||||
defer logs.CloseAndLogErr(originFile)
|
||||
|
||||
encryptedFileWriter, _ := os.Create(encryptedFilePath)
|
||||
defer encryptedFileWriter.Close()
|
||||
encryptedFileWriter, err := os.Create(encryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer logs.CloseAndLogErr(encryptedFileWriter)
|
||||
|
||||
err := encrypt(originFile, encryptedFileWriter, []byte("passphrase"))
|
||||
err = encrypt(originFile, encryptedFileWriter, []byte("passphrase"))
|
||||
require.NoError(t, err, "Failed to encrypt a file")
|
||||
encryptedContent, err := os.ReadFile(encryptedFilePath)
|
||||
require.NoError(t, err, "Couldn't read encrypted file")
|
||||
assert.NotEqual(t, encryptedContent, content, "Content wasn't encrypted")
|
||||
|
||||
encryptedFileReader, _ := os.Open(encryptedFilePath)
|
||||
defer encryptedFileReader.Close()
|
||||
encryptedFileReader, err := os.Open(encryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer logs.CloseAndLogErr(encryptedFileReader)
|
||||
|
||||
decryptedFileWriter, _ := os.Create(decryptedFilePath)
|
||||
defer decryptedFileWriter.Close()
|
||||
decryptedFileWriter, err := os.Create(decryptedFilePath)
|
||||
require.NoError(t, err)
|
||||
defer logs.CloseAndLogErr(decryptedFileWriter)
|
||||
|
||||
_, err = decrypt(encryptedFileReader, []byte("garbage"))
|
||||
require.Error(t, err, "Should not allow decrypt with wrong passphrase")
|
||||
|
||||
@@ -98,18 +98,36 @@ func Test_NeedsEncryptionMigration(t *testing.T) {
|
||||
// Special case. If portainer.db and portainer.edb exist.
|
||||
dbFile1 := path.Join(connection.Path, DatabaseFileName)
|
||||
f, _ := os.Create(dbFile1)
|
||||
f.Close()
|
||||
defer os.Remove(dbFile1)
|
||||
|
||||
err := f.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err := os.Remove(dbFile1)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
dbFile2 := path.Join(connection.Path, EncryptedDatabaseFileName)
|
||||
f, _ = os.Create(dbFile2)
|
||||
f.Close()
|
||||
defer os.Remove(dbFile2)
|
||||
|
||||
err = f.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err := os.Remove(dbFile2)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
} else if tc.dbname != "" {
|
||||
dbFile := path.Join(connection.Path, tc.dbname)
|
||||
f, _ := os.Create(dbFile)
|
||||
f.Close()
|
||||
defer os.Remove(dbFile)
|
||||
|
||||
err := f.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err := os.Remove(dbFile)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
}
|
||||
|
||||
if tc.key {
|
||||
@@ -136,7 +154,8 @@ func TestDBCompaction(t *testing.T) {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Put([]byte("key"), []byte("value"))
|
||||
err = b.Put([]byte("key"), []byte("value"))
|
||||
require.NoError(t, err)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ package boltdb
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/segmentio/encoding/json"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
@@ -37,7 +38,7 @@ func (c *DbConnection) ExportJSON(databasePath string, metadata bool) ([]byte, e
|
||||
if err != nil {
|
||||
return []byte("{}"), err
|
||||
}
|
||||
defer connection.Close()
|
||||
defer logs.CloseAndLogErr(connection)
|
||||
|
||||
backup := make(map[string]any)
|
||||
if metadata {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testBucketName = "test-bucket"
|
||||
@@ -17,70 +18,55 @@ type testStruct struct {
|
||||
}
|
||||
|
||||
func TestTxs(t *testing.T) {
|
||||
conn := DbConnection{
|
||||
Path: t.TempDir(),
|
||||
}
|
||||
conn := DbConnection{Path: t.TempDir()}
|
||||
|
||||
err := conn.Open()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := conn.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Error propagation
|
||||
err = conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return errors.New("this is an error")
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("an error was expected, got nil instead")
|
||||
}
|
||||
require.Error(t, err)
|
||||
|
||||
// Create an object
|
||||
newObj := testStruct{
|
||||
Key: "key",
|
||||
Value: "value",
|
||||
}
|
||||
newObj := testStruct{Key: "key", Value: "value"}
|
||||
|
||||
err = conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
err = tx.SetServiceName(testBucketName)
|
||||
if err != nil {
|
||||
if err := tx.SetServiceName(testBucketName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.CreateObjectWithId(testBucketName, testId, newObj)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
obj := testStruct{}
|
||||
err = conn.ViewTx(func(tx portainer.Transaction) error {
|
||||
return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if obj.Key != newObj.Key || obj.Value != newObj.Value {
|
||||
t.Fatalf("expected %s:%s, got %s:%s instead", newObj.Key, newObj.Value, obj.Key, obj.Value)
|
||||
}
|
||||
|
||||
// Update an object
|
||||
updatedObj := testStruct{
|
||||
Key: "updated-key",
|
||||
Value: "updated-value",
|
||||
}
|
||||
updatedObj := testStruct{Key: "updated-key", Value: "updated-value"}
|
||||
|
||||
err = conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return tx.UpdateObject(testBucketName, conn.ConvertToKey(testId), &updatedObj)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = conn.ViewTx(func(tx portainer.Transaction) error {
|
||||
return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if obj.Key != updatedObj.Key || obj.Value != updatedObj.Value {
|
||||
t.Fatalf("expected %s:%s, got %s:%s instead", updatedObj.Key, updatedObj.Value, obj.Key, obj.Value)
|
||||
@@ -90,16 +76,12 @@ func TestTxs(t *testing.T) {
|
||||
err = conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return tx.DeleteObject(testBucketName, conn.ConvertToKey(testId))
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
err = conn.ViewTx(func(tx portainer.Transaction) error {
|
||||
return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj)
|
||||
})
|
||||
if !dataservices.IsErrObjectNotFound(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.True(t, dataservices.IsErrObjectNotFound(err))
|
||||
|
||||
// Get next identifier
|
||||
err = conn.UpdateTx(func(tx portainer.Transaction) error {
|
||||
@@ -112,15 +94,11 @@ func TestTxs(t *testing.T) {
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try to write in a read transaction
|
||||
err = conn.ViewTx(func(tx portainer.Transaction) error {
|
||||
return tx.CreateObjectWithId(testBucketName, testId, newObj)
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("an error was expected, got nil instead")
|
||||
}
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -14,7 +15,7 @@ func TestUpdate(t *testing.T) {
|
||||
err := conn.Open()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer conn.Close()
|
||||
defer logs.CloseAndLogErr(conn)
|
||||
|
||||
service, err := NewService(conn, func(portainer.Transaction, portainer.EdgeStackID) {})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/dataservices/edgestack"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -20,7 +21,7 @@ func TestUpdateRelation(t *testing.T) {
|
||||
err := conn.Open()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer conn.Close()
|
||||
defer logs.CloseAndLogErr(conn)
|
||||
|
||||
service, err := NewService(conn)
|
||||
require.NoError(t, err)
|
||||
@@ -109,7 +110,7 @@ func TestAddEndpointRelationsForEdgeStack(t *testing.T) {
|
||||
err := conn.Open()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer conn.Close()
|
||||
defer logs.CloseAndLogErr(conn)
|
||||
|
||||
service, err := NewService(conn)
|
||||
require.NoError(t, err)
|
||||
@@ -128,7 +129,7 @@ func TestEndpointRelations(t *testing.T) {
|
||||
err := conn.Open()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer conn.Close()
|
||||
defer logs.CloseAndLogErr(conn)
|
||||
|
||||
service, err := NewService(conn)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -57,15 +57,17 @@ func (store *Store) Restore() error {
|
||||
}
|
||||
|
||||
func (store *Store) RestoreFromFile(backupFilename string) error {
|
||||
store.Close()
|
||||
if err := store.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := store.fileService.Copy(backupFilename, store.connection.GetDatabaseFilePath(), true); err != nil {
|
||||
return fmt.Errorf("unable to restore backup file %q. err: %w", backupFilename, err)
|
||||
}
|
||||
|
||||
log.Info().Str("from", backupFilename).Str("to", store.connection.GetDatabaseFilePath()).Msgf("database restored")
|
||||
|
||||
_, err := store.Open()
|
||||
if err != nil {
|
||||
if _, err := store.Open(); err != nil {
|
||||
return fmt.Errorf("unable to determine version of restored portainer backup file: %w", err)
|
||||
}
|
||||
|
||||
@@ -87,6 +89,7 @@ func (store *Store) createBackupPath() error {
|
||||
return fmt.Errorf("unable to create backup folder: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,12 @@ func TestBackup(t *testing.T) {
|
||||
Edition: int(portainer.PortainerCE),
|
||||
SchemaVersion: portainer.APIVersion,
|
||||
}
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
store.Backup("")
|
||||
|
||||
err := store.VersionService.UpdateVersion(&v)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.Backup("")
|
||||
require.NoError(t, err)
|
||||
|
||||
if !isFileExist(backupFileName) {
|
||||
t.Errorf("Expect backup file to be created %s", backupFileName)
|
||||
@@ -55,10 +59,14 @@ func TestRestore(t *testing.T) {
|
||||
updateEdition(store, portainer.PortainerCE)
|
||||
updateVersion(store, "2.4")
|
||||
|
||||
store.Backup("")
|
||||
_, err := store.Backup("")
|
||||
require.NoError(t, err)
|
||||
|
||||
updateVersion(store, "2.16")
|
||||
testVersion(store, "2.16", t)
|
||||
store.Restore()
|
||||
|
||||
err = store.Restore()
|
||||
require.NoError(t, err)
|
||||
|
||||
// check if the restore is successful and the version is correct
|
||||
testVersion(store, "2.4", t)
|
||||
@@ -68,11 +76,16 @@ func TestRestore(t *testing.T) {
|
||||
// override and set initial db version and edition
|
||||
updateEdition(store, portainer.PortainerCE)
|
||||
updateVersion(store, "2.4")
|
||||
store.Backup("")
|
||||
|
||||
_, err := store.Backup("")
|
||||
require.NoError(t, err)
|
||||
|
||||
updateVersion(store, "2.14")
|
||||
updateVersion(store, "2.16")
|
||||
testVersion(store, "2.16", t)
|
||||
store.Restore()
|
||||
|
||||
err = store.Restore()
|
||||
require.NoError(t, err)
|
||||
|
||||
// check if the restore is successful and the version is correct
|
||||
testVersion(store, "2.4", t)
|
||||
|
||||
+22
-37
@@ -45,27 +45,25 @@ func (store *Store) Open() (newStore bool, err error) {
|
||||
}
|
||||
|
||||
if err := store.encryptDB(); err != nil {
|
||||
store.RestoreFromFile(backupFilename) // restore from backup if encryption fails
|
||||
return false, err
|
||||
innerErr := store.RestoreFromFile(backupFilename) // restore from backup if encryption fails
|
||||
return false, errors.Join(err, innerErr)
|
||||
}
|
||||
}
|
||||
|
||||
err = store.connection.Open()
|
||||
if err != nil {
|
||||
if err := store.connection.Open(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = store.initServices()
|
||||
if err != nil {
|
||||
if err := store.initServices(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If no settings object exists then assume we have a new store
|
||||
_, err = store.SettingsService.Settings()
|
||||
if err != nil {
|
||||
if _, err := store.SettingsService.Settings(); err != nil {
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -78,19 +76,13 @@ func (store *Store) Close() error {
|
||||
|
||||
func (store *Store) UpdateTx(fn func(dataservices.DataStoreTx) error) error {
|
||||
return store.connection.UpdateTx(func(tx portainer.Transaction) error {
|
||||
return fn(&StoreTx{
|
||||
store: store,
|
||||
tx: tx,
|
||||
})
|
||||
return fn(&StoreTx{store: store, tx: tx})
|
||||
})
|
||||
}
|
||||
|
||||
func (store *Store) ViewTx(fn func(dataservices.DataStoreTx) error) error {
|
||||
return store.connection.ViewTx(func(tx portainer.Transaction) error {
|
||||
return fn(&StoreTx{
|
||||
store: store,
|
||||
tx: tx,
|
||||
})
|
||||
return fn(&StoreTx{store: store, tx: tx})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -105,6 +97,7 @@ func (store *Store) CheckCurrentEdition() error {
|
||||
if store.edition() != portainer.Edition {
|
||||
return portainerErrors.ErrWrongDBEdition
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -113,6 +106,7 @@ func (store *Store) edition() portainer.SoftwareEdition {
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
edition = portainer.PortainerCE
|
||||
}
|
||||
|
||||
return edition
|
||||
}
|
||||
|
||||
@@ -131,13 +125,11 @@ func (store *Store) Rollback(force bool) error {
|
||||
|
||||
func (store *Store) encryptDB() error {
|
||||
store.connection.SetEncrypted(false)
|
||||
err := store.connection.Open()
|
||||
if err != nil {
|
||||
if err := store.connection.Open(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.initServices()
|
||||
if err != nil {
|
||||
if err := store.initServices(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -150,8 +142,7 @@ func (store *Store) encryptDB() error {
|
||||
|
||||
log.Info().Str("filename", exportFilename).Msg("exporting database backup")
|
||||
|
||||
err = store.Export(exportFilename)
|
||||
if err != nil {
|
||||
if err := store.Export(exportFilename); err != nil {
|
||||
log.Error().Str("filename", exportFilename).Err(err).Msg("failed to export")
|
||||
|
||||
return err
|
||||
@@ -160,15 +151,7 @@ func (store *Store) encryptDB() error {
|
||||
log.Info().Msg("database backup exported")
|
||||
|
||||
// Close existing un-encrypted db so that we can delete the file later
|
||||
store.connection.Close()
|
||||
|
||||
// Tell the db layer to create an encrypted db when opened
|
||||
store.connection.SetEncrypted(true)
|
||||
store.connection.Open()
|
||||
|
||||
// We have to init services before import
|
||||
err = store.initServices()
|
||||
if err != nil {
|
||||
if err := store.connection.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -176,23 +159,25 @@ func (store *Store) encryptDB() error {
|
||||
log.Error().Err(err).Msg("failed to import database backup")
|
||||
|
||||
// Remove the new encrypted file that we failed to import
|
||||
os.Remove(store.connection.GetDatabaseFilePath())
|
||||
if err := os.Remove(store.connection.GetDatabaseFilePath()); err != nil {
|
||||
log.Error().Msg("failed to remove the file after import failure")
|
||||
}
|
||||
|
||||
log.Fatal().Err(portainerErrors.ErrDBImportFailed).Msg("")
|
||||
}
|
||||
|
||||
err = os.Remove(oldFilename)
|
||||
if err != nil {
|
||||
if err := os.Remove(oldFilename); err != nil {
|
||||
log.Error().Msg("failed to remove the un-encrypted db file")
|
||||
}
|
||||
|
||||
err = os.Remove(exportFilename)
|
||||
if err != nil {
|
||||
if err := os.Remove(exportFilename); err != nil {
|
||||
log.Error().Msg("failed to remove the json backup file")
|
||||
}
|
||||
|
||||
// Close db connection
|
||||
store.connection.Close()
|
||||
if err := store.connection.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msg("database successfully encrypted")
|
||||
|
||||
|
||||
@@ -51,13 +51,13 @@ func TestStoreFull(t *testing.T) {
|
||||
|
||||
func (store *Store) testEnvironments(t *testing.T) {
|
||||
id := store.CreateEndpoint(t, "local", portainer.KubernetesLocalEnvironment, "", true)
|
||||
store.CreateEndpointRelation(id)
|
||||
store.CreateEndpointRelation(t, id)
|
||||
|
||||
id = store.CreateEndpoint(t, "agent", portainer.AgentOnDockerEnvironment, agentOnDockerEnvironmentUrl, true)
|
||||
store.CreateEndpointRelation(id)
|
||||
store.CreateEndpointRelation(t, id)
|
||||
|
||||
id = store.CreateEndpoint(t, "edge", portainer.EdgeAgentOnKubernetesEnvironment, edgeAgentOnKubernetesEnvironmentUrl, true)
|
||||
store.CreateEndpointRelation(id)
|
||||
store.CreateEndpointRelation(t, id)
|
||||
}
|
||||
|
||||
func newEndpoint(endpointType portainer.EndpointType, id portainer.EndpointID, name, URL string, TLS bool) *portainer.Endpoint {
|
||||
@@ -131,7 +131,9 @@ func (store *Store) CreateEndpoint(t *testing.T, name string, endpointType porta
|
||||
}
|
||||
|
||||
setEndpointAuthorizations(expectedEndpoint)
|
||||
store.Endpoint().Create(expectedEndpoint)
|
||||
|
||||
err := store.Endpoint().Create(expectedEndpoint)
|
||||
require.NoError(t, err)
|
||||
|
||||
endpoint, err := store.Endpoint().Endpoint(id)
|
||||
require.NoError(t, err, "Endpoint() should not return an error")
|
||||
@@ -140,13 +142,14 @@ func (store *Store) CreateEndpoint(t *testing.T, name string, endpointType porta
|
||||
return endpoint.ID
|
||||
}
|
||||
|
||||
func (store *Store) CreateEndpointRelation(id portainer.EndpointID) {
|
||||
func (store *Store) CreateEndpointRelation(t *testing.T, id portainer.EndpointID) {
|
||||
relation := &portainer.EndpointRelation{
|
||||
EndpointID: id,
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
}
|
||||
|
||||
store.EndpointRelation().Create(relation)
|
||||
err := store.EndpointRelation().Create(relation)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (store *Store) testSSLSettings(t *testing.T) {
|
||||
@@ -158,7 +161,8 @@ func (store *Store) testSSLSettings(t *testing.T) {
|
||||
SelfSigned: true,
|
||||
}
|
||||
|
||||
store.SSLSettings().UpdateSettings(ssl)
|
||||
err := store.SSLSettings().UpdateSettings(ssl)
|
||||
require.NoError(t, err)
|
||||
|
||||
settings, err := store.SSLSettings().Settings()
|
||||
require.NoError(t, err, "Get sslsettings should succeed")
|
||||
@@ -271,7 +275,8 @@ func (store *Store) testCustomTemplates(t *testing.T) {
|
||||
CreatedByUserID: 10,
|
||||
}
|
||||
|
||||
customTemplate.Create(expectedTemplate)
|
||||
err := customTemplate.Create(expectedTemplate)
|
||||
require.NoError(t, err)
|
||||
|
||||
actualTemplate, err := customTemplate.Read(expectedTemplate.ID)
|
||||
require.NoError(t, err, "CustomTemplate should not return an error")
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/datastore/migrator"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -53,9 +54,11 @@ func TestMigrateData(t *testing.T) {
|
||||
}
|
||||
|
||||
testVersion(store, portainer.APIVersion, t)
|
||||
store.Close()
|
||||
err := store.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
newStore, _ = store.Open()
|
||||
newStore, err = store.Open()
|
||||
require.NoError(t, err)
|
||||
if newStore {
|
||||
t.Error("Expect store to NOT be new DB")
|
||||
}
|
||||
@@ -63,8 +66,11 @@ func TestMigrateData(t *testing.T) {
|
||||
|
||||
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
store.VersionService.UpdateVersion(&models.Version{SchemaVersion: "1.0", Edition: int(portainer.PortainerCE)})
|
||||
store.MigrateData()
|
||||
err := store.VersionService.UpdateVersion(&models.Version{SchemaVersion: "2.0", Edition: int(portainer.PortainerCE)})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.MigrateData()
|
||||
require.NoError(t, err)
|
||||
|
||||
backupfilename := store.backupFilename()
|
||||
if exists, _ := store.fileService.FileExists(backupfilename); !exists {
|
||||
@@ -73,21 +79,28 @@ func TestMigrateData(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("MigrateData should recover and restore backup during migration critical failure", func(t *testing.T) {
|
||||
os.Setenv("PORTAINER_TEST_MIGRATE_FAIL", "FAIL")
|
||||
t.Setenv("PORTAINER_TEST_MIGRATE_FAIL", "FAIL")
|
||||
|
||||
version := "2.15"
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
store.VersionService.UpdateVersion(&models.Version{SchemaVersion: version, Edition: int(portainer.PortainerCE)})
|
||||
store.MigrateData()
|
||||
|
||||
store.Open()
|
||||
err := store.VersionService.UpdateVersion(&models.Version{SchemaVersion: version, Edition: int(portainer.PortainerCE)})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.MigrateData()
|
||||
require.Error(t, err)
|
||||
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
|
||||
t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
store.VersionService.StoreIsUpdating(true)
|
||||
store.MigrateData()
|
||||
|
||||
err := store.VersionService.StoreIsUpdating(true)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.MigrateData()
|
||||
require.Error(t, err)
|
||||
|
||||
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
|
||||
// If the backup file is not blank, then it means a backup was created. We don't want that because we
|
||||
@@ -115,10 +128,12 @@ func TestMigrateData(t *testing.T) {
|
||||
|
||||
if latestMigrations.Version.Equal(semver.MustParse(portainer.APIVersion)) {
|
||||
v.MigratorCount = len(latestMigrations.MigrationFuncs)
|
||||
store.VersionService.UpdateVersion(v)
|
||||
err = store.VersionService.UpdateVersion(v)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
store.MigrateData()
|
||||
err = store.MigrateData()
|
||||
require.NoError(t, err)
|
||||
|
||||
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
|
||||
// If the backup file is not blank, then it means a backup was created. We don't want that because we
|
||||
@@ -141,8 +156,12 @@ func TestMigrateData(t *testing.T) {
|
||||
}
|
||||
|
||||
v.MigratorCount = 1000
|
||||
store.VersionService.UpdateVersion(v)
|
||||
store.MigrateData()
|
||||
|
||||
err = store.VersionService.UpdateVersion(v)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.MigrateData()
|
||||
require.NoError(t, err)
|
||||
|
||||
// If you get an error, it usually means that the backup folder doesn't exist (no backups). Expected!
|
||||
// If the backup file is not blank, then it means a backup was created. We don't want that because we
|
||||
@@ -158,14 +177,14 @@ func TestRollback(t *testing.T) {
|
||||
t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
|
||||
version := "2.11"
|
||||
|
||||
v := models.Version{
|
||||
SchemaVersion: version,
|
||||
}
|
||||
v := models.Version{SchemaVersion: version}
|
||||
|
||||
_, store := MustNewTestStore(t, false, false)
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
|
||||
_, err := store.Backup("")
|
||||
err := store.VersionService.UpdateVersion(&v)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.Backup("")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
@@ -184,7 +203,9 @@ func TestRollback(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
store.Open()
|
||||
_, err = store.Open()
|
||||
require.NoError(t, err)
|
||||
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
|
||||
@@ -197,9 +218,11 @@ func TestRollback(t *testing.T) {
|
||||
}
|
||||
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
|
||||
_, err := store.Backup("")
|
||||
err := store.VersionService.UpdateVersion(&v)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.Backup("")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
@@ -218,7 +241,8 @@ func TestRollback(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
store.Open()
|
||||
_, err = store.Open()
|
||||
require.NoError(t, err)
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
}
|
||||
@@ -237,17 +261,17 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
|
||||
fmt.Println("store.path=", store.GetConnection().GetDatabaseFilePath())
|
||||
store.connection.DeleteObject("version", []byte("VERSION"))
|
||||
|
||||
err = store.connection.DeleteObject("version", []byte("VERSION"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// defer teardown()
|
||||
err = importJSON(t, bytes.NewReader(srcJSON), store)
|
||||
if err != nil {
|
||||
if err := importJSON(t, bytes.NewReader(srcJSON), store); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run the actual migrations on our input database.
|
||||
err = store.MigrateData()
|
||||
if err != nil {
|
||||
if err := store.MigrateData(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -260,8 +284,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
|
||||
}
|
||||
|
||||
v.InstanceID = "463d5c47-0ea5-4aca-85b1-405ceefee254"
|
||||
err = store.VersionService.UpdateVersion(v)
|
||||
if err != nil {
|
||||
if err := store.VersionService.UpdateVersion(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -270,10 +293,10 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
|
||||
// exportJson rather than ExportRaw. The exportJson function allows us to
|
||||
// strip out the metadata which we don't want for our tests.
|
||||
// TODO: update connection interface in CE to allow us to use ExportRaw and pass meta false
|
||||
err = store.connection.Close()
|
||||
if err != nil {
|
||||
if err := store.connection.Close(); err != nil {
|
||||
t.Fatalf("err closing bolt connection: %v", err)
|
||||
}
|
||||
|
||||
con, ok := store.connection.(*boltdb.DbConnection)
|
||||
if !ok {
|
||||
t.Fatalf("backing database is not using boltdb, but the migrations test requires it")
|
||||
@@ -302,11 +325,15 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
|
||||
// Compare the result we got with the one we wanted.
|
||||
if diff := cmp.Diff(wantJSON, gotJSON); diff != "" {
|
||||
gotPath := filepath.Join(os.TempDir(), "portainer-migrator-test-fail.json")
|
||||
os.WriteFile(
|
||||
err = os.WriteFile(
|
||||
gotPath,
|
||||
gotJSON,
|
||||
0o600,
|
||||
)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("failed writing migrated output to temp file")
|
||||
}
|
||||
|
||||
t.Errorf(
|
||||
"migrate data from %s to %s failed\nwrote migrated input to %s\nmismatch (-want +got):\n%s",
|
||||
srcPath,
|
||||
|
||||
@@ -105,12 +105,18 @@ func (store *Store) getOrMigrateLegacyVersion() (*models.Version, error) {
|
||||
|
||||
// finishMigrateLegacyVersion writes the new version to the DB and removes the old version keys from the DB
|
||||
func (store *Store) finishMigrateLegacyVersion(versionToWrite *models.Version) error {
|
||||
err := store.VersionService.UpdateVersion(versionToWrite)
|
||||
if err := store.VersionService.UpdateVersion(versionToWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove legacy keys if present
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyDBVersionKey))
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyEditionKey))
|
||||
store.connection.DeleteObject(bucketName, []byte(legacyInstanceKey))
|
||||
if err := store.connection.DeleteObject(bucketName, []byte(legacyDBVersionKey)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
if err := store.connection.DeleteObject(bucketName, []byte(legacyEditionKey)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.connection.DeleteObject(bucketName, []byte(legacyInstanceKey))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/dataservices/edgegroup"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ func TestMigrateEdgeGroupEndpointsToRoars_2_33_0Idempotency(t *testing.T) {
|
||||
err := conn.Open()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer conn.Close()
|
||||
defer logs.CloseAndLogErr(conn)
|
||||
|
||||
edgeGroupService, err := edgegroup.NewService(conn)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -77,8 +77,12 @@ func (m *Migrator) updateRegistriesToDB32() error {
|
||||
Namespaces: []string{},
|
||||
}
|
||||
}
|
||||
m.registryService.Update(registry.ID, ®istry)
|
||||
|
||||
if err := m.registryService.Update(registry.ID, ®istry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -121,10 +125,11 @@ func (m *Migrator) updateDockerhubToDB32() error {
|
||||
if !migrated {
|
||||
// keep this one entry
|
||||
migrated = true
|
||||
} else {
|
||||
// delete subsequent duplicates
|
||||
m.registryService.Delete(r.ID)
|
||||
} else if err := m.registryService.Delete(r.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +143,6 @@ func (m *Migrator) updateDockerhubToDB32() error {
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
|
||||
if endpoint.Type != portainer.KubernetesLocalEnvironment &&
|
||||
endpoint.Type != portainer.AgentOnKubernetesEnvironment &&
|
||||
endpoint.Type != portainer.EdgeAgentOnKubernetesEnvironment {
|
||||
@@ -146,18 +150,14 @@ func (m *Migrator) updateDockerhubToDB32() error {
|
||||
userAccessPolicies := portainer.UserAccessPolicies{}
|
||||
for userId := range endpoint.UserAccessPolicies {
|
||||
if _, found := endpoint.UserAccessPolicies[userId]; found {
|
||||
userAccessPolicies[userId] = portainer.AccessPolicy{
|
||||
RoleID: 0,
|
||||
}
|
||||
userAccessPolicies[userId] = portainer.AccessPolicy{RoleID: 0}
|
||||
}
|
||||
}
|
||||
|
||||
teamAccessPolicies := portainer.TeamAccessPolicies{}
|
||||
for teamId := range endpoint.TeamAccessPolicies {
|
||||
if _, found := endpoint.TeamAccessPolicies[teamId]; found {
|
||||
teamAccessPolicies[teamId] = portainer.AccessPolicy{
|
||||
RoleID: 0,
|
||||
}
|
||||
teamAccessPolicies[teamId] = portainer.AccessPolicy{RoleID: 0}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/pendingactions/actions"
|
||||
"github.com/portainer/portainer/api/pendingactions/handlers"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type cleanNAPWithOverridePolicies struct {
|
||||
@@ -16,7 +17,10 @@ func Test_ConvertCleanNAPWithOverridePoliciesPayload(t *testing.T) {
|
||||
t.Run("test ConvertCleanNAPWithOverridePoliciesPayload", func(t *testing.T) {
|
||||
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
defer store.Close()
|
||||
defer func() {
|
||||
err := store.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
gid := portainer.EndpointGroupID(1)
|
||||
|
||||
@@ -92,7 +96,8 @@ func Test_ConvertCleanNAPWithOverridePoliciesPayload(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
store.PendingActions().Delete(d.PendingAction.ID)
|
||||
err = store.PendingActions().Delete(d.PendingAction.ID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
dockerClient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/api/pendingactions/actions"
|
||||
"github.com/portainer/portainer/pkg/endpoints"
|
||||
|
||||
@@ -89,6 +90,7 @@ func (postInitMigrator *PostInitMigrator) createPostInitMigrationPendingAction(e
|
||||
EndpointID: environmentID,
|
||||
Action: actions.PostInitMigrateEnvironment,
|
||||
}
|
||||
|
||||
pendingActions, err := postInitMigrator.dataStore.PendingActions().ReadAll()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve pending actions: %w", err)
|
||||
@@ -119,11 +121,12 @@ func (migrator *PostInitMigrator) MigrateEnvironment(environment *portainer.Endp
|
||||
log.Error().Err(err).Msgf("Error creating kubeclient for environment: %d", environment.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// if one environment fails, it is logged and the next migration runs. The error is returned at the end and handled by pending actions
|
||||
err = migrator.MigrateIngresses(*environment, kubeclient)
|
||||
if err != nil {
|
||||
if err := migrator.MigrateIngresses(*environment, kubeclient); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
case endpointutils.IsDockerEndpoint(environment):
|
||||
// get the docker client for the environment, and skip all docker migrations if there's an error
|
||||
@@ -132,8 +135,11 @@ func (migrator *PostInitMigrator) MigrateEnvironment(environment *portainer.Endp
|
||||
log.Error().Err(err).Msgf("Error creating docker client for environment: %d", environment.ID)
|
||||
return err
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
migrator.MigrateGPUs(*environment, dockerClient)
|
||||
defer logs.CloseAndLogErr(dockerClient)
|
||||
|
||||
if err := migrator.MigrateGPUs(*environment, dockerClient); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+66
-22
@@ -625,85 +625,129 @@ func (store *Store) Import(filename string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
store.Version().UpdateVersion(&backup.Version)
|
||||
err = store.Version().UpdateVersion(&backup.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range backup.CustomTemplate {
|
||||
store.CustomTemplate().Update(v.ID, &v)
|
||||
if err := store.CustomTemplate().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the custom template in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.EdgeGroup {
|
||||
store.EdgeGroup().Update(v.ID, &v)
|
||||
if err := store.EdgeGroup().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the edge group in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.EdgeJob {
|
||||
store.EdgeJob().Update(v.ID, &v)
|
||||
if err := store.EdgeJob().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the edge job in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.EdgeStack {
|
||||
store.EdgeStack().UpdateEdgeStack(v.ID, &v)
|
||||
if err := store.EdgeStack().UpdateEdgeStack(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the edge stack in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.Endpoint {
|
||||
store.Endpoint().UpdateEndpoint(v.ID, &v)
|
||||
if err := store.Endpoint().UpdateEndpoint(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the endpoint in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.EndpointGroup {
|
||||
store.EndpointGroup().Update(v.ID, &v)
|
||||
if err := store.EndpointGroup().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the endpoint group in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.EndpointRelation {
|
||||
store.EndpointRelation().UpdateEndpointRelation(v.EndpointID, &v)
|
||||
if err := store.EndpointRelation().UpdateEndpointRelation(v.EndpointID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the endpoint relation in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.HelmUserRepository {
|
||||
store.HelmUserRepository().Update(v.ID, &v)
|
||||
if err := store.HelmUserRepository().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the helm user repository in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.Registry {
|
||||
store.Registry().Update(v.ID, &v)
|
||||
if err := store.Registry().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the registry in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.ResourceControl {
|
||||
store.ResourceControl().Update(v.ID, &v)
|
||||
if err := store.ResourceControl().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the resource control in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.Role {
|
||||
store.Role().Update(v.ID, &v)
|
||||
if err := store.Role().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the role in the database")
|
||||
}
|
||||
}
|
||||
|
||||
store.Settings().UpdateSettings(&backup.Settings)
|
||||
store.SSLSettings().UpdateSettings(&backup.SSLSettings)
|
||||
if err := store.Settings().UpdateSettings(&backup.Settings); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the settings in the database")
|
||||
}
|
||||
|
||||
if err := store.SSLSettings().UpdateSettings(&backup.SSLSettings); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the SSL settings in the database")
|
||||
}
|
||||
|
||||
for _, v := range backup.Snapshot {
|
||||
store.Snapshot().Update(v.EndpointID, &v)
|
||||
if err := store.Snapshot().Update(v.EndpointID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the snapshot in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.Stack {
|
||||
store.Stack().Update(v.ID, &v)
|
||||
if err := store.Stack().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the stack in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.Tag {
|
||||
store.Tag().Update(v.ID, &v)
|
||||
if err := store.Tag().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the tag in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.TeamMembership {
|
||||
store.TeamMembership().Update(v.ID, &v)
|
||||
if err := store.TeamMembership().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the team membership in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.Team {
|
||||
store.Team().Update(v.ID, &v)
|
||||
if err := store.Team().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the team in the database")
|
||||
}
|
||||
}
|
||||
|
||||
store.TunnelServer().UpdateInfo(&backup.TunnelServer)
|
||||
if err := store.TunnelServer().UpdateInfo(&backup.TunnelServer); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the tunnel server info in the database")
|
||||
}
|
||||
|
||||
for _, user := range backup.User {
|
||||
if err := store.User().Update(user.ID, &user); err != nil {
|
||||
log.Debug().Str("user", fmt.Sprintf("%+v", user)).Err(err).Msg("failed to update the user in the database")
|
||||
log.Warn().Str("user", fmt.Sprintf("%+v", user)).Err(err).Msg("failed to update the user in the database")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range backup.Webhook {
|
||||
store.Webhook().Update(v.ID, &v)
|
||||
if err := store.Webhook().Update(v.ID, &v); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to update the webhook in the database")
|
||||
}
|
||||
}
|
||||
|
||||
return store.connection.RestoreMetadata(backup.Metadata)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/client"
|
||||
@@ -143,11 +144,16 @@ func (t *NodeNameTransport) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
resp.Body.Close()
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp.Body.Close()
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
|
||||
resp.Body = io.NopCloser(bytes.NewReader(body))
|
||||
|
||||
|
||||
+21
-8
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/docker/images"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -75,7 +76,7 @@ func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.End
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create client error")
|
||||
}
|
||||
defer cli.Close()
|
||||
defer logs.CloseAndLogErr(cli)
|
||||
|
||||
log.Debug().Str("container_id", containerId).Msg("starting to fetch container information")
|
||||
|
||||
@@ -146,13 +147,19 @@ func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.End
|
||||
|
||||
c.sr.push(func() {
|
||||
log.Debug().Str("container_id", containerId).Str("container", container.Name).Msg("restoring the container")
|
||||
cli.ContainerRename(ctx, containerId, container.Name)
|
||||
|
||||
for _, network := range container.NetworkSettings.Networks {
|
||||
cli.NetworkConnect(ctx, network.NetworkID, containerId, network)
|
||||
if err := cli.ContainerRename(ctx, containerId, container.Name); err != nil {
|
||||
log.Warn().Err(err).Msg("failure to rename container")
|
||||
}
|
||||
|
||||
cli.ContainerStart(ctx, containerId, dockercontainer.StartOptions{})
|
||||
for _, network := range container.NetworkSettings.Networks {
|
||||
if err := cli.NetworkConnect(ctx, network.NetworkID, containerId, network); err != nil {
|
||||
log.Warn().Err(err).Msg("failure to connect container to network")
|
||||
}
|
||||
}
|
||||
|
||||
if err := cli.ContainerStart(ctx, containerId, dockercontainer.StartOptions{}); err != nil {
|
||||
log.Warn().Err(err).Msg("failure to start container")
|
||||
}
|
||||
})
|
||||
|
||||
log.Debug().Str("container", strings.Split(container.Name, "/")[1]).Msg("starting to create a new container")
|
||||
@@ -175,8 +182,14 @@ func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.End
|
||||
|
||||
c.sr.push(func() {
|
||||
log.Debug().Str("container_id", create.ID).Msg("removing the new container")
|
||||
cli.ContainerStop(ctx, create.ID, dockercontainer.StopOptions{})
|
||||
cli.ContainerRemove(ctx, create.ID, dockercontainer.RemoveOptions{})
|
||||
|
||||
if err := cli.ContainerStop(ctx, create.ID, dockercontainer.StopOptions{}); err != nil {
|
||||
log.Warn().Err(err).Msg("failure to stop container")
|
||||
}
|
||||
|
||||
if err := cli.ContainerRemove(ctx, create.ID, dockercontainer.RemoveOptions{}); err != nil {
|
||||
log.Warn().Err(err).Msg("failure to remove container")
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/client"
|
||||
@@ -42,7 +43,7 @@ func (puller *Puller) Pull(ctx context.Context, img Image) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
defer logs.CloseAndLogErr(out)
|
||||
|
||||
_, err = io.ReadAll(out)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package docker
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/pkg/snapshot"
|
||||
)
|
||||
|
||||
@@ -24,7 +25,7 @@ func (snapshotter *Snapshotter) CreateSnapshot(endpoint *portainer.Endpoint) (*p
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
defer logs.CloseAndLogErr(cli)
|
||||
|
||||
return snapshot.CreateDockerSnapshot(cli)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory"
|
||||
"github.com/portainer/portainer/api/internal/registryutils"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/api/stacks/stackutils"
|
||||
"github.com/portainer/portainer/pkg/libstack"
|
||||
|
||||
@@ -180,7 +181,7 @@ func createEnvFile(stack *portainer.Stack) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer envfile.Close()
|
||||
defer logs.CloseAndLogErr(envfile)
|
||||
|
||||
// Copy from default .env file
|
||||
defaultEnvPath := path.Join(stack.ProjectPath, path.Dir(stack.EntryPoint), ".env")
|
||||
@@ -205,13 +206,14 @@ func copyDefaultEnvFile(w io.Writer, defaultEnvFilePath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer defaultEnvFile.Close()
|
||||
defer logs.CloseAndLogErr(defaultEnvFile)
|
||||
|
||||
if _, err = io.Copy(w, defaultEnvFile); err == nil {
|
||||
if _, err = fmt.Fprintf(w, "\n"); err != nil {
|
||||
return fmt.Errorf("failed to copy default env file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// If couldn't copy the .env file, then ignore the error and try to continue
|
||||
}
|
||||
@@ -223,6 +225,7 @@ func copyConfigEnvVars(w io.Writer, envs []portainer.Pair) error {
|
||||
return fmt.Errorf("failed to copy config env vars: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/pkg/libstack/compose"
|
||||
"github.com/portainer/portainer/pkg/testhelpers"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -25,8 +26,11 @@ const composedContainerName = "compose_wrapper_test"
|
||||
func setup(t *testing.T) (*portainer.Stack, *portainer.Endpoint) {
|
||||
dir := t.TempDir()
|
||||
composeFileName := "compose_wrapper_test.yml"
|
||||
f, _ := os.Create(filepath.Join(dir, composeFileName))
|
||||
f.WriteString(composeFile)
|
||||
f, err := os.Create(filepath.Join(dir, composeFileName))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = f.WriteString(composeFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
stack := &portainer.Stack{
|
||||
ProjectPath: dir,
|
||||
@@ -34,11 +38,7 @@ func setup(t *testing.T) (*portainer.Stack, *portainer.Endpoint) {
|
||||
Name: "project-name",
|
||||
}
|
||||
|
||||
endpoint := &portainer.Endpoint{
|
||||
URL: "unix://",
|
||||
}
|
||||
|
||||
return stack, endpoint
|
||||
return stack, &portainer.Endpoint{URL: "unix://"}
|
||||
}
|
||||
|
||||
func Test_UpAndDown(t *testing.T) {
|
||||
|
||||
@@ -71,7 +71,9 @@ func Test_createEnvFile(t *testing.T) {
|
||||
|
||||
func Test_createEnvFile_mergesDefultAndInplaceEnvVars(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.WriteFile(path.Join(dir, ".env"), []byte("VAR1=VAL1\nVAR2=VAL2\n"), 0600)
|
||||
err := os.WriteFile(path.Join(dir, ".env"), []byte("VAR1=VAL1\nVAR2=VAL2\n"), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
stack := &portainer.Stack{
|
||||
ProjectPath: dir,
|
||||
Env: []portainer.Pair{
|
||||
@@ -83,8 +85,12 @@ func Test_createEnvFile_mergesDefultAndInplaceEnvVars(t *testing.T) {
|
||||
assert.Equal(t, filepath.Join(stack.ProjectPath, "stack.env"), result)
|
||||
require.NoError(t, err)
|
||||
assert.FileExists(t, path.Join(dir, "stack.env"))
|
||||
f, _ := os.Open(path.Join(dir, "stack.env"))
|
||||
content, _ := io.ReadAll(f)
|
||||
|
||||
f, err := os.Open(path.Join(dir, "stack.env"))
|
||||
require.NoError(t, err)
|
||||
|
||||
content, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []byte("VAR1=VAL1\nVAR2=VAL2\n\nVAR1=NEW_VAL1\nVAR3=VAL3\n"), content)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
)
|
||||
|
||||
// CopyPath copies file or directory defined by the path to the toDir path
|
||||
@@ -67,7 +69,7 @@ func copyFile(src, dst string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer from.Close()
|
||||
defer logs.CloseAndLogErr(from)
|
||||
|
||||
// has to include 'execute' bit, otherwise fails. MkdirAll follows `mkdir -m` restrictions
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
@@ -77,7 +79,7 @@ func copyFile(src, dst string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer to.Close()
|
||||
defer logs.CloseAndLogErr(to)
|
||||
|
||||
_, err = io.Copy(to, from)
|
||||
return err
|
||||
|
||||
@@ -19,12 +19,15 @@ func Test_copyFile_returnsError_whenSourceDoesNotExist(t *testing.T) {
|
||||
func Test_copyFile_shouldMakeAbackup(t *testing.T) {
|
||||
tmpdir := t.TempDir()
|
||||
content := []byte("content")
|
||||
os.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
|
||||
|
||||
err := copyFile(path.Join(tmpdir, "origin"), path.Join(tmpdir, "copy"))
|
||||
err := os.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
copyContent, _ := os.ReadFile(path.Join(tmpdir, "copy"))
|
||||
err = copyFile(path.Join(tmpdir, "origin"), path.Join(tmpdir, "copy"))
|
||||
require.NoError(t, err)
|
||||
|
||||
copyContent, err := os.ReadFile(path.Join(tmpdir, "copy"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
@@ -59,10 +62,14 @@ func Test_CopyPath_shouldSkipWhenNotExist(t *testing.T) {
|
||||
func Test_CopyPath_shouldCopyFile(t *testing.T) {
|
||||
tmpdir := t.TempDir()
|
||||
content := []byte("content")
|
||||
os.WriteFile(path.Join(tmpdir, "file"), content, 0600)
|
||||
|
||||
os.MkdirAll(path.Join(tmpdir, "backup"), 0700)
|
||||
err := CopyPath(path.Join(tmpdir, "file"), path.Join(tmpdir, "backup"))
|
||||
err := os.WriteFile(path.Join(tmpdir, "file"), content, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = os.MkdirAll(path.Join(tmpdir, "backup"), 0700)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = CopyPath(path.Join(tmpdir, "file"), path.Join(tmpdir, "backup"))
|
||||
require.NoError(t, err)
|
||||
|
||||
copyContent, err := os.ReadFile(path.Join(tmpdir, "backup", "file"))
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -194,7 +195,7 @@ func (service *Service) Copy(fromFilePath string, toFilePath string, deleteIfExi
|
||||
return err
|
||||
}
|
||||
|
||||
defer finput.Close()
|
||||
defer logs.CloseAndLogErr(finput)
|
||||
|
||||
exists, err = service.FileExists(toFilePath)
|
||||
if err != nil {
|
||||
@@ -217,7 +218,7 @@ func (service *Service) Copy(fromFilePath string, toFilePath string, deleteIfExi
|
||||
return err
|
||||
}
|
||||
|
||||
defer foutput.Close()
|
||||
defer logs.CloseAndLogErr(foutput)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
@@ -702,7 +703,7 @@ func (service *Service) createPEMFileInStore(content []byte, fileType, filePath
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
defer logs.CloseAndLogErr(out)
|
||||
|
||||
return pem.Encode(out, block)
|
||||
}
|
||||
@@ -1008,7 +1009,7 @@ func CreateFile(path string, r io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer out.Close()
|
||||
defer logs.CloseAndLogErr(out)
|
||||
|
||||
_, err = io.Copy(out, r)
|
||||
return err
|
||||
|
||||
@@ -30,11 +30,12 @@ func Test_FileExists_whenFileNotExistsShouldReturnFalse(t *testing.T) {
|
||||
}
|
||||
|
||||
func testHelperFileExists_fileExists(t *testing.T, checker func(path string) (bool, error)) {
|
||||
file, err := os.CreateTemp("", t.Name())
|
||||
file, err := os.CreateTemp(t.TempDir(), t.Name())
|
||||
require.NoError(t, err, "CreateTemp should not fail")
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(file.Name())
|
||||
err := os.RemoveAll(file.Name())
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
exists, err := checker(file.Name())
|
||||
|
||||
@@ -58,12 +58,14 @@ func Test_movePath_succesIfOverwriteSetWhenDestinationDirExists(t *testing.T) {
|
||||
func Test_movePath_successWhenSourceExistsAndDestinationIsMissing(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
sourceDir := path.Join(tmp, "source")
|
||||
os.Mkdir(sourceDir, 0766)
|
||||
err := os.Mkdir(sourceDir, 0766)
|
||||
require.NoError(t, err)
|
||||
|
||||
file1 := addFile(t, sourceDir, "dir", "file")
|
||||
file2 := addFile(t, sourceDir, "file")
|
||||
destinationDir := path.Join(tmp, "destination")
|
||||
|
||||
err := MoveDirectory(sourceDir, destinationDir, false)
|
||||
err = MoveDirectory(sourceDir, destinationDir, false)
|
||||
require.NoError(t, err)
|
||||
assert.NoFileExists(t, file1, "source dir contents should be moved")
|
||||
assert.NoFileExists(t, file2, "source dir contents should be moved")
|
||||
|
||||
@@ -15,7 +15,8 @@ func createService(t *testing.T) *Service {
|
||||
require.NoError(t, err, "NewService should not fail")
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(dataStorePath)
|
||||
err := os.RemoveAll(dataStorePath)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
return service
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
)
|
||||
|
||||
// WriteToFile creates a file in the filesystem storage
|
||||
@@ -17,7 +18,7 @@ func WriteToFile(dst string, content []byte) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open a file %q", dst)
|
||||
}
|
||||
defer file.Close()
|
||||
defer logs.CloseAndLogErr(file)
|
||||
|
||||
_, err = file.Write(content)
|
||||
return errors.Wrapf(err, "failed to write a file %q", dst)
|
||||
|
||||
+30
-10
@@ -13,6 +13,8 @@ import (
|
||||
"github.com/portainer/portainer/api/archive"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/pkg/errors"
|
||||
@@ -76,10 +78,13 @@ func (a *azureClient) download(ctx context.Context, destination string, opt clon
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to download a zip file from Azure DevOps")
|
||||
}
|
||||
defer os.Remove(zipFilepath)
|
||||
defer func() {
|
||||
if err := os.Remove(zipFilepath); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to remove temporary zip file")
|
||||
}
|
||||
}()
|
||||
|
||||
err = archive.UnzipFile(zipFilepath, destination)
|
||||
if err != nil {
|
||||
if err := archive.UnzipFile(zipFilepath, destination); err != nil {
|
||||
return errors.Wrap(err, "failed to unzip file")
|
||||
}
|
||||
|
||||
@@ -102,7 +107,7 @@ func (a *azureClient) downloadZipFromAzureDevOps(ctx context.Context, opt cloneO
|
||||
return "", errors.WithMessage(err, "failed to create temp file")
|
||||
}
|
||||
|
||||
defer zipFile.Close()
|
||||
defer logs.CloseAndLogErr(zipFile)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", downloadUrl, nil)
|
||||
if opt.username != "" || opt.password != "" {
|
||||
@@ -123,14 +128,17 @@ func (a *azureClient) downloadZipFromAzureDevOps(ctx context.Context, opt cloneO
|
||||
return "", errors.WithMessage(err, "failed to make an HTTP request")
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
defer func() {
|
||||
if err := res.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("failed to download zip with a status \"%v\"", res.Status)
|
||||
}
|
||||
|
||||
_, err = io.Copy(zipFile, res.Body)
|
||||
if err != nil {
|
||||
if _, err := io.Copy(zipFile, res.Body); err != nil {
|
||||
return "", errors.WithMessage(err, "failed to save HTTP response to a file")
|
||||
}
|
||||
|
||||
@@ -175,7 +183,11 @@ func (a *azureClient) getRootItem(ctx context.Context, opt fetchOption) (*azureI
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, checkAzureStatusCode(fmt.Errorf("failed to get repository root item with a status \"%v\"", resp.Status), resp.StatusCode)
|
||||
@@ -417,7 +429,11 @@ func (a *azureClient) listRefs(ctx context.Context, opt baseOption) ([]string, e
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, checkAzureStatusCode(fmt.Errorf("failed to list refs with a status \"%v\"", resp.Status), resp.StatusCode)
|
||||
@@ -477,7 +493,11 @@ func (a *azureClient) listFiles(ctx context.Context, opt fetchOption) ([]string,
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to list tree url with a status \"%v\"", resp.Status)
|
||||
|
||||
@@ -139,8 +139,12 @@ func TestService_ListRefs_Azure_Concurrently(t *testing.T) {
|
||||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||
|
||||
go service.ListRefs(privateAzureRepoURL, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
service.ListRefs(privateAzureRepoURL, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
go func() {
|
||||
_, _ = service.ListRefs(privateAzureRepoURL, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
}()
|
||||
|
||||
_, err := service.ListRefs(privateAzureRepoURL, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
@@ -153,6 +157,7 @@ func TestService_ListFiles_Azure(t *testing.T) {
|
||||
err error
|
||||
matchedCount int
|
||||
}
|
||||
|
||||
service := newService(context.TODO(), 0, 0)
|
||||
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
|
||||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||
@@ -289,6 +294,7 @@ func TestService_ListFiles_Azure(t *testing.T) {
|
||||
tt.extensions,
|
||||
false,
|
||||
)
|
||||
|
||||
if tt.expect.shouldFail {
|
||||
require.Error(t, err)
|
||||
if tt.expect.err != nil {
|
||||
@@ -311,18 +317,21 @@ func TestService_ListFiles_Azure_Concurrently(t *testing.T) {
|
||||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||
|
||||
go service.ListFiles(
|
||||
privateAzureRepoURL,
|
||||
"refs/heads/main",
|
||||
username,
|
||||
accessToken,
|
||||
gittypes.GitCredentialAuthType_Basic,
|
||||
false,
|
||||
false,
|
||||
[]string{},
|
||||
false,
|
||||
)
|
||||
service.ListFiles(
|
||||
go func() {
|
||||
_, _ = service.ListFiles(
|
||||
privateAzureRepoURL,
|
||||
"refs/heads/main",
|
||||
username,
|
||||
accessToken,
|
||||
gittypes.GitCredentialAuthType_Basic,
|
||||
false,
|
||||
false,
|
||||
[]string{},
|
||||
false,
|
||||
)
|
||||
}()
|
||||
|
||||
_, err := service.ListFiles(
|
||||
privateAzureRepoURL,
|
||||
"refs/heads/main",
|
||||
username,
|
||||
@@ -333,6 +342,7 @@ func TestService_ListFiles_Azure_Concurrently(t *testing.T) {
|
||||
[]string{},
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
@@ -342,6 +352,7 @@ func getRequiredValue(t *testing.T, name string) string {
|
||||
if !ok {
|
||||
t.Fatalf("can't find required env var \"%s\"", name)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@@ -333,13 +333,12 @@ func Test_azureDownloader_latestCommitID(t *testing.T) {
|
||||
]
|
||||
}`
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(response))
|
||||
|
||||
_, _ = w.Write([]byte(response))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
a := &azureClient{
|
||||
baseUrl: server.URL,
|
||||
}
|
||||
a := &azureClient{baseUrl: server.URL}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -421,7 +420,7 @@ func Test_cloneRepository_azure(t *testing.T) {
|
||||
git := &testRepoManager{}
|
||||
|
||||
s := &Service{azure: azure, git: git}
|
||||
s.cloneRepository("", cloneOption{
|
||||
err := s.cloneRepository("", cloneOption{
|
||||
fetchOption: fetchOption{
|
||||
baseOption: baseOption{
|
||||
|
||||
@@ -430,6 +429,7 @@ func Test_cloneRepository_azure(t *testing.T) {
|
||||
},
|
||||
depth: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// if azure API is called, git isn't and vice versa
|
||||
assert.Equal(t, tt.called, azure.called)
|
||||
|
||||
@@ -82,8 +82,12 @@ func TestService_ListRefs_Github_Concurrently(t *testing.T) {
|
||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||
|
||||
repositoryUrl := privateGitRepoURL
|
||||
go service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
go func() {
|
||||
_, _ = service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
}()
|
||||
|
||||
_, err := service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
@@ -255,18 +259,21 @@ func TestService_ListFiles_Github_Concurrently(t *testing.T) {
|
||||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||
|
||||
go service.ListFiles(
|
||||
repositoryUrl,
|
||||
"refs/heads/main",
|
||||
username,
|
||||
accessToken,
|
||||
gittypes.GitCredentialAuthType_Basic,
|
||||
false,
|
||||
false,
|
||||
[]string{},
|
||||
false,
|
||||
)
|
||||
service.ListFiles(
|
||||
go func() {
|
||||
_, _ = service.ListFiles(
|
||||
repositoryUrl,
|
||||
"refs/heads/main",
|
||||
username,
|
||||
accessToken,
|
||||
gittypes.GitCredentialAuthType_Basic,
|
||||
false,
|
||||
false,
|
||||
[]string{},
|
||||
false,
|
||||
)
|
||||
}()
|
||||
|
||||
_, err := service.ListFiles(
|
||||
repositoryUrl,
|
||||
"refs/heads/main",
|
||||
username,
|
||||
@@ -277,6 +284,7 @@ func TestService_ListFiles_Github_Concurrently(t *testing.T) {
|
||||
[]string{},
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
@@ -289,8 +297,10 @@ func TestService_purgeCache_Github(t *testing.T) {
|
||||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||
service := NewService(context.TODO())
|
||||
|
||||
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
service.ListFiles(
|
||||
_, err := service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = service.ListFiles(
|
||||
repositoryUrl,
|
||||
"refs/heads/main",
|
||||
username,
|
||||
@@ -301,6 +311,7 @@ func TestService_purgeCache_Github(t *testing.T) {
|
||||
[]string{},
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||
assert.Equal(t, 1, service.repoFileCache.Len())
|
||||
@@ -320,8 +331,9 @@ func TestService_purgeCacheByTTL_Github(t *testing.T) {
|
||||
// 40*timeout is designed for giving enough time for ListRefs and ListFiles to cache the result
|
||||
service := newService(context.TODO(), 2, 40*timeout)
|
||||
|
||||
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
service.ListFiles(
|
||||
_, err := service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
require.NoError(t, err)
|
||||
_, err = service.ListFiles(
|
||||
repositoryUrl,
|
||||
"refs/heads/main",
|
||||
username,
|
||||
@@ -332,6 +344,7 @@ func TestService_purgeCacheByTTL_Github(t *testing.T) {
|
||||
[]string{},
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||
assert.Equal(t, 1, service.repoFileCache.Len())
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
)
|
||||
@@ -34,7 +35,11 @@ func (service *Service) Authorization(configuration portainer.OpenAMTConfigurati
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
defer func() {
|
||||
if err := response.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
responseBody, readErr := io.ReadAll(response.Body)
|
||||
if readErr != nil {
|
||||
@@ -47,8 +52,7 @@ func (service *Service) Authorization(configuration portainer.OpenAMTConfigurati
|
||||
}
|
||||
|
||||
var token authenticationResponse
|
||||
err = json.Unmarshal(responseBody, &token)
|
||||
if err != nil {
|
||||
if err := json.Unmarshal(responseBody, &token); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
)
|
||||
@@ -129,7 +130,11 @@ func (service *Service) getCIRACertificate(configuration portainer.OpenAMTConfig
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
defer func() {
|
||||
if err := response.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unexpected status code %s", response.Status)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -100,7 +101,11 @@ func (service *Service) executeSaveRequest(method string, url string, token stri
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
defer func() {
|
||||
if err := response.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
responseBody, readErr := io.ReadAll(response.Body)
|
||||
if readErr != nil {
|
||||
@@ -112,6 +117,7 @@ func (service *Service) executeSaveRequest(method string, url string, token stri
|
||||
if errorResponse != nil {
|
||||
return nil, errorResponse
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unexpected status code %s", response.Status)
|
||||
}
|
||||
|
||||
@@ -131,7 +137,11 @@ func (service *Service) executeGetRequest(url string, token string) ([]byte, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
defer func() {
|
||||
if err := response.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
responseBody, readErr := io.ReadAll(response.Body)
|
||||
if readErr != nil {
|
||||
|
||||
@@ -55,7 +55,11 @@ func (client *HTTPClient) ExecuteAzureAuthenticationRequest(credentials *portain
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("invalid Azure credentials")
|
||||
@@ -86,7 +90,11 @@ func Get(url string, timeout int) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
defer func() {
|
||||
if err := response.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
log.Error().Int("status_code", response.StatusCode).Msg("unexpected status code")
|
||||
@@ -138,8 +146,11 @@ func pingOperation(client *http.Client, target string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
|
||||
agentOnDockerEnvironment := resp.Header.Get(portainer.PortainerAgentHeader) != ""
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
operations "github.com/portainer/portainer/api/backup"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -44,7 +45,11 @@ func (h *Handler) backup(w http.ResponseWriter, r *http.Request) *httperror.Hand
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Failed to create backup", err)
|
||||
}
|
||||
defer os.RemoveAll(filepath.Dir(archivePath))
|
||||
defer func() {
|
||||
if err := os.RemoveAll(filepath.Dir(archivePath)); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to remove backup temp folder")
|
||||
}
|
||||
}()
|
||||
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=portainer-backup_"+filepath.Base(archivePath))
|
||||
http.ServeFile(w, r, archivePath)
|
||||
|
||||
@@ -21,28 +21,34 @@ import (
|
||||
"github.com/portainer/portainer/pkg/fips"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fips.InitFIPS(false)
|
||||
}
|
||||
|
||||
func listFiles(dir string) []string {
|
||||
func listFiles(t *testing.T, dir string) []string {
|
||||
items := make([]string, 0)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
items = append(items, path)
|
||||
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func contains(t *testing.T, list []string, path string) {
|
||||
assert.Contains(t, list, path)
|
||||
copyContent, _ := os.ReadFile(path)
|
||||
copyContent, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "content\n", string(copyContent))
|
||||
}
|
||||
|
||||
@@ -63,23 +69,25 @@ func Test_backupHandlerWithoutPassword_shouldCreateATarballArchive(t *testing.T)
|
||||
assert.Nil(t, handlerErr, "Handler should not fail")
|
||||
|
||||
response := w.Result()
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
response.Body.Close()
|
||||
body, err := io.ReadAll(response.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = response.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
archivePath := filepath.Join(tmpdir, "archive.tar.gz")
|
||||
err := os.WriteFile(archivePath, body, 0600)
|
||||
if err != nil {
|
||||
if err := os.WriteFile(archivePath, body, 0600); 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 {
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatal("Failed to extract archive: ", err)
|
||||
}
|
||||
|
||||
createdFiles := listFiles(tmpdir)
|
||||
createdFiles := listFiles(t, tmpdir)
|
||||
|
||||
contains(t, createdFiles, path.Join(tmpdir, "portainer.key"))
|
||||
contains(t, createdFiles, path.Join(tmpdir, "portainer.pub"))
|
||||
@@ -107,7 +115,9 @@ func Test_backupHandlerWithPassword_shouldCreateEncryptedATarballArchive(t *test
|
||||
|
||||
response := w.Result()
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
response.Body.Close()
|
||||
|
||||
err := response.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
@@ -117,17 +127,23 @@ func Test_backupHandlerWithPassword_shouldCreateEncryptedATarballArchive(t *test
|
||||
}
|
||||
|
||||
archivePath := filepath.Join(tmpdir, "archive.tag.gz")
|
||||
archive, _ := os.Create(archivePath)
|
||||
defer archive.Close()
|
||||
io.Copy(archive, dr)
|
||||
archive, err := os.Create(archivePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err := archive.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
_, err = io.Copy(archive, dr)
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd := exec.Command("tar", "-xzf", archivePath, "-C", tmpdir)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatal("Failed to extract archive: ", err)
|
||||
}
|
||||
|
||||
createdFiles := listFiles(tmpdir)
|
||||
createdFiles := listFiles(t, tmpdir)
|
||||
|
||||
contains(t, createdFiles, path.Join(tmpdir, "portainer.key"))
|
||||
contains(t, createdFiles, path.Join(tmpdir, "portainer.pub"))
|
||||
|
||||
@@ -118,7 +118,9 @@ func backup(t *testing.T, h *Handler, password string) []byte {
|
||||
response := w.Result()
|
||||
archive, err := io.ReadAll(response.Body)
|
||||
require.NoError(t, err)
|
||||
response.Body.Close()
|
||||
|
||||
err = response.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
return archive
|
||||
}
|
||||
|
||||
@@ -61,7 +61,11 @@ func (handler *Handler) customTemplateGitFetch(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// remove backup custom template folder
|
||||
defer cleanUpBackupCustomTemplate(backupPath)
|
||||
defer func() {
|
||||
if err := cleanUpBackupCustomTemplate(backupPath); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to remove backup custom template folder")
|
||||
}
|
||||
}()
|
||||
|
||||
commitHash, err := stackutils.DownloadGitRepository(*customTemplate.GitConfig, handler.GitService, func() string {
|
||||
return customTemplate.ProjectPath
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/jwt"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/pkg/fips"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
|
||||
@@ -105,7 +106,7 @@ func createTestFile(targetPath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
defer logs.CloseAndLogErr(f)
|
||||
|
||||
_, err = f.WriteString(testFileContent)
|
||||
|
||||
@@ -177,7 +178,10 @@ func Test_customTemplateGitFetch(t *testing.T) {
|
||||
err = prepareTestFolder(template1.ProjectPath, template1.GitConfig.ConfigFilePath)
|
||||
require.NoError(t, err, "error creating testing folder")
|
||||
|
||||
defer os.RemoveAll(filepath.Join(dir, "fixtures"))
|
||||
defer func() {
|
||||
err := os.RemoveAll(filepath.Join(dir, "fixtures"))
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// setup services
|
||||
jwtService, err := jwt.NewService("1h", store)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package edgestacks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -43,12 +42,7 @@ func setupHandler(t *testing.T) (*Handler, string) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp(t.TempDir(), "portainer-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fs, err := filesystem.NewService(tmpDir, "")
|
||||
fs, err := filesystem.NewService(t.TempDir(), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -309,7 +309,10 @@ func cacheResponse(w http.ResponseWriter, endpointID portainer.EndpointID, statu
|
||||
}
|
||||
|
||||
w.Header().Set("ETag", etag)
|
||||
io.Copy(w, resp.Body)
|
||||
|
||||
if _, err := io.Copy(w, resp.Body); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to copy response body")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
)
|
||||
@@ -121,15 +122,18 @@ func getDockerHubToken(httpClient *client.HTTPClient, registry *portainer.Regist
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", errors.New("failed fetching dockerhub token")
|
||||
}
|
||||
|
||||
var data dockerhubTokenResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&data)
|
||||
if err != nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -151,8 +155,11 @@ func getDockerHubLimits(httpClient *client.HTTPClient, token string) (*dockerhub
|
||||
return nil, err
|
||||
}
|
||||
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("failed fetching dockerhub limits")
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
portaineree "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/docker/consts"
|
||||
"github.com/portainer/portainer/api/docker/images"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
@@ -74,7 +75,7 @@ func (handler *Handler) endpointForceUpdateService(w http.ResponseWriter, r *htt
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Error creating docker client", err)
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
defer logs.CloseAndLogErr(dockerClient)
|
||||
|
||||
service, _, err := dockerClient.ServiceInspectWithRaw(context.Background(), payload.ServiceID, dockertypes.ServiceInspectOptions{InsertDefaults: true})
|
||||
if err != nil {
|
||||
|
||||
@@ -210,7 +210,10 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
endpoint.TLSConfig.TLSCACertPath = caCertPath
|
||||
} else {
|
||||
endpoint.TLSConfig.TLSCACertPath = ""
|
||||
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCA)
|
||||
|
||||
if err := handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCA); err != nil {
|
||||
log.Warn().Err(err).Msg("Unable to remove CA cert from disk")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,9 +225,14 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
endpoint.TLSConfig.TLSKeyPath = keyPath
|
||||
} else {
|
||||
endpoint.TLSConfig.TLSCertPath = ""
|
||||
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCert)
|
||||
if err := handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCert); err != nil {
|
||||
log.Warn().Err(err).Msg("Unable to remove TLS cert from disk")
|
||||
}
|
||||
|
||||
endpoint.TLSConfig.TLSKeyPath = ""
|
||||
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileKey)
|
||||
if err := handler.FileService.DeleteTLSFile(folder, portainer.TLSFileKey); err != nil {
|
||||
log.Warn().Err(err).Msg("Unable to remove TLS key from disk")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -255,8 +263,11 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
|
||||
if updateAuthorizations && endpointutils.IsKubernetesEndpoint(endpoint) {
|
||||
if err := handler.AuthorizationService.CleanNAPWithOverridePolicies(handler.DataStore, endpoint, nil); err != nil {
|
||||
handler.PendingActionsService.Create(handlers.NewCleanNAPWithOverridePolicies(endpoint.ID, nil))
|
||||
log.Warn().Err(err).Msgf("Unable to clean NAP with override policies for endpoint (%d). Will try to update when endpoint is online.", endpoint.ID)
|
||||
|
||||
if err := handler.PendingActionsService.Create(handlers.NewCleanNAPWithOverridePolicies(endpoint.ID, nil)); err != nil {
|
||||
log.Warn().Err(err).Msg("unable to schedule pending action to clean NAP with override policies")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
"github.com/portainer/portainer/pkg/validate"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type fileResponse struct {
|
||||
@@ -87,7 +88,11 @@ func (handler *Handler) gitOperationRepoFilePreview(w http.ResponseWriter, r *ht
|
||||
return httperror.InternalServerError(newErr.Error(), newErr)
|
||||
}
|
||||
|
||||
defer handler.fileService.RemoveDirectory(projectPath)
|
||||
defer func() {
|
||||
if err := handler.fileService.RemoveDirectory(projectPath); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to remove temporary project folder")
|
||||
}
|
||||
}()
|
||||
|
||||
fileContent, err := handler.fileService.GetFileContent(projectPath, payload.TargetFile)
|
||||
if err != nil {
|
||||
|
||||
@@ -42,7 +42,9 @@ func Test_helmDelete(t *testing.T) {
|
||||
|
||||
// Install a single chart directly, to be deleted by the handler
|
||||
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}
|
||||
h.helmPackageManager.Upgrade(options)
|
||||
|
||||
_, err = h.helmPackageManager.Upgrade(options)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("helmDelete succeeds with admin user", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/1/kubernetes/helm/"+options.Name, nil)
|
||||
|
||||
@@ -45,7 +45,9 @@ func Test_helmGet(t *testing.T) {
|
||||
|
||||
// Install a single chart, to be retrieved by the handler
|
||||
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}
|
||||
h.helmPackageManager.Upgrade(options)
|
||||
|
||||
_, err = h.helmPackageManager.Upgrade(options)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("helmGet sucessfuly retrieves helm release", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/1/kubernetes/helm/"+options.Name+"?namespace="+options.Namespace, nil)
|
||||
@@ -59,7 +61,9 @@ func Test_helmGet(t *testing.T) {
|
||||
data := release.Release{}
|
||||
body, err := io.ReadAll(rr.Body)
|
||||
require.NoError(t, err, "ReadAll should not return error")
|
||||
json.Unmarshal(body, &data)
|
||||
|
||||
err = json.Unmarshal(body, &data)
|
||||
require.NoError(t, err)
|
||||
is.Equal(http.StatusOK, rr.Code, "Status should be 200")
|
||||
is.Equal("nginx-1", data.Name)
|
||||
})
|
||||
|
||||
@@ -45,7 +45,9 @@ func Test_helmGetHistory(t *testing.T) {
|
||||
|
||||
// Install a single chart, to be retrieved by the handler
|
||||
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}
|
||||
h.helmPackageManager.Upgrade(options)
|
||||
|
||||
_, err = h.helmPackageManager.Upgrade(options)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("helmGetHistory sucessfuly retrieves helm release history", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/1/kubernetes/helm/"+options.Name+"/history?namespace="+options.Namespace, nil)
|
||||
@@ -59,7 +61,9 @@ func Test_helmGetHistory(t *testing.T) {
|
||||
data := []release.Release{}
|
||||
body, err := io.ReadAll(rr.Body)
|
||||
require.NoError(t, err, "ReadAll should not return error")
|
||||
json.Unmarshal(body, &data)
|
||||
|
||||
err = json.Unmarshal(body, &data)
|
||||
require.NoError(t, err)
|
||||
is.Equal(http.StatusOK, rr.Code, "Status should be 200")
|
||||
is.Len(data, 1)
|
||||
is.Equal("nginx-1", data[0].Name)
|
||||
|
||||
@@ -10,11 +10,13 @@ import (
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/kubernetes"
|
||||
"github.com/portainer/portainer/api/kubernetes/validation"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||
"github.com/portainer/portainer/pkg/libhelm/release"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -122,10 +124,14 @@ func (handler *Handler) installChart(r *http.Request, p installChartPayload, dry
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
defer func() {
|
||||
if err := os.Remove(file.Name()); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to remove temporary helm values file")
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := file.WriteString(p.Values); err != nil {
|
||||
file.Close()
|
||||
logs.CloseAndLogErr(file)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -209,8 +215,13 @@ func (handler *Handler) updateHelmAppManifest(r *http.Request, manifest []byte,
|
||||
return errors.Wrap(err, "failed to create a tmp helm manifest file")
|
||||
}
|
||||
defer func() {
|
||||
tmpfile.Close()
|
||||
os.Remove(tmpfile.Name())
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close tmp helm manifest file")
|
||||
}
|
||||
|
||||
if err := os.Remove(tmpfile.Name()); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to remove tmp helm manifest file")
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := tmpfile.Write(resource); err != nil {
|
||||
|
||||
@@ -43,7 +43,9 @@ func Test_helmList(t *testing.T) {
|
||||
|
||||
// Install a single chart. We expect to get these values back
|
||||
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}
|
||||
h.helmPackageManager.Upgrade(options)
|
||||
|
||||
_, err = h.helmPackageManager.Upgrade(options)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("helmList", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/1/kubernetes/helm", nil)
|
||||
@@ -60,7 +62,8 @@ func Test_helmList(t *testing.T) {
|
||||
require.NoError(t, err, "ReadAll should not return error")
|
||||
|
||||
data := []release.ReleaseElement{}
|
||||
json.Unmarshal(body, &data)
|
||||
err = json.Unmarshal(body, &data)
|
||||
require.NoError(t, err)
|
||||
if is.Len(data, 1, "Expected one chart entry") {
|
||||
is.Equal(options.Name, data[0].Name, "Name doesn't match")
|
||||
is.Equal(options.Chart, data[0].Chart, "Chart doesn't match")
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -56,7 +57,10 @@ func (handler *Handler) helmRepoSearch(w http.ResponseWriter, r *http.Request) *
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write(result)
|
||||
|
||||
if _, err := w.Write(result); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to write helm repo search response")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -69,7 +69,10 @@ func (handler *Handler) helmShow(w http.ResponseWriter, r *http.Request) *httper
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write(result)
|
||||
|
||||
if _, err := w.Write(result); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to write helm show response")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/hostmanagement/openamt"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
@@ -111,7 +112,11 @@ func (handler *Handler) PullAndRunContainer(ctx context.Context, endpoint *porta
|
||||
if err != nil {
|
||||
return "Unable to create Docker Client connection", err
|
||||
}
|
||||
defer docker.Close()
|
||||
defer func() {
|
||||
if err := docker.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close docker client")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := pullImage(ctx, docker, imageName); err != nil {
|
||||
return "Could not pull image from registry", err
|
||||
@@ -139,7 +144,7 @@ func pullImage(ctx context.Context, docker *client.Client, imageName string) err
|
||||
return err
|
||||
}
|
||||
|
||||
defer out.Close()
|
||||
defer logs.CloseAndLogErr(out)
|
||||
outputBytes, err := io.ReadAll(out)
|
||||
if err != nil {
|
||||
log.Error().Str("image_name", imageName).Err(err).Msg("could not read image pull output")
|
||||
|
||||
@@ -3,6 +3,7 @@ package kubernetes
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/jwt"
|
||||
"github.com/portainer/portainer/api/kubernetes"
|
||||
kubeClient "github.com/portainer/portainer/api/kubernetes/cli"
|
||||
kubeclient "github.com/portainer/portainer/api/kubernetes/cli"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -23,36 +24,47 @@ import (
|
||||
func TestGetKubernetesEvents(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
_, store := datastore.MustNewTestStore(t, true, true)
|
||||
|
||||
err := store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: 1,
|
||||
Type: portainer.AgentOnKubernetesEnvironment,
|
||||
},
|
||||
)
|
||||
err := store.Endpoint().Create(&portainer.Endpoint{ID: 1, Type: portainer.AgentOnKubernetesEnvironment})
|
||||
require.NoError(t, err, "error creating environment")
|
||||
|
||||
err = store.User().Create(&portainer.User{Username: "admin", Role: portainer.AdministratorRole})
|
||||
u := &portainer.User{Username: "admin", Role: portainer.AdministratorRole}
|
||||
err = store.User().Create(u)
|
||||
require.NoError(t, err, "error creating a user")
|
||||
|
||||
jwtService, err := jwt.NewService("1h", store)
|
||||
require.NoError(t, err, "Error initiating jwt service")
|
||||
|
||||
tk, _, _ := jwtService.GenerateToken(&portainer.TokenData{ID: 1, Username: "admin", Role: portainer.AdministratorRole})
|
||||
tk, _, err := jwtService.GenerateToken(&portainer.TokenData{ID: u.ID, Username: u.Username, Role: u.Role})
|
||||
require.NoError(t, err)
|
||||
|
||||
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "")
|
||||
|
||||
srvURL, err := url.Parse(srv.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
cli := testhelpers.NewKubernetesClient()
|
||||
factory, _ := kubeClient.NewClientFactory(nil, nil, store, "", "", "")
|
||||
factory, err := kubeclient.NewClientFactory(nil, nil, store, "", ":"+srvURL.Port(), "")
|
||||
require.NoError(t, err)
|
||||
|
||||
authorizationService := authorization.NewService(store)
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer(), authorizationService, store, jwtService, kubeClusterAccessService,
|
||||
factory, cli)
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer(), authorizationService, store, jwtService, kubeClusterAccessService, factory, cli)
|
||||
is.NotNil(handler, "Handler should not fail")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/kubernetes/1/events?resourceId=8", nil)
|
||||
ctx := security.StoreTokenData(req, &portainer.TokenData{ID: 1, Username: "admin", Role: 1})
|
||||
ctx := security.StoreTokenData(req, &portainer.TokenData{ID: u.ID, Username: u.Username, Role: u.Role})
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
ctx = security.StoreRestrictedRequestContext(req, &security.RestrictedRequestContext{UserID: u.ID, IsAdmin: true})
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
testhelpers.AddTestSecurityCookie(req, tk)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
@@ -200,7 +200,7 @@ func (handler *Handler) kubeClientMiddleware(next http.Handler) http.Handler {
|
||||
|
||||
user, err := security.RetrieveUserFromRequest(r, handler.DataStore)
|
||||
if err != nil {
|
||||
httperror.InternalServerError("an error occurred during the KubeClientMiddleware operation, unable to retrieve the user from request. Error: ", err)
|
||||
httperror.WriteError(w, http.StatusInternalServerError, "an error occurred during the KubeClientMiddleware operation, unable to retrieve the user from request. Error: ", err)
|
||||
return
|
||||
}
|
||||
log.
|
||||
@@ -255,6 +255,7 @@ func (handler *Handler) kubeClientMiddleware(next http.Handler) http.Handler {
|
||||
httperror.WriteError(w, http.StatusInternalServerError, "an error occurred during the KubeClientMiddleware operation, unable to parse server URL for building kubeconfig. Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
serverURL.Scheme = "https"
|
||||
serverURL.Host = "localhost" + handler.KubernetesClientFactory.GetAddrHTTPS()
|
||||
config.Clusters[0].Cluster.Server = serverURL.String()
|
||||
@@ -264,6 +265,7 @@ func (handler *Handler) kubeClientMiddleware(next http.Handler) http.Handler {
|
||||
httperror.WriteError(w, http.StatusInternalServerError, "an error occurred during the KubeClientMiddleware operation, unable to generate kubeconfig YAML. Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
kubeCli, err := handler.KubernetesClientFactory.CreateKubeClientFromKubeConfig(endpoint.Name, []byte(yaml), isKubeAdmin, nonAdminNamespaces)
|
||||
if err != nil {
|
||||
httperror.WriteError(w, http.StatusInternalServerError, "an error occurred during the KubeClientMiddleware operation, unable to create kubernetes client from kubeconfig. Error: ", err)
|
||||
|
||||
@@ -41,20 +41,29 @@ type motdData struct {
|
||||
func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) {
|
||||
if err := libclient.ExternalRequestDisabled(portainer.MessageOfTheDayURL); err != nil {
|
||||
log.Debug().Err(err).Msg("External request disabled: MOTD")
|
||||
response.JSON(w, &motdResponse{Message: ""})
|
||||
|
||||
if err := response.JSON(w, &motdResponse{Message: ""}); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to send MOTD response")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
motd, err := client.Get(portainer.MessageOfTheDayURL, 0)
|
||||
if err != nil {
|
||||
response.JSON(w, &motdResponse{Message: ""})
|
||||
if err := response.JSON(w, &motdResponse{Message: ""}); err != nil {
|
||||
log.Error().Err(err).Msg("failed to send MOTD response")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var data motdData
|
||||
err = json.Unmarshal(motd, &data)
|
||||
if err != nil {
|
||||
response.JSON(w, &motdResponse{Message: ""})
|
||||
if err := json.Unmarshal(motd, &data); err != nil {
|
||||
if err := response.JSON(w, &motdResponse{Message: ""}); err != nil {
|
||||
log.Error().Err(err).Msg("failed to send MOTD response")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,5 +78,7 @@ func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) {
|
||||
Style: data.Style,
|
||||
}
|
||||
|
||||
response.JSON(w, &resp)
|
||||
if err := response.JSON(w, &resp); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to send MOTD response")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,9 +86,11 @@ func (handler *Handler) deleteKubernetesSecrets(registry *portainer.Registry) {
|
||||
}
|
||||
|
||||
if len(failedNamespaces) > 0 {
|
||||
handler.PendingActionsService.Create(
|
||||
if err := handler.PendingActionsService.Create(
|
||||
handlers.NewDeleteK8sRegistrySecrets(endpointId, registry.ID, failedNamespaces),
|
||||
)
|
||||
); err != nil {
|
||||
log.Warn().Err(err).Msg("unable to schedule pending action to delete kubernetes registry secrets")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,9 @@ func (handler *Handler) createKubernetesStackFromFileContent(w http.ResponseWrit
|
||||
// otherwise return nil
|
||||
cli, err := handler.KubernetesClientFactory.GetPrivilegedKubeClient(endpoint)
|
||||
if err == nil {
|
||||
registryutils.RefreshEcrSecret(cli, endpoint, handler.DataStore, payload.Namespace)
|
||||
if err := registryutils.RefreshEcrSecret(cli, endpoint, handler.DataStore, payload.Namespace); err != nil {
|
||||
return httperror.InternalServerError("Unable to refresh ECR registry secret", err)
|
||||
}
|
||||
}
|
||||
|
||||
stackBuilderDirector := stackbuilders.NewStackBuilderDirector(k8sStackBuilder)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/api/scheduler"
|
||||
"github.com/portainer/portainer/api/stacks/deployments"
|
||||
"github.com/portainer/portainer/api/stacks/stackutils"
|
||||
@@ -174,7 +175,7 @@ func (handler *Handler) checkUniqueStackNameInDocker(endpoint *portainer.Endpoin
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
defer logs.CloseAndLogErr(dockerClient)
|
||||
if swarmMode {
|
||||
services, err := dockerClient.ServiceList(context.Background(), types.ServiceListOptions{})
|
||||
if err != nil {
|
||||
|
||||
@@ -261,7 +261,9 @@ func (handler *Handler) updateComposeStack(tx dataservices.DataStoreTx, r *http.
|
||||
return httperror.InternalServerError(err.Error(), err)
|
||||
}
|
||||
|
||||
handler.FileService.RemoveStackFileBackup(stackFolder, stack.EntryPoint)
|
||||
if err := handler.FileService.RemoveStackFileBackup(stackFolder, stack.EntryPoint); err != nil {
|
||||
log.Warn().Err(err).Msg("remove stack file backup error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -335,7 +337,9 @@ func (handler *Handler) updateSwarmStack(tx dataservices.DataStoreTx, r *http.Re
|
||||
return httperror.InternalServerError(err.Error(), err)
|
||||
}
|
||||
|
||||
handler.FileService.RemoveStackFileBackup(stackFolder, stack.EntryPoint)
|
||||
if err := handler.FileService.RemoveStackFileBackup(stackFolder, stack.EntryPoint); err != nil {
|
||||
log.Warn().Err(err).Msg("remove stack file backup error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -117,7 +117,11 @@ func (handler *Handler) updateKubernetesStack(r *http.Request, stack *portainer.
|
||||
}
|
||||
|
||||
tempFileDir, _ := os.MkdirTemp("", "kub_file_content")
|
||||
defer os.RemoveAll(tempFileDir)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(tempFileDir); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to remove temporary stack deployment directory")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := filesystem.WriteToFile(filesystem.JoinPaths(tempFileDir, stack.EntryPoint), []byte(payload.StackFileContent)); err != nil {
|
||||
return httperror.InternalServerError("Failed to persist deployment file in a temp directory", err)
|
||||
@@ -135,7 +139,9 @@ func (handler *Handler) updateKubernetesStack(r *http.Request, stack *portainer.
|
||||
// otherwise return nil
|
||||
cli, err := handler.KubernetesClientFactory.GetPrivilegedKubeClient(endpoint)
|
||||
if err == nil {
|
||||
registryutils.RefreshEcrSecret(cli, endpoint, handler.DataStore, stack.Namespace)
|
||||
if err := registryutils.RefreshEcrSecret(cli, endpoint, handler.DataStore, stack.Namespace); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to refresh ECR registry secret")
|
||||
}
|
||||
}
|
||||
|
||||
// Use temp dir as the stack project path for deployment
|
||||
@@ -162,7 +168,9 @@ func (handler *Handler) updateKubernetesStack(r *http.Request, stack *portainer.
|
||||
}
|
||||
stack.ProjectPath = projectPath
|
||||
|
||||
handler.FileService.RemoveStackFileBackup(stackFolder, stack.EntryPoint)
|
||||
if err := handler.FileService.RemoveStackFileBackup(stackFolder, stack.EntryPoint); err != nil {
|
||||
log.Warn().Err(err).Msg("remove stack file backup error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,12 +18,13 @@ func TestHandler_webhookInvoke(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, true, true)
|
||||
|
||||
webhookID := newGuidString(t)
|
||||
store.StackService.Create(&portainer.Stack{
|
||||
err := store.StackService.Create(&portainer.Stack{
|
||||
ID: 1,
|
||||
AutoUpdate: &portainer.AutoUpdateSettings{
|
||||
Webhook: webhookID,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
h := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
h.DataStore = store
|
||||
|
||||
@@ -38,7 +38,11 @@ func (handler *Handler) fetchTemplates() (*listResponse, *httperror.HandlerError
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve templates via the network", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to close templates response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to parse template file", err)
|
||||
|
||||
@@ -47,7 +47,8 @@ func Test_deleteUserRemovesAccessTokens(t *testing.T) {
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
h.deleteUser(rr, user)
|
||||
handleErr := h.deleteUser(rr, user)
|
||||
require.Nil(t, handleErr)
|
||||
|
||||
is.Equal(http.StatusNoContent, rr.Code)
|
||||
|
||||
|
||||
@@ -47,7 +47,8 @@ func Test_updateUserRemovesAccessTokens(t *testing.T) {
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
h.deleteUser(rr, user)
|
||||
handlerErr := h.deleteUser(rr, user)
|
||||
require.Nil(t, handlerErr)
|
||||
|
||||
is.Equal(http.StatusNoContent, rr.Code)
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/registryutils"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
@@ -71,7 +73,7 @@ func (handler *Handler) executeServiceWebhook(
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Error creating docker client", err)
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
defer logs.CloseAndLogErr(dockerClient)
|
||||
|
||||
service, _, err := dockerClient.ServiceInspectWithRaw(context.Background(), resourceID, dockertypes.ServiceInspectOptions{InsertDefaults: true})
|
||||
if err != nil {
|
||||
@@ -103,7 +105,10 @@ func (handler *Handler) executeServiceWebhook(
|
||||
}
|
||||
|
||||
if registry.Authentication {
|
||||
registryutils.EnsureRegTokenValid(handler.DataStore, registry)
|
||||
if err := registryutils.EnsureRegTokenValid(handler.DataStore, registry); err != nil {
|
||||
log.Warn().Err(err).Msgf("registry auth token renewal failed for registry %d", registry.ID)
|
||||
}
|
||||
|
||||
serviceUpdateOptions.EncodedRegistryAuth, err = registryutils.GetRegistryAuthHeader(registry)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Error getting registry auth header", err)
|
||||
@@ -116,7 +121,7 @@ func (handler *Handler) executeServiceWebhook(
|
||||
if err != nil {
|
||||
return httperror.NotFound("Error pulling image with the specified tag", err)
|
||||
}
|
||||
defer rc.Close()
|
||||
defer logs.CloseAndLogErr(rc)
|
||||
}
|
||||
|
||||
if _, err := dockerClient.ServiceUpdate(context.Background(), resourceID, service.Version, service.Spec, serviceUpdateOptions); err != nil {
|
||||
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/api/ws"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/validate"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
@@ -86,7 +88,7 @@ func (handler *Handler) handleAttachRequest(w http.ResponseWriter, r *http.Reque
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer websocketConn.Close()
|
||||
defer logs.CloseAndLogErr(websocketConn)
|
||||
|
||||
return hijackAttachStartOperation(websocketConn, params.endpoint, params.ID)
|
||||
}
|
||||
@@ -107,8 +109,13 @@ func hijackAttachStartOperation(
|
||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||
if err := tcpConn.SetKeepAlive(true); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to set TCP keep-alive on connection")
|
||||
}
|
||||
|
||||
if err := tcpConn.SetKeepAlivePeriod(30 * time.Second); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to set TCP keep-alive period on connection")
|
||||
}
|
||||
}
|
||||
|
||||
attachStartRequest, err := createAttachStartRequest(attachID)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/api/ws"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
@@ -91,7 +92,7 @@ func (handler *Handler) handleExecRequest(w http.ResponseWriter, r *http.Request
|
||||
return err
|
||||
}
|
||||
|
||||
defer websocketConn.Close()
|
||||
defer logs.CloseAndLogErr(websocketConn)
|
||||
|
||||
return hijackExecStartOperation(websocketConn, params.endpoint, params.ID)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
"github.com/portainer/portainer/api/ws"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
@@ -128,12 +129,12 @@ func (handler *Handler) hijackPodExecStartOperation(
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to upgrade the connection", err)
|
||||
}
|
||||
defer websocketConn.Close()
|
||||
defer logs.CloseAndLogErr(websocketConn)
|
||||
|
||||
stdinReader, stdinWriter := io.Pipe()
|
||||
defer stdinWriter.Close()
|
||||
defer logs.CloseAndLogErr(stdinWriter)
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
defer stdoutWriter.Close()
|
||||
defer logs.CloseAndLogErr(stdoutWriter)
|
||||
|
||||
// errorChan is used to propagate errors from the go routines to the caller.
|
||||
errorChan := make(chan error, 1)
|
||||
|
||||
@@ -115,19 +115,20 @@ func abortProxyOnLogout(ctx context.Context, proxy *websocketproxy.WebsocketProx
|
||||
logoutCtx := logoutcontext.GetContext(token)
|
||||
|
||||
go func() {
|
||||
log.Debug().
|
||||
Msg("logout watcher for websocket proxy started")
|
||||
log.Debug().Msg("logout watcher for websocket proxy started")
|
||||
|
||||
select {
|
||||
case <-logoutCtx.Done():
|
||||
log.Debug().
|
||||
Msg("logout watcher for websocket proxy stopped as user logged out")
|
||||
log.Debug().Msg("logout watcher for websocket proxy stopped as user logged out")
|
||||
if wsConn != nil {
|
||||
wsConn.Close()
|
||||
if err := wsConn.Close(); err != nil {
|
||||
log.Warn().
|
||||
Err(err).
|
||||
Msg("failed to close websocket connection on logout")
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
log.Debug().
|
||||
Msg("logout watcher for websocket proxy stopped as the ws connection closed")
|
||||
log.Debug().Msg("logout watcher for websocket proxy stopped as the ws connection closed")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestDeprecated(t *testing.T) {
|
||||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handledPath = r.URL.Path
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
_, _ = w.Write([]byte("success"))
|
||||
})
|
||||
|
||||
// Wrap with Deprecated middleware
|
||||
@@ -119,7 +119,7 @@ func TestDeprecatedSimple(t *testing.T) {
|
||||
// Create a test handler
|
||||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("test response"))
|
||||
_, _ = w.Write([]byte("test response"))
|
||||
})
|
||||
|
||||
// Wrap with DeprecatedSimple middleware
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -78,7 +79,7 @@ func Test_waitingMiddleware_executesImmediately_whenNotLocked(t *testing.T) {
|
||||
if elapsed >= timeout {
|
||||
t.Error("WaitingMiddleware had likely timeout, when it shouldn't")
|
||||
}
|
||||
w.Write([]byte("success"))
|
||||
_, _ = w.Write([]byte("success"))
|
||||
})).ServeHTTP(response, request)
|
||||
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
@@ -113,7 +114,7 @@ func Test_waitingMiddleware_waitsForTheLockToBeReleased(t *testing.T) {
|
||||
if elapsed >= timeout {
|
||||
t.Error("WaitingMiddleware had likely timeout, when it shouldn't")
|
||||
}
|
||||
w.Write([]byte("success"))
|
||||
_, _ = w.Write([]byte("success"))
|
||||
})).ServeHTTP(response, request)
|
||||
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
@@ -142,7 +143,7 @@ func Test_waitingMiddleware_mayTimeout_whenLockedForTooLong(t *testing.T) {
|
||||
if elapsed < timeout {
|
||||
t.Error("WaitingMiddleware suppose to timeout, but it didnt")
|
||||
}
|
||||
w.Write([]byte("success"))
|
||||
_, _ = w.Write([]byte("success"))
|
||||
})).ServeHTTP(response, request)
|
||||
|
||||
assert.Equal(t, http.StatusRequestTimeout, response.Result().StatusCode, "Request support to timeout waiting for the gate")
|
||||
@@ -159,7 +160,9 @@ func Test_waitingMiddleware_handlerPanics(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
recover()
|
||||
if r := recover(); r != nil {
|
||||
log.Warn().Msgf("Recovered in test: %v", r)
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
@@ -96,6 +96,8 @@ func (proxy *ProxyServer) start() error {
|
||||
// Close shuts down the server
|
||||
func (proxy *ProxyServer) Close() {
|
||||
if proxy.server != nil {
|
||||
proxy.server.Close()
|
||||
if err := proxy.server.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close proxy server")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/utils"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// proxy for /subscriptions/*/resourceGroups/*/providers/Microsoft.ContainerInstance/containerGroups/*
|
||||
@@ -115,7 +116,9 @@ func (transport *Transport) proxyContainerGroupGetRequest(request *http.Request)
|
||||
|
||||
responseObject = transport.decorateContainerGroup(responseObject, context)
|
||||
|
||||
utils.RewriteResponse(response, responseObject, http.StatusOK)
|
||||
if err := utils.RewriteResponse(response, responseObject, http.StatusOK); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to rewrite response")
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
@@ -140,9 +143,13 @@ func (transport *Transport) proxyContainerGroupDeleteRequest(request *http.Reque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport.removeResourceControl(responseObject, context)
|
||||
if err := transport.removeResourceControl(responseObject, context); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to remove resource control")
|
||||
}
|
||||
|
||||
utils.RewriteResponse(response, responseObject, http.StatusOK)
|
||||
if err := utils.RewriteResponse(response, responseObject, http.StatusOK); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to rewrite response")
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ func (transport *Transport) proxyContainerGroupsGetRequest(request *http.Request
|
||||
filteredValue := transport.filterContainerGroups(decoratedValue, context)
|
||||
responseObject["value"] = filteredValue
|
||||
|
||||
utils.RewriteResponse(response, responseObject, http.StatusOK)
|
||||
if err := utils.RewriteResponse(response, responseObject, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("The container groups response has no value property")
|
||||
}
|
||||
|
||||
@@ -99,7 +99,11 @@ func (proxy *dockerLocalProxy) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
httperror.WriteError(w, code, "Unable to proxy the request via the Docker socket", err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
defer func() {
|
||||
if err := res.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("proxy error: failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
for k, vv := range res.Header {
|
||||
for _, v := range vv {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer/api/archive"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/segmentio/encoding/json"
|
||||
@@ -80,7 +81,11 @@ func buildOperation(request *http.Request) error {
|
||||
}
|
||||
|
||||
tfb := archive.NewTarFileInBuffer()
|
||||
defer tfb.Close()
|
||||
defer func() {
|
||||
if err := tfb.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close tar buffer")
|
||||
}
|
||||
}()
|
||||
|
||||
for k := range request.MultipartForm.File {
|
||||
f, hdr, err := request.FormFile(k)
|
||||
@@ -88,7 +93,7 @@ func buildOperation(request *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
defer logs.CloseAndLogErr(f)
|
||||
|
||||
log.Info().Str("filename", hdr.Filename).Int64("size", hdr.Size).Msg("upload the file to build image")
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/utils"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
@@ -480,7 +481,9 @@ func (transport *Transport) proxyImageRequest(request *http.Request, unversioned
|
||||
}
|
||||
|
||||
func (transport *Transport) replaceRegistryAuthenticationHeader(request *http.Request) (*http.Response, error) {
|
||||
transport.decorateRegistryAuthenticationHeader(request)
|
||||
if err := transport.decorateRegistryAuthenticationHeader(request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transport.decorateGenericResourceCreationOperation(request, serviceObjectIdentifier, portainer.ServiceResourceControl)
|
||||
}
|
||||
@@ -584,7 +587,7 @@ func (transport *Transport) restrictedResourceOperation(request *http.Request, r
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
defer logs.CloseAndLogErr(client)
|
||||
|
||||
// the resourceID may be the resource name (as it's a valid proxy call to use the name and not the UUID)
|
||||
// so get the real resource ID and retry with it
|
||||
|
||||
@@ -391,7 +391,8 @@ func TestTransport_proxyNetworkRequest(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, r)
|
||||
if r != nil {
|
||||
r.Body.Close()
|
||||
err = r.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +401,8 @@ func TestTransport_proxyNetworkRequest(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, r)
|
||||
if r != nil {
|
||||
r.Body.Close()
|
||||
err = r.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/snapshot"
|
||||
"github.com/portainer/portainer/api/logs"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
@@ -135,7 +136,7 @@ func (transport *Transport) decorateVolumeResourceCreationOperation(request *htt
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
defer logs.CloseAndLogErr(cli)
|
||||
|
||||
if _, err := cli.VolumeInspect(context.Background(), volumeID); err == nil {
|
||||
return &http.Response{
|
||||
@@ -237,7 +238,7 @@ func (transport *Transport) getDockerID() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer client.Close()
|
||||
defer logs.CloseAndLogErr(client)
|
||||
|
||||
info, err := client.Info(context.Background())
|
||||
if err != nil {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/segmentio/encoding/json"
|
||||
"oras.land/oras-go/v2/registry/remote/retry"
|
||||
)
|
||||
@@ -57,7 +58,11 @@ func (c *Client) GetContainerPackages(ctx context.Context, useOrganisation bool,
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, resp.Status)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/segmentio/encoding/json"
|
||||
"oras.land/oras-go/v2/registry/remote/retry"
|
||||
)
|
||||
@@ -53,7 +54,11 @@ func (c *Client) GetRegistryRepositoryNames(ctx context.Context, projectID int)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn().Err(err).Msg("failed to close response body")
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("GitLab API returned status %d: %s", resp.StatusCode, resp.Status)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user