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"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/http/middlewares"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
|
@ -24,10 +25,11 @@ type Handler struct {
|
|||
}
|
||||
|
||||
// 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{
|
||||
Router: mux.NewRouter(),
|
||||
requestBouncer: bouncer,
|
||||
DataStore: dataStore,
|
||||
}
|
||||
h.Handle("/edge_stacks",
|
||||
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)
|
||||
h.Handle("/edge_stacks/{id}/status",
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -139,8 +139,7 @@ func (server *Server) Start() error {
|
|||
edgeJobsHandler.FileService = server.FileService
|
||||
edgeJobsHandler.ReverseTunnelService = server.ReverseTunnelService
|
||||
|
||||
var edgeStacksHandler = edgestacks.NewHandler(requestBouncer)
|
||||
edgeStacksHandler.DataStore = server.DataStore
|
||||
var edgeStacksHandler = edgestacks.NewHandler(requestBouncer, server.DataStore)
|
||||
edgeStacksHandler.FileService = server.FileService
|
||||
edgeStacksHandler.GitService = server.GitService
|
||||
edgeStacksHandler.KubernetesDeployer = server.KubernetesDeployer
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import _ from 'lodash-es';
|
||||
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
|
||||
|
||||
export class EdgeGroupFormController {
|
||||
/* @ngInject */
|
||||
|
@ -17,6 +18,7 @@ export class EdgeGroupFormController {
|
|||
};
|
||||
|
||||
this.associateEndpoint = this.associateEndpoint.bind(this);
|
||||
this.dissociateEndpointAsync = this.dissociateEndpointAsync.bind(this);
|
||||
this.dissociateEndpoint = this.dissociateEndpoint.bind(this);
|
||||
this.getDynamicEndpointsAsync = this.getDynamicEndpointsAsync.bind(this);
|
||||
this.getDynamicEndpoints = this.getDynamicEndpoints.bind(this);
|
||||
|
@ -39,6 +41,29 @@ export class EdgeGroupFormController {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import { PortainerEndpointTypes } from '@/portainer/models/endpoint/models';
|
|||
import { EndpointSecurityFormData } from '@/portainer/components/endpointSecurity/porEndpointSecurityModel';
|
||||
import EndpointHelper from '@/portainer/helpers/endpointHelper';
|
||||
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);
|
||||
|
||||
|
@ -105,7 +107,7 @@ function EndpointController(
|
|||
});
|
||||
}
|
||||
|
||||
$scope.updateEndpoint = function () {
|
||||
$scope.updateEndpoint = async function () {
|
||||
var endpoint = $scope.endpoint;
|
||||
var securityData = $scope.formValues.SecurityFormData;
|
||||
var TLS = securityData.TLS;
|
||||
|
@ -113,6 +115,27 @@ function EndpointController(
|
|||
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || 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 = {
|
||||
Name: endpoint.Name,
|
||||
PublicURL: endpoint.PublicURL,
|
||||
|
@ -229,6 +252,7 @@ function EndpointController(
|
|||
}
|
||||
|
||||
$scope.endpoint = endpoint;
|
||||
$scope.initialTagIds = endpoint.TagIds.slice();
|
||||
$scope.groups = groups;
|
||||
$scope.availableTags = tags;
|
||||
|
||||
|
|
Loading…
Reference in New Issue