From 48dcece76a9addcf80967d0bbd46cd017bc495e8 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Tue, 16 Oct 2018 15:03:59 -0500 Subject: [PATCH 1/5] Integrates Kubernetes namespace on Clusters flow - Modifies 'FinalizeCreationService' class to create KubernetesNamespace instance. - Adds CreateServicesAccountService to ensure gitlab and namespaced services account are created. - Adds two dedicated classes to create respective service accounts - If cluster is using RBAC as authorization, also create a role binding and a cluster role binding account --- app/models/clusters/cluster.rb | 1 + app/models/clusters/kubernetes_namespace.rb | 5 + app/models/clusters/platforms/kubernetes.rb | 16 +- .../clusters/gcp/finalize_creation_service.rb | 18 +- app/services/clusters/gcp/kubernetes.rb | 6 +- .../create_service_account_service.rb | 51 ---- .../fetch_kubernetes_token_service.rb | 8 +- .../service_accounts/base_service.rb | 47 +++ .../service_accounts/gitlab_service.rb | 54 ++++ .../service_accounts/project_service.rb | 59 ++++ .../clusters/kubernetes/configure_service.rb | 66 +++++ app/workers/all_queues.yml | 1 + .../cluster_platform_configure_worker.rb | 12 + app/workers/cluster_provision_worker.rb | 2 + .../51716-create-kube-namespace.yml | 5 + .../projects/clusters_controller_spec.rb | 11 + spec/features/projects/clusters/gcp_spec.rb | 1 + spec/features/projects/clusters/user_spec.rb | 2 + .../clusters/applications/prometheus_spec.rb | 10 +- spec/models/clusters/cluster_spec.rb | 1 + .../clusters/platforms/kubernetes_spec.rb | 33 ++- .../gcp/finalize_creation_service_spec.rb | 278 ++++++++++-------- .../create_service_account_service_spec.rb | 95 ------ .../fetch_kubernetes_token_service_spec.rb | 50 ++-- .../service_accounts/gitlab_service_spec.rb | 108 +++++++ .../service_accounts/project_service_spec.rb | 125 ++++++++ .../kubernetes/configure_service_spec.rb | 102 +++++++ spec/services/clusters/update_service_spec.rb | 7 + .../cluster_platform_configure_worker_spec.rb | 35 +++ spec/workers/cluster_provision_worker_spec.rb | 9 +- 30 files changed, 900 insertions(+), 318 deletions(-) delete mode 100644 app/services/clusters/gcp/kubernetes/create_service_account_service.rb create mode 100644 app/services/clusters/gcp/kubernetes/service_accounts/base_service.rb create mode 100644 app/services/clusters/gcp/kubernetes/service_accounts/gitlab_service.rb create mode 100644 app/services/clusters/gcp/kubernetes/service_accounts/project_service.rb create mode 100644 app/services/clusters/kubernetes/configure_service.rb create mode 100644 app/workers/cluster_platform_configure_worker.rb create mode 100644 changelogs/unreleased/51716-create-kube-namespace.yml delete mode 100644 spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb create mode 100644 spec/services/clusters/gcp/kubernetes/service_accounts/gitlab_service_spec.rb create mode 100644 spec/services/clusters/gcp/kubernetes/service_accounts/project_service_spec.rb create mode 100644 spec/services/clusters/kubernetes/configure_service_spec.rb create mode 100644 spec/workers/cluster_platform_configure_worker_spec.rb diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 222e4217e67..e9519e5202f 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -19,6 +19,7 @@ class Cluster < ActiveRecord::Base 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' diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb index fb5f6b65d9d..d0cd06c34d1 100644 --- a/app/models/clusters/kubernetes_namespace.rb +++ b/app/models/clusters/kubernetes_namespace.rb @@ -23,6 +23,11 @@ def token_name "#{namespace}-token" end + def configure_credentials + self.namespace = default_namespace + self.service_account_name = default_service_account_name + end + private def set_namespace_and_service_account_to_default diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index f0f791742f4..34f5876061a 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -6,6 +6,7 @@ class Kubernetes < ActiveRecord::Base include Gitlab::Kubernetes include ReactiveCaching include EnumWithNil + include AfterCommitQueue RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze @@ -43,6 +44,7 @@ class Kubernetes < ActiveRecord::Base validate :prevent_modification, on: :update after_save :clear_reactive_cache! + after_update :update_kubernetes_namespace alias_attribute :ca_pem, :ca_cert @@ -116,10 +118,14 @@ def kubeconfig to_kubeconfig( url: api_url, namespace: actual_namespace, - token: token, + token: fetch_token, ca_pem: ca_pem) end + def fetch_token + kubernetes_namespace&.service_account_token.presence || token + end + def default_namespace kubernetes_namespace&.namespace.presence || fallback_default_namespace end @@ -199,6 +205,14 @@ def prevent_modification true end + + def update_kubernetes_namespace + return unless namespace_changed? + + run_after_commit do + ClusterPlatformConfigureWorker.perform_async(cluster_id) + end + end end end end diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index 6ee63db8eb9..f1474ed6e74 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -11,8 +11,9 @@ def execute(provider) 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 @@ def execute(provider) private def create_gitlab_service_account! - Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute + Clusters::Gcp::Kubernetes::ServiceAccounts::GitlabService.new( + kube_client, + rbac: create_rbac_cluster? + ).execute end def configure_provider @@ -44,7 +48,15 @@ def configure_kubernetes 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 + Clusters::Kubernetes::ConfigureService.new(cluster).execute end def authorization_type diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb index d014d73b3e8..e9fa11c387f 100644 --- a/app/services/clusters/gcp/kubernetes.rb +++ b/app/services/clusters/gcp/kubernetes.rb @@ -3,9 +3,9 @@ module Clusters module Gcp module Kubernetes - SERVICE_ACCOUNT_NAME = 'gitlab' - SERVICE_ACCOUNT_NAMESPACE = 'default' - SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token' + GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab' + GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default' + GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token' CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' CLUSTER_ROLE_NAME = 'cluster-admin' end diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb deleted file mode 100644 index d17744591e6..00000000000 --- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - module Kubernetes - class CreateServiceAccountService - attr_reader :kubeclient, :rbac - - def initialize(kubeclient, rbac:) - @kubeclient = kubeclient - @rbac = rbac - end - - def execute - 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 - end - - private - - def service_account_resource - 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 - 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, - subjects - ).generate - end - - def service_account_name - SERVICE_ACCOUNT_NAME - end - - def service_account_namespace - SERVICE_ACCOUNT_NAMESPACE - end - end - end - end -end diff --git a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb index 9e09345c8dc..277cc4b788d 100644 --- a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb +++ b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb @@ -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 @@ def execute 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 diff --git a/app/services/clusters/gcp/kubernetes/service_accounts/base_service.rb b/app/services/clusters/gcp/kubernetes/service_accounts/base_service.rb new file mode 100644 index 00000000000..bf75a63aec7 --- /dev/null +++ b/app/services/clusters/gcp/kubernetes/service_accounts/base_service.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Clusters + module Gcp + module Kubernetes + module ServiceAccounts + class BaseService + def initialize(kubeclient, rbac:) + @kubeclient = kubeclient + @rbac = rbac + end + + private + + attr_reader :kubeclient, :rbac + + def service_account_resource + Gitlab::Kubernetes::ServiceAccount.new( + service_account_name, + service_account_namespace + ).generate + end + + def service_account_token_resource + Gitlab::Kubernetes::ServiceAccountToken.new( + token_name, + service_account_name, + service_account_namespace + ).generate + end + + def service_account_name + raise NotImplementedError, 'service_account_name must be implemented' + end + + def service_account_namespace + raise NotImplementedError, 'service_account_namespace must be implemented' + end + + def token_name + raise NotImplementedError, 'token name must be implemented' + end + end + end + end + end +end diff --git a/app/services/clusters/gcp/kubernetes/service_accounts/gitlab_service.rb b/app/services/clusters/gcp/kubernetes/service_accounts/gitlab_service.rb new file mode 100644 index 00000000000..4a156c230bb --- /dev/null +++ b/app/services/clusters/gcp/kubernetes/service_accounts/gitlab_service.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Clusters + module Gcp + module Kubernetes + module ServiceAccounts + class GitlabService < Clusters::Gcp::Kubernetes::ServiceAccounts::BaseService + extend ::Gitlab::Utils::Override + + def execute + 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 + end + + private + + 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, + subjects + ).generate + end + + override :service_account_name + def service_account_name + Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME + end + + override :service_account_namespace + def service_account_namespace + Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE + end + + override :token_name + def token_name + Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME + end + + def cluster_role_binding_name + Clusters::Gcp::Kubernetes::CLUSTER_ROLE_BINDING_NAME + end + + def cluster_role_name + Clusters::Gcp::Kubernetes::CLUSTER_ROLE_NAME + end + end + end + end + end +end diff --git a/app/services/clusters/gcp/kubernetes/service_accounts/project_service.rb b/app/services/clusters/gcp/kubernetes/service_accounts/project_service.rb new file mode 100644 index 00000000000..88f192c1ed2 --- /dev/null +++ b/app/services/clusters/gcp/kubernetes/service_accounts/project_service.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Clusters + module Gcp + module Kubernetes + module ServiceAccounts + class ProjectService < Clusters::Gcp::Kubernetes::ServiceAccounts::BaseService + extend ::Gitlab::Utils::Override + + def initialize(kubeclient, kubernetes_namespace:, rbac:) + super(kubeclient, rbac: rbac) + @kubernetes_namespace = kubernetes_namespace + end + + def execute + ensure_project_namespace_exists + kubeclient.create_service_account(service_account_resource) + kubeclient.create_secret(service_account_token_resource) + kubeclient.create_role_binding(role_binding_resource) if rbac + end + + private + + attr_reader :kubernetes_namespace + + def ensure_project_namespace_exists + Gitlab::Kubernetes::Namespace.new( + service_account_namespace, + kubeclient + ).ensure_exists! + end + + override :service_account_name + def service_account_name + kubernetes_namespace.service_account_name + end + + override :service_account_namespace + def service_account_namespace + kubernetes_namespace.namespace + end + + override :token_name + def token_name + kubernetes_namespace.token_name + end + + def role_binding_resource + Gitlab::Kubernetes::RoleBinding.new( + role_name: 'edit', + namespace: service_account_namespace, + service_account_name: service_account_name + ).generate + end + end + end + end + end +end diff --git a/app/services/clusters/kubernetes/configure_service.rb b/app/services/clusters/kubernetes/configure_service.rb new file mode 100644 index 00000000000..1124af196cd --- /dev/null +++ b/app/services/clusters/kubernetes/configure_service.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module Clusters + module Kubernetes + class ConfigureService + def initialize(cluster) + @cluster = cluster + @platform = cluster.platform + end + + def execute + return unless cluster_project + + find_or_build_kubernetes_namespace + configure_kubernetes_namespace + create_project_service_account + configure_kubernetes_token + + kubernetes_namespace.save! + end + + private + + attr_reader :platform, :cluster, :kubernetes_namespace + + def find_or_build_kubernetes_namespace + @kubernetes_namespace = cluster.kubernetes_namespace.presence || build_kubernetes_namespace + end + + def build_kubernetes_namespace + cluster.kubernetes_namespaces.build( + project: cluster_project.project, + cluster_project: cluster_project + ) + end + + def configure_kubernetes_namespace + kubernetes_namespace.configure_credentials + end + + def create_project_service_account + Clusters::Gcp::Kubernetes::ServiceAccounts::ProjectService.new( + platform.kubeclient, + rbac: platform.rbac?, + kubernetes_namespace: kubernetes_namespace + ).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 + + def cluster_project + @cluster_project ||= cluster.cluster_project + end + end + end +end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index f21789de37d..a66a6f4c777 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -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 diff --git a/app/workers/cluster_platform_configure_worker.rb b/app/workers/cluster_platform_configure_worker.rb new file mode 100644 index 00000000000..2b40a6e7898 --- /dev/null +++ b/app/workers/cluster_platform_configure_worker.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class ClusterPlatformConfigureWorker + include ApplicationWorker + include ClusterQueue + + def perform(cluster_id) + Clusters::Cluster.find_by_id(cluster_id).try do |cluster| + Clusters::Kubernetes::ConfigureService.new(cluster).execute + end + end +end diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb index 59de7903c1c..3d5894b73ec 100644 --- a/app/workers/cluster_provision_worker.rb +++ b/app/workers/cluster_provision_worker.rb @@ -9,6 +9,8 @@ def perform(cluster_id) 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 diff --git a/changelogs/unreleased/51716-create-kube-namespace.yml b/changelogs/unreleased/51716-create-kube-namespace.yml new file mode 100644 index 00000000000..851e19c0a38 --- /dev/null +++ b/changelogs/unreleased/51716-create-kube-namespace.yml @@ -0,0 +1,5 @@ +--- +title: Extend RBAC by having a service account restricted to project's namespace +merge_request: 22011 +author: +type: other diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 9201332c5c8..69c33ae5401 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -3,6 +3,7 @@ describe Projects::ClustersController do include AccessMatchersForController include GoogleApi::CloudPlatformHelpers + include KubernetesHelpers set(:project) { create(:project) } @@ -307,6 +308,11 @@ def go 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) } @@ -408,6 +414,11 @@ def go(format: :html) ) 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 diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 8b92b9fc869..3d17eb3a73a 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -130,6 +130,7 @@ 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 diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 9ae1dba60b5..9b61f300d9a 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -9,7 +9,9 @@ before do project.add_maintainer(user) gitlab_sign_in(user) + allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } + allow_any_instance_of(Clusters::Kubernetes::ConfigureService).to receive(:execute) end context 'when user does not have a cluster and visits cluster index page' do diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index f9776acd4c8..48ba163b38c 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -109,14 +109,20 @@ 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 diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index c245e8df815..19b76ca8cfb 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -16,6 +16,7 @@ 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) } diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index e13eb554add..ec47f523489 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -124,9 +124,17 @@ 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 @@ -319,4 +327,27 @@ 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 diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb index 303d45495ef..7fbb6cf2cf5 100644 --- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb +++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb @@ -1,156 +1,176 @@ +# 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(:cluster) { create(:cluster, :project, :providing_by_gcp) } + let(:provider) { cluster.provider } + let(:platform) { cluster.platform } + 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) } + subject { described_class.new.execute(provider) } - shared_examples 'success' do - it 'configures provider and kubernetes' do - subject + shared_examples 'success' do + it 'configures provider and kubernetes' do + subject - expect(provider).to be_created - end + expect(provider).to be_created end - shared_examples 'error' do - it 'sets an error to provider object' do - subject + it 'properly configures database models' do + subject - expect(provider.reload).to be_errored - end + 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 + it 'sets an error to provider object' do + subject - 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' } + expect(provider.reload).to be_errored + end + end + 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( - gcp_project_id, zone, cluster_name, - { - endpoint: endpoint, - username: username, - password: password - } - ) + stub_cloud_platform_get_zone_cluster_error(provider.gcp_project_id, provider.zone, cluster.name) end - context 'service account and token created' do - before do - stub_kubeclient_discover(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) - } ) - end - end - - context 'provider legacy_abac is enabled' do - include_context 'kubernetes token successfully fetched' - - it_behaves_like 'success' - - 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).to be_abac - expect(platform.authorization_type).to eq('abac') - expect(platform.token).to eq(token) - end - end - - context 'provider legacy_abac is disabled' 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) - end - - it_behaves_like 'success' - - 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).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' - 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 + 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 failed to fetch gke cluster info' do + context 'when service account fails to create' do before do - stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name) + 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( + provider.gcp_project_id, provider.zone, cluster.name, + { + endpoint: endpoint, + username: username, + password: password + } + ) + + 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) + + stub_kubeclient_get_secret( + api_url, + { + metadata_name: secret_name, + 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 + + include_context 'kubernetes information successfully fetched' + + it_behaves_like 'success' + + it 'uses ABAC authorization type' do + subject + cluster.reload + + expect(platform).to be_abac + expect(platform.authorization_type).to eq('abac') + end + + it_behaves_like 'kubernetes information not successfully fetched' + end + + context 'With an RBAC cluster' do + before do + provider.legacy_abac = false + + 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 'uses RBAC authorization type' do + subject + cluster.reload + + expect(platform).to be_rbac + expect(platform.authorization_type).to eq('rbac') + end + + it_behaves_like 'kubernetes information not successfully fetched' + end end diff --git a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb deleted file mode 100644 index b096f1fa4fb..00000000000 --- a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -# 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(:kubeclient) do - Gitlab::Kubernetes::KubeClient.new( - api_url, - auth_options: { username: username, password: password } - ) - 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( - body: hash_including( - kind: 'ServiceAccount', - metadata: { name: 'gitlab', namespace: 'default' } - ) - ) - end - - it 'creates a kubernetes secret of type ServiceAccountToken' do - subject - - expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/secrets').with( - body: hash_including( - kind: 'Secret', - metadata: { - name: 'gitlab-token', - namespace: 'default', - annotations: { - 'kubernetes.io/service-account.name': 'gitlab' - } - }, - type: 'kubernetes.io/service-account-token' - ) - ) - end - end - - context 'abac enabled cluster' do - it_behaves_like 'creates service account and token' - end - - context 'rbac enabled cluster' do - let(:rbac) { true } - - before do - 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 - subject - - 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' }, - roleRef: { - apiGroup: 'rbac.authorization.k8s.io', - kind: 'ClusterRole', - name: 'cluster-admin' - }, - subjects: [{ kind: 'ServiceAccount', namespace: 'default', name: 'gitlab' }] - ) - ) - end - end - end - end -end diff --git a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb index 2355827fa5a..4d1a6bb7b3a 100644 --- a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb @@ -1,56 +1,48 @@ # 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 - { - 'metadata': { - name: 'gitlab-token' - }, - 'data': { - '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' } + before do + stub_kubeclient_discover(api_url) + stub_kubeclient_get_secret( + api_url, + { + metadata_name: service_account_token_name, + namespace: namespace, + token: token + } + ) + end 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 } - end - - context 'when token is nil' do - let(:token) { nil } + before do + allow(kubeclient).to receive(:get_secret).and_raise(Kubeclient::HttpError.new(404, 'Not found', nil)) + end it { is_expected.to be_nil } end diff --git a/spec/services/clusters/gcp/kubernetes/service_accounts/gitlab_service_spec.rb b/spec/services/clusters/gcp/kubernetes/service_accounts/gitlab_service_spec.rb new file mode 100644 index 00000000000..d78b5f6a563 --- /dev/null +++ b/spec/services/clusters/gcp/kubernetes/service_accounts/gitlab_service_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Gcp::Kubernetes::ServiceAccounts::GitlabService, '#execute' do + include KubernetesHelpers + + let(:api_url) { 'http://111.111.111.111' } + let(:platform_kubernetes) { cluster.platform_kubernetes } + let(:namespace) { 'default' } + let(:service_account_name) { 'gitlab' } + let(:token_name) { 'gitlab-token' } + let(:service) { described_class.new(kubeclient, rbac: rbac) } + + 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: 'admin', password: 'xxx' } + ) + end + + subject { service.execute } + + 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 + + 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/#{namespace}/serviceaccounts").with( + body: hash_including( + kind: 'ServiceAccount', + metadata: { name: service_account_name, namespace: namespace } + ) + ) + end + + it 'creates a kubernetes secret' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with( + body: hash_including( + kind: 'Secret', + metadata: { + name: token_name, + namespace: namespace, + annotations: { + 'kubernetes.io/service-account.name': service_account_name + } + }, + type: 'kubernetes.io/service-account-token' + ) + ) + end + 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_cluster_role_binding(api_url) + end + + it_behaves_like 'creates service account and token' + + 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( + body: hash_including( + kind: 'ClusterRoleBinding', + metadata: { name: 'gitlab-admin' }, + roleRef: { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'ClusterRole', + name: 'cluster-admin' + }, + subjects: [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] + ) + ) + end + end +end diff --git a/spec/services/clusters/gcp/kubernetes/service_accounts/project_service_spec.rb b/spec/services/clusters/gcp/kubernetes/service_accounts/project_service_spec.rb new file mode 100644 index 00000000000..499fd158212 --- /dev/null +++ b/spec/services/clusters/gcp/kubernetes/service_accounts/project_service_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Gcp::Kubernetes::ServiceAccounts::ProjectService, '#execute' do + include KubernetesHelpers + + let(:cluster) do + create(:cluster, + :project, :provided_by_gcp, + platform_kubernetes: create(:cluster_platform_kubernetes, :configured)) + end + + let(:api_url) { 'http://111.111.111.111' } + let(:platform_kubernetes) { cluster.platform_kubernetes } + let(:cluster_project) { cluster.cluster_project } + let(:project) { cluster_project.project } + let(:namespace) { "#{project.path}-#{project.id}" } + let(:service_account_name) { "#{namespace}-service-account" } + let(:token_name) { "#{namespace}-token" } + + let(:kubernetes_namespace) do + build(:cluster_kubernetes_namespace, + cluster_project: cluster_project, + cluster: cluster, + project: project, + namespace: namespace, + service_account_name: service_account_name) + end + + let(:service) do + described_class.new( + kubeclient, + kubernetes_namespace: kubernetes_namespace, + rbac: rbac) + end + + let(:kubeclient) do + Gitlab::Kubernetes::KubeClient.new( + api_url, + auth_options: { username: 'admin', password: 'xxx' } + ) + end + + subject { service.execute } + + 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 + + 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/#{namespace}/serviceaccounts").with( + body: hash_including( + kind: 'ServiceAccount', + metadata: { name: service_account_name, namespace: namespace } + ) + ) + end + + it 'creates a kubernetes secret' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with( + body: hash_including( + kind: 'Secret', + metadata: { + name: token_name, + namespace: namespace, + annotations: { + 'kubernetes.io/service-account.name': service_account_name + } + }, + type: 'kubernetes.io/service-account-token' + ) + ) + end + 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: 'Role', + name: 'edit' + }, + subjects: [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] + ) + ) + end + end +end diff --git a/spec/services/clusters/kubernetes/configure_service_spec.rb b/spec/services/clusters/kubernetes/configure_service_spec.rb new file mode 100644 index 00000000000..2f20aacaeb2 --- /dev/null +++ b/spec/services/clusters/kubernetes/configure_service_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Kubernetes::ConfigureService, '#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 } + + subject { described_class.new(cluster).execute } + + 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 + + shared_examples 'project service account and token' do + it 'creates project service account' do + expect_any_instance_of(Clusters::Gcp::Kubernetes::ServiceAccounts::ProjectService).to receive(:execute).once + + subject + end + + it 'configures kubernetes token' do + subject + + kubernetes_namespace = cluster.reload.kubernetes_namespace + 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 no KubernetesNamespace associated' do + let(:namespace) { "#{project.path}-#{project.id}" } + + include_context 'kubernetes requests' + + it 'creates a Clusters::KubernetesNamespace' do + expect do + subject + end.to change(Clusters::KubernetesNamespace, :count).by(1) + end + + it_behaves_like 'project service account and token' + end + + context 'when there is a KubernetesNamespace associated' do + let(:namespace) { 'new-namespace' } + + let(:kubernetes_namespace) do + create(:cluster_kubernetes_namespace, + cluster: cluster, + cluster_project: cluster.cluster_project, + project: cluster.cluster_project.project) + end + + include_context 'kubernetes requests' + + before do + kubernetes_namespace + + platform.update_attribute(:namespace, namespace) + end + + it 'does not create any Clusters::KubernetesNamespace' do + expect do + subject + end.not_to change(Clusters::KubernetesNamespace, :count) + end + + it 'updates Clusters::KubernetesNamespace' do + 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 + + it_behaves_like 'project service account and token' + end +end diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb index dcd75b6912d..a1b20c61116 100644 --- a/spec/services/clusters/update_service_spec.rb +++ b/spec/services/clusters/update_service_spec.rb @@ -1,6 +1,8 @@ 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 @@ } 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') diff --git a/spec/workers/cluster_platform_configure_worker_spec.rb b/spec/workers/cluster_platform_configure_worker_spec.rb new file mode 100644 index 00000000000..37bc787bcf6 --- /dev/null +++ b/spec/workers/cluster_platform_configure_worker_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ClusterPlatformConfigureWorker do + describe '#perform' do + context 'when provider type is gcp' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + it 'configures kubernetes platform' do + expect_any_instance_of(Clusters::Kubernetes::ConfigureService).to receive(:execute).and_call_original + + described_class.new.perform(cluster.id) + end + end + + context 'when provider type is user' do + let(:cluster) { create(:cluster, :provided_by_user) } + + it 'configures kubernetes platform' do + expect_any_instance_of(Clusters::Kubernetes::ConfigureService).to receive(:execute).and_call_original + + 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::Kubernetes::ConfigureService).not_to receive(:execute) + + described_class.new.perform(123) + end + end + end +end diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb index 8054ec11a48..0a2dfef36a4 100644 --- a/spec/workers/cluster_provision_worker_spec.rb +++ b/spec/workers/cluster_provision_worker_spec.rb @@ -14,18 +14,25 @@ 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 -- GitLab From 3032becd5051abb5957f30db2036230e4348f5f5 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Mon, 29 Oct 2018 10:45:54 -0600 Subject: [PATCH 2/5] Addresses backend maintainer review - Unify GitlabService and ProjectService into one class, but with class methods that pass different parameters. - Renames methods to be more explicit - Adds 'predefined_variables' method into KubernetesNamespace, this one includes service account name, namespace, kubeconfig (with restricted permissions) and token --- app/models/clusters/cluster.rb | 7 + app/models/clusters/kubernetes_namespace.rb | 34 +++- app/models/clusters/platforms/kubernetes.rb | 26 ++- app/models/project.rb | 12 +- .../clusters/gcp/finalize_creation_service.rb | 9 +- app/services/clusters/gcp/kubernetes.rb | 1 + .../create_or_update_namespace_service.rb | 54 ++++++ .../create_service_account_service.rb | 100 +++++++++++ .../service_accounts/base_service.rb | 47 ----- .../service_accounts/gitlab_service.rb | 54 ------ .../service_accounts/project_service.rb | 59 ------- .../clusters/kubernetes/configure_service.rb | 66 ------- .../cluster_platform_configure_worker.rb | 12 +- .../clusters/kubernetes_namespaces.rb | 16 +- spec/features/projects/clusters/user_spec.rb | 2 +- .../clusters/kubernetes_namespace_spec.rb | 85 +++++---- .../clusters/platforms/kubernetes_spec.rb | 24 +-- spec/models/project_spec.rb | 14 +- ...reate_or_update_namespace_service_spec.rb} | 73 ++++---- .../create_service_account_service_spec.rb | 166 ++++++++++++++++++ .../service_accounts/gitlab_service_spec.rb | 108 ------------ .../service_accounts/project_service_spec.rb | 125 ------------- .../cluster_platform_configure_worker_spec.rb | 36 ++-- 23 files changed, 542 insertions(+), 588 deletions(-) create mode 100644 app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb create mode 100644 app/services/clusters/gcp/kubernetes/create_service_account_service.rb delete mode 100644 app/services/clusters/gcp/kubernetes/service_accounts/base_service.rb delete mode 100644 app/services/clusters/gcp/kubernetes/service_accounts/gitlab_service.rb delete mode 100644 app/services/clusters/gcp/kubernetes/service_accounts/project_service.rb delete mode 100644 app/services/clusters/kubernetes/configure_service.rb rename spec/services/clusters/{kubernetes/configure_service_spec.rb => gcp/kubernetes/create_or_update_namespace_service_spec.rb} (65%) create mode 100644 spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb delete mode 100644 spec/services/clusters/gcp/kubernetes/service_accounts/gitlab_service_spec.rb delete mode 100644 spec/services/clusters/gcp/kubernetes/service_accounts/project_service_spec.rb diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index e9519e5202f..2bd373e0950 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -129,6 +129,13 @@ def kubeclient 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 diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb index d0cd06c34d1..ac7f9193b87 100644 --- a/app/models/clusters/kubernetes_namespace.rb +++ b/app/models/clusters/kubernetes_namespace.rb @@ -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 @@ class KubernetesNamespace < ActiveRecord::Base 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,19 +26,26 @@ def token_name "#{namespace}-token" end - def configure_credentials - self.namespace = default_namespace + def configure_predefined_credentials + self.namespace = kubernetes_or_project_namespace self.service_account_name = default_service_account_name end - private + def predefined_variables + config = YAML.dump(kubeconfig) - def set_namespace_and_service_account_to_default - self.namespace ||= default_namespace - self.service_account_name ||= default_service_account_name + 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 @@ -50,5 +60,13 @@ def project_namespace 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 diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 34f5876061a..8bcd70b0ec0 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -70,20 +70,28 @@ def actual_namespace end def predefined_variables - 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) + 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 + + # From 11.5, every Cluster should have at least one + # KubernetesNamespace, so once migration has been completed, + # below branching will be removed. For more information, please see + # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22433 + unless kubernetes_namespace.present? + config = YAML.dump(kubeconfig) + + 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) + end end end @@ -118,11 +126,11 @@ def kubeconfig to_kubeconfig( url: api_url, namespace: actual_namespace, - token: fetch_token, + token: default_service_account_token, ca_pem: ca_pem) end - def fetch_token + def default_service_account_token kubernetes_namespace&.service_account_token.presence || token end diff --git a/app/models/project.rb b/app/models/project.rb index e2e309e8496..65669541eb5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1829,7 +1829,17 @@ def protected_for?(ref) end def deployment_variables(environment: nil) - deployment_platform(environment: environment)&.predefined_variables || [] + platform = deployment_platform(environment: environment) + + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break [] unless platform + + variables.concat(platform.predefined_variables) + + if platform.respond_to?(:kubernetes_namespace) + variables.concat(platform.kubernetes_namespace&.predefined_variables || []) + end + end end def auto_devops_variables diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index f1474ed6e74..3df43657fa0 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -25,7 +25,7 @@ def execute(provider) private def create_gitlab_service_account! - Clusters::Gcp::Kubernetes::ServiceAccounts::GitlabService.new( + Clusters::Gcp::Kubernetes::CreateServiceAccountService.gitlab_creator( kube_client, rbac: create_rbac_cluster? ).execute @@ -56,7 +56,12 @@ def request_kubernetes_token end def configure_project_service_account - Clusters::Kubernetes::ConfigureService.new(cluster).execute + 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 diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb index e9fa11c387f..88b85b14a5a 100644 --- a/app/services/clusters/gcp/kubernetes.rb +++ b/app/services/clusters/gcp/kubernetes.rb @@ -8,6 +8,7 @@ module Kubernetes GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token' CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' CLUSTER_ROLE_NAME = 'cluster-admin' + ROLE_BINDING_ROLE = 'edit' end end end diff --git a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb new file mode 100644 index 00000000000..a888fab2789 --- /dev/null +++ b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb @@ -0,0 +1,54 @@ +# 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 diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb new file mode 100644 index 00000000000..c8e0b330625 --- /dev/null +++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +module Clusters + module Gcp + module Kubernetes + class CreateServiceAccountService + def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:) + @kubeclient = kubeclient + @service_account_name = service_account_name + @service_account_namespace = service_account_namespace + @token_name = token_name + @rbac = rbac + 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 + ) + end + + def execute + ensure_project_namespace_exists if namespace_creator? + kubeclient.create_service_account(service_account_resource) + kubeclient.create_secret(service_account_token_resource) + create_role_or_cluster_role_binding if rbac + end + + private + + attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac + + def namespace_creator? + service_account_name != Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME + end + + 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 + end + + def service_account_token_resource + Gitlab::Kubernetes::ServiceAccountToken.new( + 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( + Clusters::Gcp::Kubernetes::CLUSTER_ROLE_BINDING_NAME, + Clusters::Gcp::Kubernetes::CLUSTER_ROLE_NAME, + subjects + ).generate + end + + def role_binding_resource + Gitlab::Kubernetes::RoleBinding.new( + role_name: Clusters::Gcp::Kubernetes::ROLE_BINDING_ROLE, + namespace: service_account_namespace, + service_account_name: service_account_name + ).generate + end + end + end + end +end diff --git a/app/services/clusters/gcp/kubernetes/service_accounts/base_service.rb b/app/services/clusters/gcp/kubernetes/service_accounts/base_service.rb deleted file mode 100644 index bf75a63aec7..00000000000 --- a/app/services/clusters/gcp/kubernetes/service_accounts/base_service.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - module Kubernetes - module ServiceAccounts - class BaseService - def initialize(kubeclient, rbac:) - @kubeclient = kubeclient - @rbac = rbac - end - - private - - attr_reader :kubeclient, :rbac - - def service_account_resource - Gitlab::Kubernetes::ServiceAccount.new( - service_account_name, - service_account_namespace - ).generate - end - - def service_account_token_resource - Gitlab::Kubernetes::ServiceAccountToken.new( - token_name, - service_account_name, - service_account_namespace - ).generate - end - - def service_account_name - raise NotImplementedError, 'service_account_name must be implemented' - end - - def service_account_namespace - raise NotImplementedError, 'service_account_namespace must be implemented' - end - - def token_name - raise NotImplementedError, 'token name must be implemented' - end - end - end - end - end -end diff --git a/app/services/clusters/gcp/kubernetes/service_accounts/gitlab_service.rb b/app/services/clusters/gcp/kubernetes/service_accounts/gitlab_service.rb deleted file mode 100644 index 4a156c230bb..00000000000 --- a/app/services/clusters/gcp/kubernetes/service_accounts/gitlab_service.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - module Kubernetes - module ServiceAccounts - class GitlabService < Clusters::Gcp::Kubernetes::ServiceAccounts::BaseService - extend ::Gitlab::Utils::Override - - def execute - 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 - end - - private - - 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, - subjects - ).generate - end - - override :service_account_name - def service_account_name - Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME - end - - override :service_account_namespace - def service_account_namespace - Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE - end - - override :token_name - def token_name - Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME - end - - def cluster_role_binding_name - Clusters::Gcp::Kubernetes::CLUSTER_ROLE_BINDING_NAME - end - - def cluster_role_name - Clusters::Gcp::Kubernetes::CLUSTER_ROLE_NAME - end - end - end - end - end -end diff --git a/app/services/clusters/gcp/kubernetes/service_accounts/project_service.rb b/app/services/clusters/gcp/kubernetes/service_accounts/project_service.rb deleted file mode 100644 index 88f192c1ed2..00000000000 --- a/app/services/clusters/gcp/kubernetes/service_accounts/project_service.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - module Kubernetes - module ServiceAccounts - class ProjectService < Clusters::Gcp::Kubernetes::ServiceAccounts::BaseService - extend ::Gitlab::Utils::Override - - def initialize(kubeclient, kubernetes_namespace:, rbac:) - super(kubeclient, rbac: rbac) - @kubernetes_namespace = kubernetes_namespace - end - - def execute - ensure_project_namespace_exists - kubeclient.create_service_account(service_account_resource) - kubeclient.create_secret(service_account_token_resource) - kubeclient.create_role_binding(role_binding_resource) if rbac - end - - private - - attr_reader :kubernetes_namespace - - def ensure_project_namespace_exists - Gitlab::Kubernetes::Namespace.new( - service_account_namespace, - kubeclient - ).ensure_exists! - end - - override :service_account_name - def service_account_name - kubernetes_namespace.service_account_name - end - - override :service_account_namespace - def service_account_namespace - kubernetes_namespace.namespace - end - - override :token_name - def token_name - kubernetes_namespace.token_name - end - - def role_binding_resource - Gitlab::Kubernetes::RoleBinding.new( - role_name: 'edit', - namespace: service_account_namespace, - service_account_name: service_account_name - ).generate - end - end - end - end - end -end diff --git a/app/services/clusters/kubernetes/configure_service.rb b/app/services/clusters/kubernetes/configure_service.rb deleted file mode 100644 index 1124af196cd..00000000000 --- a/app/services/clusters/kubernetes/configure_service.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Kubernetes - class ConfigureService - def initialize(cluster) - @cluster = cluster - @platform = cluster.platform - end - - def execute - return unless cluster_project - - find_or_build_kubernetes_namespace - configure_kubernetes_namespace - create_project_service_account - configure_kubernetes_token - - kubernetes_namespace.save! - end - - private - - attr_reader :platform, :cluster, :kubernetes_namespace - - def find_or_build_kubernetes_namespace - @kubernetes_namespace = cluster.kubernetes_namespace.presence || build_kubernetes_namespace - end - - def build_kubernetes_namespace - cluster.kubernetes_namespaces.build( - project: cluster_project.project, - cluster_project: cluster_project - ) - end - - def configure_kubernetes_namespace - kubernetes_namespace.configure_credentials - end - - def create_project_service_account - Clusters::Gcp::Kubernetes::ServiceAccounts::ProjectService.new( - platform.kubeclient, - rbac: platform.rbac?, - kubernetes_namespace: kubernetes_namespace - ).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 - - def cluster_project - @cluster_project ||= cluster.cluster_project - end - end - end -end diff --git a/app/workers/cluster_platform_configure_worker.rb b/app/workers/cluster_platform_configure_worker.rb index 2b40a6e7898..68e8335a09d 100644 --- a/app/workers/cluster_platform_configure_worker.rb +++ b/app/workers/cluster_platform_configure_worker.rb @@ -6,7 +6,17 @@ class ClusterPlatformConfigureWorker def perform(cluster_id) Clusters::Cluster.find_by_id(cluster_id).try do |cluster| - Clusters::Kubernetes::ConfigureService.new(cluster).execute + 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 diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb index 6fdada75a3d..3f10f0ecc74 100644 --- a/spec/factories/clusters/kubernetes_namespaces.rb +++ b/spec/factories/clusters/kubernetes_namespaces.rb @@ -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 diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 9b61f300d9a..250c964cc32 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -11,7 +11,7 @@ gitlab_sign_in(user) allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } - allow_any_instance_of(Clusters::Kubernetes::ConfigureService).to receive(:execute) + 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 diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb index dea58fa26c7..0dfeea5cd2f 100644 --- a/spec/models/clusters/kubernetes_namespace_spec.rb +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -10,23 +10,15 @@ 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 @@ 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 diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index ec47f523489..4f9fb3799d4 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -194,29 +194,14 @@ 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( { 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 } ) @@ -237,13 +222,6 @@ 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 diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d4b9a4c8cd6..ddee934fbe0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2405,12 +2405,24 @@ def create_build(new_pipeline = pipeline, name = 'test') 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 frmo 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 diff --git a/spec/services/clusters/kubernetes/configure_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb similarity index 65% rename from spec/services/clusters/kubernetes/configure_service_spec.rb rename to spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb index 2f20aacaeb2..fc922218ad0 100644 --- a/spec/services/clusters/kubernetes/configure_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb @@ -2,15 +2,21 @@ require 'spec_helper' -describe Clusters::Kubernetes::ConfigureService, '#execute' do +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 { described_class.new(cluster).execute } + subject do + described_class.new( + cluster: cluster, + kubernetes_namespace: kubernetes_namespace + ).execute + end shared_context 'kubernetes requests' do before do @@ -34,9 +40,26 @@ end end - shared_examples 'project service account and token' do + 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::ServiceAccounts::ProjectService).to receive(:execute).once + expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once subject end @@ -44,59 +67,49 @@ it 'configures kubernetes token' do subject - kubernetes_namespace = cluster.reload.kubernetes_namespace + 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 no KubernetesNamespace associated' do - let(:namespace) { "#{project.path}-#{project.id}" } - - include_context 'kubernetes requests' - - it 'creates a Clusters::KubernetesNamespace' do - expect do - subject - end.to change(Clusters::KubernetesNamespace, :count).by(1) - end - - it_behaves_like 'project service account and token' - end - - context 'when there is a KubernetesNamespace associated' do + context 'when there is a Kubernetes Namespace associated' do let(:namespace) { 'new-namespace' } let(:kubernetes_namespace) do create(:cluster_kubernetes_namespace, cluster: cluster, - cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) + project: cluster_project.project, + cluster_project: cluster_project) end include_context 'kubernetes requests' before do - kubernetes_namespace - - platform.update_attribute(:namespace, namespace) + platform.update_column(:namespace, 'new-namespace') end it 'does not create any Clusters::KubernetesNamespace' do - expect do - subject - end.not_to change(Clusters::KubernetesNamespace, :count) + 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 - - it_behaves_like 'project service account and token' end end diff --git a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb new file mode 100644 index 00000000000..c32e85fed8c --- /dev/null +++ b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do + include KubernetesHelpers + + let(:api_url) { 'http://111.111.111.111' } + 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: 'admin', password: 'xxx' } + ) + 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/#{namespace}/serviceaccounts").with( + body: hash_including( + kind: 'ServiceAccount', + metadata: { name: service_account_name, namespace: namespace } + ) + ) + end + + it 'creates a kubernetes secret' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with( + body: hash_including( + kind: 'Secret', + metadata: { + name: token_name, + namespace: namespace, + annotations: { + 'kubernetes.io/service-account.name': service_account_name + } + }, + type: 'kubernetes.io/service-account-token' + ) + ) + end + end + + 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 '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 '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( + body: hash_including( + kind: 'ClusterRoleBinding', + metadata: { name: 'gitlab-admin' }, + roleRef: { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'ClusterRole', + name: 'cluster-admin' + }, + 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: 'Role', + name: 'edit' + }, + subjects: [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] + ) + ) + end + end + end +end diff --git a/spec/services/clusters/gcp/kubernetes/service_accounts/gitlab_service_spec.rb b/spec/services/clusters/gcp/kubernetes/service_accounts/gitlab_service_spec.rb deleted file mode 100644 index d78b5f6a563..00000000000 --- a/spec/services/clusters/gcp/kubernetes/service_accounts/gitlab_service_spec.rb +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Clusters::Gcp::Kubernetes::ServiceAccounts::GitlabService, '#execute' do - include KubernetesHelpers - - let(:api_url) { 'http://111.111.111.111' } - let(:platform_kubernetes) { cluster.platform_kubernetes } - let(:namespace) { 'default' } - let(:service_account_name) { 'gitlab' } - let(:token_name) { 'gitlab-token' } - let(:service) { described_class.new(kubeclient, rbac: rbac) } - - 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: 'admin', password: 'xxx' } - ) - end - - subject { service.execute } - - 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 - - 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/#{namespace}/serviceaccounts").with( - body: hash_including( - kind: 'ServiceAccount', - metadata: { name: service_account_name, namespace: namespace } - ) - ) - end - - it 'creates a kubernetes secret' do - subject - - expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with( - body: hash_including( - kind: 'Secret', - metadata: { - name: token_name, - namespace: namespace, - annotations: { - 'kubernetes.io/service-account.name': service_account_name - } - }, - type: 'kubernetes.io/service-account-token' - ) - ) - end - 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_cluster_role_binding(api_url) - end - - it_behaves_like 'creates service account and token' - - 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( - body: hash_including( - kind: 'ClusterRoleBinding', - metadata: { name: 'gitlab-admin' }, - roleRef: { - apiGroup: 'rbac.authorization.k8s.io', - kind: 'ClusterRole', - name: 'cluster-admin' - }, - subjects: [ - { - kind: 'ServiceAccount', - name: service_account_name, - namespace: namespace - } - ] - ) - ) - end - end -end diff --git a/spec/services/clusters/gcp/kubernetes/service_accounts/project_service_spec.rb b/spec/services/clusters/gcp/kubernetes/service_accounts/project_service_spec.rb deleted file mode 100644 index 499fd158212..00000000000 --- a/spec/services/clusters/gcp/kubernetes/service_accounts/project_service_spec.rb +++ /dev/null @@ -1,125 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Clusters::Gcp::Kubernetes::ServiceAccounts::ProjectService, '#execute' do - include KubernetesHelpers - - let(:cluster) do - create(:cluster, - :project, :provided_by_gcp, - platform_kubernetes: create(:cluster_platform_kubernetes, :configured)) - end - - let(:api_url) { 'http://111.111.111.111' } - let(:platform_kubernetes) { cluster.platform_kubernetes } - let(:cluster_project) { cluster.cluster_project } - let(:project) { cluster_project.project } - let(:namespace) { "#{project.path}-#{project.id}" } - let(:service_account_name) { "#{namespace}-service-account" } - let(:token_name) { "#{namespace}-token" } - - let(:kubernetes_namespace) do - build(:cluster_kubernetes_namespace, - cluster_project: cluster_project, - cluster: cluster, - project: project, - namespace: namespace, - service_account_name: service_account_name) - end - - let(:service) do - described_class.new( - kubeclient, - kubernetes_namespace: kubernetes_namespace, - rbac: rbac) - end - - let(:kubeclient) do - Gitlab::Kubernetes::KubeClient.new( - api_url, - auth_options: { username: 'admin', password: 'xxx' } - ) - end - - subject { service.execute } - - 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 - - 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/#{namespace}/serviceaccounts").with( - body: hash_including( - kind: 'ServiceAccount', - metadata: { name: service_account_name, namespace: namespace } - ) - ) - end - - it 'creates a kubernetes secret' do - subject - - expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with( - body: hash_including( - kind: 'Secret', - metadata: { - name: token_name, - namespace: namespace, - annotations: { - 'kubernetes.io/service-account.name': service_account_name - } - }, - type: 'kubernetes.io/service-account-token' - ) - ) - end - 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: 'Role', - name: 'edit' - }, - subjects: [ - { - kind: 'ServiceAccount', - name: service_account_name, - namespace: namespace - } - ] - ) - ) - end - end -end diff --git a/spec/workers/cluster_platform_configure_worker_spec.rb b/spec/workers/cluster_platform_configure_worker_spec.rb index 37bc787bcf6..1a7ad8923f6 100644 --- a/spec/workers/cluster_platform_configure_worker_spec.rb +++ b/spec/workers/cluster_platform_configure_worker_spec.rb @@ -2,34 +2,32 @@ require 'spec_helper' -describe ClusterPlatformConfigureWorker do - describe '#perform' do - context 'when provider type is gcp' do - let(:cluster) { create(:cluster, :provided_by_gcp) } +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::Kubernetes::ConfigureService).to receive(:execute).and_call_original + it 'configures kubernetes platform' do + expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) - described_class.new.perform(cluster.id) - end + described_class.new.perform(cluster.id) end + end - context 'when provider type is user' do - let(:cluster) { create(:cluster, :provided_by_user) } + 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::Kubernetes::ConfigureService).to receive(:execute).and_call_original + it 'configures kubernetes platform' do + expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) - described_class.new.perform(cluster.id) - 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(Clusters::Kubernetes::ConfigureService).not_to receive(:execute) + 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 + described_class.new.perform(123) end end end -- GitLab From a8adba427023209c5d847f2eed117ee626c26244 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Wed, 31 Oct 2018 13:08:42 -0600 Subject: [PATCH 3/5] Makes #predefined_variables to be project aware Modifies Clusters::Platforms::Kubernetes#predefined_variables and KubernetesService#predefined_variables to be project aware. This will be useful when we have group clusters, and it could help to simplify a decision. --- app/models/clusters/platforms/kubernetes.rb | 15 +++++++++------ app/models/project.rb | 13 ++----------- .../project_services/kubernetes_service.rb | 7 ++++++- .../kubernetes/create_service_account_service.rb | 16 +++++++--------- .../models/clusters/platforms/kubernetes_spec.rb | 4 ++-- .../project_services/kubernetes_service_spec.rb | 6 +++--- spec/models/project_spec.rb | 3 ++- 7 files changed, 31 insertions(+), 33 deletions(-) diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 8bcd70b0ec0..78aa9216f4d 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -69,7 +69,7 @@ def actual_namespace end end - def predefined_variables + def predefined_variables_for_project(project:) Gitlab::Ci::Variables::Collection.new.tap do |variables| variables.append(key: 'KUBE_URL', value: api_url) @@ -79,11 +79,14 @@ def predefined_variables .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true) end - # From 11.5, every Cluster should have at least one - # KubernetesNamespace, so once migration has been completed, - # below branching will be removed. For more information, please see - # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22433 - unless kubernetes_namespace.present? + if project.kubernetes_namespace.present? + variables + .concat(project.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 will be removed. For more information, please see + # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22433 config = YAML.dump(kubeconfig) variables diff --git a/app/models/project.rb b/app/models/project.rb index 65669541eb5..5cd463937c5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -282,6 +282,7 @@ class Project < ActiveRecord::Base delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team delegate :add_master, to: :team # @deprecated delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings + delegate :kubernetes_namespace, to: :cluster_project, allow_nil: true # Validations validates :creator, presence: true, on: :create @@ -1829,17 +1830,7 @@ def protected_for?(ref) end def deployment_variables(environment: nil) - platform = deployment_platform(environment: environment) - - Gitlab::Ci::Variables::Collection.new.tap do |variables| - break [] unless platform - - variables.concat(platform.predefined_variables) - - if platform.respond_to?(:kubernetes_namespace) - variables.concat(platform.kubernetes_namespace&.predefined_variables || []) - end - end + deployment_platform(environment: environment)&.predefined_variables_for_project(project: self) || [] end def auto_devops_variables diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 798944d0c06..e6e86349447 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -104,7 +104,12 @@ def test(*args) { 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_for_project(project:) config = YAML.dump(kubeconfig) Gitlab::Ci::Variables::Collection.new.tap do |variables| diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb index c8e0b330625..93c1fa08591 100644 --- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb @@ -4,12 +4,13 @@ module Clusters module Gcp module Kubernetes class CreateServiceAccountService - def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:) + def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false) @kubeclient = kubeclient @service_account_name = service_account_name @service_account_namespace = service_account_namespace @token_name = token_name @rbac = rbac + @namespace_creator = namespace_creator end def self.gitlab_creator(kubeclient, rbac:) @@ -28,12 +29,13 @@ def self.namespace_creator(kubeclient, service_account_name:, service_account_na service_account_name: service_account_name, service_account_namespace: service_account_namespace, token_name: "#{service_account_namespace}-token", - rbac: rbac + rbac: rbac, + namespace_creator: true ) end def execute - ensure_project_namespace_exists if namespace_creator? + ensure_project_namespace_exists if namespace_creator kubeclient.create_service_account(service_account_resource) kubeclient.create_secret(service_account_token_resource) create_role_or_cluster_role_binding if rbac @@ -41,11 +43,7 @@ def execute private - attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac - - def namespace_creator? - service_account_name != Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME - end + attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator def ensure_project_namespace_exists Gitlab::Kubernetes::Namespace.new( @@ -55,7 +53,7 @@ def ensure_project_namespace_exists end def create_role_or_cluster_role_binding - if namespace_creator? + if namespace_creator kubeclient.create_role_binding(role_binding_resource) else kubeclient.create_cluster_role_binding(cluster_role_binding_resource) diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 4f9fb3799d4..e6c938ea58e 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -192,7 +192,7 @@ end end - describe '#predefined_variables' do + describe '#predefined_variables_for_project' do let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) } let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) } let(:api_url) { 'https://kube.domain.com' } @@ -200,7 +200,7 @@ shared_examples 'setting variables' do it 'sets the variables' do - expect(kubernetes.predefined_variables).to include( + expect(kubernetes.predefined_variables_for_project(project: cluster.project)).to include( { key: 'KUBE_URL', value: api_url, public: true }, { key: 'KUBE_CA_PEM', value: ca_pem, public: true }, { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true } diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 68ab9fd08ec..3589a5f60db 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -253,7 +253,7 @@ end end - describe '#predefined_variables' do + describe '#predefined_variables_for_project' do let(:kubeconfig) do config_file = expand_fixture_path('config/kubeconfig.yml') config = YAML.load(File.read(config_file)) @@ -274,7 +274,7 @@ shared_examples 'setting variables' do it 'sets the variables' do - expect(subject.predefined_variables).to include( + expect(subject.predefined_variables_for_project(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 @@ 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_for_project(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/) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ddee934fbe0..fc06edb18c6 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -387,6 +387,7 @@ it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) } it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } + it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster_project).with_arguments(allow_nil: true) } end describe '#to_reference_with_postfix' do @@ -2412,7 +2413,7 @@ def create_build(new_pipeline = pipeline, name = 'test') it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' end - context 'when user configured kubernetes frmo CI/CD > Clusters and KubernetesNamespace migration has been executed' do + 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 } -- GitLab From 924ad021ef54fc4c8290415dd09d57b509774ab8 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Thu, 1 Nov 2018 11:21:53 -0600 Subject: [PATCH 4/5] Make small modification on RoleBinding service - Changes RoleRef to use cluster-edit instead of simple edit, as this one does not exists - Also include minor comments from BE review regarding useless if/else branching. --- app/models/clusters/platforms/kubernetes.rb | 8 ++------ app/services/clusters/gcp/kubernetes.rb | 2 +- .../gcp/kubernetes/create_service_account_service.rb | 11 +++++++---- lib/gitlab/kubernetes/role_binding.rb | 11 ++++++----- spec/lib/gitlab/kubernetes/role_binding_spec.rb | 3 ++- .../kubernetes/create_service_account_service_spec.rb | 2 +- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 78aa9216f4d..cd14137d08e 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -85,7 +85,7 @@ def predefined_variables_for_project(project:) else # From 11.5, every Clusters::Project should have at least one # Clusters::KubernetesNamespace, so once migration has been completed, - # this else will be removed. For more information, please see + # 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) @@ -129,14 +129,10 @@ def kubeconfig to_kubeconfig( url: api_url, namespace: actual_namespace, - token: default_service_account_token, + token: token, ca_pem: ca_pem) end - def default_service_account_token - kubernetes_namespace&.service_account_token.presence || token - end - def default_namespace kubernetes_namespace&.namespace.presence || fallback_default_namespace end diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb index 88b85b14a5a..f9d5c716ef7 100644 --- a/app/services/clusters/gcp/kubernetes.rb +++ b/app/services/clusters/gcp/kubernetes.rb @@ -8,7 +8,7 @@ module Kubernetes GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token' CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' CLUSTER_ROLE_NAME = 'cluster-admin' - ROLE_BINDING_ROLE = 'edit' + ROLE_BINDING_ROLE_NAME = 'edit' end end end diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb index 93c1fa08591..88312808981 100644 --- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb @@ -4,13 +4,14 @@ module Clusters module Gcp module Kubernetes class CreateServiceAccountService - def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false) + 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:) @@ -30,7 +31,8 @@ def self.namespace_creator(kubeclient, service_account_name:, service_account_na service_account_namespace: service_account_namespace, token_name: "#{service_account_namespace}-token", rbac: rbac, - namespace_creator: true + namespace_creator: true, + role_binding_name: "gitlab-#{service_account_namespace}" ) end @@ -43,7 +45,7 @@ def execute private - attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator + 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( @@ -87,7 +89,8 @@ def cluster_role_binding_resource def role_binding_resource Gitlab::Kubernetes::RoleBinding.new( - role_name: Clusters::Gcp::Kubernetes::ROLE_BINDING_ROLE, + name: role_binding_name, + role_name: Clusters::Gcp::Kubernetes::ROLE_BINDING_ROLE_NAME, namespace: service_account_namespace, service_account_name: service_account_name ).generate diff --git a/lib/gitlab/kubernetes/role_binding.rb b/lib/gitlab/kubernetes/role_binding.rb index 4f3ee040bf2..cb0cb42d007 100644 --- a/lib/gitlab/kubernetes/role_binding.rb +++ b/lib/gitlab/kubernetes/role_binding.rb @@ -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 @@ def generate 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 diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb index da3f5d27b25..a1a59533bfb 100644 --- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb +++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb @@ -20,7 +20,7 @@ let(:role_ref) do { apiGroup: 'rbac.authorization.k8s.io', - kind: 'Role', + kind: 'ClusterRole', name: role_name } end @@ -35,6 +35,7 @@ subject do described_class.new( + name: "gitlab-#{namespace}", role_name: role_name, namespace: namespace, service_account_name: service_account_name diff --git a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb index c32e85fed8c..588edff85d4 100644 --- a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb @@ -148,7 +148,7 @@ metadata: { name: "gitlab-#{namespace}", namespace: "#{namespace}" }, roleRef: { apiGroup: 'rbac.authorization.k8s.io', - kind: 'Role', + kind: 'ClusterRole', name: 'edit' }, subjects: [ -- GitLab From 988e4edaa2a506026ed13e99f77a9576685fa281 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Fri, 2 Nov 2018 07:58:04 -0600 Subject: [PATCH 5/5] Rearranges Kubernetes#predefined_variables method Changes method to search KubernetesNamespace based on cluster relationship, instead of delegating to Project. This should help on EE when supporting multiple clusters --- app/models/clusters/platforms/kubernetes.rb | 7 +++---- app/models/project.rb | 3 +-- app/models/project_services/kubernetes_service.rb | 2 +- app/services/clusters/gcp/kubernetes.rb | 6 +++--- .../gcp/kubernetes/create_service_account_service.rb | 6 +++--- spec/models/clusters/platforms/kubernetes_spec.rb | 4 ++-- spec/models/project_services/kubernetes_service_spec.rb | 6 +++--- spec/models/project_spec.rb | 1 - 8 files changed, 16 insertions(+), 19 deletions(-) diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index cd14137d08e..008e08d9914 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -69,7 +69,7 @@ def actual_namespace end end - def predefined_variables_for_project(project:) + def predefined_variables(project:) Gitlab::Ci::Variables::Collection.new.tap do |variables| variables.append(key: 'KUBE_URL', value: api_url) @@ -79,9 +79,8 @@ def predefined_variables_for_project(project:) .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true) end - if project.kubernetes_namespace.present? - variables - .concat(project.kubernetes_namespace.predefined_variables) + 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, diff --git a/app/models/project.rb b/app/models/project.rb index 5cd463937c5..fa995b5b061 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -282,7 +282,6 @@ class Project < ActiveRecord::Base delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team delegate :add_master, to: :team # @deprecated delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings - delegate :kubernetes_namespace, to: :cluster_project, allow_nil: true # Validations validates :creator, presence: true, on: :create @@ -1830,7 +1829,7 @@ def protected_for?(ref) end def deployment_variables(environment: nil) - deployment_platform(environment: environment)&.predefined_variables_for_project(project: self) || [] + deployment_platform(environment: environment)&.predefined_variables(project: self) || [] end def auto_devops_variables diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index e6e86349447..3459ded7ccf 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -109,7 +109,7 @@ def test(*args) # 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_for_project(project:) + def predefined_variables(project:) config = YAML.dump(kubeconfig) Gitlab::Ci::Variables::Collection.new.tap do |variables| diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb index f9d5c716ef7..90ed529670c 100644 --- a/app/services/clusters/gcp/kubernetes.rb +++ b/app/services/clusters/gcp/kubernetes.rb @@ -6,9 +6,9 @@ module Kubernetes GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab' GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default' GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token' - CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' - CLUSTER_ROLE_NAME = 'cluster-admin' - ROLE_BINDING_ROLE_NAME = 'edit' + GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' + GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin' + PROJECT_CLUSTER_ROLE_NAME = 'edit' end end end diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb index 88312808981..dfc4bf7a358 100644 --- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb @@ -81,8 +81,8 @@ def cluster_role_binding_resource subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }] Gitlab::Kubernetes::ClusterRoleBinding.new( - Clusters::Gcp::Kubernetes::CLUSTER_ROLE_BINDING_NAME, - Clusters::Gcp::Kubernetes::CLUSTER_ROLE_NAME, + Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME, + Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME, subjects ).generate end @@ -90,7 +90,7 @@ def cluster_role_binding_resource def role_binding_resource Gitlab::Kubernetes::RoleBinding.new( name: role_binding_name, - role_name: Clusters::Gcp::Kubernetes::ROLE_BINDING_ROLE_NAME, + role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME, namespace: service_account_namespace, service_account_name: service_account_name ).generate diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index e6c938ea58e..2bcccc8184a 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -192,7 +192,7 @@ end end - describe '#predefined_variables_for_project' do + 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) } let(:api_url) { 'https://kube.domain.com' } @@ -200,7 +200,7 @@ shared_examples 'setting variables' do it 'sets the variables' do - expect(kubernetes.predefined_variables_for_project(project: cluster.project)).to include( + expect(kubernetes.predefined_variables(project: cluster.project)).to include( { key: 'KUBE_URL', value: api_url, public: true }, { key: 'KUBE_CA_PEM', value: ca_pem, public: true }, { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true } diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 3589a5f60db..9c27357ffaf 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -253,7 +253,7 @@ end end - describe '#predefined_variables_for_project' 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 @@ shared_examples 'setting variables' do it 'sets the variables' do - expect(subject.predefined_variables_for_project(project: project)).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 @@ it_behaves_like 'setting variables' it 'sets the KUBE_NAMESPACE' do - kube_namespace = subject.predefined_variables_for_project(project: project).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/) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index fc06edb18c6..d059854214f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -387,7 +387,6 @@ it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) } it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } - it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster_project).with_arguments(allow_nil: true) } end describe '#to_reference_with_postfix' do -- GitLab