Commit 478e59fe authored by Shinya Maeda's avatar Shinya Maeda

specs for models. Improved details.

parent d0cff7f5
...@@ -6,15 +6,6 @@ module Clusters ...@@ -6,15 +6,6 @@ module Clusters
belongs_to :user belongs_to :user
enum platform_type: {
kubernetes: 1
}
enum provider_type: {
user: 0,
gcp: 1
}
has_many :cluster_projects, class_name: 'Clusters::Project' has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project' has_many :projects, through: :cluster_projects, class_name: '::Project'
...@@ -32,6 +23,18 @@ module Clusters ...@@ -32,6 +23,18 @@ module Clusters
delegate :status_name, to: :provider, allow_nil: true delegate :status_name, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true delegate :on_creation?, to: :provider, 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 def provider
return provider_gcp if gcp? return provider_gcp if gcp?
end end
...@@ -40,15 +43,12 @@ module Clusters ...@@ -40,15 +43,12 @@ module Clusters
return platform_kubernetes if kubernetes? return platform_kubernetes if kubernetes?
end end
def project
first_project
end
def first_project def first_project
return @first_project if defined?(@first_project) return @first_project if defined?(@first_project)
@first_project = projects.first @first_project = projects.first
end end
alias_method :project, :first_project
private private
......
module Clusters module Clusters
module Platforms module Platforms
class Kubernetes < ActiveRecord::Base class Kubernetes < ActiveRecord::Base
include Gitlab::CurrentSettings
include Gitlab::Kubernetes include Gitlab::Kubernetes
include ReactiveCaching include ReactiveCaching
self.table_name = 'cluster_platforms_kubernetes' self.table_name = 'cluster_platforms_kubernetes'
TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.cluster_id] } self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.cluster_id] }
belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster' belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster'
...@@ -22,6 +20,8 @@ module Clusters ...@@ -22,6 +20,8 @@ module Clusters
key: Gitlab::Application.secrets.db_key_base, key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
before_validation :enforce_namespace_to_lower_case
validates :namespace, validates :namespace,
allow_blank: true, allow_blank: true,
length: 1..63, length: 1..63,
...@@ -34,8 +34,19 @@ module Clusters ...@@ -34,8 +34,19 @@ module Clusters
validates :token, presence: true, on: :update validates :token, presence: true, on: :update
after_save :clear_reactive_cache! after_save :clear_reactive_cache!
before_validation :enforce_namespace_to_lower_case alias_attribute :ca_pem, :ca_cert
delegate :project, to: :cluster, allow_nil: true
delegate :enabled?, to: :cluster, allow_nil: true
alias_method :active?, :enabled?
class << self
def namespace_for_project(project)
"#{project.path}-#{project.id}"
end
end
def actual_namespace def actual_namespace
if namespace.present? if namespace.present?
...@@ -45,6 +56,10 @@ module Clusters ...@@ -45,6 +56,10 @@ module Clusters
end end
end end
def default_namespace
self.class.namespace_for_project(project) if project
end
def predefined_variables def predefined_variables
config = YAML.dump(kubeconfig) config = YAML.dump(kubeconfig)
...@@ -55,9 +70,9 @@ module Clusters ...@@ -55,9 +70,9 @@ module Clusters
{ key: 'KUBECONFIG', value: config, public: false, file: true } { key: 'KUBECONFIG', value: config, public: false, file: true }
] ]
if ca_cert.present? if ca_pem.present?
variables << { key: 'KUBE_CA_PEM', value: ca_cert, public: true } variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
variables << { key: 'KUBE_CA_PEM_FILE', value: ca_cert, public: true, file: true } variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
end end
variables variables
...@@ -78,7 +93,7 @@ module Clusters ...@@ -78,7 +93,7 @@ module Clusters
# Caches resources in the namespace so other calls don't need to block on # Caches resources in the namespace so other calls don't need to block on
# network access # network access
def calculate_reactive_cache def calculate_reactive_cache
return unless active? && cluster.project && !cluster.project.pending_delete? return unless active? && project && !project.pending_delete?
# We may want to cache extra things in the future # We may want to cache extra things in the future
{ pods: read_pods } { pods: read_pods }
...@@ -89,16 +104,7 @@ module Clusters ...@@ -89,16 +104,7 @@ module Clusters
url: api_url, url: api_url,
namespace: actual_namespace, namespace: actual_namespace,
token: token, token: token,
ca_pem: ca_cert) ca_pem: ca_pem)
end
def namespace_placeholder
default_namespace || TEMPLATE_PLACEHOLDER
end
def default_namespace(project = nil)
project ||= cluster&.project
"#{project.path}-#{project.id}" if project
end end
def read_secrets def read_secrets
...@@ -123,9 +129,9 @@ module Clusters ...@@ -123,9 +129,9 @@ module Clusters
def kubeclient_ssl_options def kubeclient_ssl_options
opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER } opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
if ca_cert.present? if ca_pem.present?
opts[:cert_store] = OpenSSL::X509::Store.new opts[:cert_store] = OpenSSL::X509::Store.new
opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_cert)) opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
end end
opts opts
...@@ -166,7 +172,7 @@ module Clusters ...@@ -166,7 +172,7 @@ module Clusters
def terminal_auth def terminal_auth
{ {
token: token, token: token,
ca_pem: ca_cert, ca_pem: ca_pem,
max_session_time: current_application_settings.terminal_max_session_time max_session_time: current_application_settings.terminal_max_session_time
} }
end end
......
...@@ -2,7 +2,7 @@ module Clusters ...@@ -2,7 +2,7 @@ module Clusters
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
self.table_name = 'cluster_projects' self.table_name = 'cluster_projects'
belongs_to :cluster, inverse_of: :projects, class_name: 'Clusters::Cluster' belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :project, inverse_of: :project, class_name: 'Project' belongs_to :project, class_name: '::Project'
end end
end end
...@@ -55,7 +55,8 @@ module Clusters ...@@ -55,7 +55,8 @@ module Clusters
before_transition any => [:creating] do |provider, transition| before_transition any => [:creating] do |provider, transition|
operation_id = transition.args.first operation_id = transition.args.first
provider.operation_id = operation_id if operation_id raise 'operation_id is required' unless operation_id
provider.operation_id = operation_id
end end
before_transition any => [:errored] do |provider, transition| before_transition any => [:errored] do |provider, transition|
......
...@@ -8,7 +8,7 @@ class ClusterNameValidator < ActiveModel::EachValidator ...@@ -8,7 +8,7 @@ class ClusterNameValidator < ActiveModel::EachValidator
record.errors.add(attribute, " has to be present") record.errors.add(attribute, " has to be present")
end end
elsif record.gcp? elsif record.gcp?
if record.persisted? && record.name != value if record.persisted? && record.name_changed?
record.errors.add(attribute, " can not be changed because it's synchronized with provider") record.errors.add(attribute, " can not be changed because it's synchronized with provider")
end end
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group .form-group
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: @cluster.platform_kubernetes.default_namespace(@project) = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: Clusters::Platforms::Kubernetes.namespace_for_project(@project)
.form-group .form-group
= field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save' = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save'
FactoryGirl.define do
factory :cluster, class: Clusters::Cluster do
user
name 'test-cluster'
provider_type :user
platform_type :kubernetes
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
end
trait :provided_by_gcp do
provider_type :gcp
platform_type :kubernetes
platform_kubernetes
provider_gcp do
create(:provider_gcp, :created)
end
end
trait :providing_by_gcp do
provider_type :gcp
platform_type :kubernetes
provider_gcp do
create(:provider_gcp, :creating)
end
end
end
end
FactoryGirl.define do
factory :provider_gcp, class: Clusters::Providers::Gcp do
cluster
gcp_project_id 'test-gcp-project'
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
FactoryGirl.define do
factory :platform_kubernetes, class: Clusters::Platforms::Kubernetes do
cluster
api_url 'https://kubernetes.example.com'
ca_cert nil
token 'a' * 40
username 'xxxxxx'
password 'xxxxxx'
namespace nil
trait :ca_cert do
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
# 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
-----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-----
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 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
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 be_kind_of(Gitlab::Kubernetes) }
it { is_expected.to be_kind_of(ReactiveCaching) }
it { is_expected.to respond_to :ca_pem }
describe 'before_validation' do
context 'when namespace includes upper case' do
let(:kubernetes) { create(:platform_kubernetes, 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(:platform_kubernetes, 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
context 'when updates a record' do
let(:kubernetes) { create(:platform_kubernetes) }
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 creates a record' do
let(:kubernetes) { build(:platform_kubernetes) }
before do
kubernetes.api_url = api_url
end
context 'when api_url is nil' do
let(:api_url) { nil }
it { expect(kubernetes.save).to be_truthy }
end
end
end
context 'when validates token' do
context 'when updates a record' do
let(:kubernetes) { create(:platform_kubernetes) }
before do
kubernetes.token = token
end
context 'when token is nil' do
let(:token) { nil }
it { expect(kubernetes.save).to be_falsey }
end
end
context 'when creates a record' do
let(:kubernetes) { build(:platform_kubernetes) }
before do
kubernetes.token = token
end
context 'when token is nil' do
let(:token) { nil }
it { expect(kubernetes.save).to be_truthy }
end
end
end
end
describe '#actual_namespace' do
subject { kubernetes.actual_namespace }
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
let(:kubernetes) { create(:platform_kubernetes, 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("#{cluster.project.path}-#{cluster.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(:platform_kubernetes) }
context 'when cluster belongs to a project' do
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
it { is_expected.to eq("#{cluster.project.path}-#{cluster.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
describe '#predefined_variables' do
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
let(:kubernetes) { create(:platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) }
let(:api_url) { 'https://kube.domain.com' }
let(:ca_pem) { 'CA PEM DATA' }
let(:token) { 'token' }
let(:kubeconfig) do
config_file = expand_fixture_path('config/kubeconfig.yml')
config = YAML.load(File.read(config_file))
config.dig('users', 0, 'user')['token'] = token
config.dig('contexts', 0, 'context')['namespace'] = namespace
config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
Base64.strict_encode64(ca_pem)
YAML.dump(config)
end
shared_examples 'setting variables' do
it 'sets the variables' do
expect(kubernetes.predefined_variables).to include(
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
{ key: 'KUBE_NAMESPACE', value: namespace, public: true },
{ key: 'KUBECONFIG', value: kubeconfig, public: false, file: true },
{ key: 'KUBE_CA_PEM', value: ca_pem, public: true },
{ key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
)
end
end
context 'namespace is provided' do
let(:namespace) { 'my-project' }
before do
kubernetes.namespace = namespace
end
it_behaves_like 'setting variables'
end
context 'no namespace provided' do
let(:namespace) { kubernetes.actual_namespace }
it_behaves_like 'setting variables'
it 'sets the KUBE_NAMESPACE' do
kube_namespace = kubernetes.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
expect(kube_namespace).not_to be_nil
expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
end
end
end
describe '#terminals' do
subject { service.terminals(environment) }
let!(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
let(:project) { cluster.project }
let(:service) { create(:platform_kubernetes) }
let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
context 'with invalid pods' do
it 'returns no terminals' do
stub_reactive_cache(service, pods: [{ "bad" => "pod" }])
is_expected.to be_empty
end
end
context 'with valid pods' do
let(:pod) { kube_pod(app: environment.slug) }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
pods: [pod, pod, kube_pod(app: "should-be-filtered-out")]
)
end
it 'returns terminals' do
is_expected.to eq(terminals + terminals)
end
it 'uses max session time from settings' do
stub_application_setting(terminal_max_session_time: 600)
times = subject.map { |terminal| terminal[:max_session_time] }
expect(times).to eq [600, 600, 600, 600]
end
end
end
describe '#calculate_reactive_cache' do
subject { service.calculate_reactive_cache }
let!(:cluster) { create(:cluster, :project, enabled: enabled, platform_kubernetes: service) }
let(:service) { create(:platform_kubernetes, :ca_cert) }
let(:enabled) { true }
context 'when cluster is disabled' do
let(:enabled) { false }
it { is_expected.to be_nil }
end
context 'when kubernetes responds with valid pods' do
before do
stub_kubeclient_pods
end
it { is_expected.to eq(pods: [kube_pod]) }
end
context 'when kubernetes responds with 500s' do
before do
stub_kubeclient_pods(status: 500)
end
it { expect { subject }.to raise_error(KubeException) }
end
context 'when kubernetes responds with 404s' do
before do
stub_kubeclient_pods(status: 404)
end
it { is_expected.to eq(pods: []) }
end
end
end
require 'spec_helper'
describe Clusters::Project do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:project) }
end
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(: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(: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(: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(: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(: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(: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(: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(:provider_gcp, :creating) }
it { is_expected.to be_truthy }
end
context 'when status is created' do
let(:gcp) { create(: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(: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(:provider_gcp, :created) }
it { is_expected.to be_nil }
end
context 'when status is errored' do
let(:gcp) { build(:provider_gcp, :errored) }
it { is_expected.to be_nil }
end
end
end
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment