mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 13:29:41 +02:00
feat(stacks): scope stack names to endpoint (#4520)
* refactor(stack): create unique name function * refactor(stack): change stack resource control id * feat(stacks): validate stack unique name in endpoint * feat(stacks): prevent name collision with external stacks * refactor(stacks): move resource id util * refactor(stacks): supply resource id util with name and endpoint * fix(docker): calculate swarm resource id * feat(stack): prevent migration if stack name already exist * feat(authorization): use stackutils
This commit is contained in:
parent
a62e0496de
commit
86ad1c6af1
25 changed files with 245 additions and 99 deletions
39
api/bolt/migrator/migrate_dbversion26.go
Normal file
39
api/bolt/migrator/migrate_dbversion26.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package migrator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Migrator) updateStackResourceControlToDB27() error {
|
||||||
|
resourceControls, err := m.resourceControlService.ResourceControls()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, resource := range resourceControls {
|
||||||
|
if resource.Type != portainer.StackResourceControl {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stackName := resource.ResourceID
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stack, err := m.stackService.StackByName(stackName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.ResourceID = fmt.Sprintf("%d_%s", stack.EndpointID, stack.Name)
|
||||||
|
|
||||||
|
err = m.resourceControlService.UpdateResourceControl(resource.ID, &resource)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package migrator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt/endpoint"
|
"github.com/portainer/portainer/api/bolt/endpoint"
|
||||||
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
||||||
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
||||||
|
@ -350,5 +350,13 @@ func (m *Migrator) Migrate() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Portainer 2.2.0
|
||||||
|
if m.currentDBVersion < 27 {
|
||||||
|
err := m.updateStackResourceControlToDB27()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package stacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -51,15 +52,13 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
stacks, err := handler.DataStore.Stack().Stacks()
|
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
}
|
}
|
||||||
|
if !isUnique {
|
||||||
for _, stack := range stacks {
|
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
|
||||||
if strings.EqualFold(stack.Name, payload.Name) {
|
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
|
||||||
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
||||||
|
@ -150,15 +149,13 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
stacks, err := handler.DataStore.Stack().Stacks()
|
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
}
|
}
|
||||||
|
if !isUnique {
|
||||||
for _, stack := range stacks {
|
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
|
||||||
if strings.EqualFold(stack.Name, payload.Name) {
|
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
|
||||||
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
||||||
|
@ -249,15 +246,13 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
stacks, err := handler.DataStore.Stack().Stacks()
|
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
}
|
}
|
||||||
|
if !isUnique {
|
||||||
for _, stack := range stacks {
|
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
|
||||||
if strings.EqualFold(stack.Name, payload.Name) {
|
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
|
||||||
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
||||||
|
|
|
@ -2,10 +2,10 @@ package stacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
|
@ -47,15 +47,13 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
stacks, err := handler.DataStore.Stack().Stacks()
|
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
}
|
}
|
||||||
|
if !isUnique {
|
||||||
for _, stack := range stacks {
|
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
|
||||||
if strings.EqualFold(stack.Name, payload.Name) {
|
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
|
||||||
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
||||||
|
@ -150,15 +148,13 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
stacks, err := handler.DataStore.Stack().Stacks()
|
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
}
|
}
|
||||||
|
if !isUnique {
|
||||||
for _, stack := range stacks {
|
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
|
||||||
if strings.EqualFold(stack.Name, payload.Name) {
|
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
|
||||||
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
||||||
|
@ -257,15 +253,13 @@ func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
stacks, err := handler.DataStore.Stack().Stacks()
|
isUnique, err := handler.checkUniqueName(endpoint, payload.Name, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve stacks from the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
}
|
}
|
||||||
|
if !isUnique {
|
||||||
for _, stack := range stacks {
|
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", payload.Name)
|
||||||
if strings.EqualFold(stack.Name, payload.Name) {
|
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
|
||||||
return &httperror.HandlerError{http.StatusConflict, "A stack with this name already exists", errStackAlreadyExists}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
package stacks
|
package stacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/docker"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
"github.com/portainer/portainer/api/internal/authorization"
|
"github.com/portainer/portainer/api/internal/authorization"
|
||||||
)
|
)
|
||||||
|
@ -24,6 +28,7 @@ type Handler struct {
|
||||||
requestBouncer *security.RequestBouncer
|
requestBouncer *security.RequestBouncer
|
||||||
*mux.Router
|
*mux.Router
|
||||||
DataStore portainer.DataStore
|
DataStore portainer.DataStore
|
||||||
|
DockerClientFactory *docker.ClientFactory
|
||||||
FileService portainer.FileService
|
FileService portainer.FileService
|
||||||
GitService portainer.GitService
|
GitService portainer.GitService
|
||||||
SwarmStackManager portainer.SwarmStackManager
|
SwarmStackManager portainer.SwarmStackManager
|
||||||
|
@ -103,3 +108,50 @@ func (handler *Handler) userCanCreateStack(securityContext *security.RestrictedR
|
||||||
|
|
||||||
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
|
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (handler *Handler) checkUniqueName(endpoint *portainer.Endpoint, name string, stackID portainer.StackID, swarmMode bool) (bool, error) {
|
||||||
|
stacks, err := handler.DataStore.Stack().Stacks()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stack := range stacks {
|
||||||
|
if strings.EqualFold(stack.Name, name) && (stackID == 0 || stackID != stack.ID) && stack.EndpointID == endpoint.ID {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerClient, err := handler.DockerClientFactory.CreateClient(endpoint, "")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer dockerClient.Close()
|
||||||
|
if swarmMode {
|
||||||
|
services, err := dockerClient.ServiceList(context.Background(), types.ServiceListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range services {
|
||||||
|
serviceNS, ok := service.Spec.Labels["com.docker.stack.namespace"]
|
||||||
|
if ok && serviceNS == name {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
containers, err := dockerClient.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
containerNS, ok := container.Labels["com.docker.compose.project"]
|
||||||
|
|
||||||
|
if ok && containerNS == name {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
"github.com/portainer/portainer/api/internal/authorization"
|
"github.com/portainer/portainer/api/internal/authorization"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (handler *Handler) cleanUp(stack *portainer.Stack, doCleanUp *bool) error {
|
func (handler *Handler) cleanUp(stack *portainer.Stack, doCleanUp *bool) error {
|
||||||
|
@ -208,9 +209,9 @@ func (handler *Handler) decorateStackResponse(w http.ResponseWriter, stack *port
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
resourceControl = authorization.NewAdministratorsOnlyResourceControl(stack.Name, portainer.StackResourceControl)
|
resourceControl = authorization.NewAdministratorsOnlyResourceControl(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
|
||||||
} else {
|
} else {
|
||||||
resourceControl = authorization.NewPrivateResourceControl(stack.Name, portainer.StackResourceControl, userID)
|
resourceControl = authorization.NewPrivateResourceControl(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.DataStore.ResourceControl().CreateResourceControl(resourceControl)
|
err = handler.DataStore.ResourceControl().CreateResourceControl(resourceControl)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @id StackDelete
|
// @id StackDelete
|
||||||
|
@ -82,7 +83,7 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(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.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
"github.com/portainer/portainer/api/http/errors"
|
"github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stackFileResponse struct {
|
type stackFileResponse struct {
|
||||||
|
@ -57,7 +58,7 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(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.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
"github.com/portainer/portainer/api/http/errors"
|
"github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @id StackInspect
|
// @id StackInspect
|
||||||
|
@ -56,7 +57,7 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user info from request context", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user info from request context", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(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.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package stacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stackMigratePayload struct {
|
type stackMigratePayload struct {
|
||||||
|
@ -76,7 +78,7 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(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.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +124,16 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
|
||||||
stack.Name = payload.Name
|
stack.Name = payload.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUnique, err := handler.checkUniqueName(targetEndpoint, stack.Name, stack.ID, stack.SwarmID != "")
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isUnique {
|
||||||
|
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running on endpoint '%s'", stack.Name, targetEndpoint.Name)
|
||||||
|
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
|
||||||
|
}
|
||||||
|
|
||||||
migrationError := handler.migrateStack(r, stack, targetEndpoint)
|
migrationError := handler.migrateStack(r, stack, targetEndpoint)
|
||||||
if migrationError != nil {
|
if migrationError != nil {
|
||||||
return migrationError
|
return migrationError
|
||||||
|
|
|
@ -2,11 +2,13 @@ package stacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
|
@ -57,7 +59,16 @@ func (handler *Handler) stackStart(w http.ResponseWriter, r *http.Request) *http
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stack.Name, portainer.StackResourceControl)
|
isUnique, err := handler.checkUniqueName(endpoint, stack.Name, stack.ID, stack.SwarmID != "")
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
|
}
|
||||||
|
if !isUnique {
|
||||||
|
errorMessage := fmt.Sprintf("A stack with the name '%s' is already running", stack.Name)
|
||||||
|
return &httperror.HandlerError{http.StatusConflict, errorMessage, errors.New(errorMessage)}
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @id StackStop
|
// @id StackStop
|
||||||
|
@ -56,7 +57,7 @@ func (handler *Handler) stackStop(w http.ResponseWriter, r *http.Request) *httpe
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(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.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type updateComposeStackPayload struct {
|
type updateComposeStackPayload struct {
|
||||||
|
@ -99,7 +100,7 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(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.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
|
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
|
||||||
"github.com/portainer/portainer/api/internal/authorization"
|
"github.com/portainer/portainer/api/internal/authorization"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -117,17 +119,17 @@ func (transport *Transport) getInheritedResourceControlFromServiceOrStack(resour
|
||||||
|
|
||||||
switch resourceType {
|
switch resourceType {
|
||||||
case portainer.ContainerResourceControl:
|
case portainer.ContainerResourceControl:
|
||||||
return getInheritedResourceControlFromContainerLabels(client, resourceIdentifier, resourceControls)
|
return getInheritedResourceControlFromContainerLabels(client, transport.endpoint.ID, resourceIdentifier, resourceControls)
|
||||||
case portainer.NetworkResourceControl:
|
case portainer.NetworkResourceControl:
|
||||||
return getInheritedResourceControlFromNetworkLabels(client, resourceIdentifier, resourceControls)
|
return getInheritedResourceControlFromNetworkLabels(client, transport.endpoint.ID, resourceIdentifier, resourceControls)
|
||||||
case portainer.VolumeResourceControl:
|
case portainer.VolumeResourceControl:
|
||||||
return getInheritedResourceControlFromVolumeLabels(client, resourceIdentifier, resourceControls)
|
return getInheritedResourceControlFromVolumeLabels(client, transport.endpoint.ID, resourceIdentifier, resourceControls)
|
||||||
case portainer.ServiceResourceControl:
|
case portainer.ServiceResourceControl:
|
||||||
return getInheritedResourceControlFromServiceLabels(client, resourceIdentifier, resourceControls)
|
return getInheritedResourceControlFromServiceLabels(client, transport.endpoint.ID, resourceIdentifier, resourceControls)
|
||||||
case portainer.ConfigResourceControl:
|
case portainer.ConfigResourceControl:
|
||||||
return getInheritedResourceControlFromConfigLabels(client, resourceIdentifier, resourceControls)
|
return getInheritedResourceControlFromConfigLabels(client, transport.endpoint.ID, resourceIdentifier, resourceControls)
|
||||||
case portainer.SecretResourceControl:
|
case portainer.SecretResourceControl:
|
||||||
return getInheritedResourceControlFromSecretLabels(client, resourceIdentifier, resourceControls)
|
return getInheritedResourceControlFromSecretLabels(client, transport.endpoint.ID, resourceIdentifier, resourceControls)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -273,8 +275,9 @@ func (transport *Transport) findResourceControl(resourceIdentifier string, resou
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceLabelsObject[resourceLabelForDockerSwarmStackName] != nil {
|
if resourceLabelsObject[resourceLabelForDockerSwarmStackName] != nil {
|
||||||
inheritedSwarmStackIdentifier := resourceLabelsObject[resourceLabelForDockerSwarmStackName].(string)
|
stackName := resourceLabelsObject[resourceLabelForDockerSwarmStackName].(string)
|
||||||
resourceControl = authorization.GetResourceControlByResourceIDAndType(inheritedSwarmStackIdentifier, portainer.StackResourceControl, resourceControls)
|
stackResourceID := stackutils.ResourceControlID(transport.endpoint.ID, stackName)
|
||||||
|
resourceControl = authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls)
|
||||||
|
|
||||||
if resourceControl != nil {
|
if resourceControl != nil {
|
||||||
return resourceControl, nil
|
return resourceControl, nil
|
||||||
|
@ -282,8 +285,9 @@ func (transport *Transport) findResourceControl(resourceIdentifier string, resou
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceLabelsObject[resourceLabelForDockerComposeStackName] != nil {
|
if resourceLabelsObject[resourceLabelForDockerComposeStackName] != nil {
|
||||||
inheritedComposeStackIdentifier := resourceLabelsObject[resourceLabelForDockerComposeStackName].(string)
|
stackName := resourceLabelsObject[resourceLabelForDockerComposeStackName].(string)
|
||||||
resourceControl = authorization.GetResourceControlByResourceIDAndType(inheritedComposeStackIdentifier, portainer.StackResourceControl, resourceControls)
|
stackResourceID := stackutils.ResourceControlID(transport.endpoint.ID, stackName)
|
||||||
|
resourceControl = authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls)
|
||||||
|
|
||||||
if resourceControl != nil {
|
if resourceControl != nil {
|
||||||
return resourceControl, nil
|
return resourceControl, nil
|
||||||
|
@ -296,6 +300,20 @@ func (transport *Transport) findResourceControl(resourceIdentifier string, resou
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getStackResourceIDFromLabels(resourceLabelsObject map[string]string, endpointID portainer.EndpointID) string {
|
||||||
|
if resourceLabelsObject[resourceLabelForDockerSwarmStackName] != "" {
|
||||||
|
stackName := resourceLabelsObject[resourceLabelForDockerSwarmStackName]
|
||||||
|
return stackutils.ResourceControlID(endpointID, stackName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resourceLabelsObject[resourceLabelForDockerComposeStackName] != "" {
|
||||||
|
stackName := resourceLabelsObject[resourceLabelForDockerComposeStackName]
|
||||||
|
return stackutils.ResourceControlID(endpointID, stackName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func decorateObject(object map[string]interface{}, resourceControl *portainer.ResourceControl) map[string]interface{} {
|
func decorateObject(object map[string]interface{}, resourceControl *portainer.ResourceControl) map[string]interface{} {
|
||||||
if object["Portainer"] == nil {
|
if object["Portainer"] == nil {
|
||||||
object["Portainer"] = make(map[string]interface{})
|
object["Portainer"] = make(map[string]interface{})
|
||||||
|
|
|
@ -15,15 +15,15 @@ const (
|
||||||
configObjectIdentifier = "ID"
|
configObjectIdentifier = "ID"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getInheritedResourceControlFromConfigLabels(dockerClient *client.Client, configID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
func getInheritedResourceControlFromConfigLabels(dockerClient *client.Client, endpointID portainer.EndpointID, configID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
||||||
config, _, err := dockerClient.ConfigInspectWithRaw(context.Background(), configID)
|
config, _, err := dockerClient.ConfigInspectWithRaw(context.Background(), configID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
swarmStackName := config.Spec.Labels[resourceLabelForDockerSwarmStackName]
|
stackResourceID := getStackResourceIDFromLabels(config.Spec.Labels, endpointID)
|
||||||
if swarmStackName != "" {
|
if stackResourceID != "" {
|
||||||
return authorization.GetResourceControlByResourceIDAndType(swarmStackName, portainer.StackResourceControl, resourceControls), nil
|
return authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -19,7 +19,7 @@ const (
|
||||||
containerObjectIdentifier = "Id"
|
containerObjectIdentifier = "Id"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getInheritedResourceControlFromContainerLabels(dockerClient *client.Client, containerID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
func getInheritedResourceControlFromContainerLabels(dockerClient *client.Client, endpointID portainer.EndpointID, containerID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
||||||
container, err := dockerClient.ContainerInspect(context.Background(), containerID)
|
container, err := dockerClient.ContainerInspect(context.Background(), containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -33,14 +33,9 @@ func getInheritedResourceControlFromContainerLabels(dockerClient *client.Client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
swarmStackName := container.Config.Labels[resourceLabelForDockerSwarmStackName]
|
stackResourceID := getStackResourceIDFromLabels(container.Config.Labels, endpointID)
|
||||||
if swarmStackName != "" {
|
if stackResourceID != "" {
|
||||||
return authorization.GetResourceControlByResourceIDAndType(swarmStackName, portainer.StackResourceControl, resourceControls), nil
|
return authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls), nil
|
||||||
}
|
|
||||||
|
|
||||||
composeStackName := container.Config.Labels[resourceLabelForDockerComposeStackName]
|
|
||||||
if composeStackName != "" {
|
|
||||||
return authorization.GetResourceControlByResourceIDAndType(composeStackName, portainer.StackResourceControl, resourceControls), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
|
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
|
||||||
"github.com/portainer/portainer/api/internal/authorization"
|
"github.com/portainer/portainer/api/internal/authorization"
|
||||||
)
|
)
|
||||||
|
@ -18,15 +19,15 @@ const (
|
||||||
networkObjectName = "Name"
|
networkObjectName = "Name"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getInheritedResourceControlFromNetworkLabels(dockerClient *client.Client, networkID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
func getInheritedResourceControlFromNetworkLabels(dockerClient *client.Client, endpointID portainer.EndpointID, networkID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
||||||
network, err := dockerClient.NetworkInspect(context.Background(), networkID, types.NetworkInspectOptions{})
|
network, err := dockerClient.NetworkInspect(context.Background(), networkID, types.NetworkInspectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
swarmStackName := network.Labels[resourceLabelForDockerSwarmStackName]
|
stackResourceID := getStackResourceIDFromLabels(network.Labels, endpointID)
|
||||||
if swarmStackName != "" {
|
if stackResourceID != "" {
|
||||||
return authorization.GetResourceControlByResourceIDAndType(swarmStackName, portainer.StackResourceControl, resourceControls), nil
|
return authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -15,15 +15,15 @@ const (
|
||||||
secretObjectIdentifier = "ID"
|
secretObjectIdentifier = "ID"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getInheritedResourceControlFromSecretLabels(dockerClient *client.Client, secretID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
func getInheritedResourceControlFromSecretLabels(dockerClient *client.Client, endpointID portainer.EndpointID, secretID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
||||||
secret, _, err := dockerClient.SecretInspectWithRaw(context.Background(), secretID)
|
secret, _, err := dockerClient.SecretInspectWithRaw(context.Background(), secretID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
swarmStackName := secret.Spec.Labels[resourceLabelForDockerSwarmStackName]
|
stackResourceID := getStackResourceIDFromLabels(secret.Spec.Labels, endpointID)
|
||||||
if swarmStackName != "" {
|
if stackResourceID != "" {
|
||||||
return authorization.GetResourceControlByResourceIDAndType(swarmStackName, portainer.StackResourceControl, resourceControls), nil
|
return authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -20,15 +20,15 @@ const (
|
||||||
serviceObjectIdentifier = "ID"
|
serviceObjectIdentifier = "ID"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getInheritedResourceControlFromServiceLabels(dockerClient *client.Client, serviceID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
func getInheritedResourceControlFromServiceLabels(dockerClient *client.Client, endpointID portainer.EndpointID, serviceID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
||||||
service, _, err := dockerClient.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|
service, _, err := dockerClient.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
swarmStackName := service.Spec.Labels[resourceLabelForDockerSwarmStackName]
|
stackResourceID := getStackResourceIDFromLabels(service.Spec.Labels, endpointID)
|
||||||
if swarmStackName != "" {
|
if stackResourceID != "" {
|
||||||
return authorization.GetResourceControlByResourceIDAndType(swarmStackName, portainer.StackResourceControl, resourceControls), nil
|
return authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
|
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
"github.com/portainer/portainer/api/internal/authorization"
|
"github.com/portainer/portainer/api/internal/authorization"
|
||||||
|
@ -18,15 +18,15 @@ const (
|
||||||
volumeObjectIdentifier = "ID"
|
volumeObjectIdentifier = "ID"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getInheritedResourceControlFromVolumeLabels(dockerClient *client.Client, volumeID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
func getInheritedResourceControlFromVolumeLabels(dockerClient *client.Client, endpointID portainer.EndpointID, volumeID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
||||||
volume, err := dockerClient.VolumeInspect(context.Background(), volumeID)
|
volume, err := dockerClient.VolumeInspect(context.Background(), volumeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
swarmStackName := volume.Labels[resourceLabelForDockerSwarmStackName]
|
stackResourceID := getStackResourceIDFromLabels(volume.Labels, endpointID)
|
||||||
if swarmStackName != "" {
|
if stackResourceID != "" {
|
||||||
return authorization.GetResourceControlByResourceIDAndType(swarmStackName, portainer.StackResourceControl, resourceControls), nil
|
return authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -157,6 +157,7 @@ func (server *Server) Start() error {
|
||||||
|
|
||||||
var stackHandler = stacks.NewHandler(requestBouncer)
|
var stackHandler = stacks.NewHandler(requestBouncer)
|
||||||
stackHandler.DataStore = server.DataStore
|
stackHandler.DataStore = server.DataStore
|
||||||
|
stackHandler.DockerClientFactory = server.DockerClientFactory
|
||||||
stackHandler.FileService = server.FileService
|
stackHandler.FileService = server.FileService
|
||||||
stackHandler.SwarmStackManager = server.SwarmStackManager
|
stackHandler.SwarmStackManager = server.SwarmStackManager
|
||||||
stackHandler.ComposeStackManager = server.ComposeStackManager
|
stackHandler.ComposeStackManager = server.ComposeStackManager
|
||||||
|
|
|
@ -3,7 +3,8 @@ package authorization
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewAdministratorsOnlyResourceControl will create a new administrators only resource control associated to the resource specified by the
|
// NewAdministratorsOnlyResourceControl will create a new administrators only resource control associated to the resource specified by the
|
||||||
|
@ -110,7 +111,7 @@ func NewRestrictedResourceControl(resourceIdentifier string, resourceType portai
|
||||||
func DecorateStacks(stacks []portainer.Stack, resourceControls []portainer.ResourceControl) []portainer.Stack {
|
func DecorateStacks(stacks []portainer.Stack, resourceControls []portainer.ResourceControl) []portainer.Stack {
|
||||||
for idx, stack := range stacks {
|
for idx, stack := range stacks {
|
||||||
|
|
||||||
resourceControl := GetResourceControlByResourceIDAndType(stack.Name, portainer.StackResourceControl, resourceControls)
|
resourceControl := GetResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl, resourceControls)
|
||||||
if resourceControl != nil {
|
if resourceControl != nil {
|
||||||
stacks[idx].ResourceControl = resourceControl
|
stacks[idx].ResourceControl = resourceControl
|
||||||
}
|
}
|
||||||
|
|
12
api/internal/stackutils/stackutils.go
Normal file
12
api/internal/stackutils/stackutils.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package stackutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResourceControlID returns the stack resource control id
|
||||||
|
func ResourceControlID(endpointID portainer.EndpointID, name string) string {
|
||||||
|
return fmt.Sprintf("%d_%s", endpointID, name)
|
||||||
|
}
|
|
@ -1312,7 +1312,7 @@ const (
|
||||||
// APIVersion is the version number of the Portainer API
|
// APIVersion is the version number of the Portainer API
|
||||||
APIVersion = "2.2.0"
|
APIVersion = "2.2.0"
|
||||||
// DBVersion is the version number of the Portainer database
|
// DBVersion is the version number of the Portainer database
|
||||||
DBVersion = 26
|
DBVersion = 27
|
||||||
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
||||||
ComposeSyntaxMaxVersion = "3.9"
|
ComposeSyntaxMaxVersion = "3.9"
|
||||||
// AssetsServerURL represents the URL of the Portainer asset server
|
// AssetsServerURL represents the URL of the Portainer asset server
|
||||||
|
|
|
@ -225,5 +225,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- access-control-panel -->
|
<!-- access-control-panel -->
|
||||||
<por-access-control-panel ng-if="stack" resource-id="stack.Name" resource-control="stack.ResourceControl" resource-type="'stack'"> </por-access-control-panel>
|
<por-access-control-panel ng-if="stack" resource-id="stack.EndpointId + '_' + stack.Name" resource-control="stack.ResourceControl" resource-type="'stack'">
|
||||||
|
</por-access-control-panel>
|
||||||
<!-- !access-control-panel -->
|
<!-- !access-control-panel -->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue