diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue index 3f7c2204b9f31cadc579e45735669c2bb2ebe173..eb195ad2b30af787e1d1e2a9418fb0165eff41d2 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue @@ -13,6 +13,10 @@ export default { type: String, required: true, }, + namespacePerEnvironmentHelpPath: { + type: String, + required: true, + }, kubernetesIntegrationHelpPath: { type: String, required: true, @@ -40,6 +44,7 @@ export default { <eks-cluster-configuration-form v-if="hasCredentials" :gitlab-managed-cluster-help-path="gitlabManagedClusterHelpPath" + :namespace-per-environment-help-path="namespacePerEnvironmentHelpPath" :kubernetes-integration-help-path="kubernetesIntegrationHelpPath" :external-link-icon="externalLinkIcon" /> diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue index a653e228e3f19653b8da4db4749c85a64829975f..0249b485e202613689c8117669ec36fd42e3c3da 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue @@ -37,6 +37,10 @@ export default { type: String, required: true, }, + namespacePerEnvironmentHelpPath: { + type: String, + required: true, + }, kubernetesIntegrationHelpPath: { type: String, required: true, @@ -60,6 +64,7 @@ export default { 'selectedInstanceType', 'nodeCount', 'gitlabManagedCluster', + 'namespacePerEnvironment', 'isCreatingCluster', ]), ...mapGetters(['subnetValid']), @@ -270,6 +275,20 @@ export default { false, ); }, + namespacePerEnvironmentHelpText() { + const escapedUrl = escape(this.namespacePerEnvironmentClusterHelpPath); + + return sprintf( + s__( + 'ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared. %{startLink}More information%{endLink}', + ), + { + startLink: `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer">`, + endLink: '</a>', + }, + false, + ); + }, }, mounted() { this.fetchRegions(); @@ -290,6 +309,7 @@ export default { 'setInstanceType', 'setNodeCount', 'setGitlabManagedCluster', + 'setNamespacePerEnvironment', ]), ...mapRegionsActions({ fetchRegions: 'fetchItems' }), ...mapVpcActions({ fetchVpcs: 'fetchItems' }), @@ -519,6 +539,14 @@ export default { > <p class="form-text text-muted" v-html="gitlabManagedHelpText"></p> </div> + <div class="form-group"> + <gl-form-checkbox + :checked="namespacePerEnvironment" + @input="setNamespacePerEnvironment({ namespacePerEnvironment: $event })" + >{{ s__('ClusterIntegration|Namespace per environment') }}</gl-form-checkbox + > + <p class="form-text text-muted" v-html="namespacePerEnvironmentHelpText"></p> + </div> <div class="form-group"> <loading-button class="js-create-cluster btn-success" diff --git a/app/assets/javascripts/create_cluster/eks_cluster/index.js b/app/assets/javascripts/create_cluster/eks_cluster/index.js index fb993a7aa598b89bbadd53e8c651af60b558f183..6d1034b4a72b5c87fd475b20db330a6172624793 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/index.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/index.js @@ -9,6 +9,7 @@ Vue.use(Vuex); export default el => { const { gitlabManagedClusterHelpPath, + namespacePerEnvironmentHelpPath, kubernetesIntegrationHelpPath, accountAndExternalIdsHelpPath, createRoleArnHelpPath, @@ -42,6 +43,7 @@ export default el => { return createElement('create-eks-cluster', { props: { gitlabManagedClusterHelpPath, + namespacePerEnvironmentHelpPath, kubernetesIntegrationHelpPath, accountAndExternalIdsHelpPath, createRoleArnHelpPath, diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js index 5abff3c7831c7526668d26d06776c46d42a4826a..48c85ff627ff1340b9db8a9eab9c579bde81a8ee 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js @@ -55,6 +55,7 @@ export const createCluster = ({ dispatch, state }) => { name: state.clusterName, environment_scope: state.environmentScope, managed: state.gitlabManagedCluster, + namespace_per_environment: state.namespacePerEnvironment, provider_aws_attributes: { kubernetes_version: state.kubernetesVersion, region: state.selectedRegion, @@ -114,6 +115,10 @@ export const setGitlabManagedCluster = ({ commit }, payload) => { commit(types.SET_GITLAB_MANAGED_CLUSTER, payload); }; +export const setNamespacePerEnvironment = ({ commit }, payload) => { + commit(types.SET_NAMESPACE_PER_ENVIRONMENT, payload); +}; + export const setInstanceType = ({ commit }, payload) => { commit(types.SET_INSTANCE_TYPE, payload); }; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js index 9dee6abae5fdb71e9992a43eeae77d40e177b00f..4a48195a27b683c8ed522dee08c7116f3f9b74a1 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js @@ -10,6 +10,7 @@ export const SET_SECURITY_GROUP = 'SET_SECURITY_GROUP'; export const SET_INSTANCE_TYPE = 'SET_INSTANCE_TYPE'; export const SET_NODE_COUNT = 'SET_NODE_COUNT'; export const SET_GITLAB_MANAGED_CLUSTER = 'SET_GITLAB_MANAGED_CLUSTER'; +export const SET_NAMESPACE_PER_ENVIRONMENT = 'SET_NAMESPACE_PER_ENVIRONMENT'; export const REQUEST_CREATE_ROLE = 'REQUEST_CREATE_ROLE'; export const CREATE_ROLE_SUCCESS = 'CREATE_ROLE_SUCCESS'; export const CREATE_ROLE_ERROR = 'CREATE_ROLE_ERROR'; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js index c331d27d255c5b595aaf55dc8a18011215308492..f57236e0e31b40eb785d3d413efa4bad8f05cdf9 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js @@ -37,6 +37,9 @@ export default { [types.SET_GITLAB_MANAGED_CLUSTER](state, { gitlabManagedCluster }) { state.gitlabManagedCluster = gitlabManagedCluster; }, + [types.SET_NAMESPACE_PER_ENVIRONMENT](state, { namespacePerEnvironment }) { + state.namespacePerEnvironment = namespacePerEnvironment; + }, [types.REQUEST_CREATE_ROLE](state) { state.isCreatingRole = true; state.createRoleError = null; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js index ed51e95e434ce81fa38a351bcb748a50f8d4cc7c..c957eca1f7afbd46796ca1d17a87c98f77984e38 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js @@ -30,4 +30,5 @@ export default () => ({ createClusterError: false, gitlabManagedCluster: true, + namespacePerEnvironment: true, }); diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 7006c23321c8fe94381513fb820d67cb470b240d..52719e90e04f39c1db1922f12a8287ab0cc53d6c 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -180,13 +180,20 @@ class Clusters::ClustersController < Clusters::BaseController params.permit(:cleanup) end + def base_permitted_cluster_params + [ + :enabled, + :environment_scope, + :managed, + :namespace_per_environment + ] + end + def update_params if cluster.provided_by_user? params.require(:cluster).permit( - :enabled, + *base_permitted_cluster_params, :name, - :environment_scope, - :managed, :base_domain, :management_project_id, platform_kubernetes_attributes: [ @@ -198,9 +205,7 @@ class Clusters::ClustersController < Clusters::BaseController ) else params.require(:cluster).permit( - :enabled, - :environment_scope, - :managed, + *base_permitted_cluster_params, :base_domain, :management_project_id, platform_kubernetes_attributes: [ @@ -212,10 +217,8 @@ class Clusters::ClustersController < Clusters::BaseController def create_gcp_cluster_params params.require(:cluster).permit( - :enabled, + *base_permitted_cluster_params, :name, - :environment_scope, - :managed, provider_gcp_attributes: [ :gcp_project_id, :zone, @@ -232,10 +235,8 @@ class Clusters::ClustersController < Clusters::BaseController def create_aws_cluster_params params.require(:cluster).permit( - :enabled, + *base_permitted_cluster_params, :name, - :environment_scope, - :managed, provider_aws_attributes: [ :kubernetes_version, :key_name, @@ -255,10 +256,8 @@ class Clusters::ClustersController < Clusters::BaseController def create_user_cluster_params params.require(:cluster).permit( - :enabled, + *base_permitted_cluster_params, :name, - :environment_scope, - :managed, platform_kubernetes_attributes: [ :namespace, :api_url, diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb index 9872bbf80b5bc44e741a1541a5d06b33db9edcfb..b904666971e22e0dcdc14bf203d5f3ad5c699737 100644 --- a/app/serializers/cluster_entity.rb +++ b/app/serializers/cluster_entity.rb @@ -7,6 +7,7 @@ class ClusterEntity < Grape::Entity expose :enabled expose :environment_scope expose :id + expose :namespace_per_environment expose :name expose :nodes expose :provider_type diff --git a/app/views/clusters/clusters/_provider_details_form.html.haml b/app/views/clusters/clusters/_provider_details_form.html.haml index fcb5d4402d6ca712dcba0c0f1f32cb248494dd2e..16891c7fc21171d743fd54e213c38059b5a87ce7 100644 --- a/app/views/clusters/clusters/_provider_details_form.html.haml +++ b/app/views/clusters/clusters/_provider_details_form.html.haml @@ -42,11 +42,17 @@ class: 'js-gl-managed', label_class: 'label-bold' } .form-text.text-muted - = s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.') + = s_('ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster.') = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank' + .form-group + = field.check_box :namespace_per_environment, { label: s_('ClusterIntegration|Namespace per environment'), label_class: 'label-bold' } + .form-text.text-muted + = s_('ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared.') + = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'custom-namespace'), target: '_blank' + - if cluster.allow_user_defined_namespace? - = render('clusters/clusters/namespace', platform_field: platform_field) + = render('clusters/clusters/namespace', platform_field: platform_field, field: field) .form-group = field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success' diff --git a/app/views/clusters/clusters/aws/_new.html.haml b/app/views/clusters/clusters/aws/_new.html.haml index 3eab9b46fb3b5fcb5ff5116c89e1605ce3f119db..b1a277faae9096efbda369ed66771f243cdc76da 100644 --- a/app/views/clusters/clusters/aws/_new.html.haml +++ b/app/views/clusters/clusters/aws/_new.html.haml @@ -4,6 +4,7 @@ = s_('Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_start: documentation_link_start, link_end: '<a/>'.html_safe } - else .js-create-eks-cluster-form-container{ data: { 'gitlab-managed-cluster-help-path' => help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), + 'namespace-per-environment-help-path' => help_page_path('user/project/clusters/index.md', anchor: 'custom-namespace'), 'create-role-path' => clusterable.authorize_aws_role_path, 'create-cluster-path' => clusterable.create_aws_clusters_path, 'account-id' => Gitlab::CurrentSettings.eks_account_id, diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml index 434c02a5c415dc125ef2f82e0b6951ae6cd0d777..ceb6e1d46b0e3151f9f0eab6855973f97b694a89 100644 --- a/app/views/clusters/clusters/gcp/_form.html.haml +++ b/app/views/clusters/clusters/gcp/_form.html.haml @@ -75,9 +75,15 @@ = field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'), label_class: 'label-bold' } .form-text.text-muted - = s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.') + = s_('ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster.') = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank' + .form-group + = field.check_box :namespace_per_environment, { label: s_('ClusterIntegration|Namespace per environment'), label_class: 'label-bold' } + .form-text.text-muted + = s_('ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared.') + = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'custom-namespace'), target: '_blank' + .form-group.js-gke-cluster-creation-submit-container = field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml index 11772107135cfbddc451a0f9a7457734764a6d8e..a6097038b2eaaa43e071f716d93bfc616da70644 100644 --- a/app/views/clusters/clusters/user/_form.html.haml +++ b/app/views/clusters/clusters/user/_form.html.haml @@ -46,9 +46,15 @@ class: 'js-gl-managed', label_class: 'label-bold' } .form-text.text-muted - = s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.') + = s_('ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster.') = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank' + .form-group + = field.check_box :namespace_per_environment, { label: s_('ClusterIntegration|Namespace per environment'), label_class: 'label-bold' } + .form-text.text-muted + = s_('ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared.') + = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'custom-namespace'), target: '_blank' + = field.fields_for :platform_kubernetes, @user_cluster.platform_kubernetes do |platform_kubernetes_field| - if @user_cluster.allow_user_defined_namespace? = render('clusters/clusters/namespace', platform_field: platform_kubernetes_field) diff --git a/changelogs/unreleased/expose-clusters-namespace-per-environment-flag.yml b/changelogs/unreleased/expose-clusters-namespace-per-environment-flag.yml new file mode 100644 index 0000000000000000000000000000000000000000..99c630b59048f73a2dd88827a9309cecb91bbfdf --- /dev/null +++ b/changelogs/unreleased/expose-clusters-namespace-per-environment-flag.yml @@ -0,0 +1,6 @@ +--- +title: Expose the option to use namespace-per-project instead of namespace-per-environment + for Kubernetes clusters +merge_request: 42309 +author: +type: changed diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 0b755a81616f079cabf649cb5d1067bffd4a8f0b..c578af3dd35261c3ead7ad8422c4b6b6396e5a6e 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -270,20 +270,30 @@ If your cluster was created before GitLab 12.2, default `KUBE_NAMESPACE` will be ### Custom namespace -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27630) in GitLab 12.6. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27630) in GitLab 12.6. +> - An option to use project-wide namespaces [was added](https://gitlab.com/gitlab-org/gitlab/-/issues/38054) in GitLab 13.5. -The Kubernetes integration defaults to project-environment-specific namespaces -of the form `<project_name>-<project_id>-<environment>` (see [Deployment +The Kubernetes integration provides a `KUBECONFIG` with an auto-generated namespace +to deployment jobs. It defaults to using project-environment specific namespaces +of the form `<prefix>-<environment>`, where `<prefix>` is of the form `<project_name>-<project_id>` (see [Deployment variables](#deployment-variables)). -For **non**-GitLab-managed clusters, the namespace can be customized using -[`environment:kubernetes:namespace`](../../../ci/environments/index.md#configuring-kubernetes-deployments) +The deployment namespace can be customized in a few ways: + +- You can choose between a **namespace per [environment](../../../ci/environments/index.md)** or a **namespace per project**. A namespace per environment is the default and recommended setting, as it prevents the mixing of resources between production and non-production environments. +- When using a project-level cluster, you can additionally customize the namespace prefix. When using namespace-per-environment, the deployment namespace is `<prefix>-<environment>`, but otherwise just `<prefix>`. +- For **non-managed** clusters, the auto-generated namespace is set in the `KUBECONFIG`, but the user is responsible for ensuring its existence. The value can be fully customized using [`environment:kubernetes:namespace`](../../../ci/environments/index.md#configuring-kubernetes-deployments) in `.gitlab-ci.yml`. NOTE: **Note:** -When using a [GitLab-managed cluster](#gitlab-managed-clusters), the -namespaces are created automatically prior to deployment and [can not be -customized](https://gitlab.com/gitlab-org/gitlab/-/issues/38054). +When you customize the namespace, existing environments remain linked to their current namespaces until you [clear the cluster cache](#clearing-the-cluster-cache). + +CAUTION: **Warning:** +By default, anyone who can create a deployment job can access any CI variable within an environment's deployment job. This includes `KUBECONFIG`, which gives access to any secret available to the associated service account in your cluster. +To keep your production credentials safe, consider using [Protected Environments](../../../ci/environments/protected_environments.md), combined with either + +- a GitLab-managed cluster and namespace per environment, +- *or*, an environment-scoped cluster per protected environment (the same cluster can be added multiple times with multiple restricted service accounts). ### Integrations diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb index 8208d10c0899c8c1ef8cd81247022995513f8803..0db2321199aa8511d83118f988657afd37caf355 100644 --- a/lib/api/admin/instance_clusters.rb +++ b/lib/api/admin/instance_clusters.rb @@ -37,6 +37,7 @@ module API requires :name, type: String, desc: 'Cluster name' optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true' optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :domain, type: String, desc: 'Cluster base domain' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true' @@ -70,6 +71,7 @@ module API optional :name, type: String, desc: 'Cluster name' optional :enabled, type: Boolean, desc: 'Enable or disable Gitlab\'s connection to your Kubernetes cluster' optional :environment_scope, type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :domain, type: String, desc: 'Cluster base domain' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do diff --git a/lib/api/entities/cluster.rb b/lib/api/entities/cluster.rb index 4cb54e988ce77ef62e21587b1b27bea0ffa25933..67459092a33411ee29060a2cae417bb37f75cc0d 100644 --- a/lib/api/entities/cluster.rb +++ b/lib/api/entities/cluster.rb @@ -4,7 +4,7 @@ module API module Entities class Cluster < Grape::Entity expose :id, :name, :created_at, :domain - expose :provider_type, :platform_type, :environment_scope, :cluster_type + expose :provider_type, :platform_type, :environment_scope, :cluster_type, :namespace_per_environment expose :user, using: Entities::UserBasic expose :platform_kubernetes, using: Entities::Platform::Kubernetes expose :provider_gcp, using: Entities::Provider::Gcp diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb index ae41d9f13b81e812871c0512c2507f25e959b229..77095ee62e0b55a9d5db4e116507b02acdd4e237 100644 --- a/lib/api/group_clusters.rb +++ b/lib/api/group_clusters.rb @@ -41,6 +41,7 @@ module API requires :name, type: String, desc: 'Cluster name' optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true' optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :domain, type: String, desc: 'Cluster base domain' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true' @@ -74,6 +75,7 @@ module API optional :name, type: String, desc: 'Cluster name' optional :domain, type: String, desc: 'Cluster base domain' optional :environment_scope, type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb index 0e5605984e63f56dfbe3fc17f719db3c2abef7f4..6f189110d76b5f4d79cebb37658dc05e346aa8e6 100644 --- a/lib/api/project_clusters.rb +++ b/lib/api/project_clusters.rb @@ -45,6 +45,7 @@ module API optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true' optional :domain, type: String, desc: 'Cluster base domain' optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true' requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do @@ -78,6 +79,7 @@ module API optional :name, type: String, desc: 'Cluster name' optional :domain, type: String, desc: 'Cluster base domain' optional :environment_scope, type: String, desc: 'The associated environment to the cluster' + optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 600e5833438b3dc598a21640250b5bd2406d05d5..23d5aa9076d8214290baec21d348ec76f4a31dd6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5379,10 +5379,10 @@ msgstr "" msgid "ClusterIntegration|All installed applications and related resources" msgstr "" -msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster." +msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{startLink}More information%{endLink}" msgstr "" -msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{startLink}More information%{endLink}" +msgid "ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster." msgstr "" msgid "ClusterIntegration|Alternatively, " @@ -5565,6 +5565,12 @@ msgstr "" msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal" msgstr "" +msgid "ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared." +msgstr "" + +msgid "ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared. %{startLink}More information%{endLink}" +msgstr "" + msgid "ClusterIntegration|Did you know?" msgstr "" @@ -5826,6 +5832,9 @@ msgstr "" msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{provider_link}" msgstr "" +msgid "ClusterIntegration|Namespace per environment" +msgstr "" + msgid "ClusterIntegration|No IAM Roles found" msgstr "" diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index d2a569a9d488a26837f273ebe134351582d3895a..69bdc79c5f550a998a3f7264e23d37bd30279894 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -416,6 +416,7 @@ RSpec.describe Admin::ClustersController do expect(cluster).to be_user expect(cluster).to be_kubernetes expect(cluster).to be_platform_kubernetes_rbac + expect(cluster).to be_namespace_per_environment end end end @@ -585,6 +586,7 @@ RSpec.describe Admin::ClustersController do enabled: false, name: 'my-new-cluster-name', managed: false, + namespace_per_environment: false, base_domain: domain } } @@ -599,6 +601,7 @@ RSpec.describe Admin::ClustersController do expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') expect(cluster).not_to be_managed + expect(cluster).not_to be_namespace_per_environment expect(cluster.domain).to eq('test-domain.com') end @@ -624,6 +627,7 @@ RSpec.describe Admin::ClustersController do enabled: false, name: 'my-new-cluster-name', managed: false, + namespace_per_environment: false, domain: domain } } @@ -637,6 +641,7 @@ RSpec.describe Admin::ClustersController do expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') expect(cluster).not_to be_managed + expect(cluster).not_to be_namespace_per_environment end end diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index 81d5bc7770fb0913a509e5b2f9eb6cf9efc9fa1e..140b7b0f2a8ee5d1166f4bafb8a929237a2d40ec 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -271,6 +271,7 @@ RSpec.describe Groups::ClustersController do expect(cluster).to be_kubernetes expect(cluster.provider_gcp).to be_legacy_abac expect(cluster).to be_managed + expect(cluster).to be_namespace_per_environment end context 'when legacy_abac param is false' do @@ -358,6 +359,7 @@ RSpec.describe Groups::ClustersController do expect(cluster).to be_user expect(cluster).to be_kubernetes expect(cluster).to be_managed + expect(cluster).to be_namespace_per_environment end end @@ -387,6 +389,7 @@ RSpec.describe Groups::ClustersController do expect(cluster).to be_user expect(cluster).to be_kubernetes expect(cluster).to be_platform_kubernetes_rbac + expect(cluster).to be_namespace_per_environment end end @@ -716,6 +719,7 @@ RSpec.describe Groups::ClustersController do enabled: false, name: 'my-new-cluster-name', managed: false, + namespace_per_environment: false, domain: domain } } @@ -729,6 +733,7 @@ RSpec.describe Groups::ClustersController do expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') expect(cluster).not_to be_managed + expect(cluster).not_to be_namespace_per_environment end end diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 51a451570c5cd3d568aa3fb0b6cc831c1250c181..52cd6869b04cdfeb2b23e9bf5994061a0f989025 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -251,6 +251,7 @@ RSpec.describe Projects::ClustersController do cluster: { name: 'new-cluster', managed: '1', + namespace_per_environment: '0', provider_gcp_attributes: { gcp_project_id: 'gcp-project-12345', legacy_abac: legacy_abac_param @@ -278,6 +279,7 @@ RSpec.describe Projects::ClustersController do expect(project.clusters.first).to be_kubernetes expect(project.clusters.first.provider_gcp).to be_legacy_abac expect(project.clusters.first.managed?).to be_truthy + expect(project.clusters.first.namespace_per_environment?).to be_falsy end context 'when legacy_abac param is false' do @@ -369,6 +371,7 @@ RSpec.describe Projects::ClustersController do expect(project.clusters.first).to be_user expect(project.clusters.first).to be_kubernetes + expect(project.clusters.first).to be_namespace_per_environment end end @@ -400,6 +403,7 @@ RSpec.describe Projects::ClustersController do expect(cluster).to be_user expect(cluster).to be_kubernetes expect(cluster).to be_platform_kubernetes_rbac + expect(cluster).to be_namespace_per_environment end end @@ -726,6 +730,7 @@ RSpec.describe Projects::ClustersController do enabled: false, name: 'my-new-cluster-name', managed: false, + namespace_per_environment: false, platform_kubernetes_attributes: { namespace: 'my-namespace' } @@ -742,6 +747,7 @@ RSpec.describe Projects::ClustersController do expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') expect(cluster).not_to be_managed + expect(cluster).not_to be_namespace_per_environment expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') end diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index 90253451d6b4ea5d21c18ac2aac491f29ace4fc9..0a1d2284831b8f7cbe42474331917545bf095c06 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -66,6 +66,10 @@ RSpec.describe 'User Cluster', :js do expect(page.find_field('cluster[platform_kubernetes_attributes][authorization_type]', disabled: true)).to be_checked end end + + it 'user sees namespace per environment is enabled by default' do + expect(page).to have_checked_field('Namespace per environment') + end end context 'when user filled form with invalid parameters' do diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 9d0dc65093e7af921d8a810774f232bfa4ad0539..97d2f20403652a3865fd66247851ab69569fcd7e 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -52,6 +52,10 @@ RSpec.describe 'User Cluster', :js do it 'user sees RBAC is enabled by default' do expect(page).to have_checked_field('RBAC-enabled cluster') end + + it 'user sees namespace per environment is enabled by default' do + expect(page).to have_checked_field('Namespace per environment') + end end context 'when user filled form with invalid parameters' do diff --git a/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js b/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js index 4bf3ac430f5f063bdd53fa3e801c2b1af2a8b4fe..e0913fe2e88223a09cdce61410576f8d9e8efab8 100644 --- a/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js @@ -12,6 +12,7 @@ describe('CreateEksCluster', () => { let vm; let state; const gitlabManagedClusterHelpPath = 'gitlab-managed-cluster-help-path'; + const namespacePerEnvironmentHelpPath = 'namespace-per-environment-help-path'; const accountAndExternalIdsHelpPath = 'account-and-external-id-help-path'; const createRoleArnHelpPath = 'role-arn-help-path'; const kubernetesIntegrationHelpPath = 'kubernetes-integration'; @@ -26,6 +27,7 @@ describe('CreateEksCluster', () => { vm = shallowMount(CreateEksCluster, { propsData: { gitlabManagedClusterHelpPath, + namespacePerEnvironmentHelpPath, accountAndExternalIdsHelpPath, createRoleArnHelpPath, externalLinkIcon, @@ -53,6 +55,12 @@ describe('CreateEksCluster', () => { ); }); + it('help url for namespace per environment cluster documentation', () => { + expect(vm.find(EksClusterConfigurationForm).props('namespacePerEnvironmentHelpPath')).toBe( + namespacePerEnvironmentHelpPath, + ); + }); + it('help url for gitlab managed cluster documentation', () => { expect(vm.find(EksClusterConfigurationForm).props('kubernetesIntegrationHelpPath')).toBe( kubernetesIntegrationHelpPath, diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js index d7dd7072f6740cf069ca9179daa6036076b5203a..2600415fc9fd5f50497a179a48682eb816e273ea 100644 --- a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js @@ -169,6 +169,7 @@ describe('EksClusterConfigurationForm', () => { store, propsData: { gitlabManagedClusterHelpPath: '', + namespacePerEnvironmentHelpPath: '', kubernetesIntegrationHelpPath: '', externalLinkIcon: '', }, diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js index ed75388879081fe32f3d9a7c3f28b43b8aa43c06..f929216689a4f82281553d73e0234e8ab7b7aab4 100644 --- a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js @@ -14,6 +14,7 @@ import { SET_ROLE, SET_SECURITY_GROUP, SET_GITLAB_MANAGED_CLUSTER, + SET_NAMESPACE_PER_ENVIRONMENT, SET_INSTANCE_TYPE, SET_NODE_COUNT, REQUEST_CREATE_ROLE, @@ -40,6 +41,7 @@ describe('EKS Cluster Store Actions', () => { let instanceType; let nodeCount; let gitlabManagedCluster; + let namespacePerEnvironment; let mock; let state; let newClusterUrl; @@ -57,6 +59,7 @@ describe('EKS Cluster Store Actions', () => { instanceType = 'small-1'; nodeCount = '5'; gitlabManagedCluster = true; + namespacePerEnvironment = true; newClusterUrl = '/clusters/1'; @@ -76,19 +79,20 @@ describe('EKS Cluster Store Actions', () => { }); it.each` - action | mutation | payload | payloadDescription - ${'setClusterName'} | ${SET_CLUSTER_NAME} | ${{ clusterName }} | ${'cluster name'} - ${'setEnvironmentScope'} | ${SET_ENVIRONMENT_SCOPE} | ${{ environmentScope }} | ${'environment scope'} - ${'setKubernetesVersion'} | ${SET_KUBERNETES_VERSION} | ${{ kubernetesVersion }} | ${'kubernetes version'} - ${'setRole'} | ${SET_ROLE} | ${{ role }} | ${'role'} - ${'setRegion'} | ${SET_REGION} | ${{ region }} | ${'region'} - ${'setKeyPair'} | ${SET_KEY_PAIR} | ${{ keyPair }} | ${'key pair'} - ${'setVpc'} | ${SET_VPC} | ${{ vpc }} | ${'vpc'} - ${'setSubnet'} | ${SET_SUBNET} | ${{ subnet }} | ${'subnet'} - ${'setSecurityGroup'} | ${SET_SECURITY_GROUP} | ${{ securityGroup }} | ${'securityGroup'} - ${'setInstanceType'} | ${SET_INSTANCE_TYPE} | ${{ instanceType }} | ${'instance type'} - ${'setNodeCount'} | ${SET_NODE_COUNT} | ${{ nodeCount }} | ${'node count'} - ${'setGitlabManagedCluster'} | ${SET_GITLAB_MANAGED_CLUSTER} | ${gitlabManagedCluster} | ${'gitlab managed cluster'} + action | mutation | payload | payloadDescription + ${'setClusterName'} | ${SET_CLUSTER_NAME} | ${{ clusterName }} | ${'cluster name'} + ${'setEnvironmentScope'} | ${SET_ENVIRONMENT_SCOPE} | ${{ environmentScope }} | ${'environment scope'} + ${'setKubernetesVersion'} | ${SET_KUBERNETES_VERSION} | ${{ kubernetesVersion }} | ${'kubernetes version'} + ${'setRole'} | ${SET_ROLE} | ${{ role }} | ${'role'} + ${'setRegion'} | ${SET_REGION} | ${{ region }} | ${'region'} + ${'setKeyPair'} | ${SET_KEY_PAIR} | ${{ keyPair }} | ${'key pair'} + ${'setVpc'} | ${SET_VPC} | ${{ vpc }} | ${'vpc'} + ${'setSubnet'} | ${SET_SUBNET} | ${{ subnet }} | ${'subnet'} + ${'setSecurityGroup'} | ${SET_SECURITY_GROUP} | ${{ securityGroup }} | ${'securityGroup'} + ${'setInstanceType'} | ${SET_INSTANCE_TYPE} | ${{ instanceType }} | ${'instance type'} + ${'setNodeCount'} | ${SET_NODE_COUNT} | ${{ nodeCount }} | ${'node count'} + ${'setGitlabManagedCluster'} | ${SET_GITLAB_MANAGED_CLUSTER} | ${gitlabManagedCluster} | ${'gitlab managed cluster'} + ${'setNamespacePerEnvironment'} | ${SET_NAMESPACE_PER_ENVIRONMENT} | ${namespacePerEnvironment} | ${'namespace per environment'} `(`$action commits $mutation with $payloadDescription payload`, data => { const { action, mutation, payload } = data; @@ -179,6 +183,7 @@ describe('EKS Cluster Store Actions', () => { name: clusterName, environment_scope: environmentScope, managed: gitlabManagedCluster, + namespace_per_environment: namespacePerEnvironment, provider_aws_attributes: { kubernetes_version: kubernetesVersion, region, @@ -204,6 +209,7 @@ describe('EKS Cluster Store Actions', () => { selectedInstanceType: instanceType, nodeCount, gitlabManagedCluster, + namespacePerEnvironment, }); }); diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 024539e34ecb45418be5b597b65fc83a9df79d7a..dd9b96f39ade7d76e817b1a0a7ad2d73932b4382 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -47,6 +47,7 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do it { is_expected.to delegate_method(:external_hostname).to(:application_ingress).with_prefix } it { is_expected.to respond_to :project } + it { is_expected.to be_namespace_per_environment } describe 'applications have inverse_of: :cluster option' do let(:cluster) { create(:cluster) } diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb index b68541b5d9237c1255c4b70a125763cf3407955e..9d0661089a9962ffb8120e4136a90ef910d52ef2 100644 --- a/spec/requests/api/admin/instance_clusters_spec.rb +++ b/spec/requests/api/admin/instance_clusters_spec.rb @@ -162,6 +162,7 @@ RSpec.describe ::API::Admin::InstanceClusters do name: 'test-instance-cluster', domain: 'domain.example.com', managed: false, + namespace_per_environment: false, platform_kubernetes_attributes: platform_kubernetes_attributes, clusterable: clusterable } @@ -206,6 +207,7 @@ RSpec.describe ::API::Admin::InstanceClusters do expect(cluster_result.enabled).to eq(true) expect(platform_kubernetes.authorization_type).to eq('rbac') expect(cluster_result.managed).to be_falsy + expect(cluster_result.namespace_per_environment).to eq(false) expect(platform_kubernetes.api_url).to eq("https://example.com") expect(platform_kubernetes.token).to eq('sample-token') end @@ -235,6 +237,22 @@ RSpec.describe ::API::Admin::InstanceClusters do end end + context 'when namespace_per_environment is not set' do + let(:cluster_params) do + { + name: 'test-cluster', + domain: 'domain.example.com', + platform_kubernetes_attributes: platform_kubernetes_attributes + } + end + + it 'defaults to true' do + cluster_result = Clusters::Cluster.find(json_response['id']) + + expect(cluster_result).to be_namespace_per_environment + end + end + context 'when an instance cluster already exists' do it 'allows user to add multiple clusters' do post api('/admin/clusters/add', admin_user), params: multiple_cluster_params diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb index 068af1485e28b6d97496fec004b9f2dc9b0874fd..eb21ae9468cf4cecf3a5b01f70deb5b39e5b9cfd 100644 --- a/spec/requests/api/group_clusters_spec.rb +++ b/spec/requests/api/group_clusters_spec.rb @@ -172,6 +172,7 @@ RSpec.describe API::GroupClusters do name: 'test-cluster', domain: 'domain.example.com', managed: false, + namespace_per_environment: false, platform_kubernetes_attributes: platform_kubernetes_attributes, management_project_id: management_project_id } @@ -206,6 +207,7 @@ RSpec.describe API::GroupClusters do expect(cluster_result.domain).to eq('domain.example.com') expect(cluster_result.managed).to be_falsy expect(cluster_result.management_project_id).to eq management_project_id + expect(cluster_result.namespace_per_environment).to eq(false) expect(platform_kubernetes.rbac?).to be_truthy expect(platform_kubernetes.api_url).to eq(api_url) expect(platform_kubernetes.token).to eq('sample-token') @@ -237,6 +239,22 @@ RSpec.describe API::GroupClusters do end end + context 'when namespace_per_environment is not set' do + let(:cluster_params) do + { + name: 'test-cluster', + domain: 'domain.example.com', + platform_kubernetes_attributes: platform_kubernetes_attributes + } + end + + it 'defaults to true' do + cluster_result = Clusters::Cluster.find(json_response['id']) + + expect(cluster_result).to be_namespace_per_environment + end + end + context 'current user does not have access to management_project_id' do let(:management_project_id) { create(:project).id } diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index ff35e3804761cdf4f99c29d582409eb15c1220e3..7b37862af74ee1b65a83e6a62f12857d24a26af0 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -171,6 +171,7 @@ RSpec.describe API::ProjectClusters do name: 'test-cluster', domain: 'domain.example.com', managed: false, + namespace_per_environment: false, platform_kubernetes_attributes: platform_kubernetes_attributes, management_project_id: management_project_id } @@ -202,6 +203,7 @@ RSpec.describe API::ProjectClusters do expect(cluster_result.domain).to eq('domain.example.com') expect(cluster_result.managed).to be_falsy expect(cluster_result.management_project_id).to eq management_project_id + expect(cluster_result.namespace_per_environment).to eq(false) expect(platform_kubernetes.rbac?).to be_truthy expect(platform_kubernetes.api_url).to eq(api_url) expect(platform_kubernetes.namespace).to eq(namespace) @@ -235,6 +237,22 @@ RSpec.describe API::ProjectClusters do end end + context 'when namespace_per_environment is not set' do + let(:cluster_params) do + { + name: 'test-cluster', + domain: 'domain.example.com', + platform_kubernetes_attributes: platform_kubernetes_attributes + } + end + + it 'defaults to true' do + cluster_result = Clusters::Cluster.find(json_response['id']) + + expect(cluster_result).to be_namespace_per_environment + end + end + context 'current user does not have access to management_project_id' do let(:management_project_id) { create(:project).id }