1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-10 16:25:22 +02:00

fix(linters): upgrade golangci-lint to v2.3.1 BE-12136 (#997)

This commit is contained in:
andres-portainer 2025-08-08 21:39:21 -03:00 committed by GitHub
parent 85b7e881eb
commit 64ed988169
69 changed files with 582 additions and 318 deletions

View file

@ -1,50 +1,59 @@
version: "2"
linters:
# Disable all linters, the defaults don't pass on our code yet
disable-all: true
# Enable these for now
default: none
enable:
- unused
- depguard
- gosimple
- govet
- errorlint
- bodyclose
- copyloopvar
- depguard
- errorlint
- forbidigo
- govet
- ineffassign
- intrange
- perfsprint
- ineffassign
- bodyclose
- forbidigo
linters-settings:
forbidigo:
analyze-types: true
forbid:
- p: ^tls\.Config$
msg: 'Use crypto.CreateTLSConfiguration() instead'
- p: ^tls\.Config\.(InsecureSkipVerify|MinVersion|MaxVersion|CipherSuites|CurvePreferences)$
msg: 'Do not set this field directly, use crypto.CreateTLSConfiguration() instead'
depguard:
rules:
main:
deny:
- pkg: 'encoding/json'
desc: 'use github.com/segmentio/encoding/json'
- pkg: 'golang.org/x/exp'
desc: 'exp is not allowed'
- pkg: 'github.com/portainer/libcrypto'
desc: 'use github.com/portainer/portainer/pkg/libcrypto'
- pkg: 'github.com/portainer/libhttp'
desc: 'use github.com/portainer/portainer/pkg/libhttp'
files:
- '!**/*_test.go'
- '!**/base.go'
- '!**/base_tx.go'
# errorlint is causing a typecheck error for some reason. The go compiler will report these
# anyway, so ignore them from the linter
issues:
exclude-rules:
- path: ./
linters:
- typecheck
- staticcheck
- unused
settings:
staticcheck:
checks: ["all", "-ST1003", "-ST1005", "-ST1016", "-SA1019", "-QF1003"]
depguard:
rules:
main:
files:
- '!**/*_test.go'
- '!**/base.go'
- '!**/base_tx.go'
deny:
- pkg: encoding/json
desc: use github.com/segmentio/encoding/json
- pkg: golang.org/x/exp
desc: exp is not allowed
- pkg: github.com/portainer/libcrypto
desc: use github.com/portainer/portainer/pkg/libcrypto
- pkg: github.com/portainer/libhttp
desc: use github.com/portainer/portainer/pkg/libhttp
forbidigo:
forbid:
- pattern: ^tls\.Config$
msg: Use crypto.CreateTLSConfiguration() instead
- pattern: ^tls\.Config\.(InsecureSkipVerify|MinVersion|MaxVersion|CipherSuites|CurvePreferences)$
msg: Do not set this field directly, use crypto.CreateTLSConfiguration() instead
analyze-types: true
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View file

@ -54,8 +54,8 @@ func ecdsaGenerateKey(c elliptic.Curve, rand io.Reader) (*ecdsa.PrivateKey, erro
}
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = c
priv.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
priv.X, priv.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}

View file

@ -13,6 +13,7 @@ import (
"strings"
"github.com/portainer/portainer/pkg/fips"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
@ -56,8 +57,7 @@ func AesEncrypt(input io.Reader, output io.Writer, passphrase []byte) error {
// AesDecrypt reads from input, decrypts with AES-256 and returns the reader to read the decrypted content from
func AesDecrypt(input io.Reader, passphrase []byte) (io.Reader, error) {
fipsMode := fips.FIPSMode()
return aesDecrypt(input, passphrase, fipsMode)
return aesDecrypt(input, passphrase, fips.FIPSMode())
}
func aesDecrypt(input io.Reader, passphrase []byte, fipsMode bool) (io.Reader, error) {
@ -152,15 +152,14 @@ func aesEncryptGCM(input io.Reader, output io.Writer, passphrase []byte) error {
break // end of plaintext input
}
if err != nil && !(errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)) {
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
return err
}
// Seal encrypts the plaintext using the nonce returning the updated slice.
ciphertext = aesgcm.Seal(ciphertext[:0], nonce.Value(), buf[:n], nil)
_, err = output.Write(ciphertext)
if err != nil {
if _, err := output.Write(ciphertext); err != nil {
return err
}
@ -221,7 +220,7 @@ func aesDecryptGCM(input io.Reader, passphrase []byte) (io.Reader, error) {
break // end of ciphertext
}
if err != nil && !(errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)) {
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
return nil, err
}
@ -282,15 +281,14 @@ func aesEncryptGCMFIPS(input io.Reader, output io.Writer, passphrase []byte) err
break // end of plaintext input
}
if err != nil && !(errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)) {
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
return err
}
// Seal encrypts the plaintext
ciphertext := aesgcm.Seal(nil, nil, buf[:n], nil)
_, err = output.Write(ciphertext)
if err != nil {
if _, err := output.Write(ciphertext); err != nil {
return err
}
}
@ -343,7 +341,7 @@ func aesDecryptGCMFIPS(input io.Reader, passphrase []byte) (io.Reader, error) {
break // end of ciphertext
}
if err != nil && !(errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)) {
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
return nil, err
}
@ -365,11 +363,9 @@ func aesDecryptGCMFIPS(input io.Reader, passphrase []byte) (io.Reader, error) {
// passphrase is used to generate an encryption key.
// note: This function used to decrypt files that were encrypted without a header i.e. old archives
func aesDecryptOFB(input io.Reader, passphrase []byte) (io.Reader, error) {
var emptySalt []byte = make([]byte, 0)
// making a 32 bytes key that would correspond to AES-256
// don't necessarily need a salt, so just kept in empty
key, err := scrypt.Key(passphrase, emptySalt, 32768, 8, 1, 32)
key, err := scrypt.Key(passphrase, nil, 32768, 8, 1, 32)
if err != nil {
return nil, err
}

View file

@ -1,6 +1,8 @@
package crypto
import (
"crypto/aes"
"crypto/cipher"
"io"
"math/rand"
"os"
@ -8,8 +10,10 @@ import (
"testing"
"github.com/portainer/portainer/pkg/fips"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/scrypt"
)
func init() {
@ -23,6 +27,7 @@ func randBytes(n int) []byte {
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return b
}
@ -123,6 +128,10 @@ func Test_encryptAndDecrypt_withTheSamePassword(t *testing.T) {
testFunc(t, aesEncryptGCMFIPS, decrypt, true)
})
t.Run("legacy", func(t *testing.T) {
testFunc(t, legacyAesEncrypt, aesDecryptOFB, true)
})
}
func Test_encryptAndDecrypt_withStrongPassphrase(t *testing.T) {
@ -319,3 +328,25 @@ func Test_decryptWithDifferentPassphrase_shouldProduceWrongResult(t *testing.T)
testFunc(t, aesEncryptGCM, aesDecryptGCM)
})
}
func legacyAesEncrypt(input io.Reader, output io.Writer, passphrase []byte) error {
key, err := scrypt.Key(passphrase, nil, 32768, 8, 1, 32)
if err != nil {
return err
}
block, err := aes.NewCipher(key)
if err != nil {
return err
}
var iv [aes.BlockSize]byte
stream := cipher.NewOFB(block, iv[:])
writer := &cipher.StreamWriter{S: stream, W: output}
if _, err := io.Copy(writer, input); err != nil {
return err
}
return nil
}

View file

@ -6,12 +6,11 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log"
)
const (
BucketName = "pending_actions"
)
const BucketName = "pending_actions"
type Service struct {
dataservices.BaseDataService[portainer.PendingAction, portainer.PendingActionID]
@ -78,15 +77,14 @@ func (s ServiceTx) Update(ID portainer.PendingActionID, config *portainer.Pendin
func (s ServiceTx) DeleteByEndpointID(ID portainer.EndpointID) error {
log.Debug().Int("endpointId", int(ID)).Msg("deleting pending actions for endpoint")
pendingActions, err := s.BaseDataServiceTx.ReadAll()
pendingActions, err := s.ReadAll()
if err != nil {
return fmt.Errorf("failed to retrieve pending-actions for endpoint (%d): %w", ID, err)
}
for _, pendingAction := range pendingActions {
if pendingAction.EndpointID == ID {
err := s.BaseDataServiceTx.Delete(pendingAction.ID)
if err != nil {
if err := s.Delete(pendingAction.ID); err != nil {
log.Debug().Int("endpointId", int(ID)).Msgf("failed to delete pending action: %v", err)
}
}

View file

@ -0,0 +1,32 @@
package pendingactions_test
import (
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/stretchr/testify/require"
)
func TestDeleteByEndpoint(t *testing.T) {
_, store := datastore.MustNewTestStore(t, false, false)
// Create Endpoint 1
err := store.PendingActions().Create(&portainer.PendingAction{EndpointID: 1})
require.NoError(t, err)
// Create Endpoint 2
err = store.PendingActions().Create(&portainer.PendingAction{EndpointID: 2})
require.NoError(t, err)
// Delete Endpoint 1
err = store.PendingActions().DeleteByEndpointID(1)
require.NoError(t, err)
// Check that only Endpoint 2 remains
pendingActions, err := store.PendingActions().ReadAll()
require.NoError(t, err)
require.Len(t, pendingActions, 1)
require.Equal(t, portainer.EndpointID(2), pendingActions[0].EndpointID)
}

View file

@ -149,7 +149,7 @@ func (migrator *PostInitMigrator) MigrateGPUs(e portainer.Endpoint, dockerClient
return migrator.dataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
environment, err := tx.Endpoint().Endpoint(e.ID)
if err != nil {
log.Error().Err(err).Msgf("Error getting environment %d", environment.ID)
log.Error().Err(err).Msgf("Error getting environment %d", e.ID)
return err
}
// Early exit if we do not need to migrate!
@ -175,10 +175,11 @@ func (migrator *PostInitMigrator) MigrateGPUs(e portainer.Endpoint, dockerClient
continue
}
deviceRequests := containerDetails.HostConfig.Resources.DeviceRequests
deviceRequests := containerDetails.HostConfig.DeviceRequests
for _, deviceRequest := range deviceRequests {
if deviceRequest.Driver == "nvidia" {
environment.EnableGPUManagement = true
break containersLoop
}
}
@ -186,8 +187,7 @@ func (migrator *PostInitMigrator) MigrateGPUs(e portainer.Endpoint, dockerClient
// set the MigrateGPUs flag to false so we don't run this again
environment.PostInitMigrations.MigrateGPUs = false
err = tx.Endpoint().UpdateEndpoint(environment.ID, environment)
if err != nil {
if err := tx.Endpoint().UpdateEndpoint(environment.ID, environment); err != nil {
log.Error().Err(err).Msgf("Error updating EnableGPUManagement flag for environment %d", environment.ID)
return err
}

View file

@ -0,0 +1,75 @@
package postinit
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/segmentio/encoding/json"
"github.com/stretchr/testify/require"
)
func TestMigrateGPUs(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/containers/json") {
containerSummary := []container.Summary{{ID: "container1"}}
err := json.NewEncoder(w).Encode(containerSummary)
require.NoError(t, err)
return
}
container := container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: "container1",
HostConfig: &container.HostConfig{
Resources: container.Resources{
DeviceRequests: []container.DeviceRequest{
{Driver: "nvidia"},
},
},
},
},
}
err := json.NewEncoder(w).Encode(container)
require.NoError(t, err)
}))
defer srv.Close()
_, store := datastore.MustNewTestStore(t, true, false)
migrator := &PostInitMigrator{dataStore: store}
dockerCli, err := client.NewClientWithOpts(client.WithHost(srv.URL), client.WithHTTPClient(http.DefaultClient))
require.NoError(t, err)
// Nonexistent endpoint
err = migrator.MigrateGPUs(portainer.Endpoint{}, dockerCli)
require.Error(t, err)
// Valid endpoint
endpoint := portainer.Endpoint{ID: 1, PostInitMigrations: portainer.EndpointPostInitMigrations{MigrateGPUs: true}}
err = store.Endpoint().Create(&endpoint)
require.NoError(t, err)
err = migrator.MigrateGPUs(endpoint, dockerCli)
require.NoError(t, err)
migratedEndpoint, err := store.Endpoint().Endpoint(endpoint.ID)
require.NoError(t, err)
require.Equal(t, endpoint.ID, migratedEndpoint.ID)
require.False(t, migratedEndpoint.PostInitMigrations.MigrateGPUs)
require.True(t, migratedEndpoint.EnableGPUManagement)
}

View file

@ -24,6 +24,8 @@ const (
dockerClientVersion = "1.37"
)
type NodeNamesCtxKey struct{}
// ClientFactory is used to create Docker clients
type ClientFactory struct {
signatureService portainer.DigitalSignatureService
@ -162,7 +164,7 @@ func (t *NodeNameTransport) RoundTrip(req *http.Request) (*http.Response, error)
return resp, nil
}
nodeNames, ok := req.Context().Value("nodeNames").(map[string]string)
nodeNames, ok := req.Context().Value(NodeNamesCtxKey{}).(map[string]string)
if ok {
for idx, r := range rs {
// as there is no way to differentiate the same image available in multiple nodes only by their ID

View file

@ -9,7 +9,7 @@ import (
"github.com/containers/image/v5/docker"
imagetypes "github.com/containers/image/v5/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
@ -85,7 +85,7 @@ func (c *DigestClient) RemoteDigest(image Image) (digest.Digest, error) {
return rmDigest, nil
}
func ParseLocalImage(inspect types.ImageInspect) (*Image, error) {
func ParseLocalImage(inspect image.InspectResponse) (*Image, error) {
if IsLocalImage(inspect) || IsDanglingImage(inspect) {
return nil, errors.New("the image is not regular")
}

View file

@ -0,0 +1,33 @@
package images
import (
"testing"
"github.com/docker/docker/api/types/image"
"github.com/stretchr/testify/require"
)
func TestParseLocalImage(t *testing.T) {
// Test with a regular image
img, err := ParseLocalImage(image.InspectResponse{
ID: "sha256:9234e8fb04c47cfe0f49931e4ac7eb76fa904e33b7f8576aec0501c085f02516",
RepoTags: []string{"myimage:latest"},
RepoDigests: []string{"myimage@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1"},
})
require.NoError(t, err)
require.NotNil(t, img)
require.Equal(t, "library/myimage", img.Path)
require.Equal(t, "latest", img.Tag)
require.Equal(t, "sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1", img.Digest.String())
// Test with a dangling image
img, err = ParseLocalImage(image.InspectResponse{
ID: "sha256:abcdef1234567890",
RepoTags: []string{"<none>:<none>"},
RepoDigests: []string{"<none>@<none>"},
})
require.Error(t, err)
require.Nil(t, img)
}

View file

@ -7,9 +7,9 @@ import (
"strings"
"text/template"
"github.com/docker/docker/api/types"
"github.com/containers/image/v5/docker/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
@ -179,11 +179,11 @@ func IsLocalImage(image types.ImageInspect) bool {
// IsDanglingImage returns whether the given image is "dangling" which means
// that there are no repository references to the given image and it has no
// child images
func IsDanglingImage(image types.ImageInspect) bool {
func IsDanglingImage(image image.InspectResponse) bool {
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
}
// IsNoTagImage returns whether the given image is damaged, has no tags
func IsNoTagImage(image types.ImageInspect) bool {
func IsNoTagImage(image image.InspectResponse) bool {
return len(image.RepoTags) == 0
}

View file

@ -40,7 +40,7 @@ func Test_ClonePublicRepository_Shallow(t *testing.T) {
t.Logf("Cloning into %s", dir)
err := service.CloneRepository(dir, repositoryURL, referenceName, "", "", gittypes.GitCredentialAuthType_Basic, false)
assert.NoError(t, err)
assert.Equal(t, 1, getCommitHistoryLength(t, err, dir), "cloned repo has incorrect depth")
assert.Equal(t, 1, getCommitHistoryLength(t, dir), "cloned repo has incorrect depth")
}
func Test_ClonePublicRepository_NoGitDirectory(t *testing.T) {
@ -75,7 +75,7 @@ func Test_cloneRepository(t *testing.T) {
})
assert.NoError(t, err)
assert.Equal(t, 4, getCommitHistoryLength(t, err, dir), "cloned repo has incorrect depth")
assert.Equal(t, 4, getCommitHistoryLength(t, dir), "cloned repo has incorrect depth")
}
func Test_latestCommitID(t *testing.T) {
@ -123,7 +123,7 @@ func Test_ListFiles(t *testing.T) {
assert.Equal(t, []string{"docker-compose.yml"}, fs)
}
func getCommitHistoryLength(t *testing.T, err error, dir string) int {
func getCommitHistoryLength(t *testing.T, dir string) int {
repo, err := git.PlainOpen(dir)
if err != nil {
t.Fatalf("can't open a git repo at %s with error %v", dir, err)
@ -135,11 +135,10 @@ func getCommitHistoryLength(t *testing.T, err error, dir string) int {
}
count := 0
err = iter.ForEach(func(_ *object.Commit) error {
if err := iter.ForEach(func(_ *object.Commit) error {
count++
return nil
})
if err != nil {
}); err != nil {
t.Fatalf("can't iterate over the commit history with error %v", err)
}

View file

@ -141,10 +141,7 @@ func pingOperation(client *http.Client, target string) (bool, error) {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
agentOnDockerEnvironment := false
if resp.Header.Get(portainer.PortainerAgentHeader) != "" {
agentOnDockerEnvironment = true
}
agentOnDockerEnvironment := resp.Header.Get(portainer.PortainerAgentHeader) != ""
return agentOnDockerEnvironment, nil
}

View file

@ -1,6 +1,8 @@
package client
import (
"net/http"
"net/http/httptest"
"testing"
portainer "github.com/portainer/portainer/api"
@ -29,3 +31,14 @@ func TestExecutePingOperationFailure(t *testing.T) {
require.Error(t, err)
}
func TestPingOperation(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add(portainer.PortainerAgentHeader, "1")
}))
defer srv.Close()
agentOnDockerEnv, err := pingOperation(http.DefaultClient, srv.URL)
require.NoError(t, err)
require.True(t, agentOnDockerEnv)
}

View file

@ -6,6 +6,7 @@ import (
"net/http"
"strings"
"github.com/portainer/portainer/api/docker/client"
"github.com/portainer/portainer/api/http/handler/docker/utils"
"github.com/portainer/portainer/api/set"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
@ -50,7 +51,7 @@ func (handler *Handler) imagesList(w http.ResponseWriter, r *http.Request) *http
nodeNames := make(map[string]string)
// Pass the node names map to the context so the custom NodeNameTransport can use it
ctx := context.WithValue(r.Context(), "nodeNames", nodeNames)
ctx := context.WithValue(r.Context(), client.NodeNamesCtxKey{}, nodeNames)
images, err := cli.ImageList(ctx, image.ListOptions{})
if err != nil {

View file

@ -6,14 +6,13 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
portainer "github.com/portainer/portainer/api"
portaineree "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
dockerconsts "github.com/portainer/portainer/api/docker/consts"
"github.com/portainer/portainer/api/http/security"
)
type StackViewModel struct {
InternalStack *portaineree.Stack
InternalStack *portainer.Stack
ID portainer.StackID
Name string
@ -23,7 +22,6 @@ type StackViewModel struct {
// GetDockerStacks retrieves all the stacks associated to a specific environment filtered by the user's access.
func GetDockerStacks(tx dataservices.DataStoreTx, securityContext *security.RestrictedRequestContext, environmentID portainer.EndpointID, containers []types.Container, services []swarm.Service) ([]StackViewModel, error) {
stacks, err := tx.Stack().ReadAll()
if err != nil {
return nil, fmt.Errorf("Unable to retrieve stacks: %w", err)

View file

@ -6,15 +6,15 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
portainer "github.com/portainer/portainer/api"
portaineree "github.com/portainer/portainer/api"
dockerconsts "github.com/portainer/portainer/api/docker/consts"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/stretchr/testify/assert"
)
func TestHandler_getDockerStacks(t *testing.T) {
environment := &portaineree.Endpoint{
environment := &portainer.Endpoint{
ID: 1,
SecuritySettings: portainer.EndpointSecuritySettings{
AllowStackManagementForRegularUsers: true,
@ -46,7 +46,7 @@ func TestHandler_getDockerStacks(t *testing.T) {
},
}
stack1 := portaineree.Stack{
stack1 := portainer.Stack{
ID: 1,
Name: "stack1",
EndpointID: 1,
@ -54,8 +54,8 @@ func TestHandler_getDockerStacks(t *testing.T) {
}
datastore := testhelpers.NewDatastore(
testhelpers.WithEndpoints([]portaineree.Endpoint{*environment}),
testhelpers.WithStacks([]portaineree.Stack{
testhelpers.WithEndpoints([]portainer.Endpoint{*environment}),
testhelpers.WithStacks([]portainer.Stack{
stack1,
{
ID: 2,

View file

@ -6,11 +6,11 @@ import (
"testing"
portainer "github.com/portainer/portainer/api"
helper "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/internal/testhelpers"
)
func TestEmptyGlobalKey(t *testing.T) {
handler := NewHandler(helper.NewTestRequestBouncer())
handler := NewHandler(testhelpers.NewTestRequestBouncer())
req, err := http.NewRequest(http.MethodPost, "https://portainer.io:9443/endpoints/global-key", nil)
if err != nil {

View file

@ -723,27 +723,22 @@ func getEdgeStackStatusParam(r *http.Request) (*portainer.EdgeStackStatusType, e
}
func getShortestAsyncInterval(endpoint *portainer.Endpoint, settings *portainer.Settings) int {
var edgeIntervalUseDefault int = -1
const edgeIntervalUseDefault = -1
pingInterval := endpoint.Edge.PingInterval
if pingInterval == edgeIntervalUseDefault {
pingInterval = settings.Edge.PingInterval
}
shortestAsyncInterval := pingInterval
snapshotInterval := endpoint.Edge.SnapshotInterval
if snapshotInterval == edgeIntervalUseDefault {
snapshotInterval = settings.Edge.SnapshotInterval
}
if shortestAsyncInterval > snapshotInterval {
shortestAsyncInterval = snapshotInterval
}
commandInterval := endpoint.Edge.CommandInterval
if commandInterval == edgeIntervalUseDefault {
commandInterval = settings.Edge.CommandInterval
}
if shortestAsyncInterval > commandInterval {
shortestAsyncInterval = commandInterval
}
return shortestAsyncInterval
return min(pingInterval, snapshotInterval, commandInterval)
}

View file

@ -22,7 +22,6 @@ type filterTest struct {
}
func Test_Filter_AgentVersion(t *testing.T) {
version1Endpoint := portainer.Endpoint{ID: 1, GroupID: 1,
Type: portainer.AgentOnDockerEnvironment,
Agent: struct {
@ -78,7 +77,6 @@ func Test_Filter_AgentVersion(t *testing.T) {
}
func Test_Filter_edgeFilter(t *testing.T) {
trustedEdgeAsync := portainer.Endpoint{ID: 1, UserTrusted: true, Edge: portainer.EnvironmentEdgeSettings{AsyncMode: true}, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
untrustedEdgeAsync := portainer.Endpoint{ID: 2, UserTrusted: false, Edge: portainer.EnvironmentEdgeSettings{AsyncMode: true}, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
regularUntrustedEdgeStandard := portainer.Endpoint{ID: 3, UserTrusted: false, Edge: portainer.EnvironmentEdgeSettings{AsyncMode: false}, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
@ -280,7 +278,6 @@ func runTest(t *testing.T, test filterTest, handler *Handler, endpoints []portai
}
is.ElementsMatch(test.expected, respIds)
}
func setupFilterTest(t *testing.T, endpoints []portainer.Endpoint) *Handler {
@ -426,3 +423,25 @@ func TestFilterEndpointsByExcludeEdgeGroupIDs(t *testing.T) {
require.Len(t, egs, 1)
require.Equal(t, egs[0].ID, portainer.EdgeGroupID(2))
}
func TestGetShortestAsyncInterval(t *testing.T) {
endpoint := &portainer.Endpoint{
ID: 1,
Name: "Test Endpoint",
Edge: portainer.EnvironmentEdgeSettings{
PingInterval: -1,
SnapshotInterval: -1,
CommandInterval: -1,
},
}
settings := &portainer.Settings{
Edge: portainer.Edge{
PingInterval: 10,
SnapshotInterval: 20,
CommandInterval: 30,
},
}
require.Equal(t, 10, getShortestAsyncInterval(endpoint, settings))
}

View file

@ -10,7 +10,6 @@ import (
"github.com/portainer/portainer/api/exec/exectest"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/testhelpers"
helper "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/jwt"
"github.com/portainer/portainer/api/kubernetes"
"github.com/portainer/portainer/pkg/libhelm/options"
@ -36,7 +35,7 @@ func Test_helmDelete(t *testing.T) {
kubernetesDeployer := exectest.NewKubernetesDeployer()
helmPackageManager := test.NewMockHelmPackageManager()
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "")
h := NewHandler(helper.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
h := NewHandler(testhelpers.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
is.NotNil(h, "Handler should not fail")

View file

@ -11,7 +11,6 @@ import (
"github.com/portainer/portainer/api/exec/exectest"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/testhelpers"
helper "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/jwt"
"github.com/portainer/portainer/api/kubernetes"
"github.com/portainer/portainer/pkg/libhelm/options"
@ -39,7 +38,7 @@ func Test_helmGet(t *testing.T) {
kubernetesDeployer := exectest.NewKubernetesDeployer()
helmPackageManager := test.NewMockHelmPackageManager()
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "")
h := NewHandler(helper.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
h := NewHandler(testhelpers.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
is.NotNil(h, "Handler should not fail")

View file

@ -11,7 +11,6 @@ import (
"github.com/portainer/portainer/api/exec/exectest"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/testhelpers"
helper "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/jwt"
"github.com/portainer/portainer/api/kubernetes"
"github.com/portainer/portainer/pkg/libhelm/options"
@ -39,7 +38,7 @@ func Test_helmGetHistory(t *testing.T) {
kubernetesDeployer := exectest.NewKubernetesDeployer()
helmPackageManager := test.NewMockHelmPackageManager()
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "")
h := NewHandler(helper.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
h := NewHandler(testhelpers.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
is.NotNil(h, "Handler should not fail")

View file

@ -12,7 +12,6 @@ import (
"github.com/portainer/portainer/api/exec/exectest"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/testhelpers"
helper "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/jwt"
"github.com/portainer/portainer/api/kubernetes"
"github.com/portainer/portainer/pkg/libhelm/options"
@ -40,7 +39,7 @@ func Test_helmInstall(t *testing.T) {
kubernetesDeployer := exectest.NewKubernetesDeployer()
helmPackageManager := test.NewMockHelmPackageManager()
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "")
h := NewHandler(helper.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
h := NewHandler(testhelpers.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
is.NotNil(h, "Handler should not fail")

View file

@ -11,7 +11,6 @@ import (
"github.com/portainer/portainer/api/exec/exectest"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/testhelpers"
helper "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/jwt"
"github.com/portainer/portainer/api/kubernetes"
"github.com/portainer/portainer/pkg/libhelm/options"
@ -39,7 +38,7 @@ func Test_helmList(t *testing.T) {
kubernetesDeployer := exectest.NewKubernetesDeployer()
helmPackageManager := test.NewMockHelmPackageManager()
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "")
h := NewHandler(helper.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
h := NewHandler(testhelpers.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService)
// Install a single chart. We expect to get these values back
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}

View file

@ -7,7 +7,7 @@ import (
"net/url"
"testing"
helper "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/pkg/libhelm/test"
"github.com/stretchr/testify/assert"
)
@ -16,7 +16,7 @@ func Test_helmRepoSearch(t *testing.T) {
is := assert.New(t)
helmPackageManager := test.NewMockHelmPackageManager()
h := NewTemplateHandler(helper.NewTestRequestBouncer(), helmPackageManager)
h := NewTemplateHandler(testhelpers.NewTestRequestBouncer(), helmPackageManager)
assert.NotNil(t, h, "Handler should not fail")

View file

@ -8,7 +8,7 @@ import (
"net/url"
"testing"
helper "github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/pkg/libhelm/test"
"github.com/stretchr/testify/assert"
)
@ -17,7 +17,7 @@ func Test_helmShow(t *testing.T) {
is := assert.New(t)
helmPackageManager := test.NewMockHelmPackageManager()
h := NewTemplateHandler(helper.NewTestRequestBouncer(), helmPackageManager)
h := NewTemplateHandler(testhelpers.NewTestRequestBouncer(), helmPackageManager)
is.NotNil(h, "Handler should not fail")

View file

@ -29,9 +29,7 @@ func Test_RegistryAccess_RequiresAuthentication(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/registries/1", nil)
req = mux.SetURLVars(req, map[string]string{"id": "1"})
rr := httptest.NewRecorder()
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
bouncer := handler.RegistryAccess(testHandler)
bouncer.ServeHTTP(rr, req)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
@ -53,9 +51,7 @@ func Test_RegistryAccess_InvalidRegistryID(t *testing.T) {
rr := httptest.NewRecorder()
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
bouncer := handler.RegistryAccess(testHandler)
bouncer.ServeHTTP(rr, req)
@ -79,9 +75,7 @@ func Test_RegistryAccess_RegistryNotFound(t *testing.T) {
rr := httptest.NewRecorder()
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
bouncer := handler.RegistryAccess(testHandler)
bouncer.ServeHTTP(rr, req)

View file

@ -30,21 +30,21 @@ func TestHandler_webhookInvoke(t *testing.T) {
t.Run("invalid uuid results in http.StatusBadRequest", func(t *testing.T) {
w := httptest.NewRecorder()
req := newRequest("notuuid")
h.Router.ServeHTTP(w, req)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
})
t.Run("registered webhook ID in http.StatusNoContent", func(t *testing.T) {
w := httptest.NewRecorder()
req := newRequest(webhookID)
h.Router.ServeHTTP(w, req)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusNoContent, w.Code)
})
t.Run("unregistered webhook ID in http.StatusNotFound", func(t *testing.T) {
w := httptest.NewRecorder()
req := newRequest(newGuidString(t))
h.Router.ServeHTTP(w, req)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
})
}

View file

@ -58,8 +58,7 @@ func (handler *Handler) teamMembershipUpdate(w http.ResponseWriter, r *http.Requ
}
var payload teamMembershipUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
return httperror.BadRequest("Invalid request payload", err)
}
@ -77,7 +76,7 @@ func (handler *Handler) teamMembershipUpdate(w http.ResponseWriter, r *http.Requ
isLeadingBothTeam := security.AuthorizedTeamManagement(portainer.TeamID(payload.TeamID), securityContext) &&
security.AuthorizedTeamManagement(membership.TeamID, securityContext)
if !(securityContext.IsAdmin || isLeadingBothTeam) {
if !securityContext.IsAdmin && !isLeadingBothTeam {
return httperror.Forbidden("Permission denied to update the membership", httperrors.ErrResourceAccessDenied)
}
@ -85,8 +84,7 @@ func (handler *Handler) teamMembershipUpdate(w http.ResponseWriter, r *http.Requ
membership.TeamID = portainer.TeamID(payload.TeamID)
membership.Role = portainer.MembershipRole(payload.Role)
err = handler.DataStore.TeamMembership().Update(membership.ID, membership)
if err != nil {
if err := handler.DataStore.TeamMembership().Update(membership.ID, membership); err != nil {
return httperror.InternalServerError("Unable to persist membership changes inside the database", err)
}

View file

@ -0,0 +1,50 @@
package teammemberships
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/segmentio/encoding/json"
"github.com/stretchr/testify/require"
)
func TestUpdate(t *testing.T) {
_, store := datastore.MustNewTestStore(t, false, false)
err := store.TeamMembershipService.Create(&portainer.TeamMembership{
UserID: 3,
TeamID: 3,
Role: 1,
})
require.NoError(t, err)
h := NewHandler(testhelpers.NewTestRequestBouncer())
h.DataStore = store
payload := `{"UserID": 3, "TeamID": 2, "Role": 1}`
rr := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodPut, "/team_memberships/1", strings.NewReader(payload))
restrictedCtx := &security.RestrictedRequestContext{IsAdmin: true}
r = r.WithContext(security.StoreRestrictedRequestContext(r, restrictedCtx))
h.ServeHTTP(rr, r)
require.Equal(t, http.StatusOK, rr.Code)
var updatedMembership portainer.TeamMembership
err = json.Unmarshal(rr.Body.Bytes(), &updatedMembership)
require.NoError(t, err)
require.Equal(t, portainer.UserID(3), updatedMembership.UserID)
require.Equal(t, portainer.TeamID(2), updatedMembership.TeamID)
require.Equal(t, portainer.MembershipRole(1), updatedMembership.Role)
}

View file

@ -12,14 +12,10 @@ import (
)
func TestInitDial(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer srv.Close()
tlsSrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
tlsSrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer tlsSrv.Close()
f := func(srvURL string) {

View file

@ -9,5 +9,5 @@ import (
func TestNewLocalTransport(t *testing.T) {
transport, err := NewLocalTransport(nil, nil, nil, nil, nil)
require.NoError(t, err)
require.True(t, transport.baseTransport.httpTransport.TLSClientConfig.InsecureSkipVerify) //nolint:forbidigo
require.True(t, transport.httpTransport.TLSClientConfig.InsecureSkipVerify) //nolint:forbidigo
}

View file

@ -277,7 +277,6 @@ func TestBaseTransport_AddTokenForExec_Integration(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
capturedRequest = r
capturedHeaders = r.Header.Clone()
w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))
}))
defer testServer.Close()

View file

@ -19,9 +19,7 @@ import (
)
// testHandler200 is a simple handler which returns HTTP status 200 OK
var testHandler200 = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
var testHandler200 = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
func tokenLookupSucceed(dataStore dataservices.DataStore, jwtService portainer.JWTService) tokenLookup {
return func(r *http.Request) (*portainer.TokenData, error) {

View file

@ -9,9 +9,7 @@ import (
)
func TestLimitAccess(t *testing.T) {
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
t.Run("Request below the limit", func(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)

View file

@ -181,15 +181,15 @@ func TestExtractListModifiersQueryParams(t *testing.T) {
result := ExtractListModifiersQueryParams(req)
// Assertions
require.Equal(t, tt.expectedResult.SearchQueryParams.search, result.SearchQueryParams.search,
require.Equal(t, tt.expectedResult.search, result.search,
"Search parameter should match expected value")
require.Equal(t, tt.expectedResult.SortQueryParams.sort, result.SortQueryParams.sort,
require.Equal(t, tt.expectedResult.sort, result.sort,
"Sort parameter should match expected value")
require.Equal(t, tt.expectedResult.SortQueryParams.order, result.SortQueryParams.order,
require.Equal(t, tt.expectedResult.order, result.order,
"Order parameter should match expected value")
require.Equal(t, tt.expectedResult.PaginationQueryParams.start, result.PaginationQueryParams.start,
require.Equal(t, tt.expectedResult.start, result.start,
"Start parameter should match expected value")
require.Equal(t, tt.expectedResult.PaginationQueryParams.limit, result.PaginationQueryParams.limit,
require.Equal(t, tt.expectedResult.limit, result.limit,
"Limit parameter should match expected value")
// Verify the complete struct

View file

@ -133,7 +133,7 @@ func (service *Service) ParseAndVerifyToken(token string) (*portainer.TokenData,
}
user, err := service.dataStore.User().Read(portainer.UserID(cl.UserID))
if err != nil || user.TokenIssueAt > cl.RegisteredClaims.IssuedAt.Unix() {
if err != nil || user.TokenIssueAt > cl.IssuedAt.Unix() {
return nil, "", time.Time{}, errInvalidJWTToken
}
@ -205,7 +205,7 @@ func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt
// If expiresAt is set to a zero value, the token should never expire
if expiresAt.IsZero() {
cl.RegisteredClaims.ExpiresAt = nil
cl.ExpiresAt = nil
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, cl)

View file

@ -73,7 +73,7 @@ func parseEvent(event *corev1.Event) models.K8sEvent {
EventTime: event.EventTime.UTC(),
Kind: event.Kind,
Count: event.Count,
UID: string(event.ObjectMeta.GetUID()),
UID: string(event.GetUID()),
InvolvedObjectKind: models.K8sEventInvolvedObject{
Kind: event.InvolvedObject.Kind,
UID: string(event.InvolvedObject.UID),

View file

@ -119,14 +119,17 @@ func parseJobTimes(job batchv1.Job) jobTimes {
duration: "N/A",
}
if st := job.Status.StartTime; st != nil {
times.start = st.Time.Format(time.RFC3339)
times.duration = time.Since(st.Time).Truncate(time.Minute).String()
st := job.Status.StartTime
if st == nil {
return times
}
if ct := job.Status.CompletionTime; ct != nil {
times.finish = ct.Time.Format(time.RFC3339)
times.duration = ct.Time.Sub(st.Time).String()
}
times.start = st.Format(time.RFC3339)
times.duration = time.Since(st.Time).Truncate(time.Minute).String()
if ct := job.Status.CompletionTime; ct != nil {
times.finish = ct.Format(time.RFC3339)
times.duration = ct.Time.Sub(st.Time).String()
}
return times

View file

@ -3,8 +3,11 @@ package cli
import (
"context"
"testing"
"time"
models "github.com/portainer/portainer/api/http/models/kubernetes"
"github.com/stretchr/testify/require"
batchv1 "k8s.io/api/batch/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kfake "k8s.io/client-go/kubernetes/fake"
@ -62,3 +65,27 @@ func (kcl *KubeClient) TestFetchJobs(t *testing.T) {
}
})
}
func TestParseJobTimes(t *testing.T) {
// Empty job
jobTimes := parseJobTimes(batchv1.Job{})
require.Equal(t, "N/A", jobTimes.duration)
require.Equal(t, "N/A", jobTimes.start)
require.Equal(t, "N/A", jobTimes.finish)
// Full job
now := time.Now()
completionTime := now.Add(10 * time.Minute)
jobTimes = parseJobTimes(batchv1.Job{
Status: batchv1.JobStatus{
StartTime: &metav1.Time{Time: now},
CompletionTime: &metav1.Time{Time: completionTime},
},
})
require.Equal(t, "10m0s", jobTimes.duration)
require.Equal(t, now.Format(time.RFC3339), jobTimes.start)
require.Equal(t, completionTime.Format(time.RFC3339), jobTimes.finish)
}

View file

@ -26,7 +26,7 @@ func (kcl *KubeClient) GetNodesLimits() (portainer.K8sNodesLimits, error) {
cpu := item.Status.Allocatable.Cpu().MilliValue()
memory := item.Status.Allocatable.Memory().Value() // bytes
nodesLimits[item.ObjectMeta.Name] = &portainer.K8sNodeLimits{
nodesLimits[item.Name] = &portainer.K8sNodeLimits{
CPU: cpu,
Memory: memory,
}

View file

@ -86,7 +86,7 @@ func (kcl *KubeClient) UpdateNamespacesWithResourceQuotas(namespaces map[string]
// GetResourceQuotaFromNamespace gets the resource quota in a specific namespace where the resource quota's name is prefixed with "portainer-rq-".
func (kcl *KubeClient) GetResourceQuotaFromNamespace(namespace portainer.K8sNamespaceInfo, resourceQuotas []corev1.ResourceQuota) *corev1.ResourceQuota {
for _, resourceQuota := range resourceQuotas {
if resourceQuota.ObjectMeta.Namespace == namespace.Name && resourceQuota.ObjectMeta.Name == "portainer-rq-"+namespace.Name {
if resourceQuota.Namespace == namespace.Name && resourceQuota.Name == "portainer-rq-"+namespace.Name {
return &resourceQuota
}
}

View file

@ -4,6 +4,11 @@ import (
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientV1 "k8s.io/client-go/tools/clientcmd/api/v1"
)
@ -91,3 +96,35 @@ func Test_GenerateYAML(t *testing.T) {
})
}
}
func TestGetResourceQuotaFromNamespace(t *testing.T) {
kcl := &KubeClient{}
namespace := portainer.K8sNamespaceInfo{Name: "my-namespace"}
resourceQuotas := []v1.ResourceQuota{
{
ObjectMeta: metav1.ObjectMeta{
Name: "portainer-rq-" + namespace.Name + "-1",
Namespace: namespace.Name,
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "portainer-rq-" + namespace.Name,
Namespace: namespace.Name,
},
},
}
rq := kcl.GetResourceQuotaFromNamespace(namespace, resourceQuotas)
require.NotNil(t, rq)
require.Equal(t, namespace.Name, rq.Namespace)
// Empty cases
rq = kcl.GetResourceQuotaFromNamespace(namespace, nil)
require.Nil(t, rq)
namespace.Name = "another-namespace"
rq = kcl.GetResourceQuotaFromNamespace(namespace, resourceQuotas)
require.Nil(t, rq)
}

View file

@ -263,7 +263,7 @@ func (kcl *KubeClient) updateVolumesWithOwningApplications(volumes *[]models.K8s
for _, pod := range pods.Items {
if pod.Spec.Volumes != nil {
for _, podVolume := range pod.Spec.Volumes {
if podVolume.VolumeSource.PersistentVolumeClaim != nil && podVolume.VolumeSource.PersistentVolumeClaim.ClaimName == volume.PersistentVolumeClaim.Name && pod.Namespace == volume.PersistentVolumeClaim.Namespace {
if podVolume.PersistentVolumeClaim != nil && podVolume.PersistentVolumeClaim.ClaimName == volume.PersistentVolumeClaim.Name && pod.Namespace == volume.PersistentVolumeClaim.Namespace {
application, err := kcl.ConvertPodToApplication(pod, PortainerApplicationResources{
ReplicaSets: replicaSetItems,
Deployments: deploymentItems,

View file

@ -12,14 +12,10 @@ import (
)
func TestCreateConnectionForURL(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer srv.Close()
tlsSrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
tlsSrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer tlsSrv.Close()
srvURL, err := url.Parse(tlsSrv.URL)

View file

@ -104,5 +104,5 @@ func (b *K8sStackFileContentBuilder) Deploy(payload *StackPayload, endpoint *por
}
func (b *K8sStackFileContentBuilder) GetResponse() string {
return b.FileContentMethodStackBuilder.deploymentConfiger.GetResponse()
return b.deploymentConfiger.GetResponse()
}

View file

@ -0,0 +1,28 @@
package stackbuilders
import (
"testing"
"github.com/portainer/portainer/api/stacks/deployments"
"github.com/stretchr/testify/require"
)
type mockDeploymentConfiger struct {
deployments.StackDeploymentConfiger
}
func (m mockDeploymentConfiger) GetResponse() string {
return "mock response"
}
func TestGetResponse(t *testing.T) {
c := &K8sStackFileContentBuilder{
FileContentMethodStackBuilder: FileContentMethodStackBuilder{
StackBuilder: StackBuilder{
deploymentConfiger: mockDeploymentConfiger{},
},
},
}
require.Equal(t, "mock response", c.GetResponse())
}

View file

@ -40,12 +40,6 @@ func CreateKubernetesStackGitBuilder(dataStore dataservices.DataStore,
}
}
func (b *KubernetesStackGitBuilder) SetGeneralInfo(payload *StackPayload, endpoint *portainer.Endpoint) GitMethodStackBuildProcess {
b.GitMethodStackBuilder.SetGeneralInfo(payload, endpoint)
return b
}
func (b *KubernetesStackGitBuilder) SetUniqueInfo(payload *StackPayload) GitMethodStackBuildProcess {
if b.hasError() {
return b
@ -60,12 +54,6 @@ func (b *KubernetesStackGitBuilder) SetUniqueInfo(payload *StackPayload) GitMeth
return b
}
func (b *KubernetesStackGitBuilder) SetGitRepository(payload *StackPayload) GitMethodStackBuildProcess {
b.GitMethodStackBuilder.SetGitRepository(payload)
return b
}
func (b *KubernetesStackGitBuilder) Deploy(payload *StackPayload, endpoint *portainer.Endpoint) GitMethodStackBuildProcess {
if b.hasError() {
return b
@ -91,13 +79,3 @@ func (b *KubernetesStackGitBuilder) Deploy(payload *StackPayload, endpoint *port
return b.GitMethodStackBuilder.Deploy(payload, endpoint)
}
func (b *KubernetesStackGitBuilder) SetAutoUpdate(payload *StackPayload) GitMethodStackBuildProcess {
b.GitMethodStackBuilder.SetAutoUpdate(payload)
return b
}
func (b *KubernetesStackGitBuilder) GetResponse() string {
return b.GitMethodStackBuilder.deploymentConfiger.GetResponse()
}

View file

@ -38,12 +38,6 @@ func CreateKubernetesStackUrlBuilder(dataStore dataservices.DataStore,
}
}
func (b *KubernetesStackUrlBuilder) SetGeneralInfo(payload *StackPayload, endpoint *portainer.Endpoint) UrlMethodStackBuildProcess {
b.UrlMethodStackBuilder.SetGeneralInfo(payload, endpoint)
return b
}
func (b *KubernetesStackUrlBuilder) SetUniqueInfo(payload *StackPayload) UrlMethodStackBuildProcess {
if b.hasError() {
return b
@ -106,9 +100,9 @@ func (b *KubernetesStackUrlBuilder) Deploy(payload *StackPayload, endpoint *port
b.deploymentConfiger = k8sDeploymentConfig
return b.UrlMethodStackBuilder.Deploy(payload, endpoint)
return b.Deploy(payload, endpoint)
}
func (b *KubernetesStackUrlBuilder) GetResponse() string {
return b.UrlMethodStackBuilder.deploymentConfiger.GetResponse()
return b.deploymentConfiger.GetResponse()
}

View file

@ -0,0 +1,19 @@
package stackbuilders
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestK8SUrlBuilderGetResponse(t *testing.T) {
c := &KubernetesStackUrlBuilder{
UrlMethodStackBuilder: UrlMethodStackBuilder{
StackBuilder: StackBuilder{
deploymentConfiger: mockDeploymentConfiger{},
},
},
}
require.Equal(t, "mock response", c.GetResponse())
}

View file

@ -61,8 +61,8 @@ func (b *GitMethodStackBuilder) SetGitRepository(payload *StackPayload) GitMetho
var repoConfig gittypes.RepoConfig
if payload.Authentication {
repoConfig.Authentication = &gittypes.GitAuthentication{
Username: payload.RepositoryConfigPayload.Username,
Password: payload.RepositoryConfigPayload.Password,
Username: payload.Username,
Password: payload.Password,
}
}

View file

@ -36,7 +36,6 @@ func (b *UrlMethodStackBuilder) SetGeneralInfo(payload *StackPayload, endpoint *
}
func (b *UrlMethodStackBuilder) SetUniqueInfo(payload *StackPayload) UrlMethodStackBuildProcess {
return b
}

View file

@ -28,12 +28,6 @@ func CreateSwarmStackFileUploadBuilder(securityContext *security.RestrictedReque
}
}
func (b *SwarmStackFileUploadBuilder) SetGeneralInfo(payload *StackPayload, endpoint *portainer.Endpoint) FileUploadMethodStackBuildProcess {
b.FileUploadMethodStackBuilder.SetGeneralInfo(payload, endpoint)
return b
}
func (b *SwarmStackFileUploadBuilder) SetUniqueInfo(payload *StackPayload) FileUploadMethodStackBuildProcess {
if b.hasError() {
return b
@ -48,16 +42,6 @@ func (b *SwarmStackFileUploadBuilder) SetUniqueInfo(payload *StackPayload) FileU
return b
}
func (b *SwarmStackFileUploadBuilder) SetUploadedFile(payload *StackPayload) FileUploadMethodStackBuildProcess {
if b.hasError() {
return b
}
b.FileUploadMethodStackBuilder.SetUploadedFile(payload)
return b
}
func (b *SwarmStackFileUploadBuilder) Deploy(payload *StackPayload, endpoint *portainer.Endpoint) FileUploadMethodStackBuildProcess {
if b.hasError() {
return b

View file

@ -32,11 +32,6 @@ func CreateSwarmStackGitBuilder(securityContext *security.RestrictedRequestConte
}
}
func (b *SwarmStackGitBuilder) SetGeneralInfo(payload *StackPayload, endpoint *portainer.Endpoint) GitMethodStackBuildProcess {
b.GitMethodStackBuilder.SetGeneralInfo(payload, endpoint)
return b
}
func (b *SwarmStackGitBuilder) SetUniqueInfo(payload *StackPayload) GitMethodStackBuildProcess {
if b.hasError() {
return b
@ -50,11 +45,6 @@ func (b *SwarmStackGitBuilder) SetUniqueInfo(payload *StackPayload) GitMethodSta
return b
}
func (b *SwarmStackGitBuilder) SetGitRepository(payload *StackPayload) GitMethodStackBuildProcess {
b.GitMethodStackBuilder.SetGitRepository(payload)
return b
}
// Deploy creates deployment configuration for swarm stack
func (b *SwarmStackGitBuilder) Deploy(payload *StackPayload, endpoint *portainer.Endpoint) GitMethodStackBuildProcess {
if b.hasError() {
@ -72,8 +62,3 @@ func (b *SwarmStackGitBuilder) Deploy(payload *StackPayload, endpoint *portainer
return b.GitMethodStackBuilder.Deploy(payload, endpoint)
}
func (b *SwarmStackGitBuilder) SetAutoUpdate(payload *StackPayload) GitMethodStackBuildProcess {
b.GitMethodStackBuilder.SetAutoUpdate(payload)
return b
}

View file

@ -57,9 +57,10 @@ func IsValidEdgeStackName(name string) bool {
}
for _, r := range name {
if !(unicode.IsLower(r) || unicode.IsDigit(r) || r == '-' || r == '_') {
if !unicode.IsLower(r) && !unicode.IsDigit(r) && r != '-' && r != '_' {
return false
}
}
return true
}

View file

@ -4,6 +4,7 @@ import (
"testing"
"github.com/portainer/portainer/pkg/libhelm/options"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v3/pkg/action"
"k8s.io/client-go/tools/clientcmd"
@ -16,7 +17,7 @@ func Test_InitActionConfig(t *testing.T) {
t.Run("with nil k8sAccess should use default kubeconfig", func(t *testing.T) {
actionConfig := new(action.Configuration)
err := hspm.(*HelmSDKPackageManager).initActionConfig(actionConfig, "default", nil)
err := hspm.initActionConfig(actionConfig, "default", nil)
// The function should not fail by design, even when not running in a k8s environment
is.NoError(err, "should not return error when not in k8s environment")
@ -30,7 +31,7 @@ func Test_InitActionConfig(t *testing.T) {
}
// The function should not fail by design
err := hspm.(*HelmSDKPackageManager).initActionConfig(actionConfig, "default", k8sAccess)
err := hspm.initActionConfig(actionConfig, "default", k8sAccess)
is.NoError(err, "should not return error when using in-memory config")
})
@ -43,7 +44,7 @@ func Test_InitActionConfig(t *testing.T) {
}
// The function should not fail by design
err := hspm.(*HelmSDKPackageManager).initActionConfig(actionConfig, "default", k8sAccess)
err := hspm.initActionConfig(actionConfig, "default", k8sAccess)
is.NoError(err, "should not return error when using in-memory config with CA")
})
}
@ -117,7 +118,7 @@ resources:
cpu: 100m
memory: 128Mi
`)
values, err := hspm.(*HelmSDKPackageManager).parseValues(yamlData)
values, err := hspm.parseValues(yamlData)
is.NoError(err, "should parse valid YAML without error")
is.NotNil(values, "should return non-nil values")
@ -142,13 +143,13 @@ service:
port: 80
invalid yaml
`)
_, err := hspm.(*HelmSDKPackageManager).parseValues(yamlData)
_, err := hspm.parseValues(yamlData)
is.Error(err, "should return error for invalid YAML")
})
t.Run("should handle empty YAML", func(t *testing.T) {
yamlData := []byte(``)
values, err := hspm.(*HelmSDKPackageManager).parseValues(yamlData)
values, err := hspm.parseValues(yamlData)
is.NoError(err, "should not return error for empty YAML")
is.NotNil(values, "should return non-nil values for empty YAML")
is.Len(values, 0, "should return empty map for empty YAML")

View file

@ -6,12 +6,13 @@ import (
"github.com/portainer/portainer/pkg/libhelm/options"
"github.com/portainer/portainer/pkg/libhelm/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Install(t *testing.T) {
test.EnsureIntegrationTest(t)
is := assert.New(t)
is := require.New(t)
// Create a new SDK package manager
hspm := NewHelmSDKPackageManager()
@ -29,14 +30,12 @@ func Test_Install(t *testing.T) {
})
release, err := hspm.Upgrade(installOpts)
if release != nil {
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
}
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
is.Equal("test-nginx", release.Name, "release name should match")
is.Equal(1, release.Version, "release version should be 1")
is.NotEmpty(release.Manifest, "release manifest should not be empty")
@ -60,14 +59,12 @@ func Test_Install(t *testing.T) {
})
release, err := hspm.Upgrade(installOpts)
if release != nil {
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
}
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
is.Equal("test-nginx-2", release.Name, "release name should match")
is.Equal(1, release.Version, "release version should be 1")
is.NotEmpty(release.Manifest, "release manifest should not be empty")
@ -86,14 +83,12 @@ func Test_Install(t *testing.T) {
})
release, err := hspm.Upgrade(installOpts)
if release != nil {
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
}
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
is.Equal("portainer-test", release.Name, "release name should match")
is.Equal(1, release.Version, "release version should be 1")
is.NotEmpty(release.Manifest, "release manifest should not be empty")
@ -118,14 +113,12 @@ func Test_Install(t *testing.T) {
})
release, err := hspm.Upgrade(installOpts)
if release != nil {
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
}
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
is.Equal("test-nginx-3", release.Name, "release name should match")
})
@ -143,14 +136,12 @@ func Test_Install(t *testing.T) {
})
release, err := hspm.Upgrade(installOpts)
if release != nil {
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
}
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
is.Equal("test-nginx-4", release.Name, "release name should match")
is.Equal("default", release.Namespace, "release namespace should match")
})

View file

@ -3,7 +3,6 @@ package sdk
import (
"time"
"github.com/portainer/portainer/pkg/libhelm/types"
"helm.sh/helm/v3/pkg/cli"
)
@ -14,7 +13,7 @@ type HelmSDKPackageManager struct {
}
// NewHelmSDKPackageManager initializes a new HelmPackageManager service using the Helm SDK
func NewHelmSDKPackageManager() types.HelmPackageManager {
func NewHelmSDKPackageManager() *HelmSDKPackageManager {
settings := cli.New()
return &HelmSDKPackageManager{
settings: settings,

View file

@ -5,6 +5,7 @@ import (
"time"
"github.com/portainer/portainer/pkg/libhelm/types"
"github.com/stretchr/testify/assert"
)
@ -15,14 +16,9 @@ func Test_NewHelmSDKPackageManager(t *testing.T) {
manager := NewHelmSDKPackageManager()
is.NotNil(manager, "should return non-nil HelmPackageManager")
// Test that the returned manager is of the correct type
_, ok := manager.(*HelmSDKPackageManager)
is.True(ok, "should return a *HelmSDKPackageManager")
// Test that the manager has the expected fields
sdkManager := manager.(*HelmSDKPackageManager)
is.NotNil(sdkManager.settings, "should have non-nil settings")
is.Equal(300*time.Second, sdkManager.timeout, "should have 5 minute timeout")
is.NotNil(manager.settings, "should have non-nil settings")
is.Equal(300*time.Second, manager.timeout, "should have 5 minute timeout")
// Test that the manager implements the HelmPackageManager interface
var _ types.HelmPackageManager = manager

View file

@ -52,7 +52,7 @@ func (hspm *HelmSDKPackageManager) Show(showOpts options.ShowOptions) ([]byte, e
if err != nil {
return nil, fmt.Errorf("failed to parse chart reference: %w", err)
}
chartPath, err := showClient.ChartPathOptions.LocateChart(chartRef, hspm.settings)
chartPath, err := showClient.LocateChart(chartRef, hspm.settings)
if err != nil {
log.Error().
Str("context", "HelmClient").

View file

@ -6,12 +6,12 @@ import (
"github.com/portainer/portainer/pkg/libhelm/options"
"github.com/portainer/portainer/pkg/libhelm/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUpgrade(t *testing.T) {
test.EnsureIntegrationTest(t)
is := assert.New(t)
is := require.New(t)
// Create a new SDK package manager
hspm := NewHelmSDKPackageManager()
@ -31,14 +31,12 @@ func TestUpgrade(t *testing.T) {
})
release, err := hspm.Upgrade(upgradeOpts)
if release != nil {
defer hspm.Uninstall(options.UninstallOptions{
Name: upgradeOpts.Name,
})
}
is.NoError(err, "should successfully install release via upgrade")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: upgradeOpts.Name,
})
is.Equal(upgradeOpts.Name, release.Name, "release name should match")
is.Equal(1, release.Version, "release version should be 1 for new install")
is.NotEmpty(release.Manifest, "release manifest should not be empty")
@ -64,11 +62,11 @@ func TestUpgrade(t *testing.T) {
})
release, err := hspm.Upgrade(installOpts)
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
// Upgrade the release with the same options
upgradedRelease, err := hspm.Upgrade(installOpts)
@ -95,11 +93,11 @@ func TestUpgrade(t *testing.T) {
})
release, err := hspm.Upgrade(installOpts) // Cleanup
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
// Create values file
values, err := test.CreateValuesFile("service:\n port: 8083")
@ -139,11 +137,11 @@ func TestUpgrade(t *testing.T) {
})
release, err := hspm.Upgrade(installOpts)
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
defer hspm.Uninstall(options.UninstallOptions{
Name: installOpts.Name,
})
is.NoError(err, "should successfully install release")
is.NotNil(release, "should return non-nil release")
// Create invalid values file
values, err := test.CreateValuesFile("this is not valid yaml")

View file

@ -59,18 +59,19 @@ func newMockRelease(re *release.ReleaseElement) *release.Release {
// Install a helm chart (not thread safe)
func (hpm helmMockPackageManager) Install(installOpts options.InstallOptions) (*release.Release, error) {
releaseElement := newMockReleaseElement(installOpts)
// Enforce only one chart with the same name per namespace
for i, rel := range mockCharts {
if rel.Name == installOpts.Name && rel.Namespace == installOpts.Namespace {
mockCharts[i] = *releaseElement
return newMockRelease(releaseElement), nil
}
}
mockCharts = append(mockCharts, *releaseElement)
return newMockRelease(releaseElement), nil
}
@ -81,7 +82,7 @@ func (hpm helmMockPackageManager) Upgrade(upgradeOpts options.InstallOptions) (*
// Rollback a helm chart (not thread safe)
func (hpm helmMockPackageManager) Rollback(rollbackOpts options.RollbackOptions) (*release.Release, error) {
return hpm.Rollback(rollbackOpts)
return nil, nil
}
// Show values/readme/chart etc
@ -94,6 +95,7 @@ func (hpm helmMockPackageManager) Show(showOpts options.ShowOptions) ([]byte, er
case options.ShowValues:
return []byte(MockDataValues), nil
}
return nil, nil
}
@ -104,6 +106,7 @@ func (hpm helmMockPackageManager) Uninstall(uninstallOpts options.UninstallOptio
mockCharts = slices.Delete(mockCharts, i, i+1)
}
}
return nil
}
@ -117,6 +120,7 @@ func (hpm helmMockPackageManager) Get(getOpts options.GetOptions) (*release.Rele
index := slices.IndexFunc(mockCharts, func(re release.ReleaseElement) bool {
return re.Name == getOpts.Name && re.Namespace == getOpts.Namespace
})
return newMockRelease(&mockCharts[index]), nil
}
@ -159,8 +163,7 @@ func (hbpm helmMockPackageManager) SearchRepo(searchRepoOpts options.SearchRepoO
reader := strings.NewReader(mockPortainerIndex)
var file release.File
err := yaml.NewDecoder(reader).Decode(&file)
if err != nil {
if err := yaml.NewDecoder(reader).Decode(&file); err != nil {
return nil, errors.Wrap(err, "failed to decode index file")
}

View file

@ -41,7 +41,7 @@ func Now() Time {
}
func (t Time) MarshalJSON() ([]byte, error) {
if t.Time.IsZero() {
if t.IsZero() {
return []byte(emptyString), nil
}

View file

@ -62,8 +62,6 @@ func TestValidateHelmRepositoryURL(t *testing.T) {
return
}
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()

View file

@ -42,11 +42,11 @@ func init() {
}
func withCli(
ctx context.Context,
ctx context.Context, //nolint:staticcheck
options libstack.Options,
cliFn func(context.Context, *command.DockerCli) error,
) error {
ctx = context.Background()
ctx = context.Background() //nolint:staticcheck
cli, err := command.NewDockerCli(command.WithCombinedStreams(log.Logger))
if err != nil {

View file

@ -6,6 +6,7 @@ import (
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
@ -162,7 +163,7 @@ func TestKubernetesSnapshotNodesWithAPIError(t *testing.T) {
fakeClient := kfake.NewClientset()
// Add a reactor to simulate API error
fakeClient.Fake.PrependReactor("list", "nodes", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
fakeClient.PrependReactor("list", "nodes", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, errors.New("simulated API error")
})

View file

@ -16,7 +16,7 @@ func init() {
func IntegrationTest(t *testing.T) {
_, enabled := os.LookupEnv("INTEGRATION_TEST")
if !(integration || enabled) {
if !integration && !enabled {
t.Skip("Skipping integration test")
}
}