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
This commit is contained in:
Tiger 2019-08-22 16:40:17 +10:00
parent 5822c09296
commit 9e8daeb8a6
16 changed files with 259 additions and 267 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -2,7 +2,7 @@
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
include KubernetesHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
@ -35,8 +35,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
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_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,
@ -56,7 +56,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
end
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
expect_any_instance_of(Clusters::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
subject
end
@ -123,7 +123,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
end
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
expect_any_instance_of(Clusters::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
subject
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
include KubernetesHelpers
let(:api_url) { 'http://111.111.111.111' }
@ -143,8 +143,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
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)
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'
@ -175,10 +175,10 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
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(
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::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
namespace: namespace
},
rules: [{

View File

@ -2,7 +2,7 @@
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do
describe Clusters::Kubernetes::FetchKubernetesTokenService do
include KubernetesHelpers
describe '#execute' do