chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
)
|
||||
|
||||
func getTrustedEndpoints(tx dataservices.DataStoreTx, endpointIDs roar.Roar[portainer.EndpointID]) ([]portainer.EndpointID, error) {
|
||||
var innerErr error
|
||||
|
||||
results := []portainer.EndpointID{}
|
||||
|
||||
endpointIDs.Iterate(func(endpointID portainer.EndpointID) bool {
|
||||
endpoint, err := tx.Endpoint().Endpoint(endpointID)
|
||||
if err != nil {
|
||||
innerErr = err
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if !endpoint.UserTrusted {
|
||||
return true
|
||||
}
|
||||
|
||||
results = append(results, endpoint.ID)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return results, innerErr
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
)
|
||||
|
||||
type edgeGroupCreatePayload struct {
|
||||
Name string
|
||||
Dynamic bool
|
||||
TagIDs []portainer.TagID
|
||||
Endpoints []portainer.EndpointID
|
||||
PartialMatch bool
|
||||
}
|
||||
|
||||
func (payload *edgeGroupCreatePayload) Validate(r *http.Request) error {
|
||||
if len(payload.Name) == 0 {
|
||||
return errors.New("invalid Edge group name")
|
||||
}
|
||||
|
||||
if payload.Dynamic && len(payload.TagIDs) == 0 {
|
||||
return errors.New("tagIDs is mandatory for a dynamic Edge group")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateEndpointsOrTags(tx dataservices.DataStoreTx, edgeGroup *portainer.EdgeGroup, endpoints []portainer.EndpointID, tagIDs []portainer.TagID) error {
|
||||
if edgeGroup.Dynamic {
|
||||
edgeGroup.TagIDs = tagIDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
endpointIDs := []portainer.EndpointID{}
|
||||
|
||||
for _, endpointID := range endpoints {
|
||||
endpoint, err := tx.Endpoint().Endpoint(endpointID)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve environment from the database", err)
|
||||
}
|
||||
|
||||
if endpointutils.IsEdgeEndpoint(endpoint) {
|
||||
endpointIDs = append(endpointIDs, endpoint.ID)
|
||||
}
|
||||
}
|
||||
|
||||
edgeGroup.Endpoints = endpointIDs
|
||||
edgeGroup.EndpointIDs = roar.FromSlice(endpointIDs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// @id EdgeGroupCreate
|
||||
// @summary Create an EdgeGroup
|
||||
// @description **Access policy**: administrator
|
||||
// @tags edge_groups
|
||||
// @security ApiKeyAuth
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param body body edgeGroupCreatePayload true "EdgeGroup data"
|
||||
// @success 200 {object} portainer.EdgeGroup
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500
|
||||
// @router /edge_groups [post]
|
||||
func (handler *Handler) edgeGroupCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload edgeGroupCreatePayload
|
||||
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
|
||||
return httperror.BadRequest("Invalid request payload", err)
|
||||
}
|
||||
|
||||
var shadowEdgeGroup shadowedEdgeGroup
|
||||
err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
edgeGroups, err := tx.EdgeGroup().ReadAll()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve Edge groups from the database", err)
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
edgeGroup := &portainer.EdgeGroup{
|
||||
Name: payload.Name,
|
||||
Dynamic: payload.Dynamic,
|
||||
TagIDs: []portainer.TagID{},
|
||||
Endpoints: []portainer.EndpointID{},
|
||||
EndpointIDs: roar.Roar[portainer.EndpointID]{},
|
||||
PartialMatch: payload.PartialMatch,
|
||||
}
|
||||
|
||||
if err := calculateEndpointsOrTags(tx, edgeGroup, payload.Endpoints, payload.TagIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.EdgeGroup().Create(edgeGroup); err != nil {
|
||||
return httperror.InternalServerError("Unable to persist the Edge group inside the database", err)
|
||||
}
|
||||
|
||||
shadowEdgeGroup = shadowedEdgeGroup{EdgeGroup: *edgeGroup}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return response.TxResponse(w, shadowEdgeGroup, err)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEdgeGroupCreateHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
handler, _ := newHandlerWithEdgeEndpoints(t)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/edge_groups",
|
||||
strings.NewReader(`{"Name": "New Edge Group", "Endpoints": [1, 2, 3]}`),
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err := json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
|
||||
}
|
||||
|
||||
func TestEdgeGroupCreatePanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EdgeGroup().Create(&portainer.EdgeGroup{ID: 1, Name: "New Edge Group"})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost,
|
||||
"/edge_groups",
|
||||
strings.NewReader(`{"Name": "New Edge Group", "Endpoints": [1, 2, 3]}`),
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusBadRequest, rr.Result().StatusCode)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
)
|
||||
|
||||
// @id EdgeGroupDelete
|
||||
// @summary Deletes an EdgeGroup
|
||||
// @description **Access policy**: administrator
|
||||
// @tags edge_groups
|
||||
// @security ApiKeyAuth
|
||||
// @security jwt
|
||||
// @param id path int true "EdgeGroup Id"
|
||||
// @success 204
|
||||
// @failure 409 "Edge group is in use by an Edge stack or Edge job"
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500 "Server error"
|
||||
// @router /edge_groups/{id} [delete]
|
||||
func (handler *Handler) edgeGroupDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return httperror.BadRequest("Invalid Edge group identifier route variable", err)
|
||||
}
|
||||
|
||||
err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return deleteEdgeGroup(tx, portainer.EdgeGroupID(edgeGroupID))
|
||||
})
|
||||
|
||||
return response.TxEmptyResponse(w, err)
|
||||
}
|
||||
|
||||
func deleteEdgeGroup(tx dataservices.DataStoreTx, ID portainer.EdgeGroupID) error {
|
||||
ok, err := tx.EdgeGroup().Exists(ID)
|
||||
if !ok {
|
||||
return httperror.NotFound("Unable to find an Edge group with the specified identifier inside the database", dserrors.ErrObjectNotFound)
|
||||
} else if err != nil {
|
||||
return httperror.InternalServerError("Unable to find an Edge group with the specified identifier inside the database", err)
|
||||
}
|
||||
|
||||
edgeStacks, err := tx.EdgeStack().EdgeStacks()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve Edge stacks from the database", err)
|
||||
}
|
||||
|
||||
for _, edgeStack := range edgeStacks {
|
||||
if slices.Contains(edgeStack.EdgeGroups, ID) {
|
||||
return httperror.Conflict("Edge group is used by an Edge stack", errors.New("edge group is used by an Edge stack"))
|
||||
}
|
||||
}
|
||||
|
||||
edgeJobs, err := tx.EdgeJob().ReadAll()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve Edge jobs from the database", err)
|
||||
}
|
||||
|
||||
for _, edgeJob := range edgeJobs {
|
||||
if slices.Contains(edgeJob.EdgeGroups, ID) {
|
||||
return httperror.Conflict("Edge group is used by an Edge job", errors.New("edge group is used by an Edge job"))
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.EdgeGroup().Delete(ID)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to remove the Edge group from the database", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
)
|
||||
|
||||
// @id EdgeGroupInspect
|
||||
// @summary Inspects an EdgeGroup
|
||||
// @description **Access policy**: administrator
|
||||
// @tags edge_groups
|
||||
// @security ApiKeyAuth
|
||||
// @security jwt
|
||||
// @produce json
|
||||
// @param id path int true "EdgeGroup Id"
|
||||
// @success 200 {object} portainer.EdgeGroup
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500
|
||||
// @router /edge_groups/{id} [get]
|
||||
func (handler *Handler) edgeGroupInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return httperror.BadRequest("Invalid Edge group identifier route variable", err)
|
||||
}
|
||||
|
||||
var shadowEdgeGroup shadowedEdgeGroup
|
||||
err = handler.DataStore.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
edgeGroup, err := getEdgeGroup(tx, portainer.EdgeGroupID(edgeGroupID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edgeGroup.Endpoints = edgeGroup.EndpointIDs.ToSlice()
|
||||
|
||||
shadowEdgeGroup = shadowedEdgeGroup{EdgeGroup: *edgeGroup}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return response.TxResponse(w, shadowEdgeGroup, err)
|
||||
}
|
||||
|
||||
func getEdgeGroup(tx dataservices.DataStoreTx, ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error) {
|
||||
edgeGroup, err := tx.EdgeGroup().Read(ID)
|
||||
if tx.IsErrObjectNotFound(err) {
|
||||
return nil, httperror.NotFound("Unable to find an Edge group with the specified identifier inside the database", err)
|
||||
} else if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to find an Edge group with the specified identifier inside the database", err)
|
||||
}
|
||||
|
||||
if edgeGroup.Dynamic {
|
||||
endpoints, err := endpointutils.GetEndpointsByTags(tx, edgeGroup.TagIDs, edgeGroup.PartialMatch)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve environments and environment groups for Edge group", err)
|
||||
}
|
||||
|
||||
edgeGroup.EndpointIDs = roar.FromSlice(endpoints)
|
||||
}
|
||||
|
||||
return edgeGroup, err
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newHandlerWithEdgeEndpoints(t *testing.T) (*Handler, *datastore.Store) {
|
||||
t.Helper()
|
||||
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EndpointGroup().Create(&portainer.EndpointGroup{
|
||||
ID: 1,
|
||||
Name: "Test Group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := range 3 {
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "Test Endpoint " + strconv.Itoa(i+1),
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EndpointRelation().Create(&portainer.EndpointRelation{
|
||||
EndpointID: portainer.EndpointID(i + 1),
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
return handler, store
|
||||
}
|
||||
|
||||
func TestEdgeGroupInspectHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
handler, store := newHandlerWithEdgeEndpoints(t)
|
||||
|
||||
err := store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1, 2, 3}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/edge_groups/1",
|
||||
nil,
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
|
||||
}
|
||||
|
||||
func TestEmptyEdgeGroupInspectHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EndpointGroup().Create(&portainer.EndpointGroup{
|
||||
ID: 1,
|
||||
Name: "Test Group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
EndpointIDs: roar.Roar[portainer.EndpointID]{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/edge_groups/1",
|
||||
nil,
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make sure the frontend does not get a null value but a [] instead
|
||||
require.NotNil(t, responseGroup.Endpoints)
|
||||
require.Empty(t, responseGroup.Endpoints)
|
||||
}
|
||||
|
||||
func TestDynamicEdgeGroupInspectHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EndpointGroup().Create(&portainer.EndpointGroup{
|
||||
ID: 1,
|
||||
Name: "Test Group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.Tag().Create(&portainer.Tag{
|
||||
ID: 1,
|
||||
Name: "Test Tag",
|
||||
Endpoints: map[portainer.EndpointID]bool{
|
||||
1: true,
|
||||
2: true,
|
||||
3: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := range 3 {
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "Test Endpoint " + strconv.Itoa(i+1),
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: 1,
|
||||
TagIDs: []portainer.TagID{1},
|
||||
UserTrusted: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EndpointRelation().Create(&portainer.EndpointRelation{
|
||||
EndpointID: portainer.EndpointID(i + 1),
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
Dynamic: true,
|
||||
TagIDs: []portainer.TagID{1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/edge_groups/1",
|
||||
nil,
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
|
||||
}
|
||||
|
||||
func TestEdgeGroupInspectPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/edge_groups/1", nil)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusNotFound, rr.Result().StatusCode)
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
)
|
||||
|
||||
type shadowedEdgeGroup struct {
|
||||
portainer.EdgeGroup
|
||||
EndpointIds int `json:"EndpointIds,omitempty"` // Shadow to avoid exposing in the API
|
||||
}
|
||||
|
||||
type decoratedEdgeGroup struct {
|
||||
shadowedEdgeGroup
|
||||
HasEdgeStack bool `json:"HasEdgeStack"`
|
||||
HasEdgeJob bool `json:"HasEdgeJob"`
|
||||
EndpointTypes []portainer.EndpointType
|
||||
TrustedEndpoints []portainer.EndpointID `json:"TrustedEndpoints"`
|
||||
}
|
||||
|
||||
// @id EdgeGroupList
|
||||
// @summary list EdgeGroups
|
||||
// @description **Access policy**: administrator
|
||||
// @tags edge_groups
|
||||
// @security ApiKeyAuth
|
||||
// @security jwt
|
||||
// @produce json
|
||||
// @success 200 {array} decoratedEdgeGroup "EdgeGroups"
|
||||
// @failure 500
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @router /edge_groups [get]
|
||||
func (handler *Handler) edgeGroupList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var decoratedEdgeGroups []decoratedEdgeGroup
|
||||
var err error
|
||||
|
||||
err = handler.DataStore.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
decoratedEdgeGroups, err = getEdgeGroupList(tx)
|
||||
return err
|
||||
})
|
||||
|
||||
return response.TxResponse(w, decoratedEdgeGroups, err)
|
||||
}
|
||||
|
||||
func getEdgeGroupList(tx dataservices.DataStoreTx) ([]decoratedEdgeGroup, error) {
|
||||
edgeGroups, err := tx.EdgeGroup().ReadAll()
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve Edge groups from the database", err)
|
||||
}
|
||||
|
||||
edgeStacks, err := tx.EdgeStack().EdgeStacks()
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve Edge stacks from the database", err)
|
||||
}
|
||||
|
||||
usedEdgeGroups := make(map[portainer.EdgeGroupID]bool)
|
||||
|
||||
for _, stack := range edgeStacks {
|
||||
for _, groupID := range stack.EdgeGroups {
|
||||
usedEdgeGroups[groupID] = true
|
||||
}
|
||||
}
|
||||
|
||||
edgeJobs, err := tx.EdgeJob().ReadAll()
|
||||
if err != nil {
|
||||
return nil, 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, orgEdgeGroup.ID) {
|
||||
usedByEdgeJob = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
edgeGroup := decoratedEdgeGroup{
|
||||
shadowedEdgeGroup: shadowedEdgeGroup{EdgeGroup: orgEdgeGroup},
|
||||
EndpointTypes: []portainer.EndpointType{},
|
||||
}
|
||||
if edgeGroup.Dynamic {
|
||||
endpointIDs, err := endpointutils.GetEndpointsByTags(tx, edgeGroup.TagIDs, edgeGroup.PartialMatch)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve environments and environment groups for Edge group", err)
|
||||
}
|
||||
|
||||
edgeGroup.Endpoints = endpointIDs
|
||||
edgeGroup.TrustedEndpoints = endpointIDs
|
||||
} else {
|
||||
trustedEndpoints, err := getTrustedEndpoints(tx, edgeGroup.EndpointIDs)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve environments for Edge group", err)
|
||||
}
|
||||
|
||||
edgeGroup.Endpoints = edgeGroup.EndpointIDs.ToSlice()
|
||||
edgeGroup.TrustedEndpoints = trustedEndpoints
|
||||
}
|
||||
|
||||
endpointTypes, err := getEndpointTypes(tx, edgeGroup.EndpointIDs)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve environment types for Edge group", err)
|
||||
}
|
||||
|
||||
edgeGroup.EndpointTypes = endpointTypes
|
||||
edgeGroup.HasEdgeStack = usedEdgeGroups[edgeGroup.ID]
|
||||
edgeGroup.HasEdgeJob = usedByEdgeJob
|
||||
|
||||
decoratedEdgeGroups = append(decoratedEdgeGroups, edgeGroup)
|
||||
}
|
||||
|
||||
return decoratedEdgeGroups, nil
|
||||
}
|
||||
|
||||
func getEndpointTypes(tx dataservices.DataStoreTx, endpointIds roar.Roar[portainer.EndpointID]) ([]portainer.EndpointType, error) {
|
||||
var innerErr error
|
||||
|
||||
typeSet := map[portainer.EndpointType]bool{}
|
||||
|
||||
endpointIds.Iterate(func(endpointID portainer.EndpointID) bool {
|
||||
endpoint, err := tx.Endpoint().Endpoint(endpointID)
|
||||
if err != nil {
|
||||
innerErr = fmt.Errorf("failed fetching environment: %w", err)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
typeSet[endpoint.Type] = true
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if innerErr != nil {
|
||||
return nil, innerErr
|
||||
}
|
||||
|
||||
endpointTypes := make([]portainer.EndpointType, 0, len(typeSet))
|
||||
for endpointType := range typeSet {
|
||||
endpointTypes = append(endpointTypes, endpointType)
|
||||
}
|
||||
|
||||
return endpointTypes, nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_getEndpointTypes(t *testing.T) {
|
||||
t.Parallel()
|
||||
endpoints := []portainer.Endpoint{
|
||||
{ID: 1, Type: portainer.DockerEnvironment},
|
||||
{ID: 2, Type: portainer.AgentOnDockerEnvironment},
|
||||
{ID: 3, Type: portainer.AzureEnvironment},
|
||||
{ID: 4, Type: portainer.EdgeAgentOnDockerEnvironment},
|
||||
{ID: 5, Type: portainer.KubernetesLocalEnvironment},
|
||||
{ID: 6, Type: portainer.AgentOnKubernetesEnvironment},
|
||||
{ID: 7, Type: portainer.EdgeAgentOnKubernetesEnvironment},
|
||||
}
|
||||
|
||||
datastore := testhelpers.NewDatastore(testhelpers.WithEndpoints(endpoints))
|
||||
|
||||
tests := []struct {
|
||||
endpointIds []portainer.EndpointID
|
||||
expected []portainer.EndpointType
|
||||
}{
|
||||
{endpointIds: []portainer.EndpointID{1}, expected: []portainer.EndpointType{portainer.DockerEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{2}, expected: []portainer.EndpointType{portainer.AgentOnDockerEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{3}, expected: []portainer.EndpointType{portainer.AzureEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{4}, expected: []portainer.EndpointType{portainer.EdgeAgentOnDockerEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{5}, expected: []portainer.EndpointType{portainer.KubernetesLocalEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{6}, expected: []portainer.EndpointType{portainer.AgentOnKubernetesEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{7}, expected: []portainer.EndpointType{portainer.EdgeAgentOnKubernetesEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{7, 2}, expected: []portainer.EndpointType{portainer.EdgeAgentOnKubernetesEnvironment, portainer.AgentOnDockerEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{6, 4, 1}, expected: []portainer.EndpointType{portainer.AgentOnKubernetesEnvironment, portainer.EdgeAgentOnDockerEnvironment, portainer.DockerEnvironment}},
|
||||
{endpointIds: []portainer.EndpointID{1, 2, 3}, expected: []portainer.EndpointType{portainer.DockerEnvironment, portainer.AgentOnDockerEnvironment, portainer.AzureEnvironment}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ans, err := getEndpointTypes(datastore, roar.FromSlice(test.endpointIds))
|
||||
require.NoError(t, err, "getEndpointTypes shouldn't fail")
|
||||
|
||||
assert.ElementsMatch(t, test.expected, ans, "getEndpointTypes expected to return %b for %v, but returned %b", test.expected, test.endpointIds, ans)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getEndpointTypes_failWhenEndpointDontExist(t *testing.T) {
|
||||
t.Parallel()
|
||||
datastore := testhelpers.NewDatastore(testhelpers.WithEndpoints([]portainer.Endpoint{}))
|
||||
|
||||
_, err := getEndpointTypes(datastore, roar.FromSlice([]portainer.EndpointID{1}))
|
||||
require.Error(t, err, "getEndpointTypes should fail")
|
||||
}
|
||||
|
||||
func TestEdgeGroupListHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
handler, store := newHandlerWithEdgeEndpoints(t)
|
||||
|
||||
err := store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1, 2, 3}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/edge_groups",
|
||||
nil,
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroups []decoratedEdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroups)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, responseGroups, 1)
|
||||
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroups[0].Endpoints)
|
||||
require.Empty(t, responseGroups[0].TrustedEndpoints)
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/edge"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/slicesx"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
)
|
||||
|
||||
type edgeGroupUpdatePayload struct {
|
||||
Name string
|
||||
Dynamic bool
|
||||
TagIDs []portainer.TagID
|
||||
Endpoints []portainer.EndpointID
|
||||
PartialMatch *bool
|
||||
}
|
||||
|
||||
func (payload *edgeGroupUpdatePayload) Validate(r *http.Request) error {
|
||||
if payload.Dynamic && len(payload.TagIDs) == 0 {
|
||||
return errors.New("tagIDs is mandatory for a dynamic Edge group")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// @id EdgeGroupUpdate
|
||||
// @summary Updates an EdgeGroup
|
||||
// @description **Access policy**: administrator
|
||||
// @tags edge_groups
|
||||
// @security ApiKeyAuth
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param id path int true "EdgeGroup Id"
|
||||
// @param body body edgeGroupUpdatePayload true "EdgeGroup data"
|
||||
// @success 200 {object} portainer.EdgeGroup
|
||||
// @failure 503 "Edge compute features are disabled"
|
||||
// @failure 500
|
||||
// @router /edge_groups/{id} [put]
|
||||
func (handler *Handler) edgeGroupUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
edgeGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return httperror.BadRequest("Invalid Edge group identifier route variable", err)
|
||||
}
|
||||
|
||||
var payload edgeGroupUpdatePayload
|
||||
if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
|
||||
return httperror.BadRequest("Invalid request payload", err)
|
||||
}
|
||||
|
||||
var shadowEdgeGroup shadowedEdgeGroup
|
||||
err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
edgeGroup, err := tx.EdgeGroup().Read(portainer.EdgeGroupID(edgeGroupID))
|
||||
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||
return httperror.NotFound("Unable to find an Edge group with the specified identifier inside the database", err)
|
||||
} else if err != nil {
|
||||
return httperror.InternalServerError("Unable to find an Edge group with the specified identifier inside the database", err)
|
||||
}
|
||||
|
||||
edgeGroups, err := tx.EdgeGroup().ReadAll()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve Edge groups from the database", err)
|
||||
}
|
||||
|
||||
if payload.Name != "" {
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
edgeGroup.Name = payload.Name
|
||||
}
|
||||
|
||||
endpoints, err := tx.Endpoint().Endpoints()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve environments from database", err)
|
||||
}
|
||||
|
||||
endpointGroups, err := tx.EndpointGroup().ReadAll()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve environment groups from database", err)
|
||||
}
|
||||
|
||||
oldRelatedEndpoints := edge.EdgeGroupRelatedEndpoints(edgeGroup, endpoints, endpointGroups)
|
||||
|
||||
edgeGroup.Dynamic = payload.Dynamic
|
||||
if err := calculateEndpointsOrTags(tx, edgeGroup, payload.Endpoints, payload.TagIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if payload.PartialMatch != nil {
|
||||
edgeGroup.PartialMatch = *payload.PartialMatch
|
||||
}
|
||||
|
||||
if err := tx.EdgeGroup().Update(edgeGroup.ID, edgeGroup); err != nil {
|
||||
return httperror.InternalServerError("Unable to persist Edge group changes inside the database", err)
|
||||
}
|
||||
|
||||
newRelatedEndpoints := edge.EdgeGroupRelatedEndpoints(edgeGroup, endpoints, endpointGroups)
|
||||
endpointsToUpdate := slicesx.Unique(append(newRelatedEndpoints, oldRelatedEndpoints...))
|
||||
|
||||
edgeJobs, err := tx.EdgeJob().ReadAll()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to fetch Edge jobs", err)
|
||||
}
|
||||
|
||||
edgeStacks, err := tx.EdgeStack().EdgeStacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the edgeGroups with the modified edgeGroup for updateEndpointStacks()
|
||||
for i := range edgeGroups {
|
||||
if edgeGroups[i].ID == edgeGroup.ID {
|
||||
edgeGroups[i] = *edgeGroup
|
||||
}
|
||||
}
|
||||
|
||||
for _, endpointID := range endpointsToUpdate {
|
||||
endpoint, err := tx.Endpoint().Endpoint(endpointID)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to get Environment from database", err)
|
||||
}
|
||||
|
||||
if err := handler.updateEndpointStacks(tx, endpoint, edgeGroups, edgeStacks); err != nil {
|
||||
return httperror.InternalServerError("Unable to persist Environment relation changes inside the database", err)
|
||||
}
|
||||
|
||||
if !endpointutils.IsEdgeEndpoint(endpoint) {
|
||||
continue
|
||||
}
|
||||
|
||||
var operation string
|
||||
if slices.Contains(newRelatedEndpoints, endpointID) && slices.Contains(oldRelatedEndpoints, endpointID) {
|
||||
continue
|
||||
} else if slices.Contains(newRelatedEndpoints, endpointID) {
|
||||
operation = "add"
|
||||
} else if slices.Contains(oldRelatedEndpoints, endpointID) {
|
||||
operation = "remove"
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := handler.updateEndpointEdgeJobs(edgeGroup.ID, endpoint, edgeJobs, operation); err != nil {
|
||||
return httperror.InternalServerError("Unable to persist Environment Edge Jobs changes inside the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
shadowEdgeGroup = shadowedEdgeGroup{EdgeGroup: *edgeGroup}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return response.TxResponse(w, shadowEdgeGroup, err)
|
||||
}
|
||||
|
||||
func (handler *Handler) updateEndpointStacks(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint, edgeGroups []portainer.EdgeGroup, edgeStacks []portainer.EdgeStack) error {
|
||||
relation, err := tx.EndpointRelation().EndpointRelation(endpoint.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
endpointGroup, err := tx.EndpointGroup().Read(endpoint.GroupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edgeStackSet := map[portainer.EdgeStackID]bool{}
|
||||
|
||||
endpointEdgeStacks := edge.EndpointRelatedEdgeStacks(endpoint, endpointGroup, edgeGroups, edgeStacks)
|
||||
for _, edgeStackID := range endpointEdgeStacks {
|
||||
edgeStackSet[edgeStackID] = true
|
||||
}
|
||||
|
||||
relation.EdgeStacks = edgeStackSet
|
||||
|
||||
return tx.EndpointRelation().UpdateEndpointRelation(endpoint.ID, relation)
|
||||
}
|
||||
|
||||
func (handler *Handler) updateEndpointEdgeJobs(edgeGroupID portainer.EdgeGroupID, endpoint *portainer.Endpoint, edgeJobs []portainer.EdgeJob, operation string) error {
|
||||
for _, edgeJob := range edgeJobs {
|
||||
if !slices.Contains(edgeJob.EdgeGroups, edgeGroupID) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch operation {
|
||||
case "add", "remove":
|
||||
cache.Del(endpoint.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEdgeGroupUpdateHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
handler, store := newHandlerWithEdgeEndpoints(t)
|
||||
|
||||
err := store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPut,
|
||||
"/edge_groups/1",
|
||||
strings.NewReader(`{"Endpoints": [1, 2, 3]}`),
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
|
||||
}
|
||||
|
||||
func TestEdgeGroupUpdatePanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/edge_groups/1", strings.NewReader("{}"))
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusNotFound, rr.Result().StatusCode)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle environment(endpoint) group operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
DataStore dataservices.DataStore
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage environment(endpoint) group operations.
|
||||
func NewHandler(bouncer security.BouncerService) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/edge_groups",
|
||||
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeGroupCreate)))).Methods(http.MethodPost)
|
||||
h.Handle("/edge_groups",
|
||||
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeGroupList)))).Methods(http.MethodGet)
|
||||
h.Handle("/edge_groups/{id}",
|
||||
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeGroupInspect)))).Methods(http.MethodGet)
|
||||
h.Handle("/edge_groups/{id}",
|
||||
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeGroupUpdate)))).Methods(http.MethodPut)
|
||||
h.Handle("/edge_groups/{id}",
|
||||
bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeGroupDelete)))).Methods(http.MethodDelete)
|
||||
|
||||
return h
|
||||
}
|
||||
Reference in New Issue
Block a user