diff options
24 files changed, 422 insertions, 219 deletions
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index 0679d8c69f0..c07d955f148 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -27,11 +27,17 @@ class Projects::ClustersController < Projects::ApplicationController end def new - @cluster = project.build_cluster + @cluster = Clusters::Cluster.new( + platform_type: :kubernetes, + provider_type: :gcp).tap do |cluster| + cluster.build_provider_gcp + cluster.build_platform_kubernetes + cluster.projects << project + end end def create - @cluster = Ci::CreateService + @cluster = Clusters::CreateService .new(project, current_user, create_params) .execute(token_in_session) @@ -58,7 +64,7 @@ class Projects::ClustersController < Projects::ApplicationController end def update - Ci::UpdateClusterService + Clusters::UpdateService .new(project, current_user, update_params) .execute(cluster) @@ -89,16 +95,16 @@ class Projects::ClustersController < Projects::ApplicationController def create_params params.require(:cluster).permit( :enabled, + :name, :platform_type, :provider_type, - kubernetes_platform: [ + platform_kubernetes_attributes: [ :namespace ], - gcp_provider: [ - :project_id, - :cluster_zone, - :cluster_name, - :cluster_size, + provider_gcp_attributes: [ + :gcp_project_id, + :zone, + :num_nodes, :machine_type ]) end @@ -106,7 +112,7 @@ class Projects::ClustersController < Projects::ApplicationController def update_params params.require(:cluster).permit( :enabled, - kubernetes_platform: [ + platform_kubernetes_attributes: [ :namespace ]) end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index d7b13ac88f2..f1eedad8795 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -2,49 +2,46 @@ module Clusters class Cluster < ActiveRecord::Base include Presentable + self.table_name = 'clusters' + belongs_to :user - belongs_to :service - enum :platform_type { + enum platform_type: { kubernetes: 1 } - enum :provider_type { + enum provider_type: { user: 0, gcp: 1 } - has_many :cluster_projects - has_many :projects, through: :cluster_projects + has_many :cluster_projects, class_name: 'Clusters::Project' + has_many :projects, through: :cluster_projects, class_name: '::Project' - has_one :gcp_provider - has_one :kubernetes_platform + has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp' + has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes' - accepts_nested_attributes_for :gcp_provider - accepts_nested_attributes_for :kubernetes_platform + accepts_nested_attributes_for :provider_gcp + accepts_nested_attributes_for :platform_kubernetes - validates :kubernetes_platform, presence: true, if: :kubernetes? - validates :gcp_provider, presence: true, if: :gcp? + validates :name, cluster_name: true validate :restrict_modification, on: :update delegate :status, to: :provider, allow_nil: true delegate :status_reason, to: :provider, allow_nil: true - - def restrict_modification - if provider&.on_creation? - errors.add(:base, "cannot modify during creation") - return false - end - - true - end + delegate :status_name, to: :provider, allow_nil: true + delegate :on_creation?, to: :provider, allow_nil: true def provider - return gcp_provider if gcp? + return provider_gcp if gcp? end def platform - return kubernetes_platform if kubernetes? + return platform_kubernetes if kubernetes? + end + + def project + first_project end def first_project @@ -52,5 +49,16 @@ module Clusters @first_project = projects.first end + + private + + def restrict_modification + if provider&.on_creation? + errors.add(:base, "cannot modify during creation") + return false + end + + true + end end end diff --git a/app/models/clusters/cluster_project.rb b/app/models/clusters/cluster_project.rb deleted file mode 100644 index 7b139c2bb08..00000000000 --- a/app/models/clusters/cluster_project.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Clusters - class ClusterProject < ActiveRecord::Base - belongs_to :cluster - belongs_to :project - end -end diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index aed6f733487..d9f8927f7cc 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -4,11 +4,13 @@ module Clusters include Gitlab::Kubernetes include ReactiveCaching + self.table_name = 'cluster_platforms_kubernetes' + TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze - self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] } + self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.cluster_id] } - belongs_to :cluster + belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster' attr_encrypted :password, mode: :per_attribute_iv, @@ -28,8 +30,8 @@ module Clusters message: Gitlab::Regex.kubernetes_namespace_regex_message } - validates :api_url, url: true, presence: true - validates :token, presence: true + validates :api_url, url: true, presence: true, on: :update + validates :token, presence: true, on: :update after_save :clear_reactive_cache! @@ -53,9 +55,9 @@ module Clusters { key: 'KUBECONFIG', value: config, public: false, file: true } ] - if ca_pem.present? - variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true } - variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true } + if ca_cert.present? + variables << { key: 'KUBE_CA_PEM', value: ca_cert, public: true } + variables << { key: 'KUBE_CA_PEM_FILE', value: ca_cert, public: true, file: true } end variables @@ -76,7 +78,7 @@ module Clusters # Caches resources in the namespace so other calls don't need to block on # network access def calculate_reactive_cache - return unless active? && project && !project.pending_delete? + return unless active? && cluster.project && !cluster.project.pending_delete? # We may want to cache extra things in the future { pods: read_pods } @@ -87,15 +89,16 @@ module Clusters url: api_url, namespace: actual_namespace, token: token, - ca_pem: ca_pem) + ca_pem: ca_cert) end def namespace_placeholder default_namespace || TEMPLATE_PLACEHOLDER end - def default_namespace - "#{cluster.first_project.path}-#{cluster.first_project.id}" if cluster.first_project + def default_namespace(project = nil) + project ||= cluster&.project + "#{project.path}-#{project.id}" if project end def read_secrets @@ -120,9 +123,9 @@ module Clusters def kubeclient_ssl_options opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER } - if ca_pem.present? + if ca_cert.present? opts[:cert_store] = OpenSSL::X509::Store.new - opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem)) + opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_cert)) end opts @@ -131,7 +134,11 @@ module Clusters private def build_kubeclient!(api_path: 'api', api_version: 'v1') - raise "Incomplete settings" unless api_url && actual_namespace && token + raise "Incomplete settings" unless api_url && actual_namespace + + unless (username && password) || token + raise "Either username/password or token is required to access API" + end ::Kubeclient::Client.new( join_api_url(api_path), @@ -143,7 +150,7 @@ module Clusters end def kubeclient_auth_options - return { username: username, password: password } if username + return { username: username, password: password } if username && password return { bearer_token: token } if token end @@ -159,7 +166,7 @@ module Clusters def terminal_auth { token: token, - ca_pem: ca_pem, + ca_pem: ca_cert, max_session_time: current_application_settings.terminal_max_session_time } end diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb new file mode 100644 index 00000000000..69088100420 --- /dev/null +++ b/app/models/clusters/project.rb @@ -0,0 +1,8 @@ +module Clusters + class Project < ActiveRecord::Base + self.table_name = 'cluster_projects' + + belongs_to :cluster, inverse_of: :projects, class_name: 'Clusters::Cluster' + belongs_to :project, inverse_of: :project, class_name: 'Project' + end +end diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb index 5d4618cfe87..e4f109d2794 100644 --- a/app/models/clusters/providers/gcp.rb +++ b/app/models/clusters/providers/gcp.rb @@ -1,10 +1,12 @@ module Clusters module Providers class Gcp < ActiveRecord::Base - belongs_to :cluster + self.table_name = 'cluster_providers_gcp' - default_value_for :cluster_zone, 'us-central1-a' - default_value_for :cluster_size, 3 + belongs_to :cluster, inverse_of: :provider_gcp, class_name: 'Clusters::Cluster' + + default_value_for :zone, 'us-central1-a' + default_value_for :num_nodes, 3 default_value_for :machine_type, 'n1-standard-4' attr_encrypted :access_token, @@ -12,23 +14,16 @@ module Clusters key: Gitlab::Application.secrets.db_key_base, algorithm: 'aes-256-cbc' - validates :project_id, + validates :gcp_project_id, length: 1..63, format: { with: Gitlab::Regex.kubernetes_namespace_regex, message: Gitlab::Regex.kubernetes_namespace_regex_message } - validates :cluster_name, - length: 1..63, - format: { - with: Gitlab::Regex.kubernetes_namespace_regex, - message: Gitlab::Regex.kubernetes_namespace_regex_message - } - - validates :cluster_zone, presence: true + validates :zone, presence: true - validates :cluster_size, + validates :num_nodes, presence: true, numericality: { only_integer: true, @@ -54,9 +49,13 @@ module Clusters end before_transition any => [:errored, :created] do |provider| - provider.token = nil + provider.access_token = nil provider.operation_id = nil - provider.save! + end + + before_transition any => [:creating] do |provider, transition| + operation_id = transition.args.first + provider.operation_id = operation_id if operation_id end before_transition any => [:errored] do |provider, transition| diff --git a/app/models/project.rb b/app/models/project.rb index bc263b63881..70c75edcda3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -178,8 +178,8 @@ class Project < ActiveRecord::Base has_one :project_feature, inverse_of: :project has_one :statistics, class_name: 'ProjectStatistics' - has_many :cluster_projects, class_name: 'Clusters::ClusterProject' - has_one :cluster, through: :cluster_projects + has_one :cluster_project, class_name: 'Clusters::Project' + has_one :cluster, through: :cluster_project, class_name: 'Clusters::Cluster' # Container repositories need to remove data from the container registry, # which is not managed by the DB. Hence we're still using dependent: :destroy diff --git a/app/policies/gcp/cluster_policy.rb b/app/policies/clusters/cluster_policy.rb index e77173ea6e1..1f7c13072b9 100644 --- a/app/policies/gcp/cluster_policy.rb +++ b/app/policies/clusters/cluster_policy.rb @@ -1,8 +1,8 @@ -module Gcp +module Clusters class ClusterPolicy < BasePolicy alias_method :cluster, :subject - delegate { @subject.project } + delegate { cluster.project } rule { can?(:master_access) }.policy do enable :update_cluster diff --git a/app/presenters/gcp/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb index f7908f92a37..01cb59d0d44 100644 --- a/app/presenters/gcp/cluster_presenter.rb +++ b/app/presenters/clusters/cluster_presenter.rb @@ -1,9 +1,9 @@ -module Gcp +module Clusters class ClusterPresenter < Gitlab::View::Presenter::Delegated presents :cluster def gke_cluster_url - "https://console.cloud.google.com/kubernetes/clusters/details/#{gcp_cluster_zone}/#{gcp_cluster_name}" + "https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp? end end end diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 5429bc21256..3f458e25c14 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -1,14 +1,39 @@ module Clusters class CreateService < BaseService - def execute(access_token) - params['gcp_machine_type'] ||= GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE + attr_reader :access_token - cluster_params = - params.merge(user: current_user) + def execute(access_token) + @access_token = access_token - project.create_cluster(cluster_params).tap do |cluster| + create_cluster.tap do |cluster| ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted? end end + + private + + def create_cluster + cluster = nil + + ActiveRecord::Base.transaction do + cluster = Clusters::Cluster.create!(cluster_params) + cluster.projects << project + end + + cluster + rescue ActiveRecord::RecordInvalid => e + e.record + end + + def cluster_params + return @cluster_params if defined?(@cluster_params) + + params[:provider_gcp_attributes][:machine_type] ||= + GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE + + params[:provider_gcp_attributes][:access_token] ||= access_token + + @cluster_params = params.merge(user: current_user) + end end end diff --git a/app/services/clusters/gcp/fetch_operation_service.rb b/app/services/clusters/gcp/fetch_operation_service.rb index 013225efac4..a4cd3ca5c11 100644 --- a/app/services/clusters/gcp/fetch_operation_service.rb +++ b/app/services/clusters/gcp/fetch_operation_service.rb @@ -3,13 +3,13 @@ module Clusters class FetchOperationService def execute(provider) operation = provider.api_client.projects_zones_operations( - provider.project_id, - provider.cluster_zone, + provider.gcp_project_id, + provider.zone, provider.operation_id) yield(operation) if block_given? rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e - return provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") + provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") end end end diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index b536285b368..9f257bd0e9a 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -7,15 +7,14 @@ module Clusters @provider = provider configure_provider - configure_kubernetes_platform - request_kuberenetes_platform_token + configure_kubernetes ActiveRecord::Base.transaction do - kubernetes_platform.update! + kubernetes.save! provider.make_created! end rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e - return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}") + cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}") rescue ActiveRecord::RecordInvalid => e cluster.make_errored!("Failed to configure GKE Cluster: #{e.message}") end @@ -26,23 +25,20 @@ module Clusters provider.endpoint = gke_cluster.endpoint end - def configure_kubernetes_platform - kubernetes_platform = cluster.kubernetes_platform - kubernetes_platform.api_url = 'https://' + endpoint - kubernetes_platform.ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate) - kubernetes_platform.username = gke_cluster.master_auth.username - kubernetes_platform.password = gke_cluster.master_auth.password + def configure_kubernetes + kubernetes.api_url = 'https://' + gke_cluster.endpoint + kubernetes.ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate) + kubernetes.username = gke_cluster.master_auth.username + kubernetes.password = gke_cluster.master_auth.password + kubernetes.token = request_kuberenetes_token end - def request_kuberenetes_platform_token - kubernetes_platform.read_secrets.each do |secret| + def request_kuberenetes_token + kubernetes.read_secrets.each do |secret| name = secret.dig('metadata', 'name') if /default-token/ =~ name token_base64 = secret.dig('data', 'token') - if token_base64 - kubernetes_platform.token = Base64.decode64(token_base64) - break - end + return Base64.decode64(token_base64) if token_base64 end end end @@ -50,16 +46,16 @@ module Clusters def gke_cluster @gke_cluster ||= provider.api_client.projects_zones_clusters_get( provider.gcp_project_id, - provider.gcp_cluster_zone, - provider.gcp_cluster_name) + provider.zone, + cluster.name) end def cluster - provider.cluster + @cluster ||= provider.cluster end - def kubernetes_platform - cluster.kubernetes_platform + def kubernetes + @kubernetes ||= cluster.platform_kubernetes end end end diff --git a/app/services/clusters/gcp/provision_service.rb b/app/services/clusters/gcp/provision_service.rb index 269705000ac..8ec81e22203 100644 --- a/app/services/clusters/gcp/provision_service.rb +++ b/app/services/clusters/gcp/provision_service.rb @@ -6,43 +6,41 @@ module Clusters def execute(provider) @provider = provider - unless operation.status == 'RUNNING' || operation.status == 'PENDING' - return provider.make_errored!("Operation status is unexpected; #{operation.status_message}") + get_operation_id do |operation_id| + if provider.make_creating(operation_id) + WaitForClusterCreationWorker.perform_in( + Clusters::Gcp::VerifyProvisionStatusService::INITIAL_INTERVAL, + provider.id) + else + provider.make_errored!("Failed to update provider record; #{provider.errors}") + end end + end - provider.operation_id = operation_id + private - unless provider.operation_id - return provider.make_errored!('Can not find operation_id from self_link') - end + def get_operation_id + operation = provider.api_client.projects_zones_clusters_create( + provider.gcp_project_id, + provider.zone, + provider.cluster.name, + provider.num_nodes, + machine_type: provider.machine_type) - if provider.make_creating - WaitForClusterCreationWorker.perform_in( - WaitForClusterCreationWorker::INITIAL_INTERVAL, provider.id) - else - return provider.make_errored!("Failed to update provider record; #{provider.errors}") + unless operation.status == 'PENDING' || operation.status == 'RUNNING' + return provider.make_errored!("Operation status is unexpected; #{operation.status_message}") end - rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e - return provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") - end - private + operation_id = provider.api_client.parse_operation_id(operation.self_link) - def operation_id - api_client.parse_operation_id(operation.self_link) - end + unless operation_id + return provider.make_errored!('Can not find operation_id from self_link') + end - def operation - @operation ||= api_client.projects_zones_providers_create( - provider.project_id, - provider.provider_zone, - provider.provider_name, - provider.provider_size, - machine_type: provider.machine_type) - end + yield(operation_id) - def api_client - provider.api_client + rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") end end end diff --git a/app/services/clusters/gcp/verify_provision_status_service.rb b/app/services/clusters/gcp/verify_provision_status_service.rb index 466ea986516..bc33756f27c 100644 --- a/app/services/clusters/gcp/verify_provision_status_service.rb +++ b/app/services/clusters/gcp/verify_provision_status_service.rb @@ -12,7 +12,7 @@ module Clusters request_operation do |operation| case operation.status - when 'RUNNING' + when 'PENDING', 'RUNNING' continue_creation(operation) when 'DONE' finalize_creation @@ -25,11 +25,15 @@ module Clusters private def continue_creation(operation) - if TIMEOUT < Time.now.utc - operation.start_time.to_time.utc - return provider.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}") + if elapsed_time_from_creation(operation) < TIMEOUT + WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id) + else + provider.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}") end + end - WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id) + def elapsed_time_from_creation(operation) + Time.now.utc - operation.start_time.to_time.utc end def finalize_creation @@ -37,7 +41,7 @@ module Clusters end def request_operation(&blk) - Clusters::FetchGcpOperationService.new.execute(provider, &blk) + Clusters::Gcp::FetchOperationService.new.execute(provider, &blk) end end end diff --git a/app/validators/cluster_name_validator.rb b/app/validators/cluster_name_validator.rb new file mode 100644 index 00000000000..6c9850af30f --- /dev/null +++ b/app/validators/cluster_name_validator.rb @@ -0,0 +1,24 @@ +# ClusterNameValidator +# +# Custom validator for ClusterName. +class ClusterNameValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if record.user? + unless value.present? + record.errors.add(attribute, " has to be present") + end + elsif record.gcp? + if record.persisted? && record.name != value + record.errors.add(attribute, " can not be changed because it's synchronized with provider") + end + + unless value.length >= 1 && value.length <= 63 + record.errors.add(attribute, " is invalid syntax") + end + + unless value =~ Gitlab::Regex.kubernetes_namespace_regex + record.errors.add(attribute, Gitlab::Regex.kubernetes_namespace_regex_message) + end + end + end +end diff --git a/app/views/projects/clusters/_form.html.haml b/app/views/projects/clusters/_form.html.haml index 371cdb1e403..b3020513abf 100644 --- a/app/views/projects/clusters/_form.html.haml +++ b/app/views/projects/clusters/_form.html.haml @@ -4,34 +4,38 @@ - link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') = s_('ClusterIntegration|Read our %{link_to_help_page} on cluster integration.').html_safe % { link_to_help_page: link_to_help_page} - = form_for [@project.namespace.becomes(Namespace), @project, @cluster] do |field| + = form_for @cluster, url: namespace_project_clusters_path(@project.namespace, @project, @cluster), as: :cluster do |field| + = field.hidden_field :platform_type, :value => 'kubernetes' + = field.hidden_field :provider_type, :value => 'gcp' = form_errors(@cluster) .form-group - = field.label :gcp_cluster_name, s_('ClusterIntegration|Cluster name') - = field.text_field :gcp_cluster_name, class: 'form-control' + = field.label :name, s_('ClusterIntegration|Cluster name') + = field.text_field :name, class: 'form-control' - .form-group - = field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID') - = link_to(s_('ClusterIntegration|See your projects'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer') - = field.text_field :gcp_project_id, class: 'form-control' + = field.fields_for :provider_gcp, @cluster.provider_gcp do |provider_gcp_field| + .form-group + = provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID') + = link_to(s_('ClusterIntegration|See your projects'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer') + = provider_gcp_field.text_field :gcp_project_id, class: 'form-control' - .form-group - = field.label :gcp_cluster_zone, s_('ClusterIntegration|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 :gcp_cluster_zone, class: 'form-control', placeholder: 'us-central1-a' + .form-group + = provider_gcp_field.label :zone, s_('ClusterIntegration|Zone') + = link_to(s_('ClusterIntegration|See zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer') + = provider_gcp_field.text_field :zone, class: 'form-control', placeholder: 'us-central1-a' - .form-group - = field.label :gcp_cluster_size, s_('ClusterIntegration|Number of nodes') - = field.text_field :gcp_cluster_size, class: 'form-control', placeholder: '3' + .form-group + = provider_gcp_field.label :num_nodes, s_('ClusterIntegration|Number of nodes') + = provider_gcp_field.text_field :num_nodes, class: 'form-control', placeholder: '3' - .form-group - = field.label :gcp_machine_type, s_('ClusterIntegration|Machine type') - = link_to(s_('ClusterIntegration|See machine types'), 'https://cloud.google.com/compute/docs/machine-types', target: '_blank', rel: 'noopener noreferrer') - = field.text_field :gcp_machine_type, class: 'form-control', placeholder: 'n1-standard-4' + .form-group + = provider_gcp_field.label :machine_type, s_('ClusterIntegration|Machine type') + = link_to(s_('ClusterIntegration|See machine types'), 'https://cloud.google.com/compute/docs/machine-types', target: '_blank', rel: 'noopener noreferrer') + = provider_gcp_field.text_field :machine_type, class: 'form-control', placeholder: 'n1-standard-4' + = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| .form-group - = field.label :project_namespace, s_('ClusterIntegration|Project namespace (optional, unique)') - = field.text_field :project_namespace, class: 'form-control', placeholder: @cluster.project_namespace_placeholder + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: @cluster.platform_kubernetes.default_namespace(@project) .form-group = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save' diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml index ff76abc3553..49adae82454 100644 --- a/app/views/projects/clusters/show.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -33,7 +33,7 @@ - else = s_('ClusterIntegration|Cluster integration is disabled for this project.') - = form_for [@project.namespace.becomes(Namespace), @project, @cluster] do |field| + = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_errors(@cluster) .form-group.append-bottom-20 %label.append-bottom-10 @@ -62,9 +62,9 @@ %label.append-bottom-10{ for: 'cluter-name' } = s_('ClusterIntegration|Cluster name') .input-group - %input.form-control.cluster-name{ value: @cluster.gcp_cluster_name, disabled: true } + %input.form-control.cluster-name{ value: @cluster.name, disabled: true } %span.input-group-addon.clipboard-addon - = clipboard_button(text: @cluster.gcp_cluster_name, title: s_('ClusterIntegration|Copy cluster name')) + = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy cluster name')) %section.settings#js-cluster-advanced-settings .settings-header diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb index 79f0d73c396..0929fffc444 100644 --- a/app/workers/cluster_provision_worker.rb +++ b/app/workers/cluster_provision_worker.rb @@ -4,7 +4,7 @@ class ClusterProvisionWorker def perform(cluster_id) Clusters::Cluster.find_by_id(cluster_id).try do |cluster| - cluster.gcp_provider.try do |provider| + cluster.provider_gcp.try do |provider| Clusters::Gcp::ProvisionService.new.execute(provider) end end diff --git a/app/workers/wait_for_cluster_creation_worker.rb b/app/workers/wait_for_cluster_creation_worker.rb index d8c42c6bd55..b2f04869636 100644 --- a/app/workers/wait_for_cluster_creation_worker.rb +++ b/app/workers/wait_for_cluster_creation_worker.rb @@ -4,7 +4,7 @@ class WaitForClusterCreationWorker def perform(cluster_id) Clusters::Cluster.find_by_id(cluster_id).try do |cluster| - cluster.gcp_provider.try do |provider| + cluster.provider_gcp.try do |provider| Clusters::Gcp::VerifyProvisionStatusService.new.execute(provider) end end diff --git a/db/migrate/20171013094327_create_clusters.rb b/db/migrate/20171013094327_create_new_clusters_architectures.rb index ad30181f984..35df8cb4a60 100644 --- a/db/migrate/20171013094327_create_clusters.rb +++ b/db/migrate/20171013094327_create_new_clusters_architectures.rb @@ -1,4 +1,4 @@ -class CreateGcpClusters < ActiveRecord::Migration +class CreateNewClustersArchitectures < ActiveRecord::Migration DOWNTIME = false def change @@ -6,6 +6,7 @@ class CreateGcpClusters < ActiveRecord::Migration t.references :user, foreign_key: { on_delete: :nullify } t.boolean :enabled, default: true + t.string :name, null: false # If manual, read-write. If gcp, read-only. t.integer :provider_type, null: false t.integer :platform_type, null: false @@ -15,14 +16,14 @@ class CreateGcpClusters < ActiveRecord::Migration end create_table :cluster_projects do |t| - t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade } - t.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade } + t.references :project, null: false, index: true, foreign_key: { on_delete: :cascade } + t.references :cluster, null: false, index: true, foreign_key: { on_delete: :cascade } t.datetime_with_timezone :created_at, null: false t.datetime_with_timezone :updated_at, null: false end - create_table :cluster_kubernetes_platforms do |t| + create_table :cluster_platforms_kubernetes do |t| t.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade } t.string :api_url @@ -41,16 +42,15 @@ class CreateGcpClusters < ActiveRecord::Migration t.datetime_with_timezone :updated_at, null: false end - create_table :cluster_gcp_providers do |t| + create_table :cluster_providers_gcp do |t| t.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade } t.integer :status t.text :status_reason - t.string :project_id, null: false - t.string :cluster_zone, null: false - t.string :cluster_name, null: false - t.integer :cluster_size, null: false + t.string :gcp_project_id, null: false + t.string :zone, null: false + t.integer :num_nodes, null: false t.string :machine_type t.string :operation_id diff --git a/db/migrate/20171013104327_migrate_gcp_clusters_to_new_clusters_architectures.rb.rb b/db/migrate/20171013104327_migrate_gcp_clusters_to_new_clusters_architectures.rb.rb new file mode 100644 index 00000000000..5510b036d24 --- /dev/null +++ b/db/migrate/20171013104327_migrate_gcp_clusters_to_new_clusters_architectures.rb.rb @@ -0,0 +1,84 @@ +class MigrateGcpClustersToNewClustersArchitectures < ActiveRecord::Migration + DOWNTIME = false + + def up + # TODO: Chnage to something reaistic + ActiveRecord::Base.connection.select_rows('SELECT * from gcp_clusters;').each do |old_cluster| + id = old_cluster[0] + project_id = old_cluster[1] + user_id = old_cluster[2] + service_id = old_cluster[3] + status = old_cluster[4] + gcp_cluster_size = old_cluster[5] + created_at = old_cluster[6] + updated_at = old_cluster[7] + enabled = old_cluster[8] + status_reason = old_cluster[9] + project_namespace = old_cluster[10] + endpoint = old_cluster[11] + ca_cert = old_cluster[12] + encrypted_kubernetes_token = old_cluster[13] + encrypted_kubernetes_token_iv = old_cluster[14] + username = old_cluster[15] + encrypted_password = old_cluster[16] + encrypted_password_iv = old_cluster[17] + gcp_project_id = old_cluster[18] + gcp_cluster_zone = old_cluster[19] + gcp_cluster_name = old_cluster[20] + gcp_machine_type = old_cluster[21] + gcp_operation_id = old_cluster[22] + encrypted_gcp_token = old_cluster[23] + encrypted_gcp_token_iv = old_cluster[24] + + cluster = Clusters::Cluster.create!( + user_id: user_id, + enabled: enabled, + name: gcp_cluster_name, + provider_type: :gcp, + platform_type: :kubernetes, + created_at: created_at, + updated_at: updated_at) + + Clusters::Project.create!( + cluster: cluster, + project_id: project_id, + created_at: created_at, + updated_at: updated_at) + + Clusters::Platforms::Kubernetes.create!( + cluster: cluster, + api_url: 'https://' + endpoint, + ca_cert: ca_cert, + namespace: project_namespace, + username: username, + encrypted_password: encrypted_password, + encrypted_password_iv: encrypted_password_iv, + encrypted_token: encrypted_kubernetes_token, + encrypted_token_iv: encrypted_kubernetes_token_iv, + created_at: created_at, + updated_at: updated_at) + + Clusters::Providers::Gcp.create!( + cluster: cluster, + status: status, + status_reason: status_reason, + gcp_project_id: gcp_project_id, + zone: gcp_cluster_zone, + num_nodes: gcp_cluster_size, + machine_type: gcp_machine_type, + operation_id: gcp_operation_id, + endpoint: endpoint, + encrypted_access_token: encrypted_gcp_token, + encrypted_access_token_iv: encrypted_gcp_token_iv, + created_at: created_at, + updated_at: updated_at) + end + end + + def down + Clusters::Cluster.delete_all + Clusters::Project.delete_all + Clusters::Providers::Gcp.delete_all + Clusters::Platforms::Kubernetes.delete_all + end +end diff --git a/db/schema.rb b/db/schema.rb index c2c04873d4d..65e8a3120a1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171012101043) do +ActiveRecord::Schema.define(version: 20171013104327) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -460,6 +460,60 @@ ActiveRecord::Schema.define(version: 20171012101043) do add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree + create_table "cluster_platforms_kubernetes", force: :cascade do |t| + t.integer "cluster_id", null: false + t.string "api_url" + t.text "ca_cert" + t.string "namespace" + t.string "username" + t.text "encrypted_password" + t.string "encrypted_password_iv" + t.text "encrypted_token" + t.string "encrypted_token_iv" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "cluster_platforms_kubernetes", ["cluster_id"], name: "index_cluster_platforms_kubernetes_on_cluster_id", unique: true, using: :btree + + create_table "cluster_projects", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "cluster_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "cluster_projects", ["cluster_id"], name: "index_cluster_projects_on_cluster_id", using: :btree + add_index "cluster_projects", ["project_id"], name: "index_cluster_projects_on_project_id", using: :btree + + create_table "cluster_providers_gcp", force: :cascade do |t| + t.integer "cluster_id", null: false + t.integer "status" + t.text "status_reason" + t.string "gcp_project_id", null: false + t.string "zone", null: false + t.integer "num_nodes", null: false + t.string "machine_type" + t.string "operation_id" + t.string "endpoint" + t.text "encrypted_access_token" + t.string "encrypted_access_token_iv" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "cluster_providers_gcp", ["cluster_id"], name: "index_cluster_providers_gcp_on_cluster_id", unique: true, using: :btree + + create_table "clusters", force: :cascade do |t| + t.integer "user_id" + t.boolean "enabled", default: true + t.string "name", null: false + t.integer "provider_type", null: false + t.integer "platform_type", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "container_repositories", force: :cascade do |t| t.integer "project_id", null: false t.string "name", null: false @@ -1808,6 +1862,11 @@ ActiveRecord::Schema.define(version: 20171012101043) do add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade + add_foreign_key "cluster_platforms_kubernetes", "clusters", on_delete: :cascade + add_foreign_key "cluster_projects", "clusters", on_delete: :cascade + add_foreign_key "cluster_projects", "projects", on_delete: :cascade + add_foreign_key "cluster_providers_gcp", "clusters", on_delete: :cascade + add_foreign_key "clusters", "users", on_delete: :nullify add_foreign_key "container_repositories", "projects" add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade diff --git a/lib/gitlab/gcp/model.rb b/lib/gitlab/gcp/model.rb deleted file mode 100644 index 195391f0e3c..00000000000 --- a/lib/gitlab/gcp/model.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Gitlab - module Gcp - module Model - def table_name_prefix - "gcp_" - end - - def model_name - @model_name ||= ActiveModel::Name.new(self, nil, self.name.split("::").last) - end - end - end -end diff --git a/spec/factories/gcp/cluster.rb b/spec/factories/gcp/cluster.rb index 630e40da888..5c062737ffc 100644 --- a/spec/factories/gcp/cluster.rb +++ b/spec/factories/gcp/cluster.rb @@ -1,38 +1,38 @@ -FactoryGirl.define do - factory :gcp_cluster, class: Gcp::Cluster do - project - user - enabled true - gcp_project_id 'gcp-project-12345' - gcp_cluster_name 'test-cluster' - gcp_cluster_zone 'us-central1-a' - gcp_cluster_size 1 - gcp_machine_type 'n1-standard-4' +# FactoryGirl.define do +# factory :gcp_cluster, class: Gcp::Cluster do +# project +# user +# enabled true +# gcp_project_id 'gcp-project-12345' +# gcp_cluster_name 'test-cluster' +# gcp_cluster_zone 'us-central1-a' +# gcp_cluster_size 1 +# gcp_machine_type 'n1-standard-4' - trait :with_kubernetes_service do - after(:create) do |cluster, evaluator| - create(:kubernetes_service, project: cluster.project).tap do |service| - cluster.update(service: service) - end - end - end +# trait :with_kubernetes_service do +# after(:create) do |cluster, evaluator| +# create(:kubernetes_service, project: cluster.project).tap do |service| +# cluster.update(service: service) +# end +# end +# end - trait :custom_project_namespace do - project_namespace 'sample-app' - end +# trait :custom_project_namespace do +# project_namespace 'sample-app' +# end - trait :created_on_gke do - status_event :make_created - endpoint '111.111.111.111' - ca_cert 'xxxxxx' - kubernetes_token 'xxxxxx' - username 'xxxxxx' - password 'xxxxxx' - end +# trait :created_on_gke do +# status_event :make_created +# endpoint '111.111.111.111' +# ca_cert 'xxxxxx' +# kubernetes_token 'xxxxxx' +# username 'xxxxxx' +# password 'xxxxxx' +# end - trait :errored do - status_event :make_errored - status_reason 'general error' - end - end -end +# trait :errored do +# status_event :make_errored +# status_reason 'general error' +# end +# end +# end |