1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-25 08:19:40 +02:00

feat(edgejobs): support edge groups when using edge jobs EE-3873 (#8099)

This commit is contained in:
matias-portainer 2022-12-19 18:54:51 -03:00 committed by GitHub
parent 9732d1b5d8
commit e1b474d04f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 544 additions and 83 deletions

View file

@ -23,13 +23,13 @@ type edgeGroupCreatePayload struct {
func (payload *edgeGroupCreatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Name) {
return errors.New("Invalid Edge group name")
return errors.New("invalid Edge group name")
}
if payload.Dynamic && (payload.TagIDs == nil || len(payload.TagIDs) == 0) {
return errors.New("TagIDs is mandatory for a dynamic Edge group")
return errors.New("tagIDs is mandatory for a dynamic Edge group")
}
if !payload.Dynamic && (payload.Endpoints == nil || len(payload.Endpoints) == 0) {
return errors.New("Environment is mandatory for a static Edge group")
return errors.New("environment is mandatory for a static Edge group")
}
return nil
}
@ -61,7 +61,7 @@ func (handler *Handler) edgeGroupCreate(w http.ResponseWriter, r *http.Request)
for _, edgeGroup := range edgeGroups {
if edgeGroup.Name == payload.Name {
return httperror.BadRequest("Edge group name must be unique", errors.New("Edge group name must be unique"))
return httperror.BadRequest("Edge group name must be unique", errors.New("edge group name must be unique"))
}
}

View file

@ -42,7 +42,20 @@ func (handler *Handler) edgeGroupDelete(w http.ResponseWriter, r *http.Request)
for _, edgeStack := range edgeStacks {
for _, groupID := range edgeStack.EdgeGroups {
if groupID == portainer.EdgeGroupID(edgeGroupID) {
return httperror.Forbidden("Edge group is used by an Edge stack", errors.New("Edge group is used by an Edge stack"))
return httperror.NewError(http.StatusConflict, "Edge group is used by an Edge stack", errors.New("edge group is used by an Edge stack"))
}
}
}
edgeJobs, err := handler.DataStore.EdgeJob().EdgeJobs()
if err != nil {
return httperror.InternalServerError("Unable to retrieve Edge jobs from the database", err)
}
for _, edgeJob := range edgeJobs {
for _, groupID := range edgeJob.EdgeGroups {
if groupID == portainer.EdgeGroupID(edgeGroupID) {
return httperror.NewError(http.StatusConflict, "Edge group is used by an Edge job", errors.New("edge group is used by an Edge job"))
}
}
}

View file

@ -8,11 +8,13 @@ import (
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/slices"
)
type decoratedEdgeGroup struct {
portainer.EdgeGroup
HasEdgeStack bool `json:"HasEdgeStack"`
HasEdgeGroup bool `json:"HasEdgeGroup"`
EndpointTypes []portainer.EndpointType
}
@ -46,8 +48,21 @@ func (handler *Handler) edgeGroupList(w http.ResponseWriter, r *http.Request) *h
}
}
edgeJobs, err := handler.DataStore.EdgeJob().EdgeJobs()
if err != nil {
return httperror.InternalServerError("Unable to retrieve Edge jobs from the database", err)
}
decoratedEdgeGroups := []decoratedEdgeGroup{}
for _, orgEdgeGroup := range edgeGroups {
usedByEdgeJob := false
for _, edgeJob := range edgeJobs {
if slices.Contains(edgeJob.EdgeGroups, portainer.EdgeGroupID(orgEdgeGroup.ID)) {
usedByEdgeJob = true
break
}
}
edgeGroup := decoratedEdgeGroup{
EdgeGroup: orgEdgeGroup,
EndpointTypes: []portainer.EndpointType{},
@ -63,13 +78,15 @@ func (handler *Handler) edgeGroupList(w http.ResponseWriter, r *http.Request) *h
endpointTypes, err := getEndpointTypes(handler.DataStore.Endpoint(), edgeGroup.Endpoints)
if err != nil {
return httperror.InternalServerError("Unable to retrieve endpoint types for Edge group", err)
return httperror.InternalServerError("Unable to retrieve environment types for Edge group", err)
}
edgeGroup.EndpointTypes = endpointTypes
edgeGroup.HasEdgeStack = usedEdgeGroups[edgeGroup.ID]
edgeGroup.HasEdgeGroup = usedByEdgeJob
decoratedEdgeGroups = append(decoratedEdgeGroups, edgeGroup)
}

View file

@ -10,6 +10,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/edge"
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/portainer/portainer/api/internal/slices"
"github.com/asaskevich/govalidator"
)
@ -24,13 +25,13 @@ type edgeGroupUpdatePayload struct {
func (payload *edgeGroupUpdatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Name) {
return errors.New("Invalid Edge group name")
return errors.New("invalid Edge group name")
}
if payload.Dynamic && (payload.TagIDs == nil || len(payload.TagIDs) == 0) {
return errors.New("TagIDs is mandatory for a dynamic Edge group")
return errors.New("tagIDs is mandatory for a dynamic Edge group")
}
if !payload.Dynamic && (payload.Endpoints == nil || len(payload.Endpoints) == 0) {
return errors.New("Environments is mandatory for a static Edge group")
return errors.New("environments is mandatory for a static Edge group")
}
return nil
}
@ -75,7 +76,7 @@ func (handler *Handler) edgeGroupUpdate(w http.ResponseWriter, r *http.Request)
}
for _, edgeGroup := range edgeGroups {
if edgeGroup.Name == payload.Name && edgeGroup.ID != portainer.EdgeGroupID(edgeGroupID) {
return httperror.BadRequest("Edge group name must be unique", errors.New("Edge group name must be unique"))
return httperror.BadRequest("Edge group name must be unique", errors.New("edge group name must be unique"))
}
}
@ -123,17 +124,45 @@ func (handler *Handler) edgeGroupUpdate(w http.ResponseWriter, r *http.Request)
newRelatedEndpoints := edge.EdgeGroupRelatedEndpoints(edgeGroup, endpoints, endpointGroups)
endpointsToUpdate := append(newRelatedEndpoints, oldRelatedEndpoints...)
edgeJobs, err := handler.DataStore.EdgeJob().EdgeJobs()
if err != nil {
return httperror.InternalServerError("Unable to fetch Edge jobs", err)
}
for _, endpointID := range endpointsToUpdate {
err = handler.updateEndpoint(endpointID)
err = handler.updateEndpointStacks(endpointID)
if err != nil {
return httperror.InternalServerError("Unable to persist Environment relation changes inside the database", err)
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(endpointID)
if err != nil {
return httperror.InternalServerError("Unable to get Environment from database", err)
}
if !endpointutils.IsEdgeEndpoint(endpoint) {
continue
}
var operation string
if slices.Contains(newRelatedEndpoints, endpointID) {
operation = "add"
} else if slices.Contains(oldRelatedEndpoints, endpointID) {
operation = "remove"
} else {
continue
}
err = handler.updateEndpointEdgeJobs(edgeGroup.ID, endpointID, edgeJobs, operation)
if err != nil {
return httperror.InternalServerError("Unable to persist Environment Edge Jobs changes inside the database", err)
}
}
return response.JSON(w, edgeGroup)
}
func (handler *Handler) updateEndpoint(endpointID portainer.EndpointID) error {
func (handler *Handler) updateEndpointStacks(endpointID portainer.EndpointID) error {
relation, err := handler.DataStore.EndpointRelation().EndpointRelation(endpointID)
if err != nil {
return err
@ -170,3 +199,20 @@ func (handler *Handler) updateEndpoint(endpointID portainer.EndpointID) error {
return handler.DataStore.EndpointRelation().UpdateEndpointRelation(endpoint.ID, relation)
}
func (handler *Handler) updateEndpointEdgeJobs(edgeGroupID portainer.EdgeGroupID, endpointID portainer.EndpointID, edgeJobs []portainer.EdgeJob, operation string) error {
for _, edgeJob := range edgeJobs {
if !slices.Contains(edgeJob.EdgeGroups, edgeGroupID) {
continue
}
switch operation {
case "add":
handler.ReverseTunnelService.AddEdgeJob(endpointID, &edgeJob)
case "remove":
handler.ReverseTunnelService.RemoveEdgeJobFromEndpoint(endpointID, edgeJob.ID)
}
}
return nil
}

View file

@ -5,6 +5,7 @@ import (
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/security"
)
@ -12,7 +13,8 @@ import (
// Handler is the HTTP handler used to handle environment(endpoint) group operations.
type Handler struct {
*mux.Router
DataStore dataservices.DataStore
DataStore dataservices.DataStore
ReverseTunnelService portainer.ReverseTunnelService
}
// NewHandler creates a handler to manage environment(endpoint) group operations.