diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 1204f8827073d8d5ea694f859b2823a7105db46c..005070cca5cd5e750b612bb41cade3cf85cdaad2 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
module ClustersHelper
- # EE overrides this
def has_multiple_clusters?
- false
+ true
end
def create_new_cluster_label(provider: nil)
@@ -95,5 +94,3 @@ def can_admin_cluster?(user, cluster)
can?(user, :admin_cluster, cluster)
end
end
-
-ClustersHelper.prepend_if_ee('EE::ClustersHelper')
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index bde7a2104babb9d94195484714d9ad42c4eca5bb..a8c7e28cf27d08319c11d6406c22de647c5bab07 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -2,6 +2,7 @@
module Clusters
class Cluster < ApplicationRecord
+ prepend HasEnvironmentScope
include Presentable
include Gitlab::Utils::StrongMemoize
include FromUnion
@@ -81,6 +82,7 @@ def self.has_one_cluster_application(name) # rubocop:disable Naming/PredicateNam
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
validate :unique_management_project_environment_scope
+ validate :unique_environment_scope
after_save :clear_reactive_cache!
@@ -352,6 +354,12 @@ def unique_management_project_environment_scope
end
end
+ def unique_environment_scope
+ if clusterable.present? && clusterable.clusters.where(environment_scope: environment_scope).where.not(id: id).exists?
+ errors.add(:environment_scope, 'cannot add duplicated environment scope')
+ end
+ end
+
def managed_namespace(environment)
Clusters::KubernetesNamespaceFinder.new(
self,
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 3b893a56bd62e95ff21e03b06fee48cb619040f8..02f7711e9274808cf70fed4ea4f7e87368d73f55 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
module DeploymentPlatform
- # EE would override this and utilize environment argument
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def deployment_platform(environment: nil)
@deployment_platform ||= {}
@@ -20,16 +19,27 @@ def find_deployment_platform(environment)
find_instance_cluster_platform_kubernetes(environment: environment)
end
- # EE would override this and utilize environment argument
- def find_platform_kubernetes_with_cte(_environment)
- Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?).base_and_ancestors
+ def find_platform_kubernetes_with_cte(environment)
+ if environment
+ ::Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?)
+ .base_and_ancestors
+ .enabled
+ .on_environment(environment, relevant_only: true)
+ .first&.platform_kubernetes
+ else
+ Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?).base_and_ancestors
.enabled.default_environment
.first&.platform_kubernetes
+ end
end
- # EE would override this and utilize environment argument
def find_instance_cluster_platform_kubernetes(environment: nil)
- Clusters::Instance.new.clusters.enabled.default_environment
+ if environment
+ ::Clusters::Instance.new.clusters.enabled.on_environment(environment, relevant_only: true)
.first&.platform_kubernetes
+ else
+ Clusters::Instance.new.clusters.enabled.default_environment
+ .first&.platform_kubernetes
+ end
end
end
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index 5e669ff2e503e438f113ff9472a09c545d734429..7054b3a3652d7139d91259865f784776a99710b7 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -13,8 +13,7 @@ def self.fabricate(clusterable, **attributes)
end
def can_add_cluster?
- can?(current_user, :add_cluster, clusterable) &&
- (has_no_clusters? || multiple_clusters_available?)
+ can?(current_user, :add_cluster, clusterable)
end
def can_create_cluster?
@@ -81,17 +80,6 @@ def sidebar_text
def learn_more_link
raise NotImplementedError
end
-
- private
-
- # Overridden on EE module
- def multiple_clusters_available?
- false
- end
-
- def has_no_clusters?
- clusterable.clusters.empty?
- end
end
ClusterablePresenter.prepend_if_ee('EE::ClusterablePresenter')
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index 7b5bf6b32c2a0c6fa0b0cd78a872b2590063676d..6693a58683fbac5504c1897779748d657c439d84 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -19,10 +19,6 @@ def execute(access_token: nil)
cluster = Clusters::Cluster.new(cluster_params)
- unless can_create_cluster?
- cluster.errors.add(:base, _('Instance does not support multiple Kubernetes clusters'))
- end
-
validate_management_project_permissions(cluster)
return cluster if cluster.errors.present?
@@ -55,16 +51,9 @@ def clusterable_params
end
end
- # EE would override this method
- def can_create_cluster?
- clusterable.clusters.empty?
- end
-
def validate_management_project_permissions(cluster)
Clusters::Management::ValidateManagementProjectPermissionsService.new(current_user)
.execute(cluster, params[:management_project_id])
end
end
end
-
-Clusters::CreateService.prepend_if_ee('EE::Clusters::CreateService')
diff --git a/ee/app/views/projects/clusters/_multiple_clusters_message.html.haml b/app/views/clusters/clusters/_multiple_clusters_message.html.haml
similarity index 88%
rename from ee/app/views/projects/clusters/_multiple_clusters_message.html.haml
rename to app/views/clusters/clusters/_multiple_clusters_message.html.haml
index 0e5703e3852ad488d564dd3ce182fd7466b68809..da3e128ba3274414d40f4d84f41eb10a67e2d99c 100644
--- a/ee/app/views/projects/clusters/_multiple_clusters_message.html.haml
+++ b/app/views/clusters/clusters/_multiple_clusters_message.html.haml
@@ -1,4 +1,4 @@
-- autodevops_help_url = help_page_path('topics/autodevops/index.md', anchor: 'using-multiple-kubernetes-clusters-premium')
+- autodevops_help_url = help_page_path('topics/autodevops/index.md', anchor: 'using-multiple-kubernetes-clusters')
- help_link_start = ''.html_safe
- help_link_end = ''.html_safe
diff --git a/app/views/clusters/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml
index 24a74c59b97571c1ba5aaa387346848472245e09..31add011bfa44d2a6efd8402067cac7c1048bff2 100644
--- a/app/views/clusters/clusters/_sidebar.html.haml
+++ b/app/views/clusters/clusters/_sidebar.html.haml
@@ -5,4 +5,4 @@
%p
= clusterable.learn_more_link
-= render_if_exists 'clusters/multiple_clusters_message'
+= render 'clusters/clusters/multiple_clusters_message'
diff --git a/changelogs/unreleased/212229-move-features-to-core-multiple-kubernetes-clusters.yml b/changelogs/unreleased/212229-move-features-to-core-multiple-kubernetes-clusters.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9898b6449b664c3acaf1e93fc84bda30189b2545
--- /dev/null
+++ b/changelogs/unreleased/212229-move-features-to-core-multiple-kubernetes-clusters.yml
@@ -0,0 +1,5 @@
+---
+title: 'Multiple Kubernetes clusters now available in GitLab core'
+merge_request: 35094
+author:
+type: changed
diff --git a/doc/README.md b/doc/README.md
index 65044edff53eec16bc134c8fb883f308fe480446..5b53068de095b5bd1a549785b6c5875a2e3045e3 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -314,11 +314,11 @@ The following documentation relates to the DevOps **Configure** stage:
| [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. |
| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. |
-| [Multiple Kubernetes Clusters](user/project/clusters/index.md#multiple-kubernetes-clusters-premium) **(PREMIUM)** | Associate more than one Kubernetes clusters to your project. |
+| [Multiple Kubernetes Clusters](user/project/clusters/index.md#multiple-kubernetes-clusters) | Associate more than one Kubernetes clusters to your project. |
| [Protected variables](ci/variables/README.md#protect-a-custom-variable) | Restrict variables to protected branches and tags. |
| [Serverless](user/project/clusters/serverless/index.md) | Run serverless workloads on Kubernetes. |
| [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. |
-| [Manage your infrastructure with Terraform](user/infrastructure/index.md) | Manage your infrastructure as you run your CI/CD pipeline. |
+| [Manage your infrastructure with Terraform](user/infrastructure/index.md) | Manage your infrastructure as you run your CI/CD pipeline. |
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 42a238148e9e84bad744c4e8269403dd2144d4a3..7ea53c190e5e1cc12e44091a11e9eda51c1b80ce 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -896,8 +896,8 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
++ CI_SERVER_VERSION_PATCH=0
++ export CI_SERVER_REVISION=f4cc00ae823
++ CI_SERVER_REVISION=f4cc00ae823
-++ export GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_clusters,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
-++ GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_clusters,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,cluster_health,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
+++ export GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
+++ GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,cluster_health,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
++ export CI_PROJECT_ID=17893
++ CI_PROJECT_ID=17893
++ export CI_PROJECT_NAME=ci-debug-trace
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index e9b59b8c2223d2a186df25db4fb09790b5ff92e1..5deedd1dcfab23065ab37524af956f7f2bae7a62 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -240,11 +240,11 @@ TIP: **Tip:**
Use the [blue-green deployment](../../ci/environments/incremental_rollouts.md#blue-green-deployment) technique
to minimize downtime and risk.
-## Using multiple Kubernetes clusters **(PREMIUM)**
+## Using multiple Kubernetes clusters
When using Auto DevOps, you can deploy different environments to
different Kubernetes clusters, due to the 1:1 connection
-[existing between them](../../user/project/clusters/index.md#multiple-kubernetes-clusters-premium).
+[existing between them](../../user/project/clusters/index.md#multiple-kubernetes-clusters).
The [Deploy Job template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml)
used by Auto DevOps currently defines 3 environment names:
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index ec5191dd4ac43864e2b03030fd05996318a5ba01..39fe7c796eba0b492816164248c9d2c350ffb07c 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -305,7 +305,7 @@ all within GitLab. Despite its automatic nature, Auto DevOps can also be configu
and customized to fit your workflow. Here are some helpful resources for further reading:
1. [Auto DevOps](index.md)
-1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters-premium) **(PREMIUM)**
+1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters)
1. [Incremental rollout to production](customize.md#incremental-rollout-to-production-premium) **(PREMIUM)**
1. [Disable jobs you don't need with environment variables](customize.md#environment-variables)
1. [Use a static IP for your cluster](../../user/clusters/applications.md#using-a-static-ip)
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 8dcc08bce46a7e68269ad6dc5e228d22372f82f5..89e0c4898fb504da4fdc2bbdffb3ef9da6802634 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -38,10 +38,11 @@ the project.
In the case of sub-groups, GitLab uses the cluster of the closest ancestor group
to the project, provided the cluster is not disabled.
-## Multiple Kubernetes clusters **(PREMIUM)**
+## Multiple Kubernetes clusters
-With [GitLab Premium](https://about.gitlab.com/pricing/premium/), you can associate
-more than one Kubernetes cluster to your group, and maintain different clusters
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35094) to GitLab Core in 13.2.
+
+You can associate more than one Kubernetes cluster to your group, and maintain different clusters
for different environments, such as development, staging, and production.
When adding another cluster,
@@ -93,7 +94,7 @@ To clear the cache:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24580) in GitLab 11.8.
Domains at the cluster level permit support for multiple domains
-per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain,
+per [multiple Kubernetes clusters](#multiple-kubernetes-clusters) When specifying a domain,
this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during
the [Auto DevOps](../../../topics/autodevops/index.md) stages.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 16d78751f402f06c65da9279133916fbc7d74148..ff0aaca40b7d1145d31b56f1fa45e61f1211d3c4 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -64,11 +64,12 @@ to:
(EKS) using GitLab's UI.
- Add an integration to an existing cluster from any Kubernetes platform.
-### Multiple Kubernetes clusters **(PREMIUM)**
+### Multiple Kubernetes clusters
-> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
+> - Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35094) to GitLab core in 13.2.
-With GitLab Premium, you can associate more than one Kubernetes cluster to your
+You can associate more than one Kubernetes cluster to your
project. That way you can have different clusters for different environments,
like dev, staging, production, and so on.
diff --git a/ee/app/helpers/ee/clusters_helper.rb b/ee/app/helpers/ee/clusters_helper.rb
deleted file mode 100644
index b781b6ef68d62e786ec5e040e2ce12320f5d5b34..0000000000000000000000000000000000000000
--- a/ee/app/helpers/ee/clusters_helper.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module EE
- module ClustersHelper
- extend ::Gitlab::Utils::Override
-
- override :has_multiple_clusters?
- def has_multiple_clusters?
- clusterable.feature_available?(:multiple_clusters)
- end
- end
-end
diff --git a/ee/app/models/concerns/ee/deployment_platform.rb b/ee/app/models/concerns/ee/deployment_platform.rb
deleted file mode 100644
index 4dd505bdb4cd5fac0b0f5d40a810cd38a09ff5d6..0000000000000000000000000000000000000000
--- a/ee/app/models/concerns/ee/deployment_platform.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module EE
- module DeploymentPlatform
- extend ::Gitlab::Utils::Override
-
- override :find_platform_kubernetes_with_cte
- def find_platform_kubernetes_with_cte(environment)
- return super unless environment && feature_available?(:multiple_clusters)
-
- ::Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?)
- .base_and_ancestors
- .enabled
- .on_environment(environment, relevant_only: true)
- .first&.platform_kubernetes
- end
-
- override :find_instance_cluster_platform_kubernetes
- def find_instance_cluster_platform_kubernetes(environment: nil)
- return super unless environment && feature_available?(:multiple_clusters)
-
- ::Clusters::Instance.new.clusters.enabled.on_environment(environment, relevant_only: true)
- .first&.platform_kubernetes
- end
- end
-end
diff --git a/ee/app/models/ee/clusters/cluster.rb b/ee/app/models/ee/clusters/cluster.rb
index 4264d4fba994d19ac89fdbd541d2fba51bb52776..c4efde030d59909f5689f8ab221b359547bb5d63 100644
--- a/ee/app/models/ee/clusters/cluster.rb
+++ b/ee/app/models/ee/clusters/cluster.rb
@@ -6,23 +6,12 @@ module Cluster
extend ActiveSupport::Concern
prepended do
- prepend HasEnvironmentScope
include UsageStatistics
-
- validate :unique_environment_scope
end
def prometheus_adapter
application_prometheus
end
-
- private
-
- def unique_environment_scope
- if clusterable.present? && clusterable.clusters.where(environment_scope: environment_scope).where.not(id: id).exists?
- errors.add(:environment_scope, 'cannot add duplicated environment scope')
- end
- end
end
end
end
diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb
index e38ddb8297183ca580aa8f442410632484e5de9b..57f3837d7afdde3cbc31b66861b3d145a845cd2f 100644
--- a/ee/app/models/ee/project.rb
+++ b/ee/app/models/ee/project.rb
@@ -798,4 +798,3 @@ def requirements_ci_variables
end
EE::Project.include_if_ee('::EE::GitlabRoutingHelper')
-EE::Project.include_if_ee('::EE::DeploymentPlatform')
diff --git a/ee/app/models/license.rb b/ee/app/models/license.rb
index 3367d8ff4023f5184e8d32c5310c017ab9374d52..503c4459d5f13abf8645858a0372c9bbbcdf7602 100644
--- a/ee/app/models/license.rb
+++ b/ee/app/models/license.rb
@@ -89,7 +89,6 @@ class License < ApplicationRecord
merge_trains
metrics_reports
multiple_approval_rules
- multiple_clusters
multiple_group_issue_boards
object_storage
operations_dashboard
diff --git a/ee/app/presenters/ee/clusterable_presenter.rb b/ee/app/presenters/ee/clusterable_presenter.rb
index 822081229ed2dd87292732795909183b10346e3b..89b22ac0819c231bc3383c43a5964eccb7d605f9 100644
--- a/ee/app/presenters/ee/clusterable_presenter.rb
+++ b/ee/app/presenters/ee/clusterable_presenter.rb
@@ -2,8 +2,6 @@
module EE
module ClusterablePresenter
- extend ::Gitlab::Utils::Override
-
def metrics_cluster_path(cluster, params = {})
raise NotImplementedError
end
@@ -11,12 +9,5 @@ def metrics_cluster_path(cluster, params = {})
def metrics_dashboard_path(cluster)
raise NotImplementedError
end
-
- private
-
- override :multiple_clusters_available?
- def multiple_clusters_available?
- clusterable.feature_available?(:multiple_clusters)
- end
end
end
diff --git a/ee/app/services/ee/clusters/create_service.rb b/ee/app/services/ee/clusters/create_service.rb
deleted file mode 100644
index f06b81b28b5882197df3713cb63fad203f2adff0..0000000000000000000000000000000000000000
--- a/ee/app/services/ee/clusters/create_service.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module EE
- module Clusters
- module CreateService
- extend ::Gitlab::Utils::Override
-
- override :can_create_cluster?
- def can_create_cluster?
- super || clusterable.feature_available?(:multiple_clusters)
- end
- end
- end
-end
diff --git a/ee/lib/ee/api/group_clusters.rb b/ee/lib/ee/api/group_clusters.rb
deleted file mode 100644
index b0bdc8567d1e698294d5d6c602f96e3c4b30d26b..0000000000000000000000000000000000000000
--- a/ee/lib/ee/api/group_clusters.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module EE
- module API
- module GroupClusters
- extend ActiveSupport::Concern
-
- prepended do
- helpers do
- params :create_params_ee do
- optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster'
- end
-
- params :update_params_ee do
- optional :environment_scope, type: String, desc: 'The associated environment to the cluster'
- end
- end
- end
- end
- end
-end
diff --git a/ee/lib/ee/api/project_clusters.rb b/ee/lib/ee/api/project_clusters.rb
deleted file mode 100644
index 6bcced7f8ed63ec62f9448c3646cddff88e5b14b..0000000000000000000000000000000000000000
--- a/ee/lib/ee/api/project_clusters.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module EE
- module API
- module ProjectClusters
- extend ActiveSupport::Concern
-
- prepended do
- helpers do
- params :create_params_ee do
- optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster'
- end
-
- params :update_params_ee do
- optional :environment_scope, type: String, desc: 'The associated environment to the cluster'
- end
- end
- end
- end
- end
-end
diff --git a/ee/spec/features/projects/clusters/gcp_spec.rb b/ee/spec/features/projects/clusters/gcp_spec.rb
deleted file mode 100644
index 9175afb56acadf4373c0e6d1aca68cc422bff112..0000000000000000000000000000000000000000
--- a/ee/spec/features/projects/clusters/gcp_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Gcp Cluster', :js do
- include GoogleApi::CloudPlatformHelpers
-
- let(:project) { create(:project) }
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- gitlab_sign_in(user)
- allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
- end
-
- context 'when a user has a licence to use multiple clusers' do
- before do
- stub_licensed_features(multiple_clusters: true)
- visit project_clusters_path(project)
-
- click_link 'Add Kubernetes cluster'
- click_link 'Add existing cluster'
- end
-
- it 'user sees the "Environment scope" field' do
- expect(page).to have_css('#cluster_environment_scope')
- end
- end
-end
diff --git a/ee/spec/features/projects/clusters_spec.rb b/ee/spec/features/projects/clusters_spec.rb
deleted file mode 100644
index 95e3f192d3542102b3580309eae68009970a2f7e..0000000000000000000000000000000000000000
--- a/ee/spec/features/projects/clusters_spec.rb
+++ /dev/null
@@ -1,202 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'EE Clusters', :js do
- include GoogleApi::CloudPlatformHelpers
-
- let(:project) { create(:project) }
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- gitlab_sign_in(user)
- stub_feature_flags(clusters_list_redesign: false)
- end
-
- context 'when user has a cluster' do
- context 'when license has multiple clusters feature' do
- before do
- allow(License).to receive(:feature_available?).and_call_original
- allow(License).to receive(:feature_available?).with(:multiple_clusters).and_return(true)
- allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
- end
-
- context 'when user adds an existing cluster' do
- before do
- create(:cluster, :provided_by_user, name: 'default-cluster', environment_scope: '*', projects: [project])
- visit project_clusters_path(project)
- end
-
- it 'user sees a add cluster button ' do
- expect(page).not_to have_selector('.js-add-cluster.readonly')
- expect(page).to have_selector('.js-add-cluster')
- end
-
- context 'when user filled form with environment scope' do
- before do
- click_link 'Add Kubernetes cluster'
- click_link 'Add existing cluster'
- fill_in 'cluster_name', with: 'staging-cluster'
- fill_in 'cluster_environment_scope', with: 'staging/*'
- click_button 'Add Kubernetes cluster'
- end
-
- it 'user sees a cluster details page' do
- expect(page.find_field('cluster[name]').value).to eq('staging-cluster')
- expect(page.find_field('cluster[environment_scope]').value).to eq('staging/*')
- end
- end
-
- context 'when user updates environment scope' do
- before do
- click_link 'default-cluster'
- fill_in 'cluster_environment_scope', with: 'production/*'
- within '.js-cluster-integration-form' do
- click_button 'Save changes'
- end
- end
-
- it 'user sees a cluster details page' do
- expect(page.find_field('cluster[environment_scope]').value).to eq('production/*')
- end
- end
-
- context 'when user updates duplicated environment scope' do
- before do
- click_link 'Add Kubernetes cluster'
- click_link 'Add existing cluster'
- fill_in 'cluster_name', with: 'staging-cluster'
- fill_in 'cluster_environment_scope', with: '*'
- fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'https://0.0.0.0'
- fill_in 'cluster_platform_kubernetes_attributes_token', with: 'token'
-
- click_button 'Add Kubernetes cluster'
- end
-
- it 'users sees an environment scope validation error' do
- expect(page).to have_content('cannot add duplicated environment scope')
- end
- end
- end
-
- context 'when user adds an Google Kubernetes Engine cluster' do
- before do
- allow_any_instance_of(Projects::ClustersController)
- .to receive(:token_in_session).and_return('token')
- allow_any_instance_of(Projects::ClustersController)
- .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
-
- allow_any_instance_of(Projects::ClustersController).to receive(:authorize_google_project_billing)
- allow_any_instance_of(Projects::ClustersController).to receive(:google_project_billing_status).and_return(true)
-
- allow_any_instance_of(GoogleApi::CloudPlatform::Client)
- .to receive(:projects_zones_clusters_create) do
- OpenStruct.new(
- self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
- status: 'RUNNING'
- )
- end
-
- allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
-
- create(:cluster, :provided_by_gcp, name: 'default-cluster', environment_scope: '*', projects: [project])
- visit project_clusters_path(project)
- end
-
- it 'user sees a add cluster button ' do
- expect(page).not_to have_selector('.js-add-cluster.readonly')
- expect(page).to have_selector('.js-add-cluster')
- end
-
- context 'when user filled form with environment scope' do
- before do
- click_link 'Add Kubernetes cluster'
- click_link 'Create new cluster'
- click_link 'Google GKE'
-
- sleep 2 # wait for ajax
- execute_script('document.querySelector(".js-gcp-project-id-dropdown input").setAttribute("type", "text")')
- execute_script('document.querySelector(".js-gcp-zone-dropdown input").setAttribute("type", "text")')
- execute_script('document.querySelector(".js-gcp-machine-type-dropdown input").setAttribute("type", "text")')
- execute_script('document.querySelector(".js-gke-cluster-creation-submit").removeAttribute("disabled")')
-
- fill_in 'cluster_name', with: 'staging-cluster'
- fill_in 'cluster_environment_scope', with: 'staging/*'
- fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123'
- fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a'
- fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
- click_button 'Create Kubernetes cluster'
-
- # The frontend won't show the details until the cluster is
- # created, and we don't want to make calls out to GCP.
- provider = Clusters::Cluster.last.provider
- provider.make_created
- end
-
- it 'user sees a cluster details page' do
- expect(page).to have_content('GitLab Integration')
- expect(page.find_field('cluster[environment_scope]').value).to eq('staging/*')
- end
- end
-
- context 'when user updates environment scope' do
- before do
- click_link 'default-cluster'
- fill_in 'cluster_environment_scope', with: 'production/*'
- within ".js-cluster-integration-form" do
- click_button 'Save changes'
- end
- end
-
- it 'user sees a cluster details page' do
- expect(page.find_field('cluster[environment_scope]').value).to eq('production/*')
- end
- end
-
- context 'when user updates duplicated environment scope' do
- before do
- click_link 'Add Kubernetes cluster'
- click_link 'Create new cluster'
- click_link 'Google GKE'
-
- sleep 2 # wait for ajax
- execute_script('document.querySelector(".js-gcp-project-id-dropdown input").setAttribute("type", "text")')
- execute_script('document.querySelector(".js-gcp-zone-dropdown input").setAttribute("type", "text")')
- execute_script('document.querySelector(".js-gcp-machine-type-dropdown input").setAttribute("type", "text")')
- execute_script('document.querySelector(".js-gke-cluster-creation-submit").removeAttribute("disabled")')
-
- fill_in 'cluster_name', with: 'staging-cluster'
- fill_in 'cluster_environment_scope', with: '*'
- fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123'
- fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a'
- fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
- click_button 'Create Kubernetes cluster'
- end
-
- it 'users sees an environment scope validation error' do
- expect(page).to have_content('cannot add duplicated environment scope')
- end
- end
- end
- end
-
- context 'when license does not have multiple clusters feature' do
- before do
- allow(License).to receive(:feature_available?).and_call_original
- allow(License).to receive(:feature_available?).with(:multiple_clusters).and_return(false)
- create(:cluster, :provided_by_user, name: 'default-cluster', environment_scope: '*', projects: [project])
- end
-
- context 'when user visits cluster index page' do
- before do
- visit project_clusters_path(project)
- end
-
- it 'user sees a disabled add cluster button ' do
- expect(page).to have_selector('.js-add-cluster.disabled')
- end
- end
- end
- end
-end
diff --git a/ee/spec/helpers/ee/clusters_helper_spec.rb b/ee/spec/helpers/ee/clusters_helper_spec.rb
deleted file mode 100644
index e582b98ff0b37fed165c47738422ff10a5aa376f..0000000000000000000000000000000000000000
--- a/ee/spec/helpers/ee/clusters_helper_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ClustersHelper do
- shared_examples 'feature availablilty' do |feature|
- before do
- # clusterable is provided as a `helper_method`
- allow(helper).to receive(:clusterable).and_return(clusterable)
-
- expect(clusterable)
- .to receive(:feature_available?)
- .with(feature)
- .and_return(feature_available)
- end
-
- context 'feature unavailable' do
- let(:feature_available) { true }
-
- it { is_expected.to be_truthy }
- end
-
- context 'feature available' do
- let(:feature_available) { false }
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#has_multiple_clusters?' do
- subject { helper.has_multiple_clusters? }
-
- context 'project level' do
- let(:clusterable) { instance_double(Project) }
-
- it_behaves_like 'feature availablilty', :multiple_clusters
- end
-
- context 'group level' do
- let(:clusterable) { instance_double(Group) }
-
- it_behaves_like 'feature availablilty', :multiple_clusters
- end
- end
-end
diff --git a/ee/spec/models/concerns/ee/deployment_platform_spec.rb b/ee/spec/models/concerns/ee/deployment_platform_spec.rb
deleted file mode 100644
index 4b7d281b746c2fa6d19ebf093be05ec301b1525f..0000000000000000000000000000000000000000
--- a/ee/spec/models/concerns/ee/deployment_platform_spec.rb
+++ /dev/null
@@ -1,289 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe EE::DeploymentPlatform do
- describe '#deployment_platform' do
- let(:group) { create(:group) }
- let(:project) { create(:project, group: group) }
-
- shared_examples 'matching environment scope' do
- context 'when multiple clusters license is available' do
- before do
- stub_licensed_features(multiple_clusters: true)
- end
-
- it 'returns environment specific cluster' do
- is_expected.to eq(cluster.platform_kubernetes)
- end
- end
-
- context 'when multiple clusters licence is unavailable' do
- before do
- stub_licensed_features(multiple_clusters: false)
- end
-
- it 'returns a kubernetes platform' do
- is_expected.not_to eq(cluster.platform_kubernetes)
- is_expected.to be_kind_of(Clusters::Platforms::Kubernetes)
- end
- end
- end
-
- shared_examples 'not matching environment scope' do
- context 'when multiple clusters license is available' do
- before do
- stub_licensed_features(multiple_clusters: true)
- end
-
- it 'returns default cluster' do
- is_expected.to eq(default_cluster.platform_kubernetes)
- end
- end
-
- context 'when multiple clusters license is unavailable' do
- before do
- stub_licensed_features(multiple_clusters: false)
- end
-
- it 'returns default cluster' do
- is_expected.to eq(default_cluster.platform_kubernetes)
- end
- end
- end
-
- context 'multiple clusters use the same management project' do
- let(:management_project) { create(:project, group: group) }
-
- let!(:default_cluster) do
- create(:cluster_for_group, groups: [group], environment_scope: '*', management_project: management_project)
- end
-
- let!(:cluster) do
- create(:cluster_for_group, groups: [group], environment_scope: 'review/*', management_project: management_project)
- end
-
- let(:environment) { 'review/name' }
-
- subject { management_project.deployment_platform(environment: environment) }
-
- it_behaves_like 'matching environment scope'
- end
-
- context 'when project does not have a cluster but has group clusters' do
- let!(:default_cluster) do
- create(:cluster, :provided_by_user,
- cluster_type: :group_type, groups: [group], environment_scope: '*')
- end
-
- let!(:cluster) do
- create(:cluster, :provided_by_user,
- cluster_type: :group_type, environment_scope: 'review/*', groups: [group])
- end
-
- let(:environment) { 'review/name' }
-
- subject { project.deployment_platform(environment: environment) }
-
- context 'when environment scope is exactly matched' do
- before do
- cluster.update!(environment_scope: 'review/name')
- end
-
- it_behaves_like 'matching environment scope'
- end
-
- context 'when environment scope is matched by wildcard' do
- before do
- cluster.update!(environment_scope: 'review/*')
- end
-
- it_behaves_like 'matching environment scope'
- end
-
- context 'when environment scope does not match' do
- before do
- cluster.update!(environment_scope: 'review/*/special')
- end
-
- it_behaves_like 'not matching environment scope'
- end
-
- context 'when group belongs to a parent group' do
- let(:parent_group) { create(:group) }
- let(:group) { create(:group, parent: parent_group) }
-
- context 'when parent_group has a cluster with default scope' do
- let!(:parent_group_cluster) do
- create(:cluster, :provided_by_user,
- cluster_type: :group_type, environment_scope: '*', groups: [parent_group])
- end
-
- it_behaves_like 'matching environment scope'
- end
-
- context 'when parent_group has a cluster that is an exact match' do
- let!(:parent_group_cluster) do
- create(:cluster, :provided_by_user,
- cluster_type: :group_type, environment_scope: 'review/name', groups: [parent_group])
- end
-
- it_behaves_like 'matching environment scope'
- end
- end
- end
-
- context 'with instance clusters' do
- let!(:default_cluster) do
- create(:cluster, :provided_by_user, :instance, environment_scope: '*')
- end
-
- let!(:cluster) do
- create(:cluster, :provided_by_user, :instance, environment_scope: 'review/*')
- end
-
- let(:environment) { 'review/name' }
-
- subject { project.deployment_platform(environment: environment) }
-
- context 'when environment scope is exactly matched' do
- before do
- cluster.update!(environment_scope: 'review/name')
- end
-
- it_behaves_like 'matching environment scope'
- end
-
- context 'when environment scope is matched by wildcard' do
- before do
- cluster.update!(environment_scope: 'review/*')
- end
-
- it_behaves_like 'matching environment scope'
- end
-
- context 'when environment scope does not match' do
- before do
- cluster.update!(environment_scope: 'review/*/special')
- end
-
- it_behaves_like 'not matching environment scope'
- end
- end
-
- context 'when environment is specified' do
- let!(:default_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: '*') }
- let!(:cluster) { create(:cluster, :provided_by_user, environment_scope: 'review/*', projects: [project]) }
-
- let!(:group_default_cluster) do
- create(:cluster, :provided_by_user,
- cluster_type: :group_type, groups: [group], environment_scope: '*')
- end
-
- let(:environment) { 'review/name' }
-
- subject { project.deployment_platform(environment: environment) }
-
- context 'when environment scope is exactly matched' do
- before do
- cluster.update!(environment_scope: 'review/name')
- end
-
- it_behaves_like 'matching environment scope'
- end
-
- context 'when environment scope is matched by wildcard' do
- before do
- cluster.update!(environment_scope: 'review/*')
- end
-
- it_behaves_like 'matching environment scope'
- end
-
- context 'when environment scope does not match' do
- before do
- cluster.update!(environment_scope: 'review/*/special')
- end
-
- it_behaves_like 'not matching environment scope'
- end
-
- context 'when environment scope has _' do
- before do
- stub_licensed_features(multiple_clusters: true)
- end
-
- it 'does not treat it as wildcard' do
- cluster.update!(environment_scope: 'foo_bar/*')
-
- is_expected.to eq(default_cluster.platform_kubernetes)
- end
-
- context 'when environment name contains an underscore' do
- let(:environment) { 'foo_bar/test' }
-
- it 'matches literally for _' do
- cluster.update!(environment_scope: 'foo_bar/*')
-
- is_expected.to eq(cluster.platform_kubernetes)
- end
- end
- end
-
- # The environment name and scope cannot have % at the moment,
- # but we're considering relaxing it and we should also make sure
- # it doesn't break in case some data sneaked in somehow as we're
- # not checking this integrity in database level.
- context 'when environment scope has %' do
- before do
- stub_licensed_features(multiple_clusters: true)
- end
-
- it 'does not treat it as wildcard' do
- cluster.update_attribute(:environment_scope, '*%*')
-
- is_expected.to eq(default_cluster.platform_kubernetes)
- end
-
- context 'when environment name contains a percent char' do
- let(:environment) { 'foo%bar/test' }
-
- it 'matches literally for %' do
- cluster.update_attribute(:environment_scope, 'foo%bar/*')
-
- is_expected.to eq(cluster.platform_kubernetes)
- end
- end
- end
-
- context 'when perfectly matched cluster exists' do
- let!(:perfectly_matched_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'review/name') }
-
- before do
- stub_licensed_features(multiple_clusters: true)
- end
-
- it 'returns perfectly matched cluster as highest precedence' do
- is_expected.to eq(perfectly_matched_cluster.platform_kubernetes)
- end
- end
- end
-
- context 'with multiple clusters and multiple environments' do
- let!(:cluster_1) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'staging/*') }
- let!(:cluster_2) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'test/*') }
-
- let(:environment_1) { 'staging/name' }
- let(:environment_2) { 'test/name' }
-
- before do
- stub_licensed_features(multiple_clusters: true)
- end
-
- it 'returns the appropriate cluster' do
- expect(project.deployment_platform(environment: environment_1)).to eq(cluster_1.platform_kubernetes)
- expect(project.deployment_platform(environment: environment_2)).to eq(cluster_2.platform_kubernetes)
- end
- end
- end
-end
diff --git a/ee/spec/models/ee/clusters/cluster_spec.rb b/ee/spec/models/ee/clusters/cluster_spec.rb
deleted file mode 100644
index 8c6c54465eac1e1708e5b97baad23e1e98c5c618..0000000000000000000000000000000000000000
--- a/ee/spec/models/ee/clusters/cluster_spec.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Clusters::Cluster do
- it { is_expected.to include_module(HasEnvironmentScope) }
-
- describe 'validation' do
- subject { cluster.valid? }
-
- context 'when validates unique_environment_scope' do
- context 'for a project cluster' do
- let(:project) { create(:project) }
-
- before do
- create(:cluster, projects: [project], environment_scope: 'product/*')
- end
-
- context 'when identical environment scope exists in project' do
- let(:cluster) { build(:cluster, projects: [project], environment_scope: 'product/*') }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when identical environment scope does not exist in project' do
- let(:cluster) { build(:cluster, projects: [project], environment_scope: '*') }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when identical environment scope exists in different project' do
- let(:project2) { create(:project) }
- let(:cluster) { build(:cluster, projects: [project2], environment_scope: 'product/*') }
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'for a group cluster' do
- let(:group) { create(:group) }
-
- before do
- create(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*')
- end
-
- context 'when identical environment scope exists in group' do
- let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*') }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when identical environment scope does not exist in group' do
- let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: '*') }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when identical environment scope exists in different group' do
- let(:cluster) { build(:cluster, :group, environment_scope: 'product/*') }
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'for an instance cluster' do
- before do
- create(:cluster, :instance, environment_scope: 'product/*')
- end
-
- context 'identical environment scope exists' do
- let(:cluster) { build(:cluster, :instance, environment_scope: 'product/*') }
-
- it { is_expected.to be_falsey }
- end
-
- context 'identical environment scope does not exist' do
- let(:cluster) { build(:cluster, :instance, environment_scope: '*') }
-
- it { is_expected.to be_truthy }
- end
- end
- end
- end
-end
diff --git a/ee/spec/models/project_spec.rb b/ee/spec/models/project_spec.rb
index dad8b95317f8ab3d4bd6f5c83cc8500f0758784e..e9f234c4a65bc96162b49e44a69c7d7a60be29bb 100644
--- a/ee/spec/models/project_spec.rb
+++ b/ee/spec/models/project_spec.rb
@@ -442,52 +442,44 @@
end
describe '#deployment_variables' do
- context 'when project has a deployment platforms' do
- context 'when multiple clusters (EEP) is enabled' do
- before do
- stub_licensed_features(multiple_clusters: true)
- end
-
- let(:project) { create(:project) }
+ let(:project) { create(:project) }
- let!(:default_cluster) do
- create(:cluster,
- :not_managed,
- platform_type: :kubernetes,
- projects: [project],
- environment_scope: '*',
- platform_kubernetes: default_cluster_kubernetes)
- end
+ let!(:default_cluster) do
+ create(:cluster,
+ :not_managed,
+ platform_type: :kubernetes,
+ projects: [project],
+ environment_scope: '*',
+ platform_kubernetes: default_cluster_kubernetes)
+ end
- let!(:review_env_cluster) do
- create(:cluster,
- :not_managed,
- platform_type: :kubernetes,
- projects: [project],
- environment_scope: 'review/*',
- platform_kubernetes: review_env_cluster_kubernetes)
- end
+ let!(:review_env_cluster) do
+ create(:cluster,
+ :not_managed,
+ platform_type: :kubernetes,
+ projects: [project],
+ environment_scope: 'review/*',
+ platform_kubernetes: review_env_cluster_kubernetes)
+ end
- let(:default_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'default-AAA') }
- let(:review_env_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'review-AAA') }
+ let(:default_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'default-AAA') }
+ let(:review_env_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'review-AAA') }
- context 'when environment name is review/name' do
- let!(:environment) { create(:environment, project: project, name: 'review/name') }
+ context 'when environment name is review/name' do
+ let!(:environment) { create(:environment, project: project, name: 'review/name') }
- it 'returns variables from this service' do
- expect(project.deployment_variables(environment: 'review/name'))
- .to include(key: 'KUBE_TOKEN', value: 'review-AAA', public: false, masked: true)
- end
- end
+ it 'returns variables from this service' do
+ expect(project.deployment_variables(environment: 'review/name'))
+ .to include(key: 'KUBE_TOKEN', value: 'review-AAA', public: false, masked: true)
+ end
+ end
- context 'when environment name is other' do
- let!(:environment) { create(:environment, project: project, name: 'staging/name') }
+ context 'when environment name is other' do
+ let!(:environment) { create(:environment, project: project, name: 'staging/name') }
- it 'returns variables from this service' do
- expect(project.deployment_variables(environment: 'staging/name'))
- .to include(key: 'KUBE_TOKEN', value: 'default-AAA', public: false, masked: true)
- end
- end
+ it 'returns variables from this service' do
+ expect(project.deployment_variables(environment: 'staging/name'))
+ .to include(key: 'KUBE_TOKEN', value: 'default-AAA', public: false, masked: true)
end
end
end
diff --git a/ee/spec/requests/api/group_clusters_spec.rb b/ee/spec/requests/api/group_clusters_spec.rb
index 9be6be7bf75114881a306838564b18f4f5b5877d..b7c53d0c756cd9f9fadfa2d0eb2f240d35f3b26c 100644
--- a/ee/spec/requests/api/group_clusters_spec.rb
+++ b/ee/spec/requests/api/group_clusters_spec.rb
@@ -52,25 +52,6 @@
expect(json_response['environment_scope']).to eq('*')
end
end
-
- context 'when license has multiple clusters feature' do
- before do
- stub_licensed_features(multiple_clusters: true)
-
- create(:cluster, :provided_by_gcp, :group,
- groups: [group])
-
- post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
- end
-
- it 'responds with 201' do
- expect(response).to have_gitlab_http_status(:created)
- end
-
- it 'allows multiple clusters to be associated to group' do
- expect(group.reload.clusters.count).to eq(2)
- end
- end
end
describe 'PUT /groups/:id/clusters/:cluster_id' do
diff --git a/ee/spec/requests/api/project_clusters_spec.rb b/ee/spec/requests/api/project_clusters_spec.rb
index 75c60ecc9ae7d0bd48d8685e0c80970c48daffe0..a496fdc277f10c27fa130008b52ea1df058dc6d3 100644
--- a/ee/spec/requests/api/project_clusters_spec.rb
+++ b/ee/spec/requests/api/project_clusters_spec.rb
@@ -78,10 +78,8 @@
end
end
- context 'when license has multiple clusters feature' do
+ context 'when another cluster exists' do
before do
- stub_licensed_features(multiple_clusters: true)
-
create(:cluster, :provided_by_gcp, :project,
projects: [project])
diff --git a/ee/spec/services/ee/clusters/create_service_spec.rb b/ee/spec/services/ee/clusters/create_service_spec.rb
deleted file mode 100644
index 3168b2df510fa67e836dfc80a596b7aa44398809..0000000000000000000000000000000000000000
--- a/ee/spec/services/ee/clusters/create_service_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Clusters::CreateService do
- let(:access_token) { 'xxx' }
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
-
- subject { described_class.new(user, params).execute(access_token: access_token) }
-
- before do
- allow(project).to receive(:feature_available?).and_call_original
- end
-
- context 'when license has multiple clusters feature' do
- before do
- allow(project).to receive(:feature_available?).with(:multiple_clusters).and_return(true)
- end
-
- context 'when correct params' do
- let(:params) do
- {
- name: 'test-cluster',
- provider_type: :gcp,
- provider_gcp_attributes: {
- gcp_project_id: 'gcp-project',
- zone: 'us-central1-a',
- num_nodes: 1,
- machine_type: 'machine_type-a',
- legacy_abac: 'true'
- },
- clusterable: project
- }
- end
-
- include_examples 'create cluster service success'
- end
-
- context 'when invalid params' do
- let(:params) do
- {
- name: 'test-cluster',
- provider_type: :gcp,
- provider_gcp_attributes: {
- gcp_project_id: '!!!!!!!',
- zone: 'us-central1-a',
- num_nodes: 1,
- machine_type: 'machine_type-a'
- },
- clusterable: project
- }
- end
-
- include_examples 'create cluster service error'
- end
- end
-
- context 'when license does not have multiple clusters feature' do
- include_context 'valid cluster create params'
-
- before do
- allow(project).to receive(:feature_available?).with(:multiple_clusters).and_return(false)
- end
-
- it 'does not create a cluster' do
- expect(ClusterProvisionWorker).not_to receive(:perform_async)
- expect { subject }.to raise_error(ArgumentError).and change { Clusters::Cluster.count }.by(0)
- end
- end
-end
diff --git a/ee/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/ee/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index 9f312ba14bb16128daa8fb20b5424d99037db13a..05fa8a0d2b74f24b16466f9873cbd9c1b8a3345e 100644
--- a/ee/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/ee/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -118,8 +118,6 @@
end
before do
- stub_licensed_features(multiple_clusters: true)
-
create(:clusters_applications_prometheus, :installed,
cluster: prd_cluster, alert_manager_token: token)
create(:clusters_applications_prometheus, :installed,
diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb
index c6d10f22bb450b0e86086371dbb2685f47f9a48a..ae41d9f13b81e812871c0512c2507f25e959b229 100644
--- a/lib/api/group_clusters.rb
+++ b/lib/api/group_clusters.rb
@@ -6,18 +6,6 @@ class GroupClusters < Grape::API::Instance
before { authenticate! }
- # EE::API::GroupClusters will
- # override these methods
- helpers do
- params :create_params_ee do
- end
-
- params :update_params_ee do
- end
- end
-
- prepend_if_ee('EE::API::GroupClusters') # rubocop: disable Cop/InjectEnterpriseEditionModule
-
params do
requires :id, type: String, desc: 'The ID of the group'
end
@@ -52,6 +40,7 @@ class GroupClusters < Grape::API::Instance
params do
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 :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'
@@ -62,7 +51,6 @@ class GroupClusters < Grape::API::Instance
optional :namespace, type: String, desc: 'Unique namespace related to Group'
optional :authorization_type, type: String, values: ::Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
end
- use :create_params_ee
end
post ':id/clusters/user' do
authorize! :add_cluster, user_group
@@ -85,6 +73,7 @@ class GroupClusters < Grape::API::Instance
requires :cluster_id, type: Integer, desc: 'The cluster ID'
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 :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'
@@ -92,7 +81,6 @@ class GroupClusters < Grape::API::Instance
optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
optional :namespace, type: String, desc: 'Unique namespace related to Group'
end
- use :update_params_ee
end
put ':id/clusters/:cluster_id' do
authorize! :update_cluster, cluster
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
index e1dfb647fa08df0d18341bfeb82acc28a45f7136..0e5605984e63f56dfbe3fc17f719db3c2abef7f4 100644
--- a/lib/api/project_clusters.rb
+++ b/lib/api/project_clusters.rb
@@ -6,18 +6,6 @@ class ProjectClusters < Grape::API::Instance
before { authenticate! }
- # EE::API::ProjectClusters will
- # override these methods
- helpers do
- params :create_params_ee do
- end
-
- params :update_params_ee do
- end
- end
-
- prepend_if_ee('EE::API::ProjectClusters') # rubocop: disable Cop/InjectEnterpriseEditionModule
-
params do
requires :id, type: String, desc: 'The ID of the project'
end
@@ -56,6 +44,7 @@ class ProjectClusters < Grape::API::Instance
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 :domain, type: String, desc: 'Cluster base domain'
+ optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster'
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
@@ -65,7 +54,6 @@ class ProjectClusters < Grape::API::Instance
optional :namespace, type: String, desc: 'Unique namespace related to Project'
optional :authorization_type, type: String, values: ::Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
end
- use :create_params_ee
end
post ':id/clusters/user' do
authorize! :add_cluster, user_project
@@ -89,6 +77,7 @@ class ProjectClusters < Grape::API::Instance
requires :cluster_id, type: Integer, desc: 'The cluster ID'
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 :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'
@@ -96,7 +85,6 @@ class ProjectClusters < Grape::API::Instance
optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
optional :namespace, type: String, desc: 'Unique namespace related to Project'
end
- use :update_params_ee
end
put ':id/clusters/:cluster_id' do
authorize! :update_cluster, cluster
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3b28b67ca0d05fbbc1945ee1993f388d4c7a03ef..407deba0d43f081f7422e4023064acb0310ae720 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -12394,9 +12394,6 @@ msgstr ""
msgid "Instance administrators group already exists"
msgstr ""
-msgid "Instance does not support multiple Kubernetes clusters"
-msgstr ""
-
msgid "Instance license"
msgstr ""
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 3e1006920e7d686cbeafdbb4002115286fd61165..2e6a366f77ac21b4f963cb8199e343f9d9304dd0 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -139,6 +139,19 @@ def submit_form
end
end
+ context 'when a user adds an existing cluster' do
+ before do
+ visit project_clusters_path(project)
+
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ end
+
+ it 'user sees the "Environment scope" field' do
+ expect(page).to have_css('#cluster_environment_scope')
+ end
+ end
+
context 'when user destroys the cluster' do
before do
click_link 'Advanced Settings'
@@ -155,19 +168,6 @@ def submit_form
end
end
- context 'when a user cannot edit the environment scope' do
- before do
- visit project_clusters_path(project)
-
- click_link 'Add Kubernetes cluster'
- click_link 'Add existing cluster'
- end
-
- it 'user does not see the "Environment scope" field' do
- expect(page).not_to have_css('#cluster_environment_scope')
- end
- end
-
context 'when user has not dismissed GCP signup offer' do
before do
visit project_clusters_path(project)
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index 1cf214a5c4eb9ab7b4a71d956c2137228d671dde..c56a1ed1711b2b89f85c06f895ae1b8277a964d7 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -25,6 +25,168 @@
end
end
+ context 'when user has a cluster' do
+ before do
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
+ end
+
+ context 'when user adds an existing cluster' do
+ before do
+ create(:cluster, :provided_by_user, name: 'default-cluster', environment_scope: '*', projects: [project])
+ visit project_clusters_path(project)
+ end
+
+ it 'user sees an add cluster button' do
+ expect(page).to have_selector('.js-add-cluster:not(.readonly)')
+ end
+
+ context 'when user filled form with environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: 'staging/*'
+ click_button 'Add Kubernetes cluster'
+ end
+
+ it 'user sees a cluster details page' do
+ expect(page.find_field('cluster[name]').value).to eq('staging-cluster')
+ expect(page.find_field('cluster[environment_scope]').value).to eq('staging/*')
+ end
+ end
+
+ context 'when user updates environment scope' do
+ before do
+ click_link 'default-cluster'
+ fill_in 'cluster_environment_scope', with: 'production/*'
+ within '.js-cluster-integration-form' do
+ click_button 'Save changes'
+ end
+ end
+
+ it 'updates the environment scope' do
+ expect(page.find_field('cluster[environment_scope]').value).to eq('production/*')
+ end
+ end
+
+ context 'when user updates duplicated environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: '*'
+ fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'https://0.0.0.0'
+ fill_in 'cluster_platform_kubernetes_attributes_token', with: 'token'
+
+ click_button 'Add Kubernetes cluster'
+ end
+
+ it 'users sees an environment scope validation error' do
+ expect(page).to have_content('cannot add duplicated environment scope')
+ end
+ end
+ end
+
+ context 'when user adds a Google Kubernetes Engine cluster' do
+ before do
+ allow_any_instance_of(Projects::ClustersController)
+ .to receive(:token_in_session).and_return('token')
+ allow_any_instance_of(Projects::ClustersController)
+ .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
+
+ allow_any_instance_of(Projects::ClustersController).to receive(:authorize_google_project_billing)
+ allow_any_instance_of(Projects::ClustersController).to receive(:google_project_billing_status).and_return(true)
+
+ allow_any_instance_of(GoogleApi::CloudPlatform::Client)
+ .to receive(:projects_zones_clusters_create) do
+ OpenStruct.new(
+ self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
+ status: 'RUNNING'
+ )
+ end
+
+ allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
+
+ create(:cluster, :provided_by_gcp, name: 'default-cluster', environment_scope: '*', projects: [project])
+ visit project_clusters_path(project)
+ end
+
+ it 'user sees a add cluster button ' do
+ expect(page).to have_selector('.js-add-cluster:not(.readonly)')
+ end
+
+ context 'when user filled form with environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Create new cluster'
+ click_link 'Google GKE'
+
+ sleep 2 # wait for ajax
+ execute_script('document.querySelector(".js-gcp-project-id-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-zone-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-machine-type-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gke-cluster-creation-submit").removeAttribute("disabled")')
+
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: 'staging/*'
+ fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123'
+ fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a'
+ fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
+ click_button 'Create Kubernetes cluster'
+
+ # The frontend won't show the details until the cluster is
+ # created, and we don't want to make calls out to GCP.
+ provider = Clusters::Cluster.last.provider
+ provider.make_created
+ end
+
+ it 'user sees a cluster details page' do
+ expect(page).to have_content('GitLab Integration')
+ expect(page.find_field('cluster[environment_scope]').value).to eq('staging/*')
+ end
+ end
+
+ context 'when user updates environment scope' do
+ before do
+ click_link 'default-cluster'
+ fill_in 'cluster_environment_scope', with: 'production/*'
+ within ".js-cluster-integration-form" do
+ click_button 'Save changes'
+ end
+ end
+
+ it 'updates the environment scope' do
+ expect(page.find_field('cluster[environment_scope]').value).to eq('production/*')
+ end
+ end
+
+ context 'when user updates duplicated environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Create new cluster'
+ click_link 'Google GKE'
+
+ sleep 2 # wait for ajax
+ execute_script('document.querySelector(".js-gcp-project-id-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-zone-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-machine-type-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gke-cluster-creation-submit").removeAttribute("disabled")')
+
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: '*'
+ fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123'
+ fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a'
+ fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
+ click_button 'Create Kubernetes cluster'
+ end
+
+ it 'users sees an environment scope validation error' do
+ expect(page).to have_content('cannot add duplicated environment scope')
+ end
+ end
+ end
+ end
+
context 'when user has a cluster and visits cluster index page' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
index 2b820cd540cb3ea005d2f85a06d59c8eabb07e85..cebf6235f442c7ddd49fbfdeb1030e1e6d682fde 100644
--- a/spec/helpers/clusters_helper_spec.rb
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -101,6 +101,12 @@
end
end
+ describe '#has_multiple_clusters?' do
+ subject { helper.has_multiple_clusters? }
+
+ it { is_expected.to be_truthy }
+ end
+
describe '#cluster_type_label' do
subject { helper.cluster_type_label(cluster_type) }
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 8b1afd271daab8b0fdc8abd7b4c4f27c2e99c6c8..83060434b7c1351319618dcf7ad92f19eda7c163 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -10,6 +10,7 @@
subject { build(:cluster) }
+ it { is_expected.to include_module(HasEnvironmentScope) }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:management_project).class_name('::Project') }
it { is_expected.to have_many(:cluster_projects) }
@@ -289,6 +290,79 @@
describe 'validations' do
subject { cluster.valid? }
+ context 'when validates unique_environment_scope' do
+ context 'for a project cluster' do
+ let(:project) { create(:project) }
+
+ before do
+ create(:cluster, projects: [project], environment_scope: 'product/*')
+ end
+
+ context 'when identical environment scope exists in project' do
+ let(:cluster) { build(:cluster, projects: [project], environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when identical environment scope does not exist in project' do
+ let(:cluster) { build(:cluster, projects: [project], environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when identical environment scope exists in different project' do
+ let(:project2) { create(:project) }
+ let(:cluster) { build(:cluster, projects: [project2], environment_scope: 'product/*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'for a group cluster' do
+ let(:group) { create(:group) }
+
+ before do
+ create(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*')
+ end
+
+ context 'when identical environment scope exists in group' do
+ let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when identical environment scope does not exist in group' do
+ let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when identical environment scope exists in different group' do
+ let(:cluster) { build(:cluster, :group, environment_scope: 'product/*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'for an instance cluster' do
+ before do
+ create(:cluster, :instance, environment_scope: 'product/*')
+ end
+
+ context 'identical environment scope exists' do
+ let(:cluster) { build(:cluster, :instance, environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'identical environment scope does not exist' do
+ let(:cluster) { build(:cluster, :instance, environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
context 'when validates name' do
context 'when provided by user' do
let!(:cluster) { build(:cluster, :provided_by_user, name: name) }
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index b5b7efa0c47866da4648c32dab16f8b8dd2fca92..2bb6aa27e2158dd22d7a5f572a5b03afe43afe50 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -8,6 +8,241 @@
describe '#deployment_platform' do
subject { project.deployment_platform }
+ context 'multiple clusters' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ shared_examples 'matching environment scope' do
+ it 'returns environment specific cluster' do
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+
+ shared_examples 'not matching environment scope' do
+ it 'returns default cluster' do
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+ end
+
+ context 'multiple clusters use the same management project' do
+ let(:management_project) { create(:project, group: group) }
+
+ let!(:default_cluster) do
+ create(:cluster_for_group, groups: [group], environment_scope: '*', management_project: management_project)
+ end
+
+ let!(:cluster) do
+ create(:cluster_for_group, groups: [group], environment_scope: 'review/*', management_project: management_project)
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { management_project.deployment_platform(environment: environment) }
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when project does not have a cluster but has group clusters' do
+ let!(:default_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, groups: [group], environment_scope: '*')
+ end
+
+ let!(:cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: 'review/*', groups: [group])
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+
+ context 'when group belongs to a parent group' do
+ let(:parent_group) { create(:group) }
+ let(:group) { create(:group, parent: parent_group) }
+
+ context 'when parent_group has a cluster with default scope' do
+ let!(:parent_group_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: '*', groups: [parent_group])
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when parent_group has a cluster that is an exact match' do
+ let!(:parent_group_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: 'review/name', groups: [parent_group])
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+ end
+ end
+
+ context 'with instance clusters' do
+ let!(:default_cluster) do
+ create(:cluster, :provided_by_user, :instance, environment_scope: '*')
+ end
+
+ let!(:cluster) do
+ create(:cluster, :provided_by_user, :instance, environment_scope: 'review/*')
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+ end
+
+ context 'when environment is specified' do
+ let!(:default_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: '*') }
+ let!(:cluster) { create(:cluster, :provided_by_user, environment_scope: 'review/*', projects: [project]) }
+
+ let!(:group_default_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, groups: [group], environment_scope: '*')
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+
+ context 'when environment scope has _' do
+ it 'does not treat it as wildcard' do
+ cluster.update!(environment_scope: 'foo_bar/*')
+
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+
+ context 'when environment name contains an underscore' do
+ let(:environment) { 'foo_bar/test' }
+
+ it 'matches literally for _' do
+ cluster.update!(environment_scope: 'foo_bar/*')
+
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ # The environment name and scope cannot have % at the moment,
+ # but we're considering relaxing it and we should also make sure
+ # it doesn't break in case some data sneaked in somehow as we're
+ # not checking this integrity in database level.
+ context 'when environment scope has %' do
+ it 'does not treat it as wildcard' do
+ cluster.update_attribute(:environment_scope, '*%*')
+
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+
+ context 'when environment name contains a percent char' do
+ let(:environment) { 'foo%bar/test' }
+
+ it 'matches literally for %' do
+ cluster.update_attribute(:environment_scope, 'foo%bar/*')
+
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ context 'when perfectly matched cluster exists' do
+ let!(:perfectly_matched_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'review/name') }
+
+ it 'returns perfectly matched cluster as highest precedence' do
+ is_expected.to eq(perfectly_matched_cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ context 'with multiple clusters and multiple environments' do
+ let!(:cluster_1) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'staging/*') }
+ let!(:cluster_2) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'test/*') }
+
+ let(:environment_1) { 'staging/name' }
+ let(:environment_2) { 'test/name' }
+
+ it 'returns the appropriate cluster' do
+ expect(project.deployment_platform(environment: environment_1)).to eq(cluster_1.platform_kubernetes)
+ expect(project.deployment_platform(environment: environment_2)).to eq(cluster_2.platform_kubernetes)
+ end
+ end
+ end
+
context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service' do
it { is_expected.to be_nil }
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 94b0b55d32c082485c581126349382eaf90cbf3d..da3e1b7d611f6303f263ccb098fff02219ab6c83 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2905,28 +2905,73 @@
subject { project.deployment_variables(environment: environment, kubernetes_namespace: namespace) }
- before do
- expect(project).to receive(:deployment_platform).with(environment: environment)
- .and_return(deployment_platform)
- end
+ context 'when the deployment platform is stubbed' do
+ before do
+ expect(project).to receive(:deployment_platform).with(environment: environment)
+ .and_return(deployment_platform)
+ end
+
+ context 'when project has a deployment platform' do
+ let(:platform_variables) { %w(platform variables) }
+ let(:deployment_platform) { double }
+
+ before do
+ expect(deployment_platform).to receive(:predefined_variables)
+ .with(project: project, environment_name: environment, kubernetes_namespace: namespace)
+ .and_return(platform_variables)
+ end
+
+ it { is_expected.to eq platform_variables }
+ end
- context 'when project has no deployment platform' do
- let(:deployment_platform) { nil }
+ context 'when project has no deployment platform' do
+ let(:deployment_platform) { nil }
- it { is_expected.to eq [] }
+ it { is_expected.to eq [] }
+ end
end
- context 'when project has a deployment platform' do
- let(:platform_variables) { %w(platform variables) }
- let(:deployment_platform) { double }
+ context 'when project has a deployment platforms' do
+ let(:project) { create(:project) }
- before do
- expect(deployment_platform).to receive(:predefined_variables)
- .with(project: project, environment_name: environment, kubernetes_namespace: namespace)
- .and_return(platform_variables)
+ let!(:default_cluster) do
+ create(:cluster,
+ :not_managed,
+ platform_type: :kubernetes,
+ projects: [project],
+ environment_scope: '*',
+ platform_kubernetes: default_cluster_kubernetes)
+ end
+
+ let!(:review_env_cluster) do
+ create(:cluster,
+ :not_managed,
+ platform_type: :kubernetes,
+ projects: [project],
+ environment_scope: 'review/*',
+ platform_kubernetes: review_env_cluster_kubernetes)
+ end
+
+ let(:default_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'default-AAA') }
+ let(:review_env_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'review-AAA') }
+
+ context 'when environment name is review/name' do
+ let!(:environment) { create(:environment, project: project, name: 'review/name') }
+
+ it 'returns variables from this service' do
+ expect(project.deployment_variables(environment: 'review/name'))
+ .to include(key: 'KUBE_TOKEN', value: 'review-AAA', public: false, masked: true)
+ end
end
- it { is_expected.to eq platform_variables }
+ context 'when environment name is other' do
+ let!(:environment) { create(:environment, project: project, name: 'staging/name') }
+
+ it 'returns variables from this service' do
+ expect(project.deployment_variables(environment: 'staging/name'))
+ .to include(key: 'KUBE_TOKEN', value: 'default-AAA', public: false, masked: true)
+ end
+ end
end
end
diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb
index 0e695cc64a23e52084038f711b01b418fdd44c79..068af1485e28b6d97496fec004b9f2dc9b0874fd 100644
--- a/spec/requests/api/group_clusters_spec.rb
+++ b/spec/requests/api/group_clusters_spec.rb
@@ -266,29 +266,51 @@
end
end
- context 'when user tries to add multiple clusters' do
+ context 'non-authorized user' do
before do
- create(:cluster, :provided_by_gcp, :group,
- groups: [group])
-
- post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
+ post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
end
- it 'responds with 400' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first).to eq(_('Instance does not support multiple Kubernetes clusters'))
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
+ end
- context 'non-authorized user' do
+ describe 'PUT /groups/:id/clusters/:cluster_id' do
+ let(:api_url) { 'https://kubernetes.example.com' }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ environment_scope: 'test/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'when another cluster exists' do
before do
- post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
+ create(:cluster, :provided_by_gcp, :group,
+ groups: [group])
+
+ post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
end
- it 'responds with 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
- expect(json_response['message']).to eq('403 Forbidden')
+ it 'allows multiple clusters to be associated to group' do
+ expect(group.reload.clusters.count).to eq(2)
end
end
end
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index f01b3bc9d5f723ed8dcc09757e2bf3f028d85106..ff35e3804761cdf4f99c29d582409eb15c1220e3 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -40,7 +40,7 @@
expect(response).to include_pagination_headers
end
- it 'onlies include authorized clusters' do
+ it 'only includes authorized clusters' do
cluster_ids = json_response.map { |cluster| cluster['id'] }
expect(response).to have_gitlab_http_status(:ok)
@@ -258,29 +258,52 @@
end
end
- context 'when user tries to add multiple clusters' do
+ context 'non-authorized user' do
before do
- create(:cluster, :provided_by_gcp, :project,
- projects: [project])
-
- post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
+ post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
end
- it 'responds with 400' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first)
- .to eq(_('Instance does not support multiple Kubernetes clusters'))
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
+ end
- context 'non-authorized user' do
+ describe 'POST /projects/:id/clusters/user with multiple clusters' do
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:namespace) { project.path }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ namespace: namespace
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ environment_scope: 'production/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'when another cluster exists' do
before do
- post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
+ create(:cluster, :provided_by_gcp, :project,
+ projects: [project])
+
+ post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end
- it 'responds with 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq('403 Forbidden')
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'allows multiple clusters to be associated to project' do
+ expect(project.reload.clusters.count).to eq(2)
end
end
end
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index d45749b52d082e964dd6157f427a9fbfce35cbc2..6e252bee7c0dcf1b03cbef2a940d559a9bc63d27 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -53,13 +53,54 @@
include_context 'valid cluster create params'
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
- it 'does not create a cluster' do
- expect(ClusterProvisionWorker).not_to receive(:perform_async)
- expect { subject }.to raise_error(ArgumentError).and change { Clusters::Cluster.count }.by(0)
+ it 'creates another cluster' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+ expect { subject }.to change { Clusters::Cluster.count }.by(1)
end
end
end
+ context 'when another cluster exists' do
+ let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
+
+ context 'when correct params' do
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: 'gcp-project',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a',
+ legacy_abac: 'true'
+ },
+ clusterable: project
+ }
+ end
+
+ include_examples 'create cluster service success'
+ end
+
+ context 'when invalid params' do
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: '!!!!!!!',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a'
+ },
+ clusterable: project
+ }
+ end
+
+ include_examples 'create cluster service error'
+ end
+ end
+
context 'when params includes :management_project_id' do
subject(:cluster) { described_class.new(user, params).execute(access_token: access_token) }
diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index 852382dd0d84713f70c21002f8bddba23a63a9cf..557bf2162775957e6f24bcc31f65a4e2bfb189df 100644
--- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -102,6 +102,41 @@
let(:payload_alert_firing) { payload_raw['alerts'].first }
let(:token) { 'token' }
+ context 'with environment specific clusters' do
+ let(:prd_cluster) do
+ cluster
+ end
+
+ let(:stg_cluster) do
+ create(:cluster, :provided_by_user, projects: [project], enabled: true, environment_scope: 'stg/*')
+ end
+
+ let(:stg_environment) do
+ create(:environment, project: project, name: 'stg/1')
+ end
+
+ let(:alert_firing) do
+ create(:prometheus_alert, project: project, environment: stg_environment)
+ end
+
+ before do
+ create(:clusters_applications_prometheus, :installed,
+ cluster: prd_cluster, alert_manager_token: token)
+ create(:clusters_applications_prometheus, :installed,
+ cluster: stg_cluster, alert_manager_token: nil)
+ end
+
+ context 'without token' do
+ let(:token_input) { nil }
+
+ it_behaves_like 'notifies alerts'
+ end
+
+ context 'with token' do
+ it_behaves_like 'no notifications', http_status: :unauthorized
+ end
+ end
+
context 'with project specific cluster' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index 31aee08baec6820e2dfd41885936220eddcaad4f..f8a58a828ce4d52f90c5c7f77dffdbd6409a5373 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
RSpec.shared_context 'valid cluster create params' do
+ let(:clusterable) { Clusters::Instance.new }
let(:params) do
{
name: 'test-cluster',
@@ -11,12 +12,14 @@
num_nodes: 1,
machine_type: 'machine_type-a',
legacy_abac: 'true'
- }
+ },
+ clusterable: clusterable
}
end
end
RSpec.shared_context 'invalid cluster create params' do
+ let(:clusterable) { Clusters::Instance.new }
let(:params) do
{
name: 'test-cluster',
@@ -26,7 +29,9 @@
zone: 'us-central1-a',
num_nodes: 1,
machine_type: 'machine_type-a'
- }
+ },
+ clusterable: clusterable
+
}
end
end