chore(linters): enforce error checking in CE BE-12527 (#1723)

This commit is contained in:
andres-portainer
2026-01-26 14:37:55 -03:00
committed by GitHub
parent a2a7ead82a
commit 71c000756b
146 changed files with 1335 additions and 737 deletions
+5 -1
View File
@@ -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$
+8 -8
View File
@@ -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)
+4 -1
View File
@@ -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
View File
@@ -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.
+8 -5
View File
@@ -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,
+6 -3
View File
@@ -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
View File
@@ -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")
+3 -4
View File
@@ -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
View File
@@ -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
}
+2 -4
View File
@@ -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
+3 -1
View File
@@ -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
}
+10 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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")
+26 -7
View File
@@ -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
})
+2 -1
View File
@@ -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 {
+19 -41
View File
@@ -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)
}
+2 -1
View File
@@ -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)
+6 -3
View File
@@ -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
}
+19 -6
View File
@@ -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
View File
@@ -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")
+13 -8
View File
@@ -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")
+60 -33
View File
@@ -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,
+11 -5
View File
@@ -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))
}
+2 -1
View File
@@ -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)
+10 -10
View File
@@ -77,8 +77,12 @@ func (m *Migrator) updateRegistriesToDB32() error {
Namespaces: []string{},
}
}
m.registryService.Update(registry.ID, &registry)
if err := m.registryService.Update(registry.ID, &registry); 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}
}
}
+7 -2
View File
@@ -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)
}
})
}
+10 -4
View File
@@ -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
View File
@@ -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)
+8 -2
View File
@@ -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
View File
@@ -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 {
+2 -1
View File
@@ -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)
+2 -1
View File
@@ -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)
}
+5 -2
View File
@@ -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
}
+7 -7
View File
@@ -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) {
+9 -3
View File
@@ -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)
}
+4 -2
View File
@@ -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
+13 -6
View File
@@ -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"))
+5 -4
View 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
+3 -2
View File
@@ -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())
+4 -2
View File
@@ -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")
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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
View File
@@ -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)
+25 -14
View File
@@ -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
}
+5 -5
View File
@@ -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)
+31 -18
View File
@@ -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 -3
View File
@@ -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
}
+6 -1
View File
@@ -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)
+12 -2
View File
@@ -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 {
+15 -4
View File
@@ -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) != ""
+6 -1
View File
@@ -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)
+33 -17
View File
@@ -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"))
+3 -1
View File
@@ -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 {
+15 -4
View File
@@ -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 {
+3 -1
View File
@@ -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)
+6 -2
View File
@@ -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)
})
+6 -2
View File
@@ -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)
+15 -4
View File
@@ -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 {
+5 -2
View File
@@ -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")
+5 -1
View File
@@ -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
}
+4 -1
View File
@@ -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")
+24 -12
View File
@@ -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()
+3 -1
View File
@@ -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)
+17 -6
View File
@@ -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)
+2 -1
View File
@@ -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 {
+6 -2
View File
@@ -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)
+2 -1
View File
@@ -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)
+2 -1
View File
@@ -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 -3
View File
@@ -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 {
+10 -3
View File
@@ -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)
+2 -1
View File
@@ -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)
}
+4 -3
View File
@@ -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)
+8 -7
View File
@@ -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")
}
}()
}
+2 -2
View File
@@ -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
+7 -4
View File
@@ -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()
}()
+3 -1
View File
@@ -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")
}
}
}
+10 -3
View File
@@ -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")
}
+5 -1
View File
@@ -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 {
+7 -2
View File
@@ -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")
+5 -2
View File
@@ -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)
}
}
}
+3 -2
View File
@@ -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 {
+6 -1
View File
@@ -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)
+6 -1
View File
@@ -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