From 9e8daeb8a6066109c3254178fe689dc8066d88db Mon Sep 17 00:00:00 2001 From: Tiger Date: Thu, 22 Aug 2019 16:40:17 +1000 Subject: Move generic k8s services out of GCP namespace These services aren't specific to GCP, and will be used for AWS as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/46686 --- .../clusters/gcp/finalize_creation_service.rb | 8 +- app/services/clusters/gcp/kubernetes.rb | 16 -- .../create_or_update_namespace_service.rb | 47 ----- .../create_or_update_service_account_service.rb | 141 --------------- .../kubernetes/fetch_kubernetes_token_service.rb | 42 ----- .../create_or_update_namespace_service.rb | 45 +++++ .../create_or_update_service_account_service.rb | 139 +++++++++++++++ .../kubernetes/fetch_kubernetes_token_service.rb | 40 +++++ app/services/clusters/kubernetes/kubernetes.rb | 14 ++ .../ci/build/prerequisite/kubernetes_namespace.rb | 2 +- spec/features/groups/clusters/user_spec.rb | 2 +- spec/features/projects/clusters/user_spec.rb | 2 +- .../prerequisite/kubernetes_namespace_spec.rb | 6 +- .../create_or_update_namespace_service_spec.rb | 142 --------------- ...reate_or_update_service_account_service_spec.rb | 194 --------------------- .../fetch_kubernetes_token_service_spec.rb | 110 ------------ .../create_or_update_namespace_service_spec.rb | 142 +++++++++++++++ ...reate_or_update_service_account_service_spec.rb | 194 +++++++++++++++++++++ .../fetch_kubernetes_token_service_spec.rb | 110 ++++++++++++ 19 files changed, 694 insertions(+), 702 deletions(-) delete mode 100644 app/services/clusters/gcp/kubernetes.rb delete mode 100644 app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb delete mode 100644 app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb delete mode 100644 app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb create mode 100644 app/services/clusters/kubernetes/create_or_update_namespace_service.rb create mode 100644 app/services/clusters/kubernetes/create_or_update_service_account_service.rb create mode 100644 app/services/clusters/kubernetes/fetch_kubernetes_token_service.rb create mode 100644 app/services/clusters/kubernetes/kubernetes.rb delete mode 100644 spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb delete mode 100644 spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb delete mode 100644 spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb create mode 100644 spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb create mode 100644 spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb create mode 100644 spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index 2f3c1df7651..c5cde831964 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -26,7 +26,7 @@ module Clusters private def create_gitlab_service_account! - Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator( + Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator( kube_client, rbac: create_rbac_cluster? ).execute @@ -49,10 +49,10 @@ module Clusters end def request_kubernetes_token - Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new( + Clusters::Kubernetes::FetchKubernetesTokenService.new( kube_client, - Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, - Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE + Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, + Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE ).execute end diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb deleted file mode 100644 index 85711764785..00000000000 --- a/app/services/clusters/gcp/kubernetes.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - module Kubernetes - GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab' - GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default' - GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token' - GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' - GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin' - PROJECT_CLUSTER_ROLE_NAME = 'edit' - GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role' - GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding' - 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 deleted file mode 100644 index c45dac7b273..00000000000 --- a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb +++ /dev/null @@ -1,47 +0,0 @@ -# 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 - create_project_service_account - configure_kubernetes_token - - kubernetes_namespace.save! - end - - private - - attr_reader :cluster, :kubernetes_namespace, :platform - - def create_project_service_account - Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.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_or_update_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb deleted file mode 100644 index 7c5450dbcd6..00000000000 --- a/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb +++ /dev/null @@ -1,141 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - module Kubernetes - class CreateOrUpdateServiceAccountService - def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil) - @kubeclient = kubeclient - @service_account_name = service_account_name - @service_account_namespace = service_account_namespace - @token_name = token_name - @rbac = rbac - @namespace_creator = namespace_creator - @role_binding_name = role_binding_name - end - - def self.gitlab_creator(kubeclient, rbac:) - self.new( - kubeclient, - service_account_name: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME, - service_account_namespace: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE, - token_name: Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, - rbac: rbac - ) - end - - def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:) - self.new( - kubeclient, - service_account_name: service_account_name, - service_account_namespace: service_account_namespace, - token_name: "#{service_account_namespace}-token", - rbac: rbac, - namespace_creator: true, - role_binding_name: "gitlab-#{service_account_namespace}" - ) - end - - def execute - ensure_project_namespace_exists if namespace_creator - - kubeclient.create_or_update_service_account(service_account_resource) - kubeclient.create_or_update_secret(service_account_token_resource) - - return unless rbac - - create_role_or_cluster_role_binding - - return unless namespace_creator - - create_or_update_knative_serving_role - create_or_update_knative_serving_role_binding - end - - private - - attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name - - def ensure_project_namespace_exists - Gitlab::Kubernetes::Namespace.new( - service_account_namespace, - kubeclient - ).ensure_exists! - end - - def create_role_or_cluster_role_binding - if namespace_creator - kubeclient.create_or_update_role_binding(role_binding_resource) - else - kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource) - end - end - - def create_or_update_knative_serving_role - kubeclient.update_role(knative_serving_role_resource) - end - - def create_or_update_knative_serving_role_binding - kubeclient.update_role_binding(knative_serving_role_binding_resource) - 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::GITLAB_CLUSTER_ROLE_BINDING_NAME, - Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME, - subjects - ).generate - end - - def role_binding_resource - Gitlab::Kubernetes::RoleBinding.new( - name: role_binding_name, - role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME, - role_kind: :ClusterRole, - namespace: service_account_namespace, - service_account_name: service_account_name - ).generate - end - - def knative_serving_role_resource - Gitlab::Kubernetes::Role.new( - name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, - namespace: service_account_namespace, - rules: [{ - apiGroups: %w(serving.knative.dev), - resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services), - verbs: %w(get list create update delete patch watch) - }] - ).generate - end - - def knative_serving_role_binding_resource - Gitlab::Kubernetes::RoleBinding.new( - name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, - role_name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, - role_kind: :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/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb deleted file mode 100644 index 5d9bdc52d37..00000000000 --- a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - module Kubernetes - class FetchKubernetesTokenService - DEFAULT_TOKEN_RETRY_DELAY = 5.seconds - TOKEN_RETRY_LIMIT = 5 - - attr_reader :kubeclient, :service_account_token_name, :namespace - - def initialize(kubeclient, service_account_token_name, namespace, token_retry_delay: DEFAULT_TOKEN_RETRY_DELAY) - @kubeclient = kubeclient - @service_account_token_name = service_account_token_name - @namespace = namespace - @token_retry_delay = token_retry_delay - end - - def execute - # Kubernetes will create the Secret and set the token asynchronously - # so it is necessary to retry - # https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#token-controller - TOKEN_RETRY_LIMIT.times do - token_base64 = get_secret&.dig('data', 'token') - return Base64.decode64(token_base64) if token_base64 - - sleep @token_retry_delay - end - - nil - end - - private - - def get_secret - kubeclient.get_secret(service_account_token_name, namespace).as_json - rescue Kubeclient::ResourceNotFoundError - end - end - end - end -end diff --git a/app/services/clusters/kubernetes/create_or_update_namespace_service.rb b/app/services/clusters/kubernetes/create_or_update_namespace_service.rb new file mode 100644 index 00000000000..15be8446cc0 --- /dev/null +++ b/app/services/clusters/kubernetes/create_or_update_namespace_service.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Clusters + module Kubernetes + class CreateOrUpdateNamespaceService + def initialize(cluster:, kubernetes_namespace:) + @cluster = cluster + @kubernetes_namespace = kubernetes_namespace + @platform = cluster.platform + end + + def execute + create_project_service_account + configure_kubernetes_token + + kubernetes_namespace.save! + end + + private + + attr_reader :cluster, :kubernetes_namespace, :platform + + def create_project_service_account + Clusters::Kubernetes::CreateOrUpdateServiceAccountService.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::Kubernetes::FetchKubernetesTokenService.new( + platform.kubeclient, + kubernetes_namespace.token_name, + kubernetes_namespace.namespace + ).execute + end + end + end +end diff --git a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb new file mode 100644 index 00000000000..8b8ad924b64 --- /dev/null +++ b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +module Clusters + module Kubernetes + class CreateOrUpdateServiceAccountService + def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil) + @kubeclient = kubeclient + @service_account_name = service_account_name + @service_account_namespace = service_account_namespace + @token_name = token_name + @rbac = rbac + @namespace_creator = namespace_creator + @role_binding_name = role_binding_name + end + + def self.gitlab_creator(kubeclient, rbac:) + self.new( + kubeclient, + service_account_name: Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME, + service_account_namespace: Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE, + token_name: Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, + rbac: rbac + ) + end + + def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:) + self.new( + kubeclient, + service_account_name: service_account_name, + service_account_namespace: service_account_namespace, + token_name: "#{service_account_namespace}-token", + rbac: rbac, + namespace_creator: true, + role_binding_name: "gitlab-#{service_account_namespace}" + ) + end + + def execute + ensure_project_namespace_exists if namespace_creator + + kubeclient.create_or_update_service_account(service_account_resource) + kubeclient.create_or_update_secret(service_account_token_resource) + + return unless rbac + + create_role_or_cluster_role_binding + + return unless namespace_creator + + create_or_update_knative_serving_role + create_or_update_knative_serving_role_binding + end + + private + + attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name + + def ensure_project_namespace_exists + Gitlab::Kubernetes::Namespace.new( + service_account_namespace, + kubeclient + ).ensure_exists! + end + + def create_role_or_cluster_role_binding + if namespace_creator + kubeclient.create_or_update_role_binding(role_binding_resource) + else + kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource) + end + end + + def create_or_update_knative_serving_role + kubeclient.update_role(knative_serving_role_resource) + end + + def create_or_update_knative_serving_role_binding + kubeclient.update_role_binding(knative_serving_role_binding_resource) + 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::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME, + Clusters::Kubernetes::GITLAB_CLUSTER_ROLE_NAME, + subjects + ).generate + end + + def role_binding_resource + Gitlab::Kubernetes::RoleBinding.new( + name: role_binding_name, + role_name: Clusters::Kubernetes::PROJECT_CLUSTER_ROLE_NAME, + role_kind: :ClusterRole, + namespace: service_account_namespace, + service_account_name: service_account_name + ).generate + end + + def knative_serving_role_resource + Gitlab::Kubernetes::Role.new( + name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, + namespace: service_account_namespace, + rules: [{ + apiGroups: %w(serving.knative.dev), + resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services), + verbs: %w(get list create update delete patch watch) + }] + ).generate + end + + def knative_serving_role_binding_resource + Gitlab::Kubernetes::RoleBinding.new( + name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, + role_name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, + role_kind: :Role, + namespace: service_account_namespace, + service_account_name: service_account_name + ).generate + end + end + end +end diff --git a/app/services/clusters/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/kubernetes/fetch_kubernetes_token_service.rb new file mode 100644 index 00000000000..aaf437abfad --- /dev/null +++ b/app/services/clusters/kubernetes/fetch_kubernetes_token_service.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Clusters + module Kubernetes + class FetchKubernetesTokenService + DEFAULT_TOKEN_RETRY_DELAY = 5.seconds + TOKEN_RETRY_LIMIT = 5 + + attr_reader :kubeclient, :service_account_token_name, :namespace + + def initialize(kubeclient, service_account_token_name, namespace, token_retry_delay: DEFAULT_TOKEN_RETRY_DELAY) + @kubeclient = kubeclient + @service_account_token_name = service_account_token_name + @namespace = namespace + @token_retry_delay = token_retry_delay + end + + def execute + # Kubernetes will create the Secret and set the token asynchronously + # so it is necessary to retry + # https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#token-controller + TOKEN_RETRY_LIMIT.times do + token_base64 = get_secret&.dig('data', 'token') + return Base64.decode64(token_base64) if token_base64 + + sleep @token_retry_delay + end + + nil + end + + private + + def get_secret + kubeclient.get_secret(service_account_token_name, namespace).as_json + rescue Kubeclient::ResourceNotFoundError + end + end + end +end diff --git a/app/services/clusters/kubernetes/kubernetes.rb b/app/services/clusters/kubernetes/kubernetes.rb new file mode 100644 index 00000000000..7d5d0c2c1d6 --- /dev/null +++ b/app/services/clusters/kubernetes/kubernetes.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Clusters + module Kubernetes + GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab' + GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default' + GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token' + GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' + GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin' + PROJECT_CLUSTER_ROLE_NAME = 'edit' + GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role' + GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding' + end +end diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb index 6ab4fca3854..f448d55f00a 100644 --- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb +++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb @@ -43,7 +43,7 @@ module Gitlab end def create_namespace - Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( + Clusters::Kubernetes::CreateOrUpdateNamespaceService.new( cluster: deployment_cluster, kubernetes_namespace: kubernetes_namespace || build_namespace_record ).execute diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index 84a8691a7f2..8891866c1f8 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -13,7 +13,7 @@ describe 'User Cluster', :js do gitlab_sign_in(user) allow(Groups::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } - allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) + allow_any_instance_of(Clusters::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected) end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 3899aab8170..84f2e3e09ae 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -13,7 +13,7 @@ describe 'User Cluster', :js do gitlab_sign_in(user) allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } - allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) + allow_any_instance_of(Clusters::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected) end diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index 775550f2acc..c7a5ac783b3 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -87,7 +87,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do .with(cluster, environment: deployment.environment) .and_return(namespace_builder) - expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService) + expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService) .to receive(:new) .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace) .and_return(service) @@ -107,7 +107,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do it 'creates a namespace using the tokenless record' do expect(Clusters::BuildKubernetesNamespaceService).not_to receive(:new) - expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService) + expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService) .to receive(:new) .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace) .and_return(service) @@ -123,7 +123,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end it 'does not create a namespace' do - expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new) + expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new) subject end diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb deleted file mode 100644 index e44cc3f5a78..00000000000 --- a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb +++ /dev/null @@ -1,142 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do - include KubernetesHelpers - - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:platform) { cluster.platform } - let(:api_url) { 'https://kubernetes.example.com' } - let(:project) { cluster.project } - let(:environment) { create(:environment, project: project) } - let(:cluster_project) { cluster.cluster_project } - let(:namespace) { "#{project.name}-#{project.id}-#{environment.slug}" } - - subject do - described_class.new( - cluster: cluster, - kubernetes_namespace: kubernetes_namespace - ).execute - end - - before do - stub_kubeclient_discover(api_url) - stub_kubeclient_get_namespace(api_url) - stub_kubeclient_get_service_account_error(api_url, 'gitlab') - stub_kubeclient_create_service_account(api_url) - stub_kubeclient_get_secret_error(api_url, 'gitlab-token') - stub_kubeclient_create_secret(api_url) - - stub_kubeclient_get_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace) - stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace) - stub_kubeclient_get_namespace(api_url, namespace: namespace) - stub_kubeclient_get_service_account_error(api_url, "#{namespace}-service-account", namespace: namespace) - stub_kubeclient_create_service_account(api_url, namespace: namespace) - stub_kubeclient_create_secret(api_url, namespace: namespace) - stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace) - stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace) - stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) - - stub_kubeclient_get_secret( - api_url, - { - metadata_name: "#{namespace}-token", - token: Base64.encode64('sample-token'), - namespace: namespace - } - ) - end - - shared_examples 'successful creation of kubernetes namespace' do - 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::CreateOrUpdateServiceAccountService).to receive(:execute).once - - subject - end - - it 'configures kubernetes token' do - subject - - kubernetes_namespace.reload - expect(kubernetes_namespace.namespace).to eq(namespace) - expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account") - expect(kubernetes_namespace.encrypted_service_account_token).to be_present - end - end - - context 'group clusters' do - let(:cluster) { create(:cluster, :group, :provided_by_gcp) } - let(:group) { cluster.group } - let(:project) { create(:project, group: group) } - - context 'when kubernetes namespace is not persisted' do - let(:kubernetes_namespace) do - build(:cluster_kubernetes_namespace, - cluster: cluster, - project: project, - environment: environment) - end - - it_behaves_like 'successful creation of kubernetes namespace' - end - end - - context 'project clusters' do - context 'when kubernetes namespace is not persisted' do - let(:kubernetes_namespace) do - build(:cluster_kubernetes_namespace, - cluster: cluster, - project: cluster_project.project, - cluster_project: cluster_project, - environment: environment) - end - - it_behaves_like 'successful creation of kubernetes namespace' - end - - context 'when there is a Kubernetes Namespace associated' do - let(:namespace) { "new-namespace-#{environment.slug}" } - - let(:kubernetes_namespace) do - create(:cluster_kubernetes_namespace, - cluster: cluster, - project: cluster_project.project, - cluster_project: cluster_project, - environment: environment) - end - - before do - platform.update_column(:namespace, 'new-namespace') - end - - it 'does not create any Clusters::KubernetesNamespace' do - subject - - expect(cluster.kubernetes_namespaces).to eq([kubernetes_namespace]) - end - - it 'creates project service account' do - expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once - - subject - end - - it 'updates Clusters::KubernetesNamespace' do - subject - - kubernetes_namespace.reload - - expect(kubernetes_namespace.namespace).to eq(namespace) - expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account") - expect(kubernetes_namespace.encrypted_service_account_token).to be_present - end - end - end -end diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb deleted file mode 100644 index 8b874989758..00000000000 --- a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb +++ /dev/null @@ -1,194 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService 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_get_service_account_error(api_url, service_account_name, namespace: namespace) - stub_kubeclient_create_service_account(api_url, namespace: namespace) - - stub_kubeclient_get_secret_error(api_url, token_name, 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 } - let(:cluster_role_binding_name) { 'gitlab-admin' } - - before do - cluster.platform_kubernetes.rbac! - - stub_kubeclient_get_cluster_role_binding_error(api_url, cluster_role_binding_name) - stub_kubeclient_create_cluster_role_binding(api_url) - end - - it_behaves_like 'creates service account and token' - - it 'creates 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 } - let(:role_binding_name) { "gitlab-#{namespace}"} - - before do - cluster.platform_kubernetes.rbac! - - stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace) - stub_kubeclient_create_role_binding(api_url, namespace: namespace) - stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace) - stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) - end - - it_behaves_like 'creates service account and token' - - it 'creates a namespaced role binding with edit access' do - subject - - expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings").with( - body: hash_including( - kind: 'RoleBinding', - metadata: { name: "gitlab-#{namespace}", namespace: "#{namespace}" }, - roleRef: { - apiGroup: 'rbac.authorization.k8s.io', - kind: 'ClusterRole', - name: 'edit' - }, - subjects: [ - { - kind: 'ServiceAccount', - name: service_account_name, - namespace: namespace - } - ] - ) - ) - end - - it 'creates a role and role binding granting knative serving permissions to the service account' do - subject - - expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME}").with( - body: hash_including( - metadata: { - name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, - namespace: namespace - }, - rules: [{ - apiGroups: %w(serving.knative.dev), - resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services), - verbs: %w(get list create update delete patch watch) - }] - ) - ) - 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 deleted file mode 100644 index 93c0dc37ade..00000000000 --- a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do - include KubernetesHelpers - - describe '#execute' do - let(:api_url) { 'http://111.111.111.111' } - let(:namespace) { 'my-namespace' } - let(:service_account_token_name) { 'gitlab-token' } - - let(:kubeclient) do - Gitlab::Kubernetes::KubeClient.new( - api_url, - auth_options: { username: 'admin', password: 'xxx' } - ) - end - - subject { described_class.new(kubeclient, service_account_token_name, namespace, token_retry_delay: 0).execute } - - before do - stub_kubeclient_discover(api_url) - end - - context 'when params correct' do - let(:decoded_token) { 'xxx.token.xxx' } - let(:token) { Base64.encode64(decoded_token) } - context 'when the secret exists' do - before do - 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 there is a 500 error' do - before do - stub_kubeclient_get_secret_error(api_url, service_account_token_name, namespace: namespace, status: 500) - end - - it { expect { subject }.to raise_error(Kubeclient::HttpError) } - end - - context 'when the secret does not exist on the first try' do - before do - stub_kubeclient_get_secret_not_found_then_found( - api_url, - { - metadata_name: service_account_token_name, - namespace: namespace, - token: token - } - ) - end - - it 'retries and finds the token' do - expect(subject).to eq(decoded_token) - end - end - - context 'when the secret permanently does not exist' do - before do - stub_kubeclient_get_secret_error(api_url, service_account_token_name, namespace: namespace, status: 404) - end - - it { is_expected.to be_nil } - end - - context 'when the secret is missing a token on the first try' do - before do - stub_kubeclient_get_secret_missing_token_then_with_token( - api_url, - { - metadata_name: service_account_token_name, - namespace: namespace, - token: token - } - ) - end - - it 'retries and finds the token' do - expect(subject).to eq(decoded_token) - end - end - - context 'when the secret is permanently missing a token' do - before do - stub_kubeclient_get_secret( - api_url, - { - metadata_name: service_account_token_name, - namespace: namespace, - token: nil - } - ) - end - - it { is_expected.to be_nil } - end - end - end -end diff --git a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb new file mode 100644 index 00000000000..5a3b1cd6cfb --- /dev/null +++ b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::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(:environment) { create(:environment, project: project) } + let(:cluster_project) { cluster.cluster_project } + let(:namespace) { "#{project.name}-#{project.id}-#{environment.slug}" } + + subject do + described_class.new( + cluster: cluster, + kubernetes_namespace: kubernetes_namespace + ).execute + end + + before do + stub_kubeclient_discover(api_url) + stub_kubeclient_get_namespace(api_url) + stub_kubeclient_get_service_account_error(api_url, 'gitlab') + stub_kubeclient_create_service_account(api_url) + stub_kubeclient_get_secret_error(api_url, 'gitlab-token') + stub_kubeclient_create_secret(api_url) + + stub_kubeclient_get_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace) + stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace) + stub_kubeclient_get_namespace(api_url, namespace: namespace) + stub_kubeclient_get_service_account_error(api_url, "#{namespace}-service-account", namespace: namespace) + stub_kubeclient_create_service_account(api_url, namespace: namespace) + stub_kubeclient_create_secret(api_url, namespace: namespace) + stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace) + stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace) + stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) + + stub_kubeclient_get_secret( + api_url, + { + metadata_name: "#{namespace}-token", + token: Base64.encode64('sample-token'), + namespace: namespace + } + ) + end + + shared_examples 'successful creation of kubernetes namespace' do + 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::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once + + subject + end + + it 'configures kubernetes token' do + subject + + kubernetes_namespace.reload + expect(kubernetes_namespace.namespace).to eq(namespace) + expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account") + expect(kubernetes_namespace.encrypted_service_account_token).to be_present + end + end + + context 'group clusters' do + let(:cluster) { create(:cluster, :group, :provided_by_gcp) } + let(:group) { cluster.group } + let(:project) { create(:project, group: group) } + + context 'when kubernetes namespace is not persisted' do + let(:kubernetes_namespace) do + build(:cluster_kubernetes_namespace, + cluster: cluster, + project: project, + environment: environment) + end + + it_behaves_like 'successful creation of kubernetes namespace' + end + end + + context 'project clusters' do + context 'when kubernetes namespace is not persisted' do + let(:kubernetes_namespace) do + build(:cluster_kubernetes_namespace, + cluster: cluster, + project: cluster_project.project, + cluster_project: cluster_project, + environment: environment) + end + + it_behaves_like 'successful creation of kubernetes namespace' + end + + context 'when there is a Kubernetes Namespace associated' do + let(:namespace) { "new-namespace-#{environment.slug}" } + + let(:kubernetes_namespace) do + create(:cluster_kubernetes_namespace, + cluster: cluster, + project: cluster_project.project, + cluster_project: cluster_project, + environment: environment) + end + + before do + platform.update_column(:namespace, 'new-namespace') + end + + it 'does not create any Clusters::KubernetesNamespace' do + subject + + expect(cluster.kubernetes_namespaces).to eq([kubernetes_namespace]) + end + + it 'creates project service account' do + expect_any_instance_of(Clusters::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once + + subject + end + + it 'updates Clusters::KubernetesNamespace' do + subject + + kubernetes_namespace.reload + + expect(kubernetes_namespace.namespace).to eq(namespace) + expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account") + expect(kubernetes_namespace.encrypted_service_account_token).to be_present + end + end + end +end diff --git a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb new file mode 100644 index 00000000000..10dbfc800ff --- /dev/null +++ b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService 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_get_service_account_error(api_url, service_account_name, namespace: namespace) + stub_kubeclient_create_service_account(api_url, namespace: namespace) + + stub_kubeclient_get_secret_error(api_url, token_name, 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 } + let(:cluster_role_binding_name) { 'gitlab-admin' } + + before do + cluster.platform_kubernetes.rbac! + + stub_kubeclient_get_cluster_role_binding_error(api_url, cluster_role_binding_name) + stub_kubeclient_create_cluster_role_binding(api_url) + end + + it_behaves_like 'creates service account and token' + + it 'creates 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 } + let(:role_binding_name) { "gitlab-#{namespace}"} + + before do + cluster.platform_kubernetes.rbac! + + stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace) + stub_kubeclient_create_role_binding(api_url, namespace: namespace) + stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace) + stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) + end + + it_behaves_like 'creates service account and token' + + it 'creates a namespaced role binding with edit access' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings").with( + body: hash_including( + kind: 'RoleBinding', + metadata: { name: "gitlab-#{namespace}", namespace: "#{namespace}" }, + roleRef: { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'ClusterRole', + name: 'edit' + }, + subjects: [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] + ) + ) + end + + it 'creates a role and role binding granting knative serving permissions to the service account' do + subject + + expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME}").with( + body: hash_including( + metadata: { + name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, + namespace: namespace + }, + rules: [{ + apiGroups: %w(serving.knative.dev), + resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services), + verbs: %w(get list create update delete patch watch) + }] + ) + ) + end + end + end +end diff --git a/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb new file mode 100644 index 00000000000..145528616ee --- /dev/null +++ b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Kubernetes::FetchKubernetesTokenService do + include KubernetesHelpers + + describe '#execute' do + let(:api_url) { 'http://111.111.111.111' } + let(:namespace) { 'my-namespace' } + let(:service_account_token_name) { 'gitlab-token' } + + let(:kubeclient) do + Gitlab::Kubernetes::KubeClient.new( + api_url, + auth_options: { username: 'admin', password: 'xxx' } + ) + end + + subject { described_class.new(kubeclient, service_account_token_name, namespace, token_retry_delay: 0).execute } + + before do + stub_kubeclient_discover(api_url) + end + + context 'when params correct' do + let(:decoded_token) { 'xxx.token.xxx' } + let(:token) { Base64.encode64(decoded_token) } + context 'when the secret exists' do + before do + 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 there is a 500 error' do + before do + stub_kubeclient_get_secret_error(api_url, service_account_token_name, namespace: namespace, status: 500) + end + + it { expect { subject }.to raise_error(Kubeclient::HttpError) } + end + + context 'when the secret does not exist on the first try' do + before do + stub_kubeclient_get_secret_not_found_then_found( + api_url, + { + metadata_name: service_account_token_name, + namespace: namespace, + token: token + } + ) + end + + it 'retries and finds the token' do + expect(subject).to eq(decoded_token) + end + end + + context 'when the secret permanently does not exist' do + before do + stub_kubeclient_get_secret_error(api_url, service_account_token_name, namespace: namespace, status: 404) + end + + it { is_expected.to be_nil } + end + + context 'when the secret is missing a token on the first try' do + before do + stub_kubeclient_get_secret_missing_token_then_with_token( + api_url, + { + metadata_name: service_account_token_name, + namespace: namespace, + token: token + } + ) + end + + it 'retries and finds the token' do + expect(subject).to eq(decoded_token) + end + end + + context 'when the secret is permanently missing a token' do + before do + stub_kubeclient_get_secret( + api_url, + { + metadata_name: service_account_token_name, + namespace: namespace, + token: nil + } + ) + end + + it { is_expected.to be_nil } + end + end + end +end -- cgit v1.2.1