Merge branch 'refactor-clusters' into 'master'
Refactor Clusters to be consisted from GcpProvider and KubernetesPlatform See merge request gitlab-org/gitlab-ce!14879
This commit is contained in:
commit
c71cf908cd
|
|
@ -27,11 +27,13 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@cluster = project.build_cluster
|
||||
@cluster = Clusters::Cluster.new.tap do |cluster|
|
||||
cluster.build_provider_gcp
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@cluster = Ci::CreateClusterService
|
||||
@cluster = Clusters::CreateService
|
||||
.new(project, current_user, create_params)
|
||||
.execute(token_in_session)
|
||||
|
||||
|
|
@ -58,7 +60,7 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
Ci::UpdateClusterService
|
||||
Clusters::UpdateService
|
||||
.new(project, current_user, update_params)
|
||||
.execute(cluster)
|
||||
|
||||
|
|
@ -88,19 +90,19 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
|
||||
def create_params
|
||||
params.require(:cluster).permit(
|
||||
:gcp_project_id,
|
||||
:gcp_cluster_zone,
|
||||
:gcp_cluster_name,
|
||||
:gcp_cluster_size,
|
||||
:gcp_machine_type,
|
||||
:project_namespace,
|
||||
:enabled)
|
||||
:enabled,
|
||||
:name,
|
||||
:provider_type,
|
||||
provider_gcp_attributes: [
|
||||
:gcp_project_id,
|
||||
:zone,
|
||||
:num_nodes,
|
||||
:machine_type
|
||||
])
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:cluster).permit(
|
||||
:project_namespace,
|
||||
:enabled)
|
||||
params.require(:cluster).permit(:enabled)
|
||||
end
|
||||
|
||||
def authorize_google_api
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
module Clusters
|
||||
class Cluster < ActiveRecord::Base
|
||||
include Presentable
|
||||
|
||||
self.table_name = 'clusters'
|
||||
|
||||
belongs_to :user
|
||||
|
||||
has_many :cluster_projects, class_name: 'Clusters::Project'
|
||||
has_many :projects, through: :cluster_projects, class_name: '::Project'
|
||||
|
||||
# we force autosave to happen when we save `Cluster` model
|
||||
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
|
||||
|
||||
# We have to ":destroy" it today to ensure that we clean also the Kubernetes Integration
|
||||
has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
accepts_nested_attributes_for :provider_gcp, update_only: true
|
||||
accepts_nested_attributes_for :platform_kubernetes, update_only: true
|
||||
|
||||
validates :name, cluster_name: true
|
||||
validate :restrict_modification, on: :update
|
||||
|
||||
# TODO: Move back this into Clusters::Platforms::Kubernetes in 10.3
|
||||
# We need callback here because `enabled` belongs to Clusters::Cluster
|
||||
# Callbacks in Clusters::Platforms::Kubernetes will not be called after update
|
||||
after_save :update_kubernetes_integration!
|
||||
|
||||
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
|
||||
delegate :update_kubernetes_integration!, to: :platform, allow_nil: true
|
||||
|
||||
enum platform_type: {
|
||||
kubernetes: 1
|
||||
}
|
||||
|
||||
enum provider_type: {
|
||||
user: 0,
|
||||
gcp: 1
|
||||
}
|
||||
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
scope :disabled, -> { where(enabled: false) }
|
||||
|
||||
def provider
|
||||
return provider_gcp if gcp?
|
||||
end
|
||||
|
||||
def platform
|
||||
return platform_kubernetes if kubernetes?
|
||||
end
|
||||
|
||||
def first_project
|
||||
return @first_project if defined?(@first_project)
|
||||
|
||||
@first_project = projects.first
|
||||
end
|
||||
alias_method :project, :first_project
|
||||
|
||||
private
|
||||
|
||||
def restrict_modification
|
||||
if provider&.on_creation?
|
||||
errors.add(:base, "cannot modify during creation")
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
module Clusters
|
||||
module Platforms
|
||||
class Kubernetes < ActiveRecord::Base
|
||||
self.table_name = 'cluster_platforms_kubernetes'
|
||||
|
||||
belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster'
|
||||
|
||||
attr_encrypted :password,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
attr_encrypted :token,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
before_validation :enforce_namespace_to_lower_case
|
||||
|
||||
validates :namespace,
|
||||
allow_blank: true,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
|
||||
validates :api_url, url: true, presence: true
|
||||
validates :token, presence: true
|
||||
|
||||
# TODO: Glue code till we migrate Kubernetes Integration into Platforms::Kubernetes
|
||||
after_destroy :destroy_kubernetes_integration!
|
||||
|
||||
alias_attribute :ca_pem, :ca_cert
|
||||
|
||||
delegate :project, to: :cluster, allow_nil: true
|
||||
delegate :enabled?, to: :cluster, allow_nil: true
|
||||
|
||||
class << self
|
||||
def namespace_for_project(project)
|
||||
"#{project.path}-#{project.id}"
|
||||
end
|
||||
end
|
||||
|
||||
def actual_namespace
|
||||
if namespace.present?
|
||||
namespace
|
||||
else
|
||||
default_namespace
|
||||
end
|
||||
end
|
||||
|
||||
def default_namespace
|
||||
self.class.namespace_for_project(project) if project
|
||||
end
|
||||
|
||||
def update_kubernetes_integration!
|
||||
raise 'Kubernetes service already configured' unless manages_kubernetes_service?
|
||||
|
||||
# This is neccesary, otheriwse enabled? returns true even though cluster updated with enabled: false
|
||||
cluster.reload
|
||||
|
||||
ensure_kubernetes_service&.update!(
|
||||
active: enabled?,
|
||||
api_url: api_url,
|
||||
namespace: namespace,
|
||||
token: token,
|
||||
ca_pem: ca_cert
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enforce_namespace_to_lower_case
|
||||
self.namespace = self.namespace&.downcase
|
||||
end
|
||||
|
||||
# TODO: glue code till we migrate Kubernetes Service into Platforms::Kubernetes class
|
||||
def manages_kubernetes_service?
|
||||
return true unless kubernetes_service&.active?
|
||||
|
||||
kubernetes_service.api_url == api_url
|
||||
end
|
||||
|
||||
def destroy_kubernetes_integration!
|
||||
return unless manages_kubernetes_service?
|
||||
|
||||
kubernetes_service&.destroy!
|
||||
end
|
||||
|
||||
def kubernetes_service
|
||||
@kubernetes_service ||= project&.kubernetes_service
|
||||
end
|
||||
|
||||
def ensure_kubernetes_service
|
||||
@kubernetes_service ||= kubernetes_service || project&.build_kubernetes_service
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
module Clusters
|
||||
class Project < ActiveRecord::Base
|
||||
self.table_name = 'cluster_projects'
|
||||
|
||||
belongs_to :cluster, class_name: 'Clusters::Cluster'
|
||||
belongs_to :project, class_name: '::Project'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
module Clusters
|
||||
module Providers
|
||||
class Gcp < ActiveRecord::Base
|
||||
self.table_name = 'cluster_providers_gcp'
|
||||
|
||||
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,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
validates :gcp_project_id,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
validates :zone, presence: true
|
||||
|
||||
validates :num_nodes,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than: 0
|
||||
}
|
||||
|
||||
state_machine :status, initial: :scheduled do
|
||||
state :scheduled, value: 1
|
||||
state :creating, value: 2
|
||||
state :created, value: 3
|
||||
state :errored, value: 4
|
||||
|
||||
event :make_creating do
|
||||
transition any - [:creating] => :creating
|
||||
end
|
||||
|
||||
event :make_created do
|
||||
transition any - [:created] => :created
|
||||
end
|
||||
|
||||
event :make_errored do
|
||||
transition any - [:errored] => :errored
|
||||
end
|
||||
|
||||
before_transition any => [:errored, :created] do |provider|
|
||||
provider.access_token = nil
|
||||
provider.operation_id = nil
|
||||
end
|
||||
|
||||
before_transition any => [:creating] do |provider, transition|
|
||||
operation_id = transition.args.first
|
||||
raise ArgumentError.new('operation_id is required') unless operation_id.present?
|
||||
provider.operation_id = operation_id
|
||||
end
|
||||
|
||||
before_transition any => [:errored] do |provider, transition|
|
||||
status_reason = transition.args.first
|
||||
provider.status_reason = status_reason if status_reason
|
||||
end
|
||||
end
|
||||
|
||||
def on_creation?
|
||||
scheduled? || creating?
|
||||
end
|
||||
|
||||
def api_client
|
||||
return unless access_token
|
||||
|
||||
@api_client ||= GoogleApi::CloudPlatform::Client.new(access_token, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
module Gcp
|
||||
class Cluster < ActiveRecord::Base
|
||||
extend Gitlab::Gcp::Model
|
||||
include Presentable
|
||||
|
||||
belongs_to :project, inverse_of: :cluster
|
||||
belongs_to :user
|
||||
belongs_to :service
|
||||
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
scope :disabled, -> { where(enabled: false) }
|
||||
|
||||
default_value_for :gcp_cluster_zone, 'us-central1-a'
|
||||
default_value_for :gcp_cluster_size, 3
|
||||
default_value_for :gcp_machine_type, 'n1-standard-4'
|
||||
|
||||
attr_encrypted :password,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
attr_encrypted :kubernetes_token,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
attr_encrypted :gcp_token,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
validates :gcp_project_id,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
validates :gcp_cluster_name,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
validates :gcp_cluster_zone, presence: true
|
||||
|
||||
validates :gcp_cluster_size,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than: 0
|
||||
}
|
||||
|
||||
validates :project_namespace,
|
||||
allow_blank: true,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
# if we do not do status transition we prevent change
|
||||
validate :restrict_modification, on: :update, unless: :status_changed?
|
||||
|
||||
state_machine :status, initial: :scheduled do
|
||||
state :scheduled, value: 1
|
||||
state :creating, value: 2
|
||||
state :created, value: 3
|
||||
state :errored, value: 4
|
||||
|
||||
event :make_creating do
|
||||
transition any - [:creating] => :creating
|
||||
end
|
||||
|
||||
event :make_created do
|
||||
transition any - [:created] => :created
|
||||
end
|
||||
|
||||
event :make_errored do
|
||||
transition any - [:errored] => :errored
|
||||
end
|
||||
|
||||
before_transition any => [:errored, :created] do |cluster|
|
||||
cluster.gcp_token = nil
|
||||
cluster.gcp_operation_id = nil
|
||||
end
|
||||
|
||||
before_transition any => [:errored] do |cluster, transition|
|
||||
status_reason = transition.args.first
|
||||
cluster.status_reason = status_reason if status_reason
|
||||
end
|
||||
end
|
||||
|
||||
def project_namespace_placeholder
|
||||
"#{project.path}-#{project.id}"
|
||||
end
|
||||
|
||||
def on_creation?
|
||||
scheduled? || creating?
|
||||
end
|
||||
|
||||
def api_url
|
||||
'https://' + endpoint if endpoint
|
||||
end
|
||||
|
||||
def restrict_modification
|
||||
if on_creation?
|
||||
errors.add(:base, "cannot modify during creation")
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -186,7 +186,9 @@ class Project < ActiveRecord::Base
|
|||
has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
|
||||
has_one :project_feature, inverse_of: :project
|
||||
has_one :statistics, class_name: 'ProjectStatistics'
|
||||
has_one :cluster, class_name: 'Gcp::Cluster', inverse_of: :project
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
module Ci
|
||||
class CreateClusterService < BaseService
|
||||
def execute(access_token)
|
||||
params['gcp_machine_type'] ||= GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE
|
||||
|
||||
cluster_params =
|
||||
params.merge(user: current_user,
|
||||
gcp_token: access_token)
|
||||
|
||||
project.create_cluster(cluster_params).tap do |cluster|
|
||||
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
module Ci
|
||||
class FetchGcpOperationService
|
||||
def execute(cluster)
|
||||
api_client =
|
||||
GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil)
|
||||
|
||||
operation = api_client.projects_zones_operations(
|
||||
cluster.gcp_project_id,
|
||||
cluster.gcp_cluster_zone,
|
||||
cluster.gcp_operation_id)
|
||||
|
||||
yield(operation) if block_given?
|
||||
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
|
||||
return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
module Ci
|
||||
class FinalizeClusterCreationService
|
||||
def execute(cluster)
|
||||
api_client =
|
||||
GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil)
|
||||
|
||||
begin
|
||||
gke_cluster = api_client.projects_zones_clusters_get(
|
||||
cluster.gcp_project_id,
|
||||
cluster.gcp_cluster_zone,
|
||||
cluster.gcp_cluster_name)
|
||||
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
|
||||
return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}")
|
||||
end
|
||||
|
||||
endpoint = gke_cluster.endpoint
|
||||
api_url = 'https://' + endpoint
|
||||
ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate)
|
||||
username = gke_cluster.master_auth.username
|
||||
password = gke_cluster.master_auth.password
|
||||
|
||||
kubernetes_token = Ci::FetchKubernetesTokenService.new(
|
||||
api_url, ca_cert, username, password).execute
|
||||
|
||||
unless kubernetes_token
|
||||
return cluster.make_errored!('Failed to get a default token of kubernetes')
|
||||
end
|
||||
|
||||
Ci::IntegrateClusterService.new.execute(
|
||||
cluster, endpoint, ca_cert, kubernetes_token, username, password)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
module Ci
|
||||
class IntegrateClusterService
|
||||
def execute(cluster, endpoint, ca_cert, token, username, password)
|
||||
Gcp::Cluster.transaction do
|
||||
cluster.update!(
|
||||
enabled: true,
|
||||
endpoint: endpoint,
|
||||
ca_cert: ca_cert,
|
||||
kubernetes_token: token,
|
||||
username: username,
|
||||
password: password,
|
||||
service: cluster.project.find_or_initialize_service('kubernetes'),
|
||||
status_event: :make_created)
|
||||
|
||||
cluster.service.update!(
|
||||
active: true,
|
||||
api_url: cluster.api_url,
|
||||
ca_pem: ca_cert,
|
||||
namespace: cluster.project_namespace,
|
||||
token: token)
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
cluster.make_errored!("Failed to integrate cluster into kubernetes_service: #{e.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
module Ci
|
||||
class ProvisionClusterService
|
||||
def execute(cluster)
|
||||
api_client =
|
||||
GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil)
|
||||
|
||||
begin
|
||||
operation = api_client.projects_zones_clusters_create(
|
||||
cluster.gcp_project_id,
|
||||
cluster.gcp_cluster_zone,
|
||||
cluster.gcp_cluster_name,
|
||||
cluster.gcp_cluster_size,
|
||||
machine_type: cluster.gcp_machine_type)
|
||||
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
|
||||
return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}")
|
||||
end
|
||||
|
||||
unless operation.status == 'RUNNING' || operation.status == 'PENDING'
|
||||
return cluster.make_errored!("Operation status is unexpected; #{operation.status_message}")
|
||||
end
|
||||
|
||||
cluster.gcp_operation_id = api_client.parse_operation_id(operation.self_link)
|
||||
|
||||
unless cluster.gcp_operation_id
|
||||
return cluster.make_errored!('Can not find operation_id from self_link')
|
||||
end
|
||||
|
||||
if cluster.make_creating
|
||||
WaitForClusterCreationWorker.perform_in(
|
||||
WaitForClusterCreationWorker::INITIAL_INTERVAL, cluster.id)
|
||||
else
|
||||
return cluster.make_errored!("Failed to update cluster record; #{cluster.errors}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
module Ci
|
||||
class UpdateClusterService < BaseService
|
||||
def execute(cluster)
|
||||
Gcp::Cluster.transaction do
|
||||
cluster.update!(params)
|
||||
|
||||
if params['enabled'] == 'true'
|
||||
cluster.service.update!(
|
||||
active: true,
|
||||
api_url: cluster.api_url,
|
||||
ca_pem: cluster.ca_cert,
|
||||
namespace: cluster.project_namespace,
|
||||
token: cluster.kubernetes_token)
|
||||
else
|
||||
cluster.service.update!(active: false)
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
cluster.errors.add(:base, e.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
module Clusters
|
||||
class CreateService < BaseService
|
||||
attr_reader :access_token
|
||||
|
||||
def execute(access_token)
|
||||
@access_token = access_token
|
||||
|
||||
create_cluster.tap do |cluster|
|
||||
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_cluster
|
||||
Clusters::Cluster.create(cluster_params)
|
||||
end
|
||||
|
||||
def cluster_params
|
||||
return @cluster_params if defined?(@cluster_params)
|
||||
|
||||
params[:provider_gcp_attributes].try do |provider|
|
||||
provider[:access_token] = access_token
|
||||
end
|
||||
|
||||
@cluster_params = params.merge(user: current_user, projects: [project])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
module Clusters
|
||||
module Gcp
|
||||
class FetchOperationService
|
||||
def execute(provider)
|
||||
operation = provider.api_client.projects_zones_operations(
|
||||
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
|
||||
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
module Clusters
|
||||
module Gcp
|
||||
class FinalizeCreationService
|
||||
attr_reader :provider
|
||||
|
||||
def execute(provider)
|
||||
@provider = provider
|
||||
|
||||
configure_provider
|
||||
configure_kubernetes
|
||||
|
||||
cluster.save!
|
||||
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
|
||||
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
provider.make_errored!("Failed to configure GKE Cluster: #{e.message}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def configure_provider
|
||||
provider.endpoint = gke_cluster.endpoint
|
||||
provider.status_event = :make_created
|
||||
end
|
||||
|
||||
def configure_kubernetes
|
||||
cluster.platform_type = :kubernetes
|
||||
cluster.build_platform_kubernetes(
|
||||
api_url: 'https://' + gke_cluster.endpoint,
|
||||
ca_cert: Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
|
||||
username: gke_cluster.master_auth.username,
|
||||
password: gke_cluster.master_auth.password,
|
||||
token: request_kuberenetes_token)
|
||||
end
|
||||
|
||||
def request_kuberenetes_token
|
||||
Ci::FetchKubernetesTokenService.new(
|
||||
'https://' + gke_cluster.endpoint,
|
||||
Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
|
||||
gke_cluster.master_auth.username,
|
||||
gke_cluster.master_auth.password).execute
|
||||
end
|
||||
|
||||
def gke_cluster
|
||||
@gke_cluster ||= provider.api_client.projects_zones_clusters_get(
|
||||
provider.gcp_project_id,
|
||||
provider.zone,
|
||||
cluster.name)
|
||||
end
|
||||
|
||||
def cluster
|
||||
@cluster ||= provider.cluster
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
module Clusters
|
||||
module Gcp
|
||||
class ProvisionService
|
||||
attr_reader :provider
|
||||
|
||||
def execute(provider)
|
||||
@provider = provider
|
||||
|
||||
get_operation_id do |operation_id|
|
||||
if provider.make_creating(operation_id)
|
||||
WaitForClusterCreationWorker.perform_in(
|
||||
Clusters::Gcp::VerifyProvisionStatusService::INITIAL_INTERVAL,
|
||||
provider.cluster_id)
|
||||
else
|
||||
provider.make_errored!("Failed to update provider record; #{provider.errors}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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)
|
||||
|
||||
unless operation.status == 'PENDING' || operation.status == 'RUNNING'
|
||||
return provider.make_errored!("Operation status is unexpected; #{operation.status_message}")
|
||||
end
|
||||
|
||||
operation_id = provider.api_client.parse_operation_id(operation.self_link)
|
||||
|
||||
unless operation_id
|
||||
return provider.make_errored!('Can not find operation_id from self_link')
|
||||
end
|
||||
|
||||
yield(operation_id)
|
||||
|
||||
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
|
||||
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
module Clusters
|
||||
module Gcp
|
||||
class VerifyProvisionStatusService
|
||||
attr_reader :provider
|
||||
|
||||
INITIAL_INTERVAL = 2.minutes
|
||||
EAGER_INTERVAL = 10.seconds
|
||||
TIMEOUT = 20.minutes
|
||||
|
||||
def execute(provider)
|
||||
@provider = provider
|
||||
|
||||
request_operation do |operation|
|
||||
case operation.status
|
||||
when 'PENDING', 'RUNNING'
|
||||
continue_creation(operation)
|
||||
when 'DONE'
|
||||
finalize_creation
|
||||
else
|
||||
return provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def continue_creation(operation)
|
||||
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
|
||||
|
||||
def elapsed_time_from_creation(operation)
|
||||
Time.now.utc - operation.start_time.to_time.utc
|
||||
end
|
||||
|
||||
def finalize_creation
|
||||
Clusters::Gcp::FinalizeCreationService.new.execute(provider)
|
||||
end
|
||||
|
||||
def request_operation(&blk)
|
||||
Clusters::Gcp::FetchOperationService.new.execute(provider, &blk)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module Clusters
|
||||
class UpdateService < BaseService
|
||||
def execute(cluster)
|
||||
cluster.update(params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -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_changed?
|
||||
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,32 @@
|
|||
- 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 :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'
|
||||
|
||||
.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
|
||||
= field.label :gcp_cluster_size, s_('ClusterIntegration|Number of nodes')
|
||||
= field.text_field :gcp_cluster_size, 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'
|
||||
= 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 :project_namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
|
||||
= field.text_field :project_namespace, class: 'form-control', placeholder: @cluster.project_namespace_placeholder
|
||||
= 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
|
||||
= 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
|
||||
= 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'
|
||||
|
||||
.form-group
|
||||
= field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save'
|
||||
|
|
|
|||
|
|
@ -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.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ class ClusterProvisionWorker
|
|||
include ClusterQueue
|
||||
|
||||
def perform(cluster_id)
|
||||
Gcp::Cluster.find_by_id(cluster_id).try do |cluster|
|
||||
Ci::ProvisionClusterService.new.execute(cluster)
|
||||
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
|
||||
cluster.provider.try do |provider|
|
||||
Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,25 +2,10 @@ class WaitForClusterCreationWorker
|
|||
include Sidekiq::Worker
|
||||
include ClusterQueue
|
||||
|
||||
INITIAL_INTERVAL = 2.minutes
|
||||
EAGER_INTERVAL = 10.seconds
|
||||
TIMEOUT = 20.minutes
|
||||
|
||||
def perform(cluster_id)
|
||||
Gcp::Cluster.find_by_id(cluster_id).try do |cluster|
|
||||
Ci::FetchGcpOperationService.new.execute(cluster) do |operation|
|
||||
case operation.status
|
||||
when 'RUNNING'
|
||||
if TIMEOUT < Time.now.utc - operation.start_time.to_time.utc
|
||||
return cluster.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}")
|
||||
end
|
||||
|
||||
WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, cluster.id)
|
||||
when 'DONE'
|
||||
Ci::FinalizeClusterCreationService.new.execute(cluster)
|
||||
else
|
||||
return cluster.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
|
||||
end
|
||||
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
|
||||
cluster.provider.try do |provider|
|
||||
Clusters::Gcp::VerifyProvisionStatusService.new.execute(provider) if cluster.gcp?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
class CreateNewClustersArchitectures < ActiveRecord::Migration
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
create_table :clusters do |t|
|
||||
t.references :user, index: true, foreign_key: { on_delete: :nullify }
|
||||
|
||||
t.integer :provider_type
|
||||
t.integer :platform_type
|
||||
|
||||
t.datetime_with_timezone :created_at, null: false
|
||||
t.datetime_with_timezone :updated_at, null: false
|
||||
|
||||
t.boolean :enabled, index: true, default: true
|
||||
|
||||
t.string :name, null: false # If manual, read-write. If gcp, read-only.
|
||||
end
|
||||
|
||||
create_table :cluster_projects do |t|
|
||||
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_platforms_kubernetes do |t|
|
||||
t.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
|
||||
|
||||
t.datetime_with_timezone :created_at, null: false
|
||||
t.datetime_with_timezone :updated_at, null: false
|
||||
|
||||
t.text :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
|
||||
end
|
||||
|
||||
create_table :cluster_providers_gcp do |t|
|
||||
t.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
|
||||
|
||||
t.integer :status
|
||||
t.integer :num_nodes, null: false
|
||||
|
||||
t.datetime_with_timezone :created_at, null: false
|
||||
t.datetime_with_timezone :updated_at, null: false
|
||||
|
||||
t.text :status_reason
|
||||
|
||||
t.string :gcp_project_id, null: false
|
||||
t.string :zone, null: false
|
||||
t.string :machine_type
|
||||
t.string :operation_id
|
||||
|
||||
t.string :endpoint
|
||||
|
||||
t.text :encrypted_access_token
|
||||
t.string :encrypted_access_token_iv
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
class MigrateGcpClustersToNewClustersArchitectures < ActiveRecord::Migration
|
||||
DOWNTIME = false
|
||||
|
||||
class GcpCluster < ActiveRecord::Base
|
||||
self.table_name = 'gcp_clusters'
|
||||
|
||||
belongs_to :project, class_name: 'Project'
|
||||
|
||||
include EachBatch
|
||||
end
|
||||
|
||||
class Cluster < ActiveRecord::Base
|
||||
self.table_name = 'clusters'
|
||||
|
||||
has_many :cluster_projects, class_name: 'ClustersProject'
|
||||
has_many :projects, through: :cluster_projects, class_name: 'Project'
|
||||
has_one :provider_gcp, class_name: 'ProvidersGcp'
|
||||
has_one :platform_kubernetes, class_name: 'PlatformsKubernetes'
|
||||
|
||||
accepts_nested_attributes_for :provider_gcp
|
||||
accepts_nested_attributes_for :platform_kubernetes
|
||||
|
||||
enum platform_type: {
|
||||
kubernetes: 1
|
||||
}
|
||||
|
||||
enum provider_type: {
|
||||
user: 0,
|
||||
gcp: 1
|
||||
}
|
||||
end
|
||||
|
||||
class Project < ActiveRecord::Base
|
||||
self.table_name = 'projects'
|
||||
|
||||
has_one :cluster_project, class_name: 'ClustersProject'
|
||||
has_one :cluster, through: :cluster_project, class_name: 'Cluster'
|
||||
end
|
||||
|
||||
class ClustersProject < ActiveRecord::Base
|
||||
self.table_name = 'cluster_projects'
|
||||
|
||||
belongs_to :cluster, class_name: 'Cluster'
|
||||
belongs_to :project, class_name: 'Project'
|
||||
end
|
||||
|
||||
class ProvidersGcp < ActiveRecord::Base
|
||||
self.table_name = 'cluster_providers_gcp'
|
||||
end
|
||||
|
||||
class PlatformsKubernetes < ActiveRecord::Base
|
||||
self.table_name = 'cluster_platforms_kubernetes'
|
||||
end
|
||||
|
||||
def up
|
||||
GcpCluster.all.find_each(batch_size: 1) do |gcp_cluster|
|
||||
Cluster.create(
|
||||
enabled: gcp_cluster.enabled,
|
||||
user_id: gcp_cluster.user_id,
|
||||
name: gcp_cluster.gcp_cluster_name,
|
||||
provider_type: Cluster.provider_types[:gcp],
|
||||
platform_type: Cluster.platform_types[:kubernetes],
|
||||
projects: [gcp_cluster.project],
|
||||
provider_gcp_attributes: {
|
||||
status: gcp_cluster.status,
|
||||
status_reason: gcp_cluster.status_reason,
|
||||
gcp_project_id: gcp_cluster.gcp_project_id,
|
||||
zone: gcp_cluster.gcp_cluster_zone,
|
||||
num_nodes: gcp_cluster.gcp_cluster_size,
|
||||
machine_type: gcp_cluster.gcp_machine_type,
|
||||
operation_id: gcp_cluster.gcp_operation_id,
|
||||
endpoint: gcp_cluster.endpoint,
|
||||
encrypted_access_token: gcp_cluster.encrypted_gcp_token,
|
||||
encrypted_access_token_iv: gcp_cluster.encrypted_gcp_token_iv
|
||||
},
|
||||
platform_kubernetes_attributes: {
|
||||
cluster_id: gcp_cluster.id,
|
||||
api_url: api_url(gcp_cluster.endpoint),
|
||||
ca_cert: gcp_cluster.ca_cert,
|
||||
namespace: gcp_cluster.project_namespace,
|
||||
username: gcp_cluster.username,
|
||||
encrypted_password: gcp_cluster.encrypted_password,
|
||||
encrypted_password_iv: gcp_cluster.encrypted_password_iv,
|
||||
encrypted_token: gcp_cluster.encrypted_kubernetes_token,
|
||||
encrypted_token_iv: gcp_cluster.encrypted_kubernetes_token_iv
|
||||
} )
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
execute('DELETE FROM clusters')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def api_url(endpoint)
|
||||
endpoint ? 'https://' + endpoint : nil
|
||||
end
|
||||
end
|
||||
62
db/schema.rb
62
db/schema.rb
|
|
@ -462,6 +462,63 @@ ActiveRecord::Schema.define(version: 20171101134435) 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.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
t.text "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"
|
||||
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_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "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.integer "num_nodes", null: false
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
t.text "status_reason"
|
||||
t.string "gcp_project_id", null: false
|
||||
t.string "zone", null: false
|
||||
t.string "machine_type"
|
||||
t.string "operation_id"
|
||||
t.string "endpoint"
|
||||
t.text "encrypted_access_token"
|
||||
t.string "encrypted_access_token_iv"
|
||||
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.integer "provider_type"
|
||||
t.integer "platform_type"
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
t.boolean "enabled", default: true
|
||||
t.string "name", null: false
|
||||
end
|
||||
|
||||
add_index "clusters", ["enabled"], name: "index_clusters_on_enabled", using: :btree
|
||||
add_index "clusters", ["user_id"], name: "index_clusters_on_user_id", using: :btree
|
||||
|
||||
create_table "container_repositories", force: :cascade do |t|
|
||||
t.integer "project_id", null: false
|
||||
t.string "name", null: false
|
||||
|
|
@ -1809,6 +1866,11 @@ ActiveRecord::Schema.define(version: 20171101134435) 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -8,8 +8,8 @@ module Gitlab
|
|||
triggers: 'Ci::Trigger',
|
||||
pipeline_schedules: 'Ci::PipelineSchedule',
|
||||
builds: 'Ci::Build',
|
||||
cluster: 'Gcp::Cluster',
|
||||
clusters: 'Gcp::Cluster',
|
||||
cluster: 'Clusters::Cluster',
|
||||
clusters: 'Clusters::Cluster',
|
||||
hooks: 'ProjectHook',
|
||||
merge_access_levels: 'ProtectedBranch::MergeAccessLevel',
|
||||
push_access_levels: 'ProtectedBranch::PushAccessLevel',
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ module Gitlab
|
|||
deploy_keys: DeployKey.count,
|
||||
deployments: Deployment.count,
|
||||
environments: ::Environment.count,
|
||||
gcp_clusters: ::Gcp::Cluster.count,
|
||||
gcp_clusters_enabled: ::Gcp::Cluster.enabled.count,
|
||||
gcp_clusters_disabled: ::Gcp::Cluster.disabled.count,
|
||||
clusters: ::Clusters::Cluster.count,
|
||||
clusters_enabled: ::Clusters::Cluster.enabled.count,
|
||||
clusters_disabled: ::Clusters::Cluster.disabled.count,
|
||||
in_review_folder: ::Environment.in_review_folder.count,
|
||||
groups: Group.count,
|
||||
issues: Issue.count,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ require 'google/apis/container_v1'
|
|||
module GoogleApi
|
||||
module CloudPlatform
|
||||
class Client < GoogleApi::Auth
|
||||
DEFAULT_MACHINE_TYPE = 'n1-standard-1'.freeze
|
||||
SCOPE = 'https://www.googleapis.com/auth/cloud-platform'.freeze
|
||||
LEAST_TOKEN_LIFE_TIME = 10.minutes
|
||||
|
||||
|
|
|
|||
|
|
@ -1,67 +1,107 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Projects::ClustersController do
|
||||
set(:user) { create(:user) }
|
||||
set(:project) { create(:project) }
|
||||
let(:role) { :master }
|
||||
|
||||
before do
|
||||
project.team << [user, role]
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
include AccessMatchersForController
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
|
||||
describe 'GET index' do
|
||||
subject do
|
||||
get :index, namespace_id: project.namespace,
|
||||
project_id: project
|
||||
end
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'when cluster is already created' do
|
||||
let!(:cluster) { create(:gcp_cluster, :created_on_gke, project: project) }
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'redirects to show a cluster' do
|
||||
subject
|
||||
context 'when project has a cluster' do
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
expect(response).to redirect_to(project_cluster_path(project, cluster))
|
||||
it { expect(go).to redirect_to(project_cluster_path(project, project.cluster)) }
|
||||
end
|
||||
|
||||
context 'when project does not have a cluster' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it { expect(go).to redirect_to(new_project_cluster_path(project)) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when we do not have cluster' do
|
||||
it 'redirects to create a cluster' do
|
||||
subject
|
||||
describe 'security' do
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
expect(response).to redirect_to(new_project_cluster_path(project))
|
||||
end
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
get :index, namespace_id: project.namespace.to_param, project_id: project
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET login' do
|
||||
render_views
|
||||
let(:project) { create(:project) }
|
||||
|
||||
subject do
|
||||
get :login, namespace_id: project.namespace,
|
||||
project_id: project
|
||||
end
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'when we do have omniauth configured' do
|
||||
it 'shows login button' do
|
||||
subject
|
||||
|
||||
expect(response.body).to include('auth_buttons/signin_with_google')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when we do not have omniauth configured' do
|
||||
before do
|
||||
stub_omniauth_setting(providers: [])
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'shows notice message' do
|
||||
subject
|
||||
context 'when omniauth has been configured' do
|
||||
let(:key) { 'secere-key' }
|
||||
|
||||
expect(response.body).to include('Ask your GitLab administrator if you want to use this service.')
|
||||
let(:session_key_for_redirect_uri) do
|
||||
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(SecureRandom).to receive(:hex).and_return(key)
|
||||
end
|
||||
|
||||
it 'has authorize_url' do
|
||||
go
|
||||
|
||||
expect(assigns(:authorize_url)).to include(key)
|
||||
expect(session[session_key_for_redirect_uri]).to eq(project_clusters_url(project))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when omniauth has not configured' do
|
||||
before do
|
||||
stub_omniauth_setting(providers: [])
|
||||
end
|
||||
|
||||
it 'does not have authorize_url' do
|
||||
go
|
||||
|
||||
expect(assigns(:authorize_url)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
get :login, namespace_id: project.namespace, project_id: project
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -74,235 +114,335 @@ describe Projects::ClustersController do
|
|||
end
|
||||
|
||||
describe 'GET new' do
|
||||
render_views
|
||||
let(:project) { create(:project) }
|
||||
|
||||
subject do
|
||||
get :new, namespace_id: project.namespace,
|
||||
project_id: project
|
||||
end
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'when logged' do
|
||||
before do
|
||||
make_logged_in
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'shows a creation form' do
|
||||
subject
|
||||
context 'when access token is valid' do
|
||||
before do
|
||||
stub_google_api_validate_token
|
||||
end
|
||||
|
||||
expect(response.body).to include('Create cluster')
|
||||
it 'has new object' do
|
||||
go
|
||||
|
||||
expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when access token is expired' do
|
||||
before do
|
||||
stub_google_api_expired_token
|
||||
end
|
||||
|
||||
it { expect(go).to redirect_to(login_project_clusters_path(project)) }
|
||||
end
|
||||
|
||||
context 'when access token is not stored in session' do
|
||||
it { expect(go).to redirect_to(login_project_clusters_path(project)) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not logged' do
|
||||
it_behaves_like 'requires to login'
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
get :new, namespace_id: project.namespace, project_id: project
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
subject do
|
||||
post :create, params.merge(namespace_id: project.namespace,
|
||||
project_id: project)
|
||||
let(:project) { create(:project) }
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
name: 'new-cluster',
|
||||
provider_type: :gcp,
|
||||
provider_gcp_attributes: {
|
||||
gcp_project_id: '111'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when not logged' do
|
||||
let(:params) { {} }
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it_behaves_like 'requires to login'
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
before do
|
||||
make_logged_in
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when all required parameters are set' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
gcp_cluster_name: 'new-cluster',
|
||||
gcp_project_id: '111'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when access token is valid' do
|
||||
before do
|
||||
expect(ClusterProvisionWorker).to receive(:perform_async) { }
|
||||
stub_google_api_validate_token
|
||||
end
|
||||
|
||||
it 'creates a new cluster' do
|
||||
expect { subject }.to change { Gcp::Cluster.count }
|
||||
|
||||
expect(response).to redirect_to(project_cluster_path(project, project.cluster))
|
||||
context 'when creates a cluster on gke' do
|
||||
it 'creates a new cluster' do
|
||||
expect(ClusterProvisionWorker).to receive(:perform_async)
|
||||
expect { go }.to change { Clusters::Cluster.count }
|
||||
expect(response).to redirect_to(project_cluster_path(project, project.cluster))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not all required parameters are set' do
|
||||
render_views
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
project_namespace: 'some namespace'
|
||||
}
|
||||
}
|
||||
context 'when access token is expired' do
|
||||
before do
|
||||
stub_google_api_expired_token
|
||||
end
|
||||
|
||||
it 'shows an error message' do
|
||||
expect { subject }.not_to change { Gcp::Cluster.count }
|
||||
|
||||
expect(response).to render_template(:new)
|
||||
it 'redirects to login page' do
|
||||
expect(go).to redirect_to(login_project_clusters_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when access token is not stored in session' do
|
||||
it 'redirects to login page' do
|
||||
expect(go).to redirect_to(login_project_clusters_path(project))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
post :create, params.merge(namespace_id: project.namespace, project_id: project)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET status' do
|
||||
let(:cluster) { create(:gcp_cluster, :created_on_gke, project: project) }
|
||||
let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
subject do
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "responds with matching schema" do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('cluster_status')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
get :status, namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster,
|
||||
format: :json
|
||||
end
|
||||
|
||||
it "responds with matching schema" do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('cluster_status')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET show' do
|
||||
render_views
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
let(:cluster) { create(:gcp_cluster, :created_on_gke, project: project) }
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
subject do
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "renders view" do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(assigns(:cluster)).to eq(cluster)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
get :show, namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster
|
||||
end
|
||||
|
||||
context 'when logged as master' do
|
||||
it "allows to update cluster" do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.body).to include("Save")
|
||||
end
|
||||
|
||||
it "allows remove integration" do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.body).to include("Remove integration")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged as developer' do
|
||||
let(:role) { :developer }
|
||||
|
||||
it "does not allow to access page" do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT update' do
|
||||
render_views
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
let(:service) { project.build_kubernetes_service }
|
||||
let(:cluster) { create(:gcp_cluster, :created_on_gke, project: project, service: service) }
|
||||
let(:params) { {} }
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
subject do
|
||||
put :update, params.merge(namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster)
|
||||
end
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when logged as master' do
|
||||
context 'when valid params are used' do
|
||||
context 'when update enabled' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: { enabled: false }
|
||||
}
|
||||
end
|
||||
|
||||
it "redirects back to show page" do
|
||||
subject
|
||||
it "updates and redirects back to show page" do
|
||||
go
|
||||
|
||||
cluster.reload
|
||||
expect(response).to redirect_to(project_cluster_path(project, project.cluster))
|
||||
expect(flash[:notice]).to eq('Cluster was successfully updated.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid params are used' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: { project_namespace: 'my Namespace 321321321 #' }
|
||||
}
|
||||
expect(cluster.enabled).to be_falsey
|
||||
end
|
||||
|
||||
it "rejects changes" do
|
||||
subject
|
||||
context 'when cluster is being created' do
|
||||
let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
it "rejects changes" do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
expect(cluster.enabled).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged as developer' do
|
||||
let(:role) { :developer }
|
||||
|
||||
it "does not allow to update cluster" do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
describe 'security' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: { enabled: false }
|
||||
}
|
||||
end
|
||||
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
put :update, params.merge(namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'delete update' do
|
||||
let(:cluster) { create(:gcp_cluster, :created_on_gke, project: project) }
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
subject do
|
||||
delete :destroy, namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster
|
||||
end
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'when logged as master' do
|
||||
it "redirects back to clusters list" do
|
||||
subject
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "destroys and redirects back to clusters list" do
|
||||
expect { go }
|
||||
.to change { Clusters::Cluster.count }.by(-1)
|
||||
.and change { Clusters::Platforms::Kubernetes.count }.by(-1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(-1)
|
||||
|
||||
expect(response).to redirect_to(project_clusters_path(project))
|
||||
expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged as developer' do
|
||||
let(:role) { :developer }
|
||||
context 'when cluster is being created' do
|
||||
let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
|
||||
|
||||
it "does not allow to destroy cluster" do
|
||||
subject
|
||||
it "destroys and redirects back to clusters list" do
|
||||
expect { go }
|
||||
.to change { Clusters::Cluster.count }.by(-1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(-1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response).to redirect_to(project_clusters_path(project))
|
||||
expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provider is user' do
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_user) }
|
||||
|
||||
it "destroys and redirects back to clusters list" do
|
||||
expect { go }
|
||||
.to change { Clusters::Cluster.count }.by(-1)
|
||||
.and change { Clusters::Platforms::Kubernetes.count }.by(-1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(0)
|
||||
|
||||
expect(response).to redirect_to(project_clusters_path(project))
|
||||
expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_logged_in
|
||||
session[GoogleApi::CloudPlatform::Client.session_key_for_token] = '1234'
|
||||
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = in_hour.to_i.to_s
|
||||
end
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def in_hour
|
||||
Time.now + 1.hour
|
||||
def go
|
||||
delete :destroy, namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
FactoryGirl.define do
|
||||
factory :cluster, class: Clusters::Cluster do
|
||||
user
|
||||
name 'test-cluster'
|
||||
|
||||
trait :project do
|
||||
after(:create) do |cluster, evaluator|
|
||||
cluster.projects << create(:project)
|
||||
end
|
||||
end
|
||||
|
||||
trait :provided_by_user do
|
||||
provider_type :user
|
||||
platform_type :kubernetes
|
||||
|
||||
platform_kubernetes do
|
||||
create(:cluster_platform_kubernetes, :configured)
|
||||
end
|
||||
end
|
||||
|
||||
trait :provided_by_gcp do
|
||||
provider_type :gcp
|
||||
platform_type :kubernetes
|
||||
|
||||
before(:create) do |cluster, evaluator|
|
||||
cluster.platform_kubernetes = build(:cluster_platform_kubernetes, :configured)
|
||||
cluster.provider_gcp = build(:cluster_provider_gcp, :created)
|
||||
end
|
||||
end
|
||||
|
||||
trait :providing_by_gcp do
|
||||
provider_type :gcp
|
||||
|
||||
provider_gcp do
|
||||
create(:cluster_provider_gcp, :creating)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
FactoryGirl.define do
|
||||
factory :cluster_platform_kubernetes, class: Clusters::Platforms::Kubernetes do
|
||||
cluster
|
||||
namespace nil
|
||||
api_url 'https://kubernetes.example.com'
|
||||
token 'a' * 40
|
||||
|
||||
trait :configured do
|
||||
api_url 'https://kubernetes.example.com'
|
||||
token 'a' * 40
|
||||
username 'xxxxxx'
|
||||
password 'xxxxxx'
|
||||
|
||||
after(:create) do |platform_kubernetes, evaluator|
|
||||
pem_file = File.expand_path(Rails.root.join('spec/fixtures/clusters/sample_cert.pem'))
|
||||
platform_kubernetes.ca_cert = File.read(pem_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
FactoryGirl.define do
|
||||
factory :cluster_provider_gcp, class: Clusters::Providers::Gcp do
|
||||
cluster
|
||||
gcp_project_id 'test-gcp-project'
|
||||
|
||||
trait :scheduled do
|
||||
access_token 'access_token_123'
|
||||
end
|
||||
|
||||
trait :creating do
|
||||
access_token 'access_token_123'
|
||||
|
||||
after(:build) do |gcp, evaluator|
|
||||
gcp.make_creating('operation-123')
|
||||
end
|
||||
end
|
||||
|
||||
trait :created do
|
||||
endpoint '111.111.111.111'
|
||||
|
||||
after(:build) do |gcp, evaluator|
|
||||
gcp.make_created
|
||||
end
|
||||
end
|
||||
|
||||
trait :errored do
|
||||
after(:build) do |gcp, evaluator|
|
||||
gcp.make_errored('Something wrong')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
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 :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 :errored do
|
||||
status_event :make_errored
|
||||
status_reason 'general error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Clusters', :js do
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
|
||||
let!(:project) { create(:project, :repository) }
|
||||
let!(:user) { create(:user) }
|
||||
|
||||
|
|
@ -11,8 +13,10 @@ feature 'Clusters', :js do
|
|||
|
||||
context 'when user has signed in Google' do
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:validate_token).and_return(true)
|
||||
allow_any_instance_of(Projects::ClustersController)
|
||||
.to receive(:token_in_session).and_return('token')
|
||||
allow_any_instance_of(Projects::ClustersController)
|
||||
.to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
|
||||
end
|
||||
|
||||
context 'when user does not have a cluster and visits cluster index page' do
|
||||
|
|
@ -36,15 +40,15 @@ feature 'Clusters', :js do
|
|||
|
||||
allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
|
||||
|
||||
fill_in 'cluster_gcp_project_id', with: 'gcp-project-123'
|
||||
fill_in 'cluster_gcp_cluster_name', with: 'dev-cluster'
|
||||
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
|
||||
fill_in 'cluster_name', with: 'dev-cluster'
|
||||
click_button 'Create cluster'
|
||||
end
|
||||
|
||||
it 'user sees a cluster details page and creation status' do
|
||||
expect(page).to have_content('Cluster is being created on Google Container Engine...')
|
||||
|
||||
Gcp::Cluster.last.make_created!
|
||||
Clusters::Cluster.last.provider.make_created!
|
||||
|
||||
expect(page).to have_content('Cluster was successfully created on Google Container Engine')
|
||||
end
|
||||
|
|
@ -62,7 +66,8 @@ feature 'Clusters', :js do
|
|||
end
|
||||
|
||||
context 'when user has a cluster and visits cluster index page' do
|
||||
let!(:cluster) { create(:gcp_cluster, :created_on_gke, :with_kubernetes_service, project: project) }
|
||||
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
before do
|
||||
visit project_clusters_path(project)
|
||||
|
|
@ -70,7 +75,7 @@ feature 'Clusters', :js do
|
|||
|
||||
it 'user sees an cluster details page' do
|
||||
expect(page).to have_button('Save')
|
||||
expect(page.find(:css, '.cluster-name').value).to eq(cluster.gcp_cluster_name)
|
||||
expect(page.find(:css, '.cluster-name').value).to eq(cluster.name)
|
||||
end
|
||||
|
||||
context 'when user disables the cluster' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFtTCCA52gAwIBAgIJAOutg3Kf2y5dMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTcxMDI5MTgxOTU3WhcNMTgxMDI5MTgxOTU3WjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
||||
CgKCAgEAvQysroM3TLxaavadSPnFIltrYnxCnU4PvCR8971HMWXsq7Z4ShU4BbbE
|
||||
8yp7oUFjulSwW6DhdIvnQb8ihLKictLmrA0isQqrD/iNpKZ6/lI4DGWw4QzrvMnW
|
||||
V4yy2QZNpg9tzQHd4+xkeeIoG23RijDU/sPd5dqxF+rPHBfCVInmYvSzLvMhneNj
|
||||
Bt6gV02gU9e9hsnMatsDvEbvWKp7wcbPot0nWrfZulx2QAWyXy+zG9mJQUds6yc0
|
||||
4agAeT9JEb/xtRgR/kS0aUHSGnfSnhZiEn17s0PhTmbu7qSHgzgB+7oJrC9jPoUh
|
||||
S2Wo3n0xykAjHrA8wC/Ddw3L38S41VQ58GEfNchistPswyMmXo/Oenv9P3s/kCOI
|
||||
fndiksFNdqVo51y9Vjngj589hpOseFDyKmWPIEQZ9kxW/crjP6RZWWLHgz26KtxZ
|
||||
uJaoYL8VBbYfrk/bucw0Ma2GEOp8rTsBE7SvgejXZa78q+381Kzc/utW6VwSXqzY
|
||||
xeIitft0rXi17SZ+XoiTkIXtHn0ZwMtOXNDBADTpFmKa6wVACQilvcpOYD8gUHyH
|
||||
pB+EDRdST3M4Fiq1MBAVhk8Lj3tHSJ/1ymeF1PWSu57AnJlzerzq2fcfPotNNd37
|
||||
ZPNkPh0kxPLwxbAyrHflzx9qVVdI1irY9055mNSnhzlec4qJ9cECAwEAAaOBpzCB
|
||||
pDAdBgNVHQ4EFgQUnVa5dYPoIG/3+qXml0bX8+N16GwwdQYDVR0jBG4wbIAUnVa5
|
||||
dYPoIG/3+qXml0bX8+N16GyhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT
|
||||
b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDr
|
||||
rYNyn9suXTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAUg4cyxXi1
|
||||
VR8ejTpaAruRyJ1pEG9Kc3kiIRXODy60z3hJXnx9LkScPkWGiuL5XacfZ2rMd4bw
|
||||
oVXIyi8U1UHWfAH8EZdrFKkU92jCiL5soHUONxLAvQEJ/FTR/qijrpzLCxXBdVQE
|
||||
xFEDWUu6rxLFyjEwzwnRTLgpjR606fdb7qXHkuAMvZ/ezJj8j97hok3Odpn4lr2H
|
||||
6hMTpK7HmDBX+kmdJJ+yBrm9hG1Pzpl7QU0dkxZ+qJNFjYMLnziiTwkv0c5ZaA9E
|
||||
NykZUcOv3Sjb6spu1A/E2BSq4WTjkIjrogFlfimE1vmUmObTRJOqUB0Vky1kHEwN
|
||||
pg7QqIJQmof1EAIaSM/YpUWXyumBwGLDUEud1JUz05In9Q4IZjEwZSJwbQW4fUia
|
||||
A93m9rk3Lw3xsFcaUdPMFIXk0rPoF1IgmV/oqb0gK95lOWRLbN+AV8qpKPpcKXOc
|
||||
TkIdFE47ZisEDhIdF6wC1izEMLeMEsPAO7/Y6MY4nRxsinSe95lRaw+yQpzx+mvJ
|
||||
Q7n1kiHI9Pd5M3+CiQda0d/GO1o5ORJnUGJRvr9HKuNmE7Lif0As/N0AlywjzE7A
|
||||
6Z8AEiWyRV1ffshu1k2UKmzvZuZeGGKRtrIjbJIRAtpRVtVZZGzhq5/sojCLoJ+u
|
||||
texqFBUo/4mFRZa4pDItUdyOlDy2/LO/ag==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -6,7 +6,7 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
|
|||
let(:admin) { create(:admin) }
|
||||
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
|
||||
let(:project) { create(:project, :repository, namespace: namespace) }
|
||||
let(:cluster) { project.create_cluster!(gcp_cluster_name: "gke-test-creation-1", gcp_project_id: 'gitlab-internal-153318', gcp_cluster_zone: 'us-central1-a', gcp_cluster_size: '1', project_namespace: 'aaa', gcp_machine_type: 'n1-standard-1')}
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
render_views
|
||||
|
||||
|
|
|
|||
|
|
@ -148,9 +148,18 @@ deploy_keys:
|
|||
- deploy_keys_projects
|
||||
- projects
|
||||
cluster:
|
||||
- project
|
||||
- cluster_projects
|
||||
- projects
|
||||
- user
|
||||
- service
|
||||
- provider_gcp
|
||||
- platform_kubernetes
|
||||
cluster_projects:
|
||||
- projects
|
||||
- clusters
|
||||
provider_gcp:
|
||||
- cluster
|
||||
platform_kubernetes:
|
||||
- cluster
|
||||
services:
|
||||
- project
|
||||
- service_hook
|
||||
|
|
@ -182,6 +191,7 @@ project:
|
|||
- tags
|
||||
- chat_services
|
||||
- cluster
|
||||
- cluster_project
|
||||
- creator
|
||||
- group
|
||||
- namespace
|
||||
|
|
|
|||
|
|
@ -313,30 +313,47 @@ Ci::PipelineSchedule:
|
|||
- deleted_at
|
||||
- created_at
|
||||
- updated_at
|
||||
Gcp::Cluster:
|
||||
Clusters::Cluster:
|
||||
- id
|
||||
- user_id
|
||||
- enabled
|
||||
- name
|
||||
- provider_type
|
||||
- platform_type
|
||||
- created_at
|
||||
- updated_at
|
||||
Clusters::Project:
|
||||
- id
|
||||
- project_id
|
||||
- user_id
|
||||
- service_id
|
||||
- enabled
|
||||
- cluster_id
|
||||
- created_at
|
||||
- updated_at
|
||||
Clusters::Providers::Gcp:
|
||||
- id
|
||||
- cluster_id
|
||||
- status
|
||||
- status_reason
|
||||
- project_namespace
|
||||
- gcp_project_id
|
||||
- zone
|
||||
- num_nodes
|
||||
- machine_type
|
||||
- operation_id
|
||||
- endpoint
|
||||
- encrypted_access_token
|
||||
- encrypted_access_token_iv
|
||||
- created_at
|
||||
- updated_at
|
||||
Clusters::Platforms::Kubernetes:
|
||||
- id
|
||||
- cluster_id
|
||||
- api_url
|
||||
- ca_cert
|
||||
- encrypted_kubernetes_token
|
||||
- encrypted_kubernetes_token_iv
|
||||
- namespace
|
||||
- username
|
||||
- encrypted_password
|
||||
- encrypted_password_iv
|
||||
- gcp_project_id
|
||||
- gcp_cluster_zone
|
||||
- gcp_cluster_name
|
||||
- gcp_cluster_size
|
||||
- gcp_machine_type
|
||||
- gcp_operation_id
|
||||
- encrypted_gcp_token
|
||||
- encrypted_gcp_token_iv
|
||||
- encrypted_token
|
||||
- encrypted_token_iv
|
||||
- created_at
|
||||
- updated_at
|
||||
DeployKey:
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ describe Gitlab::UsageData do
|
|||
deploy_keys
|
||||
deployments
|
||||
environments
|
||||
gcp_clusters
|
||||
gcp_clusters_enabled
|
||||
gcp_clusters_disabled
|
||||
clusters
|
||||
clusters_enabled
|
||||
clusters_disabled
|
||||
in_review_folder
|
||||
groups
|
||||
issues
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'post_migrate', '20171013104327_migrate_gcp_clusters_to_new_clusters_architectures.rb')
|
||||
|
||||
describe MigrateGcpClustersToNewClustersArchitectures, :migration do
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:service) { create(:kubernetes_service, project: project) }
|
||||
|
||||
context 'when cluster is being created' do
|
||||
let(:project_id) { project.id }
|
||||
let(:user_id) { user.id }
|
||||
let(:service_id) { service.id }
|
||||
let(:status) { 2 } # creating
|
||||
let(:gcp_cluster_size) { 1 }
|
||||
let(:created_at) { "'2017-10-17 20:24:02'" }
|
||||
let(:updated_at) { "'2017-10-17 20:28:44'" }
|
||||
let(:enabled) { true }
|
||||
let(:status_reason) { "''" }
|
||||
let(:project_namespace) { "'sample-app'" }
|
||||
let(:endpoint) { 'NULL' }
|
||||
let(:ca_cert) { 'NULL' }
|
||||
let(:encrypted_kubernetes_token) { 'NULL' }
|
||||
let(:encrypted_kubernetes_token_iv) { 'NULL' }
|
||||
let(:username) { 'NULL' }
|
||||
let(:encrypted_password) { 'NULL' }
|
||||
let(:encrypted_password_iv) { 'NULL' }
|
||||
let(:gcp_project_id) { "'gcp_project_id'" }
|
||||
let(:gcp_cluster_zone) { "'gcp_cluster_zone'" }
|
||||
let(:gcp_cluster_name) { "'gcp_cluster_name'" }
|
||||
let(:gcp_machine_type) { "'gcp_machine_type'" }
|
||||
let(:gcp_operation_id) { 'NULL' }
|
||||
let(:encrypted_gcp_token) { "'encrypted_gcp_token'" }
|
||||
let(:encrypted_gcp_token_iv) { "'encrypted_gcp_token_iv'" }
|
||||
|
||||
let(:cluster) { Clusters::Cluster.last }
|
||||
let(:cluster_id) { cluster.id }
|
||||
|
||||
before do
|
||||
ActiveRecord::Base.connection.execute <<-SQL
|
||||
INSERT INTO gcp_clusters (project_id, user_id, service_id, status, gcp_cluster_size, created_at, updated_at, enabled, status_reason, project_namespace, endpoint, ca_cert, encrypted_kubernetes_token, encrypted_kubernetes_token_iv, username, encrypted_password, encrypted_password_iv, gcp_project_id, gcp_cluster_zone, gcp_cluster_name, gcp_machine_type, gcp_operation_id, encrypted_gcp_token, encrypted_gcp_token_iv)
|
||||
VALUES (#{project_id}, #{user_id}, #{service_id}, #{status}, #{gcp_cluster_size}, #{created_at}, #{updated_at}, #{enabled}, #{status_reason}, #{project_namespace}, #{endpoint}, #{ca_cert}, #{encrypted_kubernetes_token}, #{encrypted_kubernetes_token_iv}, #{username}, #{encrypted_password}, #{encrypted_password_iv}, #{gcp_project_id}, #{gcp_cluster_zone}, #{gcp_cluster_name}, #{gcp_machine_type}, #{gcp_operation_id}, #{encrypted_gcp_token}, #{encrypted_gcp_token_iv});
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'correctly migrate to new clusters architectures' do
|
||||
migrate!
|
||||
|
||||
expect(Clusters::Cluster.count).to eq(1)
|
||||
expect(Clusters::Project.count).to eq(1)
|
||||
expect(Clusters::Providers::Gcp.count).to eq(1)
|
||||
expect(Clusters::Platforms::Kubernetes.count).to eq(1)
|
||||
|
||||
expect(cluster.user).to eq(user)
|
||||
expect(cluster.enabled).to be_truthy
|
||||
expect(cluster.name).to eq(gcp_cluster_name.delete!("'"))
|
||||
expect(cluster.provider_type).to eq('gcp')
|
||||
expect(cluster.platform_type).to eq('kubernetes')
|
||||
|
||||
expect(cluster.project).to eq(project)
|
||||
expect(project.cluster).to eq(cluster)
|
||||
|
||||
expect(cluster.provider_gcp.cluster).to eq(cluster)
|
||||
expect(cluster.provider_gcp.status).to eq(status)
|
||||
expect(cluster.provider_gcp.status_reason).to eq(tr(status_reason))
|
||||
expect(cluster.provider_gcp.gcp_project_id).to eq(tr(gcp_project_id))
|
||||
expect(cluster.provider_gcp.zone).to eq(tr(gcp_cluster_zone))
|
||||
expect(cluster.provider_gcp.num_nodes).to eq(gcp_cluster_size)
|
||||
expect(cluster.provider_gcp.machine_type).to eq(tr(gcp_machine_type))
|
||||
expect(cluster.provider_gcp.operation_id).to be_nil
|
||||
expect(cluster.provider_gcp.endpoint).to be_nil
|
||||
expect(cluster.provider_gcp.encrypted_access_token).to eq(tr(encrypted_gcp_token))
|
||||
expect(cluster.provider_gcp.encrypted_access_token_iv).to eq(tr(encrypted_gcp_token_iv))
|
||||
|
||||
expect(cluster.platform_kubernetes.cluster).to eq(cluster)
|
||||
expect(cluster.platform_kubernetes.api_url).to be_nil
|
||||
expect(cluster.platform_kubernetes.ca_cert).to be_nil
|
||||
expect(cluster.platform_kubernetes.namespace).to eq(tr(project_namespace))
|
||||
expect(cluster.platform_kubernetes.username).to be_nil
|
||||
expect(cluster.platform_kubernetes.encrypted_password).to be_nil
|
||||
expect(cluster.platform_kubernetes.encrypted_password_iv).to be_nil
|
||||
expect(cluster.platform_kubernetes.encrypted_token).to be_nil
|
||||
expect(cluster.platform_kubernetes.encrypted_token_iv).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cluster has been created' do
|
||||
let(:project_id) { project.id }
|
||||
let(:user_id) { user.id }
|
||||
let(:service_id) { service.id }
|
||||
let(:status) { 3 } # created
|
||||
let(:gcp_cluster_size) { 1 }
|
||||
let(:created_at) { "'2017-10-17 20:24:02'" }
|
||||
let(:updated_at) { "'2017-10-17 20:28:44'" }
|
||||
let(:enabled) { true }
|
||||
let(:status_reason) { "'general error'" }
|
||||
let(:project_namespace) { "'sample-app'" }
|
||||
let(:endpoint) { "'111.111.111.111'" }
|
||||
let(:ca_cert) { "'ca_cert'" }
|
||||
let(:encrypted_kubernetes_token) { "'encrypted_kubernetes_token'" }
|
||||
let(:encrypted_kubernetes_token_iv) { "'encrypted_kubernetes_token_iv'" }
|
||||
let(:username) { "'username'" }
|
||||
let(:encrypted_password) { "'encrypted_password'" }
|
||||
let(:encrypted_password_iv) { "'encrypted_password_iv'" }
|
||||
let(:gcp_project_id) { "'gcp_project_id'" }
|
||||
let(:gcp_cluster_zone) { "'gcp_cluster_zone'" }
|
||||
let(:gcp_cluster_name) { "'gcp_cluster_name'" }
|
||||
let(:gcp_machine_type) { "'gcp_machine_type'" }
|
||||
let(:gcp_operation_id) { "'gcp_operation_id'" }
|
||||
let(:encrypted_gcp_token) { "'encrypted_gcp_token'" }
|
||||
let(:encrypted_gcp_token_iv) { "'encrypted_gcp_token_iv'" }
|
||||
|
||||
let(:cluster) { Clusters::Cluster.last }
|
||||
let(:cluster_id) { cluster.id }
|
||||
|
||||
before do
|
||||
ActiveRecord::Base.connection.execute <<-SQL
|
||||
INSERT INTO gcp_clusters (project_id, user_id, service_id, status, gcp_cluster_size, created_at, updated_at, enabled, status_reason, project_namespace, endpoint, ca_cert, encrypted_kubernetes_token, encrypted_kubernetes_token_iv, username, encrypted_password, encrypted_password_iv, gcp_project_id, gcp_cluster_zone, gcp_cluster_name, gcp_machine_type, gcp_operation_id, encrypted_gcp_token, encrypted_gcp_token_iv)
|
||||
VALUES (#{project_id}, #{user_id}, #{service_id}, #{status}, #{gcp_cluster_size}, #{created_at}, #{updated_at}, #{enabled}, #{status_reason}, #{project_namespace}, #{endpoint}, #{ca_cert}, #{encrypted_kubernetes_token}, #{encrypted_kubernetes_token_iv}, #{username}, #{encrypted_password}, #{encrypted_password_iv}, #{gcp_project_id}, #{gcp_cluster_zone}, #{gcp_cluster_name}, #{gcp_machine_type}, #{gcp_operation_id}, #{encrypted_gcp_token}, #{encrypted_gcp_token_iv});
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'correctly migrate to new clusters architectures' do
|
||||
migrate!
|
||||
|
||||
expect(Clusters::Cluster.count).to eq(1)
|
||||
expect(Clusters::Project.count).to eq(1)
|
||||
expect(Clusters::Providers::Gcp.count).to eq(1)
|
||||
expect(Clusters::Platforms::Kubernetes.count).to eq(1)
|
||||
|
||||
expect(cluster.user).to eq(user)
|
||||
expect(cluster.enabled).to be_truthy
|
||||
expect(cluster.name).to eq(tr(gcp_cluster_name))
|
||||
expect(cluster.provider_type).to eq('gcp')
|
||||
expect(cluster.platform_type).to eq('kubernetes')
|
||||
|
||||
expect(cluster.project).to eq(project)
|
||||
expect(project.cluster).to eq(cluster)
|
||||
|
||||
expect(cluster.provider_gcp.cluster).to eq(cluster)
|
||||
expect(cluster.provider_gcp.status).to eq(status)
|
||||
expect(cluster.provider_gcp.status_reason).to eq(tr(status_reason))
|
||||
expect(cluster.provider_gcp.gcp_project_id).to eq(tr(gcp_project_id))
|
||||
expect(cluster.provider_gcp.zone).to eq(tr(gcp_cluster_zone))
|
||||
expect(cluster.provider_gcp.num_nodes).to eq(gcp_cluster_size)
|
||||
expect(cluster.provider_gcp.machine_type).to eq(tr(gcp_machine_type))
|
||||
expect(cluster.provider_gcp.operation_id).to eq(tr(gcp_operation_id))
|
||||
expect(cluster.provider_gcp.endpoint).to eq(tr(endpoint))
|
||||
expect(cluster.provider_gcp.encrypted_access_token).to eq(tr(encrypted_gcp_token))
|
||||
expect(cluster.provider_gcp.encrypted_access_token_iv).to eq(tr(encrypted_gcp_token_iv))
|
||||
|
||||
expect(cluster.platform_kubernetes.cluster).to eq(cluster)
|
||||
expect(cluster.platform_kubernetes.api_url).to eq('https://' + tr(endpoint))
|
||||
expect(cluster.platform_kubernetes.ca_cert).to eq(tr(ca_cert))
|
||||
expect(cluster.platform_kubernetes.namespace).to eq(tr(project_namespace))
|
||||
expect(cluster.platform_kubernetes.username).to eq(tr(username))
|
||||
expect(cluster.platform_kubernetes.encrypted_password).to eq(tr(encrypted_password))
|
||||
expect(cluster.platform_kubernetes.encrypted_password_iv).to eq(tr(encrypted_password_iv))
|
||||
expect(cluster.platform_kubernetes.encrypted_token).to eq(tr(encrypted_kubernetes_token))
|
||||
expect(cluster.platform_kubernetes.encrypted_token_iv).to eq(tr(encrypted_kubernetes_token_iv))
|
||||
end
|
||||
end
|
||||
|
||||
def tr(s)
|
||||
s.delete("'")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Cluster do
|
||||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to have_many(:projects) }
|
||||
it { is_expected.to have_one(:provider_gcp) }
|
||||
it { is_expected.to have_one(:platform_kubernetes) }
|
||||
it { is_expected.to delegate_method(:status).to(:provider) }
|
||||
it { is_expected.to delegate_method(:status_reason).to(:provider) }
|
||||
it { is_expected.to delegate_method(:status_name).to(:provider) }
|
||||
it { is_expected.to delegate_method(:on_creation?).to(:provider) }
|
||||
it { is_expected.to delegate_method(:update_kubernetes_integration!).to(:platform) }
|
||||
it { is_expected.to respond_to :project }
|
||||
|
||||
describe '.enabled' do
|
||||
subject { described_class.enabled }
|
||||
|
||||
let!(:cluster) { create(:cluster, enabled: true) }
|
||||
|
||||
before do
|
||||
create(:cluster, enabled: false)
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(cluster) }
|
||||
end
|
||||
|
||||
describe '.disabled' do
|
||||
subject { described_class.disabled }
|
||||
|
||||
let!(:cluster) { create(:cluster, enabled: false) }
|
||||
|
||||
before do
|
||||
create(:cluster, enabled: true)
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(cluster) }
|
||||
end
|
||||
|
||||
describe 'validation' do
|
||||
subject { cluster.valid? }
|
||||
|
||||
context 'when validates name' do
|
||||
context 'when provided by user' do
|
||||
let!(:cluster) { build(:cluster, :provided_by_user, name: name) }
|
||||
|
||||
context 'when name is empty' do
|
||||
let(:name) { '' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when name is nil' do
|
||||
let(:name) { nil }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when name is present' do
|
||||
let(:name) { 'cluster-name-1' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided by gcp' do
|
||||
let!(:cluster) { build(:cluster, :provided_by_gcp, name: name) }
|
||||
|
||||
context 'when name is shorter than 1' do
|
||||
let(:name) { '' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when name is longer than 63' do
|
||||
let(:name) { 'a' * 64 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when name includes invalid character' do
|
||||
let(:name) { '!!!!!!' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when name is present' do
|
||||
let(:name) { 'cluster-name-1' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when record is persisted' do
|
||||
let(:name) { 'cluster-name-1' }
|
||||
|
||||
before do
|
||||
cluster.save!
|
||||
end
|
||||
|
||||
context 'when name is changed' do
|
||||
before do
|
||||
cluster.name = 'new-cluster-name'
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when name is same' do
|
||||
before do
|
||||
cluster.name = name
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates restrict_modification' do
|
||||
context 'when creation is on going' do
|
||||
let!(:cluster) { create(:cluster, :providing_by_gcp) }
|
||||
|
||||
it { expect(cluster.update(enabled: false)).to be_falsey }
|
||||
end
|
||||
|
||||
context 'when creation is done' do
|
||||
let!(:cluster) { create(:cluster, :provided_by_gcp) }
|
||||
|
||||
it { expect(cluster.update(enabled: false)).to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider' do
|
||||
subject { cluster.provider }
|
||||
|
||||
context 'when provider is gcp' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
||||
|
||||
it 'returns a provider' do
|
||||
is_expected.to eq(cluster.provider_gcp)
|
||||
expect(subject.class.name.deconstantize).to eq(Clusters::Providers.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provider is user' do
|
||||
let(:cluster) { create(:cluster, :provided_by_user) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#platform' do
|
||||
subject { cluster.platform }
|
||||
|
||||
context 'when platform is kubernetes' do
|
||||
let(:cluster) { create(:cluster, :provided_by_user) }
|
||||
|
||||
it 'returns a platform' do
|
||||
is_expected.to eq(cluster.platform_kubernetes)
|
||||
expect(subject.class.name.deconstantize).to eq(Clusters::Platforms.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#first_project' do
|
||||
subject { cluster.first_project }
|
||||
|
||||
context 'when cluster belongs to a project' do
|
||||
let(:cluster) { create(:cluster, :project) }
|
||||
let(:project) { Clusters::Project.find_by_cluster_id(cluster.id).project }
|
||||
|
||||
it { is_expected.to eq(project) }
|
||||
end
|
||||
|
||||
context 'when cluster does not belong to projects' do
|
||||
let(:cluster) { create(:cluster) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do
|
||||
include KubernetesHelpers
|
||||
include ReactiveCachingHelpers
|
||||
|
||||
it { is_expected.to belong_to(:cluster) }
|
||||
it { is_expected.to respond_to :ca_pem }
|
||||
|
||||
describe 'before_validation' do
|
||||
context 'when namespace includes upper case' do
|
||||
let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
|
||||
let(:namespace) { 'ABC' }
|
||||
|
||||
it 'converts to lower case' do
|
||||
expect(kubernetes.namespace).to eq('abc')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validation' do
|
||||
subject { kubernetes.valid? }
|
||||
|
||||
context 'when validates namespace' do
|
||||
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: namespace) }
|
||||
|
||||
context 'when namespace is blank' do
|
||||
let(:namespace) { '' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when namespace is longer than 63' do
|
||||
let(:namespace) { 'a' * 64 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when namespace includes invalid character' do
|
||||
let(:namespace) { '!!!!!!' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when namespace is vaild' do
|
||||
let(:namespace) { 'namespace-123' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates api_url' do
|
||||
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
|
||||
|
||||
before do
|
||||
kubernetes.api_url = api_url
|
||||
end
|
||||
|
||||
context 'when api_url is invalid url' do
|
||||
let(:api_url) { '!!!!!!' }
|
||||
|
||||
it { expect(kubernetes.save).to be_falsey }
|
||||
end
|
||||
|
||||
context 'when api_url is nil' do
|
||||
let(:api_url) { nil }
|
||||
|
||||
it { expect(kubernetes.save).to be_falsey }
|
||||
end
|
||||
|
||||
context 'when api_url is valid url' do
|
||||
let(:api_url) { 'https://111.111.111.111' }
|
||||
|
||||
it { expect(kubernetes.save).to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates token' do
|
||||
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
|
||||
|
||||
before do
|
||||
kubernetes.token = token
|
||||
end
|
||||
|
||||
context 'when token is nil' do
|
||||
let(:token) { nil }
|
||||
|
||||
it { expect(kubernetes.save).to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after_save from Clusters::Cluster' do
|
||||
context 'when platform_kubernetes is being cerated' do
|
||||
let(:enabled) { true }
|
||||
let(:project) { create(:project) }
|
||||
let(:cluster) { build(:cluster, provider_type: :gcp, platform_type: :kubernetes, platform_kubernetes: platform, provider_gcp: provider, enabled: enabled, projects: [project]) }
|
||||
let(:platform) { build(:cluster_platform_kubernetes, :configured) }
|
||||
let(:provider) { build(:cluster_provider_gcp) }
|
||||
let(:kubernetes_service) { project.kubernetes_service }
|
||||
|
||||
it 'updates KubernetesService' do
|
||||
cluster.save!
|
||||
|
||||
expect(kubernetes_service.active).to eq(enabled)
|
||||
expect(kubernetes_service.api_url).to eq(platform.api_url)
|
||||
expect(kubernetes_service.namespace).to eq(platform.namespace)
|
||||
expect(kubernetes_service.ca_pem).to eq(platform.ca_cert)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when platform_kubernetes has been created' do
|
||||
let(:enabled) { false }
|
||||
let!(:project) { create(:project) }
|
||||
let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
let(:platform) { cluster.platform }
|
||||
let(:kubernetes_service) { project.kubernetes_service }
|
||||
|
||||
it 'updates KubernetesService' do
|
||||
cluster.update(enabled: enabled)
|
||||
|
||||
expect(kubernetes_service.active).to eq(enabled)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when kubernetes_service has been configured without cluster integration' do
|
||||
let!(:project) { create(:project) }
|
||||
let(:cluster) { build(:cluster, provider_type: :gcp, platform_type: :kubernetes, platform_kubernetes: platform, provider_gcp: provider, projects: [project]) }
|
||||
let(:platform) { build(:cluster_platform_kubernetes, :configured, api_url: 'https://111.111.111.111') }
|
||||
let(:provider) { build(:cluster_provider_gcp) }
|
||||
|
||||
before do
|
||||
create(:kubernetes_service, project: project)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { cluster.save! }.to raise_error('Kubernetes service already configured')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#actual_namespace' do
|
||||
subject { kubernetes.actual_namespace }
|
||||
|
||||
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
|
||||
let(:project) { cluster.project }
|
||||
let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
|
||||
|
||||
context 'when namespace is present' do
|
||||
let(:namespace) { 'namespace-123' }
|
||||
|
||||
it { is_expected.to eq(namespace) }
|
||||
end
|
||||
|
||||
context 'when namespace is not present' do
|
||||
let(:namespace) { nil }
|
||||
|
||||
it { is_expected.to eq("#{project.path}-#{project.id}") }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.namespace_for_project' do
|
||||
subject { described_class.namespace_for_project(project) }
|
||||
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it { is_expected.to eq("#{project.path}-#{project.id}") }
|
||||
end
|
||||
|
||||
describe '#default_namespace' do
|
||||
subject { kubernetes.default_namespace }
|
||||
|
||||
let(:kubernetes) { create(:cluster_platform_kubernetes, :configured) }
|
||||
|
||||
context 'when cluster belongs to a project' do
|
||||
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
it { is_expected.to eq("#{project.path}-#{project.id}") }
|
||||
end
|
||||
|
||||
context 'when cluster belongs to nothing' do
|
||||
let!(:cluster) { create(:cluster, platform_kubernetes: kubernetes) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Project do
|
||||
it { is_expected.to belong_to(:cluster) }
|
||||
it { is_expected.to belong_to(:project) }
|
||||
end
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Providers::Gcp do
|
||||
it { is_expected.to belong_to(:cluster) }
|
||||
it { is_expected.to validate_presence_of(:zone) }
|
||||
|
||||
describe 'default_value_for' do
|
||||
let(:gcp) { build(:cluster_provider_gcp) }
|
||||
|
||||
it "has default value" do
|
||||
expect(gcp.zone).to eq('us-central1-a')
|
||||
expect(gcp.num_nodes).to eq(3)
|
||||
expect(gcp.machine_type).to eq('n1-standard-4')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validation' do
|
||||
subject { gcp.valid? }
|
||||
|
||||
context 'when validates gcp_project_id' do
|
||||
let(:gcp) { build(:cluster_provider_gcp, gcp_project_id: gcp_project_id) }
|
||||
|
||||
context 'when gcp_project_id is shorter than 1' do
|
||||
let(:gcp_project_id) { '' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when gcp_project_id is longer than 63' do
|
||||
let(:gcp_project_id) { 'a' * 64 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when gcp_project_id includes invalid character' do
|
||||
let(:gcp_project_id) { '!!!!!!' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when gcp_project_id is valid' do
|
||||
let(:gcp_project_id) { 'gcp-project-1' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates num_nodes' do
|
||||
let(:gcp) { build(:cluster_provider_gcp, num_nodes: num_nodes) }
|
||||
|
||||
context 'when num_nodes is string' do
|
||||
let(:num_nodes) { 'A3' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when num_nodes is nil' do
|
||||
let(:num_nodes) { nil }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when num_nodes is smaller than 1' do
|
||||
let(:num_nodes) { 0 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when num_nodes is valid' do
|
||||
let(:num_nodes) { 3 }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#state_machine' do
|
||||
context 'when any => [:created]' do
|
||||
let(:gcp) { build(:cluster_provider_gcp, :creating) }
|
||||
|
||||
before do
|
||||
gcp.make_created
|
||||
end
|
||||
|
||||
it 'nullify access_token and operation_id' do
|
||||
expect(gcp.access_token).to be_nil
|
||||
expect(gcp.operation_id).to be_nil
|
||||
expect(gcp).to be_created
|
||||
end
|
||||
end
|
||||
|
||||
context 'when any => [:creating]' do
|
||||
let(:gcp) { build(:cluster_provider_gcp) }
|
||||
|
||||
context 'when operation_id is present' do
|
||||
let(:operation_id) { 'operation-xxx' }
|
||||
|
||||
before do
|
||||
gcp.make_creating(operation_id)
|
||||
end
|
||||
|
||||
it 'sets operation_id' do
|
||||
expect(gcp.operation_id).to eq(operation_id)
|
||||
expect(gcp).to be_creating
|
||||
end
|
||||
end
|
||||
|
||||
context 'when operation_id is nil' do
|
||||
let(:operation_id) { nil }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { gcp.make_creating(operation_id) }
|
||||
.to raise_error('operation_id is required')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when any => [:errored]' do
|
||||
let(:gcp) { build(:cluster_provider_gcp, :creating) }
|
||||
let(:status_reason) { 'err msg' }
|
||||
|
||||
it 'nullify access_token and operation_id' do
|
||||
gcp.make_errored(status_reason)
|
||||
|
||||
expect(gcp.access_token).to be_nil
|
||||
expect(gcp.operation_id).to be_nil
|
||||
expect(gcp.status_reason).to eq(status_reason)
|
||||
expect(gcp).to be_errored
|
||||
end
|
||||
|
||||
context 'when status_reason is nil' do
|
||||
let(:gcp) { build(:cluster_provider_gcp, :errored) }
|
||||
|
||||
it 'does not set status_reason' do
|
||||
gcp.make_errored(nil)
|
||||
|
||||
expect(gcp.status_reason).not_to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#on_creation?' do
|
||||
subject { gcp.on_creation? }
|
||||
|
||||
context 'when status is creating' do
|
||||
let(:gcp) { create(:cluster_provider_gcp, :creating) }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when status is created' do
|
||||
let(:gcp) { create(:cluster_provider_gcp, :created) }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#api_client' do
|
||||
subject { gcp.api_client }
|
||||
|
||||
context 'when status is creating' do
|
||||
let(:gcp) { build(:cluster_provider_gcp, :creating) }
|
||||
|
||||
it 'returns Cloud Platform API clinet' do
|
||||
expect(subject).to be_an_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
expect(subject.access_token).to eq(gcp.access_token)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is created' do
|
||||
let(:gcp) { build(:cluster_provider_gcp, :created) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'when status is errored' do
|
||||
let(:gcp) { build(:cluster_provider_gcp, :errored) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,264 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gcp::Cluster do
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to belong_to(:service) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:gcp_cluster_zone) }
|
||||
|
||||
describe '.enabled' do
|
||||
subject { described_class.enabled }
|
||||
|
||||
let!(:cluster) { create(:gcp_cluster, enabled: true) }
|
||||
|
||||
before do
|
||||
create(:gcp_cluster, enabled: false)
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(cluster) }
|
||||
end
|
||||
|
||||
describe '.disabled' do
|
||||
subject { described_class.disabled }
|
||||
|
||||
let!(:cluster) { create(:gcp_cluster, enabled: false) }
|
||||
|
||||
before do
|
||||
create(:gcp_cluster, enabled: true)
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(cluster) }
|
||||
end
|
||||
|
||||
describe '#default_value_for' do
|
||||
let(:cluster) { described_class.new }
|
||||
|
||||
it { expect(cluster.gcp_cluster_zone).to eq('us-central1-a') }
|
||||
it { expect(cluster.gcp_cluster_size).to eq(3) }
|
||||
it { expect(cluster.gcp_machine_type).to eq('n1-standard-4') }
|
||||
end
|
||||
|
||||
describe '#validates' do
|
||||
subject { cluster.valid? }
|
||||
|
||||
context 'when validates gcp_project_id' do
|
||||
let(:cluster) { build(:gcp_cluster, gcp_project_id: gcp_project_id) }
|
||||
|
||||
context 'when valid' do
|
||||
let(:gcp_project_id) { 'gcp-project-12345' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when empty' do
|
||||
let(:gcp_project_id) { '' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when too long' do
|
||||
let(:gcp_project_id) { 'A' * 64 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when includes abnormal character' do
|
||||
let(:gcp_project_id) { '!!!!!!' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates gcp_cluster_name' do
|
||||
let(:cluster) { build(:gcp_cluster, gcp_cluster_name: gcp_cluster_name) }
|
||||
|
||||
context 'when valid' do
|
||||
let(:gcp_cluster_name) { 'test-cluster' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when empty' do
|
||||
let(:gcp_cluster_name) { '' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when too long' do
|
||||
let(:gcp_cluster_name) { 'A' * 64 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when includes abnormal character' do
|
||||
let(:gcp_cluster_name) { '!!!!!!' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates gcp_cluster_size' do
|
||||
let(:cluster) { build(:gcp_cluster, gcp_cluster_size: gcp_cluster_size) }
|
||||
|
||||
context 'when valid' do
|
||||
let(:gcp_cluster_size) { 1 }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when zero' do
|
||||
let(:gcp_cluster_size) { 0 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates project_namespace' do
|
||||
let(:cluster) { build(:gcp_cluster, project_namespace: project_namespace) }
|
||||
|
||||
context 'when valid' do
|
||||
let(:project_namespace) { 'default-namespace' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when empty' do
|
||||
let(:project_namespace) { '' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when too long' do
|
||||
let(:project_namespace) { 'A' * 64 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when includes abnormal character' do
|
||||
let(:project_namespace) { '!!!!!!' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates restrict_modification' do
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
|
||||
before do
|
||||
cluster.make_creating!
|
||||
end
|
||||
|
||||
context 'when created' do
|
||||
before do
|
||||
cluster.make_created!
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when creating' do
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#state_machine' do
|
||||
let(:cluster) { build(:gcp_cluster) }
|
||||
|
||||
context 'when transits to created state' do
|
||||
before do
|
||||
cluster.gcp_token = 'tmp'
|
||||
cluster.gcp_operation_id = 'tmp'
|
||||
cluster.make_created!
|
||||
end
|
||||
|
||||
it 'nullify gcp_token and gcp_operation_id' do
|
||||
expect(cluster.gcp_token).to be_nil
|
||||
expect(cluster.gcp_operation_id).to be_nil
|
||||
expect(cluster).to be_created
|
||||
end
|
||||
end
|
||||
|
||||
context 'when transits to errored state' do
|
||||
let(:reason) { 'something wrong' }
|
||||
|
||||
before do
|
||||
cluster.make_errored!(reason)
|
||||
end
|
||||
|
||||
it 'sets status_reason' do
|
||||
expect(cluster.status_reason).to eq(reason)
|
||||
expect(cluster).to be_errored
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#project_namespace_placeholder' do
|
||||
subject { cluster.project_namespace_placeholder }
|
||||
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
|
||||
it 'returns a placeholder' do
|
||||
is_expected.to eq("#{cluster.project.path}-#{cluster.project.id}")
|
||||
end
|
||||
end
|
||||
|
||||
describe '#on_creation?' do
|
||||
subject { cluster.on_creation? }
|
||||
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
|
||||
context 'when status is creating' do
|
||||
before do
|
||||
cluster.make_creating!
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when status is created' do
|
||||
before do
|
||||
cluster.make_created!
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#api_url' do
|
||||
subject { cluster.api_url }
|
||||
|
||||
let(:cluster) { create(:gcp_cluster, :created_on_gke) }
|
||||
let(:api_url) { 'https://' + cluster.endpoint }
|
||||
|
||||
it { is_expected.to eq(api_url) }
|
||||
end
|
||||
|
||||
describe '#restrict_modification' do
|
||||
subject { cluster.restrict_modification }
|
||||
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
|
||||
context 'when status is created' do
|
||||
before do
|
||||
cluster.make_created!
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when status is creating' do
|
||||
before do
|
||||
cluster.make_creating!
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
|
||||
it 'sets error' do
|
||||
is_expected.to be_falsey
|
||||
expect(cluster.errors).not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -145,7 +145,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
|
|||
let(:discovery_url) { 'https://kubernetes.example.com/api/v1' }
|
||||
|
||||
before do
|
||||
stub_kubeclient_discover
|
||||
stub_kubeclient_discover(service.api_url)
|
||||
end
|
||||
|
||||
context 'with path prefix in api_url' do
|
||||
|
|
@ -153,7 +153,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
|
|||
|
||||
it 'tests with the prefix' do
|
||||
service.api_url = 'https://kubernetes.example.com/prefix'
|
||||
stub_kubeclient_discover
|
||||
stub_kubeclient_discover(service.api_url)
|
||||
|
||||
expect(service.test[:success]).to be_truthy
|
||||
expect(WebMock).to have_requested(:get, discovery_url).once
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gcp::ClusterPolicy, :models do
|
||||
set(:project) { create(:project) }
|
||||
set(:cluster) { create(:gcp_cluster, project: project) }
|
||||
describe Clusters::ClusterPolicy, :models do
|
||||
let(:cluster) { create(:cluster, :project) }
|
||||
let(:project) { cluster.project }
|
||||
let(:user) { create(:user) }
|
||||
let(:policy) { described_class.new(user, cluster) }
|
||||
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gcp::ClusterPresenter do
|
||||
let(:project) { create(:project) }
|
||||
let(:cluster) { create(:gcp_cluster, project: project) }
|
||||
describe Clusters::ClusterPresenter do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
||||
|
||||
subject(:presenter) do
|
||||
described_class.new(cluster)
|
||||
|
|
@ -22,14 +21,14 @@ describe Gcp::ClusterPresenter do
|
|||
end
|
||||
|
||||
it 'forwards missing methods to cluster' do
|
||||
expect(presenter.gcp_cluster_zone).to eq(cluster.gcp_cluster_zone)
|
||||
expect(presenter.status).to eq(cluster.status)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#gke_cluster_url' do
|
||||
subject { described_class.new(cluster).gke_cluster_url }
|
||||
|
||||
it { is_expected.to include(cluster.gcp_cluster_zone) }
|
||||
it { is_expected.to include(cluster.gcp_cluster_name) }
|
||||
it { is_expected.to include(cluster.provider.zone) }
|
||||
it { is_expected.to include(cluster.name) }
|
||||
end
|
||||
end
|
||||
|
|
@ -1,22 +1,38 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ClusterEntity do
|
||||
set(:cluster) { create(:gcp_cluster, :errored) }
|
||||
let(:request) { double('request') }
|
||||
|
||||
let(:entity) do
|
||||
described_class.new(cluster)
|
||||
end
|
||||
|
||||
describe '#as_json' do
|
||||
subject { entity.as_json }
|
||||
subject { described_class.new(cluster).as_json }
|
||||
|
||||
it 'contains status' do
|
||||
expect(subject[:status]).to eq(:errored)
|
||||
context 'when provider type is gcp' do
|
||||
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
|
||||
|
||||
context 'when status is creating' do
|
||||
let(:provider) { create(:cluster_provider_gcp, :creating) }
|
||||
|
||||
it 'has corresponded data' do
|
||||
expect(subject[:status]).to eq(:creating)
|
||||
expect(subject[:status_reason]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is errored' do
|
||||
let(:provider) { create(:cluster_provider_gcp, :errored) }
|
||||
|
||||
it 'has corresponded data' do
|
||||
expect(subject[:status]).to eq(:errored)
|
||||
expect(subject[:status_reason]).to eq(provider.status_reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'contains status reason' do
|
||||
expect(subject[:status_reason]).to eq('general error')
|
||||
context 'when provider type is user' do
|
||||
let(:cluster) { create(:cluster, provider_type: :user) }
|
||||
|
||||
it 'has nil' do
|
||||
expect(subject[:status]).to be_nil
|
||||
expect(subject[:status_reason]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ClusterSerializer do
|
||||
let(:serializer) do
|
||||
described_class.new
|
||||
end
|
||||
|
||||
describe '#represent_status' do
|
||||
subject { serializer.represent_status(resource) }
|
||||
subject { described_class.new.represent_status(cluster) }
|
||||
|
||||
context 'when represents only status' do
|
||||
let(:resource) { create(:gcp_cluster, :errored) }
|
||||
context 'when provider type is gcp' do
|
||||
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
|
||||
let(:provider) { create(:cluster_provider_gcp, :errored) }
|
||||
|
||||
it 'serializes only status' do
|
||||
expect(subject.keys).to contain_exactly(:status, :status_reason)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provider type is user' do
|
||||
let(:cluster) { create(:cluster, provider_type: :user) }
|
||||
|
||||
it 'serializes only status' do
|
||||
expect(subject.keys).to contain_exactly(:status, :status_reason)
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Ci::CreateClusterService do
|
||||
describe '#execute' do
|
||||
let(:access_token) { 'xxx' }
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:result) { described_class.new(project, user, params).execute(access_token) }
|
||||
|
||||
context 'when correct params' do
|
||||
let(:params) do
|
||||
{
|
||||
gcp_project_id: 'gcp-project',
|
||||
gcp_cluster_name: 'test-cluster',
|
||||
gcp_cluster_zone: 'us-central1-a',
|
||||
gcp_cluster_size: 1
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a cluster object' do
|
||||
expect(ClusterProvisionWorker).to receive(:perform_async)
|
||||
expect { result }.to change { Gcp::Cluster.count }.by(1)
|
||||
expect(result.gcp_project_id).to eq('gcp-project')
|
||||
expect(result.gcp_cluster_name).to eq('test-cluster')
|
||||
expect(result.gcp_cluster_zone).to eq('us-central1-a')
|
||||
expect(result.gcp_cluster_size).to eq(1)
|
||||
expect(result.gcp_token).to eq(access_token)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid params' do
|
||||
let(:params) do
|
||||
{
|
||||
gcp_project_id: 'gcp-project',
|
||||
gcp_cluster_name: 'test-cluster',
|
||||
gcp_cluster_zone: 'us-central1-a',
|
||||
gcp_cluster_size: 'ABC'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns an error' do
|
||||
expect(ClusterProvisionWorker).not_to receive(:perform_async)
|
||||
expect { result }.to change { Gcp::Cluster.count }.by(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'google/apis'
|
||||
|
||||
describe Ci::FetchGcpOperationService do
|
||||
describe '#execute' do
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
let(:operation) { double }
|
||||
|
||||
context 'when suceeded' do
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:projects_zones_operations).and_return(operation)
|
||||
end
|
||||
|
||||
it 'fetch the gcp operaion' do
|
||||
expect { |b| described_class.new.execute(cluster, &b) }
|
||||
.to yield_with_args(operation)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when raises an error' do
|
||||
let(:error) { Google::Apis::ServerError.new('a') }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:projects_zones_operations).and_raise(error)
|
||||
end
|
||||
|
||||
it 'sets an error to cluster object' do
|
||||
expect { |b| described_class.new.execute(cluster, &b) }
|
||||
.not_to yield_with_args
|
||||
expect(cluster.reload).to be_errored
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Ci::FinalizeClusterCreationService do
|
||||
describe '#execute' do
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
let(:result) { described_class.new.execute(cluster) }
|
||||
|
||||
context 'when suceeded to get cluster from api' do
|
||||
let(:gke_cluster) { double }
|
||||
|
||||
before do
|
||||
allow(gke_cluster).to receive(:endpoint).and_return('111.111.111.111')
|
||||
allow(gke_cluster).to receive(:master_auth).and_return(spy)
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:projects_zones_clusters_get).and_return(gke_cluster)
|
||||
end
|
||||
|
||||
context 'when suceeded to get kubernetes token' do
|
||||
let(:kubernetes_token) { 'abc' }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Ci::FetchKubernetesTokenService)
|
||||
.to receive(:execute).and_return(kubernetes_token)
|
||||
end
|
||||
|
||||
it 'executes integration cluster' do
|
||||
expect_any_instance_of(Ci::IntegrateClusterService).to receive(:execute)
|
||||
described_class.new.execute(cluster)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed to get kubernetes token' do
|
||||
before do
|
||||
allow_any_instance_of(Ci::FetchKubernetesTokenService)
|
||||
.to receive(:execute).and_return(nil)
|
||||
end
|
||||
|
||||
it 'sets an error to cluster object' do
|
||||
described_class.new.execute(cluster)
|
||||
|
||||
expect(cluster.reload).to be_errored
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed to get cluster from api' do
|
||||
let(:error) { Google::Apis::ServerError.new('a') }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:projects_zones_clusters_get).and_raise(error)
|
||||
end
|
||||
|
||||
it 'sets an error to cluster object' do
|
||||
described_class.new.execute(cluster)
|
||||
|
||||
expect(cluster.reload).to be_errored
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Ci::IntegrateClusterService do
|
||||
describe '#execute' do
|
||||
let(:cluster) { create(:gcp_cluster, :custom_project_namespace) }
|
||||
let(:endpoint) { '123.123.123.123' }
|
||||
let(:ca_cert) { 'ca_cert_xxx' }
|
||||
let(:token) { 'token_xxx' }
|
||||
let(:username) { 'username_xxx' }
|
||||
let(:password) { 'password_xxx' }
|
||||
|
||||
before do
|
||||
described_class
|
||||
.new.execute(cluster, endpoint, ca_cert, token, username, password)
|
||||
|
||||
cluster.reload
|
||||
end
|
||||
|
||||
context 'when correct params' do
|
||||
it 'creates a cluster object' do
|
||||
expect(cluster.endpoint).to eq(endpoint)
|
||||
expect(cluster.ca_cert).to eq(ca_cert)
|
||||
expect(cluster.kubernetes_token).to eq(token)
|
||||
expect(cluster.username).to eq(username)
|
||||
expect(cluster.password).to eq(password)
|
||||
expect(cluster.service.active).to be_truthy
|
||||
expect(cluster.service.api_url).to eq(cluster.api_url)
|
||||
expect(cluster.service.ca_pem).to eq(ca_cert)
|
||||
expect(cluster.service.namespace).to eq(cluster.project_namespace)
|
||||
expect(cluster.service.token).to eq(token)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid params' do
|
||||
let(:endpoint) { nil }
|
||||
|
||||
it 'sets an error to cluster object' do
|
||||
expect(cluster).to be_errored
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Ci::ProvisionClusterService do
|
||||
describe '#execute' do
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
let(:operation) { spy }
|
||||
|
||||
shared_examples 'error' do
|
||||
it 'sets an error to cluster object' do
|
||||
described_class.new.execute(cluster)
|
||||
|
||||
expect(cluster.reload).to be_errored
|
||||
end
|
||||
end
|
||||
|
||||
context 'when suceeded to request provision' do
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:projects_zones_clusters_create).and_return(operation)
|
||||
end
|
||||
|
||||
context 'when operation status is RUNNING' do
|
||||
before do
|
||||
allow(operation).to receive(:status).and_return('RUNNING')
|
||||
end
|
||||
|
||||
context 'when suceeded to parse gcp operation id' do
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:parse_operation_id).and_return('operation-123')
|
||||
end
|
||||
|
||||
context 'when cluster status is scheduled' do
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:parse_operation_id).and_return('operation-123')
|
||||
end
|
||||
|
||||
it 'schedules a worker for status minitoring' do
|
||||
expect(WaitForClusterCreationWorker).to receive(:perform_in)
|
||||
|
||||
described_class.new.execute(cluster)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cluster status is creating' do
|
||||
before do
|
||||
cluster.make_creating!
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed to parse gcp operation id' do
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:parse_operation_id).and_return(nil)
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when operation status is others' do
|
||||
before do
|
||||
allow(operation).to receive(:status).and_return('others')
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed to request provision' do
|
||||
let(:error) { Google::Apis::ServerError.new('a') }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:projects_zones_clusters_create).and_raise(error)
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Ci::UpdateClusterService do
|
||||
describe '#execute' do
|
||||
let(:cluster) { create(:gcp_cluster, :created_on_gke, :with_kubernetes_service) }
|
||||
|
||||
before do
|
||||
described_class.new(cluster.project, cluster.user, params).execute(cluster)
|
||||
|
||||
cluster.reload
|
||||
end
|
||||
|
||||
context 'when correct params' do
|
||||
context 'when enabled is true' do
|
||||
let(:params) { { 'enabled' => 'true' } }
|
||||
|
||||
it 'enables cluster and overwrite kubernetes service' do
|
||||
expect(cluster.enabled).to be_truthy
|
||||
expect(cluster.service.active).to be_truthy
|
||||
expect(cluster.service.api_url).to eq(cluster.api_url)
|
||||
expect(cluster.service.ca_pem).to eq(cluster.ca_cert)
|
||||
expect(cluster.service.namespace).to eq(cluster.project_namespace)
|
||||
expect(cluster.service.token).to eq(cluster.kubernetes_token)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when enabled is false' do
|
||||
let(:params) { { 'enabled' => 'false' } }
|
||||
|
||||
it 'disables cluster and kubernetes service' do
|
||||
expect(cluster.enabled).to be_falsy
|
||||
expect(cluster.service.active).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::CreateService do
|
||||
let(:access_token) { 'xxx' }
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:result) { described_class.new(project, user, params).execute(access_token) }
|
||||
|
||||
context 'when provider is gcp' do
|
||||
context 'when correct params' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'test-cluster',
|
||||
provider_type: :gcp,
|
||||
provider_gcp_attributes: {
|
||||
gcp_project_id: 'gcp-project',
|
||||
zone: 'us-central1-a',
|
||||
num_nodes: 1,
|
||||
machine_type: 'machine_type-a'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a cluster object and performs a worker' do
|
||||
expect(ClusterProvisionWorker).to receive(:perform_async)
|
||||
|
||||
expect { result }
|
||||
.to change { Clusters::Cluster.count }.by(1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(1)
|
||||
|
||||
expect(result.name).to eq('test-cluster')
|
||||
expect(result.user).to eq(user)
|
||||
expect(result.project).to eq(project)
|
||||
expect(result.provider.gcp_project_id).to eq('gcp-project')
|
||||
expect(result.provider.zone).to eq('us-central1-a')
|
||||
expect(result.provider.num_nodes).to eq(1)
|
||||
expect(result.provider.machine_type).to eq('machine_type-a')
|
||||
expect(result.provider.access_token).to eq(access_token)
|
||||
expect(result.platform).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid params' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'test-cluster',
|
||||
provider_type: :gcp,
|
||||
provider_gcp_attributes: {
|
||||
gcp_project_id: '!!!!!!!',
|
||||
zone: 'us-central1-a',
|
||||
num_nodes: 1,
|
||||
machine_type: 'machine_type-a'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns an error' do
|
||||
expect(ClusterProvisionWorker).not_to receive(:perform_async)
|
||||
expect { result }.to change { Clusters::Cluster.count }.by(0)
|
||||
expect(result.errors[:"provider_gcp.gcp_project_id"]).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Gcp::FetchOperationService do
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
|
||||
describe '#execute' do
|
||||
let(:provider) { create(:cluster_provider_gcp, :creating) }
|
||||
let(:gcp_project_id) { provider.gcp_project_id }
|
||||
let(:zone) { provider.zone }
|
||||
let(:operation_id) { provider.operation_id }
|
||||
|
||||
shared_examples 'success' do
|
||||
it 'yields' do
|
||||
expect { |b| described_class.new.execute(provider, &b) }
|
||||
.to yield_with_args
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'error' do
|
||||
it 'sets an error to provider object' do
|
||||
expect { |b| described_class.new.execute(provider, &b) }
|
||||
.not_to yield_with_args
|
||||
expect(provider.reload).to be_errored
|
||||
end
|
||||
end
|
||||
|
||||
context 'when suceeded to fetch operation' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_operation(gcp_project_id, zone, operation_id)
|
||||
end
|
||||
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
|
||||
context 'when Internal Server Error happened' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_operation_error(gcp_project_id, zone, operation_id)
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Gcp::FinalizeCreationService do
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
include KubernetesHelpers
|
||||
|
||||
describe '#execute' do
|
||||
let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
|
||||
let(:provider) { cluster.provider }
|
||||
let(:platform) { cluster.platform }
|
||||
let(:gcp_project_id) { provider.gcp_project_id }
|
||||
let(:zone) { provider.zone }
|
||||
let(:cluster_name) { cluster.name }
|
||||
|
||||
shared_examples 'success' do
|
||||
it 'configures provider and kubernetes' do
|
||||
described_class.new.execute(provider)
|
||||
|
||||
expect(provider).to be_created
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'error' do
|
||||
it 'sets an error to provider object' do
|
||||
described_class.new.execute(provider)
|
||||
|
||||
expect(provider.reload).to be_errored
|
||||
end
|
||||
end
|
||||
|
||||
context 'when suceeded to fetch gke cluster info' do
|
||||
let(:endpoint) { '111.111.111.111' }
|
||||
let(:api_url) { 'https://' + endpoint }
|
||||
let(:username) { 'sample-username' }
|
||||
let(:password) { 'sample-password' }
|
||||
|
||||
before do
|
||||
stub_cloud_platform_get_zone_cluster(
|
||||
gcp_project_id, zone, cluster_name,
|
||||
{
|
||||
endpoint: endpoint,
|
||||
username: username,
|
||||
password: password
|
||||
}
|
||||
)
|
||||
|
||||
stub_kubeclient_discover(api_url)
|
||||
end
|
||||
|
||||
context 'when suceeded to fetch kuberenetes token' do
|
||||
let(:token) { 'sample-token' }
|
||||
|
||||
before do
|
||||
stub_kubeclient_get_secrets(
|
||||
api_url,
|
||||
{
|
||||
token: Base64.encode64(token)
|
||||
} )
|
||||
end
|
||||
|
||||
it_behaves_like 'success'
|
||||
|
||||
it 'has corresponded data' do
|
||||
described_class.new.execute(provider)
|
||||
cluster.reload
|
||||
provider.reload
|
||||
platform.reload
|
||||
|
||||
expect(provider.endpoint).to eq(endpoint)
|
||||
expect(platform.api_url).to eq(api_url)
|
||||
expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert))
|
||||
expect(platform.username).to eq(username)
|
||||
expect(platform.password).to eq(password)
|
||||
expect(platform.token).to eq(token)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when default-token is not found' do
|
||||
before do
|
||||
stub_kubeclient_get_secrets(api_url, metadata_name: 'aaaa')
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
|
||||
context 'when token is empty' do
|
||||
before do
|
||||
stub_kubeclient_get_secrets(api_url, token: '')
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
|
||||
context 'when failed to fetch kuberenetes token' do
|
||||
before do
|
||||
stub_kubeclient_get_secrets_error(api_url)
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed to fetch gke cluster info' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name)
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Gcp::ProvisionService do
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
|
||||
describe '#execute' do
|
||||
let(:provider) { create(:cluster_provider_gcp, :scheduled) }
|
||||
let(:gcp_project_id) { provider.gcp_project_id }
|
||||
let(:zone) { provider.zone }
|
||||
|
||||
shared_examples 'success' do
|
||||
it 'schedules a worker for status minitoring' do
|
||||
expect(WaitForClusterCreationWorker).to receive(:perform_in)
|
||||
|
||||
described_class.new.execute(provider)
|
||||
|
||||
expect(provider.reload).to be_creating
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'error' do
|
||||
it 'sets an error to provider object' do
|
||||
described_class.new.execute(provider)
|
||||
|
||||
expect(provider.reload).to be_errored
|
||||
end
|
||||
end
|
||||
|
||||
context 'when suceeded to request provision' do
|
||||
before do
|
||||
stub_cloud_platform_create_cluster(gcp_project_id, zone)
|
||||
end
|
||||
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
|
||||
context 'when operation status is unexpected' do
|
||||
before do
|
||||
stub_cloud_platform_create_cluster(
|
||||
gcp_project_id, zone,
|
||||
{
|
||||
"status": 'unexpected'
|
||||
} )
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
|
||||
context 'when selfLink is unexpected' do
|
||||
before do
|
||||
stub_cloud_platform_create_cluster(
|
||||
gcp_project_id, zone,
|
||||
{
|
||||
"selfLink": 'unexpected'
|
||||
})
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
|
||||
context 'when Internal Server Error happened' do
|
||||
before do
|
||||
stub_cloud_platform_create_cluster_error(gcp_project_id, zone)
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Gcp::VerifyProvisionStatusService do
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
|
||||
describe '#execute' do
|
||||
let(:provider) { create(:cluster_provider_gcp, :creating) }
|
||||
let(:gcp_project_id) { provider.gcp_project_id }
|
||||
let(:zone) { provider.zone }
|
||||
let(:operation_id) { provider.operation_id }
|
||||
|
||||
shared_examples 'continue_creation' do
|
||||
it 'schedules a worker for status minitoring' do
|
||||
expect(WaitForClusterCreationWorker).to receive(:perform_in)
|
||||
|
||||
described_class.new.execute(provider)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'finalize_creation' do
|
||||
it 'schedules a worker for status minitoring' do
|
||||
expect_any_instance_of(Clusters::Gcp::FinalizeCreationService).to receive(:execute)
|
||||
|
||||
described_class.new.execute(provider)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'error' do
|
||||
it 'sets an error to provider object' do
|
||||
described_class.new.execute(provider)
|
||||
|
||||
expect(provider.reload).to be_errored
|
||||
end
|
||||
end
|
||||
|
||||
context 'when operation status is RUNNING' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_operation(
|
||||
gcp_project_id, zone, operation_id,
|
||||
{
|
||||
"status": 'RUNNING',
|
||||
"startTime": 1.minute.ago.strftime("%FT%TZ")
|
||||
} )
|
||||
end
|
||||
|
||||
it_behaves_like 'continue_creation'
|
||||
|
||||
context 'when cluster creation time exceeds timeout' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_operation(
|
||||
gcp_project_id, zone, operation_id,
|
||||
{
|
||||
"status": 'RUNNING',
|
||||
"startTime": 30.minutes.ago.strftime("%FT%TZ")
|
||||
} )
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when operation status is PENDING' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_operation(
|
||||
gcp_project_id, zone, operation_id,
|
||||
{
|
||||
"status": 'PENDING',
|
||||
"startTime": 1.minute.ago.strftime("%FT%TZ")
|
||||
} )
|
||||
end
|
||||
|
||||
it_behaves_like 'continue_creation'
|
||||
end
|
||||
|
||||
context 'when operation status is DONE' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_operation(
|
||||
gcp_project_id, zone, operation_id,
|
||||
{
|
||||
"status": 'DONE'
|
||||
} )
|
||||
end
|
||||
|
||||
it_behaves_like 'finalize_creation'
|
||||
end
|
||||
|
||||
context 'when operation status is unexpected' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_operation(
|
||||
gcp_project_id, zone, operation_id,
|
||||
{
|
||||
"status": 'unexpected'
|
||||
} )
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
|
||||
context 'when failed to get operation status' do
|
||||
before do
|
||||
stub_cloud_platform_get_zone_operation_error(gcp_project_id, zone, operation_id)
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Clusters::UpdateService do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(cluster.project, cluster.user, params).execute(cluster) }
|
||||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_user) }
|
||||
|
||||
context 'when correct params' do
|
||||
context 'when enabled is true' do
|
||||
let(:params) { { enabled: true } }
|
||||
|
||||
it 'enables cluster' do
|
||||
is_expected.to eq(true)
|
||||
expect(cluster.enabled).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when enabled is false' do
|
||||
let(:params) { { enabled: false } }
|
||||
|
||||
it 'disables cluster' do
|
||||
is_expected.to eq(true)
|
||||
expect(cluster.enabled).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace is specified' do
|
||||
let(:params) do
|
||||
{
|
||||
platform_kubernetes_attributes: {
|
||||
namespace: 'custom-namespace'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates namespace' do
|
||||
is_expected.to eq(true)
|
||||
expect(cluster.platform.namespace).to eq('custom-namespace')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid params' do
|
||||
let(:params) do
|
||||
{
|
||||
platform_kubernetes_attributes: {
|
||||
namespace: '!!!'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
is_expected.to eq(false)
|
||||
expect(cluster.errors[:"platform_kubernetes.namespace"]).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
module GoogleApi
|
||||
module CloudPlatformHelpers
|
||||
def stub_google_api_validate_token
|
||||
request.session[GoogleApi::CloudPlatform::Client.session_key_for_token] = 'token'
|
||||
request.session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = 1.hour.since.to_i.to_s
|
||||
end
|
||||
|
||||
def stub_google_api_expired_token
|
||||
request.session[GoogleApi::CloudPlatform::Client.session_key_for_token] = 'token'
|
||||
request.session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = 1.hour.ago.to_i.to_s
|
||||
end
|
||||
|
||||
def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options)
|
||||
WebMock.stub_request(:get, cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id))
|
||||
.to_return(cloud_platform_response(cloud_platform_cluster_body(options)))
|
||||
end
|
||||
|
||||
def stub_cloud_platform_get_zone_cluster_error(project_id, zone, cluster_id)
|
||||
WebMock.stub_request(:get, cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id))
|
||||
.to_return(status: [500, "Internal Server Error"])
|
||||
end
|
||||
|
||||
def stub_cloud_platform_create_cluster(project_id, zone, **options)
|
||||
WebMock.stub_request(:post, cloud_platform_create_cluster_url(project_id, zone))
|
||||
.to_return(cloud_platform_response(cloud_platform_operation_body(options)))
|
||||
end
|
||||
|
||||
def stub_cloud_platform_create_cluster_error(project_id, zone)
|
||||
WebMock.stub_request(:post, cloud_platform_create_cluster_url(project_id, zone))
|
||||
.to_return(status: [500, "Internal Server Error"])
|
||||
end
|
||||
|
||||
def stub_cloud_platform_get_zone_operation(project_id, zone, operation_id, **options)
|
||||
WebMock.stub_request(:get, cloud_platform_get_zone_operation_url(project_id, zone, operation_id))
|
||||
.to_return(cloud_platform_response(cloud_platform_operation_body(options)))
|
||||
end
|
||||
|
||||
def stub_cloud_platform_get_zone_operation_error(project_id, zone, operation_id)
|
||||
WebMock.stub_request(:get, cloud_platform_get_zone_operation_url(project_id, zone, operation_id))
|
||||
.to_return(status: [500, "Internal Server Error"])
|
||||
end
|
||||
|
||||
def cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id)
|
||||
"https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/clusters/#{cluster_id}"
|
||||
end
|
||||
|
||||
def cloud_platform_create_cluster_url(project_id, zone)
|
||||
"https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/clusters"
|
||||
end
|
||||
|
||||
def cloud_platform_get_zone_operation_url(project_id, zone, operation_id)
|
||||
"https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/operations/#{operation_id}"
|
||||
end
|
||||
|
||||
def cloud_platform_response(body)
|
||||
{ status: 200, headers: { 'Content-Type' => 'application/json' }, body: body.to_json }
|
||||
end
|
||||
|
||||
def load_sample_cert
|
||||
pem_file = File.expand_path(Rails.root.join('spec/fixtures/clusters/sample_cert.pem'))
|
||||
Base64.encode64(File.read(pem_file))
|
||||
end
|
||||
|
||||
##
|
||||
# gcloud container clusters create
|
||||
# https://cloud.google.com/container-engine/reference/rest/v1/projects.zones.clusters/create
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
def cloud_platform_cluster_body(**options)
|
||||
{
|
||||
"name": options[:name] || 'string',
|
||||
"description": options[:description] || 'string',
|
||||
"initialNodeCount": options[:initialNodeCount] || 'number',
|
||||
"masterAuth": {
|
||||
"username": options[:username] || 'string',
|
||||
"password": options[:password] || 'string',
|
||||
"clusterCaCertificate": options[:clusterCaCertificate] || load_sample_cert,
|
||||
"clientCertificate": options[:clientCertificate] || 'string',
|
||||
"clientKey": options[:clientKey] || 'string'
|
||||
},
|
||||
"loggingService": options[:loggingService] || 'string',
|
||||
"monitoringService": options[:monitoringService] || 'string',
|
||||
"network": options[:network] || 'string',
|
||||
"clusterIpv4Cidr": options[:clusterIpv4Cidr] || 'string',
|
||||
"subnetwork": options[:subnetwork] || 'string',
|
||||
"enableKubernetesAlpha": options[:enableKubernetesAlpha] || 'boolean',
|
||||
"labelFingerprint": options[:labelFingerprint] || 'string',
|
||||
"selfLink": options[:selfLink] || 'string',
|
||||
"zone": options[:zone] || 'string',
|
||||
"endpoint": options[:endpoint] || 'string',
|
||||
"initialClusterVersion": options[:initialClusterVersion] || 'string',
|
||||
"currentMasterVersion": options[:currentMasterVersion] || 'string',
|
||||
"currentNodeVersion": options[:currentNodeVersion] || 'string',
|
||||
"createTime": options[:createTime] || 'string',
|
||||
"status": options[:status] || 'RUNNING',
|
||||
"statusMessage": options[:statusMessage] || 'string',
|
||||
"nodeIpv4CidrSize": options[:nodeIpv4CidrSize] || 'number',
|
||||
"servicesIpv4Cidr": options[:servicesIpv4Cidr] || 'string',
|
||||
"currentNodeCount": options[:currentNodeCount] || 'number',
|
||||
"expireTime": options[:expireTime] || 'string'
|
||||
}
|
||||
end
|
||||
|
||||
def cloud_platform_operation_body(**options)
|
||||
{
|
||||
"name": options[:name] || 'operation-1234567891234-1234567',
|
||||
"zone": options[:zone] || 'us-central1-a',
|
||||
"operationType": options[:operationType] || 'CREATE_CLUSTER',
|
||||
"status": options[:status] || 'PENDING',
|
||||
"detail": options[:detail] || 'detail',
|
||||
"statusMessage": options[:statusMessage] || '',
|
||||
"selfLink": options[:selfLink] || 'https://container.googleapis.com/v1/projects/123456789101/zones/us-central1-a/operations/operation-1234567891234-1234567',
|
||||
"targetLink": options[:targetLink] || 'https://container.googleapis.com/v1/projects/123456789101/zones/us-central1-a/clusters/test-cluster',
|
||||
"startTime": options[:startTime] || '2017-09-13T16:49:13.055601589Z',
|
||||
"endTime": options[:endTime] || ''
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -9,22 +9,51 @@ module KubernetesHelpers
|
|||
kube_response(kube_pods_body)
|
||||
end
|
||||
|
||||
def stub_kubeclient_discover
|
||||
WebMock.stub_request(:get, service.api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
|
||||
def stub_kubeclient_discover(api_url)
|
||||
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
|
||||
end
|
||||
|
||||
def stub_kubeclient_pods(response = nil)
|
||||
stub_kubeclient_discover
|
||||
stub_kubeclient_discover(service.api_url)
|
||||
pods_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods"
|
||||
|
||||
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
|
||||
end
|
||||
|
||||
def stub_kubeclient_get_secrets(api_url, **options)
|
||||
WebMock.stub_request(:get, api_url + '/api/v1/secrets')
|
||||
.to_return(kube_response(kube_v1_secrets_body(options)))
|
||||
end
|
||||
|
||||
def stub_kubeclient_get_secrets_error(api_url)
|
||||
WebMock.stub_request(:get, api_url + '/api/v1/secrets')
|
||||
.to_return(status: [404, "Internal Server Error"])
|
||||
end
|
||||
|
||||
def kube_v1_secrets_body(**options)
|
||||
{
|
||||
"kind" => "SecretList",
|
||||
"apiVersion": "v1",
|
||||
"items" => [
|
||||
{
|
||||
"metadata": {
|
||||
"name": options[:metadata_name] || "default-token-1",
|
||||
"namespace": "kube-system"
|
||||
},
|
||||
"data": {
|
||||
"token": options[:token] || Base64.encode64('token-sample-123')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def kube_v1_discovery_body
|
||||
{
|
||||
"kind" => "APIResourceList",
|
||||
"resources" => [
|
||||
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" }
|
||||
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
|
||||
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,11 +2,22 @@ require 'spec_helper'
|
|||
|
||||
describe ClusterProvisionWorker do
|
||||
describe '#perform' do
|
||||
context 'when cluster exists' do
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
context 'when provider type is gcp' do
|
||||
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
|
||||
let(:provider) { create(:cluster_provider_gcp, :scheduled) }
|
||||
|
||||
it 'provision a cluster' do
|
||||
expect_any_instance_of(Ci::ProvisionClusterService).to receive(:execute)
|
||||
expect_any_instance_of(Clusters::Gcp::ProvisionService).to receive(:execute)
|
||||
|
||||
described_class.new.perform(cluster.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provider type is user' do
|
||||
let(:cluster) { create(:cluster, provider_type: :user) }
|
||||
|
||||
it 'does not provision a cluster' do
|
||||
expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute)
|
||||
|
||||
described_class.new.perform(cluster.id)
|
||||
end
|
||||
|
|
@ -14,7 +25,7 @@ describe ClusterProvisionWorker do
|
|||
|
||||
context 'when cluster does not exist' do
|
||||
it 'does not provision a cluster' do
|
||||
expect_any_instance_of(Ci::ProvisionClusterService).not_to receive(:execute)
|
||||
expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute)
|
||||
|
||||
described_class.new.perform(123)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,65 +2,32 @@ require 'spec_helper'
|
|||
|
||||
describe WaitForClusterCreationWorker do
|
||||
describe '#perform' do
|
||||
context 'when cluster exists' do
|
||||
let(:cluster) { create(:gcp_cluster) }
|
||||
let(:operation) { double }
|
||||
context 'when provider type is gcp' do
|
||||
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
|
||||
let(:provider) { create(:cluster_provider_gcp, :creating) }
|
||||
|
||||
before do
|
||||
allow(operation).to receive(:status).and_return(status)
|
||||
allow(operation).to receive(:start_time).and_return(1.minute.ago)
|
||||
allow(operation).to receive(:status_message).and_return('error')
|
||||
allow_any_instance_of(Ci::FetchGcpOperationService).to receive(:execute).and_yield(operation)
|
||||
it 'provision a cluster' do
|
||||
expect_any_instance_of(Clusters::Gcp::VerifyProvisionStatusService).to receive(:execute)
|
||||
|
||||
described_class.new.perform(cluster.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when operation status is RUNNING' do
|
||||
let(:status) { 'RUNNING' }
|
||||
context 'when provider type is user' do
|
||||
let(:cluster) { create(:cluster, provider_type: :user) }
|
||||
|
||||
it 'reschedules worker' do
|
||||
expect(described_class).to receive(:perform_in)
|
||||
it 'does not provision a cluster' do
|
||||
expect_any_instance_of(Clusters::Gcp::VerifyProvisionStatusService).not_to receive(:execute)
|
||||
|
||||
described_class.new.perform(cluster.id)
|
||||
end
|
||||
|
||||
context 'when operation timeout' do
|
||||
before do
|
||||
allow(operation).to receive(:start_time).and_return(30.minutes.ago.utc)
|
||||
end
|
||||
|
||||
it 'sets an error message on cluster' do
|
||||
described_class.new.perform(cluster.id)
|
||||
|
||||
expect(cluster.reload).to be_errored
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when operation status is DONE' do
|
||||
let(:status) { 'DONE' }
|
||||
|
||||
it 'finalizes cluster creation' do
|
||||
expect_any_instance_of(Ci::FinalizeClusterCreationService).to receive(:execute)
|
||||
|
||||
described_class.new.perform(cluster.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when operation status is others' do
|
||||
let(:status) { 'others' }
|
||||
|
||||
it 'sets an error message on cluster' do
|
||||
described_class.new.perform(cluster.id)
|
||||
|
||||
expect(cluster.reload).to be_errored
|
||||
end
|
||||
described_class.new.perform(cluster.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cluster does not exist' do
|
||||
it 'does not provision a cluster' do
|
||||
expect_any_instance_of(Ci::FetchGcpOperationService).not_to receive(:execute)
|
||||
expect_any_instance_of(Clusters::Gcp::VerifyProvisionStatusService).not_to receive(:execute)
|
||||
|
||||
described_class.new.perform(1234)
|
||||
described_class.new.perform(123)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue