Commit 5ede567d authored by Thong Kuah's avatar Thong Kuah Committed by Kamil Trzciński

Incorporates Kubernetes Namespace into Cluster's flow

parent 2a89f065
......@@ -19,6 +19,7 @@ module Clusters
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
has_one :cluster_project, -> { order(id: :desc) }, class_name: 'Clusters::Project'
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::Group'
......@@ -128,6 +129,13 @@ module Clusters
platform_kubernetes.kubeclient if kubernetes?
end
def find_or_initialize_kubernetes_namespace(cluster_project)
kubernetes_namespaces.find_or_initialize_by(
project: cluster_project.project,
cluster_project: cluster_project
)
end
private
def restrict_modification
......
......@@ -2,6 +2,8 @@
module Clusters
class KubernetesNamespace < ActiveRecord::Base
include Gitlab::Kubernetes
self.table_name = 'clusters_kubernetes_namespaces'
belongs_to :cluster_project, class_name: 'Clusters::Project'
......@@ -12,7 +14,8 @@ module Clusters
validates :namespace, presence: true
validates :namespace, uniqueness: { scope: :cluster_id }
before_validation :set_namespace_and_service_account_to_default, on: :create
delegate :ca_pem, to: :platform_kubernetes, allow_nil: true
delegate :api_url, to: :platform_kubernetes, allow_nil: true
attr_encrypted :service_account_token,
mode: :per_attribute_iv,
......@@ -23,14 +26,26 @@ module Clusters
"#{namespace}-token"
end
private
def configure_predefined_credentials
self.namespace = kubernetes_or_project_namespace
self.service_account_name = default_service_account_name
end
def set_namespace_and_service_account_to_default
self.namespace ||= default_namespace
self.service_account_name ||= default_service_account_name
def predefined_variables
config = YAML.dump(kubeconfig)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables
.append(key: 'KUBE_SERVICE_ACCOUNT', value: service_account_name)
.append(key: 'KUBE_NAMESPACE', value: namespace)
.append(key: 'KUBE_TOKEN', value: service_account_token, public: false)
.append(key: 'KUBECONFIG', value: config, public: false, file: true)
end
end
def default_namespace
private
def kubernetes_or_project_namespace
platform_kubernetes&.namespace.presence || project_namespace
end
......@@ -45,5 +60,13 @@ module Clusters
def project_slug
"#{project.path}-#{project.id}".downcase
end
def kubeconfig
to_kubeconfig(
url: api_url,
namespace: namespace,
token: service_account_token,
ca_pem: ca_pem)
end
end
end
......@@ -6,6 +6,7 @@ module Clusters
include Gitlab::Kubernetes
include ReactiveCaching
include EnumWithNil
include AfterCommitQueue
RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
......@@ -43,6 +44,7 @@ module Clusters
validate :prevent_modification, on: :update
after_save :clear_reactive_cache!
after_update :update_kubernetes_namespace
alias_attribute :ca_pem, :ca_cert
......@@ -67,20 +69,30 @@ module Clusters
end
end
def predefined_variables
def predefined_variables(project:)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'KUBE_URL', value: api_url)
if ca_pem.present?
variables
.append(key: 'KUBE_CA_PEM', value: ca_pem)
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end
if kubernetes_namespace = cluster.kubernetes_namespaces.find_by(project: project)
variables.concat(kubernetes_namespace.predefined_variables)
else
# From 11.5, every Clusters::Project should have at least one
# Clusters::KubernetesNamespace, so once migration has been completed,
# this 'else' branch will be removed. For more information, please see
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22433
config = YAML.dump(kubeconfig)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables
.append(key: 'KUBE_URL', value: api_url)
.append(key: 'KUBE_TOKEN', value: token, public: false)
.append(key: 'KUBE_NAMESPACE', value: actual_namespace)
.append(key: 'KUBECONFIG', value: config, public: false, file: true)
if ca_pem.present?
variables
.append(key: 'KUBE_CA_PEM', value: ca_pem)
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end
end
end
......@@ -199,6 +211,14 @@ module Clusters
true
end
def update_kubernetes_namespace
return unless namespace_changed?
run_after_commit do
ClusterPlatformConfigureWorker.perform_async(cluster_id)
end
end
end
end
end
......@@ -1829,7 +1829,7 @@ class Project < ActiveRecord::Base
end
def deployment_variables(environment: nil)
deployment_platform(environment: environment)&.predefined_variables || []
deployment_platform(environment: environment)&.predefined_variables(project: self) || []
end
def auto_devops_variables
......
......@@ -104,7 +104,12 @@ class KubernetesService < DeploymentService
{ success: false, result: err }
end
def predefined_variables
# Project param was added on
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011,
# as a way to keep this service compatible with
# Clusters::Platforms::Kubernetes, it won't be used on this method
# as it's only needed for Clusters::Cluster.
def predefined_variables(project:)
config = YAML.dump(kubeconfig)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
......
......@@ -11,8 +11,9 @@ module Clusters
configure_provider
create_gitlab_service_account!
configure_kubernetes
cluster.save!
configure_project_service_account
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
rescue Kubeclient::HttpError => e
......@@ -24,7 +25,10 @@ module Clusters
private
def create_gitlab_service_account!
Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute
Clusters::Gcp::Kubernetes::CreateServiceAccountService.gitlab_creator(
kube_client,
rbac: create_rbac_cluster?
).execute
end
def configure_provider
......@@ -44,7 +48,20 @@ module Clusters
end
def request_kubernetes_token
Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute
Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(
kube_client,
Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
).execute
end
def configure_project_service_account
kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace(cluster.cluster_project)
Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: cluster,
kubernetes_namespace: kubernetes_namespace
).execute
end
def authorization_type
......
......@@ -3,11 +3,12 @@
module Clusters
module Gcp
module Kubernetes
SERVICE_ACCOUNT_NAME = 'gitlab'
SERVICE_ACCOUNT_NAMESPACE = 'default'
SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token'
CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
CLUSTER_ROLE_NAME = 'cluster-admin'
GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab'
GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default'
GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token'
GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
PROJECT_CLUSTER_ROLE_NAME = 'edit'
end
end
end
# frozen_string_literal: true
module Clusters
module Gcp
module Kubernetes
class CreateOrUpdateNamespaceService
def initialize(cluster:, kubernetes_namespace:)
@cluster = cluster
@kubernetes_namespace = kubernetes_namespace
@platform = cluster.platform
end
def execute
configure_kubernetes_namespace
create_project_service_account
configure_kubernetes_token
kubernetes_namespace.save!
rescue ::Kubeclient::HttpError => err
raise err unless err.error_code = 404
end
private
attr_reader :cluster, :kubernetes_namespace, :platform
def configure_kubernetes_namespace
kubernetes_namespace.configure_predefined_credentials
end
def create_project_service_account
Clusters::Gcp::Kubernetes::CreateServiceAccountService.namespace_creator(
platform.kubeclient,
service_account_name: kubernetes_namespace.service_account_name,
service_account_namespace: kubernetes_namespace.namespace,
rbac: platform.rbac?
).execute
end
def configure_kubernetes_token
kubernetes_namespace.service_account_token = fetch_service_account_token
end
def fetch_service_account_token
Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(
platform.kubeclient,
kubernetes_namespace.token_name,
kubernetes_namespace.namespace
).execute
end
end
end
end
end
......@@ -4,46 +4,96 @@ module Clusters
module Gcp
module Kubernetes
class CreateServiceAccountService
attr_reader :kubeclient, :rbac
def initialize(kubeclient, rbac:)
def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil)
@kubeclient = kubeclient
@service_account_name = service_account_name
@service_account_namespace = service_account_namespace
@token_name = token_name
@rbac = rbac
@namespace_creator = namespace_creator
@role_binding_name = role_binding_name
end
def self.gitlab_creator(kubeclient, rbac:)
self.new(
kubeclient,
service_account_name: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME,
service_account_namespace: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE,
token_name: Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
rbac: rbac
)
end
def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:)
self.new(
kubeclient,
service_account_name: service_account_name,
service_account_namespace: service_account_namespace,
token_name: "#{service_account_namespace}-token",
rbac: rbac,
namespace_creator: true,
role_binding_name: "gitlab-#{service_account_namespace}"
)
end
def execute
ensure_project_namespace_exists if namespace_creator
kubeclient.create_service_account(service_account_resource)
kubeclient.create_secret(service_account_token_resource)
kubeclient.create_cluster_role_binding(cluster_role_binding_resource) if rbac
create_role_or_cluster_role_binding if rbac
end
private
attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name
def ensure_project_namespace_exists
Gitlab::Kubernetes::Namespace.new(
service_account_namespace,
kubeclient
).ensure_exists!
end
def create_role_or_cluster_role_binding
if namespace_creator
kubeclient.create_role_binding(role_binding_resource)
else
kubeclient.create_cluster_role_binding(cluster_role_binding_resource)
end
end
def service_account_resource
Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate
Gitlab::Kubernetes::ServiceAccount.new(
service_account_name,
service_account_namespace
).generate
end
def service_account_token_resource
Gitlab::Kubernetes::ServiceAccountToken.new(
SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_namespace).generate
token_name,
service_account_name,
service_account_namespace
).generate
end
def cluster_role_binding_resource
subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
Gitlab::Kubernetes::ClusterRoleBinding.new(
CLUSTER_ROLE_BINDING_NAME,
CLUSTER_ROLE_NAME,
Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME,
Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME,
subjects
).generate
end
def service_account_name
SERVICE_ACCOUNT_NAME
end
def service_account_namespace
SERVICE_ACCOUNT_NAMESPACE
def role_binding_resource
Gitlab::Kubernetes::RoleBinding.new(
name: role_binding_name,
role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME,
namespace: service_account_namespace,
service_account_name: service_account_name
).generate
end
end
end
......
......@@ -4,10 +4,12 @@ module Clusters
module Gcp
module Kubernetes
class FetchKubernetesTokenService
attr_reader :kubeclient
attr_reader :kubeclient, :service_account_token_name, :namespace
def initialize(kubeclient)
def initialize(kubeclient, service_account_token_name, namespace)
@kubeclient = kubeclient
@service_account_token_name = service_account_token_name
@namespace = namespace
end
def execute
......@@ -18,7 +20,7 @@ module Clusters
private
def get_secret
kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json
kubeclient.get_secret(service_account_token_name, namespace).as_json
rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404
......
......@@ -28,6 +28,7 @@
- gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:wait_for_cluster_creation
- gcp_cluster:cluster_wait_for_ingress_ip_address
- gcp_cluster:cluster_platform_configure
- github_import_advance_stage
- github_importer:github_import_import_diff_note
......
# frozen_string_literal: true
class ClusterPlatformConfigureWorker
include ApplicationWorker
include ClusterQueue
def perform(cluster_id)
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
next unless cluster.cluster_project
kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace(cluster.cluster_project)
Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: cluster,
kubernetes_namespace: kubernetes_namespace
).execute
end
rescue ::Kubeclient::HttpError => err
Rails.logger.error "Failed to create/update Kubernetes Namespace. id: #{kubernetes_namespace.id} message: #{err.message}"
end
end
......@@ -9,6 +9,8 @@ class ClusterProvisionWorker
cluster.provider.try do |provider|
Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp?
end
ClusterPlatformConfigureWorker.perform_async(cluster.id) if cluster.user?
end
end
end
---
title: Extend RBAC by having a service account restricted to project's namespace
merge_request: 22011
author:
type: other
......@@ -3,9 +3,8 @@
module Gitlab
module Kubernetes
class RoleBinding
attr_reader :role_name, :namespace, :service_account_name
def initialize(role_name:, namespace:, service_account_name:)
def initialize(name:, role_name:, namespace:, service_account_name:)
@name = name
@role_name = role_name
@namespace = namespace
@service_account_name = service_account_name
......@@ -21,14 +20,16 @@ module Gitlab
private
attr_reader :name, :role_name, :namespace, :service_account_name
def metadata
{ name: "gitlab-#{namespace}", namespace: namespace }
{ name: name, namespace: namespace }
end
def role_ref
{
apiGroup: 'rbac.authorization.k8s.io',
kind: 'Role',
kind: 'ClusterRole',
name: role_name
}
end
......
......@@ -5,6 +5,7 @@ require 'spec_helper'
describe Projects::ClustersController do
include AccessMatchersForController
include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers
set(:project) { create(:project) }
......@@ -309,6 +310,11 @@ describe Projects::ClustersController do
end
describe 'security' do
before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
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(:maintainer).of(project) }
......@@ -412,6 +418,11 @@ describe Projects::ClustersController do
)
end
before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
context 'when cluster is provided by GCP' do
it "updates and redirects back to show page" do
go
......
......@@ -2,8 +2,18 @@
FactoryBot.define do
factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do
cluster
project
cluster_project
association :cluster, :project, :provided_by_gcp
namespace { |n| "environment#{n}" }
after(:build) do |kubernetes_namespace|
cluster_project = kubernetes_namespace.cluster.cluster_project
kubernetes_namespace.project = cluster_project.project
kubernetes_namespace.cluster_project = cluster_project
end
trait :with_token do
service_account_token { Faker::Lorem.characters(10) }
end
end
end
......@@ -130,6 +130,7 @@ describe 'Gcp Cluster', :js do
context 'when user changes cluster parameters' do
before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
page.within('#js-cluster-details') { click_button 'Save changes' }
end
......
......@@ -9,7 +9,9 @@ describe 'User Cluster', :js do
before do
project.add_maintainer(user)
gitlab_sign_in(user)
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
end
context 'when user does not have a cluster and visits cluster index page' do
......
......@@ -20,7 +20,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_ref) do
{
apiGroup: 'rbac.authorization.k8s.io',
kind: 'Role',
kind: 'ClusterRole',
name: role_name
}
end
......@@ -35,6 +35,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
subject do
described_class.new(
name: "gitlab-#{namespace}",
role_name: role_name,
namespace: namespace,
service_account_name: service_account_name
......
......@@ -109,14 +109,20 @@ describe Clusters::Applications::Prometheus do
end
context 'cluster has kubeclient' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url }
let(:kube_client) { subject.cluster.kubeclient.core_client }
subject { create(:clusters_applications_prometheus) }
subject { create(:clusters_applications_prometheus, cluster: cluster) }
before do
subject.cluster.platform_kubernetes.namespace = 'a-namespace'
stub_kubeclient_discover(subject.cluster.platform_kubernetes.api_url)
stub_kubeclient_discover(cluster.platform_kubernetes.api_url)
create(:cluster_kubernetes_namespace,
cluster: cluster,
cluster_project: cluster.cluster_project,
project: cluster.cluster_project.project)
end
it 'creates proxy prometheus rest client' do
......
......@@ -16,6 +16,7 @@ describe Clusters::Cluster do
it { is_expected.to have_one(:application_runner) }
it { is_expected.to have_many(:kubernetes_namespaces) }
it { is_expected.to have_one(:kubernetes_namespace) }
it { is_expected.to have_one(:cluster_project) }
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
......
......@@ -10,23 +10,15 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
describe 'namespace uniqueness validation' do
let(:cluster_project) { create(:cluster_project) }
let(:kubernetes_namespace) do
build(:cluster_kubernetes_namespace,
cluster: cluster_project.cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
subject { kubernetes_namespace }
context 'when cluster is using the namespace' do
before do
create(:cluster_kubernetes_namespace,
cluster: cluster_project.cluster,
project: cluster_project.project,
cluster_project: cluster_project,
namespace: kubernetes_namespace.namespace)
cluster: kubernetes_namespace.cluster,
namespace: 'my-namespace')
end
it { is_expected.not_to be_valid }
......@@ -37,48 +29,79 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
end
end
describe '#set_namespace_and_service_account_to_default' do
let(:cluster) { platform.cluster }
let(:cluster_project) { create(:cluster_project, cluster: cluster) }
let(:kubernetes_namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster_project.cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
describe '#configure_predefined_variables' do
let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace) }
let(:cluster) { kubernetes_namespace.cluster }
let(:platform) { kubernetes_namespace.platform_kubernetes }
describe 'namespace' do
let(:platform) { create(:cluster_platform_kubernetes, namespace: namespace) }
subject { kubernetes_namespace.configure_predefined_credentials }
subject { kubernetes_namespace.namespace }
describe 'namespace' do
before do
platform.update_column(:namespace, namespace)
end
context 'when platform has a namespace assigned' do
let(:namespace) { 'platform-namespace' }
it 'should copy the namespace' do
is_expected.to eq('platform-namespace')
subject
expect(kubernetes_namespace.namespace).to eq('platform-namespace')
end
end
context 'when platform does not have namespace assigned' do
let(:project) { kubernetes_namespace.project }
let(:namespace) { nil }
let(:project_slug) { "#{project.path}-#{project.id}" }
it 'should set default namespace' do
project_slug = "#{cluster_project.project.path}-#{cluster_project.project_id}"
it 'should fallback to project namespace' do
subject
is_expected.to eq(project_slug)
expect(kubernetes_namespace.namespace).to eq(project_slug)
end
end
end
describe 'service_account_name' do
let(:platform) { create(:cluster_platform_kubernetes) }
subject { kubernetes_namespace.service_account_name }
let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" }
it 'should set a service account name based on namespace' do
is_expected.to eq("#{kubernetes_namespace.namespace}-service-account")
subject
expect(kubernetes_namespace.service_account_name).to eq(service_account_name)
end
end
end
describe '#predefined_variables' do
let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, service_account_token: token) }
let(:cluster) { create(:cluster, :project, platform_kubernetes: platform) }
let(:platform) { create(:cluster_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.safe_load(File.read(config_file))
config.dig('users', 0, 'user')['token'] = token
config.dig('contexts', 0, 'context')['namespace'] = kubernetes_namespace.namespace
config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
Base64.strict_encode64(ca_pem)
YAML.dump(config)
end
it 'sets the variables' do
expect(kubernetes_namespace.predefined_variables).to include(
{ key: 'KUBE_SERVICE_ACCOUNT', value: kubernetes_namespace.service_account_name, public: true },
{ key: 'KUBE_NAMESPACE', value: kubernetes_namespace.namespace, public: true },
{ key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false },
{ key: 'KUBECONFIG', value: kubeconfig, public: false, file: true }
)
end
end
end
......@@ -124,9 +124,17 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
describe '#kubeclient' do
let(:cluster) { create(:cluster, :project) }
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace', cluster: cluster) }
subject { kubernetes.kubeclient }
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace') }
before do
create(:cluster_kubernetes_namespace,
cluster: kubernetes.cluster,
cluster_project: kubernetes.cluster.cluster_project,
project: kubernetes.cluster.cluster_project.project)
end
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
end
......@@ -186,29 +194,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
describe '#predefined_variables' do
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) }
let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) }
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(
expect(kubernetes.predefined_variables(project: cluster.project)).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 }
)
......@@ -229,13 +222,6 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
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
......@@ -319,4 +305,27 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to include(pods: []) }
end
end
describe '#update_kubernetes_namespace' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
let(:platform) { cluster.platform }
context 'when namespace is updated' do
it 'should call ConfigureWorker' do
expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id).once
platform.namespace = 'new-namespace'
platform.save
end
end
context 'when namespace is not updated' do
it 'should not call ConfigureWorker' do
expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async)
platform.username = "new-username"
platform.save
end
end
end
end
......@@ -253,7 +253,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end
end
describe '#predefined_variables' do
describe '#predefined_variable' do
let(:kubeconfig) do
config_file = expand_fixture_path('config/kubeconfig.yml')
config = YAML.load(File.read(config_file))
......@@ -274,7 +274,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
shared_examples 'setting variables' do
it 'sets the variables' do
expect(subject.predefined_variables).to include(
expect(subject.predefined_variables(project: project)).to include(
{ key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
{ key: 'KUBE_TOKEN', value: 'token', public: false },
{ key: 'KUBE_NAMESPACE', value: namespace, public: true },
......@@ -301,7 +301,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
it_behaves_like 'setting variables'
it 'sets the KUBE_NAMESPACE' do
kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
kube_namespace = subject.predefined_variables(project: project).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/)
......
......@@ -2405,12 +2405,24 @@ describe Project do
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
context 'when user configured kubernetes from CI/CD > Clusters' do
context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do
let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace) }
let!(:cluster) { kubernetes_namespace.cluster }
let(:project) { kubernetes_namespace.project }
it 'should return token from kubernetes namespace' do
expect(project.deployment_variables).to include(
{ key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
)
end
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Gcp::FinalizeCreationService do
describe Clusters::Gcp::FinalizeCreationService, '#execute' 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 }
let(:endpoint) { '111.111.111.111' }
let(:api_url) { 'https://' + endpoint }
let(:username) { 'sample-username' }
let(:password) { 'sample-password' }
let(:secret_name) { 'gitlab-token' }
let(:token) { 'sample-token' }
let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" }
subject { described_class.new.execute(provider) }
......@@ -20,6 +25,29 @@ describe Clusters::Gcp::FinalizeCreationService do
expect(provider).to be_created
end
it 'properly configures database models' do
subject
cluster.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
it 'creates kubernetes namespace model' do
subject
kubernetes_namespace = cluster.reload.kubernetes_namespace
expect(kubernetes_namespace).to be_persisted
expect(kubernetes_namespace.namespace).to eq(namespace)
expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
expect(kubernetes_namespace.service_account_token).to be_present
end
end
shared_examples 'error' do
......@@ -30,127 +58,119 @@ describe Clusters::Gcp::FinalizeCreationService do
end
end
context 'when succeeded 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' }
let(:secret_name) { 'gitlab-token' }
shared_examples 'kubernetes information not successfully fetched' do
context 'when failed to fetch gke cluster info' do
before do
stub_cloud_platform_get_zone_cluster_error(provider.gcp_project_id, provider.zone, cluster.name)
end
it_behaves_like 'error'
end
context 'when token is empty' do
let(:token) { '' }
it_behaves_like 'error'
end
context 'when failed to fetch kubernetes token' do
before do
stub_kubeclient_get_secret_error(api_url, secret_name, namespace: 'default')
end
it_behaves_like 'error'
end
context 'when service account fails to create' do
before do
stub_kubeclient_create_service_account_error(api_url, namespace: 'default')
end
it_behaves_like 'error'
end
end
shared_context 'kubernetes information successfully fetched' do
before do
stub_cloud_platform_get_zone_cluster(
gcp_project_id, zone, cluster_name,
provider.gcp_project_id, provider.zone, cluster.name,
{
endpoint: endpoint,
username: username,
password: password
}
)
end
context 'service account and token created' do
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url)
stub_kubeclient_create_namespace(api_url)
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_create_secret(api_url)
end
shared_context 'kubernetes token successfully fetched' do
let(:token) { 'sample-token' }
before do
stub_kubeclient_get_secret(
api_url,
{
metadata_name: secret_name,
token: Base64.encode64(token)
} )
token: Base64.encode64(token),
namespace: 'default'
}
)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
stub_kubeclient_get_secret(
api_url,
{
metadata_name: "#{namespace}-token",
token: Base64.encode64(token),
namespace: namespace
}
)
end
end
context 'With a legacy ABAC cluster' do
before do
provider.legacy_abac = true
end
context 'provider legacy_abac is enabled' do
include_context 'kubernetes token successfully fetched'
include_context 'kubernetes information successfully fetched'
it_behaves_like 'success'
it 'properly configures database models' do
it 'uses ABAC authorization type' do
subject
cluster.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).to be_abac
expect(platform.authorization_type).to eq('abac')
expect(platform.token).to eq(token)
end
it_behaves_like 'kubernetes information not successfully fetched'
end
context 'provider legacy_abac is disabled' do
context 'With an RBAC cluster' do
before do
provider.legacy_abac = false
end
include_context 'kubernetes token successfully fetched'
context 'cluster role binding created' do
before do
stub_kubeclient_create_cluster_role_binding(api_url)
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
end
include_context 'kubernetes information successfully fetched'
it_behaves_like 'success'
it 'properly configures database models' do
it 'uses RBAC authorization type' do
subject
cluster.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).to be_rbac
expect(platform.token).to eq(token)
end
end
end
context 'when token is empty' do
before do
stub_kubeclient_get_secret(api_url, token: '', metadata_name: secret_name)
end
it_behaves_like 'error'
end
context 'when failed to fetch kubernetes token' do
before do
stub_kubeclient_get_secret_error(api_url, secret_name)
end
it_behaves_like 'error'
expect(platform.authorization_type).to eq('rbac')
end
context 'when service account fails to create' do
before do
stub_kubeclient_create_service_account_error(api_url)
end
it_behaves_like 'error'
end
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
it_behaves_like 'kubernetes information not successfully fetched'
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
include KubernetesHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:platform) { cluster.platform }
let(:api_url) { 'https://kubernetes.example.com' }
let(:project) { cluster.project }
let(:cluster_project) { cluster.cluster_project }
subject do
described_class.new(
cluster: cluster,
kubernetes_namespace: kubernetes_namespace
).execute
end
shared_context 'kubernetes requests' do
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url)
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_create_secret(api_url)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
stub_kubeclient_get_secret(
api_url,
{
metadata_name: "#{namespace}-token",
token: Base64.encode64('sample-token'),
namespace: namespace
}
)
end
end
context 'when kubernetes namespace is not persisted' do
let(:namespace) { "#{project.path}-#{project.id}" }
let(:kubernetes_namespace) do
build(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
include_context 'kubernetes requests'
it 'creates a Clusters::KubernetesNamespace' do
expect do
subject
end.to change(Clusters::KubernetesNamespace, :count).by(1)
end
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once
subject
end
it 'configures kubernetes token' do
subject
kubernetes_namespace.reload
expect(kubernetes_namespace.namespace).to eq(namespace)
expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
expect(kubernetes_namespace.encrypted_service_account_token).to be_present
end
end
context 'when there is a Kubernetes Namespace associated' do
let(:namespace) { 'new-namespace' }
let(:kubernetes_namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
include_context 'kubernetes requests'
before do
platform.update_column(:namespace, 'new-namespace')
end
it 'does not create any Clusters::KubernetesNamespace' do
subject
expect(cluster.kubernetes_namespace).to eq(kubernetes_namespace)
end
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once
subject
end
it 'updates Clusters::KubernetesNamespace' do
subject
kubernetes_namespace.reload
expect(kubernetes_namespace.namespace).to eq(namespace)
expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
expect(kubernetes_namespace.encrypted_service_account_token).to be_present
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
include KubernetesHelpers
let(:service) { described_class.new(kubeclient, rbac: rbac) }
describe '#execute' do
let(:rbac) { false }
let(:api_url) { 'http://111.111.111.111' }
let(:username) { 'admin' }
let(:password) { 'xxx' }
let(:platform_kubernetes) { cluster.platform_kubernetes }
let(:cluster_project) { cluster.cluster_project }
let(:project) { cluster_project.project }
let(:cluster) do
create(:cluster,
:project, :provided_by_gcp,
platform_kubernetes: create(:cluster_platform_kubernetes, :configured))
end
let(:kubeclient) do
Gitlab::Kubernetes::KubeClient.new(
api_url,
auth_options: { username: username, password: password }
auth_options: { username: 'admin', password: 'xxx' }
)
end
subject { service.execute }
context 'when params are correct' do
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_create_secret(api_url)
end
shared_examples 'creates service account and token' do
it 'creates a kubernetes service account' do
subject
expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/serviceaccounts').with(
expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts").with(
body: hash_including(
kind: 'ServiceAccount',
metadata: { name: 'gitlab', namespace: 'default' }
metadata: { name: service_account_name, namespace: namespace }
)
)
end
it 'creates a kubernetes secret of type ServiceAccountToken' do
it 'creates a kubernetes secret' do
subject
expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/secrets').with(
expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with(
body: hash_including(
kind: 'Secret',
metadata: {
name: 'gitlab-token',
namespace: 'default',
name: token_name,
namespace: namespace,
annotations: {
'kubernetes.io/service-account.name': 'gitlab'
'kubernetes.io/service-account.name': service_account_name
}
},
type: 'kubernetes.io/service-account-token'
......@@ -60,23 +52,41 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
end
end
context 'abac enabled cluster' do
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace )
stub_kubeclient_create_secret(api_url, namespace: namespace)
end
describe '.gitlab_creator' do
let(:namespace) { 'default' }
let(:service_account_name) { 'gitlab' }
let(:token_name) { 'gitlab-token' }
subject { described_class.gitlab_creator(kubeclient, rbac: rbac).execute }
context 'with ABAC cluster' do
let(:rbac) { false }
it_behaves_like 'creates service account and token'
end
context 'rbac enabled cluster' do
context 'with RBAC cluster' do
let(:rbac) { true }
before do
cluster.platform_kubernetes.rbac!
stub_kubeclient_create_cluster_role_binding(api_url)
end
it_behaves_like 'creates service account and token'
it 'creates a kubernetes cluster role binding' do
it 'should create a cluster role binding with cluster-admin access' do
subject
expect(WebMock).to have_requested(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings').with(
expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings").with(
body: hash_including(
kind: 'ClusterRoleBinding',
metadata: { name: 'gitlab-admin' },
......@@ -85,11 +95,72 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
kind: 'ClusterRole',
name: 'cluster-admin'
},
subjects: [{ kind: 'ServiceAccount', namespace: 'default', name: 'gitlab' }]
subjects: [
{
kind: 'ServiceAccount',
name: service_account_name,
namespace: namespace
}
]
)
)
end
end
end
describe '.namespace_creator' do
let(:namespace) { "#{project.path}-#{project.id}" }
let(:service_account_name) { "#{namespace}-service-account" }
let(:token_name) { "#{namespace}-token" }
subject do
described_class.namespace_creator(
kubeclient,
service_account_name: service_account_name,
service_account_namespace: namespace,
rbac: rbac
).execute
end
context 'with ABAC cluster' do
let(:rbac) { false }
it_behaves_like 'creates service account and token'
end
context 'With RBAC enabled cluster' do
let(:rbac) { true }
before do
cluster.platform_kubernetes.rbac!
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
end
it_behaves_like 'creates service account and token'
it 'creates a namespaced role binding with edit access' do
subject
expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings").with(
body: hash_including(
kind: 'RoleBinding',
metadata: { name: "gitlab-#{namespace}", namespace: "#{namespace}" },
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: 'edit'
},
subjects: [
{
kind: 'ServiceAccount',
name: service_account_name,
namespace: namespace
}
]
)
)
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do
include KubernetesHelpers
describe '#execute' do
let(:api_url) { 'http://111.111.111.111' }
let(:username) { 'admin' }
let(:password) { 'xxx' }
let(:namespace) { 'my-namespace' }
let(:service_account_token_name) { 'gitlab-token' }
let(:kubeclient) do
Gitlab::Kubernetes::KubeClient.new(
api_url,
auth_options: { username: username, password: password }
auth_options: { username: 'admin', password: 'xxx' }
)
end
subject { described_class.new(kubeclient).execute }
subject { described_class.new(kubeclient, service_account_token_name, namespace).execute }
context 'when params correct' do
let(:decoded_token) { 'xxx.token.xxx' }
let(:token) { Base64.encode64(decoded_token) }
let(:secret_json) do
context 'when gitlab-token exists' do
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_secret(
api_url,
{
'metadata': {
name: 'gitlab-token'
},
'data': {
'token': token
metadata_name: service_account_token_name,
namespace: namespace,
token: token
}
}
end
before do
allow_any_instance_of(Kubeclient::Client)
.to receive(:get_secret).and_return(secret_json)
)
end
context 'when gitlab-token exists' do
let(:metadata_name) { 'gitlab-token' }
it { is_expected.to eq(decoded_token) }
end
context 'when gitlab-token does not exist' do
let(:secret_json) { {} }
it { is_expected.to be_nil }
before do
allow(kubeclient).to receive(:get_secret).and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
end
context 'when token is nil' do
let(:token) { nil }
it { is_expected.to be_nil }
end
end
......
require 'spec_helper'
describe Clusters::UpdateService do
include KubernetesHelpers
describe '#execute' do
subject { described_class.new(cluster.user, params).execute(cluster) }
......@@ -34,6 +36,11 @@ describe Clusters::UpdateService do
}
end
before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
it 'updates namespace' do
is_expected.to eq(true)
expect(cluster.platform.namespace).to eq('custom-namespace')
......
# frozen_string_literal: true
require 'spec_helper'
describe ClusterPlatformConfigureWorker, '#execute' do
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
it 'configures kubernetes platform' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
described_class.new.perform(cluster.id)
end
end
context 'when provider type is user' do
let(:cluster) { create(:cluster, :project, :provided_by_user) }
it 'configures kubernetes platform' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
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(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute)
described_class.new.perform(123)
end
end
end
......@@ -14,18 +14,25 @@ describe ClusterProvisionWorker do
end
context 'when provider type is user' do
let(:cluster) { create(:cluster, provider_type: :user) }
let(:cluster) { create(:cluster, :provided_by_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
it 'configures kubernetes platform' do
expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id)
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(Clusters::Gcp::ProvisionService).not_to receive(:execute)
expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async)
described_class.new.perform(123)
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