summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzciński <ayufan@ayufan.eu>2018-11-02 12:34:40 +0000
committerKamil Trzciński <ayufan@ayufan.eu>2018-11-02 12:34:40 +0000
commit46fd31594e166b17d1c0ca232b6acdd09c1b1cdb (patch)
tree28a50ec6870fb9eb977116079efe789a419f4306
parent473262a04b098396c0633436c0e9572561d332c7 (diff)
parentfec21f5542b4b50cb1e49a6c50c8b4d57fa9f62e (diff)
downloadgitlab-ce-46fd31594e166b17d1c0ca232b6acdd09c1b1cdb.tar.gz
Merge branch 'top_level_clusters_controller' into 'master'
Top level clusters controller See merge request gitlab-org/gitlab-ce!22438
-rw-r--r--app/assets/javascripts/pages/projects/clusters/gcp/new/index.js5
-rw-r--r--app/controllers/clusters/applications_controller.rb28
-rw-r--r--app/controllers/clusters/base_controller.rb37
-rw-r--r--app/controllers/clusters/clusters_controller.rb218
-rw-r--r--app/controllers/concerns/project_unauthorized.rb10
-rw-r--r--app/controllers/concerns/routable_actions.rb16
-rw-r--r--app/controllers/projects/application_controller.rb3
-rw-r--r--app/controllers/projects/clusters/applications_controller.rb26
-rw-r--r--app/controllers/projects/clusters_controller.rb222
-rw-r--r--app/finders/clusters_finder.rb8
-rw-r--r--app/helpers/clusters_helper.rb5
-rw-r--r--app/presenters/clusterable_presenter.rb46
-rw-r--r--app/presenters/clusters/cluster_presenter.rb8
-rw-r--r--app/presenters/project_clusterable_presenter.rb15
-rw-r--r--app/services/clusters/create_service.rb22
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml (renamed from app/views/projects/clusters/_advanced_settings.html.haml)2
-rw-r--r--app/views/clusters/clusters/_banner.html.haml (renamed from app/views/projects/clusters/_banner.html.haml)0
-rw-r--r--app/views/clusters/clusters/_cluster.html.haml (renamed from app/views/projects/clusters/_cluster.html.haml)4
-rw-r--r--app/views/clusters/clusters/_empty_state.html.haml (renamed from app/views/projects/clusters/_empty_state.html.haml)4
-rw-r--r--app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml (renamed from app/views/projects/clusters/_gcp_signup_offer_banner.html.haml)0
-rw-r--r--app/views/clusters/clusters/_integration_form.html.haml (renamed from app/views/projects/clusters/_integration_form.html.haml)6
-rw-r--r--app/views/clusters/clusters/_sidebar.html.haml (renamed from app/views/projects/clusters/_sidebar.html.haml)0
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml (renamed from app/views/projects/clusters/gcp/_form.html.haml)4
-rw-r--r--app/views/clusters/clusters/gcp/_header.html.haml (renamed from app/views/projects/clusters/gcp/_header.html.haml)0
-rw-r--r--app/views/clusters/clusters/gcp/_show.html.haml (renamed from app/views/projects/clusters/gcp/_show.html.haml)2
-rw-r--r--app/views/clusters/clusters/index.html.haml (renamed from app/views/projects/clusters/index.html.haml)0
-rw-r--r--app/views/clusters/clusters/new.html.haml (renamed from app/views/projects/clusters/new.html.haml)8
-rw-r--r--app/views/clusters/clusters/show.html.haml (renamed from app/views/projects/clusters/show.html.haml)21
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml (renamed from app/views/projects/clusters/user/_form.html.haml)4
-rw-r--r--app/views/clusters/clusters/user/_header.html.haml (renamed from app/views/projects/clusters/user/_header.html.haml)0
-rw-r--r--app/views/clusters/clusters/user/_show.html.haml (renamed from app/views/projects/clusters/user/_show.html.haml)2
-rw-r--r--changelogs/unreleased/top_level_clusters_controller.yml6
-rw-r--r--config/routes.rb17
-rw-r--r--config/routes/project.rb15
-rw-r--r--qa/qa/page/project/operations/kubernetes/add.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/add_existing.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/index.rb2
-rw-r--r--spec/controllers/projects/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb41
-rw-r--r--spec/presenters/clusterable_presenter_spec.rb17
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb14
-rw-r--r--spec/presenters/project_clusterable_presenter_spec.rb77
-rw-r--r--spec/services/clusters/create_service_spec.rb31
43 files changed, 634 insertions, 318 deletions
diff --git a/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js b/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js
deleted file mode 100644
index d4f34e32a48..00000000000
--- a/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
-
-document.addEventListener('DOMContentLoaded', () => {
- initGkeDropdowns();
-});
diff --git a/app/controllers/clusters/applications_controller.rb b/app/controllers/clusters/applications_controller.rb
new file mode 100644
index 00000000000..250f42f3096
--- /dev/null
+++ b/app/controllers/clusters/applications_controller.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class Clusters::ApplicationsController < Clusters::BaseController
+ before_action :cluster
+ before_action :authorize_create_cluster!, only: [:create]
+
+ def create
+ Clusters::Applications::CreateService
+ .new(@cluster, current_user, create_cluster_application_params)
+ .execute(request)
+
+ head :no_content
+ rescue Clusters::Applications::CreateService::InvalidApplicationError
+ render_404
+ rescue StandardError
+ head :bad_request
+ end
+
+ private
+
+ def cluster
+ @cluster ||= clusterable.clusters.find(params[:id]) || render_404
+ end
+
+ def create_cluster_application_params
+ params.permit(:application, :hostname)
+ end
+end
diff --git a/app/controllers/clusters/base_controller.rb b/app/controllers/clusters/base_controller.rb
new file mode 100644
index 00000000000..ef42f7c4074
--- /dev/null
+++ b/app/controllers/clusters/base_controller.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+class Clusters::BaseController < ApplicationController
+ include RoutableActions
+
+ skip_before_action :authenticate_user!
+ before_action :authorize_read_cluster!
+
+ helper_method :clusterable
+
+ private
+
+ def cluster
+ @cluster ||= clusterable.clusters.find(params[:id])
+ .present(current_user: current_user)
+ end
+
+ def authorize_update_cluster!
+ access_denied! unless can?(current_user, :update_cluster, cluster)
+ end
+
+ def authorize_admin_cluster!
+ access_denied! unless can?(current_user, :admin_cluster, cluster)
+ end
+
+ def authorize_read_cluster!
+ access_denied! unless can?(current_user, :read_cluster, clusterable)
+ end
+
+ def authorize_create_cluster!
+ access_denied! unless can?(current_user, :create_cluster, clusterable)
+ end
+
+ def clusterable
+ raise NotImplementedError
+ end
+end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
new file mode 100644
index 00000000000..f6f2060ebb5
--- /dev/null
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -0,0 +1,218 @@
+# frozen_string_literal: true
+
+class Clusters::ClustersController < Clusters::BaseController
+ include RoutableActions
+
+ before_action :cluster, except: [:index, :new, :create_gcp, :create_user]
+ before_action :generate_gcp_authorize_url, only: [:new]
+ before_action :validate_gcp_token, only: [:new]
+ before_action :gcp_cluster, only: [:new]
+ before_action :user_cluster, only: [:new]
+ before_action :authorize_create_cluster!, only: [:new]
+ before_action :authorize_update_cluster!, only: [:update]
+ before_action :authorize_admin_cluster!, only: [:destroy]
+ before_action :update_applications_status, only: [:cluster_status]
+
+ helper_method :token_in_session
+
+ STATUS_POLLING_INTERVAL = 10_000
+
+ def index
+ clusters = ClustersFinder.new(clusterable, current_user, :all).execute
+ @clusters = clusters.page(params[:page]).per(20)
+ end
+
+ def new
+ end
+
+ # Overridding ActionController::Metal#status is NOT a good idea
+ def cluster_status
+ respond_to do |format|
+ format.json do
+ Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL)
+
+ render json: ClusterSerializer
+ .new(current_user: @current_user)
+ .represent_status(@cluster)
+ end
+ end
+ end
+
+ def show
+ end
+
+ def update
+ Clusters::UpdateService
+ .new(current_user, update_params)
+ .execute(cluster)
+
+ if cluster.valid?
+ respond_to do |format|
+ format.json do
+ head :no_content
+ end
+ format.html do
+ flash[:notice] = _('Kubernetes cluster was successfully updated.')
+ redirect_to cluster.show_path
+ end
+ end
+ else
+ respond_to do |format|
+ format.json { head :bad_request }
+ format.html { render :show }
+ end
+ end
+ end
+
+ def destroy
+ if cluster.destroy
+ flash[:notice] = _('Kubernetes cluster integration was successfully removed.')
+ redirect_to clusterable.index_path, status: :found
+ else
+ flash[:notice] = _('Kubernetes cluster integration was not removed.')
+ render :show
+ end
+ end
+
+ def create_gcp
+ @gcp_cluster = ::Clusters::CreateService
+ .new(current_user, create_gcp_cluster_params)
+ .execute(access_token: token_in_session)
+ .present(current_user: current_user)
+
+ if @gcp_cluster.persisted?
+ redirect_to @gcp_cluster.show_path
+ else
+ generate_gcp_authorize_url
+ validate_gcp_token
+ user_cluster
+
+ render :new, locals: { active_tab: 'gcp' }
+ end
+ end
+
+ def create_user
+ @user_cluster = ::Clusters::CreateService
+ .new(current_user, create_user_cluster_params)
+ .execute(access_token: token_in_session)
+ .present(current_user: current_user)
+
+ if @user_cluster.persisted?
+ redirect_to @user_cluster.show_path
+ else
+ generate_gcp_authorize_url
+ validate_gcp_token
+ gcp_cluster
+
+ render :new, locals: { active_tab: 'user' }
+ end
+ end
+
+ private
+
+ def update_params
+ if cluster.managed?
+ params.require(:cluster).permit(
+ :enabled,
+ :environment_scope,
+ platform_kubernetes_attributes: [
+ :namespace
+ ]
+ )
+ else
+ params.require(:cluster).permit(
+ :enabled,
+ :name,
+ :environment_scope,
+ platform_kubernetes_attributes: [
+ :api_url,
+ :token,
+ :ca_cert,
+ :namespace
+ ]
+ )
+ end
+ end
+
+ def create_gcp_cluster_params
+ params.require(:cluster).permit(
+ :enabled,
+ :name,
+ :environment_scope,
+ provider_gcp_attributes: [
+ :gcp_project_id,
+ :zone,
+ :num_nodes,
+ :machine_type,
+ :legacy_abac
+ ]).merge(
+ provider_type: :gcp,
+ platform_type: :kubernetes,
+ clusterable: clusterable.subject
+ )
+ end
+
+ def create_user_cluster_params
+ params.require(:cluster).permit(
+ :enabled,
+ :name,
+ :environment_scope,
+ platform_kubernetes_attributes: [
+ :namespace,
+ :api_url,
+ :token,
+ :ca_cert,
+ :authorization_type
+ ]).merge(
+ provider_type: :user,
+ platform_type: :kubernetes,
+ clusterable: clusterable.subject
+ )
+ end
+
+ def generate_gcp_authorize_url
+ state = generate_session_key_redirect(clusterable.new_path.to_s)
+
+ @authorize_url = GoogleApi::CloudPlatform::Client.new(
+ nil, callback_google_api_auth_url,
+ state: state).authorize_url
+ rescue GoogleApi::Auth::ConfigMissingError
+ # no-op
+ end
+
+ def gcp_cluster
+ @gcp_cluster = ::Clusters::Cluster.new.tap do |cluster|
+ cluster.build_provider_gcp
+ end
+ end
+
+ def user_cluster
+ @user_cluster = ::Clusters::Cluster.new.tap do |cluster|
+ cluster.build_platform_kubernetes
+ end
+ end
+
+ def validate_gcp_token
+ @valid_gcp_token = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
+ .validate_token(expires_at_in_session)
+ end
+
+ def token_in_session
+ session[GoogleApi::CloudPlatform::Client.session_key_for_token]
+ end
+
+ def expires_at_in_session
+ @expires_at_in_session ||=
+ session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
+ end
+
+ def generate_session_key_redirect(uri)
+ GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
+ session[key] = uri
+ end
+ end
+
+ def update_applications_status
+ @cluster.applications.each(&:schedule_status_update)
+ end
+end
diff --git a/app/controllers/concerns/project_unauthorized.rb b/app/controllers/concerns/project_unauthorized.rb
new file mode 100644
index 00000000000..f59440dbc59
--- /dev/null
+++ b/app/controllers/concerns/project_unauthorized.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module ProjectUnauthorized
+ extend ActiveSupport::Concern
+
+ # EE would override this
+ def project_unauthorized_proc
+ # no-op
+ end
+end
diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb
index 88939b002b2..5624eb3aa45 100644
--- a/app/controllers/concerns/routable_actions.rb
+++ b/app/controllers/concerns/routable_actions.rb
@@ -3,23 +3,25 @@
module RoutableActions
extend ActiveSupport::Concern
- def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil)
+ def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil, not_found_or_authorized_proc: nil)
routable = routable_klass.find_by_full_path(requested_full_path, follow_redirects: request.get?)
if routable_authorized?(routable, extra_authorization_proc)
ensure_canonical_path(routable, requested_full_path)
routable
else
- handle_not_found_or_authorized(routable)
+ if not_found_or_authorized_proc
+ not_found_or_authorized_proc.call(routable)
+ end
+
+ route_not_found unless performed?
+
nil
end
end
- # This is overridden in gitlab-ee.
- def handle_not_found_or_authorized(_routable)
- route_not_found
- end
-
def routable_authorized?(routable, extra_authorization_proc)
+ return false unless routable
+
action = :"read_#{routable.class.to_s.underscore}"
return false unless can?(current_user, action, routable)
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index a2bdcaefa9b..e0677ce3fbc 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -3,6 +3,7 @@
class Projects::ApplicationController < ApplicationController
include CookiesHelper
include RoutableActions
+ include ProjectUnauthorized
include ChecksCollaboration
skip_before_action :authenticate_user!
@@ -21,7 +22,7 @@ class Projects::ApplicationController < ApplicationController
path = File.join(params[:namespace_id], params[:project_id] || params[:id])
auth_proc = ->(project) { !project.pending_delete? }
- @project = find_routable!(Project, path, extra_authorization_proc: auth_proc)
+ @project = find_routable!(Project, path, extra_authorization_proc: auth_proc, not_found_or_authorized_proc: project_unauthorized_proc)
end
def build_canonical_path(project)
diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb
index bcea96bce94..c7b6218d007 100644
--- a/app/controllers/projects/clusters/applications_controller.rb
+++ b/app/controllers/projects/clusters/applications_controller.rb
@@ -1,29 +1,17 @@
# frozen_string_literal: true
-class Projects::Clusters::ApplicationsController < Projects::ApplicationController
- before_action :cluster
- before_action :authorize_read_cluster!
- before_action :authorize_create_cluster!, only: [:create]
+class Projects::Clusters::ApplicationsController < Clusters::ApplicationsController
+ include ProjectUnauthorized
- def create
- Clusters::Applications::CreateService
- .new(@cluster, current_user, create_cluster_application_params)
- .execute(request)
-
- head :no_content
- rescue Clusters::Applications::CreateService::InvalidApplicationError
- render_404
- rescue StandardError
- head :bad_request
- end
+ prepend_before_action :project
private
- def cluster
- @cluster ||= project.clusters.find(params[:id]) || render_404
+ def clusterable
+ @clusterable ||= ClusterablePresenter.fabricate(project, current_user: current_user)
end
- def create_cluster_application_params
- params.permit(:application, :hostname)
+ def project
+ @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc)
end
end
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index 62adc66fb09..feda6deeaa6 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -1,224 +1,24 @@
# frozen_string_literal: true
-class Projects::ClustersController < Projects::ApplicationController
- before_action :cluster, except: [:index, :new, :create_gcp, :create_user]
- before_action :authorize_read_cluster!
- before_action :generate_gcp_authorize_url, only: [:new]
- before_action :validate_gcp_token, only: [:new]
- before_action :gcp_cluster, only: [:new]
- before_action :user_cluster, only: [:new]
- before_action :authorize_create_cluster!, only: [:new]
- before_action :authorize_update_cluster!, only: [:update]
- before_action :authorize_admin_cluster!, only: [:destroy]
- before_action :update_applications_status, only: [:status]
- helper_method :token_in_session
+class Projects::ClustersController < Clusters::ClustersController
+ include ProjectUnauthorized
- STATUS_POLLING_INTERVAL = 10_000
+ prepend_before_action :project
+ before_action :repository
- def index
- clusters = ClustersFinder.new(project, current_user, :all).execute
- @clusters = clusters.page(params[:page]).per(20)
- end
-
- def new
- end
-
- def status
- respond_to do |format|
- format.json do
- Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL)
-
- render json: ClusterSerializer
- .new(project: @project, current_user: @current_user)
- .represent_status(@cluster)
- end
- end
- end
-
- def show
- end
-
- def update
- Clusters::UpdateService
- .new(current_user, update_params)
- .execute(cluster)
-
- if cluster.valid?
- respond_to do |format|
- format.json do
- head :no_content
- end
- format.html do
- flash[:notice] = _('Kubernetes cluster was successfully updated.')
- redirect_to project_cluster_path(project, cluster)
- end
- end
- else
- respond_to do |format|
- format.json { head :bad_request }
- format.html { render :show }
- end
- end
- end
-
- def destroy
- if cluster.destroy
- flash[:notice] = _('Kubernetes cluster integration was successfully removed.')
- redirect_to project_clusters_path(project), status: :found
- else
- flash[:notice] = _('Kubernetes cluster integration was not removed.')
- render :show
- end
- end
-
- def create_gcp
- @gcp_cluster = ::Clusters::CreateService
- .new(current_user, create_gcp_cluster_params)
- .execute(project: project, access_token: token_in_session)
-
- if @gcp_cluster.persisted?
- redirect_to project_cluster_path(project, @gcp_cluster)
- else
- generate_gcp_authorize_url
- validate_gcp_token
- user_cluster
-
- render :new, locals: { active_tab: 'gcp' }
- end
- end
-
- def create_user
- @user_cluster = ::Clusters::CreateService
- .new(current_user, create_user_cluster_params)
- .execute(project: project, access_token: token_in_session)
-
- if @user_cluster.persisted?
- redirect_to project_cluster_path(project, @user_cluster)
- else
- generate_gcp_authorize_url
- validate_gcp_token
- gcp_cluster
-
- render :new, locals: { active_tab: 'user' }
- end
- end
+ layout 'project'
private
- def cluster
- @cluster ||= project.clusters.find(params[:id])
- .present(current_user: current_user)
- end
-
- def update_params
- if cluster.managed?
- params.require(:cluster).permit(
- :enabled,
- :environment_scope,
- platform_kubernetes_attributes: [
- :namespace
- ]
- )
- else
- params.require(:cluster).permit(
- :enabled,
- :name,
- :environment_scope,
- platform_kubernetes_attributes: [
- :api_url,
- :token,
- :ca_cert,
- :namespace
- ]
- )
- end
- end
-
- def create_gcp_cluster_params
- params.require(:cluster).permit(
- :enabled,
- :name,
- :environment_scope,
- provider_gcp_attributes: [
- :gcp_project_id,
- :zone,
- :num_nodes,
- :machine_type,
- :legacy_abac
- ]).merge(
- provider_type: :gcp,
- platform_type: :kubernetes
- )
- end
-
- def create_user_cluster_params
- params.require(:cluster).permit(
- :enabled,
- :name,
- :environment_scope,
- platform_kubernetes_attributes: [
- :namespace,
- :api_url,
- :token,
- :ca_cert,
- :authorization_type
- ]).merge(
- provider_type: :user,
- platform_type: :kubernetes
- )
- end
-
- def generate_gcp_authorize_url
- state = generate_session_key_redirect(new_project_cluster_path(@project).to_s)
-
- @authorize_url = GoogleApi::CloudPlatform::Client.new(
- nil, callback_google_api_auth_url,
- state: state).authorize_url
- rescue GoogleApi::Auth::ConfigMissingError
- # no-op
- end
-
- def gcp_cluster
- @gcp_cluster = ::Clusters::Cluster.new.tap do |cluster|
- cluster.build_provider_gcp
- end
- end
-
- def user_cluster
- @user_cluster = ::Clusters::Cluster.new.tap do |cluster|
- cluster.build_platform_kubernetes
- end
- end
-
- def validate_gcp_token
- @valid_gcp_token = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
- .validate_token(expires_at_in_session)
- end
-
- def token_in_session
- session[GoogleApi::CloudPlatform::Client.session_key_for_token]
- end
-
- def expires_at_in_session
- @expires_at_in_session ||=
- session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
- end
-
- def generate_session_key_redirect(uri)
- GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
- session[key] = uri
- end
- end
-
- def authorize_update_cluster!
- access_denied! unless can?(current_user, :update_cluster, cluster)
+ def clusterable
+ @clusterable ||= ClusterablePresenter.fabricate(project, current_user: current_user)
end
- def authorize_admin_cluster!
- access_denied! unless can?(current_user, :admin_cluster, cluster)
+ def project
+ @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc)
end
- def update_applications_status
- @cluster.applications.each(&:schedule_status_update)
+ def repository
+ @repository ||= project.repository
end
end
diff --git a/app/finders/clusters_finder.rb b/app/finders/clusters_finder.rb
index b40d6c41b71..0cce493b73e 100644
--- a/app/finders/clusters_finder.rb
+++ b/app/finders/clusters_finder.rb
@@ -1,20 +1,20 @@
# frozen_string_literal: true
class ClustersFinder
- def initialize(project, user, scope)
- @project = project
+ def initialize(clusterable, user, scope)
+ @clusterable = clusterable
@user = user
@scope = scope || :active
end
def execute
- clusters = project.clusters
+ clusters = clusterable.clusters
filter_by_scope(clusters)
end
private
- attr_reader :project, :user, :scope
+ attr_reader :clusterable, :user, :scope
def filter_by_scope(clusters)
case scope.to_sym
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 19eb763e1de..916dcb1a308 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module ClustersHelper
- def has_multiple_clusters?(project)
+ # EE overrides this
+ def has_multiple_clusters?
false
end
@@ -10,7 +11,7 @@ module ClustersHelper
return unless show_gcp_signup_offer?
content_tag :section, class: 'no-animate expanded' do
- render 'projects/clusters/gcp_signup_offer_banner'
+ render 'clusters/clusters/gcp_signup_offer_banner'
end
end
end
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
new file mode 100644
index 00000000000..cff0e74d6ea
--- /dev/null
+++ b/app/presenters/clusterable_presenter.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+class ClusterablePresenter < Gitlab::View::Presenter::Delegated
+ presents :clusterable
+
+ def self.fabricate(clusterable, **attributes)
+ presenter_class = "#{clusterable.class.name}ClusterablePresenter".constantize
+ attributes_with_presenter_class = attributes.merge(presenter_class: presenter_class)
+
+ Gitlab::View::Presenter::Factory
+ .new(clusterable, attributes_with_presenter_class)
+ .fabricate!
+ end
+
+ def can_create_cluster?
+ can?(current_user, :create_cluster, clusterable)
+ end
+
+ def index_path
+ polymorphic_path([clusterable, :clusters])
+ end
+
+ def new_path
+ new_polymorphic_path([clusterable, :cluster])
+ end
+
+ def create_user_clusters_path
+ polymorphic_path([clusterable, :clusters], action: :create_user)
+ end
+
+ def create_gcp_clusters_path
+ polymorphic_path([clusterable, :clusters], action: :create_gcp)
+ end
+
+ def cluster_status_cluster_path(cluster, params = {})
+ raise NotImplementedError
+ end
+
+ def install_applications_cluster_path(cluster, application)
+ raise NotImplementedError
+ end
+
+ def cluster_path(cluster, params = {})
+ raise NotImplementedError
+ end
+end
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index dfdd8e82f97..78d632eb77c 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -11,5 +11,13 @@ module Clusters
def can_toggle_cluster?
can?(current_user, :update_cluster, cluster) && created?
end
+
+ def show_path
+ if cluster.project_type?
+ project_cluster_path(project, cluster)
+ else
+ raise NotImplementedError
+ end
+ end
end
end
diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb
new file mode 100644
index 00000000000..12077b2e735
--- /dev/null
+++ b/app/presenters/project_clusterable_presenter.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ProjectClusterablePresenter < ClusterablePresenter
+ def cluster_status_cluster_path(cluster, params = {})
+ cluster_status_project_cluster_path(clusterable, cluster, params)
+ end
+
+ def install_applications_cluster_path(cluster, application)
+ install_applications_project_cluster_path(clusterable, cluster, application)
+ end
+
+ def cluster_path(cluster, params = {})
+ project_cluster_path(clusterable, cluster, params)
+ end
+end
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index cd843b8ffa8..270db4a52fd 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -8,10 +8,11 @@ module Clusters
@current_user, @params = user, params.dup
end
- def execute(project:, access_token: nil)
- raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?(project)
+ def execute(access_token: nil)
+ raise ArgumentError, 'Unknown clusterable provided' unless clusterable
+ raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?
- cluster_params = params.merge(user: current_user, cluster_type: :project_type, projects: [project])
+ cluster_params = params.merge(user: current_user).merge(clusterable_params)
cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token
end
@@ -27,9 +28,20 @@ module Clusters
Clusters::Cluster.create(cluster_params)
end
+ def clusterable
+ @clusterable ||= params.delete(:clusterable)
+ end
+
+ def clusterable_params
+ case clusterable
+ when ::Project
+ { cluster_type: :project_type, projects: [clusterable] }
+ end
+ end
+
# EE would override this method
- def can_create_cluster?(project)
- project.clusters.empty?
+ def can_create_cluster?
+ clusterable.clusters.empty?
end
end
end
diff --git a/app/views/projects/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index 243e8cd9ba0..7037c80aa6b 100644
--- a/app/views/projects/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -12,4 +12,4 @@
= s_('ClusterIntegration|Remove Kubernetes cluster integration')
%p
= s_("ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster.")
- = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster.")})
+ = link_to(s_('ClusterIntegration|Remove integration'), clusterable.cluster_path(@cluster), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster.")})
diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml
index 73cfea0ef92..73cfea0ef92 100644
--- a/app/views/projects/clusters/_banner.html.haml
+++ b/app/views/clusters/clusters/_banner.html.haml
diff --git a/app/views/projects/clusters/_cluster.html.haml b/app/views/clusters/clusters/_cluster.html.haml
index 2d7f7c6b1fb..facbcb7fc59 100644
--- a/app/views/projects/clusters/_cluster.html.haml
+++ b/app/views/clusters/clusters/_cluster.html.haml
@@ -2,7 +2,7 @@
.table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster")
.table-mobile-content
- = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster)
+ = link_to cluster.name, cluster.show_path
.table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope")
.table-mobile-content= cluster.environment_scope
@@ -16,7 +16,7 @@
class: "#{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}",
"aria-label": s_("ClusterIntegration|Toggle Kubernetes Cluster"),
disabled: !cluster.can_toggle_cluster?,
- data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
+ data: { endpoint: clusterable.cluster_path(cluster, format: :json) } }
%input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? }
= icon("spinner spin", class: "loading-icon")
%span.toggle-icon
diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/clusters/clusters/_empty_state.html.haml
index b8a3556a206..800e76d92ef 100644
--- a/app/views/projects/clusters/_empty_state.html.haml
+++ b/app/views/clusters/clusters/_empty_state.html.haml
@@ -7,6 +7,6 @@
- link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
%p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
- - if can?(current_user, :create_cluster, @project)
+ - if clusterable.can_create_cluster?
.text-center
- = link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
+ = link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success'
diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
index 73b11d509d3..73b11d509d3 100644
--- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
diff --git a/app/views/projects/clusters/_integration_form.html.haml b/app/views/clusters/clusters/_integration_form.html.haml
index d0a553e3414..5e451f60c9d 100644
--- a/app/views/projects/clusters/_integration_form.html.haml
+++ b/app/views/clusters/clusters/_integration_form.html.haml
@@ -1,4 +1,4 @@
-= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
+= form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster do |field|
= form_errors(@cluster)
.form-group
%h5= s_('ClusterIntegration|Integration status')
@@ -13,7 +13,7 @@
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
.form-text.text-muted= s_('ClusterIntegration|Enable or disable GitLab\'s connection to your Kubernetes cluster.')
- - if has_multiple_clusters?(@project)
+ - if has_multiple_clusters?
.form-group
%h5= s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
@@ -23,7 +23,7 @@
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
- - unless has_multiple_clusters?(@project)
+ - unless has_multiple_clusters?
%h5= s_('ClusterIntegration|Environment scope')
%p
%code *
diff --git a/app/views/projects/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml
index 3d10348212f..3d10348212f 100644
--- a/app/views/projects/clusters/_sidebar.html.haml
+++ b/app/views/clusters/clusters/_sidebar.html.haml
diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index 171ceeceb68..ad842036a62 100644
--- a/app/views/projects/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -12,14 +12,14 @@
%p= link_to('Select a different Google account', @authorize_url)
-= form_for @gcp_cluster, html: { class: 'js-gke-cluster-creation prepend-top-20', data: { token: token_in_session } }, url: create_gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
+= form_for @gcp_cluster, html: { class: 'js-gke-cluster-creation prepend-top-20', data: { token: token_in_session } }, url: clusterable.create_gcp_clusters_path, as: :cluster do |field|
= form_errors(@gcp_cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
- = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
+ = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?, placeholder: s_('ClusterIntegration|Environment scope')
= field.fields_for :provider_gcp, @gcp_cluster.provider_gcp do |provider_gcp_field|
.form-group
diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/clusters/clusters/gcp/_header.html.haml
index a2ad3cd64df..a2ad3cd64df 100644
--- a/app/views/projects/clusters/gcp/_header.html.haml
+++ b/app/views/clusters/clusters/gcp/_header.html.haml
diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/clusters/clusters/gcp/_show.html.haml
index 779c9c245c1..6021b220285 100644
--- a/app/views/projects/clusters/gcp/_show.html.haml
+++ b/app/views/clusters/clusters/gcp/_show.html.haml
@@ -6,7 +6,7 @@
%span.input-group-append
= clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'), class: 'input-group-text btn-default')
-= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
+= form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster do |field|
= form_errors(@cluster)
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
diff --git a/app/views/projects/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml
index a55de84b5cd..a55de84b5cd 100644
--- a/app/views/projects/clusters/index.html.haml
+++ b/app/views/clusters/clusters/index.html.haml
diff --git a/app/views/projects/clusters/new.html.haml b/app/views/clusters/clusters/new.html.haml
index a38003f5750..eeeef6bd824 100644
--- a/app/views/projects/clusters/new.html.haml
+++ b/app/views/clusters/clusters/new.html.haml
@@ -19,9 +19,9 @@
.tab-content.gitlab-tab-content
.tab-pane{ id: 'create-gcp-cluster-pane', class: active_when(active_tab == 'gcp'), role: 'tabpanel' }
- = render 'projects/clusters/gcp/header'
+ = render 'clusters/clusters/gcp/header'
- if @valid_gcp_token
- = render 'projects/clusters/gcp/form'
+ = render 'clusters/clusters/gcp/form'
- elsif @authorize_url
.signin-with-google
= link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px'), @authorize_url)
@@ -32,5 +32,5 @@
= s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link }
.tab-pane{ id: 'add-user-cluster-pane', class: active_when(active_tab == 'user'), role: 'tabpanel' }
- = render 'projects/clusters/user/header'
- = render 'projects/clusters/user/form'
+ = render 'clusters/clusters/user/header'
+ = render 'clusters/clusters/user/form'
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index eddd3613c5f..1e1157c34bd 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -1,24 +1,25 @@
- @content_class = "limit-container-width" unless fluid_layout
-- add_to_breadcrumbs "Kubernetes Clusters", project_clusters_path(@project)
+- add_to_breadcrumbs "Kubernetes Clusters", clusterable.index_path
- breadcrumb_title @cluster.name
- page_title _("Kubernetes Cluster")
+- manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project
- expanded = Rails.env.test?
-- status_path = status_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster)
+- status_path = clusterable.cluster_status_cluster_path(@cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster)
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
- install_helm_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :helm),
- install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress),
- install_prometheus_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :prometheus),
- install_runner_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :runner),
- install_jupyter_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :jupyter),
+ install_helm_path: clusterable.install_applications_cluster_path(@cluster, :helm),
+ install_ingress_path: clusterable.install_applications_cluster_path(@cluster, :ingress),
+ install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus),
+ install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
+ install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address'),
ingress_dns_help_path: help_page_path('topics/autodevops/quick_start_guide.md', anchor: 'point-dns-at-cluster-ip'),
- manage_prometheus_path: edit_project_service_path(@cluster.project, 'prometheus') } }
+ manage_prometheus_path: manage_prometheus_path } }
.js-cluster-application-notice
.flash-container
@@ -38,9 +39,9 @@
%p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
.settings-content
- if @cluster.managed?
- = render 'projects/clusters/gcp/show'
+ = render 'clusters/clusters/gcp/show'
- else
- = render 'projects/clusters/user/show'
+ = render 'clusters/clusters/user/show'
%section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index 54a6e685bb0..4e6232b69de 100644
--- a/app/views/projects/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -1,9 +1,9 @@
-= form_for @user_cluster, url: create_user_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
+= form_for @user_cluster, url: clusterable.create_user_clusters_path, as: :cluster do |field|
= form_errors(@user_cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
- - if has_multiple_clusters?(@project)
+ - if has_multiple_clusters?
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
= field.text_field :environment_scope, class: 'form-control', placeholder: s_('ClusterIntegration|Environment scope')
diff --git a/app/views/projects/clusters/user/_header.html.haml b/app/views/clusters/clusters/user/_header.html.haml
index 749177fa6c1..749177fa6c1 100644
--- a/app/views/projects/clusters/user/_header.html.haml
+++ b/app/views/clusters/clusters/user/_header.html.haml
diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/clusters/clusters/user/_show.html.haml
index 5b57f7ceb7d..a871fef0240 100644
--- a/app/views/projects/clusters/user/_show.html.haml
+++ b/app/views/clusters/clusters/user/_show.html.haml
@@ -1,4 +1,4 @@
-= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
+= form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster do |field|
= form_errors(@cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
diff --git a/changelogs/unreleased/top_level_clusters_controller.yml b/changelogs/unreleased/top_level_clusters_controller.yml
new file mode 100644
index 00000000000..1fe1d048de4
--- /dev/null
+++ b/changelogs/unreleased/top_level_clusters_controller.yml
@@ -0,0 +1,6 @@
+---
+title: Change to top level controller for clusters so that we can use it for project
+ clusters (now) and group clusters (later)
+merge_request: 22438
+author:
+type: other
diff --git a/config/routes.rb b/config/routes.rb
index 37c7f98ec98..d2d91647d0b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -84,6 +84,23 @@ Rails.application.routes.draw do
draw :instance_statistics
end
+ concern :clusterable do
+ resources :clusters, only: [:index, :new, :show, :update, :destroy] do
+ collection do
+ post :create_user
+ post :create_gcp
+ end
+
+ member do
+ scope :applications do
+ post '/:application', to: 'clusters/applications#create', as: :install_applications
+ end
+
+ get :cluster_status, format: :json
+ end
+ end
+ end
+
draw :api
draw :sidekiq
draw :help
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 73c46f72168..387d2363552 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -206,20 +206,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :clusters, except: [:edit, :create] do
- collection do
- post :create_gcp
- post :create_user
- end
-
- member do
- get :status, format: :json
-
- scope :applications do
- post '/:application', to: 'clusters/applications#create', as: :install_applications
- end
- end
- end
+ concerns :clusterable
resources :environments, except: [:destroy] do
member do
diff --git a/qa/qa/page/project/operations/kubernetes/add.rb b/qa/qa/page/project/operations/kubernetes/add.rb
index 18c16ca6db7..939f912ea85 100644
--- a/qa/qa/page/project/operations/kubernetes/add.rb
+++ b/qa/qa/page/project/operations/kubernetes/add.rb
@@ -4,7 +4,7 @@ module QA
module Operations
module Kubernetes
class Add < Page::Base
- view 'app/views/projects/clusters/new.html.haml' do
+ view 'app/views/clusters/clusters/new.html.haml' do
element :add_existing_cluster_button, "Add existing cluster" # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb
index f8e026b4405..f3ab636ecc1 100644
--- a/qa/qa/page/project/operations/kubernetes/add_existing.rb
+++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb
@@ -4,7 +4,7 @@ module QA
module Operations
module Kubernetes
class AddExisting < Page::Base
- view 'app/views/projects/clusters/user/_form.html.haml' do
+ view 'app/views/clusters/clusters/user/_form.html.haml' do
element :cluster_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
element :api_url, 'text_field :api_url' # rubocop:disable QA/ElementWithPattern
element :ca_certificate, 'text_area :ca_cert' # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb
index 312b459ac89..67a74af1cd2 100644
--- a/qa/qa/page/project/operations/kubernetes/index.rb
+++ b/qa/qa/page/project/operations/kubernetes/index.rb
@@ -4,7 +4,7 @@ module QA
module Operations
module Kubernetes
class Index < Page::Base
- view 'app/views/projects/clusters/_empty_state.html.haml' do
+ view 'app/views/clusters/clusters/_empty_state.html.haml' do
element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
end
diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb
index 9e17e392d3d..8106453a775 100644
--- a/spec/controllers/projects/clusters/applications_controller_spec.rb
+++ b/spec/controllers/projects/clusters/applications_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::Clusters::ApplicationsController do
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 9201332c5c8..64fa787e469 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ClustersController do
@@ -218,9 +220,9 @@ describe Projects::ClustersController do
describe 'security' do
before do
allow_any_instance_of(described_class)
- .to receive(:token_in_session).and_return('token')
+ .to receive(:token_in_session).and_return('token')
allow_any_instance_of(described_class)
- .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
+ .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
.to receive(:projects_zones_clusters_create) do
OpenStruct.new(
@@ -318,14 +320,15 @@ describe Projects::ClustersController do
end
end
- describe 'GET status' do
+ describe 'GET cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
def go
- get :status, namespace_id: project.namespace,
- project_id: project,
- id: cluster,
- format: :json
+ get :cluster_status,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: cluster,
+ format: :json
end
describe 'functionality' do
@@ -359,9 +362,10 @@ describe Projects::ClustersController do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
def go
- get :show, namespace_id: project.namespace,
- project_id: project,
- id: cluster
+ get :show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
end
describe 'functionality' do
@@ -401,8 +405,8 @@ describe Projects::ClustersController do
end
def go(format: :html)
- put :update, params.merge(namespace_id: project.namespace,
- project_id: project,
+ put :update, params.merge(namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
id: cluster,
format: format
)
@@ -530,9 +534,10 @@ describe Projects::ClustersController do
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
def go
- delete :destroy, namespace_id: project.namespace,
- project_id: project,
- id: cluster
+ delete :destroy,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
end
describe 'functionality' do
@@ -591,4 +596,10 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:external) }
end
end
+
+ context 'no project_id param' do
+ it 'does not respond to any action without project_id param' do
+ expect { get :index }.to raise_error(ActionController::UrlGenerationError)
+ end
+ end
end
diff --git a/spec/presenters/clusterable_presenter_spec.rb b/spec/presenters/clusterable_presenter_spec.rb
new file mode 100644
index 00000000000..4f4ae5e07c5
--- /dev/null
+++ b/spec/presenters/clusterable_presenter_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClusterablePresenter do
+ include Gitlab::Routing.url_helpers
+
+ describe '.fabricate' do
+ let(:project) { create(:project) }
+
+ subject { described_class.fabricate(project) }
+
+ it 'creates an object from a descendant presenter' do
+ expect(subject).to be_kind_of(ProjectClusterablePresenter)
+ end
+ end
+end
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index e96dbfb73c0..7af181f37d5 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -1,7 +1,9 @@
require 'spec_helper'
describe Clusters::ClusterPresenter do
- let(:cluster) { create(:cluster, :provided_by_gcp) }
+ include Gitlab::Routing.url_helpers
+
+ let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
subject(:presenter) do
described_class.new(cluster)
@@ -71,4 +73,14 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq(false) }
end
end
+
+ describe '#show_path' do
+ subject { described_class.new(cluster).show_path }
+
+ context 'project_type cluster' do
+ let(:project) { cluster.project }
+
+ it { is_expected.to eq(project_cluster_path(project, cluster)) }
+ end
+ end
end
diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb
new file mode 100644
index 00000000000..c50d90ae1e8
--- /dev/null
+++ b/spec/presenters/project_clusterable_presenter_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectClusterablePresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:presenter) { described_class.new(project) }
+ let(:project) { create(:project) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+
+ describe '#can_create_cluster?' do
+ let(:user) { create(:user) }
+
+ subject { presenter.can_create_cluster? }
+
+ before do
+ allow(presenter).to receive(:current_user).and_return(user)
+ end
+
+ context 'when user can create' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when user cannot create' do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#index_path' do
+ subject { presenter.index_path }
+
+ it { is_expected.to eq(project_clusters_path(project)) }
+ end
+
+ describe '#new_path' do
+ subject { presenter.new_path }
+
+ it { is_expected.to eq(new_project_cluster_path(project)) }
+ end
+
+ describe '#create_user_clusters_path' do
+ subject { presenter.create_user_clusters_path }
+
+ it { is_expected.to eq(create_user_project_clusters_path(project)) }
+ end
+
+ describe '#create_gcp_clusters_path' do
+ subject { presenter.create_gcp_clusters_path }
+
+ it { is_expected.to eq(create_gcp_project_clusters_path(project)) }
+ end
+
+ describe '#cluster_status_cluster_path' do
+ subject { presenter.cluster_status_cluster_path(cluster) }
+
+ it { is_expected.to eq(cluster_status_project_cluster_path(project, cluster)) }
+ end
+
+ describe '#install_applications_cluster_path' do
+ let(:application) { :helm }
+
+ subject { presenter.install_applications_cluster_path(cluster, application) }
+
+ it { is_expected.to eq(install_applications_project_cluster_path(project, cluster, application)) }
+ end
+
+ describe '#cluster_path' do
+ subject { presenter.cluster_path(cluster) }
+
+ it { is_expected.to eq(project_cluster_path(project, cluster)) }
+ end
+end
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index 3959295c13e..274880f2c49 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -5,18 +5,43 @@ describe Clusters::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
- subject { described_class.new(user, params).execute(project: project, access_token: access_token) }
+ subject { described_class.new(user, params).execute(access_token: access_token) }
context 'when provider is gcp' do
context 'when project has no clusters' do
context 'when correct params' do
- include_context 'valid cluster create params'
+ 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
- include_context 'invalid cluster create params'
+ 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