Commit d4e35913 authored by Shinya Maeda's avatar Shinya Maeda
Browse files

This works

parent 580af3249709
......@@ -27,7 +27,13 @@
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
......@@ -31,7 +37,7 @@
end
def create
@cluster = Ci::CreateService
@cluster = Clusters::CreateService
.new(project, current_user, create_params)
.execute(token_in_session)
......@@ -58,7 +64,7 @@
end
def update
Ci::UpdateClusterService
Clusters::UpdateService
.new(project, current_user, update_params)
.execute(cluster)
......@@ -89,5 +95,6 @@
def create_params
params.require(:cluster).permit(
:enabled,
:name,
:platform_type,
:provider_type,
......@@ -92,5 +99,5 @@
:platform_type,
:provider_type,
kubernetes_platform: [
platform_kubernetes_attributes: [
:namespace
],
......@@ -95,10 +102,9 @@
: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 @@
def update_params
params.require(:cluster).permit(
:enabled,
kubernetes_platform: [
platform_kubernetes_attributes: [
:namespace
])
end
......
......@@ -2,4 +2,6 @@
class Cluster < ActiveRecord::Base
include Presentable
self.table_name = 'clusters'
belongs_to :user
......@@ -5,3 +7,2 @@
belongs_to :user
belongs_to :service
......@@ -7,5 +8,5 @@
enum :platform_type {
enum platform_type: {
kubernetes: 1
}
......@@ -9,8 +10,8 @@
kubernetes: 1
}
enum :provider_type {
enum provider_type: {
user: 0,
gcp: 1
}
......@@ -13,10 +14,7 @@
user: 0,
gcp: 1
}
has_many :cluster_projects
has_many :projects, through: :cluster_projects
has_one :gcp_provider
has_one :kubernetes_platform
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
......@@ -22,4 +20,4 @@
accepts_nested_attributes_for :gcp_provider
accepts_nested_attributes_for :kubernetes_platform
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp'
has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes'
......@@ -25,7 +23,9 @@
validates :kubernetes_platform, presence: true, if: :kubernetes?
validates :gcp_provider, presence: true, if: :gcp?
accepts_nested_attributes_for :provider_gcp
accepts_nested_attributes_for :platform_kubernetes
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
......@@ -28,7 +28,29 @@
validate :restrict_modification, on: :update
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :status_name, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
def provider
return provider_gcp if gcp?
end
def platform
return platform_kubernetes if kubernetes?
end
def project
first_project
end
def first_project
return @first_project if defined?(@first_project)
@first_project = projects.first
end
private
def restrict_modification
if provider&.on_creation?
......@@ -38,19 +60,5 @@
true
end
def provider
return gcp_provider if gcp?
end
def platform
return kubernetes_platform if kubernetes?
end
def first_project
return @first_project if defined?(@first_project)
@first_project = projects.first
end
end
end
module Clusters
class ClusterProject < ActiveRecord::Base
belongs_to :cluster
belongs_to :project
end
end
......@@ -4,5 +4,7 @@
include Gitlab::Kubernetes
include ReactiveCaching
self.table_name = 'cluster_platforms_kubernetes'
TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze
......@@ -7,4 +9,4 @@
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] }
......@@ -10,5 +12,5 @@
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 @@
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 @@
{ 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 @@
# 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,10 +89,10 @@
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
......@@ -91,11 +93,12 @@
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,5 +123,5 @@
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
......@@ -124,5 +127,5 @@
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 @@
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 @@
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 @@
def terminal_auth
{
token: token,
ca_pem: ca_pem,
ca_pem: ca_cert,
max_session_time: current_application_settings.terminal_max_session_time
}
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 Providers
class Gcp < ActiveRecord::Base
belongs_to :cluster
self.table_name = 'cluster_providers_gcp'
......@@ -5,6 +5,8 @@
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,10 +14,10 @@
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
}
......@@ -16,13 +18,8 @@
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 :zone, presence: true
......@@ -28,7 +25,5 @@
validates :cluster_zone, presence: true
validates :cluster_size,
validates :num_nodes,
presence: true,
numericality: {
only_integer: true,
......@@ -54,5 +49,5 @@
end
before_transition any => [:errored, :created] do |provider|
provider.token = nil
provider.access_token = nil
provider.operation_id = nil
......@@ -58,5 +53,9 @@
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|
......
......@@ -178,8 +178,8 @@
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
......
module Clusters
class ClusterPolicy < BasePolicy
alias_method :cluster, :subject
delegate { cluster.project }
rule { can?(:master_access) }.policy do
enable :update_cluster
enable :admin_cluster
end
end
end
module Gcp
class ClusterPolicy < BasePolicy
alias_method :cluster, :subject
delegate { @subject.project }
rule { can?(:master_access) }.policy do
enable :update_cluster
enable :admin_cluster
end
end
end
module Clusters
class ClusterPresenter < Gitlab::View::Presenter::Delegated
presents :cluster
def gke_cluster_url
"https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp?
end
end
end
module Gcp
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}"
end
end
end
module Clusters
class CreateService < BaseService
def execute(access_token)
params['gcp_machine_type'] ||= GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE
attr_reader :access_token
......@@ -5,4 +4,4 @@
cluster_params =
params.merge(user: current_user)
def execute(access_token)
@access_token = access_token
......@@ -8,5 +7,5 @@
project.create_cluster(cluster_params).tap do |cluster|
create_cluster.tap do |cluster|
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
end
end
......@@ -10,5 +9,31 @@
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
......@@ -3,9 +3,9 @@
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
......@@ -8,8 +8,8 @@
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
......
......@@ -7,7 +7,6 @@
@provider = provider
configure_provider
configure_kubernetes_platform
request_kuberenetes_platform_token
configure_kubernetes
ActiveRecord::Base.transaction do
......@@ -12,6 +11,6 @@
ActiveRecord::Base.transaction do
kubernetes_platform.update!
kubernetes.save!
provider.make_created!
end
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
......@@ -15,7 +14,7 @@
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,11 +25,11 @@
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
......@@ -35,7 +34,7 @@
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')
......@@ -39,10 +38,7 @@
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,8 +46,8 @@
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
......@@ -55,6 +51,6 @@
end
def cluster
provider.cluster
@cluster ||= provider.cluster
end
......@@ -59,7 +55,7 @@
end
def kubernetes_platform
cluster.kubernetes_platform
def kubernetes
@kubernetes ||= cluster.platform_kubernetes
end
end
end
......
......@@ -6,12 +6,12 @@
def execute(provider)
@provider = provider
unless operation.status == 'RUNNING' || operation.status == 'PENDING'
return provider.make_errored!("Operation status is unexpected; #{operation.status_message}")
end