mirror of https://github.com/portainer/portainer
feat(k8s/resource-pool): add the ability to mark/unmark resource pool as system (#5360)
* feat(k8s/resource-pool): add the ability to mark/unmark resource pool as system fix(kube/ns): check label to see if namespace is system refactor(k8s/namespaces): rename variables feat(kubernetes): toggle system state in the server (#5361) fix(app/resource-pool): UI fixes feat(app/resource-pool): add confirmation modal when unamrking system namespace * refactor(app): review changes * feat(app/namespaces): introduce store to retrieve namespace system status without changing all the kubernetes models refactor(app/namespaces): remove unused code first introduced for system tagging fix(app/namespaces): cache namespaces to retrieve system status regardless of namespace reference format refactor(app): migrate namespace store from helper to a separate singleton refactor(app): remove KubernetesNamespaceHelper from DI cycle * refactor(app): normalize usage of KubernetesNamespaceHelper functions * refactor(app/k8s): change namespace store to functions instead of class Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>pull/5543/head
parent
5ab98f41f1
commit
1830a80a61
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
})
|
||||
}
|
|
@ -1226,6 +1226,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
|
||||
|
|
|
@ -27,8 +27,6 @@ angular
|
|||
.constant('PAGINATION_MAX_ITEMS', 10)
|
||||
.constant('APPLICATION_CACHE_VALIDITY', 3600)
|
||||
.constant('CONSOLE_COMMANDS_LABEL_PREFIX', 'io.portainer.commands.')
|
||||
.constant('PREDEFINED_NETWORKS', ['host', 'bridge', 'none'])
|
||||
.constant('KUBERNETES_DEFAULT_NAMESPACE', 'default')
|
||||
.constant('KUBERNETES_SYSTEM_NAMESPACES', ['kube-system', 'kube-public', 'kube-node-lease', 'portainer']);
|
||||
.constant('PREDEFINED_NETWORKS', ['host', 'bridge', 'none']);
|
||||
|
||||
export const PORTAINER_FADEOUT = 1500;
|
||||
|
|
|
@ -11,7 +11,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule]).conf
|
|||
parent: 'endpoint',
|
||||
abstract: true,
|
||||
|
||||
onEnter: /* @ngInject */ function onEnter($async, $state, endpoint, EndpointProvider, KubernetesHealthService, Notifications, StateManager) {
|
||||
onEnter: /* @ngInject */ function onEnter($async, $state, endpoint, EndpointProvider, KubernetesHealthService, KubernetesNamespaceService, Notifications, StateManager) {
|
||||
return $async(async () => {
|
||||
if (![5, 6, 7].includes(endpoint.Type)) {
|
||||
$state.go('portainer.home');
|
||||
|
@ -34,6 +34,8 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule]).conf
|
|||
if (endpoint.Type === 7 && endpoint.Status === 2) {
|
||||
throw new Error('Unable to contact Edge agent, please ensure that the agent is properly running on the remote environment.');
|
||||
}
|
||||
|
||||
await KubernetesNamespaceService.get();
|
||||
} catch (e) {
|
||||
Notifications.error('Failed loading endpoint', e);
|
||||
$state.go('portainer.home', {}, { reload: true });
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
angular.module('portainer.docker').controller('KubernetesApplicationsDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'KubernetesNamespaceHelper',
|
||||
'DatatableService',
|
||||
'Authentication',
|
||||
function ($scope, $controller, KubernetesNamespaceHelper, DatatableService, Authentication) {
|
||||
function ($scope, $controller, DatatableService, Authentication) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
|
||||
var ctrl = this;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import _ from 'lodash-es';
|
||||
import { KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||
|
||||
angular.module('portainer.docker').controller('KubernetesApplicationsPortsDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'KubernetesNamespaceHelper',
|
||||
'DatatableService',
|
||||
'Authentication',
|
||||
function ($scope, $controller, KubernetesNamespaceHelper, DatatableService, Authentication) {
|
||||
function ($scope, $controller, DatatableService, Authentication) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
this.state = Object.assign(this.state, {
|
||||
expandedItems: [],
|
||||
|
|
|
@ -134,7 +134,7 @@
|
|||
</td>
|
||||
<td>
|
||||
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: item.ResourcePool })" ng-click="$event.stopPropagation();">{{ item.ResourcePool }}</a>
|
||||
<span class="label label-info image-tag label-margins" ng-if="$ctrl.isSystemNamespace(item)">system</span>
|
||||
<span class="label label-info image-tag label-margins" ng-if="$ctrl.isSystemNamespace(item.ResourcePool)">system</span>
|
||||
</td>
|
||||
<td>{{ item.Applications.length }}</td>
|
||||
<td>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import _ from 'lodash-es';
|
||||
import { KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
angular.module('portainer.docker').controller('KubernetesApplicationsStacksDatatableController', [
|
||||
angular.module('portainer.kubernetes').controller('KubernetesApplicationsStacksDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'KubernetesNamespaceHelper',
|
||||
'DatatableService',
|
||||
'Authentication',
|
||||
function ($scope, $controller, KubernetesNamespaceHelper, DatatableService, Authentication) {
|
||||
function ($scope, $controller, DatatableService, Authentication) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
this.state = Object.assign(this.state, {
|
||||
expandedItems: [],
|
||||
|
@ -33,15 +33,19 @@ angular.module('portainer.docker').controller('KubernetesApplicationsStacksDatat
|
|||
* Do not allow applications in system namespaces to be selected
|
||||
*/
|
||||
this.allowSelection = function (item) {
|
||||
return !this.isSystemNamespace(item);
|
||||
return !this.isSystemNamespace(item.ResourcePool);
|
||||
};
|
||||
|
||||
this.isSystemNamespace = function (item) {
|
||||
return KubernetesNamespaceHelper.isSystemNamespace(item.ResourcePool);
|
||||
/**
|
||||
* @param {String} namespace Namespace (string name)
|
||||
* @returns Boolean
|
||||
*/
|
||||
this.isSystemNamespace = function (namespace) {
|
||||
return KubernetesNamespaceHelper.isSystemNamespace(namespace);
|
||||
};
|
||||
|
||||
this.isDisplayed = function (item) {
|
||||
return !ctrl.isSystemNamespace(item) || ctrl.settings.showSystem;
|
||||
return !ctrl.isSystemNamespace(item.ResourcePool) || ctrl.settings.showSystem;
|
||||
};
|
||||
|
||||
this.expandItem = function (item, expanded) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
angular.module('portainer.docker').controller('KubernetesConfigurationsDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'KubernetesNamespaceHelper',
|
||||
'DatatableService',
|
||||
'Authentication',
|
||||
function ($scope, $controller, KubernetesNamespaceHelper, DatatableService, Authentication) {
|
||||
function ($scope, $controller, DatatableService, Authentication) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
|
||||
const ctrl = this;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
angular.module('portainer.docker').controller('KubernetesNodeApplicationsDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'KubernetesNamespaceHelper',
|
||||
'DatatableService',
|
||||
function ($scope, $controller, KubernetesNamespaceHelper, DatatableService) {
|
||||
function ($scope, $controller, DatatableService) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
|
||||
this.isSystemNamespace = function (item) {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
angular.module('portainer.docker').controller('KubernetesResourcePoolsDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'Authentication',
|
||||
'KubernetesNamespaceHelper',
|
||||
'DatatableService',
|
||||
function ($scope, $controller, Authentication, KubernetesNamespaceHelper, DatatableService) {
|
||||
function ($scope, $controller, Authentication, DatatableService) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
|
||||
var ctrl = this;
|
||||
|
@ -19,14 +20,14 @@ angular.module('portainer.docker').controller('KubernetesResourcePoolsDatatableC
|
|||
|
||||
this.canManageAccess = function (item) {
|
||||
if (!this.endpoint.Kubernetes.Configuration.RestrictDefaultNamespace) {
|
||||
return item.Namespace.Name !== 'default' && !this.isSystemNamespace(item);
|
||||
return !KubernetesNamespaceHelper.isDefaultNamespace(item.Namespace.Name) && !this.isSystemNamespace(item);
|
||||
} else {
|
||||
return !this.isSystemNamespace(item);
|
||||
}
|
||||
};
|
||||
|
||||
this.disableRemove = function (item) {
|
||||
return KubernetesNamespaceHelper.isSystemNamespace(item.Namespace.Name) || item.Namespace.Name === 'default';
|
||||
return this.isSystemNamespace(item) || KubernetesNamespaceHelper.isDefaultNamespace(item.Namespace.Name);
|
||||
};
|
||||
|
||||
this.isSystemNamespace = function (item) {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import angular from 'angular';
|
||||
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
// TODO: review - refactor to use `extends GenericDatatableController`
|
||||
class KubernetesVolumesDatatableController {
|
||||
/* @ngInject */
|
||||
constructor($async, $controller, Authentication, KubernetesNamespaceHelper, DatatableService) {
|
||||
constructor($async, $controller, Authentication, DatatableService) {
|
||||
this.$async = $async;
|
||||
this.$controller = $controller;
|
||||
this.Authentication = Authentication;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.DatatableService = DatatableService;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
|
@ -29,7 +29,7 @@ class KubernetesVolumesDatatableController {
|
|||
}
|
||||
|
||||
isSystemNamespace(item) {
|
||||
return this.KubernetesNamespaceHelper.isSystemNamespace(item.ResourcePool.Namespace.Name);
|
||||
return KubernetesNamespaceHelper.isSystemNamespace(item.ResourcePool.Namespace.Name);
|
||||
}
|
||||
|
||||
isDisplayed(item) {
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import _ from 'lodash-es';
|
||||
import { KubernetesNamespace } from 'Kubernetes/models/namespace/models';
|
||||
import { KubernetesNamespaceCreatePayload } from 'Kubernetes/models/namespace/payloads';
|
||||
import { KubernetesPortainerResourcePoolNameLabel, KubernetesPortainerResourcePoolOwnerLabel } from 'Kubernetes/models/resource-pool/models';
|
||||
import {
|
||||
KubernetesPortainerResourcePoolNameLabel,
|
||||
KubernetesPortainerResourcePoolOwnerLabel,
|
||||
KubernetesPortainerNamespaceSystemLabel,
|
||||
} from 'Kubernetes/models/resource-pool/models';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
class KubernetesNamespaceConverter {
|
||||
export default class KubernetesNamespaceConverter {
|
||||
static apiToNamespace(data, yaml) {
|
||||
const res = new KubernetesNamespace();
|
||||
res.Id = data.metadata.uid;
|
||||
|
@ -13,6 +18,14 @@ class KubernetesNamespaceConverter {
|
|||
res.Yaml = yaml ? yaml.data : '';
|
||||
res.ResourcePoolName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerResourcePoolNameLabel] : '';
|
||||
res.ResourcePoolOwner = data.metadata.labels ? data.metadata.labels[KubernetesPortainerResourcePoolOwnerLabel] : '';
|
||||
|
||||
res.IsSystem = KubernetesNamespaceHelper.isDefaultSystemNamespace(data.metadata.name);
|
||||
if (data.metadata.labels) {
|
||||
const systemLabel = data.metadata.labels[KubernetesPortainerNamespaceSystemLabel];
|
||||
if (!_.isEmpty(systemLabel)) {
|
||||
res.IsSystem = systemLabel === 'true';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -20,6 +33,7 @@ class KubernetesNamespaceConverter {
|
|||
const res = new KubernetesNamespaceCreatePayload();
|
||||
res.metadata.name = namespace.Name;
|
||||
res.metadata.labels[KubernetesPortainerResourcePoolNameLabel] = namespace.ResourcePoolName;
|
||||
|
||||
if (namespace.ResourcePoolOwner) {
|
||||
const resourcePoolOwner = _.truncate(namespace.ResourcePoolOwner, { length: 63, omission: '' });
|
||||
res.metadata.labels[KubernetesPortainerResourcePoolOwnerLabel] = resourcePoolOwner;
|
||||
|
@ -27,5 +41,3 @@ class KubernetesNamespaceConverter {
|
|||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesNamespaceConverter;
|
||||
|
|
|
@ -18,6 +18,7 @@ class KubernetesResourcePoolConverter {
|
|||
namespace.Name = formValues.Name;
|
||||
namespace.ResourcePoolName = formValues.Name;
|
||||
namespace.ResourcePoolOwner = formValues.Owner;
|
||||
namespace.IsSystem = formValues.IsSystem;
|
||||
|
||||
const quota = KubernetesResourceQuotaConverter.resourcePoolFormValuesToResourceQuota(formValues);
|
||||
|
||||
|
|
|
@ -1,21 +1,33 @@
|
|||
import _ from 'lodash-es';
|
||||
import angular from 'angular';
|
||||
|
||||
class KubernetesNamespaceHelper {
|
||||
/* @ngInject */
|
||||
constructor(KUBERNETES_SYSTEM_NAMESPACES, KUBERNETES_DEFAULT_NAMESPACE) {
|
||||
this.KUBERNETES_SYSTEM_NAMESPACES = KUBERNETES_SYSTEM_NAMESPACES;
|
||||
this.KUBERNETES_DEFAULT_NAMESPACE = KUBERNETES_DEFAULT_NAMESPACE;
|
||||
import { KUBERNETES_DEFAULT_NAMESPACE, KUBERNETES_DEFAULT_SYSTEM_NAMESPACES } from 'Kubernetes/models/namespace/models';
|
||||
import { isSystem } from 'Kubernetes/store/namespace';
|
||||
|
||||
export default class KubernetesNamespaceHelper {
|
||||
/**
|
||||
* Check if namespace is system or not
|
||||
* @param {String} namespace Namespace (string name) to evaluate
|
||||
* @returns Boolean
|
||||
*/
|
||||
static isSystemNamespace(namespace) {
|
||||
return isSystem(namespace);
|
||||
}
|
||||
|
||||
isSystemNamespace(namespace) {
|
||||
return _.includes(this.KUBERNETES_SYSTEM_NAMESPACES, namespace);
|
||||
/**
|
||||
* Check if namespace is default or not
|
||||
* @param {String} namespace Namespace (string name) to evaluate
|
||||
* @returns Boolean
|
||||
*/
|
||||
static isDefaultNamespace(namespace) {
|
||||
return namespace === KUBERNETES_DEFAULT_NAMESPACE;
|
||||
}
|
||||
|
||||
isDefaultNamespace(namespace) {
|
||||
return namespace === this.KUBERNETES_DEFAULT_NAMESPACE;
|
||||
/**
|
||||
* Check if namespace is one of the default system namespaces
|
||||
* @param {String} namespace Namespace (string name) to evaluate
|
||||
* @returns Boolean
|
||||
*/
|
||||
static isDefaultSystemNamespace(namespace) {
|
||||
return _.includes(KUBERNETES_DEFAULT_SYSTEM_NAMESPACES, namespace);
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesNamespaceHelper;
|
||||
angular.module('portainer.app').service('KubernetesNamespaceHelper', KubernetesNamespaceHelper);
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
export function KubernetesNamespace() {
|
||||
return {
|
||||
Id: '',
|
||||
Name: '',
|
||||
CreationDate: '',
|
||||
Status: '',
|
||||
Yaml: '',
|
||||
ResourcePoolName: '',
|
||||
ResourcePoolOwner: '',
|
||||
};
|
||||
export class KubernetesNamespace {
|
||||
constructor() {
|
||||
this.Id = '';
|
||||
this.Name = '';
|
||||
this.CreationDate = '';
|
||||
this.Status = '';
|
||||
this.Yaml = '';
|
||||
this.ResourcePoolName = '';
|
||||
this.ResourcePoolOwner = '';
|
||||
this.IsSystem = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const KUBERNETES_DEFAULT_SYSTEM_NAMESPACES = ['kube-system', 'kube-public', 'kube-node-lease', 'portainer'];
|
||||
export const KUBERNETES_DEFAULT_NAMESPACE = 'default';
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
export function KubernetesResourcePoolFormValues(defaults) {
|
||||
return {
|
||||
Name: '',
|
||||
MemoryLimit: defaults.MemoryLimit,
|
||||
CpuLimit: defaults.CpuLimit,
|
||||
HasQuota: false,
|
||||
IngressClasses: [], // KubernetesResourcePoolIngressClassFormValue
|
||||
Registries: [], // RegistryViewModel
|
||||
EndpointId: 0,
|
||||
};
|
||||
this.Name = '';
|
||||
this.MemoryLimit = defaults.MemoryLimit;
|
||||
this.CpuLimit = defaults.CpuLimit;
|
||||
this.HasQuota = false;
|
||||
this.IngressClasses = []; // KubernetesResourcePoolIngressClassFormValue
|
||||
this.Registries = []; // RegistryViewModel
|
||||
this.EndpointId = 0;
|
||||
this.IsSystem = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,8 @@ export const KubernetesPortainerResourcePoolNameLabel = 'io.portainer.kubernetes
|
|||
|
||||
export const KubernetesPortainerResourcePoolOwnerLabel = 'io.portainer.kubernetes.resourcepool.owner';
|
||||
|
||||
export const KubernetesPortainerNamespaceSystemLabel = 'io.portainer.kubernetes.namespace.system';
|
||||
|
||||
/**
|
||||
* KubernetesResourcePool Model
|
||||
*/
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
export default class KubernetesRegistryAccessController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, EndpointService, Notifications, KubernetesResourcePoolService, KubernetesNamespaceHelper) {
|
||||
constructor($async, $state, EndpointService, Notifications, KubernetesResourcePoolService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.EndpointService = EndpointService;
|
||||
|
||||
this.state = {
|
||||
|
@ -60,7 +61,7 @@ export default class KubernetesRegistryAccessController {
|
|||
const resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
|
||||
this.resourcePools = resourcePools
|
||||
.filter((pool) => !this.KubernetesNamespaceHelper.isSystemNamespace(pool.Namespace.Name) && !this.savedResourcePools.find(({ value }) => value === pool.Namespace.Name))
|
||||
.filter((pool) => !KubernetesNamespaceHelper.isSystemNamespace(pool.Namespace.Name) && !this.savedResourcePools.find(({ value }) => value === pool.Namespace.Name))
|
||||
.map((pool) => ({ name: pool.Namespace.Name, id: pool.Namespace.Id }));
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve namespaces');
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.kubernetes').factory('KubernetesPortainerNamespaces', KubernetesPortainerNamespacesFactory);
|
||||
|
||||
function KubernetesPortainerNamespacesFactory($resource) {
|
||||
const url = '/api/kubernetes/:endpointId/namespaces/:namespaceName/:action';
|
||||
return $resource(
|
||||
url,
|
||||
{},
|
||||
{
|
||||
toggleSystem: { method: 'PUT', params: { action: 'system' } },
|
||||
}
|
||||
);
|
||||
}
|
|
@ -4,6 +4,7 @@ import angular from 'angular';
|
|||
import PortainerError from 'Portainer/error';
|
||||
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
||||
import KubernetesNamespaceConverter from 'Kubernetes/converters/namespace';
|
||||
import { updateNamespaces } from 'Kubernetes/store/namespace';
|
||||
import $allSettled from 'Portainer/services/allSettled';
|
||||
|
||||
class KubernetesNamespaceService {
|
||||
|
@ -27,7 +28,9 @@ class KubernetesNamespaceService {
|
|||
params.id = name;
|
||||
await this.KubernetesNamespaces().status(params).$promise;
|
||||
const [raw, yaml] = await Promise.all([this.KubernetesNamespaces().get(params).$promise, this.KubernetesNamespaces().getYaml(params).$promise]);
|
||||
return KubernetesNamespaceConverter.apiToNamespace(raw, yaml);
|
||||
const ns = KubernetesNamespaceConverter.apiToNamespace(raw, yaml);
|
||||
updateNamespaces([ns]);
|
||||
return ns;
|
||||
} catch (err) {
|
||||
throw new PortainerError('Unable to retrieve namespace', err);
|
||||
}
|
||||
|
@ -43,7 +46,9 @@ class KubernetesNamespaceService {
|
|||
return KubernetesNamespaceConverter.apiToNamespace(item);
|
||||
}
|
||||
});
|
||||
return _.without(visibleNamespaces, undefined);
|
||||
const res = _.without(visibleNamespaces, undefined);
|
||||
updateNamespaces(res);
|
||||
return res;
|
||||
} catch (err) {
|
||||
throw new PortainerError('Unable to retrieve namespaces', err);
|
||||
}
|
||||
|
|
|
@ -5,12 +5,20 @@ import KubernetesResourcePoolConverter from 'Kubernetes/converters/resourcePool'
|
|||
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
||||
|
||||
/* @ngInject */
|
||||
export function KubernetesResourcePoolService($async, EndpointService, KubernetesNamespaceService, KubernetesResourceQuotaService, KubernetesIngressService) {
|
||||
export function KubernetesResourcePoolService(
|
||||
$async,
|
||||
EndpointService,
|
||||
KubernetesNamespaceService,
|
||||
KubernetesResourceQuotaService,
|
||||
KubernetesIngressService,
|
||||
KubernetesPortainerNamespaces
|
||||
) {
|
||||
return {
|
||||
get,
|
||||
create,
|
||||
patch,
|
||||
delete: _delete,
|
||||
toggleSystem,
|
||||
};
|
||||
|
||||
async function getOne(name) {
|
||||
|
@ -67,9 +75,8 @@ export function KubernetesResourcePoolService($async, EndpointService, Kubernete
|
|||
|
||||
function patch(oldFormValues, newFormValues) {
|
||||
return $async(async () => {
|
||||
const [oldNamespace, oldQuota, oldIngresses, oldRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(oldFormValues);
|
||||
const [newNamespace, newQuota, newIngresses, newRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(newFormValues);
|
||||
void oldNamespace, newNamespace;
|
||||
const [, oldQuota, oldIngresses, oldRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(oldFormValues);
|
||||
const [, newQuota, newIngresses, newRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(newFormValues);
|
||||
|
||||
if (oldQuota && newQuota) {
|
||||
await KubernetesResourceQuotaService.patch(oldQuota, newQuota);
|
||||
|
@ -114,6 +121,10 @@ export function KubernetesResourcePoolService($async, EndpointService, Kubernete
|
|||
await KubernetesNamespaceService.delete(pool.Namespace);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSystem(endpointId, namespaceName, system) {
|
||||
return KubernetesPortainerNamespaces.toggleSystem({ namespaceName, endpointId }, { system }).$promise;
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('portainer.kubernetes').service('KubernetesResourcePoolService', KubernetesResourcePoolService);
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
// singleton pattern as:
|
||||
// * we don't want to use AngularJS DI to fetch the single instance
|
||||
// * we need to use the Store in static functions / non-instanciated classes
|
||||
const storeNamespaces = {};
|
||||
|
||||
/**
|
||||
* Check if a namespace of the store is system or not
|
||||
* @param {String} name Namespace name
|
||||
* @returns Boolean
|
||||
*/
|
||||
export function isSystem(name) {
|
||||
return storeNamespaces[name] && storeNamespaces[name].IsSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from KubernetesNamespaceService.get()
|
||||
* @param {KubernetesNamespace[]} namespaces list of namespaces to update in Store
|
||||
*/
|
||||
export function updateNamespaces(namespaces) {
|
||||
namespaces.forEach((ns) => (storeNamespaces[ns.Name] = ns));
|
||||
}
|
|
@ -28,6 +28,7 @@ import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceRese
|
|||
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application/index';
|
||||
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { KubernetesNodeHelper } from 'Kubernetes/node/helper';
|
||||
|
||||
class KubernetesCreateApplicationController {
|
||||
|
@ -47,7 +48,6 @@ class KubernetesCreateApplicationController {
|
|||
KubernetesNodeService,
|
||||
KubernetesIngressService,
|
||||
KubernetesPersistentVolumeClaimService,
|
||||
KubernetesNamespaceHelper,
|
||||
KubernetesVolumeService,
|
||||
RegistryService
|
||||
) {
|
||||
|
@ -64,7 +64,6 @@ class KubernetesCreateApplicationController {
|
|||
this.KubernetesVolumeService = KubernetesVolumeService;
|
||||
this.KubernetesIngressService = KubernetesIngressService;
|
||||
this.KubernetesPersistentVolumeClaimService = KubernetesPersistentVolumeClaimService;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.RegistryService = RegistryService;
|
||||
|
||||
this.ApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
||||
|
@ -955,7 +954,7 @@ class KubernetesCreateApplicationController {
|
|||
]);
|
||||
this.ingresses = ingresses;
|
||||
|
||||
this.resourcePools = _.filter(resourcePools, (resourcePool) => !this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
this.resourcePools = _.filter(resourcePools, (resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
if (!this.formValues.ResourcePool) {
|
||||
return;
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<td>Namespace</td>
|
||||
<td data-cy="k8sAppDetail-resourcePoolName">
|
||||
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.application.ResourcePool })">{{ ctrl.application.ResourcePool }}</a>
|
||||
<span style="margin-left: 5px;" class="label label-info image-tag" ng-if="ctrl.isSystemNamespace(item)">system</span>
|
||||
<span style="margin-left: 5px;" class="label label-info image-tag" ng-if="ctrl.isSystemNamespace()">system</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -7,6 +7,7 @@ import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
|||
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||
import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models';
|
||||
import { KubernetesPodContainerTypes } from 'Kubernetes/pod/models/index';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
function computeTolerations(nodes, application) {
|
||||
const pod = application.Pods[0];
|
||||
|
@ -106,7 +107,6 @@ class KubernetesApplicationController {
|
|||
KubernetesStackService,
|
||||
KubernetesPodService,
|
||||
KubernetesNodeService,
|
||||
KubernetesNamespaceHelper,
|
||||
EndpointProvider
|
||||
) {
|
||||
this.$async = $async;
|
||||
|
@ -122,8 +122,6 @@ class KubernetesApplicationController {
|
|||
this.KubernetesPodService = KubernetesPodService;
|
||||
this.KubernetesNodeService = KubernetesNodeService;
|
||||
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
|
||||
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
||||
this.KubernetesApplicationTypes = KubernetesApplicationTypes;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
|
@ -153,7 +151,7 @@ class KubernetesApplicationController {
|
|||
}
|
||||
|
||||
isSystemNamespace() {
|
||||
return this.KubernetesNamespaceHelper.isSystemNamespace(this.application.ResourcePool);
|
||||
return KubernetesNamespaceHelper.isSystemNamespace(this.application.ResourcePool);
|
||||
}
|
||||
|
||||
isExternalApplication() {
|
||||
|
|
|
@ -3,10 +3,11 @@ import _ from 'lodash-es';
|
|||
import { KubernetesConfigurationFormValues, KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
class KubernetesCreateConfigurationController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $window, ModalService, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService, KubernetesNamespaceHelper) {
|
||||
constructor($async, $state, $window, ModalService, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$window = $window;
|
||||
|
@ -16,7 +17,6 @@ class KubernetesCreateConfigurationController {
|
|||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesConfigurationTypes = KubernetesConfigurationTypes;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.createConfigurationAsync = this.createConfigurationAsync.bind(this);
|
||||
|
@ -94,7 +94,7 @@ class KubernetesCreateConfigurationController {
|
|||
|
||||
try {
|
||||
const resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
this.resourcePools = _.filter(resourcePools, (resourcePool) => !this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
this.resourcePools = _.filter(resourcePools, (resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
await this.getConfigurations();
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
|
||||
import { KubernetesConfigurationFormValues } from 'Kubernetes/models/configuration/formvalues';
|
||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import KubernetesConfigurationConverter from 'Kubernetes/converters/configuration';
|
||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||
import _ from 'lodash-es';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
class KubernetesConfigurationController {
|
||||
/* @ngInject */
|
||||
|
@ -21,8 +23,7 @@ class KubernetesConfigurationController {
|
|||
KubernetesResourcePoolService,
|
||||
ModalService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesEventService,
|
||||
KubernetesNamespaceHelper
|
||||
KubernetesEventService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
|
@ -36,7 +37,6 @@ class KubernetesConfigurationController {
|
|||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
this.KubernetesEventService = KubernetesEventService;
|
||||
this.KubernetesConfigurationTypes = KubernetesConfigurationTypes;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.KubernetesConfigMapService = KubernetesConfigMapService;
|
||||
this.KubernetesSecretService = KubernetesSecretService;
|
||||
|
||||
|
@ -52,7 +52,7 @@ class KubernetesConfigurationController {
|
|||
}
|
||||
|
||||
isSystemNamespace() {
|
||||
return this.KubernetesNamespaceHelper.isSystemNamespace(this.configuration.Namespace);
|
||||
return KubernetesNamespaceHelper.isSystemNamespace(this.configuration.Namespace);
|
||||
}
|
||||
|
||||
isSystemConfig() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { KubernetesFormValidationReferences } from 'Kubernetes/models/applicatio
|
|||
import { KubernetesIngressClass } from 'Kubernetes/ingress/models';
|
||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
class KubernetesConfigureController {
|
||||
/* #region CONSTRUCTOR */
|
||||
|
@ -18,7 +19,6 @@ class KubernetesConfigureController {
|
|||
EndpointService,
|
||||
EndpointProvider,
|
||||
ModalService,
|
||||
KubernetesNamespaceHelper,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesIngressService,
|
||||
KubernetesMetricsService
|
||||
|
@ -30,7 +30,6 @@ class KubernetesConfigureController {
|
|||
this.EndpointService = EndpointService;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.ModalService = ModalService;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesIngressService = KubernetesIngressService;
|
||||
this.KubernetesMetricsService = KubernetesMetricsService;
|
||||
|
@ -147,8 +146,7 @@ class KubernetesConfigureController {
|
|||
const allResourcePools = await this.KubernetesResourcePoolService.get();
|
||||
const resourcePools = _.filter(
|
||||
allResourcePools,
|
||||
(resourcePool) =>
|
||||
!this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) && !this.KubernetesNamespaceHelper.isDefaultNamespace(resourcePool.Namespace.Name)
|
||||
(resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) && !KubernetesNamespaceHelper.isDefaultNamespace(resourcePool.Namespace.Name)
|
||||
);
|
||||
|
||||
ingressesToDel.forEach((ingress) => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
class KubernetesDashboardController {
|
||||
/* @ngInject */
|
||||
|
@ -13,7 +14,6 @@ class KubernetesDashboardController {
|
|||
KubernetesApplicationService,
|
||||
KubernetesConfigurationService,
|
||||
KubernetesVolumeService,
|
||||
KubernetesNamespaceHelper,
|
||||
Authentication,
|
||||
TagService
|
||||
) {
|
||||
|
@ -25,7 +25,6 @@ class KubernetesDashboardController {
|
|||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.KubernetesVolumeService = KubernetesVolumeService;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.Authentication = Authentication;
|
||||
this.TagService = TagService;
|
||||
|
||||
|
@ -65,13 +64,8 @@ class KubernetesDashboardController {
|
|||
: '-';
|
||||
|
||||
if (!isAdmin) {
|
||||
this.pools = _.filter(pools, (pool) => {
|
||||
return !this.KubernetesNamespaceHelper.isSystemNamespace(pool.Namespace.Name);
|
||||
});
|
||||
|
||||
this.configurations = _.filter(configurations, (config) => {
|
||||
return !KubernetesConfigurationHelper.isSystemToken(config);
|
||||
});
|
||||
this.pools = _.filter(pools, (pool) => !KubernetesNamespaceHelper.isSystemNamespace(pool.Namespace.Name));
|
||||
this.configurations = _.filter(configurations, (config) => !KubernetesConfigurationHelper.isSystemToken(config));
|
||||
} else {
|
||||
this.pools = pools;
|
||||
this.configurations = configurations;
|
||||
|
|
|
@ -15,9 +15,18 @@
|
|||
<form class="form-horizontal" autocomplete="off" name="resourcePoolEditForm" style="padding: 20px; margin-top: 10px;">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="pool_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" name="pool_name" ng-model="ctrl.pool.Namespace.Name" disabled />
|
||||
<div class="col-sm-12">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ ctrl.pool.Namespace.Name }}
|
||||
<span class="label label-info image-tag label-margins" ng-if="ctrl.isSystem">system</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
|
@ -396,13 +405,14 @@
|
|||
<!-- !summary -->
|
||||
|
||||
<!-- actions -->
|
||||
<div ng-if="ctrl.isAdmin && ctrl.isEditable" class="col-sm-12 form-section-title">
|
||||
<div ng-if="ctrl.isAdmin && !ctrl.isDefaultNamespace" class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div ng-if="ctrl.isAdmin && ctrl.isEditable" class="form-group">
|
||||
<div ng-if="ctrl.isAdmin && !ctrl.isDefaultNamespace" class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
ng-if="ctrl.isEditable"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="!resourcePoolEditForm.$valid || ctrl.isUpdateButtonDisabled()"
|
||||
ng-click="ctrl.updateResourcePool()"
|
||||
|
@ -411,6 +421,10 @@
|
|||
<span ng-hide="ctrl.state.actionInProgress" data-cy="k8sNamespaceEdit-updateNamespaceButton">Update namespace</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Update in progress...</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="ctrl.markUnmarkAsSystem()" button-spinner="ctrl.state.actionInProgress">
|
||||
<span ng-if="ctrl.isSystem">Unmark as system</span>
|
||||
<span ng-if="!ctrl.isSystem">Mark as system</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
|
|
|
@ -15,6 +15,7 @@ import { KubernetesFormValidationReferences } from 'Kubernetes/models/applicatio
|
|||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||
import KubernetesResourceQuotaConverter from 'Kubernetes/converters/resourceQuota';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
class KubernetesResourcePoolController {
|
||||
/* #region CONSTRUCTOR */
|
||||
|
@ -34,7 +35,6 @@ class KubernetesResourcePoolController {
|
|||
KubernetesEventService,
|
||||
KubernetesPodService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesNamespaceHelper,
|
||||
KubernetesIngressService,
|
||||
KubernetesVolumeService
|
||||
) {
|
||||
|
@ -53,7 +53,6 @@ class KubernetesResourcePoolController {
|
|||
KubernetesEventService,
|
||||
KubernetesPodService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesNamespaceHelper,
|
||||
KubernetesIngressService,
|
||||
KubernetesVolumeService,
|
||||
});
|
||||
|
@ -171,11 +170,11 @@ class KubernetesResourcePoolController {
|
|||
}
|
||||
|
||||
/* #region UPDATE NAMESPACE */
|
||||
async updateResourcePoolAsync() {
|
||||
async updateResourcePoolAsync(oldFormValues, newFormValues) {
|
||||
this.state.actionInProgress = true;
|
||||
try {
|
||||
this.checkDefaults();
|
||||
await this.KubernetesResourcePoolService.patch(this.savedFormValues, this.formValues);
|
||||
await this.KubernetesResourcePoolService.patch(oldFormValues, newFormValues);
|
||||
this.Notifications.success('Namespace successfully updated', this.pool.Namespace.Name);
|
||||
this.$state.reload();
|
||||
} catch (err) {
|
||||
|
@ -202,13 +201,45 @@ class KubernetesResourcePoolController {
|
|||
${warnings.ingress ? messages.ingress : ''}<br/><br/>Do you wish to continue?`;
|
||||
this.ModalService.confirmUpdate(displayedMessage, (confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.updateResourcePoolAsync);
|
||||
return this.$async(this.updateResourcePoolAsync, this.savedFormValues, this.formValues);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return this.$async(this.updateResourcePoolAsync);
|
||||
return this.$async(this.updateResourcePoolAsync, this.savedFormValues, this.formValues);
|
||||
}
|
||||
}
|
||||
|
||||
async confirmMarkUnmarkAsSystem() {
|
||||
const message = this.isSystem
|
||||
? 'Unmarking this namespace as system will allow non administrator users to manage it and the resources in contains depending on the access control settings. Are you sure?'
|
||||
: 'Marking this namespace as a system namespace will prevent non administrator users from managing it and the resources it contains. Are you sure?';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.ModalService.confirmUpdate(message, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
markUnmarkAsSystem() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
const namespaceName = this.$state.params.id;
|
||||
this.state.actionInProgress = true;
|
||||
|
||||
const confirmed = await this.confirmMarkUnmarkAsSystem();
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
await this.KubernetesResourcePoolService.toggleSystem(this.endpoint.Id, namespaceName, !this.isSystem);
|
||||
|
||||
this.Notifications.success('Namespace successfully updated', namespaceName);
|
||||
this.$state.reload();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to create namespace');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
hasEventWarnings() {
|
||||
|
@ -361,6 +392,7 @@ class KubernetesResourcePoolController {
|
|||
this.formValues = new KubernetesResourcePoolFormValues(KubernetesResourceQuotaDefaults);
|
||||
this.formValues.Name = this.pool.Namespace.Name;
|
||||
this.formValues.EndpointId = this.endpoint.Id;
|
||||
this.formValues.IsSystem = this.pool.Namespace.IsSystem;
|
||||
|
||||
_.forEach(nodes, (item) => {
|
||||
this.state.sliderMaxMemory += filesizeParser(item.Memory);
|
||||
|
@ -377,11 +409,9 @@ class KubernetesResourcePoolController {
|
|||
this.state.resourceReservation.CPU = quota.CpuLimitUsed;
|
||||
this.state.resourceReservation.Memory = KubernetesResourceReservationHelper.megaBytesValue(quota.MemoryLimitUsed);
|
||||
}
|
||||
|
||||
this.isEditable = !this.KubernetesNamespaceHelper.isSystemNamespace(this.pool.Namespace.Name);
|
||||
if (this.pool.Namespace.Name === 'default') {
|
||||
this.isEditable = false;
|
||||
}
|
||||
this.isSystem = KubernetesNamespaceHelper.isSystemNamespace(this.pool.Namespace.Name);
|
||||
this.isDefaultNamespace = KubernetesNamespaceHelper.isDefaultNamespace(this.pool.Namespace.Name);
|
||||
this.isEditable = !this.isSystem && !this.isDefaultNamespace;
|
||||
|
||||
await this.getEvents();
|
||||
await this.getApplications();
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<td>Namespace</td>
|
||||
<td>
|
||||
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.volume.ResourcePool.Namespace.Name })">{{ ctrl.volume.ResourcePool.Namespace.Name }}</a>
|
||||
<span style="margin-left: 5px;" class="label label-info image-tag" ng-if="ctrl.isSystemNamespace(item)">system</span>
|
||||
<span style="margin-left: 5px;" class="label label-info image-tag" ng-if="ctrl.isSystemNamespace()">system</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -4,6 +4,7 @@ import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
|||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||
import { KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
|
||||
import filesizeParser from 'filesize-parser';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
class KubernetesVolumeController {
|
||||
/* @ngInject */
|
||||
|
@ -14,7 +15,6 @@ class KubernetesVolumeController {
|
|||
LocalStorage,
|
||||
KubernetesVolumeService,
|
||||
KubernetesEventService,
|
||||
KubernetesNamespaceHelper,
|
||||
KubernetesApplicationService,
|
||||
KubernetesPersistentVolumeClaimService,
|
||||
ModalService,
|
||||
|
@ -27,7 +27,6 @@ class KubernetesVolumeController {
|
|||
|
||||
this.KubernetesVolumeService = KubernetesVolumeService;
|
||||
this.KubernetesEventService = KubernetesEventService;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
this.KubernetesPersistentVolumeClaimService = KubernetesPersistentVolumeClaimService;
|
||||
this.ModalService = ModalService;
|
||||
|
@ -55,7 +54,7 @@ class KubernetesVolumeController {
|
|||
}
|
||||
|
||||
isSystemNamespace() {
|
||||
return this.KubernetesNamespaceHelper.isSystemNamespace(this.volume.ResourcePool.Namespace.Name);
|
||||
return KubernetesNamespaceHelper.isSystemNamespace(this.volume.ResourcePool.Namespace.Name);
|
||||
}
|
||||
|
||||
isUsed() {
|
||||
|
|
Loading…
Reference in New Issue