From 1f26bc6e8b2fe158c2ba08005f4d68d2f298e724 Mon Sep 17 00:00:00 2001 From: Maxime Bajeux Date: Thu, 15 Oct 2020 03:02:29 +0200 Subject: [PATCH] feat(namespace): Hide Default Namespace for non-admins (#25) * feat(namespace): Hide Default Namespace for non-admins * feat(namespace): fix expected behavior when turning on the setting * feat(resourcePool): Handle when user doesn't have access to any resource pool * Update app/kubernetes/views/applications/create/createApplication.html * Update app/kubernetes/views/configurations/create/createConfiguration.html * Update app/kubernetes/views/applications/create/createApplication.html * Update app/kubernetes/views/configurations/create/createConfiguration.html Co-authored-by: Anthony Lapenna --- api/go.sum | 3 + api/http/handler/endpoints/endpoint_update.go | 28 + api/http/handler/endpoints/handler.go | 16 +- api/http/handler/websocket/pod.go | 2 +- api/http/proxy/factory/kubernetes.go | 6 +- api/http/server.go | 1 + api/kubernetes.go | 1 + api/kubernetes/cli/access.go | 29 +- api/kubernetes/cli/client.go | 10 +- api/portainer.go | 1 + .../resourcePoolsDatatableController.js | 10 +- .../create/createApplication.html | 2682 +++++++++-------- .../create/createApplicationController.js | 7 +- .../create/createConfiguration.html | 78 +- .../create/createConfigurationController.js | 7 +- app/kubernetes/views/configure/configure.html | 21 + .../views/configure/configureController.js | 3 + 17 files changed, 1517 insertions(+), 1388 deletions(-) diff --git a/api/go.sum b/api/go.sum index d7b7db557..15917ef26 100644 --- a/api/go.sum +++ b/api/go.sum @@ -405,10 +405,13 @@ k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb h1:ZUNsbuPdXWrj0rZziRfCWc k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4= k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= +k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 h1:07mhG/2oEoo3N+sHVOo0L9PJ/qvbk3N5n2dj8IWefnQ= k8s.io/client-go v0.0.0-20191114101535-6c5935290e33/go.mod h1:4L/zQOBkEf4pArQJ+CMk1/5xjA30B5oyWv+Bzb44DOw= k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc= k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= +k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go index 19188338c..1c235c5cb 100644 --- a/api/http/handler/endpoints/endpoint_update.go +++ b/api/http/handler/endpoints/endpoint_update.go @@ -122,6 +122,7 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * } } + oldRestrictDefaultNamespace := endpoint.Kubernetes.Configuration.RestrictDefaultNamespace if payload.Kubernetes != nil { endpoint.Kubernetes = *payload.Kubernetes } @@ -269,5 +270,32 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * } } + if payload.Kubernetes != nil && payload.Kubernetes.Configuration.RestrictDefaultNamespace != oldRestrictDefaultNamespace { + users, err := handler.DataStore.User().Users() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find users inside the database", err} + } + cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint, handler.DataStore) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create Kubernetes client", err} + } + for _, user := range users { + memberships, err := handler.DataStore.TeamMembership().TeamMembershipsByUserID(user.ID) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find memberships inside the database", err} + } + + teamIds := make([]int, 0) + for _, membership := range memberships { + teamIds = append(teamIds, int(membership.TeamID)) + } + + err = cli.SetupUserServiceAccount(int(user.ID), teamIds) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update namespaces accesses", err} + } + } + } + return response.JSON(w, endpoint) } diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go index 8234e3553..972397632 100644 --- a/api/http/handler/endpoints/handler.go +++ b/api/http/handler/endpoints/handler.go @@ -9,6 +9,7 @@ import ( "github.com/portainer/portainer/api/http/proxy" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/internal/authorization" + "github.com/portainer/portainer/api/kubernetes/cli" ) func hideFields(endpoint *portainer.Endpoint) { @@ -21,13 +22,14 @@ func hideFields(endpoint *portainer.Endpoint) { // Handler is the HTTP handler used to handle endpoint operations. type Handler struct { *mux.Router - requestBouncer *security.RequestBouncer - AuthorizationService *authorization.Service - DataStore portainer.DataStore - FileService portainer.FileService - ProxyManager *proxy.Manager - ReverseTunnelService portainer.ReverseTunnelService - SnapshotService portainer.SnapshotService + requestBouncer *security.RequestBouncer + AuthorizationService *authorization.Service + DataStore portainer.DataStore + FileService portainer.FileService + ProxyManager *proxy.Manager + ReverseTunnelService portainer.ReverseTunnelService + SnapshotService portainer.SnapshotService + KubernetesClientFactory *cli.ClientFactory } // NewHandler creates a handler to manage endpoint operations. diff --git a/api/http/handler/websocket/pod.go b/api/http/handler/websocket/pod.go index 103b2b60f..b8d3fb424 100644 --- a/api/http/handler/websocket/pod.go +++ b/api/http/handler/websocket/pod.go @@ -98,7 +98,7 @@ func (handler *Handler) websocketPodExec(w http.ResponseWriter, r *http.Request) go streamFromWebsocketToWriter(websocketConn, stdinWriter, errorChan) go streamFromReaderToWebsocket(websocketConn, stdoutReader, errorChan) - cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint) + cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint, handler.DataStore) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create Kubernetes client", err} } diff --git a/api/http/proxy/factory/kubernetes.go b/api/http/proxy/factory/kubernetes.go index 2cb09dc62..a318e4c77 100644 --- a/api/http/proxy/factory/kubernetes.go +++ b/api/http/proxy/factory/kubernetes.go @@ -28,7 +28,7 @@ func (factory *ProxyFactory) newKubernetesLocalProxy(endpoint *portainer.Endpoin return nil, err } - kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint) + kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint, factory.dataStore) if err != nil { return nil, err } @@ -59,7 +59,7 @@ func (factory *ProxyFactory) newKubernetesEdgeHTTPProxy(endpoint *portainer.Endp return nil, err } - kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint) + kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint, factory.dataStore) if err != nil { return nil, err } @@ -86,7 +86,7 @@ func (factory *ProxyFactory) newKubernetesAgentHTTPSProxy(endpoint *portainer.En remoteURL.Scheme = "https" - kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint) + kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint, factory.dataStore) if err != nil { return nil, err } diff --git a/api/http/server.go b/api/http/server.go index b7f58690e..557b7889e 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -124,6 +124,7 @@ func (server *Server) Start() error { endpointHandler.SnapshotService = server.SnapshotService endpointHandler.ProxyManager = proxyManager endpointHandler.ReverseTunnelService = server.ReverseTunnelService + endpointHandler.KubernetesClientFactory = server.KubernetesClientFactory var endpointEdgeHandler = endpointedge.NewHandler(requestBouncer) endpointEdgeHandler.DataStore = server.DataStore diff --git a/api/kubernetes.go b/api/kubernetes.go index 33f446ee2..d58e398ec 100644 --- a/api/kubernetes.go +++ b/api/kubernetes.go @@ -9,6 +9,7 @@ func KubernetesDefault() KubernetesData { ResourceOverCommitPercentage: 80, StorageClasses: []KubernetesStorageClassConfig{}, IngressClasses: []KubernetesIngressClassConfig{}, + RestrictDefaultNamespace: false, }, Snapshots: []KubernetesSnapshot{}, } diff --git a/api/kubernetes/cli/access.go b/api/kubernetes/cli/access.go index bd79d3fce..3d266ef25 100644 --- a/api/kubernetes/cli/access.go +++ b/api/kubernetes/cli/access.go @@ -18,6 +18,18 @@ type ( ) func (kcl *KubeClient) setupNamespaceAccesses(userID int, teamIDs []int, serviceAccountName string) error { + endpoint, err := kcl.dataStore.Endpoint().Endpoint(portainer.EndpointID(kcl.endpointID)) + if err != nil { + return err + } + + if endpoint.Kubernetes.Configuration.RestrictDefaultNamespace { + err = kcl.removeNamespaceAccessForServiceAccount(serviceAccountName, defaultNamespace) + if err != nil { + return err + } + } + configMap, err := kcl.cli.CoreV1().ConfigMaps(portainerNamespace).Get(portainerConfigMapName, metav1.GetOptions{}) if k8serrors.IsNotFound(err) { return nil @@ -38,8 +50,23 @@ func (kcl *KubeClient) setupNamespaceAccesses(userID int, teamIDs []int, service return err } + if !endpoint.Kubernetes.Configuration.RestrictDefaultNamespace { + if _, ok := accessPolicies[defaultNamespace]; ok { + delete(accessPolicies, defaultNamespace) + data, err := json.Marshal(accessPolicies) + if err != nil { + return err + } + configMap.Data[portainerConfigMapAccessPoliciesKey] = string(data) + _, err = kcl.cli.CoreV1().ConfigMaps(portainerNamespace).Update(configMap) + if err != nil { + return err + } + } + } + for _, namespace := range namespaces.Items { - if namespace.Name == defaultNamespace { + if namespace.Name == defaultNamespace && !endpoint.Kubernetes.Configuration.RestrictDefaultNamespace { continue } diff --git a/api/kubernetes/cli/client.go b/api/kubernetes/cli/client.go index 707bf3924..5266e3051 100644 --- a/api/kubernetes/cli/client.go +++ b/api/kubernetes/cli/client.go @@ -27,6 +27,8 @@ type ( KubeClient struct { cli *kubernetes.Clientset instanceID string + endpointID portainer.EndpointID + dataStore portainer.DataStore } ) @@ -42,11 +44,11 @@ func NewClientFactory(signatureService portainer.DigitalSignatureService, revers // GetKubeClient checks if an existing client is already registered for the endpoint and returns it if one is found. // If no client is registered, it will create a new client, register it, and returns it. -func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) { +func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint, dataStore portainer.DataStore) (portainer.KubeClient, error) { key := strconv.Itoa(int(endpoint.ID)) client, ok := factory.endpointClients.Get(key) if !ok { - client, err := factory.createKubeClient(endpoint) + client, err := factory.createKubeClient(endpoint, dataStore) if err != nil { return nil, err } @@ -58,7 +60,7 @@ func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint) (porta return client.(portainer.KubeClient), nil } -func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) { +func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint, dataStore portainer.DataStore) (portainer.KubeClient, error) { cli, err := factory.CreateClient(endpoint) if err != nil { return nil, err @@ -67,6 +69,8 @@ func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint) (po kubecli := &KubeClient{ cli: cli, instanceID: factory.instanceID, + endpointID: endpoint.ID, + dataStore: dataStore, } return kubecli, nil diff --git a/api/portainer.go b/api/portainer.go index 3e9f45109..60dc9ecb7 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -343,6 +343,7 @@ type ( ResourceOverCommitPercentage int `json:"ResourceOverCommitPercentage"` StorageClasses []KubernetesStorageClassConfig `json:"StorageClasses"` IngressClasses []KubernetesIngressClassConfig `json:"IngressClasses"` + RestrictDefaultNamespace bool `json:"RestrictDefaultNamespace"` } // KubernetesStorageClassConfig represents a Kubernetes Storage Class configuration diff --git a/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js b/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js index b3181bfe9..31427061f 100644 --- a/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js +++ b/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js @@ -4,7 +4,8 @@ angular.module('portainer.docker').controller('KubernetesResourcePoolsDatatableC 'Authentication', 'KubernetesNamespaceHelper', 'DatatableService', - function ($scope, $controller, Authentication, KubernetesNamespaceHelper, DatatableService) { + 'EndpointProvider', + function ($scope, $controller, Authentication, KubernetesNamespaceHelper, DatatableService, EndpointProvider) { angular.extend(this, $controller('GenericDatatableController', { $scope: $scope })); var ctrl = this; @@ -18,7 +19,11 @@ angular.module('portainer.docker').controller('KubernetesResourcePoolsDatatableC }; this.canManageAccess = function (item) { - return item.Namespace.Name !== 'default' && !this.isSystemNamespace(item); + if (!this.endpoint.Kubernetes.Configuration.RestrictDefaultNamespace) { + return item.Namespace.Name !== 'default' && !this.isSystemNamespace(item); + } else { + return !this.isSystemNamespace(item); + } }; this.disableRemove = function (item) { @@ -41,6 +46,7 @@ angular.module('portainer.docker').controller('KubernetesResourcePoolsDatatableC }; this.$onInit = function () { + this.endpoint = EndpointProvider.currentEndpoint(); this.isAdmin = Authentication.isAdmin(); this.setDefaults(); this.prepareTableFromDataset(); diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html index 78e7b2674..62a3668e5 100644 --- a/app/kubernetes/views/applications/create/createApplication.html +++ b/app/kubernetes/views/applications/create/createApplication.html @@ -78,7 +78,7 @@ Resource pool -
+
-
+
This resource pool has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of the resource pool.
- - -
- Stack -
- -
-
- - Portainer can automatically bundle multiple applications inside a stack. Enter a name of a new stack or select an existing stack in the list. Leave empty to use the - application name. -
-
- -
- -
- -
-
- - -
- Environment -
- -
-
- - - add environment variable - -
- -
-
-
-
- name - -
-
- -

Environment variable name is required.

-

This field must consist alphanumeric characters, '-' or '_', start with an alphabetic - character, and end with an alphanumeric character (e.g. 'my-var', or 'MY_VAR123').

-
-

This environment variable is already defined.

-
-
- -
- value - -
- -
- - -
-
-
-
- - -
- Configurations -
- -
-
- - - add configuration - -
-
- - Portainer will automatically expose all the keys of a configuration as environment variables. This behavior can be overriden to filesystem mounts for each key via - the override button. -
-
- - -
- -
- -
-
- - - -
- -
-
-
- The following keys will be loaded from the {{ config.SelectedConfiguration.Name }} configuration as environment variables: - - {{ key }}{{ $last ? '' : ', ' }} - -
-
- - - -
-
-
-
- configuration key - -
- -
-
- path on disk - -
-
- -

Path is required.

-
-

This path is already used.

-
-
- -
- - -
-
-
- -
- - - -
- Persisting data -
- -
+
- No storage option is available to persist data, contact your administrator to enable a storage option. -
-
- -
-
- - - add persisted folder - -
- -
-
-
- path in container - -
- -
- - - - -
- -
- requested size - - - - -
- -
- storage - - -
- -
- volume - -
- -
-
- - -
-
-
- -
-
-
- -

Path is required.

-
-

This path is already defined.

-
-
- -
- -
-
- -

Size is required.

-

This value must be greater than zero.

-
-
-
- -

Volume is required.

-
-

This volume is already used.

-
-
- -
- -
-
+ You do not have access to any resource pool. Contact your administrator to get access to a resource pool.
- -
-
-
- -
-
- -
-
- Specify how the data will be used across instances. -
-
- - -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- -
- - -
- Resource reservations -
- -
-
- - Resource reservations are applied per instance of the application. -
-
- -
-
- - A resource quota is set on this resource pool, you must specify resource reservations. Resource reservations are applied per instance of the application. Maximums - are inherited from the resource pool quota. -
-
- -
-
- - This resource pool has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of the - resource pool. -
-
- - -
- -
- -
-
- -
-
-

- Maximum memory usage (MB) -

-
-
-
-
-
-

Value must be between {{ ctrl.state.sliders.memory.min }} and {{ ctrl.state.sliders.memory.max }} -

-
-
-
- - -
- -
- -
-
-

- Maximum CPU usage -

-
-
- - - -
- Deployment -
- -
-
- Select how you want to deploy your application inside the cluster. -
-
- - -
-
-
- - -
-
- - -
-
- - -
-
-
- - - -
-
- - -
-
- - -
-
- - This application will reserve the following resources: {{ ctrl.formValues.CpuLimit * ctrl.formValues.ReplicaCount | kubernetesApplicationCPUValue }} CPU and - {{ ctrl.formValues.MemoryLimit * ctrl.formValues.ReplicaCount }} MB of memory. -
-
- -
-
- - This application would exceed available resources. Please review resource reservations or the instance count. -
-
- -
-
- - The following storage option(s) do not support concurrent access from multiples instances: {{ ctrl.getNonScalableStorage() }}. You will not be able to scale that application. -
-
- - - -
- Auto-scaling -
- -
-
- - -
-
- -
-
-

- This feature is currently disabled and must be enabled by an administrator user. -

-

- Server metrics features must be enabled in the - endpoint configuration view. -

-
-
- -
- - - - - - - - - - - - - -
Minimum instancesMaximum instances - Target CPU usage (%) - - -
-
- -
-
-
- -

Minimum instances is required.

-

Minimum instances must be greater than 0.

-

Minimum instances must be smaller than maximum instances.

-
-
-
-
-
- -
-
-
- -

Maximum instances is required.

-

Maximum instances must be greater than minimum instances.

-
-
-
-
-
- -
-
-
- -

Target CPU usage is required.

-

Target CPU usage must be greater than 0.

-

Target CPU usage must be smaller than 100.

-
-
-
-
- -
-
- - This application would exceed available resources. Please review resource reservations or the maximum instance count of the auto-scaling policy. -
-
-
- - -
+
- Placement preferences and constraints + Stack +
+ +
+
+ + Portainer can automatically bundle multiple applications inside a stack. Enter a name of a new stack or select an existing stack in the list. Leave empty to use + the application name. +
- +
+ +
+ +
+
+ + +
+ Environment +
+
- - - add rule + + + add environment variable
-
- - Deploy this application on nodes that respect ALL of the following placement rules. Placement rules are based on node labels. -
-
-
-
- +
+
- -
-
- + +

Environment variable name is required.

+

This field must consist alphanumeric characters, '-' or '_', start with an alphabetic + character, and end with an alphanumeric character (e.g. 'my-var', or 'MY_VAR123').

+
+

This environment variable is already defined.

+
-
- -
-
-
-
-

- This label is already defined. -

+
+
+ + +
+ Configurations +
+ +
+
+ + + add configuration + +
+
+ + Portainer will automatically expose all the keys of a configuration as environment variables. This behavior can be overriden to filesystem mounts for each key via + the override button. +
+
+ + +
+ +
+ +
+
+ + + +
+ +
+
+
+ The following keys will be loaded from the {{ config.SelectedConfiguration.Name }} configuration as environment variables: + + {{ key }}{{ $last ? '' : ', ' }} + +
+
+ + + +
+
+
+
+ configuration key + +
+ +
+
+ path on disk +
+
+ +

Path is required.

+
+

This path is already used.

+
+
+ +
+ +
+
-
+ + + +
+ Persisting data +
+ +
+
+ + No storage option is available to persist data, contact your administrator to enable a storage option. +
+
+ +
+
+ + + add persisted folder + +
+ +
+
+
+ path in container + +
+ +
+ + + + +
+ +
+ requested size + + + + +
+ +
+ storage + + +
+ +
+ volume + +
+ +
+
+ + +
+
+
+ +
+
+
+ +

Path is required.

+
+

This path is already defined.

+
+
+ +
+ +
+
+ +

Size is required.

+

This value must be greater than zero.

+
+
+
+ +

Volume is required.

+
+

This volume is already used.

+
+
+ +
+ +
+
+
+
+ + + +
- +
- Specify the policy associated to the placement rules. + Specify how the data will be used across instances.
- -
+ +
-
- -
- + +
+ + +
+ Resource reservations +
+ +
+
+ + Resource reservations are applied per instance of the application. +
+
+ +
+
+ + A resource quota is set on this resource pool, you must specify resource reservations. Resource reservations are applied per instance of the application. Maximums + are inherited from the resource pool quota. +
+
+ +
+
+ + This resource pool has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of + the resource pool. +
+
+ + +
+ +
+ +
+
+ +
+
+

+ Maximum memory usage (MB) +

+
+
+
+
+
+

Value must be between {{ ctrl.state.sliders.memory.min }} and + {{ ctrl.state.sliders.memory.max }} +

+
+
+
+ + +
+ +
+ +
+
+

+ Maximum CPU usage +

+
+
+ + + +
+ Deployment +
+ +
+
+ Select how you want to deploy your application inside the cluster. +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+ + +
+
+ + +
+
+ + This application will reserve the following resources: + {{ ctrl.formValues.CpuLimit * ctrl.formValues.ReplicaCount | kubernetesApplicationCPUValue }} CPU and + {{ ctrl.formValues.MemoryLimit * ctrl.formValues.ReplicaCount }} MB of memory. +
+
+ +
+
+ + This application would exceed available resources. Please review resource reservations or the instance count. +
+
+ +
+
+ + The following storage option(s) do not support concurrent access from multiples instances: {{ ctrl.getNonScalableStorage() }}. You will not be able to scale that application. +
+
+ + + +
+ Auto-scaling +
+ +
+
+ + +
+
+ +
+
+

+ This feature is currently disabled and must be enabled by an administrator user. +

+

+ Server metrics features must be enabled in the + endpoint configuration view. +

+
+
+ +
+ + + + + + + + + + + + + +
Minimum instancesMaximum instances + Target CPU usage (%) + + +
+
+ +
+
+
+ +

Minimum instances is required.

+

Minimum instances must be greater than 0.

+

Minimum instances must be smaller than maximum instances.

+
+
+
+
+
+ +
+
+
+ +

Maximum instances is required.

+

Maximum instances must be greater than minimum instances.

+
+
+
+
+
+ +
+
+
+ +

Target CPU usage is required.

+

Target CPU usage must be greater than 0.

+

Target CPU usage must be smaller than 100.

+
+
+
+
+ +
+
+ + This application would exceed available resources. Please review resource reservations or the maximum instance count of the auto-scaling policy. +
+
+
+ + +
+
+ Placement preferences and constraints +
+ + +
+
+ + + add rule + +
+ +
+ + Deploy this application on nodes that respect ALL of the following placement rules. Placement rules are based on node labels. +
+ +
+
+
+ +
+
+ +
+ +
+ + +
+
+
+
+
+

+ This label is already defined. +

+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ Specify the policy associated to the placement rules. +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
+ +
+ +
+ Publishing the application +
+ +
+
+ Select how you want to publish your application. +
+
+ + +
+
+
+ + + +
+ +
+ + + +
+
+ + + +
+
+ + + +
+
+
+ + + +
+
+ + + publish a new port + +
+ +
+ + When publishing a port in cluster mode, the node port is optional. If left empty Kubernetes will use a random port number. If you wish to specify a port, use a + port number inside the default range 30000-32767. +
+
+ At least one published port must be defined. +
+ +
+ +
+
+ container port + +
+ +
+ node port + +
+ +
+ load balancer port + +
+ +
+ ingress + +
+ +
+ route + +
+ +
+
+ + +
+ + +
+
+ + + +
+
+
+
+

Container port number is required.

+

Container port number must be inside the range 1-65535.

+

Container port number must be inside the range 1-65535.

+
+

+ This port is already used. +

+
+
+ +
+
+
+

Node port number must be inside the range 30000-32767.

+

Node port number must be inside the range 30000-32767.

+
+

+ This port is already used. +

+
+
+ +
+
+
+

Ingress selection is required.

+
+
+
+
+
+
+

Route is required.

+

This field must consist of alphanumeric characters or the special characters: '-', '_' or + '/'. It must start and end with an alphanumeric character (e.g. 'my-route', or 'route-123').

+
+

+ This route is already used. +

+
+
+ +
+
+
+

Load balancer port number is required.

+

Load balancer port number must be inside the range 1-65535.

+

Load balancer port number must be inside the range 1-65535.

+
+

+ + This port is already used. +

+
+
+ +
+
+ +
-
- Publishing the application -
- -
-
- Select how you want to publish your application. -
-
- - -
-
-
- - - -
- -
- - - -
-
- - - -
-
- - - -
-
-
- - - -
-
- - - publish a new port - -
- -
- - When publishing a port in cluster mode, the node port is optional. If left empty Kubernetes will use a random port number. If you wish to specify a port, use a port - number inside the default range 30000-32767. -
-
- At least one published port must be defined. -
- -
- -
-
- container port - -
- -
- node port - -
- -
- load balancer port - -
- -
- ingress - -
- -
- route - -
- -
-
- - -
- - -
-
- - - -
-
-
-
-

Container port number is required.

-

Container port number must be inside the range 1-65535.

-

Container port number must be inside the range 1-65535.

-
-

- This port is already used. -

-
-
- -
-
-
-

Node port number must be inside the range 30000-32767.

-

Node port number must be inside the range 30000-32767.

-
-

- This port is already used. -

-
-
- -
-
-
-

Ingress selection is required.

-
-
-
-
-
-
-

Route is required.

-

This field must consist of alphanumeric characters or the special characters: '-', '_' or - '/'. It must start and end with an alphanumeric character (e.g. 'my-route', or 'route-123').

-
-

- This route is already used. -

-
-
- -
-
-
-

Load balancer port number is required.

-

Load balancer port number must be inside the range 1-65535.

-

Load balancer port number must be inside the range 1-65535.

-
-

- - This port is already used. -

-
-
- -
-
- -
-
- -
Actions
diff --git a/app/kubernetes/views/applications/create/createApplicationController.js b/app/kubernetes/views/applications/create/createApplicationController.js index 01d250d04..9e59c1dd6 100644 --- a/app/kubernetes/views/applications/create/createApplicationController.js +++ b/app/kubernetes/views/applications/create/createApplicationController.js @@ -589,7 +589,8 @@ class KubernetesCreateApplicationController { const hasNoChanges = this.isEditAndNoChangesMade(); const nonScalable = this.isNonScalable(); const notInternalNoPorts = this.isNotInternalAndHasNoPublishedPorts(); - return overflow || autoScalerOverflow || inProgress || invalid || hasNoChanges || nonScalable || notInternalNoPorts; + const noResourcePool = !this.formValues.ResourcePool; + return overflow || autoScalerOverflow || inProgress || invalid || hasNoChanges || nonScalable || notInternalNoPorts || noResourcePool; } disableLoadBalancerEdit() { @@ -879,7 +880,11 @@ class KubernetesCreateApplicationController { this.ingresses = ingresses; this.resourcePools = _.filter(resourcePools, (resourcePool) => !this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name)); + this.formValues.ResourcePool = this.resourcePools[0]; + if (!this.formValues.ResourcePool) { + return; + } // TODO: refactor @Max // Don't pull all volumes and applications across all namespaces diff --git a/app/kubernetes/views/configurations/create/createConfiguration.html b/app/kubernetes/views/configurations/create/createConfiguration.html index 446f7a334..b56efc147 100644 --- a/app/kubernetes/views/configurations/create/createConfiguration.html +++ b/app/kubernetes/views/configurations/create/createConfiguration.html @@ -48,7 +48,7 @@
-
+
-
+
This resource pool has exhausted its resource capacity and you will not be able to deploy the configuration. Contact your administrator to expand the capacity of the resource pool.
+
+
+ + You do not have access to any resource pool. Contact your administrator to get access to a resource pool. +
+
-
- Configuration type -
- -
-
- Select the type of data that you want to save in the configuration. +
+
+ Configuration type
-
- -
-
-
- - -
-
- - +
+
+ Select the type of data that you want to save in the configuration.
-
- - + +
+
+
+ + +
+
+ + +
+
+
+ + + +
diff --git a/app/kubernetes/views/configurations/create/createConfigurationController.js b/app/kubernetes/views/configurations/create/createConfigurationController.js index 7182c267d..73d94049d 100644 --- a/app/kubernetes/views/configurations/create/createConfigurationController.js +++ b/app/kubernetes/views/configurations/create/createConfigurationController.js @@ -28,9 +28,9 @@ class KubernetesCreateConfigurationController { isFormValid() { const uniqueCheck = !this.state.alreadyExist && this.state.isDataValid; if (this.formValues.IsSimple) { - return this.formValues.Data.length > 0 && uniqueCheck; + return this.formValues.Data.length > 0 && uniqueCheck && this.formValues.ResourcePool; } - return uniqueCheck; + return uniqueCheck && this.formValues.ResourcePool; } async createConfigurationAsync() { @@ -79,6 +79,9 @@ class KubernetesCreateConfigurationController { this.resourcePools = _.filter(resourcePools, (resourcePool) => !this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name)); this.formValues.ResourcePool = this.resourcePools[0]; + if (!this.formValues.ResourcePool) { + return; + } await this.getConfigurations(); } catch (err) { this.Notifications.error('Failure', err, 'Unable to load view data'); diff --git a/app/kubernetes/views/configure/configure.html b/app/kubernetes/views/configure/configure.html index 293fcb7e2..e31fb3812 100644 --- a/app/kubernetes/views/configure/configure.html +++ b/app/kubernetes/views/configure/configure.html @@ -131,6 +131,27 @@
+ +
+ Security +
+ +
+ + By default, all the users have access to the default namespace. Enable this option to set accesses on the default namespace. + +
+ +
+
+ + +
+
+ +
Resources and Metrics
diff --git a/app/kubernetes/views/configure/configureController.js b/app/kubernetes/views/configure/configureController.js index 16757afa4..b3a643734 100644 --- a/app/kubernetes/views/configure/configureController.js +++ b/app/kubernetes/views/configure/configureController.js @@ -103,6 +103,7 @@ class KubernetesConfigureController { endpoint.Kubernetes.Configuration.EnableResourceOverCommit = this.formValues.EnableResourceOverCommit; endpoint.Kubernetes.Configuration.ResourceOverCommitPercentage = this.formValues.ResourceOverCommitPercentage; endpoint.Kubernetes.Configuration.IngressClasses = ingressClasses; + endpoint.Kubernetes.Configuration.RestrictDefaultNamespace = this.formValues.RestrictDefaultNamespace; } transformFormValues() { @@ -193,6 +194,7 @@ class KubernetesConfigureController { EnableResourceOverCommit: false, ResourceOverCommitPercentage: 80, IngressClasses: [], + RestrictDefaultNamespace: false, }; try { @@ -217,6 +219,7 @@ class KubernetesConfigureController { this.formValues.UseServerMetrics = this.endpoint.Kubernetes.Configuration.UseServerMetrics; this.formValues.EnableResourceOverCommit = this.endpoint.Kubernetes.Configuration.EnableResourceOverCommit; this.formValues.ResourceOverCommitPercentage = this.endpoint.Kubernetes.Configuration.ResourceOverCommitPercentage; + this.formValues.RestrictDefaultNamespace = this.endpoint.Kubernetes.Configuration.RestrictDefaultNamespace; this.formValues.IngressClasses = _.map(this.endpoint.Kubernetes.Configuration.IngressClasses, (ic) => { ic.IsNew = false; ic.NeedsDeletion = false;