Merge branch 'top_level_clusters_controller' into 'master'
Top level clusters controller See merge request gitlab-org/gitlab-ce!22438
This commit is contained in:
		
						commit
						46fd31594e
					
				| 
						 | 
				
			
			@ -1,5 +0,0 @@
 | 
			
		|||
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
 | 
			
		||||
 | 
			
		||||
document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
  initGkeDropdowns();
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
  def clusterable
 | 
			
		||||
    @clusterable ||= ClusterablePresenter.fabricate(project, 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
 | 
			
		||||
  def project
 | 
			
		||||
    @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc)
 | 
			
		||||
  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)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def authorize_admin_cluster!
 | 
			
		||||
    access_denied! unless can?(current_user, :admin_cluster, cluster)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def update_applications_status
 | 
			
		||||
    @cluster.applications.each(&:schedule_status_update)
 | 
			
		||||
  def repository
 | 
			
		||||
    @repository ||= project.repository
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.")})
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			@ -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 *
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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|
 | 
			
		||||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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')
 | 
			
		||||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe Projects::Clusters::ApplicationsController do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe Projects::ClustersController do
 | 
			
		||||
| 
						 | 
				
			
			@ -318,12 +320,13 @@ 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,
 | 
			
		||||
      get :cluster_status,
 | 
			
		||||
        namespace_id: project.namespace.to_param,
 | 
			
		||||
        project_id: project.to_param,
 | 
			
		||||
        id: cluster,
 | 
			
		||||
        format: :json
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			@ -359,7 +362,8 @@ describe Projects::ClustersController do
 | 
			
		|||
    let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
 | 
			
		||||
 | 
			
		||||
    def go
 | 
			
		||||
      get :show, namespace_id: project.namespace,
 | 
			
		||||
      get :show,
 | 
			
		||||
        namespace_id: project.namespace,
 | 
			
		||||
        project_id: project,
 | 
			
		||||
        id: cluster
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			@ -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,7 +534,8 @@ describe Projects::ClustersController do
 | 
			
		|||
    let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
 | 
			
		||||
 | 
			
		||||
    def go
 | 
			
		||||
      delete :destroy, namespace_id: project.namespace,
 | 
			
		||||
      delete :destroy,
 | 
			
		||||
        namespace_id: project.namespace,
 | 
			
		||||
        project_id: project,
 | 
			
		||||
        id: cluster
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue