mirror of https://github.com/portainer/portainer
fix(edge-stacks): add an endpoint to delete the status of an edge stack EE-2432 (#6551)
parent
61a3bfe994
commit
8a6024ce9b
|
@ -0,0 +1,64 @@
|
||||||
|
package edgestacks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
"github.com/portainer/libhttp/request"
|
||||||
|
"github.com/portainer/libhttp/response"
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/http/middlewares"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (handler *Handler) handlerDBErr(err error, msg string) *httperror.HandlerError {
|
||||||
|
httpErr := &httperror.HandlerError{http.StatusInternalServerError, msg, err}
|
||||||
|
|
||||||
|
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||||
|
httpErr.StatusCode = http.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// @id EdgeStackStatusDelete
|
||||||
|
// @summary Delete an EdgeStack status
|
||||||
|
// @description Authorized only if the request is done by an Edge Environment(Endpoint)
|
||||||
|
// @tags edge_stacks
|
||||||
|
// @produce json
|
||||||
|
// @param id path string true "EdgeStack Id"
|
||||||
|
// @success 200 {object} portainer.EdgeStack
|
||||||
|
// @failure 500
|
||||||
|
// @failure 400
|
||||||
|
// @failure 404
|
||||||
|
// @failure 403
|
||||||
|
// @router /edge_stacks/{id}/status/{endpoint_id} [delete]
|
||||||
|
func (handler *Handler) edgeStackStatusDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
|
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid stack identifier route variable", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint, err := middlewares.FetchEndpoint(r)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a valid endpoint from the handler context", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access environment", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
stack, err := handler.DataStore.EdgeStack().EdgeStack(portainer.EdgeStackID(stackID))
|
||||||
|
if err != nil {
|
||||||
|
return handler.handlerDBErr(err, "Unable to find a stack with the specified identifier inside the database")
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(stack.Status, endpoint.ID)
|
||||||
|
|
||||||
|
err = handler.DataStore.EdgeStack().UpdateEdgeStack(stack.ID, stack)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack changes inside the database", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.JSON(w, stack)
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ 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/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
|
"github.com/portainer/portainer/api/http/middlewares"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,10 +25,11 @@ type Handler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a handler to manage environment(endpoint) group operations.
|
// NewHandler creates a handler to manage environment(endpoint) group operations.
|
||||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
func NewHandler(bouncer *security.RequestBouncer, dataStore dataservices.DataStore) *Handler {
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
Router: mux.NewRouter(),
|
Router: mux.NewRouter(),
|
||||||
requestBouncer: bouncer,
|
requestBouncer: bouncer,
|
||||||
|
DataStore: dataStore,
|
||||||
}
|
}
|
||||||
h.Handle("/edge_stacks",
|
h.Handle("/edge_stacks",
|
||||||
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeStackCreate)))).Methods(http.MethodPost)
|
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeStackCreate)))).Methods(http.MethodPost)
|
||||||
|
@ -43,6 +45,12 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeStackFile)))).Methods(http.MethodGet)
|
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeStackFile)))).Methods(http.MethodGet)
|
||||||
h.Handle("/edge_stacks/{id}/status",
|
h.Handle("/edge_stacks/{id}/status",
|
||||||
bouncer.PublicAccess(httperror.LoggerHandler(h.edgeStackStatusUpdate))).Methods(http.MethodPut)
|
bouncer.PublicAccess(httperror.LoggerHandler(h.edgeStackStatusUpdate))).Methods(http.MethodPut)
|
||||||
|
|
||||||
|
edgeStackStatusRouter := h.NewRoute().Subrouter()
|
||||||
|
edgeStackStatusRouter.Use(middlewares.WithEndpoint(h.DataStore.Endpoint(), "endpoint_id"))
|
||||||
|
|
||||||
|
edgeStackStatusRouter.PathPrefix("/edge_stacks/{id}/status/{endpoint_id}").Handler(bouncer.PublicAccess(httperror.LoggerHandler(h.edgeStackStatusDelete))).Methods(http.MethodDelete)
|
||||||
|
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,8 +139,7 @@ func (server *Server) Start() error {
|
||||||
edgeJobsHandler.FileService = server.FileService
|
edgeJobsHandler.FileService = server.FileService
|
||||||
edgeJobsHandler.ReverseTunnelService = server.ReverseTunnelService
|
edgeJobsHandler.ReverseTunnelService = server.ReverseTunnelService
|
||||||
|
|
||||||
var edgeStacksHandler = edgestacks.NewHandler(requestBouncer)
|
var edgeStacksHandler = edgestacks.NewHandler(requestBouncer, server.DataStore)
|
||||||
edgeStacksHandler.DataStore = server.DataStore
|
|
||||||
edgeStacksHandler.FileService = server.FileService
|
edgeStacksHandler.FileService = server.FileService
|
||||||
edgeStacksHandler.GitService = server.GitService
|
edgeStacksHandler.GitService = server.GitService
|
||||||
edgeStacksHandler.KubernetesDeployer = server.KubernetesDeployer
|
edgeStacksHandler.KubernetesDeployer = server.KubernetesDeployer
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
|
||||||
|
|
||||||
export class EdgeGroupFormController {
|
export class EdgeGroupFormController {
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
@ -17,6 +18,7 @@ export class EdgeGroupFormController {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.associateEndpoint = this.associateEndpoint.bind(this);
|
this.associateEndpoint = this.associateEndpoint.bind(this);
|
||||||
|
this.dissociateEndpointAsync = this.dissociateEndpointAsync.bind(this);
|
||||||
this.dissociateEndpoint = this.dissociateEndpoint.bind(this);
|
this.dissociateEndpoint = this.dissociateEndpoint.bind(this);
|
||||||
this.getDynamicEndpointsAsync = this.getDynamicEndpointsAsync.bind(this);
|
this.getDynamicEndpointsAsync = this.getDynamicEndpointsAsync.bind(this);
|
||||||
this.getDynamicEndpoints = this.getDynamicEndpoints.bind(this);
|
this.getDynamicEndpoints = this.getDynamicEndpoints.bind(this);
|
||||||
|
@ -39,6 +41,29 @@ export class EdgeGroupFormController {
|
||||||
}
|
}
|
||||||
|
|
||||||
dissociateEndpoint(endpoint) {
|
dissociateEndpoint(endpoint) {
|
||||||
|
return this.$async(this.dissociateEndpointAsync, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
async dissociateEndpointAsync(endpoint) {
|
||||||
|
const confirmed = await confirmAsync({
|
||||||
|
title: 'Confirm action',
|
||||||
|
message: 'Removing the environment from this group will remove its corresponding edge stacks',
|
||||||
|
buttons: {
|
||||||
|
cancel: {
|
||||||
|
label: 'Cancel',
|
||||||
|
className: 'btn-default',
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
label: 'Confirm',
|
||||||
|
className: 'btn-primary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.model.Endpoints = _.filter(this.model.Endpoints, (id) => id !== endpoint.Id);
|
this.model.Endpoints = _.filter(this.model.Endpoints, (id) => id !== endpoint.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { PortainerEndpointTypes } from '@/portainer/models/endpoint/models';
|
||||||
import { EndpointSecurityFormData } from '@/portainer/components/endpointSecurity/porEndpointSecurityModel';
|
import { EndpointSecurityFormData } from '@/portainer/components/endpointSecurity/porEndpointSecurityModel';
|
||||||
import EndpointHelper from '@/portainer/helpers/endpointHelper';
|
import EndpointHelper from '@/portainer/helpers/endpointHelper';
|
||||||
import { getAMTInfo } from 'Portainer/hostmanagement/open-amt/open-amt.service';
|
import { getAMTInfo } from 'Portainer/hostmanagement/open-amt/open-amt.service';
|
||||||
|
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
|
||||||
|
import { isEdgeEnvironment } from '@/portainer/environments/utils';
|
||||||
|
|
||||||
angular.module('portainer.app').controller('EndpointController', EndpointController);
|
angular.module('portainer.app').controller('EndpointController', EndpointController);
|
||||||
|
|
||||||
|
@ -105,7 +107,7 @@ function EndpointController(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.updateEndpoint = function () {
|
$scope.updateEndpoint = async function () {
|
||||||
var endpoint = $scope.endpoint;
|
var endpoint = $scope.endpoint;
|
||||||
var securityData = $scope.formValues.SecurityFormData;
|
var securityData = $scope.formValues.SecurityFormData;
|
||||||
var TLS = securityData.TLS;
|
var TLS = securityData.TLS;
|
||||||
|
@ -113,6 +115,27 @@ function EndpointController(
|
||||||
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
|
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
|
||||||
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
|
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
|
||||||
|
|
||||||
|
if (isEdgeEnvironment(endpoint.Type) && _.difference($scope.initialTagIds, endpoint.TagIds).length > 0) {
|
||||||
|
let confirmed = await confirmAsync({
|
||||||
|
title: 'Confirm action',
|
||||||
|
message: 'Removing tags from this environment will remove the corresponding edge stacks when dynamic grouping is being used',
|
||||||
|
buttons: {
|
||||||
|
cancel: {
|
||||||
|
label: 'Cancel',
|
||||||
|
className: 'btn-default',
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
label: 'Confirm',
|
||||||
|
className: 'btn-primary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
Name: endpoint.Name,
|
Name: endpoint.Name,
|
||||||
PublicURL: endpoint.PublicURL,
|
PublicURL: endpoint.PublicURL,
|
||||||
|
@ -229,6 +252,7 @@ function EndpointController(
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.endpoint = endpoint;
|
$scope.endpoint = endpoint;
|
||||||
|
$scope.initialTagIds = endpoint.TagIds.slice();
|
||||||
$scope.groups = groups;
|
$scope.groups = groups;
|
||||||
$scope.availableTags = tags;
|
$scope.availableTags = tags;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue