diff options
author | Shinya Maeda <shinya@gitlab.com> | 2017-10-03 17:44:06 +0300 |
---|---|---|
committer | Shinya Maeda <shinya@gitlab.com> | 2017-10-03 17:44:06 +0300 |
commit | 6b7889f750c56962c0674467c3fbfd7976b9b44f (patch) | |
tree | 1491f42c332465194630417afc138010e23fc163 /app | |
parent | fd6776214abb2603ce2fb5dfb078229f0a0195e5 (diff) |
Implement Policy. Use show instead of edit. Chnage db column. fix comments. dry up workers
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/projects/clusters_controller.rb | 28 | ||||
-rw-r--r-- | app/models/gcp/cluster.rb | 9 | ||||
-rw-r--r-- | app/policies/gcp/cluster_policy.rb | 16 | ||||
-rw-r--r-- | app/policies/project_policy.rb | 3 | ||||
-rw-r--r-- | app/services/ci/create_cluster_service.rb | 8 | ||||
-rw-r--r-- | app/services/ci/create_gke_cluster_service.rb | 37 | ||||
-rw-r--r-- | app/services/ci/fetch_gcp_operation_service.rb | 17 | ||||
-rw-r--r-- | app/services/ci/finalize_cluster_creation_service.rb | 33 | ||||
-rw-r--r-- | app/views/projects/clusters/_form.html.haml | 18 | ||||
-rw-r--r-- | app/views/projects/clusters/show.html.haml (renamed from app/views/projects/clusters/edit.html.haml) | 4 | ||||
-rw-r--r-- | app/workers/cluster_creation_worker.rb | 40 | ||||
-rw-r--r-- | app/workers/wait_for_cluster_creation_worker.rb | 73 |
12 files changed, 158 insertions, 128 deletions
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index 2c53e034428..1b6165def51 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -1,7 +1,10 @@ class Projects::ClustersController < Projects::ApplicationController before_action :cluster, except: [:login, :index, :new, :create] - before_action :authorize_admin_cluster! - before_action :authorize_google_api, except: [:login] + before_action :authorize_read_cluster! + before_action :authorize_create_cluster!, only: [:new, :create] + before_action :authorize_google_api, only: [:new, :create] + before_action :authorize_update_cluster!, only: [:update] + before_action :authorize_admin_cluster!, only: [:destroy] def login begin @@ -16,7 +19,7 @@ class Projects::ClustersController < Projects::ApplicationController def index if project.cluster - redirect_to edit_project_cluster_path(project, project.cluster) + redirect_to project_cluster_path(project, project.cluster) else redirect_to new_project_cluster_path(project) end @@ -32,7 +35,6 @@ class Projects::ClustersController < Projects::ApplicationController .execute(token_in_session) if @cluster.persisted? - ClusterCreationWorker.perform_async(@cluster.id) redirect_to project_clusters_path(project) else render :new @@ -52,7 +54,7 @@ class Projects::ClustersController < Projects::ApplicationController end end - def edit + def show end def update @@ -60,14 +62,14 @@ class Projects::ClustersController < Projects::ApplicationController .new(project, current_user, cluster_params) .execute(cluster) - render :edit + render :show end def destroy if cluster.destroy redirect_to project_clusters_path(project), status: 302 else - render :edit + render :show end end @@ -79,8 +81,8 @@ class Projects::ClustersController < Projects::ApplicationController def cluster_params params.require(:cluster) - .permit(:gcp_project_id, :cluster_zone, :cluster_name, :cluster_size, - :machine_type, :project_namespace, :enabled) + .permit(:gcp_project_id, :gcp_cluster_zone, :gcp_cluster_name, :gcp_cluster_size, + :gcp_machine_type, :project_namespace, :enabled) end def authorize_google_api @@ -99,4 +101,12 @@ class Projects::ClustersController < Projects::ApplicationController @expires_at_in_session ||= session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] end + + def authorize_update_cluster! + return access_denied! unless can?(current_user, :update_cluster, cluster) + end + + def authorize_admin_cluster! + return access_denied! unless can?(current_user, :admin_cluster, cluster) + end end diff --git a/app/models/gcp/cluster.rb b/app/models/gcp/cluster.rb index 006cb1feb0c..8b39bedad55 100644 --- a/app/models/gcp/cluster.rb +++ b/app/models/gcp/cluster.rb @@ -8,19 +8,16 @@ module Gcp attr_encrypted :password, mode: :per_attribute_iv, - insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, algorithm: 'aes-256-cbc' attr_encrypted :kubernetes_token, mode: :per_attribute_iv, - insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, algorithm: 'aes-256-cbc' attr_encrypted :gcp_token, mode: :per_attribute_iv, - insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, algorithm: 'aes-256-cbc' @@ -33,9 +30,9 @@ module Gcp } validates :gcp_project_id, presence: true - validates :cluster_zone, presence: true - validates :cluster_name, presence: true - validates :cluster_size, presence: true, + validates :gcp_cluster_zone, presence: true + validates :gcp_cluster_name, presence: true + validates :gcp_cluster_size, presence: true, numericality: { only_integer: true, greater_than: 0 } validate :restrict_modification, on: :update diff --git a/app/policies/gcp/cluster_policy.rb b/app/policies/gcp/cluster_policy.rb new file mode 100644 index 00000000000..dd0a5bff146 --- /dev/null +++ b/app/policies/gcp/cluster_policy.rb @@ -0,0 +1,16 @@ +module Gcp + class ClusterPolicy < BasePolicy + alias_method :cluster, :subject + + delegate { @subject.project } + + condition(:safe_to_change) do + can?(:master_access) && !cluster.on_creation? + end + + rule { safe_to_change }.policy do + enable :update_cluster + enable :admin_cluster + end + end +end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 279b19eb576..b9894284226 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -164,6 +164,7 @@ class ProjectPolicy < BasePolicy enable :create_pipeline enable :update_pipeline enable :create_pipeline_schedule + enable :read_cluster enable :create_merge_request enable :create_wiki enable :push_code @@ -188,7 +189,7 @@ class ProjectPolicy < BasePolicy enable :admin_build enable :admin_container_image enable :admin_pipeline - enable :admin_cluster + enable :create_cluster enable :admin_environment enable :admin_deployment enable :admin_pages diff --git a/app/services/ci/create_cluster_service.rb b/app/services/ci/create_cluster_service.rb index 48d7c9aef23..2fb6df9d669 100644 --- a/app/services/ci/create_cluster_service.rb +++ b/app/services/ci/create_cluster_service.rb @@ -1,14 +1,14 @@ module Ci class CreateClusterService < BaseService def execute(access_token) - if params['machine_type'].blank? - params['machine_type'] = GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE - end + params['gcp_machine_type'] ||= GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE project.create_cluster( params.merge(user: current_user, status: Gcp::Cluster.statuses[:scheduled], - gcp_token: access_token)) + gcp_token: access_token)).tap do |cluster| + ClusterCreationWorker.perform_async(cluster.id) if cluster.persisted? + end end end end diff --git a/app/services/ci/create_gke_cluster_service.rb b/app/services/ci/create_gke_cluster_service.rb new file mode 100644 index 00000000000..2407de5fc75 --- /dev/null +++ b/app/services/ci/create_gke_cluster_service.rb @@ -0,0 +1,37 @@ +module Ci + class CreateGkeClusterService + def execute(cluster) + api_client = + GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil) + + begin + operation = api_client.projects_zones_clusters_create( + cluster.gcp_project_id, + cluster.gcp_cluster_zone, + cluster.gcp_cluster_name, + cluster.gcp_cluster_size, + machine_type: cluster.gcp_machine_type + ) + rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + return cluster.errored!("Failed to request to CloudPlatform; #{e.message}") + end + + unless operation.status == 'RUNNING' || operation.status == 'PENDING' + return cluster.errored!("Operation status is unexpected; #{operation.status_message}") + end + + operation_id = api_client.parse_operation_id(operation.self_link) + + unless operation_id + return cluster.errored!('Can not find operation_id from self_link') + end + + if cluster.creating!(operation_id) + WaitForClusterCreationWorker.perform_in( + WaitForClusterCreationWorker::INITIAL_INTERVAL, cluster.id) + else + return cluster.errored!("Failed to update cluster record; #{cluster.errors}") + end + end + end +end diff --git a/app/services/ci/fetch_gcp_operation_service.rb b/app/services/ci/fetch_gcp_operation_service.rb new file mode 100644 index 00000000000..24892d1ab47 --- /dev/null +++ b/app/services/ci/fetch_gcp_operation_service.rb @@ -0,0 +1,17 @@ +module Ci + class FetchGcpOperationService + def execute(cluster) + api_client = + GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil) + + operation = api_client.projects_zones_operations( + cluster.gcp_project_id, + cluster.gcp_cluster_zone, + cluster.gcp_operation_id) + + yield(operation) if block_given? + rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + return cluster.errored!("Failed to request to CloudPlatform; #{e.message}") + end + end +end diff --git a/app/services/ci/finalize_cluster_creation_service.rb b/app/services/ci/finalize_cluster_creation_service.rb new file mode 100644 index 00000000000..03cd761deb7 --- /dev/null +++ b/app/services/ci/finalize_cluster_creation_service.rb @@ -0,0 +1,33 @@ +module Ci + class FinalizeClusterCreationService + def execute(cluster) + api_client = + GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil) + + begin + gke_cluster = api_client.projects_zones_clusters_get( + cluster.gcp_project_id, + cluster.gcp_cluster_zone, + cluster.gcp_cluster_name) + rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + return cluster.errored!("Failed to request to CloudPlatform; #{e.message}") + end + + endpoint = gke_cluster.endpoint + api_url = 'https://' + endpoint + ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate) + username = gke_cluster.master_auth.username + password = gke_cluster.master_auth.password + + kubernetes_token = Ci::FetchKubernetesTokenService.new( + api_url, ca_cert, username, password).execute + + unless kubernetes_token + return cluster.errored!('Failed to get a default token of kubernetes') + end + + Ci::IntegrateClusterService.new.execute( + cluster, endpoint, ca_cert, kubernetes_token, username, password) + end + end +end diff --git a/app/views/projects/clusters/_form.html.haml b/app/views/projects/clusters/_form.html.haml index ebe4d9380eb..a59c48b2c68 100644 --- a/app/views/projects/clusters/_form.html.haml +++ b/app/views/projects/clusters/_form.html.haml @@ -7,8 +7,8 @@ = form_for [@project.namespace.becomes(Namespace), @project, @cluster] do |field| = form_errors(@cluster) .form-group - = field.label :cluster_name - = field.text_field :cluster_name, class: 'form-control' + = field.label :gcp_cluster_name + = field.text_field :gcp_cluster_name, class: 'form-control' .form-group = field.label :gcp_project_id @@ -16,25 +16,25 @@ = field.text_field :gcp_project_id, class: 'form-control' .form-group - = field.label :cluster_zone + = field.label :gcp_cluster_zone = link_to(s_('ClusterIntegration|See zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer') - = field.text_field :cluster_zone, class: 'form-control' + = field.text_field :gcp_cluster_zone, class: 'form-control' .form-group - = field.label :cluster_size - = field.text_field :cluster_size, class: 'form-control' + = field.label :gcp_cluster_size + = field.text_field :gcp_cluster_size, class: 'form-control' .form-group = field.label :project_namespace = field.text_field :project_namespace, class: 'form-control' .form-group - = field.label :machine_type + = field.label :gcp_machine_type = link_to(s_('ClusterIntegration|Machine type'), 'https://cloud.google.com/compute/docs/machine-types', target: '_blank', rel: 'noopener noreferrer') - = field.text_field :machine_type, class: 'form-control' + = field.text_field :gcp_machine_type, class: 'form-control' .form-group = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save' -# TODO: Remove before merge - = link_to "Create on Google Container Engine", namespace_project_clusters_path(@project.namespace, @project, cluster: {cluster_name: "gke-test-creation#{Random.rand(100)}", gcp_project_id: 'gitlab-internal-153318', cluster_zone: 'us-central1-a', cluster_size: '1', project_namespace: 'aaa', machine_type: 'n1-standard-1'}), method: :post + = link_to "Create on Google Container Engine", namespace_project_clusters_path(@project.namespace, @project, cluster: {gcp_cluster_name: "gke-test-creation#{Random.rand(100)}", gcp_project_id: 'gitlab-internal-153318', gcp_cluster_zone: 'us-central1-a', gcp_cluster_size: '1', project_namespace: 'aaa', gcp_machine_type: 'n1-standard-1'}), method: :post diff --git a/app/views/projects/clusters/edit.html.haml b/app/views/projects/clusters/show.html.haml index 0f6be36e56a..7d09c9e8934 100644 --- a/app/views/projects/clusters/edit.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -55,9 +55,9 @@ %label = s_('ClusterIntegration|Cluster name') .input-group - %input.form-control{ value: @cluster.cluster_name, disabled: true} + %input.form-control{ value: @cluster.gcp_cluster_name, disabled: true} %span.input-group-addon.clipboard-addon - = clipboard_button(text: @cluster.cluster_name, title: s_('ClusterIntegration|Copy cluster name')) + = clipboard_button(text: @cluster.gcp_cluster_name, title: s_('ClusterIntegration|Copy cluster name')) %br -# - if can?(current_user, :admin_cluster, @cluster) diff --git a/app/workers/cluster_creation_worker.rb b/app/workers/cluster_creation_worker.rb index 697cb73c2ac..220519134b9 100644 --- a/app/workers/cluster_creation_worker.rb +++ b/app/workers/cluster_creation_worker.rb @@ -3,44 +3,8 @@ class ClusterCreationWorker include DedicatedSidekiqQueue def perform(cluster_id) - cluster = Gcp::Cluster.find_by_id(cluster_id) - - unless cluster - return Rails.logger.error "Cluster object is not found; #{cluster_id}" - end - - api_client = - GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil) - - operation = api_client.projects_zones_clusters_create( - cluster.gcp_project_id, - cluster.cluster_zone, - cluster.cluster_name, - cluster.cluster_size, - machine_type: cluster.machine_type - ) - - if operation.is_a?(StandardError) - return cluster.errored!("Failed to request to CloudPlatform; #{operation.message}") - end - - unless operation.status == 'RUNNING' || operation.status == 'PENDING' - return cluster.errored!("Operation status is unexpected; #{operation.status_message}") - end - - operation_id = api_client.parse_operation_id(operation.self_link) - - unless operation_id - return cluster.errored!('Can not find operation_id from self_link') - end - - if cluster.creating!(operation_id) - WaitForClusterCreationWorker.perform_in( - WaitForClusterCreationWorker::INITIAL_INTERVAL, - cluster.id - ) - else - return cluster.errored!("Failed to update cluster record; #{cluster.errors}") + Gcp::Cluster.find_by_id(cluster_id).try do |cluster| + Ci::CreateGkeClusterService.new.execute(cluster) end end end diff --git a/app/workers/wait_for_cluster_creation_worker.rb b/app/workers/wait_for_cluster_creation_worker.rb index 0dd60de5150..d1f9b4e07f4 100644 --- a/app/workers/wait_for_cluster_creation_worker.rb +++ b/app/workers/wait_for_cluster_creation_worker.rb @@ -7,66 +7,21 @@ class WaitForClusterCreationWorker TIMEOUT = 20.minutes def perform(cluster_id) - cluster = Gcp::Cluster.find_by_id(cluster_id) - - unless cluster - return Rails.logger.error "Cluster object is not found; #{cluster_id}" - end - - api_client = - GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil) - - operation = api_client.projects_zones_operations( - cluster.gcp_project_id, - cluster.cluster_zone, - cluster.gcp_operation_id) - - if operation.is_a?(StandardError) - return cluster.errored!("Failed to request to CloudPlatform; #{operation.message}") - end - - case operation.status - when 'RUNNING' - if Time.now < operation.start_time.to_time + TIMEOUT - WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, cluster.id) - else - return cluster.errored!("Cluster creation time exceeds timeout; #{TIMEOUT}") + Gcp::Cluster.find_by_id(cluster_id).try do |cluster| + Ci::FetchGcpOperationService.new.execute(cluster) do |operation| + case operation.status + when 'RUNNING' + if TIMEOUT < Time.now - operation.start_time.to_time + return cluster.errored!("Cluster creation time exceeds timeout; #{TIMEOUT}") + end + + WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, cluster.id) + when 'DONE' + Ci::FinalizeClusterCreationService.new.execute(cluster) + else + return cluster.errored!("Unexpected operation status; #{operation.status} #{operation.status_message}") + end end - when 'DONE' - integrate(cluster, api_client) - else - return cluster.errored!("Unexpected operation status; #{operation.status} #{operation.status_message}") end end - - def integrate(cluster, api_client) - gke_cluster = api_client.projects_zones_clusters_get( - cluster.gcp_project_id, - cluster.cluster_zone, - cluster.cluster_name) - - if gke_cluster.is_a?(StandardError) - return cluster.errored!("Failed to request to CloudPlatform; #{gke_cluster.message}") - end - - begin - endpoint = gke_cluster.endpoint - api_url = 'https://' + endpoint - ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate) - username = gke_cluster.master_auth.username - password = gke_cluster.master_auth.password - rescue Exception => e - return cluster.errored!("Can not extract the expected data; #{e}") - end - - kubernetes_token = Ci::FetchKubernetesTokenService.new( - api_url, ca_cert, username, password).execute - - unless kubernetes_token - return cluster.errored!('Failed to get a default token of kubernetes') - end - - Ci::IntegrateClusterService.new.execute( - cluster, endpoint, ca_cert, kubernetes_token, username, password) - end end |