mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 07:49:41 +02:00
feat(stacks): support compose v2.0 stack (#1963)
This commit is contained in:
parent
ef15cd30eb
commit
e3d564325b
174 changed files with 7898 additions and 5849 deletions
31
api/http/handler/resourcecontrols/handler.go
Normal file
31
api/http/handler/resourcecontrols/handler.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package resourcecontrols
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle resource control operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
ResourceControlService portainer.ResourceControlService
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage resource control operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/resource_controls",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.resourceControlCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/resource_controls/{id}",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.resourceControlUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/resource_controls/{id}",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.resourceControlDelete))).Methods(http.MethodDelete)
|
||||
|
||||
return h
|
||||
}
|
116
api/http/handler/resourcecontrols/resourcecontrol_create.go
Normal file
116
api/http/handler/resourcecontrols/resourcecontrol_create.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package resourcecontrols
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
type resourceControlCreatePayload struct {
|
||||
ResourceID string
|
||||
Type string
|
||||
AdministratorsOnly bool
|
||||
Users []int
|
||||
Teams []int
|
||||
SubResourceIDs []string
|
||||
}
|
||||
|
||||
func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
|
||||
if govalidator.IsNull(payload.ResourceID) {
|
||||
return portainer.Error("Invalid resource identifier")
|
||||
}
|
||||
|
||||
if govalidator.IsNull(payload.Type) {
|
||||
return portainer.Error("Invalid type")
|
||||
}
|
||||
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// POST request on /api/resource_controls
|
||||
func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload resourceControlCreatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
var resourceControlType portainer.ResourceControlType
|
||||
switch payload.Type {
|
||||
case "container":
|
||||
resourceControlType = portainer.ContainerResourceControl
|
||||
case "service":
|
||||
resourceControlType = portainer.ServiceResourceControl
|
||||
case "volume":
|
||||
resourceControlType = portainer.VolumeResourceControl
|
||||
case "network":
|
||||
resourceControlType = portainer.NetworkResourceControl
|
||||
case "secret":
|
||||
resourceControlType = portainer.SecretResourceControl
|
||||
case "stack":
|
||||
resourceControlType = portainer.StackResourceControl
|
||||
case "config":
|
||||
resourceControlType = portainer.ConfigResourceControl
|
||||
default:
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid type value. Value must be one of: container, service, volume, network, secret, stack or config", portainer.ErrInvalidResourceControlType}
|
||||
}
|
||||
|
||||
rc, err := handler.ResourceControlService.ResourceControlByResourceID(payload.ResourceID)
|
||||
if err != nil && err != portainer.ErrResourceControlNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve resource controls from the database", err}
|
||||
}
|
||||
if rc != nil {
|
||||
return &httperror.HandlerError{http.StatusConflict, "A resource control is already associated to this resource", portainer.ErrResourceControlAlreadyExists}
|
||||
}
|
||||
|
||||
var userAccesses = make([]portainer.UserResourceAccess, 0)
|
||||
for _, v := range payload.Users {
|
||||
userAccess := portainer.UserResourceAccess{
|
||||
UserID: portainer.UserID(v),
|
||||
AccessLevel: portainer.ReadWriteAccessLevel,
|
||||
}
|
||||
userAccesses = append(userAccesses, userAccess)
|
||||
}
|
||||
|
||||
var teamAccesses = make([]portainer.TeamResourceAccess, 0)
|
||||
for _, v := range payload.Teams {
|
||||
teamAccess := portainer.TeamResourceAccess{
|
||||
TeamID: portainer.TeamID(v),
|
||||
AccessLevel: portainer.ReadWriteAccessLevel,
|
||||
}
|
||||
teamAccesses = append(teamAccesses, teamAccess)
|
||||
}
|
||||
|
||||
resourceControl := portainer.ResourceControl{
|
||||
ResourceID: payload.ResourceID,
|
||||
SubResourceIDs: payload.SubResourceIDs,
|
||||
Type: resourceControlType,
|
||||
AdministratorsOnly: payload.AdministratorsOnly,
|
||||
UserAccesses: userAccesses,
|
||||
TeamAccesses: teamAccesses,
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if !security.AuthorizedResourceControlCreation(&resourceControl, securityContext) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to create a resource control for the specified resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
err = handler.ResourceControlService.CreateResourceControl(&resourceControl)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the resource control inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, resourceControl)
|
||||
}
|
42
api/http/handler/resourcecontrols/resourcecontrol_delete.go
Normal file
42
api/http/handler/resourcecontrols/resourcecontrol_delete.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package resourcecontrols
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
// DELETE request on /api/resource_controls/:id
|
||||
func (handler *Handler) resourceControlDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
resourceControlID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid resource control identifier route variable", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControl(portainer.ResourceControlID(resourceControlID))
|
||||
if err == portainer.ErrResourceControlNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a resource control with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a resource control with with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if !security.AuthorizedResourceControlDeletion(resourceControl, securityContext) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to delete the resource control", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
err = handler.ResourceControlService.DeleteResourceControl(portainer.ResourceControlID(resourceControlID))
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the resource control from the database", err}
|
||||
}
|
||||
|
||||
return response.Empty(w)
|
||||
}
|
83
api/http/handler/resourcecontrols/resourcecontrol_update.go
Normal file
83
api/http/handler/resourcecontrols/resourcecontrol_update.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package resourcecontrols
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/request"
|
||||
"github.com/portainer/portainer/http/response"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
type resourceControlUpdatePayload struct {
|
||||
AdministratorsOnly bool
|
||||
Users []int
|
||||
Teams []int
|
||||
}
|
||||
|
||||
func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT request on /api/resource_controls/:id
|
||||
func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
resourceControlID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid resource control identifier route variable", err}
|
||||
}
|
||||
|
||||
var payload resourceControlUpdatePayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControl(portainer.ResourceControlID(resourceControlID))
|
||||
if err == portainer.ErrResourceControlNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a resource control with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a resource control with with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
resourceControl.AdministratorsOnly = payload.AdministratorsOnly
|
||||
|
||||
var userAccesses = make([]portainer.UserResourceAccess, 0)
|
||||
for _, v := range payload.Users {
|
||||
userAccess := portainer.UserResourceAccess{
|
||||
UserID: portainer.UserID(v),
|
||||
AccessLevel: portainer.ReadWriteAccessLevel,
|
||||
}
|
||||
userAccesses = append(userAccesses, userAccess)
|
||||
}
|
||||
resourceControl.UserAccesses = userAccesses
|
||||
|
||||
var teamAccesses = make([]portainer.TeamResourceAccess, 0)
|
||||
for _, v := range payload.Teams {
|
||||
teamAccess := portainer.TeamResourceAccess{
|
||||
TeamID: portainer.TeamID(v),
|
||||
AccessLevel: portainer.ReadWriteAccessLevel,
|
||||
}
|
||||
teamAccesses = append(teamAccesses, teamAccess)
|
||||
}
|
||||
resourceControl.TeamAccesses = teamAccesses
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if !security.AuthorizedResourceControlUpdate(resourceControl, securityContext) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the resource control", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
err = handler.ResourceControlService.UpdateResourceControl(resourceControl.ID, resourceControl)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist resource control changes inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, resourceControl)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue