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