mirror of https://github.com/portainer/portainer
chore(code): clean up the code EE-7251 (#11948)
parent
be9d3285e1
commit
bfa27d9103
|
@ -8,6 +8,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
@ -73,7 +74,6 @@ func (connection *DbConnection) IsEncryptedStore() bool {
|
||||||
// NeedsEncryptionMigration returns true if database encryption is enabled and
|
// NeedsEncryptionMigration returns true if database encryption is enabled and
|
||||||
// we have an un-encrypted DB that requires migration to an encrypted DB
|
// we have an un-encrypted DB that requires migration to an encrypted DB
|
||||||
func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
|
func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
|
||||||
|
|
||||||
// Cases: Note, we need to check both portainer.db and portainer.edb
|
// Cases: Note, we need to check both portainer.db and portainer.edb
|
||||||
// to determine if it's a new store. We only need to differentiate between cases 2,3 and 5
|
// to determine if it's a new store. We only need to differentiate between cases 2,3 and 5
|
||||||
|
|
||||||
|
@ -121,11 +121,11 @@ func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
|
||||||
|
|
||||||
// Open opens and initializes the BoltDB database.
|
// Open opens and initializes the BoltDB database.
|
||||||
func (connection *DbConnection) Open() error {
|
func (connection *DbConnection) Open() error {
|
||||||
|
|
||||||
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
|
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
|
||||||
|
|
||||||
// Now we open the db
|
// Now we open the db
|
||||||
databasePath := connection.GetDatabaseFilePath()
|
databasePath := connection.GetDatabaseFilePath()
|
||||||
|
|
||||||
db, err := bolt.Open(databasePath, 0600, &bolt.Options{
|
db, err := bolt.Open(databasePath, 0600, &bolt.Options{
|
||||||
Timeout: 1 * time.Second,
|
Timeout: 1 * time.Second,
|
||||||
InitialMmapSize: connection.InitialMmapSize,
|
InitialMmapSize: connection.InitialMmapSize,
|
||||||
|
@ -178,6 +178,7 @@ func (connection *DbConnection) ViewTx(fn func(portainer.Transaction) error) err
|
||||||
func (connection *DbConnection) BackupTo(w io.Writer) error {
|
func (connection *DbConnection) BackupTo(w io.Writer) error {
|
||||||
return connection.View(func(tx *bolt.Tx) error {
|
return connection.View(func(tx *bolt.Tx) error {
|
||||||
_, err := tx.WriteTo(w)
|
_, err := tx.WriteTo(w)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -192,6 +193,7 @@ func (connection *DbConnection) ExportRaw(filename string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile(filename, b, 0600)
|
return os.WriteFile(filename, b, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +214,7 @@ func keyToString(b []byte) string {
|
||||||
|
|
||||||
v := binary.BigEndian.Uint64(b)
|
v := binary.BigEndian.Uint64(b)
|
||||||
if v <= math.MaxInt32 {
|
if v <= math.MaxInt32 {
|
||||||
return fmt.Sprintf("%d", v)
|
return strconv.FormatUint(v, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(b)
|
return string(b)
|
||||||
|
@ -321,22 +323,22 @@ func (connection *DbConnection) CreateObjectWithStringId(bucketName string, id [
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (connection *DbConnection) GetAll(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
func (connection *DbConnection) GetAll(bucketName string, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
|
||||||
return connection.ViewTx(func(tx portainer.Transaction) error {
|
return connection.ViewTx(func(tx portainer.Transaction) error {
|
||||||
return tx.GetAll(bucketName, obj, append)
|
return tx.GetAll(bucketName, obj, appendFn)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: decide which Unmarshal to use, and use one...
|
// TODO: decide which Unmarshal to use, and use one...
|
||||||
func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
func (connection *DbConnection) GetAllWithJsoniter(bucketName string, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
|
||||||
return connection.ViewTx(func(tx portainer.Transaction) error {
|
return connection.ViewTx(func(tx portainer.Transaction) error {
|
||||||
return tx.GetAllWithJsoniter(bucketName, obj, append)
|
return tx.GetAllWithJsoniter(bucketName, obj, appendFn)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (connection *DbConnection) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, append func(o interface{}) (interface{}, error)) error {
|
func (connection *DbConnection) GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj interface{}, appendFn func(o interface{}) (interface{}, error)) error {
|
||||||
return connection.ViewTx(func(tx portainer.Transaction) error {
|
return connection.ViewTx(func(tx portainer.Transaction) error {
|
||||||
return tx.GetAllWithKeyPrefix(bucketName, keyPrefix, obj, append)
|
return tx.GetAllWithKeyPrefix(bucketName, keyPrefix, obj, appendFn)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,14 +347,13 @@ func (connection *DbConnection) BackupMetadata() (map[string]interface{}, error)
|
||||||
buckets := map[string]interface{}{}
|
buckets := map[string]interface{}{}
|
||||||
|
|
||||||
err := connection.View(func(tx *bolt.Tx) error {
|
err := connection.View(func(tx *bolt.Tx) error {
|
||||||
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
return tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
|
||||||
bucketName := string(name)
|
bucketName := string(name)
|
||||||
seqId := bucket.Sequence()
|
seqId := bucket.Sequence()
|
||||||
buckets[bucketName] = int(seqId)
|
buckets[bucketName] = int(seqId)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return buckets, err
|
return buckets, err
|
||||||
|
@ -366,6 +367,7 @@ func (connection *DbConnection) RestoreMetadata(s map[string]interface{}) error
|
||||||
id, ok := v.(float64) // JSON ints are unmarshalled to interface as float64. See: https://pkg.go.dev/encoding/json#Decoder.Decode
|
id, ok := v.(float64) // JSON ints are unmarshalled to interface as float64. See: https://pkg.go.dev/encoding/json#Decoder.Decode
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Str("bucket", bucketName).Msg("failed to restore metadata to bucket, skipped")
|
log.Error().Str("bucket", bucketName).Msg("failed to restore metadata to bucket, skipped")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,13 @@ import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/segmentio/encoding/json"
|
"github.com/segmentio/encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errEncryptedStringTooShort = fmt.Errorf("encrypted string too short")
|
var errEncryptedStringTooShort = errors.New("encrypted string too short")
|
||||||
|
|
||||||
// MarshalObject encodes an object to binary format
|
// MarshalObject encodes an object to binary format
|
||||||
func (connection *DbConnection) MarshalObject(object interface{}) ([]byte, error) {
|
func (connection *DbConnection) MarshalObject(object interface{}) ([]byte, error) {
|
||||||
|
@ -70,22 +69,20 @@ func encrypt(plaintext []byte, passphrase []byte) (encrypted []byte, err error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return encrypted, err
|
return encrypted, err
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := make([]byte, gcm.NonceSize())
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
return encrypted, err
|
return encrypted, err
|
||||||
}
|
}
|
||||||
ciphertextByte := gcm.Seal(
|
|
||||||
nonce,
|
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
||||||
nonce,
|
|
||||||
plaintext,
|
|
||||||
nil)
|
|
||||||
return ciphertextByte, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err error) {
|
func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err error) {
|
||||||
if string(encrypted) == "false" {
|
if string(encrypted) == "false" {
|
||||||
return []byte("false"), nil
|
return []byte("false"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := aes.NewCipher(passphrase)
|
block, err := aes.NewCipher(passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return encrypted, errors.Wrap(err, "Error creating cypher block")
|
return encrypted, errors.Wrap(err, "Error creating cypher block")
|
||||||
|
@ -102,11 +99,8 @@ func decrypt(encrypted []byte, passphrase []byte) (plaintextByte []byte, err err
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce, ciphertextByteClean := encrypted[:nonceSize], encrypted[nonceSize:]
|
nonce, ciphertextByteClean := encrypted[:nonceSize], encrypted[nonceSize:]
|
||||||
plaintextByte, err = gcm.Open(
|
|
||||||
nil,
|
plaintextByte, err = gcm.Open(nil, nonce, ciphertextByteClean, nil)
|
||||||
nonce,
|
|
||||||
ciphertextByteClean,
|
|
||||||
nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return encrypted, errors.Wrap(err, "Error decrypting text")
|
return encrypted, errors.Wrap(err, "Error decrypting text")
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,9 +74,10 @@ func (tx *DbTransaction) DeleteAllObjects(bucketName string, obj interface{}, ma
|
||||||
|
|
||||||
func (tx *DbTransaction) GetNextIdentifier(bucketName string) int {
|
func (tx *DbTransaction) GetNextIdentifier(bucketName string) int {
|
||||||
bucket := tx.tx.Bucket([]byte(bucketName))
|
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||||
|
|
||||||
id, err := bucket.NextSequence()
|
id, err := bucket.NextSequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("bucket", bucketName).Msg("failed to get the next identifer")
|
log.Error().Err(err).Str("bucket", bucketName).Msg("failed to get the next identifier")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package consts
|
||||||
const (
|
const (
|
||||||
ComposeStackNameLabel = "com.docker.compose.project"
|
ComposeStackNameLabel = "com.docker.compose.project"
|
||||||
SwarmStackNameLabel = "com.docker.stack.namespace"
|
SwarmStackNameLabel = "com.docker.stack.namespace"
|
||||||
SwarmServiceIdLabel = "com.docker.swarm.service.id"
|
SwarmServiceIDLabel = "com.docker.swarm.service.id"
|
||||||
SwarmNodeIdLabel = "com.docker.swarm.node.id"
|
SwarmNodeIDLabel = "com.docker.swarm.node.id"
|
||||||
HideStackLabel = "io.portainer.hideStack"
|
HideStackLabel = "io.portainer.hideStack"
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,12 +7,12 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockercontainer "github.com/docker/docker/api/types/container"
|
dockercontainer "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||||
"github.com/portainer/portainer/api/docker/images"
|
"github.com/portainer/portainer/api/docker/images"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,9 +37,7 @@ func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.End
|
||||||
return nil, errors.Wrap(err, "create client error")
|
return nil, errors.Wrap(err, "create client error")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(cli *client.Client) {
|
defer cli.Close()
|
||||||
cli.Close()
|
|
||||||
}(cli)
|
|
||||||
|
|
||||||
log.Debug().Str("container_id", containerId).Msg("starting to fetch container information")
|
log.Debug().Str("container_id", containerId).Msg("starting to fetch container information")
|
||||||
|
|
||||||
|
|
|
@ -48,11 +48,11 @@ func (c *DigestClient) ContainersImageStatus(ctx context.Context, containers []t
|
||||||
statuses := make([]Status, len(containers))
|
statuses := make([]Status, len(containers))
|
||||||
for i, ct := range containers {
|
for i, ct := range containers {
|
||||||
var nodeName string
|
var nodeName string
|
||||||
if swarmNodeId := ct.Labels[consts.SwarmNodeIdLabel]; swarmNodeId != "" {
|
if swarmNodeId := ct.Labels[consts.SwarmNodeIDLabel]; swarmNodeId != "" {
|
||||||
if swarmNodeName, ok := swarmID2NameCache.Get(swarmNodeId); ok {
|
if swarmNodeName, ok := swarmID2NameCache.Get(swarmNodeId); ok {
|
||||||
nodeName, _ = swarmNodeName.(string)
|
nodeName, _ = swarmNodeName.(string)
|
||||||
} else {
|
} else {
|
||||||
node, _, err := cli.NodeInspectWithRaw(ctx, ct.Labels[consts.SwarmNodeIdLabel])
|
node, _, err := cli.NodeInspectWithRaw(ctx, ct.Labels[consts.SwarmNodeIDLabel])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Error
|
return Error
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ func (c *DigestClient) ServiceImageStatus(ctx context.Context, serviceID string,
|
||||||
|
|
||||||
containers, err := cli.ContainerList(ctx, container.ListOptions{
|
containers, err := cli.ContainerList(ctx, container.ListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
Filters: filters.NewArgs(filters.Arg("label", consts.SwarmServiceIdLabel+"="+serviceID)),
|
Filters: filters.NewArgs(filters.Arg("label", consts.SwarmServiceIDLabel+"="+serviceID)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("serviceID", serviceID).Msg("cannot list container for the service")
|
log.Warn().Err(err).Str("serviceID", serviceID).Msg("cannot list container for the service")
|
||||||
|
|
|
@ -90,7 +90,7 @@ func singleAPIRequest(h *Handler, jwt string, is *assert.Assertions, expect stri
|
||||||
FileContent string
|
FileContent string
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPut, "/custom_templates/1/git_fetch", bytes.NewBuffer([]byte("{}")))
|
req := httptest.NewRequest(http.MethodPut, "/custom_templates/1/git_fetch", bytes.NewBufferString("{}"))
|
||||||
testhelpers.AddTestSecurityCookie(req, jwt)
|
testhelpers.AddTestSecurityCookie(req, jwt)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
|
@ -196,7 +196,7 @@ func Test_customTemplateGitFetch(t *testing.T) {
|
||||||
}
|
}
|
||||||
h := NewHandler(requestBouncer, store, fileService, invalidGitService)
|
h := NewHandler(requestBouncer, store, fileService, invalidGitService)
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPut, "/custom_templates/1/git_fetch", bytes.NewBuffer([]byte("{}")))
|
req := httptest.NewRequest(http.MethodPut, "/custom_templates/1/git_fetch", bytes.NewBufferString("{}"))
|
||||||
testhelpers.AddTestSecurityCookie(req, jwt1)
|
testhelpers.AddTestSecurityCookie(req, jwt1)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,8 +31,7 @@ func (handler *Handler) recreate(w http.ResponseWriter, r *http.Request) *httper
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload RecreatePayload
|
var payload RecreatePayload
|
||||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.BadRequest("Invalid request payload", err)
|
return httperror.BadRequest("Invalid request payload", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,8 +40,7 @@ func (handler *Handler) recreate(w http.ResponseWriter, r *http.Request) *httper
|
||||||
return httperror.NotFound("Unable to find an environment on request context", err)
|
return httperror.NotFound("Unable to find an environment on request context", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.bouncer.AuthorizedEndpointOperation(r, endpoint)
|
if err := handler.bouncer.AuthorizedEndpointOperation(r, endpoint); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.Forbidden("Permission denied to force update service", err)
|
return httperror.Forbidden("Permission denied to force update service", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +57,9 @@ func (handler *Handler) recreate(w http.ResponseWriter, r *http.Request) *httper
|
||||||
go func() {
|
go func() {
|
||||||
images.EvictImageStatus(containerID)
|
images.EvictImageStatus(containerID)
|
||||||
images.EvictImageStatus(newContainer.Config.Labels[consts.ComposeStackNameLabel])
|
images.EvictImageStatus(newContainer.Config.Labels[consts.ComposeStackNameLabel])
|
||||||
images.EvictImageStatus(newContainer.Config.Labels[consts.SwarmServiceIdLabel])
|
images.EvictImageStatus(newContainer.Config.Labels[consts.SwarmServiceIDLabel])
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return response.JSON(w, newContainer)
|
return response.JSON(w, newContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ func (handler *Handler) createResourceControl(oldContainerId string, newContaine
|
||||||
resourceControls, err := handler.dataStore.ResourceControl().ReadAll()
|
resourceControls, err := handler.dataStore.ResourceControl().ReadAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Exporting Resource Controls")
|
log.Error().Err(err).Msg("Exporting Resource Controls")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,11 +75,10 @@ func (handler *Handler) createResourceControl(oldContainerId string, newContaine
|
||||||
if resourceControl == nil {
|
if resourceControl == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl.ResourceID = newContainerId
|
resourceControl.ResourceID = newContainerId
|
||||||
err = handler.dataStore.ResourceControl().Create(resourceControl)
|
if err := handler.dataStore.ResourceControl().Create(resourceControl); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Str("containerId", newContainerId).Msg("Failed to create new resource control for container")
|
log.Error().Err(err).Str("containerId", newContainerId).Msg("Failed to create new resource control for container")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,12 +86,12 @@ func (handler *Handler) updateWebhook(oldContainerId string, newContainerId stri
|
||||||
webhook, err := handler.dataStore.Webhook().WebhookByResourceID(oldContainerId)
|
webhook, err := handler.dataStore.Webhook().WebhookByResourceID(oldContainerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("containerId", oldContainerId).Msg("cannot find webhook by containerId")
|
log.Error().Err(err).Str("containerId", oldContainerId).Msg("cannot find webhook by containerId")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
webhook.ResourceID = newContainerId
|
webhook.ResourceID = newContainerId
|
||||||
err = handler.dataStore.Webhook().Update(webhook.ID, webhook)
|
if err := handler.dataStore.Webhook().Update(webhook.ID, webhook); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Int("webhookId", int(webhook.ID)).Msg("cannot update webhook")
|
log.Error().Err(err).Int("webhookId", int(webhook.ID)).Msg("cannot update webhook")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
edgestackutils "github.com/portainer/portainer/api/internal/edge/edgestacks"
|
edgestackutils "github.com/portainer/portainer/api/internal/edge/edgestacks"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (handler *Handler) updateStackVersion(stack *portainer.EdgeStack, deploymentType portainer.EdgeStackDeploymentType, config []byte, oldGitHash string, relatedEnvironmentsIDs []portainer.EndpointID) error {
|
func (handler *Handler) updateStackVersion(stack *portainer.EdgeStack, deploymentType portainer.EdgeStackDeploymentType, config []byte, oldGitHash string, relatedEnvironmentsIDs []portainer.EndpointID) error {
|
||||||
|
|
||||||
stack.Version = stack.Version + 1
|
stack.Version = stack.Version + 1
|
||||||
stack.Status = edgestackutils.NewStatus(stack.Status, relatedEnvironmentsIDs)
|
stack.Status = edgestackutils.NewStatus(stack.Status, relatedEnvironmentsIDs)
|
||||||
|
|
||||||
|
@ -19,11 +19,9 @@ func (handler *Handler) updateStackVersion(stack *portainer.EdgeStack, deploymen
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) storeStackFile(stack *portainer.EdgeStack, deploymentType portainer.EdgeStackDeploymentType, config []byte) error {
|
func (handler *Handler) storeStackFile(stack *portainer.EdgeStack, deploymentType portainer.EdgeStackDeploymentType, config []byte) error {
|
||||||
|
|
||||||
if deploymentType != stack.DeploymentType {
|
if deploymentType != stack.DeploymentType {
|
||||||
// deployment type was changed - need to delete all old files
|
// deployment type was changed - need to delete all old files
|
||||||
err := handler.FileService.RemoveDirectory(stack.ProjectPath)
|
if err := handler.FileService.RemoveDirectory(stack.ProjectPath); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Msg("Unable to clear old files")
|
log.Warn().Err(err).Msg("Unable to clear old files")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +48,7 @@ func (handler *Handler) storeStackFile(stack *portainer.EdgeStack, deploymentTyp
|
||||||
entryPoint = stack.ManifestPath
|
entryPoint = stack.ManifestPath
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := handler.FileService.StoreEdgeStackFileFromBytes(stackFolder, entryPoint, config)
|
if _, err := handler.FileService.StoreEdgeStackFileFromBytes(stackFolder, entryPoint, config); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to persist updated Compose file with version on disk: %w", err)
|
return fmt.Errorf("unable to persist updated Compose file with version on disk: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,13 @@ import (
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
|
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||||
"github.com/portainer/portainer/api/internal/tag"
|
"github.com/portainer/portainer/api/internal/tag"
|
||||||
"github.com/portainer/portainer/api/pendingactions/handlers"
|
"github.com/portainer/portainer/api/pendingactions/handlers"
|
||||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,17 +55,17 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload endpointGroupUpdatePayload
|
var payload endpointGroupUpdatePayload
|
||||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.BadRequest("Invalid request payload", err)
|
return httperror.BadRequest("Invalid request payload", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var endpointGroup *portainer.EndpointGroup
|
var endpointGroup *portainer.EndpointGroup
|
||||||
err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
|
||||||
|
if err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||||
endpointGroup, err = handler.updateEndpointGroup(tx, portainer.EndpointGroupID(endpointGroupID), payload)
|
endpointGroup, err = handler.updateEndpointGroup(tx, portainer.EndpointGroupID(endpointGroupID), payload)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
var httpErr *httperror.HandlerError
|
var httpErr *httperror.HandlerError
|
||||||
if errors.As(err, &httpErr) {
|
if errors.As(err, &httpErr) {
|
||||||
return httpErr
|
return httpErr
|
||||||
|
@ -151,14 +153,11 @@ func (handler *Handler) updateEndpointGroup(tx dataservices.DataStoreTx, endpoin
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
if endpoint.GroupID == endpointGroup.ID {
|
if endpoint.GroupID == endpointGroup.ID && endpointutils.IsKubernetesEndpoint(&endpoint) {
|
||||||
if endpoint.Type == portainer.KubernetesLocalEnvironment || endpoint.Type == portainer.AgentOnKubernetesEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
|
if err := handler.AuthorizationService.CleanNAPWithOverridePolicies(tx, &endpoint, endpointGroup); err != nil {
|
||||||
err = handler.AuthorizationService.CleanNAPWithOverridePolicies(tx, &endpoint, endpointGroup)
|
|
||||||
if err != nil {
|
|
||||||
// Update flag with endpoint and continue
|
// Update flag with endpoint and continue
|
||||||
go func(endpointID portainer.EndpointID, endpointGroupID portainer.EndpointGroupID) {
|
go func(endpointID portainer.EndpointID, endpointGroupID portainer.EndpointGroupID) {
|
||||||
err := handler.PendingActionsService.Create(handlers.NewCleanNAPWithOverridePolicies(endpointID, &endpointGroupID))
|
if err := handler.PendingActionsService.Create(handlers.NewCleanNAPWithOverridePolicies(endpointID, &endpointGroupID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msgf("Unable to create pending action to clean NAP with override policies for endpoint (%d) and endpoint group (%d).", endpointID, endpointGroupID)
|
log.Error().Err(err).Msgf("Unable to create pending action to clean NAP with override policies for endpoint (%d) and endpoint group (%d).", endpointID, endpointGroupID)
|
||||||
}
|
}
|
||||||
}(endpoint.ID, endpointGroup.ID)
|
}(endpoint.ID, endpointGroup.ID)
|
||||||
|
@ -166,10 +165,8 @@ func (handler *Handler) updateEndpointGroup(tx dataservices.DataStoreTx, endpoin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.EndpointGroup().Update(endpointGroup.ID, endpointGroup)
|
if err := tx.EndpointGroup().Update(endpointGroup.ID, endpointGroup); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, httperror.InternalServerError("Unable to persist environment group changes inside the database", err)
|
return nil, httperror.InternalServerError("Unable to persist environment group changes inside the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,13 +174,11 @@ func (handler *Handler) updateEndpointGroup(tx dataservices.DataStoreTx, endpoin
|
||||||
endpoints, err := tx.Endpoint().Endpoints()
|
endpoints, err := tx.Endpoint().Endpoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, httperror.InternalServerError("Unable to retrieve environments from the database", err)
|
return nil, httperror.InternalServerError("Unable to retrieve environments from the database", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
if endpoint.GroupID == endpointGroup.ID {
|
if endpoint.GroupID == endpointGroup.ID {
|
||||||
err = handler.updateEndpointRelations(tx, &endpoint, endpointGroup)
|
if err := handler.updateEndpointRelations(tx, &endpoint, endpointGroup); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, httperror.InternalServerError("Unable to persist environment relations changes inside the database", err)
|
return nil, httperror.InternalServerError("Unable to persist environment relations changes inside the database", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,10 +64,9 @@ func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *
|
||||||
return httperror.BadRequest("Invalid boolean query parameter", err)
|
return httperror.BadRequest("Invalid boolean query parameter", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
if err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||||
return handler.deleteEndpoint(tx, portainer.EndpointID(endpointID), deleteCluster)
|
return handler.deleteEndpoint(tx, portainer.EndpointID(endpointID), deleteCluster)
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
var handlerError *httperror.HandlerError
|
var handlerError *httperror.HandlerError
|
||||||
if errors.As(err, &handlerError) {
|
if errors.As(err, &handlerError) {
|
||||||
return handlerError
|
return handlerError
|
||||||
|
@ -87,7 +86,7 @@ func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *
|
||||||
// @security ApiKeyAuth || jwt
|
// @security ApiKeyAuth || jwt
|
||||||
// @accept json
|
// @accept json
|
||||||
// @produce json
|
// @produce json
|
||||||
// @param body body endpointDeleteBatchPayload true "List of environments to delete, with optional deleteCluster flag to clean-up assocaited resources (cloud environments only)"
|
// @param body body endpointDeleteBatchPayload true "List of environments to delete, with optional deleteCluster flag to clean-up associated resources (cloud environments only)"
|
||||||
// @success 204 "Environment(s) successfully deleted."
|
// @success 204 "Environment(s) successfully deleted."
|
||||||
// @failure 207 {object} endpointDeleteBatchPartialResponse "Partial success. Some environments were deleted successfully, while others failed."
|
// @failure 207 {object} endpointDeleteBatchPartialResponse "Partial success. Some environments were deleted successfully, while others failed."
|
||||||
// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria."
|
// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria."
|
||||||
|
@ -139,28 +138,24 @@ func (handler *Handler) deleteEndpoint(tx dataservices.DataStoreTx, endpointID p
|
||||||
|
|
||||||
if endpoint.TLSConfig.TLS {
|
if endpoint.TLSConfig.TLS {
|
||||||
folder := strconv.Itoa(int(endpointID))
|
folder := strconv.Itoa(int(endpointID))
|
||||||
err = handler.FileService.DeleteTLSFiles(folder)
|
if err := handler.FileService.DeleteTLSFiles(folder); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msgf("Unable to remove TLS files from disk when deleting endpoint %d", endpointID)
|
log.Error().Err(err).Msgf("Unable to remove TLS files from disk when deleting endpoint %d", endpointID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Snapshot().Delete(endpointID)
|
if err := tx.Snapshot().Delete(endpointID); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Msgf("Unable to remove the snapshot from the database")
|
log.Warn().Err(err).Msgf("Unable to remove the snapshot from the database")
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.ProxyManager.DeleteEndpointProxy(endpoint.ID)
|
handler.ProxyManager.DeleteEndpointProxy(endpoint.ID)
|
||||||
|
|
||||||
if len(endpoint.UserAccessPolicies) > 0 || len(endpoint.TeamAccessPolicies) > 0 {
|
if len(endpoint.UserAccessPolicies) > 0 || len(endpoint.TeamAccessPolicies) > 0 {
|
||||||
err = handler.AuthorizationService.UpdateUsersAuthorizationsTx(tx)
|
if err := handler.AuthorizationService.UpdateUsersAuthorizationsTx(tx); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Msgf("Unable to update user authorizations")
|
log.Warn().Err(err).Msgf("Unable to update user authorizations")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.EndpointRelation().DeleteEndpointRelation(endpoint.ID)
|
if err := tx.EndpointRelation().DeleteEndpointRelation(endpoint.ID); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Msgf("Unable to remove environment relation from the database")
|
log.Warn().Err(err).Msgf("Unable to remove environment relation from the database")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,8 +184,7 @@ func (handler *Handler) deleteEndpoint(tx dataservices.DataStoreTx, endpointID p
|
||||||
return e == endpoint.ID
|
return e == endpoint.ID
|
||||||
})
|
})
|
||||||
|
|
||||||
err = tx.EdgeGroup().Update(edgeGroup.ID, &edgeGroup)
|
if err := tx.EdgeGroup().Update(edgeGroup.ID, &edgeGroup); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Msgf("Unable to update edge group")
|
log.Warn().Err(err).Msgf("Unable to update edge group")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,13 +241,11 @@ func (handler *Handler) deleteEndpoint(tx dataservices.DataStoreTx, endpointID p
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the pending actions
|
// delete the pending actions
|
||||||
err = tx.PendingActions().DeleteByEndpointID(endpoint.ID)
|
if err := tx.PendingActions().DeleteByEndpointID(endpoint.ID); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Int("endpointId", int(endpoint.ID)).Msgf("Unable to delete pending actions")
|
log.Warn().Err(err).Int("endpointId", int(endpoint.ID)).Msgf("Unable to delete pending actions")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Endpoint().DeleteEndpoint(endpointID)
|
if err := tx.Endpoint().DeleteEndpoint(endpointID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to delete the environment from the database", err)
|
return httperror.InternalServerError("Unable to delete the environment from the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ func (handler *Handler) endpointForceUpdateService(w http.ResponseWriter, r *htt
|
||||||
// ignore errors from this cleanup function, log them instead
|
// ignore errors from this cleanup function, log them instead
|
||||||
containers, err := dockerClient.ContainerList(context.TODO(), container.ListOptions{
|
containers, err := dockerClient.ContainerList(context.TODO(), container.ListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
Filters: filters.NewArgs(filters.Arg("label", consts.SwarmServiceIdLabel+"="+payload.ServiceID)),
|
Filters: filters.NewArgs(filters.Arg("label", consts.SwarmServiceIDLabel+"="+payload.ServiceID)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("Environment", endpoint.Name).Msg("Error listing containers")
|
log.Warn().Err(err).Str("Environment", endpoint.Name).Msg("Error listing containers")
|
||||||
|
|
|
@ -32,8 +32,7 @@ func (handler *Handler) registryDelete(w http.ResponseWriter, r *http.Request) *
|
||||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable to retrieve info from request context", err)
|
return httperror.InternalServerError("Unable to retrieve info from request context", err)
|
||||||
}
|
} else if !securityContext.IsAdmin {
|
||||||
if !securityContext.IsAdmin {
|
|
||||||
return httperror.Forbidden("Permission denied to delete registry", httperrors.ErrResourceAccessDenied)
|
return httperror.Forbidden("Permission denied to delete registry", httperrors.ErrResourceAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,21 +46,16 @@ func (handler *Handler) registryDelete(w http.ResponseWriter, r *http.Request) *
|
||||||
return httperror.InternalServerError(fmt.Sprintf("Unable to load registry %q from the database", registry.Name), err)
|
return httperror.InternalServerError(fmt.Sprintf("Unable to load registry %q from the database", registry.Name), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.DataStore.Registry().Delete(portainer.RegistryID(registryID))
|
if err := handler.DataStore.Registry().Delete(portainer.RegistryID(registryID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to remove the registry from the database", err)
|
return httperror.InternalServerError("Unable to remove the registry from the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.deleteKubernetesSecrets(registry)
|
handler.deleteKubernetesSecrets(registry)
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to delete registry secrets", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.Empty(w)
|
return response.Empty(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) deleteKubernetesSecrets(registry *portainer.Registry) error {
|
func (handler *Handler) deleteKubernetesSecrets(registry *portainer.Registry) {
|
||||||
|
|
||||||
for endpointId, access := range registry.RegistryAccesses {
|
for endpointId, access := range registry.RegistryAccesses {
|
||||||
if access.Namespaces != nil {
|
if access.Namespaces != nil {
|
||||||
// Obtain a kubeclient for the endpoint
|
// Obtain a kubeclient for the endpoint
|
||||||
|
@ -69,6 +63,7 @@ func (handler *Handler) deleteKubernetesSecrets(registry *portainer.Registry) er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Skip environments that can't be loaded from the DB
|
// Skip environments that can't be loaded from the DB
|
||||||
log.Warn().Err(err).Msgf("Unable to load the environment with id %d from the database", endpointId)
|
log.Warn().Err(err).Msgf("Unable to load the environment with id %d from the database", endpointId)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,13 +71,14 @@ func (handler *Handler) deleteKubernetesSecrets(registry *portainer.Registry) er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Skip environments that can't get a kubeclient from
|
// Skip environments that can't get a kubeclient from
|
||||||
log.Warn().Err(err).Msgf("Unable to get kubernetes client for environment %d", endpointId)
|
log.Warn().Err(err).Msgf("Unable to get kubernetes client for environment %d", endpointId)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
failedNamespaces := make([]string, 0)
|
failedNamespaces := make([]string, 0)
|
||||||
|
|
||||||
for _, ns := range access.Namespaces {
|
for _, ns := range access.Namespaces {
|
||||||
err = cli.DeleteRegistrySecret(registry.ID, ns)
|
if err := cli.DeleteRegistrySecret(registry.ID, ns); err != nil {
|
||||||
if err != nil {
|
|
||||||
failedNamespaces = append(failedNamespaces, ns)
|
failedNamespaces = append(failedNamespaces, ns)
|
||||||
log.Warn().Err(err).Msgf("Unable to delete registry secret %q from namespace %q for environment %d. Retrying offline", cli.RegistrySecretName(registry.ID), ns, endpointId)
|
log.Warn().Err(err).Msgf("Unable to delete registry secret %q from namespace %q for environment %d. Retrying offline", cli.RegistrySecretName(registry.ID), ns, endpointId)
|
||||||
}
|
}
|
||||||
|
@ -95,6 +91,4 @@ func (handler *Handler) deleteKubernetesSecrets(registry *portainer.Registry) er
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,11 +32,10 @@ func (handler *Handler) resourceControlDelete(w http.ResponseWriter, r *http.Req
|
||||||
if handler.DataStore.IsErrObjectNotFound(err) {
|
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||||
return httperror.NotFound("Unable to find a resource control with the specified identifier inside the database", err)
|
return httperror.NotFound("Unable to find a resource control with the specified identifier inside the database", err)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httperror.InternalServerError("Unable to find a resource control with with the specified identifier inside the database", err)
|
return httperror.InternalServerError("Unable to find a resource control with the specified identifier inside the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.DataStore.ResourceControl().Delete(portainer.ResourceControlID(resourceControlID))
|
if err := handler.DataStore.ResourceControl().Delete(portainer.ResourceControlID(resourceControlID)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to remove the resource control from the database", err)
|
return httperror.InternalServerError("Unable to remove the resource control from the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
|
||||||
if payload.Public && payload.AdministratorsOnly {
|
if payload.Public && payload.AdministratorsOnly {
|
||||||
return errors.New("invalid payload: cannot set public and administrators only")
|
return errors.New("invalid payload: cannot set public and administrators only")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +59,7 @@ func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Req
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload resourceControlUpdatePayload
|
var payload resourceControlUpdatePayload
|
||||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.BadRequest("Invalid request payload", err)
|
return httperror.BadRequest("Invalid request payload", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Req
|
||||||
if handler.DataStore.IsErrObjectNotFound(err) {
|
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||||
return httperror.NotFound("Unable to find a resource control with the specified identifier inside the database", err)
|
return httperror.NotFound("Unable to find a resource control with the specified identifier inside the database", err)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return httperror.InternalServerError("Unable to find a resource control with with the specified identifier inside the database", err)
|
return httperror.InternalServerError("Unable to find a resource control with the specified identifier inside the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||||
|
@ -106,8 +106,7 @@ func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Req
|
||||||
return httperror.Forbidden("Permission denied to update the resource control", httperrors.ErrResourceAccessDenied)
|
return httperror.Forbidden("Permission denied to update the resource control", httperrors.ErrResourceAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.DataStore.ResourceControl().Update(resourceControl.ID, resourceControl)
|
if err := handler.DataStore.ResourceControl().Update(resourceControl.ID, resourceControl); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to persist resource control changes inside the database", err)
|
return httperror.InternalServerError("Unable to persist resource control changes inside the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ func (payload *updateComposeStackPayload) Validate(r *http.Request) error {
|
||||||
if govalidator.IsNull(payload.StackFileContent) {
|
if govalidator.IsNull(payload.StackFileContent) {
|
||||||
return errors.New("Invalid stack file content")
|
return errors.New("Invalid stack file content")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ func (payload *updateSwarmStackPayload) Validate(r *http.Request) error {
|
||||||
if govalidator.IsNull(payload.StackFileContent) {
|
if govalidator.IsNull(payload.StackFileContent) {
|
||||||
return errors.New("Invalid stack file content")
|
return errors.New("Invalid stack file content")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,8 +104,7 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
||||||
return httperror.InternalServerError("Unable to find the environment associated to the stack inside the database", err)
|
return httperror.InternalServerError("Unable to find the environment associated to the stack inside the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
|
if err := handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.Forbidden("Permission denied to access environment", err)
|
return httperror.Forbidden("Permission denied to access environment", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,45 +115,40 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
||||||
|
|
||||||
//only check resource control when it is a DockerSwarmStack or a DockerComposeStack
|
//only check resource control when it is a DockerSwarmStack or a DockerComposeStack
|
||||||
if stack.Type == portainer.DockerSwarmStack || stack.Type == portainer.DockerComposeStack {
|
if stack.Type == portainer.DockerSwarmStack || stack.Type == portainer.DockerComposeStack {
|
||||||
|
|
||||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
|
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable to retrieve a resource control associated to the stack", err)
|
return httperror.InternalServerError("Unable to retrieve a resource control associated to the stack", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl)
|
if access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to verify user authorizations to validate stack access", err)
|
return httperror.InternalServerError("Unable to verify user authorizations to validate stack access", err)
|
||||||
}
|
} else if !access {
|
||||||
if !access {
|
|
||||||
return httperror.Forbidden("Access denied to resource", httperrors.ErrResourceAccessDenied)
|
return httperror.Forbidden("Access denied to resource", httperrors.ErrResourceAccessDenied)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canManage, err := handler.userCanManageStacks(securityContext, endpoint)
|
if canManage, err := handler.userCanManageStacks(securityContext, endpoint); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to verify user authorizations to validate stack deletion", err)
|
return httperror.InternalServerError("Unable to verify user authorizations to validate stack deletion", err)
|
||||||
}
|
} else if !canManage {
|
||||||
if !canManage {
|
|
||||||
errMsg := "Stack editing is disabled for non-admin users"
|
errMsg := "Stack editing is disabled for non-admin users"
|
||||||
|
|
||||||
return httperror.Forbidden(errMsg, errors.New(errMsg))
|
return httperror.Forbidden(errMsg, errors.New(errMsg))
|
||||||
}
|
}
|
||||||
|
|
||||||
updateError := handler.updateAndDeployStack(r, stack, endpoint)
|
if err := handler.updateAndDeployStack(r, stack, endpoint); err != nil {
|
||||||
if updateError != nil {
|
return err
|
||||||
return updateError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := handler.DataStore.User().Read(securityContext.UserID)
|
user, err := handler.DataStore.User().Read(securityContext.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.BadRequest("Cannot find context user", errors.Wrap(err, "failed to fetch the user"))
|
return httperror.BadRequest("Cannot find context user", errors.Wrap(err, "failed to fetch the user"))
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.UpdatedBy = user.Username
|
stack.UpdatedBy = user.Username
|
||||||
stack.UpdateDate = time.Now().Unix()
|
stack.UpdateDate = time.Now().Unix()
|
||||||
stack.Status = portainer.StackStatusActive
|
stack.Status = portainer.StackStatusActive
|
||||||
|
|
||||||
err = handler.DataStore.Stack().Update(stack.ID, stack)
|
if err := handler.DataStore.Stack().Update(stack.ID, stack); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to persist the stack changes inside the database", err)
|
return httperror.InternalServerError("Unable to persist the stack changes inside the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,19 +161,20 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) updateAndDeployStack(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
func (handler *Handler) updateAndDeployStack(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
||||||
if stack.Type == portainer.DockerSwarmStack {
|
switch stack.Type {
|
||||||
|
case portainer.DockerSwarmStack:
|
||||||
stack.Name = handler.SwarmStackManager.NormalizeStackName(stack.Name)
|
stack.Name = handler.SwarmStackManager.NormalizeStackName(stack.Name)
|
||||||
|
|
||||||
return handler.updateSwarmStack(r, stack, endpoint)
|
return handler.updateSwarmStack(r, stack, endpoint)
|
||||||
} else if stack.Type == portainer.DockerComposeStack {
|
case portainer.DockerComposeStack:
|
||||||
stack.Name = handler.ComposeStackManager.NormalizeStackName(stack.Name)
|
stack.Name = handler.ComposeStackManager.NormalizeStackName(stack.Name)
|
||||||
|
|
||||||
return handler.updateComposeStack(r, stack, endpoint)
|
return handler.updateComposeStack(r, stack, endpoint)
|
||||||
} else if stack.Type == portainer.KubernetesStack {
|
case portainer.KubernetesStack:
|
||||||
return handler.updateKubernetesStack(r, stack, endpoint)
|
return handler.updateKubernetesStack(r, stack, endpoint)
|
||||||
} else {
|
|
||||||
return httperror.InternalServerError("Unsupported stack", errors.Errorf("unsupported stack type: %v", stack.Type))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return httperror.InternalServerError("Unsupported stack", errors.Errorf("unsupported stack type: %v", stack.Type))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
||||||
|
@ -191,8 +188,7 @@ func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Sta
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload updateComposeStackPayload
|
var payload updateComposeStackPayload
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.BadRequest("Invalid request payload", err)
|
return httperror.BadRequest("Invalid request payload", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,8 +200,7 @@ func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Sta
|
||||||
}
|
}
|
||||||
|
|
||||||
stackFolder := strconv.Itoa(int(stack.ID))
|
stackFolder := strconv.Itoa(int(stack.ID))
|
||||||
_, err = handler.FileService.UpdateStoreStackFileFromBytes(stackFolder, stack.EntryPoint, []byte(payload.StackFileContent))
|
if _, err := handler.FileService.UpdateStoreStackFileFromBytes(stackFolder, stack.EntryPoint, []byte(payload.StackFileContent)); err != nil {
|
||||||
if err != nil {
|
|
||||||
if rollbackErr := handler.FileService.RollbackStackFile(stackFolder, stack.EntryPoint); rollbackErr != nil {
|
if rollbackErr := handler.FileService.RollbackStackFile(stackFolder, stack.EntryPoint); rollbackErr != nil {
|
||||||
log.Warn().Err(rollbackErr).Msg("rollback stack file error")
|
log.Warn().Err(rollbackErr).Msg("rollback stack file error")
|
||||||
}
|
}
|
||||||
|
@ -236,8 +231,7 @@ func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Sta
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy the stack
|
// Deploy the stack
|
||||||
err = composeDeploymentConfig.Deploy()
|
if err := composeDeploymentConfig.Deploy(); err != nil {
|
||||||
if err != nil {
|
|
||||||
if rollbackErr := handler.FileService.RollbackStackFile(stackFolder, stack.EntryPoint); rollbackErr != nil {
|
if rollbackErr := handler.FileService.RollbackStackFile(stackFolder, stack.EntryPoint); rollbackErr != nil {
|
||||||
log.Warn().Err(rollbackErr).Msg("rollback stack file error")
|
log.Warn().Err(rollbackErr).Msg("rollback stack file error")
|
||||||
}
|
}
|
||||||
|
@ -261,8 +255,7 @@ func (handler *Handler) updateSwarmStack(r *http.Request, stack *portainer.Stack
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload updateSwarmStackPayload
|
var payload updateSwarmStackPayload
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.BadRequest("Invalid request payload", err)
|
return httperror.BadRequest("Invalid request payload", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,8 +267,7 @@ func (handler *Handler) updateSwarmStack(r *http.Request, stack *portainer.Stack
|
||||||
}
|
}
|
||||||
|
|
||||||
stackFolder := strconv.Itoa(int(stack.ID))
|
stackFolder := strconv.Itoa(int(stack.ID))
|
||||||
_, err = handler.FileService.UpdateStoreStackFileFromBytes(stackFolder, stack.EntryPoint, []byte(payload.StackFileContent))
|
if _, err := handler.FileService.UpdateStoreStackFileFromBytes(stackFolder, stack.EntryPoint, []byte(payload.StackFileContent)); err != nil {
|
||||||
if err != nil {
|
|
||||||
if rollbackErr := handler.FileService.RollbackStackFile(stackFolder, stack.EntryPoint); rollbackErr != nil {
|
if rollbackErr := handler.FileService.RollbackStackFile(stackFolder, stack.EntryPoint); rollbackErr != nil {
|
||||||
log.Warn().Err(rollbackErr).Msg("rollback stack file error")
|
log.Warn().Err(rollbackErr).Msg("rollback stack file error")
|
||||||
}
|
}
|
||||||
|
@ -306,8 +298,7 @@ func (handler *Handler) updateSwarmStack(r *http.Request, stack *portainer.Stack
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy the stack
|
// Deploy the stack
|
||||||
err = swarmDeploymentConfig.Deploy()
|
if err := swarmDeploymentConfig.Deploy(); err != nil {
|
||||||
if err != nil {
|
|
||||||
if rollbackErr := handler.FileService.RollbackStackFile(stackFolder, stack.EntryPoint); rollbackErr != nil {
|
if rollbackErr := handler.FileService.RollbackStackFile(stackFolder, stack.EntryPoint); rollbackErr != nil {
|
||||||
log.Warn().Err(rollbackErr).Msg("rollback stack file error")
|
log.Warn().Err(rollbackErr).Msg("rollback stack file error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,7 @@ func (handler *Handler) templateFile(w http.ResponseWriter, r *http.Request) *ht
|
||||||
|
|
||||||
defer handler.cleanUp(projectPath)
|
defer handler.cleanUp(projectPath)
|
||||||
|
|
||||||
err = handler.GitService.CloneRepository(projectPath, template.Repository.URL, "", "", "", false)
|
if err := handler.GitService.CloneRepository(projectPath, template.Repository.URL, "", "", "", false); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to clone git repository", err)
|
return httperror.InternalServerError("Unable to clone git repository", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +81,10 @@ func (handler *Handler) templateFile(w http.ResponseWriter, r *http.Request) *ht
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(w, fileResponse{FileContent: string(fileContent)})
|
return response.JSON(w, fileResponse{FileContent: string(fileContent)})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) cleanUp(projectPath string) {
|
func (handler *Handler) cleanUp(projectPath string) {
|
||||||
err := handler.FileService.RemoveDirectory(projectPath)
|
if err := handler.FileService.RemoveDirectory(projectPath); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Debug().Err(err).Msg("HTTP error: unable to cleanup stack creation")
|
log.Debug().Err(err).Msg("HTTP error: unable to cleanup stack creation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package webhooks
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -26,15 +25,12 @@ import (
|
||||||
// @failure 500
|
// @failure 500
|
||||||
// @router /webhooks/{id} [post]
|
// @router /webhooks/{id} [post]
|
||||||
func (handler *Handler) webhookExecute(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) webhookExecute(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
|
|
||||||
webhookToken, err := request.RetrieveRouteVariableValue(r, "token")
|
webhookToken, err := request.RetrieveRouteVariableValue(r, "token")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Invalid service id parameter", err)
|
return httperror.InternalServerError("Invalid service id parameter", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
webhook, err := handler.DataStore.Webhook().WebhookByToken(webhookToken)
|
webhook, err := handler.DataStore.Webhook().WebhookByToken(webhookToken)
|
||||||
|
|
||||||
if handler.DataStore.IsErrObjectNotFound(err) {
|
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||||
return httperror.NotFound("Unable to find a webhook with this token", err)
|
return httperror.NotFound("Unable to find a webhook with this token", err)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -118,15 +114,12 @@ func (handler *Handler) executeServiceWebhook(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.NotFound("Error pulling image with the specified tag", err)
|
return httperror.NotFound("Error pulling image with the specified tag", err)
|
||||||
}
|
}
|
||||||
defer func(rc io.ReadCloser) {
|
defer rc.Close()
|
||||||
_ = rc.Close()
|
|
||||||
}(rc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = dockerClient.ServiceUpdate(context.Background(), resourceID, service.Version, service.Spec, serviceUpdateOptions)
|
if _, err := dockerClient.ServiceUpdate(context.Background(), resourceID, service.Version, service.Spec, serviceUpdateOptions); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Error updating service", err)
|
return httperror.InternalServerError("Error updating service", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Empty(w)
|
return response.Empty(w)
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ func Test_waitingMiddleware_executesImmediately_whenNotLocked(t *testing.T) {
|
||||||
|
|
||||||
timeout := 2 * time.Second
|
timeout := 2 * time.Second
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
if elapsed >= timeout {
|
if elapsed >= timeout {
|
||||||
|
@ -81,7 +82,7 @@ func Test_waitingMiddleware_executesImmediately_whenNotLocked(t *testing.T) {
|
||||||
|
|
||||||
body, _ := io.ReadAll(response.Body)
|
body, _ := io.ReadAll(response.Body)
|
||||||
if string(body) != "success" {
|
if string(body) != "success" {
|
||||||
t.Error("Didn't receive expected result from the hanlder")
|
t.Error("Didn't receive expected result from the handler")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +106,7 @@ func Test_waitingMiddleware_waitsForTheLockToBeReleased(t *testing.T) {
|
||||||
|
|
||||||
timeout := 10 * time.Second
|
timeout := 10 * time.Second
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
if elapsed >= timeout {
|
if elapsed >= timeout {
|
||||||
|
@ -115,7 +117,7 @@ func Test_waitingMiddleware_waitsForTheLockToBeReleased(t *testing.T) {
|
||||||
|
|
||||||
body, _ := io.ReadAll(response.Body)
|
body, _ := io.ReadAll(response.Body)
|
||||||
if string(body) != "success" {
|
if string(body) != "success" {
|
||||||
t.Error("Didn't receive expected result from the hanlder")
|
t.Error("Didn't receive expected result from the handler")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ func (proxy *ProxyServer) start() error {
|
||||||
err := proxy.server.Serve(listener)
|
err := proxy.server.Serve(listener)
|
||||||
log.Debug().Str("host", proxyHost).Msg("exiting proxy server")
|
log.Debug().Str("host", proxyHost).Msg("exiting proxy server")
|
||||||
|
|
||||||
if err != nil && err != http.ErrServerClosed {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Debug().Str("host", proxyHost).Err(err).Msg("proxy server exited with an error")
|
log.Debug().Str("host", proxyHost).Err(err).Msg("proxy server exited with an error")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -22,7 +22,7 @@ func NewTransport(signatureService portainer.DigitalSignatureService, httpTransp
|
||||||
return transport
|
return transport
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTrip is the implementation of the the http.RoundTripper interface
|
// RoundTrip is the implementation of the http.RoundTripper interface
|
||||||
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||||
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -26,17 +26,16 @@ func (transport *Transport) proxyContainerGroupRequest(request *http.Request) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) proxyContainerGroupPutRequest(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) proxyContainerGroupPutRequest(request *http.Request) (*http.Response, error) {
|
||||||
|
|
||||||
tokenData, err := security.RetrieveTokenData(request)
|
tokenData, err := security.RetrieveTokenData(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, httperror.Forbidden("Permission denied to access environment", err)
|
return nil, httperror.Forbidden("Permission denied to access environment", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//add a lock before processing existence check
|
// Add a lock before processing existence check
|
||||||
transport.mutex.Lock()
|
transport.mutex.Lock()
|
||||||
defer transport.mutex.Unlock()
|
defer transport.mutex.Unlock()
|
||||||
|
|
||||||
//generate a temp http GET request based on the current PUT request
|
// Generate a temp http GET request based on the current PUT request
|
||||||
validationRequest := &http.Request{
|
validationRequest := &http.Request{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: request.URL,
|
URL: request.URL,
|
||||||
|
@ -45,7 +44,7 @@ func (transport *Transport) proxyContainerGroupPutRequest(request *http.Request)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
//fire the request to Azure API to validate if there is an existing container instance with the same name
|
// Fire the request to Azure API to validate if there is an existing container instance with the same name
|
||||||
// positive - reject the request
|
// positive - reject the request
|
||||||
// negative - continue the process
|
// negative - continue the process
|
||||||
validationResponse, err := http.DefaultTransport.RoundTrip(validationRequest)
|
validationResponse, err := http.DefaultTransport.RoundTrip(validationRequest)
|
||||||
|
@ -63,6 +62,7 @@ func (transport *Transport) proxyContainerGroupPutRequest(request *http.Request)
|
||||||
"message": "A container instance with the same name already exists inside the selected resource group",
|
"message": "A container instance with the same name already exists inside the selected resource group",
|
||||||
}
|
}
|
||||||
err = utils.RewriteResponse(resp, errObj, http.StatusConflict)
|
err = utils.RewriteResponse(resp, errObj, http.StatusConflict)
|
||||||
|
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ func NewTransport(credentials *portainer.AzureCredentials, dataStore dataservice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTrip is the implementation of the the http.RoundTripper interface
|
// RoundTrip is the implementation of the http.RoundTripper interface
|
||||||
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||||
return transport.proxyAzureRequest(request)
|
return transport.proxyAzureRequest(request)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ func NewTransport(parameters *TransportParameters, httpTransport *http.Transport
|
||||||
return transport, nil
|
return transport, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTrip is the implementation of the the http.RoundTripper interface
|
// RoundTrip is the implementation of the http.RoundTripper interface
|
||||||
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||||
return transport.ProxyDockerRequest(request)
|
return transport.ProxyDockerRequest(request)
|
||||||
}
|
}
|
||||||
|
@ -489,13 +489,15 @@ func (transport *Transport) decorateRegistryAuthenticationHeader(request *http.R
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) restrictedResourceOperation(request *http.Request, resourceID string, dockerResourceID string, resourceType portainer.ResourceControlType, volumeBrowseRestrictionCheck bool) (*http.Response, error) {
|
func (transport *Transport) restrictedResourceOperation(request *http.Request, resourceID string, dockerResourceID string, resourceType portainer.ResourceControlType, volumeBrowseRestrictionCheck bool) (*http.Response, error) {
|
||||||
var err error
|
|
||||||
tokenData, err := security.RetrieveTokenData(request)
|
tokenData, err := security.RetrieveTokenData(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tokenData.Role != portainer.AdministratorRole {
|
if tokenData.Role == portainer.AdministratorRole {
|
||||||
|
return transport.executeDockerRequest(request)
|
||||||
|
}
|
||||||
|
|
||||||
if volumeBrowseRestrictionCheck {
|
if volumeBrowseRestrictionCheck {
|
||||||
securitySettings, err := transport.fetchEndpointSecuritySettings()
|
securitySettings, err := transport.fetchEndpointSecuritySettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -545,7 +547,7 @@ func (transport *Transport) restrictedResourceOperation(request *http.Request, r
|
||||||
if resourceControl != nil && !authorization.UserCanAccessResource(tokenData.ID, userTeamIDs, resourceControl) {
|
if resourceControl != nil && !authorization.UserCanAccessResource(tokenData.ID, userTeamIDs, resourceControl) {
|
||||||
return utils.WriteAccessDeniedResponse()
|
return utils.WriteAccessDeniedResponse()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return transport.executeDockerRequest(request)
|
return transport.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -587,8 +589,7 @@ func (transport *Transport) rewriteOperation(request *http.Request, operation re
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) interceptAndRewriteRequest(request *http.Request, operation operationRequest) (*http.Response, error) {
|
func (transport *Transport) interceptAndRewriteRequest(request *http.Request, operation operationRequest) (*http.Response, error) {
|
||||||
err := operation(request)
|
if err := operation(request); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -653,7 +654,10 @@ func (transport *Transport) executeGenericResourceDeletionOperation(request *htt
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == http.StatusNoContent || response.StatusCode == http.StatusOK {
|
if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
resourceControl, err := transport.dataStore.ResourceControl().ResourceControlByResourceIDAndType(resourceIdentifierAttribute, resourceType)
|
resourceControl, err := transport.dataStore.ResourceControl().ResourceControlByResourceIDAndType(resourceIdentifierAttribute, resourceType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if dataservices.IsErrObjectNotFound(err) {
|
if dataservices.IsErrObjectNotFound(err) {
|
||||||
|
@ -664,12 +668,10 @@ func (transport *Transport) executeGenericResourceDeletionOperation(request *htt
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceControl != nil {
|
if resourceControl != nil {
|
||||||
err = transport.dataStore.ResourceControl().Delete(resourceControl.ID)
|
if err := transport.dataStore.ResourceControl().Delete(resourceControl.ID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
@ -683,6 +685,7 @@ func (transport *Transport) executeRequestAndRewriteResponse(request *http.Reque
|
||||||
if response.StatusCode == http.StatusOK {
|
if response.StatusCode == http.StatusOK {
|
||||||
err = operation(response, executor)
|
err = operation(response, executor)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,7 +727,10 @@ func (transport *Transport) createRegistryAccessContext(request *http.Request) (
|
||||||
}
|
}
|
||||||
accessContext.registries = registries
|
accessContext.registries = registries
|
||||||
|
|
||||||
if user.Role != portainer.AdministratorRole {
|
if user.Role == portainer.AdministratorRole {
|
||||||
|
return accessContext, nil
|
||||||
|
}
|
||||||
|
|
||||||
accessContext.isAdmin = false
|
accessContext.isAdmin = false
|
||||||
|
|
||||||
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
||||||
|
@ -733,7 +739,6 @@ func (transport *Transport) createRegistryAccessContext(request *http.Request) (
|
||||||
}
|
}
|
||||||
|
|
||||||
accessContext.teamMemberships = teamMemberships
|
accessContext.teamMemberships = teamMemberships
|
||||||
}
|
|
||||||
|
|
||||||
return accessContext, nil
|
return accessContext, nil
|
||||||
}
|
}
|
||||||
|
@ -756,7 +761,10 @@ func (transport *Transport) createOperationContext(request *http.Request) (*rest
|
||||||
resourceControls: resourceControls,
|
resourceControls: resourceControls,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tokenData.Role != portainer.AdministratorRole {
|
if tokenData.Role == portainer.AdministratorRole {
|
||||||
|
return operationContext, nil
|
||||||
|
}
|
||||||
|
|
||||||
operationContext.isAdmin = false
|
operationContext.isAdmin = false
|
||||||
|
|
||||||
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
||||||
|
@ -770,7 +778,6 @@ func (transport *Transport) createOperationContext(request *http.Request) (*rest
|
||||||
}
|
}
|
||||||
|
|
||||||
operationContext.userTeamIDs = userTeamIDs
|
operationContext.userTeamIDs = userTeamIDs
|
||||||
}
|
|
||||||
|
|
||||||
return operationContext, nil
|
return operationContext, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ func NewTransport() *Transport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTrip is the implementation of the the http.RoundTripper interface
|
// RoundTrip is the implementation of the http.RoundTripper interface
|
||||||
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||||
token := request.Header.Get("Private-Token")
|
token := request.Header.Get("Private-Token")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package factory
|
package factory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
@ -81,7 +80,7 @@ func (factory *ProxyFactory) newKubernetesEdgeHTTPProxy(endpoint *portainer.Endp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (factory *ProxyFactory) newKubernetesAgentHTTPSProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
func (factory *ProxyFactory) newKubernetesAgentHTTPSProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||||
endpointURL := fmt.Sprintf("https://%s", endpoint.URL)
|
endpointURL := "https://" + endpoint.URL
|
||||||
remoteURL, err := url.Parse(endpointURL)
|
remoteURL, err := url.Parse(endpointURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||||
|
"github.com/portainer/portainer/api/http/proxy/factory"
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||||
|
|
||||||
cmap "github.com/orcaman/concurrent-map"
|
|
||||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
var ErrProxyFactoryNotInitialized = errors.New("proxy factory not initialized")
|
||||||
|
|
||||||
// Manager represents a service used to manage proxies to environments (endpoints) and extensions.
|
// Manager represents a service used to manage proxies to environments (endpoints) and extensions.
|
||||||
Manager struct {
|
type Manager struct {
|
||||||
proxyFactory *factory.ProxyFactory
|
proxyFactory *factory.ProxyFactory
|
||||||
endpointProxies cmap.ConcurrentMap
|
endpointProxies cmap.ConcurrentMap
|
||||||
k8sClientFactory *cli.ClientFactory
|
k8sClientFactory *cli.ClientFactory
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// NewManager initializes a new proxy Service
|
// NewManager initializes a new proxy Service
|
||||||
func NewManager(kubernetesClientFactory *cli.ClientFactory) *Manager {
|
func NewManager(kubernetesClientFactory *cli.ClientFactory) *Manager {
|
||||||
|
@ -36,11 +36,11 @@ func (manager *Manager) NewProxyFactory(dataStore dataservices.DataStore, signat
|
||||||
manager.proxyFactory = factory.NewProxyFactory(dataStore, signatureService, tunnelService, clientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService)
|
manager.proxyFactory = factory.NewProxyFactory(dataStore, signatureService, tunnelService, clientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAndRegisterEndpointProxy creates a new HTTP reverse proxy based on environment(endpoint) properties and and adds it to the registered proxies.
|
// CreateAndRegisterEndpointProxy creates a new HTTP reverse proxy based on environment(endpoint) properties and adds it to the registered proxies.
|
||||||
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
||||||
func (manager *Manager) CreateAndRegisterEndpointProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
func (manager *Manager) CreateAndRegisterEndpointProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||||
if manager.proxyFactory == nil {
|
if manager.proxyFactory == nil {
|
||||||
return nil, fmt.Errorf("proxy factory not init")
|
return nil, ErrProxyFactoryNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy, err := manager.proxyFactory.NewEndpointProxy(endpoint)
|
proxy, err := manager.proxyFactory.NewEndpointProxy(endpoint)
|
||||||
|
@ -49,6 +49,7 @@ func (manager *Manager) CreateAndRegisterEndpointProxy(endpoint *portainer.Endpo
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.endpointProxies.Set(fmt.Sprint(endpoint.ID), proxy)
|
manager.endpointProxies.Set(fmt.Sprint(endpoint.ID), proxy)
|
||||||
|
|
||||||
return proxy, nil
|
return proxy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +57,9 @@ func (manager *Manager) CreateAndRegisterEndpointProxy(endpoint *portainer.Endpo
|
||||||
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
||||||
func (manager *Manager) CreateAgentProxyServer(endpoint *portainer.Endpoint) (*factory.ProxyServer, error) {
|
func (manager *Manager) CreateAgentProxyServer(endpoint *portainer.Endpoint) (*factory.ProxyServer, error) {
|
||||||
if manager.proxyFactory == nil {
|
if manager.proxyFactory == nil {
|
||||||
return nil, fmt.Errorf("proxy factory not init")
|
return nil, ErrProxyFactoryNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.proxyFactory.NewAgentProxy(endpoint)
|
return manager.proxyFactory.NewAgentProxy(endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +87,8 @@ func (manager *Manager) DeleteEndpointProxy(endpointID portainer.EndpointID) {
|
||||||
// CreateGitlabProxy creates a new HTTP reverse proxy that can be used to send requests to the Gitlab API
|
// CreateGitlabProxy creates a new HTTP reverse proxy that can be used to send requests to the Gitlab API
|
||||||
func (manager *Manager) CreateGitlabProxy(url string) (http.Handler, error) {
|
func (manager *Manager) CreateGitlabProxy(url string) (http.Handler, error) {
|
||||||
if manager.proxyFactory == nil {
|
if manager.proxyFactory == nil {
|
||||||
return nil, fmt.Errorf("proxy factory not init")
|
return nil, ErrProxyFactoryNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.proxyFactory.NewGitlabProxy(url)
|
return manager.proxyFactory.NewGitlabProxy(url)
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,7 +275,7 @@ func (bouncer *RequestBouncer) mwIsTeamLeader(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// mwAuthenticateFirst authenticates a request an auth token.
|
// mwAuthenticateFirst authenticates a request an auth token.
|
||||||
// A result of a first succeded token lookup would be used for the authentication.
|
// A result of a first succeeded token lookup would be used for the authentication.
|
||||||
func (bouncer *RequestBouncer) mwAuthenticateFirst(tokenLookups []tokenLookup, next http.Handler) http.Handler {
|
func (bouncer *RequestBouncer) mwAuthenticateFirst(tokenLookups []tokenLookup, next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var token *portainer.TokenData
|
var token *portainer.TokenData
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
"github.com/portainer/portainer/api/http/client"
|
"github.com/portainer/portainer/api/http/client"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,10 +24,9 @@ import (
|
||||||
// will save the signature from the first request after the admin user is created, ensuring that
|
// will save the signature from the first request after the admin user is created, ensuring that
|
||||||
// it matches in the event of a backup restoration.
|
// it matches in the event of a backup restoration.
|
||||||
func InitEndpoint(shutdownCtx context.Context, adminCreationDone <-chan struct{}, flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) {
|
func InitEndpoint(shutdownCtx context.Context, adminCreationDone <-chan struct{}, flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-shutdownCtx.Done():
|
case <-shutdownCtx.Done():
|
||||||
log.Debug().Msg("shutdown endpoint initalization")
|
log.Debug().Msg("shutdown endpoint initialization")
|
||||||
|
|
||||||
case <-adminCreationDone:
|
case <-adminCreationDone:
|
||||||
// Wait for the admin user to be created before initializing the primary endpoint
|
// Wait for the admin user to be created before initializing the primary endpoint
|
||||||
|
@ -35,8 +35,7 @@ func InitEndpoint(shutdownCtx context.Context, adminCreationDone <-chan struct{}
|
||||||
// 2. Using the API with the /api/users/admin/init endpoint
|
// 2. Using the API with the /api/users/admin/init endpoint
|
||||||
log.Debug().Msg("init primary endpoint")
|
log.Debug().Msg("init primary endpoint")
|
||||||
|
|
||||||
err := initEndpoint(flags, dataStore, snapshotService)
|
if err := initEndpoint(flags, dataStore, snapshotService); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("failed initializing environment")
|
log.Fatal().Err(err).Msg("failed initializing environment")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +60,7 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, s
|
||||||
if *flags.TLS || *flags.TLSSkipVerify {
|
if *flags.TLS || *flags.TLSSkipVerify {
|
||||||
return createTLSSecuredEndpoint(flags, dataStore, snapshotService)
|
return createTLSSecuredEndpoint(flags, dataStore, snapshotService)
|
||||||
}
|
}
|
||||||
|
|
||||||
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
|
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,8 +123,7 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := snapshotService.SnapshotEndpoint(endpoint)
|
if err := snapshotService.SnapshotEndpoint(endpoint); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("endpoint", endpoint.Name).
|
Str("endpoint", endpoint.Name).
|
||||||
Str("URL", endpoint.URL).
|
Str("URL", endpoint.URL).
|
||||||
|
@ -137,8 +136,7 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.
|
||||||
|
|
||||||
func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
|
func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
|
||||||
if strings.HasPrefix(endpointURL, "tcp://") {
|
if strings.HasPrefix(endpointURL, "tcp://") {
|
||||||
_, err := client.ExecutePingOperation(endpointURL, nil)
|
if _, err := client.ExecutePingOperation(endpointURL, nil); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,8 +170,7 @@ func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStor
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := snapshotService.SnapshotEndpoint(endpoint)
|
if err := snapshotService.SnapshotEndpoint(endpoint); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("endpoint", endpoint.Name).
|
Str("endpoint", endpoint.Name).
|
||||||
Str("URL", endpoint.URL).Err(err).
|
Str("URL", endpoint.URL).Err(err).
|
||||||
|
|
|
@ -5,20 +5,20 @@ import (
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isRegistryAssignedToNamespace(registry portainer.Registry, endpointID portainer.EndpointID, namespace string) (in bool) {
|
func isRegistryAssignedToNamespace(registry portainer.Registry, endpointID portainer.EndpointID, namespace string) bool {
|
||||||
for _, ns := range registry.RegistryAccesses[endpointID].Namespaces {
|
for _, ns := range registry.RegistryAccesses[endpointID].Namespaces {
|
||||||
if ns == namespace {
|
if ns == namespace {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func RefreshEcrSecret(cli portainer.KubeClient, endpoint *portainer.Endpoint, dataStore dataservices.DataStore, namespace string) (err error) {
|
func RefreshEcrSecret(cli portainer.KubeClient, endpoint *portainer.Endpoint, dataStore dataservices.DataStore, namespace string) error {
|
||||||
registries, err := dataStore.Registry().ReadAll()
|
registries, err := dataStore.Registry().ReadAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, registry := range registries {
|
for _, registry := range registries {
|
||||||
|
@ -30,21 +30,18 @@ func RefreshEcrSecret(cli portainer.KubeClient, endpoint *portainer.Endpoint, da
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = EnsureRegTokenValid(dataStore, ®istry)
|
if err := EnsureRegTokenValid(dataStore, ®istry); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cli.DeleteRegistrySecret(registry.ID, namespace)
|
if err := cli.DeleteRegistrySecret(registry.ID, namespace); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cli.CreateRegistrySecret(®istry, namespace)
|
if err := cli.CreateRegistrySecret(®istry, namespace); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ func (s Set[T]) Add(key T) {
|
||||||
// Contains returns true if the set contains the key.
|
// Contains returns true if the set contains the key.
|
||||||
func (s Set[T]) Contains(key T) bool {
|
func (s Set[T]) Contains(key T) bool {
|
||||||
_, ok := s[key]
|
_, ok := s[key]
|
||||||
|
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,18 +48,17 @@ func (s Set[T]) Keys() []T {
|
||||||
|
|
||||||
// Clear removes all keys from the set.
|
// Clear removes all keys from the set.
|
||||||
func (s Set[T]) Copy() Set[T] {
|
func (s Set[T]) Copy() Set[T] {
|
||||||
copy := make(Set[T])
|
c := make(Set[T])
|
||||||
|
|
||||||
for key := range s {
|
for key := range s {
|
||||||
copy.Add(key)
|
c.Add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
return copy
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Difference returns a new set containing the keys that are in the first set but not in the second set.
|
// Difference returns a new set containing the keys that are in the first set but not in the second set.
|
||||||
func (set Set[T]) Difference(second Set[T]) Set[T] {
|
func (set Set[T]) Difference(second Set[T]) Set[T] {
|
||||||
|
|
||||||
difference := set.Copy()
|
difference := set.Copy()
|
||||||
|
|
||||||
for key := range second {
|
for key := range second {
|
||||||
|
@ -106,5 +106,6 @@ func ToSet[T SetKey](keys []T) Set[T] {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
set.Add(key)
|
set.Add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ type filterTestCase[T any] struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
func TestFilter(t *testing.T) {
|
||||||
|
|
||||||
intTestCases := []filterTestCase[int]{
|
intTestCases := []filterTestCase[int]{
|
||||||
{
|
{
|
||||||
name: "Filter even numbers",
|
name: "Filter even numbers",
|
||||||
|
@ -59,7 +58,6 @@ func TestFilter(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
runTestCases(t, stringTestCases)
|
runTestCases(t, stringTestCases)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTestCases[T any](t *testing.T, testCases []filterTestCase[T]) {
|
func runTestCases[T any](t *testing.T, testCases []filterTestCase[T]) {
|
||||||
|
@ -85,9 +83,7 @@ func TestMap(t *testing.T) {
|
||||||
name: "Map integers to strings",
|
name: "Map integers to strings",
|
||||||
input: []int{1, 2, 3, 4, 5},
|
input: []int{1, 2, 3, 4, 5},
|
||||||
expected: []string{"1", "2", "3", "4", "5"},
|
expected: []string{"1", "2", "3", "4", "5"},
|
||||||
mapper: func(n int) string {
|
mapper: strconv.Itoa,
|
||||||
return strconv.Itoa(n)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ func NewService(
|
||||||
// NewBackgroundSnapshotter queues snapshots of existing edge environments that
|
// NewBackgroundSnapshotter queues snapshots of existing edge environments that
|
||||||
// do not have one already
|
// do not have one already
|
||||||
func NewBackgroundSnapshotter(dataStore dataservices.DataStore, tunnelService portainer.ReverseTunnelService) {
|
func NewBackgroundSnapshotter(dataStore dataservices.DataStore, tunnelService portainer.ReverseTunnelService) {
|
||||||
err := dataStore.ViewTx(func(tx dataservices.DataStoreTx) error {
|
if err := dataStore.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||||
endpoints, err := tx.Endpoint().Endpoints()
|
endpoints, err := tx.Endpoint().Endpoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -76,9 +76,9 @@ func NewBackgroundSnapshotter(dataStore dataservices.DataStore, tunnelService po
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("background snapshotter failure")
|
log.Error().Err(err).Msg("background snapshotter failure")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,6 +138,7 @@ func (service *Service) SnapshotEndpoint(endpoint *portainer.Endpoint) error {
|
||||||
if endpoint.Type == portainer.AgentOnDockerEnvironment || endpoint.Type == portainer.AgentOnKubernetesEnvironment {
|
if endpoint.Type == portainer.AgentOnDockerEnvironment || endpoint.Type == portainer.AgentOnKubernetesEnvironment {
|
||||||
var err error
|
var err error
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
|
||||||
if endpoint.TLSConfig.TLS {
|
if endpoint.TLSConfig.TLS {
|
||||||
tlsConfig, err = crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
tlsConfig, err = crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -243,6 +244,7 @@ func (service *Service) startSnapshotLoop() {
|
||||||
case <-service.shutdownCtx.Done():
|
case <-service.shutdownCtx.Done():
|
||||||
log.Debug().Msg("shutting down snapshotting")
|
log.Debug().Msg("shutting down snapshotting")
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
|
|
||||||
return
|
return
|
||||||
case interval := <-service.snapshotIntervalCh:
|
case interval := <-service.snapshotIntervalCh:
|
||||||
ticker.Reset(interval)
|
ticker.Reset(interval)
|
||||||
|
@ -265,6 +267,7 @@ func (service *Service) snapshotEndpoints() error {
|
||||||
|
|
||||||
service.dataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
service.dataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||||
updateEndpointStatus(tx, &endpoint, snapshotError, service.pendingActionsService)
|
updateEndpointStatus(tx, &endpoint, snapshotError, service.pendingActionsService)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -284,6 +287,7 @@ func updateEndpointStatus(tx dataservices.DataStoreTx, endpoint *portainer.Endpo
|
||||||
}
|
}
|
||||||
|
|
||||||
latestEndpointReference.Status = portainer.EndpointStatusUp
|
latestEndpointReference.Status = portainer.EndpointStatusUp
|
||||||
|
|
||||||
if snapshotError != nil {
|
if snapshotError != nil {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("endpoint", endpoint.Name).
|
Str("endpoint", endpoint.Name).
|
||||||
|
@ -295,8 +299,7 @@ func updateEndpointStatus(tx dataservices.DataStoreTx, endpoint *portainer.Endpo
|
||||||
|
|
||||||
latestEndpointReference.Agent.Version = endpoint.Agent.Version
|
latestEndpointReference.Agent.Version = endpoint.Agent.Version
|
||||||
|
|
||||||
err = tx.Endpoint().UpdateEndpoint(latestEndpointReference.ID, latestEndpointReference)
|
if err := tx.Endpoint().UpdateEndpoint(latestEndpointReference.ID, latestEndpointReference); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("endpoint", endpoint.Name).
|
Str("endpoint", endpoint.Name).
|
||||||
Str("URL", endpoint.URL).Err(err).
|
Str("URL", endpoint.URL).Err(err).
|
||||||
|
@ -323,5 +326,6 @@ func FetchDockerID(snapshot portainer.DockerSnapshot) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterInfo := swarmInfo.Cluster
|
clusterInfo := swarmInfo.Cluster
|
||||||
|
|
||||||
return clusterInfo.ID, nil
|
return clusterInfo.ID, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package url
|
package url
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -16,7 +15,7 @@ func ParseURL(endpointURL string) (*url.URL, error) {
|
||||||
!strings.HasPrefix(endpointURL, "//") &&
|
!strings.HasPrefix(endpointURL, "//") &&
|
||||||
!strings.HasPrefix(endpointURL, `unix:`) &&
|
!strings.HasPrefix(endpointURL, `unix:`) &&
|
||||||
!strings.HasPrefix(endpointURL, `npipe:`) {
|
!strings.HasPrefix(endpointURL, `npipe:`) {
|
||||||
endpointURL = fmt.Sprintf("//%s", endpointURL)
|
endpointURL = "//" + endpointURL
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.Parse(endpointURL)
|
return url.Parse(endpointURL)
|
||||||
|
|
|
@ -2,7 +2,7 @@ package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
@ -20,7 +20,7 @@ import (
|
||||||
// - The shell pod will be automatically removed after a specified max life (prevent zombie pods)
|
// - The shell pod will be automatically removed after a specified max life (prevent zombie pods)
|
||||||
// - The shell pod will be automatically removed if request is cancelled (or client closes websocket connection)
|
// - The shell pod will be automatically removed if request is cancelled (or client closes websocket connection)
|
||||||
func (kcl *KubeClient) CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*portainer.KubernetesShellPod, error) {
|
func (kcl *KubeClient) CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*portainer.KubernetesShellPod, error) {
|
||||||
maxPodKeepAliveSecondsStr := fmt.Sprintf("%d", int(portainer.WebSocketKeepAlive.Seconds()))
|
maxPodKeepAliveSecondsStr := strconv.Itoa(int(portainer.WebSocketKeepAlive.Seconds()))
|
||||||
|
|
||||||
podPrefix := userShellPodPrefix(serviceAccountName)
|
podPrefix := userShellPodPrefix(serviceAccountName)
|
||||||
|
|
||||||
|
@ -57,9 +57,10 @@ func (kcl *KubeClient) CreateUserShellPod(ctx context.Context, serviceAccountNam
|
||||||
// Wait for pod to reach ready state
|
// Wait for pod to reach ready state
|
||||||
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 20*time.Second)
|
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 20*time.Second)
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
err = kcl.waitForPodStatus(timeoutCtx, v1.PodRunning, shellPod)
|
|
||||||
if err != nil {
|
if err := kcl.waitForPodStatus(timeoutCtx, v1.PodRunning, shellPod); err != nil {
|
||||||
kcl.cli.CoreV1().Pods(portainerNamespace).Delete(context.TODO(), shellPod.Name, metav1.DeleteOptions{})
|
kcl.cli.CoreV1().Pods(portainerNamespace).Delete(context.TODO(), shellPod.Name, metav1.DeleteOptions{})
|
||||||
|
|
||||||
return nil, errors.Wrap(err, "aborting pod creation; error waiting for shell pod ready status")
|
return nil, errors.Wrap(err, "aborting pod creation; error waiting for shell pod ready status")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +92,6 @@ func (kcl *KubeClient) CreateUserShellPod(ctx context.Context, serviceAccountNam
|
||||||
func (kcl *KubeClient) waitForPodStatus(ctx context.Context, phase v1.PodPhase, pod *v1.Pod) error {
|
func (kcl *KubeClient) waitForPodStatus(ctx context.Context, phase v1.PodPhase, pod *v1.Pod) error {
|
||||||
log.Debug().Str("pod", pod.Name).Msg("waiting for pod ready")
|
log.Debug().Str("pod", pod.Name).Msg("waiting for pod ready")
|
||||||
|
|
||||||
pollDelay := 500 * time.Millisecond
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
@ -106,7 +106,7 @@ func (kcl *KubeClient) waitForPodStatus(ctx context.Context, phase v1.PodPhase,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
<-time.After(pollDelay)
|
time.Sleep(500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,18 +34,17 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (kcl *KubeClient) DeleteRegistrySecret(registry portainer.RegistryID, namespace string) error {
|
func (kcl *KubeClient) DeleteRegistrySecret(registry portainer.RegistryID, namespace string) error {
|
||||||
err := kcl.cli.CoreV1().Secrets(namespace).Delete(context.TODO(), kcl.RegistrySecretName(registry), metav1.DeleteOptions{})
|
if err := kcl.cli.CoreV1().Secrets(namespace).Delete(context.TODO(), kcl.RegistrySecretName(registry), metav1.DeleteOptions{}); err != nil && !k8serrors.IsNotFound(err) {
|
||||||
if err != nil && !k8serrors.IsNotFound(err) {
|
|
||||||
return errors.Wrap(err, "failed removing secret")
|
return errors.Wrap(err, "failed removing secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kcl *KubeClient) CreateRegistrySecret(registry *portainer.Registry, namespace string) (err error) {
|
func (kcl *KubeClient) CreateRegistrySecret(registry *portainer.Registry, namespace string) error {
|
||||||
username, password, err := registryutils.GetRegEffectiveCredential(registry)
|
username, password, err := registryutils.GetRegEffectiveCredential(registry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
config := dockerConfig{
|
config := dockerConfig{
|
||||||
|
@ -79,13 +78,11 @@ func (kcl *KubeClient) CreateRegistrySecret(registry *portainer.Registry, namesp
|
||||||
Type: v1.SecretTypeDockerConfigJson,
|
Type: v1.SecretTypeDockerConfigJson,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = kcl.cli.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
|
if _, err := kcl.cli.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
|
||||||
return errors.Wrap(err, "failed saving secret")
|
return errors.Wrap(err, "failed saving secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *KubeClient) IsRegistrySecret(namespace, secretName string) (bool, error) {
|
func (cli *KubeClient) IsRegistrySecret(namespace, secretName string) (bool, error) {
|
||||||
|
@ -101,7 +98,6 @@ func (cli *KubeClient) IsRegistrySecret(namespace, secretName string) (bool, err
|
||||||
isSecret := secret.Type == v1.SecretTypeDockerConfigJson
|
isSecret := secret.Type == v1.SecretTypeDockerConfigJson
|
||||||
|
|
||||||
return isSecret, nil
|
return isSecret, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*KubeClient) RegistrySecretName(registryID portainer.RegistryID) string {
|
func (*KubeClient) RegistrySecretName(registryID portainer.RegistryID) string {
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (*Service) AuthenticateUser(username, password string, settings *portainer.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errUserNotFound) {
|
if errors.Is(err, errUserNotFound) {
|
||||||
// prevent user enumeration timing attack by attempting the bind with a fake user
|
// prevent user enumeration timing attack by attempting the bind with a fake user
|
||||||
// and whatever password was provided should definately fail
|
// and whatever password was provided should definitely fail
|
||||||
// https://en.wikipedia.org/wiki/Timing_attack
|
// https://en.wikipedia.org/wiki/Timing_attack
|
||||||
userDN = "portainer-fake-ldap-username"
|
userDN = "portainer-fake-ldap-username"
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package oauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"strconv"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
)
|
)
|
||||||
|
@ -16,7 +16,7 @@ func getUsername(datamap map[string]interface{}, configuration *portainer.OAuthS
|
||||||
if !ok {
|
if !ok {
|
||||||
username, ok := datamap[configuration.UserIdentifier].(float64)
|
username, ok := datamap[configuration.UserIdentifier].(float64)
|
||||||
if ok && username != 0 {
|
if ok && username != 0 {
|
||||||
return fmt.Sprint(int(username)), nil
|
return strconv.Itoa(int(username)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ type FileContentMethodStackBuildProcess interface {
|
||||||
Deploy(payload *StackPayload, endpoint *portainer.Endpoint) FileContentMethodStackBuildProcess
|
Deploy(payload *StackPayload, endpoint *portainer.Endpoint) FileContentMethodStackBuildProcess
|
||||||
// Save the stack information to database
|
// Save the stack information to database
|
||||||
SaveStack() (*portainer.Stack, *httperror.HandlerError)
|
SaveStack() (*portainer.Stack, *httperror.HandlerError)
|
||||||
// Get reponse from http request. Use if it is needed
|
// Get response from HTTP request. Use if it is needed
|
||||||
GetResponse() string
|
GetResponse() string
|
||||||
// Process the file content
|
// Process the file content
|
||||||
SetFileContent(payload *StackPayload) FileContentMethodStackBuildProcess
|
SetFileContent(payload *StackPayload) FileContentMethodStackBuildProcess
|
||||||
|
@ -32,19 +32,15 @@ func (b *FileContentMethodStackBuilder) SetGeneralInfo(payload *StackPayload, en
|
||||||
b.stack.EndpointID = endpoint.ID
|
b.stack.EndpointID = endpoint.ID
|
||||||
b.stack.Status = portainer.StackStatusActive
|
b.stack.Status = portainer.StackStatusActive
|
||||||
b.stack.CreationDate = time.Now().Unix()
|
b.stack.CreationDate = time.Now().Unix()
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FileContentMethodStackBuilder) SetUniqueInfo(payload *StackPayload) FileContentMethodStackBuildProcess {
|
func (b *FileContentMethodStackBuilder) SetUniqueInfo(payload *StackPayload) FileContentMethodStackBuildProcess {
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FileContentMethodStackBuilder) SetFileContent(payload *StackPayload) FileContentMethodStackBuildProcess {
|
func (b *FileContentMethodStackBuilder) SetFileContent(payload *StackPayload) FileContentMethodStackBuildProcess {
|
||||||
if b.hasError() {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +50,8 @@ func (b *FileContentMethodStackBuilder) Deploy(payload *StackPayload, endpoint *
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy the stack
|
// Deploy the stack
|
||||||
err := b.deploymentConfiger.Deploy()
|
if err := b.deploymentConfiger.Deploy(); err != nil {
|
||||||
if err != nil {
|
|
||||||
b.err = httperror.InternalServerError(err.Error(), err)
|
b.err = httperror.InternalServerError(err.Error(), err)
|
||||||
return b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
|
|
|
@ -17,7 +17,7 @@ type FileUploadMethodStackBuildProcess interface {
|
||||||
Deploy(payload *StackPayload, endpoint *portainer.Endpoint) FileUploadMethodStackBuildProcess
|
Deploy(payload *StackPayload, endpoint *portainer.Endpoint) FileUploadMethodStackBuildProcess
|
||||||
// Save the stack information to database
|
// Save the stack information to database
|
||||||
SaveStack() (*portainer.Stack, *httperror.HandlerError)
|
SaveStack() (*portainer.Stack, *httperror.HandlerError)
|
||||||
// Get reponse from http request. Use if it is needed
|
// Get response from HTTP request. Use if it is needed
|
||||||
GetResponse() string
|
GetResponse() string
|
||||||
// Process the upload file
|
// Process the upload file
|
||||||
SetUploadedFile(payload *StackPayload) FileUploadMethodStackBuildProcess
|
SetUploadedFile(payload *StackPayload) FileUploadMethodStackBuildProcess
|
||||||
|
@ -33,11 +33,11 @@ func (b *FileUploadMethodStackBuilder) SetGeneralInfo(payload *StackPayload, end
|
||||||
b.stack.EndpointID = endpoint.ID
|
b.stack.EndpointID = endpoint.ID
|
||||||
b.stack.Status = portainer.StackStatusActive
|
b.stack.Status = portainer.StackStatusActive
|
||||||
b.stack.CreationDate = time.Now().Unix()
|
b.stack.CreationDate = time.Now().Unix()
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FileUploadMethodStackBuilder) SetUniqueInfo(payload *StackPayload) FileUploadMethodStackBuildProcess {
|
func (b *FileUploadMethodStackBuilder) SetUniqueInfo(payload *StackPayload) FileUploadMethodStackBuildProcess {
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ func (b *FileUploadMethodStackBuilder) SetUploadedFile(payload *StackPayload) Fi
|
||||||
b.err = httperror.InternalServerError("Unable to persist Compose file on disk", err)
|
b.err = httperror.InternalServerError("Unable to persist Compose file on disk", err)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
b.stack.ProjectPath = projectPath
|
b.stack.ProjectPath = projectPath
|
||||||
|
|
||||||
return b
|
return b
|
||||||
|
@ -63,13 +64,14 @@ func (b *FileUploadMethodStackBuilder) Deploy(payload *StackPayload, endpoint *p
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy the stack
|
// Deploy the stack
|
||||||
err := b.deploymentConfiger.Deploy()
|
if err := b.deploymentConfiger.Deploy(); err != nil {
|
||||||
if err != nil {
|
|
||||||
b.err = httperror.InternalServerError(err.Error(), err)
|
b.err = httperror.InternalServerError(err.Error(), err)
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
b.doCleanUp = false
|
b.doCleanUp = false
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ type GitMethodStackBuildProcess interface {
|
||||||
Deploy(payload *StackPayload, endpoint *portainer.Endpoint) GitMethodStackBuildProcess
|
Deploy(payload *StackPayload, endpoint *portainer.Endpoint) GitMethodStackBuildProcess
|
||||||
// Save the stack information to database and return the stack object
|
// Save the stack information to database and return the stack object
|
||||||
SaveStack() (*portainer.Stack, *httperror.HandlerError)
|
SaveStack() (*portainer.Stack, *httperror.HandlerError)
|
||||||
// Get reponse from http request. Use if it is needed
|
// Get response from HTTP request. Use if it is needed
|
||||||
GetResponse() string
|
GetResponse() string
|
||||||
// Set git repository configuration
|
// Set git repository configuration
|
||||||
SetGitRepository(payload *StackPayload) GitMethodStackBuildProcess
|
SetGitRepository(payload *StackPayload) GitMethodStackBuildProcess
|
||||||
|
@ -45,11 +45,11 @@ func (b *GitMethodStackBuilder) SetGeneralInfo(payload *StackPayload, endpoint *
|
||||||
b.stack.Status = portainer.StackStatusActive
|
b.stack.Status = portainer.StackStatusActive
|
||||||
b.stack.CreationDate = time.Now().Unix()
|
b.stack.CreationDate = time.Now().Unix()
|
||||||
b.stack.AutoUpdate = payload.AutoUpdate
|
b.stack.AutoUpdate = payload.AutoUpdate
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GitMethodStackBuilder) SetUniqueInfo(payload *StackPayload) GitMethodStackBuildProcess {
|
func (b *GitMethodStackBuilder) SetUniqueInfo(payload *StackPayload) GitMethodStackBuildProcess {
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ func (b *GitMethodStackBuilder) SetGitRepository(payload *StackPayload) GitMetho
|
||||||
if payload.ComposeFile == "" {
|
if payload.ComposeFile == "" {
|
||||||
repoConfig.ConfigFilePath = filesystem.ComposeFileDefaultName
|
repoConfig.ConfigFilePath = filesystem.ComposeFileDefaultName
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a manifest file is specified (for kube git apps), then use it instead of the default compose file name
|
// If a manifest file is specified (for kube git apps), then use it instead of the default compose file name
|
||||||
if payload.ManifestFile != "" {
|
if payload.ManifestFile != "" {
|
||||||
repoConfig.ConfigFilePath = payload.ManifestFile
|
repoConfig.ConfigFilePath = payload.ManifestFile
|
||||||
|
@ -97,6 +98,7 @@ func (b *GitMethodStackBuilder) SetGitRepository(payload *StackPayload) GitMetho
|
||||||
// Update the latest commit id
|
// Update the latest commit id
|
||||||
repoConfig.ConfigHash = commitHash
|
repoConfig.ConfigHash = commitHash
|
||||||
b.stack.GitConfig = &repoConfig
|
b.stack.GitConfig = &repoConfig
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +136,7 @@ func (b *GitMethodStackBuilder) SetAutoUpdate(payload *StackPayload) GitMethodSt
|
||||||
|
|
||||||
b.stack.AutoUpdate.JobID = jobID
|
b.stack.AutoUpdate.JobID = jobID
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,11 +38,13 @@ func (b *SwarmStackFileUploadBuilder) SetUniqueInfo(payload *StackPayload) FileU
|
||||||
if b.hasError() {
|
if b.hasError() {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
b.stack.Name = payload.Name
|
b.stack.Name = payload.Name
|
||||||
b.stack.Type = portainer.DockerSwarmStack
|
b.stack.Type = portainer.DockerSwarmStack
|
||||||
b.stack.SwarmID = payload.SwarmID
|
b.stack.SwarmID = payload.SwarmID
|
||||||
b.stack.EntryPoint = filesystem.ComposeFileDefaultName
|
b.stack.EntryPoint = filesystem.ComposeFileDefaultName
|
||||||
b.stack.Env = payload.Env
|
b.stack.Env = payload.Env
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ func IsEnabled(feat Feature) bool {
|
||||||
// IsSupported returns true if the feature is supported
|
// IsSupported returns true if the feature is supported
|
||||||
func IsSupported(feat Feature) bool {
|
func IsSupported(feat Feature) bool {
|
||||||
_, ok := featureFlags[feat]
|
_, ok := featureFlags[feat]
|
||||||
|
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,14 +82,15 @@ func Parse(features []string, supportedFeatures []Feature) {
|
||||||
if env != "" {
|
if env != "" {
|
||||||
envFeatures = strings.Split(env, ",")
|
envFeatures = strings.Split(env, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
features = append(features, envFeatures...)
|
features = append(features, envFeatures...)
|
||||||
|
|
||||||
// loop through feature flags to check if they are supported
|
// loop through feature flags to check if they are supported
|
||||||
for _, feat := range features {
|
for _, feat := range features {
|
||||||
f := Feature(strings.ToLower(feat))
|
f := Feature(strings.ToLower(feat))
|
||||||
_, ok := featureFlags[f]
|
if _, ok := featureFlags[f]; !ok {
|
||||||
if !ok {
|
|
||||||
log.Warn().Str("feature", f.String()).Msgf("unknown feature flag")
|
log.Warn().Str("feature", f.String()).Msgf("unknown feature flag")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (hbpm *helmBinaryPackageManager) SearchRepo(searchRepoOpts options.SearchRe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow redirect behavior to be overriden if specified.
|
// Allow redirect behavior to be overridden if specified.
|
||||||
if client.CheckRedirect == nil {
|
if client.CheckRedirect == nil {
|
||||||
client.CheckRedirect = defaultCheckRedirect
|
client.CheckRedirect = defaultCheckRedirect
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package release
|
||||||
import "github.com/portainer/portainer/pkg/libhelm/time"
|
import "github.com/portainer/portainer/pkg/libhelm/time"
|
||||||
|
|
||||||
// Release is the struct that holds the information for a helm release.
|
// Release is the struct that holds the information for a helm release.
|
||||||
// The struct definitions have been copied from the offical Helm Golang client/library.
|
// The struct definitions have been copied from the official Helm Golang client/library.
|
||||||
|
|
||||||
// ReleaseElement is a struct that represents a release
|
// ReleaseElement is a struct that represents a release
|
||||||
// This is the official struct from the helm project (golang codebase) - exported
|
// This is the official struct from the helm project (golang codebase) - exported
|
||||||
|
|
|
@ -28,7 +28,7 @@ func ValidateHelmRepositoryURL(repoUrl string, client *http.Client) error {
|
||||||
|
|
||||||
if client == nil {
|
if client == nil {
|
||||||
client = &http.Client{
|
client = &http.Client{
|
||||||
Timeout: time.Second * 120,
|
Timeout: 120 * time.Second,
|
||||||
Transport: http.DefaultTransport,
|
Transport: http.DefaultTransport,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Package error provides error/logging functions that can be used in conjuction with http.Handler.
|
// Package error provides error/logging functions that can be used in conjunction with http.Handler.
|
||||||
package error
|
package error
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -21,8 +21,7 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (handler LoggerHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
func (handler LoggerHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
err := handler(rw, r)
|
if err := handler(rw, r); err != nil {
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(rw, err)
|
writeErrorResponse(rw, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +33,7 @@ func capitalize(s string) string {
|
||||||
|
|
||||||
// Capitalize the first letter of the word or sentence
|
// Capitalize the first letter of the word or sentence
|
||||||
firstLetter := unicode.ToUpper(rune(s[0]))
|
firstLetter := unicode.ToUpper(rune(s[0]))
|
||||||
|
|
||||||
return string(firstLetter) + s[1:]
|
return string(firstLetter) + s[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,12 @@ func writeErrorResponse(rw http.ResponseWriter, err *HandlerError) {
|
||||||
err.Err = errors.New(capitalize(err.Message))
|
err.Err = errors.New(capitalize(err.Message))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().CallerSkipFrame(2).Err(err.Err).Int("status_code", err.StatusCode).Str("msg", err.Message).Msg("HTTP error")
|
log.Debug().
|
||||||
|
CallerSkipFrame(2).
|
||||||
|
Err(err.Err).
|
||||||
|
Int("status_code", err.StatusCode).
|
||||||
|
Str("msg", err.Message).
|
||||||
|
Msg("HTTP error")
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
rw.WriteHeader(err.StatusCode)
|
rw.WriteHeader(err.StatusCode)
|
||||||
|
|
|
@ -22,7 +22,6 @@ func (p *requestPayload) Validate(r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_GetPayload(t *testing.T) {
|
func Test_GetPayload(t *testing.T) {
|
||||||
|
|
||||||
payload := requestPayload{
|
payload := requestPayload{
|
||||||
FirstName: "John",
|
FirstName: "John",
|
||||||
LastName: "Doe",
|
LastName: "Doe",
|
||||||
|
@ -34,6 +33,7 @@ func Test_GetPayload(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(payloadJSON))
|
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(payloadJSON))
|
||||||
|
|
||||||
newPayload, err := request.GetPayload[requestPayload](r)
|
newPayload, err := request.GetPayload[requestPayload](r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -5,7 +5,7 @@ package request
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -33,10 +33,11 @@ func RetrieveMultiPartFormFile(request *http.Request, requestParameter string) (
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
fileContent, err := ioutil.ReadAll(file)
|
fileContent, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileContent, headers.Filename, nil
|
return fileContent, headers.Filename, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,10 +47,10 @@ func RetrieveMultiPartFormJSONValue(request *http.Request, name string, target i
|
||||||
value, err := RetrieveMultiPartFormValue(request, name, optional)
|
value, err := RetrieveMultiPartFormValue(request, name, optional)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
} else if value == "" {
|
||||||
if value == "" {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Unmarshal([]byte(value), target)
|
return json.Unmarshal([]byte(value), target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +61,7 @@ func RetrieveMultiPartFormValue(request *http.Request, name string, optional boo
|
||||||
if value == "" && !optional {
|
if value == "" && !optional {
|
||||||
return "", errors.New(ErrMissingFormDataValue)
|
return "", errors.New(ErrMissingFormDataValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +72,7 @@ func RetrieveNumericMultiPartFormValue(request *http.Request, name string, optio
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return strconv.Atoi(value)
|
return strconv.Atoi(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +83,7 @@ func RetrieveBooleanMultiPartFormValue(request *http.Request, name string, optio
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return value == "true", nil
|
return value == "true", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,10 +93,12 @@ func RetrieveRouteVariableValue(request *http.Request, name string) (string, err
|
||||||
if routeVariables == nil {
|
if routeVariables == nil {
|
||||||
return "", errors.New(ErrInvalidRequestURL)
|
return "", errors.New(ErrInvalidRequestURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
routeVar := routeVariables[name]
|
routeVar := routeVariables[name]
|
||||||
if routeVar == "" {
|
if routeVar == "" {
|
||||||
return "", errors.New(ErrInvalidRequestURL)
|
return "", errors.New(ErrInvalidRequestURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return routeVar, nil
|
return routeVar, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +108,7 @@ func RetrieveNumericRouteVariableValue(request *http.Request, name string) (int,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return strconv.Atoi(routeVar)
|
return strconv.Atoi(routeVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +119,7 @@ func RetrieveQueryParameter(request *http.Request, name string, optional bool) (
|
||||||
if queryParameter == "" && !optional {
|
if queryParameter == "" && !optional {
|
||||||
return "", errors.New(ErrMissingQueryParameter)
|
return "", errors.New(ErrMissingQueryParameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryParameter, nil
|
return queryParameter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,10 +129,10 @@ func RetrieveNumericQueryParameter(request *http.Request, name string, optional
|
||||||
queryParameter, err := RetrieveQueryParameter(request, name, optional)
|
queryParameter, err := RetrieveQueryParameter(request, name, optional)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
} else if queryParameter == "" && optional {
|
||||||
if queryParameter == "" && optional {
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return strconv.Atoi(queryParameter)
|
return strconv.Atoi(queryParameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +143,7 @@ func RetrieveBooleanQueryParameter(request *http.Request, name string, optional
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryParameter == "true", nil
|
return queryParameter == "true", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,9 +153,9 @@ func RetrieveJSONQueryParameter(request *http.Request, name string, target inter
|
||||||
queryParameter, err := RetrieveQueryParameter(request, name, optional)
|
queryParameter, err := RetrieveQueryParameter(request, name, optional)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
} else if queryParameter == "" {
|
||||||
if queryParameter == "" {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Unmarshal([]byte(queryParameter), target)
|
return json.Unmarshal([]byte(queryParameter), target)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue