Commit d0cff7f5 authored by Shinya Maeda's avatar Shinya Maeda

This works

parent e1d12ba9
...@@ -27,11 +27,17 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -27,11 +27,17 @@ class Projects::ClustersController < Projects::ApplicationController
end end
def new 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 end
def create def create
@cluster = Ci::CreateService @cluster = Clusters::CreateService
.new(project, current_user, create_params) .new(project, current_user, create_params)
.execute(token_in_session) .execute(token_in_session)
...@@ -58,7 +64,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -58,7 +64,7 @@ class Projects::ClustersController < Projects::ApplicationController
end end
def update def update
Ci::UpdateClusterService Clusters::UpdateService
.new(project, current_user, update_params) .new(project, current_user, update_params)
.execute(cluster) .execute(cluster)
...@@ -89,16 +95,16 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -89,16 +95,16 @@ class Projects::ClustersController < Projects::ApplicationController
def create_params def create_params
params.require(:cluster).permit( params.require(:cluster).permit(
:enabled, :enabled,
:name,
:platform_type, :platform_type,
:provider_type, :provider_type,
kubernetes_platform: [ platform_kubernetes_attributes: [
:namespace :namespace
], ],
gcp_provider: [ provider_gcp_attributes: [
:project_id, :gcp_project_id,
:cluster_zone, :zone,
:cluster_name, :num_nodes,
:cluster_size,
:machine_type :machine_type
]) ])
end end
...@@ -106,7 +112,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -106,7 +112,7 @@ class Projects::ClustersController < Projects::ApplicationController
def update_params def update_params
params.require(:cluster).permit( params.require(:cluster).permit(
:enabled, :enabled,
kubernetes_platform: [ platform_kubernetes_attributes: [
:namespace :namespace
]) ])
end end
......
...@@ -2,49 +2,46 @@ module Clusters ...@@ -2,49 +2,46 @@ module Clusters
class Cluster < ActiveRecord::Base class Cluster < ActiveRecord::Base
include Presentable include Presentable
self.table_name = 'clusters'
belongs_to :user belongs_to :user
belongs_to :service
enum :platform_type { enum platform_type: {
kubernetes: 1 kubernetes: 1
} }
enum :provider_type { enum provider_type: {
user: 0, user: 0,
gcp: 1 gcp: 1
} }
has_many :cluster_projects has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects has_many :projects, through: :cluster_projects, class_name: '::Project'
has_one :gcp_provider has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp'
has_one :kubernetes_platform has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes'
accepts_nested_attributes_for :gcp_provider accepts_nested_attributes_for :provider_gcp
accepts_nested_attributes_for :kubernetes_platform accepts_nested_attributes_for :platform_kubernetes
validates :kubernetes_platform, presence: true, if: :kubernetes? validates :name, cluster_name: true
validates :gcp_provider, presence: true, if: :gcp?
validate :restrict_modification, on: :update validate :restrict_modification, on: :update
delegate :status, to: :provider, allow_nil: true delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true delegate :status_reason, to: :provider, allow_nil: true
delegate :status_name, to: :provider, allow_nil: true
def restrict_modification delegate :on_creation?, to: :provider, allow_nil: true
if provider&.on_creation?
errors.add(:base, "cannot modify during creation")
return false
end
true
end
def provider def provider
return gcp_provider if gcp? return provider_gcp if gcp?
end end
def platform def platform
return kubernetes_platform if kubernetes? return platform_kubernetes if kubernetes?
end
def project
first_project
end end
def first_project def first_project
...@@ -52,5 +49,16 @@ module Clusters ...@@ -52,5 +49,16 @@ module Clusters
@first_project = projects.first @first_project = projects.first
end end
private
def restrict_modification
if provider&.on_creation?
errors.add(:base, "cannot modify during creation")
return false
end
true
end
end end
end end
module Clusters
class ClusterProject < ActiveRecord::Base
belongs_to :cluster
belongs_to :project
end
end
...@@ -4,11 +4,13 @@ module Clusters ...@@ -4,11 +4,13 @@ module Clusters
include Gitlab::Kubernetes include Gitlab::Kubernetes
include ReactiveCaching include ReactiveCaching
self.table_name = 'cluster_platforms_kubernetes'
TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze 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, attr_encrypted :password,
mode: :per_attribute_iv, mode: :per_attribute_iv,
...@@ -28,8 +30,8 @@ module Clusters ...@@ -28,8 +30,8 @@ module Clusters
message: Gitlab::Regex.kubernetes_namespace_regex_message message: Gitlab::Regex.kubernetes_namespace_regex_message
} }
validates :api_url, url: true, presence: true validates :api_url, url: true, presence: true, on: :update
validates :token, presence: true validates :token, presence: true, on: :update
after_save :clear_reactive_cache! after_save :clear_reactive_cache!
...@@ -53,9 +55,9 @@ module Clusters ...@@ -53,9 +55,9 @@ module Clusters
{ key: 'KUBECONFIG', value: config, public: false, file: true } { key: 'KUBECONFIG', value: config, public: false, file: true }
] ]
if ca_pem.present? if ca_cert.present?
variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true } variables << { key: 'KUBE_CA_PEM', value: ca_cert, public: true }
variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true } variables << { key: 'KUBE_CA_PEM_FILE', value: ca_cert, public: true, file: true }
end end
variables variables
...@@ -76,7 +78,7 @@ module Clusters ...@@ -76,7 +78,7 @@ module Clusters
# Caches resources in the namespace so other calls don't need to block on # Caches resources in the namespace so other calls don't need to block on
# network access # network access
def calculate_reactive_cache 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 # We may want to cache extra things in the future
{ pods: read_pods } { pods: read_pods }
...@@ -87,15 +89,16 @@ module Clusters ...@@ -87,15 +89,16 @@ module Clusters
url: api_url, url: api_url,
namespace: actual_namespace, namespace: actual_namespace,
token: token, token: token,
ca_pem: ca_pem) ca_pem: ca_cert)
end end
def namespace_placeholder def namespace_placeholder
default_namespace || TEMPLATE_PLACEHOLDER default_namespace || TEMPLATE_PLACEHOLDER
end end
def default_namespace def default_namespace(project = nil)
"#{cluster.first_project.path}-#{cluster.first_project.id}" if cluster.first_project project ||= cluster&.project
"#{project.path}-#{project.id}" if project
end end
def read_secrets def read_secrets
...@@ -120,9 +123,9 @@ module Clusters ...@@ -120,9 +123,9 @@ module Clusters
def kubeclient_ssl_options def kubeclient_ssl_options
opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER } 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] = 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 end
opts opts
...@@ -131,7 +134,11 @@ module Clusters ...@@ -131,7 +134,11 @@ module Clusters
private private
def build_kubeclient!(api_path: 'api', api_version: 'v1') 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( ::Kubeclient::Client.new(
join_api_url(api_path), join_api_url(api_path),
...@@ -143,7 +150,7 @@ module Clusters ...@@ -143,7 +150,7 @@ module Clusters
end end
def kubeclient_auth_options 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 return { bearer_token: token } if token
end end
...@@ -159,7 +166,7 @@ module Clusters ...@@ -159,7 +166,7 @@ module Clusters
def terminal_auth def terminal_auth
{ {
token: token, token: token,
ca_pem: ca_pem, ca_pem: ca_cert,
max_session_time: current_application_settings.terminal_max_session_time max_session_time: current_application_settings.terminal_max_session_time
} }
end end
......
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
module Clusters module Clusters
module Providers module Providers
class Gcp < ActiveRecord::Base class Gcp < ActiveRecord::Base
belongs_to :cluster self.table_name = 'cluster_providers_gcp'
default_value_for :cluster_zone, 'us-central1-a' belongs_to :cluster, inverse_of: :provider_gcp, class_name: 'Clusters::Cluster'
default_value_for :cluster_size, 3
default_value_for :zone, 'us-central1-a'
default_value_for :num_nodes, 3
default_value_for :machine_type, 'n1-standard-4' default_value_for :machine_type, 'n1-standard-4'
attr_encrypted :access_token, attr_encrypted :access_token,
...@@ -12,23 +14,16 @@ module Clusters ...@@ -12,23 +14,16 @@ module Clusters
key: Gitlab::Application.secrets.db_key_base, key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
validates :project_id, validates :gcp_project_id,
length: 1..63, length: 1..63,
format: { format: {
with: Gitlab::Regex.kubernetes_namespace_regex, with: Gitlab::Regex.kubernetes_namespace_regex,
message: Gitlab::Regex.kubernetes_namespace_regex_message message: Gitlab::Regex.kubernetes_namespace_regex_message
} }
validates :cluster_name, validates :zone, presence: true
length: 1..63,
format: {
with: Gitlab::Regex.kubernetes_namespace_regex,
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
validates :cluster_zone, presence: true
validates :cluster_size, validates :num_nodes,
presence: true, presence: true,
numericality: { numericality: {
only_integer: true, only_integer: true,
...@@ -54,9 +49,13 @@ module Clusters ...@@ -54,9 +49,13 @@ module Clusters
end end
before_transition any => [:errored, :created] do |provider| before_transition any => [:errored, :created] do |provider|
provider.token = nil provider.access_token = nil
provider.operation_id = 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 end
before_transition any => [:errored] do |provider, transition| before_transition any => [:errored] do |provider, transition|
......
...@@ -178,8 +178,8 @@ class Project < ActiveRecord::Base ...@@ -178,8 +178,8 @@ class Project < ActiveRecord::Base
has_one :project_feature, inverse_of: :project has_one :project_feature, inverse_of: :project
has_one :statistics, class_name: 'ProjectStatistics' has_one :statistics, class_name: 'ProjectStatistics'
has_many :cluster_projects, class_name: 'Clusters::ClusterProject' has_one :cluster_project, class_name: 'Clusters::Project'
has_one :cluster, through: :cluster_projects has_one :cluster, through: :cluster_project, class_name: 'Clusters::Cluster'
# Container repositories need to remove data from the container registry, # Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy # which is not managed by the DB. Hence we're still using dependent: :destroy
......
module Gcp module Clusters
class ClusterPolicy < BasePolicy class ClusterPolicy < BasePolicy
alias_method :cluster, :subject alias_method :cluster, :subject
delegate { @subject.project } delegate { cluster.project }
rule { can?(:master_access) }.policy do rule { can?(:master_access) }.policy do
enable :update_cluster enable :update_cluster
......
module Gcp module Clusters
class ClusterPresenter < Gitlab::View::Presenter::Delegated class ClusterPresenter < Gitlab::View::Presenter::Delegated
presents :cluster presents :cluster
def gke_cluster_url 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 end
end end
module Clusters module Clusters
class CreateService < BaseService class CreateService < BaseService
def execute(access_token) attr_reader :access_token
params['gcp_machine_type'] ||= GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE
cluster_params = def execute(access_token)
params.merge(user: current_user) @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? ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
end end
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
end end
...@@ -3,13 +3,13 @@ module Clusters ...@@ -3,13 +3,13 @@ module Clusters
class FetchOperationService class FetchOperationService
def execute(provider) def execute(provider)
operation = provider.api_client.projects_zones_operations( operation = provider.api_client.projects_zones_operations(
provider.project_id, provider.gcp_project_id,
provider.cluster_zone, provider.zone,
provider.operation_id) provider.operation_id)
yield(operation) if block_given? yield(operation) if block_given?
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e 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 end
end end
......
...@@ -7,15 +7,14 @@ module Clusters ...@@ -7,15 +7,14 @@ module Clusters
@provider = provider @provider = provider
configure_provider configure_provider
configure_kubernetes_platform configure_kubernetes
request_kuberenetes_platform_token
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
kubernetes_platform.update! kubernetes.save!
provider.make_created! provider.make_created!
end end
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e 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 rescue ActiveRecord::RecordInvalid => e
cluster.make_errored!("Failed to configure GKE Cluster: #{e.message}") cluster.make_errored!("Failed to configure GKE Cluster: #{e.message}")
end end
...@@ -26,23 +25,20 @@ module Clusters ...@@ -26,23 +25,20 @@ module Clusters
provider.endpoint = gke_cluster.endpoint provider.endpoint = gke_cluster.endpoint
end end
def configure_kubernetes_platform def configure_kubernetes
kubernetes_platform = cluster.kubernetes_platform kubernetes.api_url = 'https://' + gke_cluster.endpoint
kubernetes_platform.api_url = 'https://' + endpoint kubernetes.ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate)
kubernetes_platform.ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate) kubernetes.username = gke_cluster.master_auth.username
kubernetes_platform.username = gke_cluster.master_auth.username kubernetes.password = gke_cluster.master_auth.password
kubernetes_platform.password = gke_cluster.master_auth.password kubernetes.token = request_kuberenetes_token
end end
def request_kuberenetes_platform_token def request_kuberenetes_token
kubernetes_platform.read_secrets.each do |secret| kubernetes.read_secrets.each do |secret|
name = secret.dig('metadata', 'name') name = secret.dig('metadata', 'name')
if /default-token/ =~ name if /default-token/ =~ name
token_base64 = secret.dig('data', 'token') token_base64 = secret.dig('data', 'token')
if token_base64 return Base64.decode64(token_base64) if token_base64
kubernetes_platform.token = Base64.decode64(token_base64)
break
end
end end
end end
end end
...@@ -50,16 +46,16 @@ module Clusters ...@@ -50,16 +46,16 @@ module Clusters
def gke_cluster def gke_cluster
@gke_cluster ||= provider.api_client.projects_zones_clusters_get( @gke_cluster ||= provider.api_client.projects_zones_clusters_get(
provider.gcp_project_id, provider.gcp_project_id,
provider.gcp_cluster_zone, provider.zone,
provider.gcp_cluster_name) cluster.name)
end end
def cluster def cluster
provider.cluster @cluster ||= provider.cluster
end end
def kubernetes_platform def kubernetes
cluster.kubernetes_platform @kubernetes ||= cluster.platform_kubernetes
end end
end end
end end
......
...@@ -6,43 +6,41 @@ module Clusters ...@@ -6,43 +6,41 @@ module Clusters
def execute(provider) def execute(provider)
@provider = provider @provider = provider
unless operation.status == 'RUNNING' || operation.status == 'PENDING' get_operation_id do |operation_id|
return provider.make_errored!("Operation status is unexpected; #{operation.status_message}") 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
end
provider.operation_id = operation_id private
unless provider.operation_id def get_operation_id
return provider.make_errored!('Can not find operation_id from self_link') operation = provider.api_client.projects_zones_clusters_create(
end provider.gcp_project_id,
provider.zone,
provider.cluster.name,
provider.num_nodes,
machine_type: provider.machine_type)
if provider.make_creating unless operation.status == 'PENDING' || operation.status == 'RUNNING'
WaitForClusterCreationWorker.perform_in( return provider.make_errored!("Operation status is unexpected; #{operation.status_message}")
WaitForClusterCreationWorker::INITIAL_INTERVAL, provider.id)
else
return provider.make_errored!("Failed to update provider record; #{provider.errors}")
end 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 unless operation_id
api_client.parse_operation_id(operation.self_link) return provider.make_errored!('Can not find operation_id from self_link')
end end
def operation yield(operation_id)
@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
def api_client rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.api_client provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
end end
end end
end end
......
...@@ -12,7 +12,7 @@ module Clusters ...@@ -12,7 +12,7 @@ module Clusters
request_operation do |operation| request_operation do |operation|
case operation.status case operation.status
when 'RUNNING' when 'PENDING', 'RUNNING'
continue_creation(operation) continue_creation(operation)
when 'DONE' when 'DONE'
finalize_creation finalize_creation
...@@ -25,11 +25,15 @@ module Clusters ...@@ -25,11 +25,15 @@ module Clusters
private private
def continue_creation(operation) def continue_creation(operation)
if TIMEOUT < Time.now.utc - operation.start_time.to_time.utc if elapsed_time_from_creation(operation) < TIMEOUT
return provider.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}") WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id)
else
provider.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}")
end 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 end
def finalize_creation def finalize_creation
...@@ -37,7 +41,7 @@ module Clusters ...@@ -37,7 +41,7 @@ module Clusters
end end
def request_operation(&blk) def request_operation(&blk)
Clusters::FetchGcpOperationService.new.execute(provider, &blk) Clusters::Gcp::FetchOperationService.new.execute(provider, &blk)
end end
end end
end end
......
# 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
...@@ -4,34 +4,38 @@ ...@@ -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') - 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} = 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_errors(@cluster)
.form-group .form-group
= field.label :gcp_cluster_name, s_('ClusterIntegration|Cluster name') = field.label :name, s_('ClusterIntegration|Cluster name')
= field.text_field :gcp_cluster_name, class: 'form-control' = field.text_field :name, class: 'form-control'
.form-group = field.fields_for :provider_gcp, @cluster.provider_gcp do |provider_gcp_field|
= field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID') .form-group
= link_to(s_('ClusterIntegration|See your projects'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer') = provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID')
= field.text_field :gcp_project_id, class: 'form-control' = 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 .form-group
= field.label :gcp_cluster_zone, s_('ClusterIntegration|Zone') = 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') = 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' = provider_gcp_field.text_field :zone, class: 'form-control', placeholder: 'us-central1-a'
.form-group .form-group
= field.label :gcp_cluster_size, s_('ClusterIntegration|Number of nodes') = provider_gcp_field.label :num_nodes, s_('ClusterIntegration|Number of nodes')
= field.text_field :gcp_cluster_size, class: 'form-control', placeholder: '3' = provider_gcp_field.text_field :num_nodes, class: 'form-control', placeholder: '3'
.form-group .form-group
= field.label :gcp_machine_type, s_('ClusterIntegration|Machine type') = 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') = 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' = 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 .form-group
= field.label :project_namespace, s_('ClusterIntegration|Project namespace (optional, unique)') = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
= field.text_field :project_namespace, class: 'form-control', placeholder: @cluster.project_namespace_placeholder = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: @cluster.platform_kubernetes.default_namespace(@project)
.form-group .form-group
= field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save' = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save'
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
- else - else
= s_('ClusterIntegration|Cluster integration is disabled for this project.') = 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_errors(@cluster)
.form-group.append-bottom-20 .form-group.append-bottom-20
%label.append-bottom-10 %label.append-bottom-10
...@@ -62,9 +62,9 @@ ...@@ -62,9 +62,9 @@
%label.append-bottom-10{ for: 'cluter-name' } %label.append-bottom-10{ for: 'cluter-name' }
= s_('ClusterIntegration|Cluster name') = s_('ClusterIntegration|Cluster name')
.input-group .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 %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 %section.settings#js-cluster-advanced-settings
.settings-header .settings-header
......
...@@ -4,7 +4,7 @@ class ClusterProvisionWorker ...@@ -4,7 +4,7 @@ class ClusterProvisionWorker
def perform(cluster_id) def perform(cluster_id)
Clusters::Cluster.find_by_id(cluster_id).try do |cluster| 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) Clusters::Gcp::ProvisionService.new.execute(provider)
end end
end end
......
...@@ -4,7 +4,7 @@ class WaitForClusterCreationWorker ...@@ -4,7 +4,7 @@ class WaitForClusterCreationWorker
def perform(cluster_id) def perform(cluster_id)
Clusters::Cluster.find_by_id(cluster_id).try do |cluster| 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) Clusters::Gcp::VerifyProvisionStatusService.new.execute(provider)
end end
end end
......
class CreateGcpClusters < ActiveRecord::Migration class CreateNewClustersArchitectures < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
def change def change
...@@ -6,6 +6,7 @@ class CreateGcpClusters < ActiveRecord::Migration ...@@ -6,6 +6,7 @@ class CreateGcpClusters < ActiveRecord::Migration
t.references :user, foreign_key: { on_delete: :nullify } t.references :user, foreign_key: { on_delete: :nullify }
t.boolean :enabled, default: true 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 :provider_type, null: false
t.integer :platform_type, null: false t.integer :platform_type, null: false
...@@ -15,14 +16,14 @@ class CreateGcpClusters < ActiveRecord::Migration ...@@ -15,14 +16,14 @@ class CreateGcpClusters < ActiveRecord::Migration
end end
create_table :cluster_projects do |t| create_table :cluster_projects do |t|
t.references :project, 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: { unique: 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 :created_at, null: false
t.datetime_with_timezone :updated_at, null: false t.datetime_with_timezone :updated_at, null: false
end 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.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
t.string :api_url t.string :api_url
...@@ -41,16 +42,15 @@ class CreateGcpClusters < ActiveRecord::Migration ...@@ -41,16 +42,15 @@ class CreateGcpClusters < ActiveRecord::Migration
t.datetime_with_timezone :updated_at, null: false t.datetime_with_timezone :updated_at, null: false
end 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.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
t.integer :status t.integer :status
t.text :status_reason t.text :status_reason
t.string :project_id, null: false t.string :gcp_project_id, null: false
t.string :cluster_zone, null: false t.string :zone, null: false
t.string :cluster_name, null: false t.integer :num_nodes, null: false
t.integer :cluster_size, null: false
t.string :machine_type t.string :machine_type
t.string :operation_id t.string :operation_id
......
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
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -460,6 +460,60 @@ ActiveRecord::Schema.define(version: 20171012101043) do ...@@ -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 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| create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.string "name", null: false t.string "name", null: false
...@@ -1808,6 +1862,11 @@ ActiveRecord::Schema.define(version: 20171012101043) do ...@@ -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", "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_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 "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 "container_repositories", "projects"
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
......
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
FactoryGirl.define do # FactoryGirl.define do
factory :gcp_cluster, class: Gcp::Cluster do # factory :gcp_cluster, class: Gcp::Cluster do
project # project
user # user
enabled true # enabled true
gcp_project_id 'gcp-project-12345' # gcp_project_id 'gcp-project-12345'
gcp_cluster_name 'test-cluster' # gcp_cluster_name 'test-cluster'
gcp_cluster_zone 'us-central1-a' # gcp_cluster_zone 'us-central1-a'
gcp_cluster_size 1 # gcp_cluster_size 1
gcp_machine_type 'n1-standard-4' # gcp_machine_type 'n1-standard-4'
trait :with_kubernetes_service do # trait :with_kubernetes_service do
after(:create) do |cluster, evaluator| # after(:create) do |cluster, evaluator|
create(:kubernetes_service, project: cluster.project).tap do |service| # create(:kubernetes_service, project: cluster.project).tap do |service|
cluster.update(service: service) # cluster.update(service: service)
end # end
end # end
end # end
trait :custom_project_namespace do # trait :custom_project_namespace do
project_namespace 'sample-app' # project_namespace 'sample-app'
end # end
trait :created_on_gke do # trait :created_on_gke do
status_event :make_created # status_event :make_created
endpoint '111.111.111.111' # endpoint '111.111.111.111'
ca_cert 'xxxxxx' # ca_cert 'xxxxxx'
kubernetes_token 'xxxxxx' # kubernetes_token 'xxxxxx'
username 'xxxxxx' # username 'xxxxxx'
password 'xxxxxx' # password 'xxxxxx'
end # end
trait :errored do # trait :errored do
status_event :make_errored # status_event :make_errored
status_reason 'general error' # status_reason 'general error'
end # end
end # end
end # end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment