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 d4f34e32a48b3d583daf98fce3e948410bbd21fc..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..250f42f30968454ad96c489ef975d63e75d71a63 --- /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 0000000000000000000000000000000000000000..ef42f7c4074c619d7389ea501c954022555cca8d --- /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 0000000000000000000000000000000000000000..f6f2060ebb5aa83efcf35470627af82d86b8e9d5 --- /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 0000000000000000000000000000000000000000..f59440dbc598d334de74832307e52de896b5303f --- /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 88939b002b27042359fa72696f221aafb6a53b51..5624eb3aa45cb060c664909efb6912c1bfd873f9 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 a2bdcaefa9be81077e8913ec74e732dc65e4390d..e0677ce3fbc41d255c169630c7a76b1161f1cfe4 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 bcea96bce9422f734420fb54f611a70967d1e427..c7b6218d0073300f20681c99578609a73070037f 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 62adc66fb097b15213bc219b4f2f2f941d4c669d..feda6deeaa6c36fed3ee3debfcc0d7aa92175ec9 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 b40d6c41b711f7115a6acbd30a13be358504cc4f..0cce493b73e5babbd32793f87db2135011556112 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 19eb763e1de789a49a0bc693dccd9340cda0b3a0..916dcb1a3083ceaed1c9f6bda911586394da7f97 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 0000000000000000000000000000000000000000..cff0e74d6ea39899a2dcf5125d8c06ee0653ddcf --- /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 dfdd8e82f970d71c8de8c2dd153afccb804c20ee..78d632eb77c1b5ef7c85ec0bcdfbb36391628aa3 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 0000000000000000000000000000000000000000..12077b2e7352d4da44b7d407d46b3b18cfc849a7 --- /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 cd843b8ffa80d523f8d0b22880a2ec3dc79fd539..270db4a52fde67beb41eb033ba314a3c98cd341c 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 similarity index 68% rename from app/views/projects/clusters/_advanced_settings.html.haml rename to app/views/clusters/clusters/_advanced_settings.html.haml index 243e8cd9ba0c13f953cf8dbd9f2c3ae2d443764c..7037c80aa6bee8e77b4115c6ecc0dc1e039490aa 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 similarity index 100% rename from app/views/projects/clusters/_banner.html.haml rename to 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 similarity index 86% rename from app/views/projects/clusters/_cluster.html.haml rename to app/views/clusters/clusters/_cluster.html.haml index 2d7f7c6b1fb1822a1a14e43cda83e0d265bb7061..facbcb7fc592658faf69d83415091b88e0bb4067 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 similarity index 84% rename from app/views/projects/clusters/_empty_state.html.haml rename to app/views/clusters/clusters/_empty_state.html.haml index b8a3556a206683b20768c30322478759c7eabe01..800e76d92ef8002f524d806ca34c03ff0eea7d9f 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 similarity index 100% rename from app/views/projects/clusters/_gcp_signup_offer_banner.html.haml rename to 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 similarity index 89% rename from app/views/projects/clusters/_integration_form.html.haml rename to app/views/clusters/clusters/_integration_form.html.haml index d0a553e3414a11c1ba11889616b1d424ef279d2c..5e451f60c9d57ae771b07f83b8912c5d7913708f 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 similarity index 100% rename from app/views/projects/clusters/_sidebar.html.haml rename to 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 similarity index 94% rename from app/views/projects/clusters/gcp/_form.html.haml rename to app/views/clusters/clusters/gcp/_form.html.haml index 171ceeceb68bcc201062d395e048c9cb980e737b..ad842036a620e05335509c9e2275a7fb1550de0a 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 similarity index 100% rename from app/views/projects/clusters/gcp/_header.html.haml rename to 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 similarity index 96% rename from app/views/projects/clusters/gcp/_show.html.haml rename to app/views/clusters/clusters/gcp/_show.html.haml index 779c9c245c163d121250b1eb23b049ff0ae5de46..6021b220285e380732bed23be009d34d8230081d 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 similarity index 100% rename from app/views/projects/clusters/index.html.haml rename to 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 similarity index 91% rename from app/views/projects/clusters/new.html.haml rename to app/views/clusters/clusters/new.html.haml index a38003f57502d6ac63e45db0f6bd2aea147e9c0f..eeeef6bd824d08e7f41c435bdc8c329e6e04ac6c 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 similarity index 60% rename from app/views/projects/clusters/show.html.haml rename to app/views/clusters/clusters/show.html.haml index eddd3613c5f8e4c8a5ab01d9229fae6973e60ede..1e1157c34bd95f300eeea067ee16a1fe56fb7aef 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 similarity index 93% rename from app/views/projects/clusters/user/_form.html.haml rename to app/views/clusters/clusters/user/_form.html.haml index 54a6e685bb0ed1eb9a50f012f56c41e10fd82a34..4e6232b69de37d9043185b85a987718750299aec 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 similarity index 100% rename from app/views/projects/clusters/user/_header.html.haml rename to 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 similarity index 95% rename from app/views/projects/clusters/user/_show.html.haml rename to app/views/clusters/clusters/user/_show.html.haml index 5b57f7ceb7de6c912298a8477768b3b91f68c522..a871fef0240c6694cd74872aabb3184dd8bc7812 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 0000000000000000000000000000000000000000..1fe1d048de408f2a4317b5577c14a30adba10a1a --- /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 37c7f98ec98a292b152e11b27cf21a49ccc02d26..d2d91647d0b49229ac59ef0216d76f3b49e8da31 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 73c46f72168ca63e905b17ac5541fab0f02728bc..387d2363552422aff270fd1955fa6681b8b564ab 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 18c16ca6db704a52138415296224c0b62c05fd14..939f912ea85d77b4bb0f804dd32200f739ffec5c 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 f8e026b4405890ad2658536f63e7777ec4bc9a44..f3ab636ecc120d62edda914d5e8ff1f979e604c9 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 312b459ac892a2b55e951db1bbc23681c12cd85f..67a74af1cd2411172a927f87ad873da09617c8df 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 9e17e392d3dd82dd05a54cf0f73d301d2fb3b390..8106453a7752c3a2901b9d574f5445d6c2eea22d 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 9201332c5c8e048728c5faea6cbf23f4a70bf6ba..64fa787e46956a948a2085d022b2dafeea55083d 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 0000000000000000000000000000000000000000..4f4ae5e07c5b94fa93460d38a6a2af974d67c862 --- /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 e96dbfb73c0b9ef7373e8e1b525e43b65a9d0caa..7af181f37d540292e740eaf4c079cfd2f0496d3f 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 0000000000000000000000000000000000000000..c50d90ae1e82d192eebde5b5a043a81fd44f5bc2 --- /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 3959295c13edbe7122ac6f08cee444e5cd13e088..274880f2c49e643551dfca4c7ac6c3f96958cad9 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