Commit 46311254 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '212229-move-features-to-core-multiple-kubernetes-clusters' into 'master'

[RUN AS-IF-FOSS] Move feature to core: "Multiple Kubernetes clusters"

Closes #212229

See merge request gitlab-org/gitlab!35094
parents 999e21a7 9ac622c0
# frozen_string_literal: true # frozen_string_literal: true
module ClustersHelper module ClustersHelper
# EE overrides this
def has_multiple_clusters? def has_multiple_clusters?
false true
end end
def create_new_cluster_label(provider: nil) def create_new_cluster_label(provider: nil)
...@@ -95,5 +94,3 @@ module ClustersHelper ...@@ -95,5 +94,3 @@ module ClustersHelper
can?(user, :admin_cluster, cluster) can?(user, :admin_cluster, cluster)
end end
end end
ClustersHelper.prepend_if_ee('EE::ClustersHelper')
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module Clusters module Clusters
class Cluster < ApplicationRecord class Cluster < ApplicationRecord
prepend HasEnvironmentScope
include Presentable include Presentable
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include FromUnion include FromUnion
...@@ -81,6 +82,7 @@ module Clusters ...@@ -81,6 +82,7 @@ module Clusters
validate :no_groups, unless: :group_type? validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type? validate :no_projects, unless: :project_type?
validate :unique_management_project_environment_scope validate :unique_management_project_environment_scope
validate :unique_environment_scope
after_save :clear_reactive_cache! after_save :clear_reactive_cache!
...@@ -354,6 +356,12 @@ module Clusters ...@@ -354,6 +356,12 @@ module Clusters
end end
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) def managed_namespace(environment)
Clusters::KubernetesNamespaceFinder.new( Clusters::KubernetesNamespaceFinder.new(
self, self,
......
# frozen_string_literal: true # frozen_string_literal: true
module DeploymentPlatform module DeploymentPlatform
# EE would override this and utilize environment argument
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def deployment_platform(environment: nil) def deployment_platform(environment: nil)
@deployment_platform ||= {} @deployment_platform ||= {}
...@@ -20,16 +19,27 @@ module DeploymentPlatform ...@@ -20,16 +19,27 @@ module DeploymentPlatform
find_instance_cluster_platform_kubernetes(environment: environment) find_instance_cluster_platform_kubernetes(environment: environment)
end end
# EE would override this and utilize environment argument def find_platform_kubernetes_with_cte(environment)
def find_platform_kubernetes_with_cte(_environment) if environment
Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?).base_and_ancestors ::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 .enabled.default_environment
.first&.platform_kubernetes .first&.platform_kubernetes
end
end end
# EE would override this and utilize environment argument
def find_instance_cluster_platform_kubernetes(environment: nil) 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 .first&.platform_kubernetes
else
Clusters::Instance.new.clusters.enabled.default_environment
.first&.platform_kubernetes
end
end end
end end
...@@ -13,8 +13,7 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated ...@@ -13,8 +13,7 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
end end
def can_add_cluster? def can_add_cluster?
can?(current_user, :add_cluster, clusterable) && can?(current_user, :add_cluster, clusterable)
(has_no_clusters? || multiple_clusters_available?)
end end
def can_create_cluster? def can_create_cluster?
...@@ -81,17 +80,6 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated ...@@ -81,17 +80,6 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
def learn_more_link def learn_more_link
raise NotImplementedError raise NotImplementedError
end end
private
# Overridden on EE module
def multiple_clusters_available?
false
end
def has_no_clusters?
clusterable.clusters.empty?
end
end end
ClusterablePresenter.prepend_if_ee('EE::ClusterablePresenter') ClusterablePresenter.prepend_if_ee('EE::ClusterablePresenter')
...@@ -19,10 +19,6 @@ module Clusters ...@@ -19,10 +19,6 @@ module Clusters
cluster = Clusters::Cluster.new(cluster_params) 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) validate_management_project_permissions(cluster)
return cluster if cluster.errors.present? return cluster if cluster.errors.present?
...@@ -55,16 +51,9 @@ module Clusters ...@@ -55,16 +51,9 @@ module Clusters
end end
end end
# EE would override this method
def can_create_cluster?
clusterable.clusters.empty?
end
def validate_management_project_permissions(cluster) def validate_management_project_permissions(cluster)
Clusters::Management::ValidateManagementProjectPermissionsService.new(current_user) Clusters::Management::ValidateManagementProjectPermissionsService.new(current_user)
.execute(cluster, params[:management_project_id]) .execute(cluster, params[:management_project_id])
end end
end end
end end
Clusters::CreateService.prepend_if_ee('EE::Clusters::CreateService')
- 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 = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
- help_link_end = '</a>'.html_safe - help_link_end = '</a>'.html_safe
......
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
%p %p
= clusterable.learn_more_link = clusterable.learn_more_link
= render_if_exists 'clusters/multiple_clusters_message' = render 'clusters/clusters/multiple_clusters_message'
---
title: 'Multiple Kubernetes clusters now available in GitLab core'
merge_request: 35094
author:
type: changed
...@@ -316,11 +316,11 @@ The following documentation relates to the DevOps **Configure** stage: ...@@ -316,11 +316,11 @@ The following documentation relates to the DevOps **Configure** stage:
| [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. | | [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. |
| [Installing Applications](user/project/clusters/index.md#installing-applications) | Install Helm charts such as Ingress and Prometheus on Kubernetes. | | [Installing Applications](user/project/clusters/index.md#installing-applications) | Install Helm charts such as Ingress and Prometheus on Kubernetes. |
| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. | | [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. | | [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. | | [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. | | [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. |
<div align="right"> <div align="right">
<a type="button" class="btn btn-default" href="#overview"> <a type="button" class="btn btn-default" href="#overview">
......
...@@ -893,8 +893,8 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then ...@@ -893,8 +893,8 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
++ CI_SERVER_VERSION_PATCH=0 ++ CI_SERVER_VERSION_PATCH=0
++ export CI_SERVER_REVISION=f4cc00ae823 ++ export CI_SERVER_REVISION=f4cc00ae823
++ 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 ++ 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_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 ++ 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 ++ export CI_PROJECT_ID=17893
++ CI_PROJECT_ID=17893 ++ CI_PROJECT_ID=17893
++ export CI_PROJECT_NAME=ci-debug-trace ++ export CI_PROJECT_NAME=ci-debug-trace
......
...@@ -248,11 +248,11 @@ TIP: **Tip:** ...@@ -248,11 +248,11 @@ TIP: **Tip:**
Use the [blue-green deployment](../../ci/environments/incremental_rollouts.md#blue-green-deployment) technique Use the [blue-green deployment](../../ci/environments/incremental_rollouts.md#blue-green-deployment) technique
to minimize downtime and risk. to minimize downtime and risk.
## Using multiple Kubernetes clusters **(PREMIUM)** ## Using multiple Kubernetes clusters
When using Auto DevOps, you can deploy different environments to When using Auto DevOps, you can deploy different environments to
different Kubernetes clusters, due to the 1:1 connection 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) 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: used by Auto DevOps currently defines 3 environment names:
......
...@@ -291,7 +291,7 @@ all within GitLab. Despite its automatic nature, Auto DevOps can also be configu ...@@ -291,7 +291,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: and customized to fit your workflow. Here are some helpful resources for further reading:
1. [Auto DevOps](index.md) 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. [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. [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) 1. [Use a static IP for your cluster](../../user/clusters/applications.md#using-a-static-ip)
......
...@@ -38,10 +38,11 @@ the project. ...@@ -38,10 +38,11 @@ the project.
In the case of sub-groups, GitLab uses the cluster of the closest ancestor group In the case of sub-groups, GitLab uses the cluster of the closest ancestor group
to the project, provided the cluster is not disabled. 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 > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35094) to GitLab Core in 13.2.
more than one Kubernetes cluster to your group, and maintain different clusters
You can associate more than one Kubernetes cluster to your group, and maintain different clusters
for different environments, such as development, staging, and production. for different environments, such as development, staging, and production.
When adding another cluster, When adding another cluster,
...@@ -93,7 +94,7 @@ To clear the cache: ...@@ -93,7 +94,7 @@ To clear the cache:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24580) in GitLab 11.8. > [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 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 this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during
the [Auto DevOps](../../../topics/autodevops/index.md) stages. the [Auto DevOps](../../../topics/autodevops/index.md) stages.
......
...@@ -64,11 +64,12 @@ to: ...@@ -64,11 +64,12 @@ to:
(EKS) using GitLab's UI. (EKS) using GitLab's UI.
- Add an integration to an existing cluster from any Kubernetes platform. - 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, project. That way you can have different clusters for different environments,
like dev, staging, production, and so on. like dev, staging, production, and so on.
......
# 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
# 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
...@@ -6,23 +6,12 @@ module EE ...@@ -6,23 +6,12 @@ module EE
extend ActiveSupport::Concern extend ActiveSupport::Concern
prepended do prepended do
prepend HasEnvironmentScope
include UsageStatistics include UsageStatistics
validate :unique_environment_scope
end end
def prometheus_adapter def prometheus_adapter
application_prometheus application_prometheus
end 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 end
end end
...@@ -780,4 +780,3 @@ module EE ...@@ -780,4 +780,3 @@ module EE
end end
EE::Project.include_if_ee('::EE::GitlabRoutingHelper') EE::Project.include_if_ee('::EE::GitlabRoutingHelper')
EE::Project.include_if_ee('::EE::DeploymentPlatform')
...@@ -89,7 +89,6 @@ class License < ApplicationRecord ...@@ -89,7 +89,6 @@ class License < ApplicationRecord
merge_trains merge_trains
metrics_reports metrics_reports
multiple_approval_rules multiple_approval_rules
multiple_clusters
multiple_group_issue_boards multiple_group_issue_boards
object_storage object_storage
operations_dashboard operations_dashboard
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
module EE module EE
module ClusterablePresenter module ClusterablePresenter
extend ::Gitlab::Utils::Override
def metrics_cluster_path(cluster, params = {}) def metrics_cluster_path(cluster, params = {})
raise NotImplementedError raise NotImplementedError
end end
...@@ -11,12 +9,5 @@ module EE ...@@ -11,12 +9,5 @@ module EE
def metrics_dashboard_path(cluster) def metrics_dashboard_path(cluster)
raise NotImplementedError raise NotImplementedError
end end
private
override :multiple_clusters_available?
def multiple_clusters_available?
clusterable.feature_available?(:multiple_clusters)
end
end end
end end
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
...@@ -442,52 +442,44 @@ RSpec.describe Project do ...@@ -442,52 +442,44 @@ RSpec.describe Project do
end end
describe '#deployment_variables' do describe '#deployment_variables' do
context 'when project has a deployment platforms' do let(:project) { create(:project) }
context 'when multiple clusters (EEP) is enabled' do
before do
stub_licensed_features(multiple_clusters: true)
end
let(:project) { create(:project) }
let!(:default_cluster) do let!(:default_cluster) do
create(:cluster, create(:cluster,
:not_managed, :not_managed,
platform_type: :kubernetes, platform_type: :kubernetes,
projects: [project], projects: [project],
environment_scope: '*', environment_scope: '*',
platform_kubernetes: default_cluster_kubernetes) platform_kubernetes: default_cluster_kubernetes)
end end
let!(:review_env_cluster) do let!(:review_env_cluster) do
create(:cluster, create(:cluster,
:not_managed, :not_managed,
platform_type: :kubernetes, platform_type: :kubernetes,
projects: [project], projects: [project],
environment_scope: 'review/*', environment_scope: 'review/*',
platform_kubernetes: review_env_cluster_kubernetes) platform_kubernetes: review_env_cluster_kubernetes)
end end
let(:default_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'default-AAA') } let(:default_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'default-AAA') }
let(:review_env_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'review-AAA') } let(:review_env_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'review-AAA') }
context 'when environment name is review/name' do context 'when environment name is review/name' do
let!(:environment) { create(:environment, project: project, name: 'review/name') } let!(:environment) { create(:environment, project: project, name: 'review/name') }
it 'returns variables from this service' do it 'returns variables from this service' do
expect(project.deployment_variables(environment: 'review/name')) expect(project.deployment_variables(environment: 'review/name'))
.to include(key: 'KUBE_TOKEN', value: 'review-AAA', public: false, masked: true) .to include(key: 'KUBE_TOKEN', value: 'review-AAA', public: false, masked: true)
end end
end end
context 'when environment name is other' do context 'when environment name is other' do
let!(:environment) { create(:environment, project: project, name: 'staging/name') } let!(:environment) { create(:environment, project: project, name: 'staging/name') }
it 'returns variables from this service' do it 'returns variables from this service' do
expect(project.deployment_variables(environment: 'staging/name')) expect(project.deployment_variables(environment: 'staging/name'))
.to include(key: 'KUBE_TOKEN', value: 'default-AAA', public: false, masked: true) .to include(key: 'KUBE_TOKEN', value: 'default-AAA', public: false, masked: true)
end
end
end end
end end
end end
......
...@@ -52,25 +52,6 @@ RSpec.describe API::GroupClusters do ...@@ -52,25 +52,6 @@ RSpec.describe API::GroupClusters do
expect(json_response['environment_scope']).to eq('*') expect(json_response['environment_scope']).to eq('*')
end end
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 end
describe 'PUT /groups/:id/clusters/:cluster_id' do describe 'PUT /groups/:id/clusters/:cluster_id' do
......
...@@ -78,10 +78,8 @@ RSpec.describe API::ProjectClusters do ...@@ -78,10 +78,8 @@ RSpec.describe API::ProjectClusters do
end end
end end
context 'when license has multiple clusters feature' do context 'when another cluster exists' do
before do before do
stub_licensed_features(multiple_clusters: true)
create(:cluster, :provided_by_gcp, :project, create(:cluster, :provided_by_gcp, :project,
projects: [project]) projects: [project])
......
# 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
...@@ -118,8 +118,6 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do ...@@ -118,8 +118,6 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
end end
before do before do
stub_licensed_features(multiple_clusters: true)
create(:clusters_applications_prometheus, :installed, create(:clusters_applications_prometheus, :installed,
cluster: prd_cluster, alert_manager_token: token) cluster: prd_cluster, alert_manager_token: token)
create(:clusters_applications_prometheus, :installed, create(:clusters_applications_prometheus, :installed,
......
...@@ -6,18 +6,6 @@ module API ...@@ -6,18 +6,6 @@ module API
before { authenticate! } 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 params do
requires :id, type: String, desc: 'The ID of the group' requires :id, type: String, desc: 'The ID of the group'
end end
...@@ -52,6 +40,7 @@ module API ...@@ -52,6 +40,7 @@ module API
params do params do
requires :name, type: String, desc: 'Cluster name' 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 :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 :domain, type: String, desc: 'Cluster base domain'
optional :management_project_id, type: Integer, desc: 'The ID of the management project' 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' 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 @@ module API ...@@ -62,7 +51,6 @@ module API
optional :namespace, type: String, desc: 'Unique namespace related to Group' 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' optional :authorization_type, type: String, values: ::Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
end end
use :create_params_ee
end end
post ':id/clusters/user' do post ':id/clusters/user' do
authorize! :add_cluster, user_group authorize! :add_cluster, user_group
...@@ -85,6 +73,7 @@ module API ...@@ -85,6 +73,7 @@ module API
requires :cluster_id, type: Integer, desc: 'The cluster ID' requires :cluster_id, type: Integer, desc: 'The cluster ID'
optional :name, type: String, desc: 'Cluster name' optional :name, type: String, desc: 'Cluster name'
optional :domain, type: String, desc: 'Cluster base domain' 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 :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 :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
...@@ -92,7 +81,6 @@ module API ...@@ -92,7 +81,6 @@ module API
optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)' 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' optional :namespace, type: String, desc: 'Unique namespace related to Group'
end end
use :update_params_ee
end end
put ':id/clusters/:cluster_id' do put ':id/clusters/:cluster_id' do
authorize! :update_cluster, cluster authorize! :update_cluster, cluster
......
...@@ -6,18 +6,6 @@ module API ...@@ -6,18 +6,6 @@ module API
before { authenticate! } 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 params do
requires :id, type: String, desc: 'The ID of the project' requires :id, type: String, desc: 'The ID of the project'
end end
...@@ -56,6 +44,7 @@ module API ...@@ -56,6 +44,7 @@ module API
requires :name, type: String, desc: 'Cluster name' 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 :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 :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 :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' 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 requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
...@@ -65,7 +54,6 @@ module API ...@@ -65,7 +54,6 @@ module API
optional :namespace, type: String, desc: 'Unique namespace related to Project' 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' optional :authorization_type, type: String, values: ::Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
end end
use :create_params_ee
end end
post ':id/clusters/user' do post ':id/clusters/user' do
authorize! :add_cluster, user_project authorize! :add_cluster, user_project
...@@ -89,6 +77,7 @@ module API ...@@ -89,6 +77,7 @@ module API
requires :cluster_id, type: Integer, desc: 'The cluster ID' requires :cluster_id, type: Integer, desc: 'The cluster ID'
optional :name, type: String, desc: 'Cluster name' optional :name, type: String, desc: 'Cluster name'
optional :domain, type: String, desc: 'Cluster base domain' 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 :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 :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
...@@ -96,7 +85,6 @@ module API ...@@ -96,7 +85,6 @@ module API
optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)' 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' optional :namespace, type: String, desc: 'Unique namespace related to Project'
end end
use :update_params_ee
end end
put ':id/clusters/:cluster_id' do put ':id/clusters/:cluster_id' do
authorize! :update_cluster, cluster authorize! :update_cluster, cluster
......
...@@ -12490,9 +12490,6 @@ msgstr "" ...@@ -12490,9 +12490,6 @@ msgstr ""
msgid "Instance administrators group already exists" msgid "Instance administrators group already exists"
msgstr "" msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
msgid "Instance license" msgid "Instance license"
msgstr "" msgstr ""
......
...@@ -139,6 +139,19 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do ...@@ -139,6 +139,19 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end end
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 context 'when user destroys the cluster' do
before do before do
click_link 'Advanced Settings' click_link 'Advanced Settings'
...@@ -155,19 +168,6 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do ...@@ -155,19 +168,6 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end end
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 context 'when user has not dismissed GCP signup offer' do
before do before do
visit project_clusters_path(project) visit project_clusters_path(project)
......
...@@ -25,6 +25,168 @@ RSpec.describe 'Clusters', :js do ...@@ -25,6 +25,168 @@ RSpec.describe 'Clusters', :js do
end end
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 context 'when user has a cluster and visits cluster index page' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project } let(:project) { cluster.project }
......
...@@ -101,6 +101,12 @@ RSpec.describe ClustersHelper do ...@@ -101,6 +101,12 @@ RSpec.describe ClustersHelper do
end end
end end
describe '#has_multiple_clusters?' do
subject { helper.has_multiple_clusters? }
it { is_expected.to be_truthy }
end
describe '#cluster_type_label' do describe '#cluster_type_label' do
subject { helper.cluster_type_label(cluster_type) } subject { helper.cluster_type_label(cluster_type) }
......
...@@ -10,6 +10,7 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -10,6 +10,7 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
subject { build(:cluster) } subject { build(:cluster) }
it { is_expected.to include_module(HasEnvironmentScope) }
it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:management_project).class_name('::Project') } it { is_expected.to belong_to(:management_project).class_name('::Project') }
it { is_expected.to have_many(:cluster_projects) } it { is_expected.to have_many(:cluster_projects) }
...@@ -289,6 +290,79 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -289,6 +290,79 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
describe 'validations' do describe 'validations' do
subject { cluster.valid? } 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 validates name' do
context 'when provided by user' do context 'when provided by user' do
let!(:cluster) { build(:cluster, :provided_by_user, name: name) } let!(:cluster) { build(:cluster, :provided_by_user, name: name) }
......
...@@ -8,6 +8,241 @@ RSpec.describe DeploymentPlatform do ...@@ -8,6 +8,241 @@ RSpec.describe DeploymentPlatform do
describe '#deployment_platform' do describe '#deployment_platform' do
subject { project.deployment_platform } 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 context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service' do
it { is_expected.to be_nil } it { is_expected.to be_nil }
end end
......
...@@ -2905,28 +2905,73 @@ RSpec.describe Project do ...@@ -2905,28 +2905,73 @@ RSpec.describe Project do
subject { project.deployment_variables(environment: environment, kubernetes_namespace: namespace) } subject { project.deployment_variables(environment: environment, kubernetes_namespace: namespace) }
before do context 'when the deployment platform is stubbed' do
expect(project).to receive(:deployment_platform).with(environment: environment) before do
.and_return(deployment_platform) expect(project).to receive(:deployment_platform).with(environment: environment)
end .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 context 'when project has no deployment platform' do
let(:deployment_platform) { nil } let(:deployment_platform) { nil }
it { is_expected.to eq [] } it { is_expected.to eq [] }
end
end end
context 'when project has a deployment platform' do context 'when project has a deployment platforms' do
let(:platform_variables) { %w(platform variables) } let(:project) { create(:project) }
let(:deployment_platform) { double }
before do let!(:default_cluster) do
expect(deployment_platform).to receive(:predefined_variables) create(:cluster,
.with(project: project, environment_name: environment, kubernetes_namespace: namespace) :not_managed,
.and_return(platform_variables) 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 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
end end
......
...@@ -266,29 +266,51 @@ RSpec.describe API::GroupClusters do ...@@ -266,29 +266,51 @@ RSpec.describe API::GroupClusters do
end end
end end
context 'when user tries to add multiple clusters' do context 'non-authorized user' do
before do before do
create(:cluster, :provided_by_gcp, :group, post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
groups: [group])
post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
end end
it 'responds with 400' do it 'responds with 403' do
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']['base'].first).to eq(_('Instance does not support multiple Kubernetes clusters'))
expect(json_response['message']).to eq('403 Forbidden')
end end
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 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 end
it 'responds with 403' do it 'responds with 201' do
expect(response).to have_gitlab_http_status(:forbidden) 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 end
end end
......
...@@ -40,7 +40,7 @@ RSpec.describe API::ProjectClusters do ...@@ -40,7 +40,7 @@ RSpec.describe API::ProjectClusters do
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
end end
it 'onlies include authorized clusters' do it 'only includes authorized clusters' do
cluster_ids = json_response.map { |cluster| cluster['id'] } cluster_ids = json_response.map { |cluster| cluster['id'] }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
...@@ -258,29 +258,52 @@ RSpec.describe API::ProjectClusters do ...@@ -258,29 +258,52 @@ RSpec.describe API::ProjectClusters do
end end
end end
context 'when user tries to add multiple clusters' do context 'non-authorized user' do
before do before do
create(:cluster, :provided_by_gcp, :project, post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
projects: [project])
post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end end
it 'responds with 400' do it 'responds with 403' do
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']['base'].first) expect(json_response['message']).to eq('403 Forbidden')
.to eq(_('Instance does not support multiple Kubernetes clusters'))
end end
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 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 end
it 'responds with 403' do it 'responds with 201' do
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:created)
expect(json_response['message']).to eq('403 Forbidden') end
it 'allows multiple clusters to be associated to project' do
expect(project.reload.clusters.count).to eq(2)
end end
end end
end end
......
...@@ -53,13 +53,54 @@ RSpec.describe Clusters::CreateService do ...@@ -53,13 +53,54 @@ RSpec.describe Clusters::CreateService do
include_context 'valid cluster create params' include_context 'valid cluster create params'
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) } let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
it 'does not create a cluster' do it 'creates another cluster' do
expect(ClusterProvisionWorker).not_to receive(:perform_async) expect(ClusterProvisionWorker).to receive(:perform_async)
expect { subject }.to raise_error(ArgumentError).and change { Clusters::Cluster.count }.by(0) expect { subject }.to change { Clusters::Cluster.count }.by(1)
end end
end 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 context 'when params includes :management_project_id' do
subject(:cluster) { described_class.new(user, params).execute(access_token: access_token) } subject(:cluster) { described_class.new(user, params).execute(access_token: access_token) }
......
...@@ -102,6 +102,41 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do ...@@ -102,6 +102,41 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
let(:payload_alert_firing) { payload_raw['alerts'].first } let(:payload_alert_firing) { payload_raw['alerts'].first }
let(:token) { 'token' } 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 context 'with project specific cluster' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
......
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_context 'valid cluster create params' do RSpec.shared_context 'valid cluster create params' do
let(:clusterable) { Clusters::Instance.new }
let(:params) do let(:params) do
{ {
name: 'test-cluster', name: 'test-cluster',
...@@ -11,12 +12,14 @@ RSpec.shared_context 'valid cluster create params' do ...@@ -11,12 +12,14 @@ RSpec.shared_context 'valid cluster create params' do
num_nodes: 1, num_nodes: 1,
machine_type: 'machine_type-a', machine_type: 'machine_type-a',
legacy_abac: 'true' legacy_abac: 'true'
} },
clusterable: clusterable
} }
end end
end end
RSpec.shared_context 'invalid cluster create params' do RSpec.shared_context 'invalid cluster create params' do
let(:clusterable) { Clusters::Instance.new }
let(:params) do let(:params) do
{ {
name: 'test-cluster', name: 'test-cluster',
...@@ -26,7 +29,9 @@ RSpec.shared_context 'invalid cluster create params' do ...@@ -26,7 +29,9 @@ RSpec.shared_context 'invalid cluster create params' do
zone: 'us-central1-a', zone: 'us-central1-a',
num_nodes: 1, num_nodes: 1,
machine_type: 'machine_type-a' machine_type: 'machine_type-a'
} },
clusterable: clusterable
} }
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment