mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
Merge branch 'develop' into feat/EE-809/EE-466/kube-advanced-apps
This commit is contained in:
commit
0979e6ec8f
97 changed files with 1155 additions and 314 deletions
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
endpointutils "github.com/portainer/portainer/api/internal/endpoint"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
snapshotutils "github.com/portainer/portainer/api/internal/snapshot"
|
||||
)
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
endpointutils "github.com/portainer/portainer/api/internal/endpoint"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
)
|
||||
|
||||
// GET request on /endpoints/{id}/registries?namespace
|
||||
|
|
|
@ -48,8 +48,8 @@ type Handler struct {
|
|||
EndpointGroupHandler *endpointgroups.Handler
|
||||
EndpointHandler *endpoints.Handler
|
||||
EndpointProxyHandler *endpointproxy.Handler
|
||||
FileHandler *file.Handler
|
||||
KubernetesHandler *kubernetes.Handler
|
||||
FileHandler *file.Handler
|
||||
MOTDHandler *motd.Handler
|
||||
RegistryHandler *registries.Handler
|
||||
ResourceControlHandler *resourcecontrols.Handler
|
||||
|
|
|
@ -1,28 +1,68 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/middlewares"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler which will natively deal with to external endpoints.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
DataStore portainer.DataStore
|
||||
KubernetesClientFactory *cli.ClientFactory
|
||||
dataStore portainer.DataStore
|
||||
kubernetesClientFactory *cli.ClientFactory
|
||||
authorizationService *authorization.Service
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to process pre-proxied requests to external APIs.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
func NewHandler(bouncer *security.RequestBouncer, authorizationService *authorization.Service, dataStore portainer.DataStore, kubernetesClientFactory *cli.ClientFactory) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
Router: mux.NewRouter(),
|
||||
dataStore: dataStore,
|
||||
kubernetesClientFactory: kubernetesClientFactory,
|
||||
authorizationService: authorizationService,
|
||||
}
|
||||
h.PathPrefix("/kubernetes/{id}/config").Handler(
|
||||
|
||||
kubeRouter := h.PathPrefix("/kubernetes/{id}").Subrouter()
|
||||
|
||||
kubeRouter.Use(bouncer.AuthenticatedAccess)
|
||||
kubeRouter.Use(middlewares.WithEndpoint(dataStore.Endpoint(), "id"))
|
||||
kubeRouter.Use(kubeOnlyMiddleware)
|
||||
|
||||
kubeRouter.PathPrefix("/config").Handler(
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.getKubernetesConfig))).Methods(http.MethodGet)
|
||||
|
||||
// namespaces
|
||||
// in the future this piece of code might be in another package (or a few different packages - namespaces/namespace?)
|
||||
// to keep it simple, we've decided to leave it like this.
|
||||
namespaceRouter := kubeRouter.PathPrefix("/namespaces/{namespace}").Subrouter()
|
||||
namespaceRouter.Handle("/system", bouncer.RestrictedAccess(httperror.LoggerHandler(h.namespacesToggleSystem))).Methods(http.MethodPut)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func kubeOnlyMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
|
||||
endpoint, err := middlewares.FetchEndpoint(request)
|
||||
if err != nil {
|
||||
httperror.WriteError(rw, http.StatusInternalServerError, "Unable to find an endpoint on request context", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !endpointutils.IsKubernetesEndpoint(endpoint) {
|
||||
errMessage := "Endpoint is not a kubernetes endpoint"
|
||||
httperror.WriteError(rw, http.StatusBadRequest, errMessage, errors.New(errMessage))
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, request)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func (handler *Handler) getKubernetesConfig(w http.ResponseWriter, r *http.Reque
|
|||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
||||
}
|
||||
|
||||
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
||||
endpoint, err := handler.dataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
||||
if err == bolterrors.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
|
@ -56,7 +56,7 @@ func (handler *Handler) getKubernetesConfig(w http.ResponseWriter, r *http.Reque
|
|||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint)
|
||||
cli, err := handler.kubernetesClientFactory.GetKubeClient(endpoint)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create Kubernetes client", err}
|
||||
}
|
||||
|
|
65
api/http/handler/kubernetes/namespaces_toggle_system.go
Normal file
65
api/http/handler/kubernetes/namespaces_toggle_system.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api/http/middlewares"
|
||||
)
|
||||
|
||||
type namespacesToggleSystemPayload struct {
|
||||
// Toggle the system state of this namespace to true or false
|
||||
System bool `example:"true"`
|
||||
}
|
||||
|
||||
func (payload *namespacesToggleSystemPayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// @id KubernetesNamespacesToggleSystem
|
||||
// @summary Toggle the system state for a namespace
|
||||
// @description Toggle the system state for a namespace
|
||||
// @description **Access policy**: administrator or endpoint admin
|
||||
// @security jwt
|
||||
// @tags kubernetes
|
||||
// @accept json
|
||||
// @param id path int true "Endpoint identifier"
|
||||
// @param namespace path string true "Namespace name"
|
||||
// @param body body namespacesToggleSystemPayload true "Update details"
|
||||
// @success 200 "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 404 "Endpoint not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /kubernetes/{id}/namespaces/{namespace}/system [put]
|
||||
func (handler *Handler) namespacesToggleSystem(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpoint, err := middlewares.FetchEndpoint(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint on request context", err}
|
||||
}
|
||||
|
||||
namespaceName, err := request.RetrieveRouteVariableValue(r, "namespace")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid namespace identifier route variable", err}
|
||||
}
|
||||
|
||||
var payload namespacesToggleSystemPayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
kubeClient, err := handler.kubernetesClientFactory.GetKubeClient(endpoint)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create kubernetes client", err}
|
||||
}
|
||||
|
||||
err = kubeClient.ToggleSystemState(namespaceName, payload.System)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to toggle system status", err}
|
||||
}
|
||||
|
||||
return response.Empty(rw)
|
||||
|
||||
}
|
|
@ -86,17 +86,12 @@ func (handler *Handler) websocketShellPodExec(w http.ResponseWriter, r *http.Req
|
|||
return nil
|
||||
}
|
||||
|
||||
serviceAccountToken, isAdminToken, err := handler.getToken(r, endpoint, false)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to get user service account token", err}
|
||||
}
|
||||
|
||||
handlerErr := handler.hijackPodExecStartOperation(
|
||||
w,
|
||||
r,
|
||||
cli,
|
||||
serviceAccountToken,
|
||||
isAdminToken,
|
||||
"",
|
||||
true,
|
||||
endpoint,
|
||||
shellPod.Namespace,
|
||||
shellPod.PodName,
|
||||
|
|
58
api/http/middlewares/endpoint.go
Normal file
58
api/http/middlewares/endpoint.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
requesthelpers "github.com/portainer/libhttp/request"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
contextEndpoint = "endpoint"
|
||||
)
|
||||
|
||||
func WithEndpoint(endpointService portainer.EndpointService, endpointIDParam string) mux.MiddlewareFunc {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
|
||||
if endpointIDParam == "" {
|
||||
endpointIDParam = "id"
|
||||
}
|
||||
|
||||
endpointID, err := requesthelpers.RetrieveNumericRouteVariableValue(request, endpointIDParam)
|
||||
if err != nil {
|
||||
httperror.WriteError(rw, http.StatusBadRequest, "Invalid endpoint identifier route variable", err)
|
||||
return
|
||||
}
|
||||
|
||||
endpoint, err := endpointService.Endpoint(portainer.EndpointID(endpointID))
|
||||
if err != nil {
|
||||
statusCode := http.StatusInternalServerError
|
||||
|
||||
if err == bolterrors.ErrObjectNotFound {
|
||||
statusCode = http.StatusNotFound
|
||||
}
|
||||
httperror.WriteError(rw, statusCode, "Unable to find an endpoint with the specified identifier inside the database", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(request.Context(), contextEndpoint, endpoint)
|
||||
|
||||
next.ServeHTTP(rw, request.WithContext(ctx))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FetchEndpoint(request *http.Request) (*portainer.Endpoint, error) {
|
||||
contextData := request.Context().Value(contextEndpoint)
|
||||
if contextData == nil {
|
||||
return nil, errors.New("Unable to find endpoint data in request context")
|
||||
}
|
||||
|
||||
return contextData.(*portainer.Endpoint), nil
|
||||
}
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/portainer/portainer/api/http/handler/endpointproxy"
|
||||
"github.com/portainer/portainer/api/http/handler/endpoints"
|
||||
"github.com/portainer/portainer/api/http/handler/file"
|
||||
kube "github.com/portainer/portainer/api/http/handler/kubernetes"
|
||||
kubehandler "github.com/portainer/portainer/api/http/handler/kubernetes"
|
||||
"github.com/portainer/portainer/api/http/handler/motd"
|
||||
"github.com/portainer/portainer/api/http/handler/registries"
|
||||
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
|
||||
|
@ -160,11 +160,9 @@ func (server *Server) Start() error {
|
|||
endpointProxyHandler.ProxyManager = server.ProxyManager
|
||||
endpointProxyHandler.ReverseTunnelService = server.ReverseTunnelService
|
||||
|
||||
var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public"))
|
||||
var kubernetesHandler = kubehandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.KubernetesClientFactory)
|
||||
|
||||
var kubernetesHandler = kube.NewHandler(requestBouncer)
|
||||
kubernetesHandler.DataStore = server.DataStore
|
||||
kubernetesHandler.KubernetesClientFactory = server.KubernetesClientFactory
|
||||
var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public"))
|
||||
|
||||
var motdHandler = motd.NewHandler(requestBouncer)
|
||||
|
||||
|
@ -244,8 +242,8 @@ func (server *Server) Start() error {
|
|||
EndpointHandler: endpointHandler,
|
||||
EndpointEdgeHandler: endpointEdgeHandler,
|
||||
EndpointProxyHandler: endpointProxyHandler,
|
||||
FileHandler: fileHandler,
|
||||
KubernetesHandler: kubernetesHandler,
|
||||
FileHandler: fileHandler,
|
||||
MOTDHandler: motdHandler,
|
||||
RegistryHandler: registryHandler,
|
||||
ResourceControlHandler: resourceControlHandler,
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package endpoint
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
// IsKubernetesEndpoint returns true if this is a kubernetes endpoint
|
||||
func IsKubernetesEndpoint(endpoint *portainer.Endpoint) bool {
|
||||
return endpoint.Type == portainer.KubernetesLocalEnvironment ||
|
||||
endpoint.Type == portainer.AgentOnKubernetesEnvironment ||
|
||||
endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment
|
||||
}
|
||||
|
||||
// IsDockerEndpoint returns true if this is a docker endpoint
|
||||
func IsDockerEndpoint(endpoint *portainer.Endpoint) bool {
|
||||
return endpoint.Type == portainer.DockerEnvironment ||
|
||||
endpoint.Type == portainer.AgentOnDockerEnvironment ||
|
||||
endpoint.Type == portainer.EdgeAgentOnDockerEnvironment
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// IsLocalEndpoint returns true if this is a local endpoint
|
||||
func IsLocalEndpoint(endpoint *portainer.Endpoint) bool {
|
||||
return strings.HasPrefix(endpoint.URL, "unix://") || strings.HasPrefix(endpoint.URL, "npipe://") || endpoint.Type == 5
|
||||
}
|
||||
|
|
73
api/kubernetes/cli/namespace.go
Normal file
73
api/kubernetes/cli/namespace.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
systemNamespaceLabel = "io.portainer.kubernetes.namespace.system"
|
||||
)
|
||||
|
||||
func defaultSystemNamespaces() map[string]struct{} {
|
||||
return map[string]struct{}{
|
||||
"kube-system": {},
|
||||
"kube-public": {},
|
||||
"kube-node-lease": {},
|
||||
"portainer": {},
|
||||
}
|
||||
}
|
||||
|
||||
func isSystemNamespace(namespace v1.Namespace) bool {
|
||||
systemLabelValue, hasSystemLabel := namespace.Labels[systemNamespaceLabel]
|
||||
if hasSystemLabel {
|
||||
return systemLabelValue == "true"
|
||||
}
|
||||
|
||||
systemNamespaces := defaultSystemNamespaces()
|
||||
|
||||
_, isSystem := systemNamespaces[namespace.Name]
|
||||
|
||||
return isSystem
|
||||
}
|
||||
|
||||
// ToggleSystemState will set a namespace as a system namespace, or remove this state
|
||||
// if isSystem is true it will set `systemNamespaceLabel` to "true" and false otherwise
|
||||
// this will skip if namespace is "default" or if the required state is already set
|
||||
func (kcl *KubeClient) ToggleSystemState(namespaceName string, isSystem bool) error {
|
||||
if namespaceName == "default" {
|
||||
return nil
|
||||
}
|
||||
|
||||
nsService := kcl.cli.CoreV1().Namespaces()
|
||||
|
||||
namespace, err := nsService.Get(namespaceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed fetching namespace object")
|
||||
}
|
||||
|
||||
if isSystemNamespace(*namespace) == isSystem {
|
||||
return nil
|
||||
}
|
||||
|
||||
if namespace.Labels == nil {
|
||||
namespace.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
namespace.Labels[systemNamespaceLabel] = strconv.FormatBool(isSystem)
|
||||
|
||||
_, err = nsService.Update(namespace)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed updating namespace object")
|
||||
}
|
||||
|
||||
if isSystem {
|
||||
return kcl.NamespaceAccessPoliciesDeleteNamespace(namespaceName)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
185
api/kubernetes/cli/namespace_test.go
Normal file
185
api/kubernetes/cli/namespace_test.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
core "k8s.io/api/core/v1"
|
||||
ktypes "k8s.io/api/core/v1"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kfake "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func Test_ToggleSystemState(t *testing.T) {
|
||||
t.Run("should skip is default (exit without error)", func(t *testing.T) {
|
||||
nsName := "default"
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(&core.Namespace{ObjectMeta: meta.ObjectMeta{Name: nsName}}),
|
||||
instanceID: "instance",
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
err := kcl.ToggleSystemState(nsName, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ns, err := kcl.cli.CoreV1().Namespaces().Get(nsName, meta.GetOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, exists := ns.Labels[systemNamespaceLabel]
|
||||
assert.False(t, exists, "system label should not exists")
|
||||
})
|
||||
|
||||
t.Run("should fail if namespace doesn't exist", func(t *testing.T) {
|
||||
nsName := "not-exist"
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(),
|
||||
instanceID: "instance",
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
err := kcl.ToggleSystemState(nsName, true)
|
||||
assert.Error(t, err)
|
||||
|
||||
})
|
||||
|
||||
t.Run("if called with the same state, should skip (exit without error)", func(t *testing.T) {
|
||||
nsName := "namespace"
|
||||
tests := []struct {
|
||||
isSystem bool
|
||||
}{
|
||||
{isSystem: true},
|
||||
{isSystem: false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(strconv.FormatBool(test.isSystem), func(t *testing.T) {
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(&core.Namespace{ObjectMeta: meta.ObjectMeta{Name: nsName, Labels: map[string]string{
|
||||
systemNamespaceLabel: strconv.FormatBool(test.isSystem),
|
||||
}}}),
|
||||
instanceID: "instance",
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
err := kcl.ToggleSystemState(nsName, test.isSystem)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ns, err := kcl.cli.CoreV1().Namespaces().Get(nsName, meta.GetOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.isSystem, isSystemNamespace(*ns))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for regular namespace if isSystem is true and doesn't have a label, should set the label to true", func(t *testing.T) {
|
||||
nsName := "namespace"
|
||||
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(&core.Namespace{ObjectMeta: meta.ObjectMeta{Name: nsName}}),
|
||||
instanceID: "instance",
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
err := kcl.ToggleSystemState(nsName, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ns, err := kcl.cli.CoreV1().Namespaces().Get(nsName, meta.GetOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
labelValue, exists := ns.Labels[systemNamespaceLabel]
|
||||
assert.True(t, exists, "system label should exists")
|
||||
|
||||
assert.Equal(t, "true", labelValue)
|
||||
})
|
||||
|
||||
t.Run("for default system namespace if isSystem is false and doesn't have a label, should set the label to false", func(t *testing.T) {
|
||||
nsName := "portainer"
|
||||
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(&core.Namespace{ObjectMeta: meta.ObjectMeta{Name: nsName}}),
|
||||
instanceID: "instance",
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
err := kcl.ToggleSystemState(nsName, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ns, err := kcl.cli.CoreV1().Namespaces().Get(nsName, meta.GetOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
labelValue, exists := ns.Labels[systemNamespaceLabel]
|
||||
assert.True(t, exists, "system label should exists")
|
||||
|
||||
assert.Equal(t, "false", labelValue)
|
||||
})
|
||||
|
||||
t.Run("for system namespace (with label), if called with false, should set the label", func(t *testing.T) {
|
||||
nsName := "namespace"
|
||||
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(&core.Namespace{ObjectMeta: meta.ObjectMeta{Name: nsName, Labels: map[string]string{
|
||||
systemNamespaceLabel: "true",
|
||||
}}}),
|
||||
instanceID: "instance",
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
err := kcl.ToggleSystemState(nsName, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ns, err := kcl.cli.CoreV1().Namespaces().Get(nsName, meta.GetOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
labelValue, exists := ns.Labels[systemNamespaceLabel]
|
||||
assert.True(t, exists, "system label should exists")
|
||||
assert.Equal(t, "false", labelValue)
|
||||
})
|
||||
|
||||
t.Run("for non system namespace (with label), if called with true, should set the label, and remove accesses", func(t *testing.T) {
|
||||
nsName := "ns1"
|
||||
|
||||
namespace := &core.Namespace{ObjectMeta: meta.ObjectMeta{Name: nsName, Labels: map[string]string{
|
||||
systemNamespaceLabel: "false",
|
||||
}}}
|
||||
|
||||
config := &ktypes.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: portainerConfigMapName,
|
||||
Namespace: portainerNamespace,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"NamespaceAccessPolicies": `{"ns1":{"UserAccessPolicies":{"2":{"RoleId":0}}}, "ns2":{"UserAccessPolicies":{"2":{"RoleId":0}}}}`,
|
||||
},
|
||||
}
|
||||
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(namespace, config),
|
||||
instanceID: "instance",
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
err := kcl.ToggleSystemState(nsName, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ns, err := kcl.cli.CoreV1().Namespaces().Get(nsName, meta.GetOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
labelValue, exists := ns.Labels[systemNamespaceLabel]
|
||||
assert.True(t, exists, "system label should exists")
|
||||
assert.Equal(t, "true", labelValue)
|
||||
|
||||
expectedPolicies := map[string]portainer.K8sNamespaceAccessPolicy{
|
||||
"ns2": {UserAccessPolicies: portainer.UserAccessPolicies{2: {RoleID: 0}}},
|
||||
}
|
||||
actualPolicies, err := kcl.GetNamespaceAccessPolicies()
|
||||
assert.NoError(t, err, "failed to fetch policies")
|
||||
assert.Equal(t, expectedPolicies, actualPolicies)
|
||||
|
||||
})
|
||||
}
|
|
@ -1230,6 +1230,7 @@ type (
|
|||
CreateRegistrySecret(registry *Registry, namespace string) error
|
||||
IsRegistrySecret(namespace, secretName string) (bool, error)
|
||||
GetKubeConfig(ctx context.Context, apiServerURL string, bearerToken string, tokenData *TokenData) (*clientV1.Config, error)
|
||||
ToggleSystemState(namespace string, isSystem bool) error
|
||||
}
|
||||
|
||||
// KubernetesDeployer represents a service to deploy a manifest inside a Kubernetes endpoint
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue