diff options
author | Thong Kuah <tkuah@gitlab.com> | 2018-11-02 15:46:15 +0000 |
---|---|---|
committer | Kamil TrzciĆski <ayufan@ayufan.eu> | 2018-11-02 15:46:15 +0000 |
commit | 5ede567d718bcf69a204dee83155399a401cb465 (patch) | |
tree | 932ca30df7e32ab92a664ebfd2b9284641061e73 /app | |
parent | 2a89f065a478839e330d1f0c5f314ddf8489d77b (diff) | |
download | gitlab-ce-5ede567d718bcf69a204dee83155399a401cb465.tar.gz |
Incorporates Kubernetes Namespace into Cluster's flow
Diffstat (limited to 'app')
-rw-r--r-- | app/models/clusters/cluster.rb | 8 | ||||
-rw-r--r-- | app/models/clusters/kubernetes_namespace.rb | 35 | ||||
-rw-r--r-- | app/models/clusters/platforms/kubernetes.rb | 36 | ||||
-rw-r--r-- | app/models/project.rb | 2 | ||||
-rw-r--r-- | app/models/project_services/kubernetes_service.rb | 7 | ||||
-rw-r--r-- | app/services/clusters/gcp/finalize_creation_service.rb | 23 | ||||
-rw-r--r-- | app/services/clusters/gcp/kubernetes.rb | 11 | ||||
-rw-r--r-- | app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb | 54 | ||||
-rw-r--r-- | app/services/clusters/gcp/kubernetes/create_service_account_service.rb | 78 | ||||
-rw-r--r-- | app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb | 8 | ||||
-rw-r--r-- | app/workers/all_queues.yml | 1 | ||||
-rw-r--r-- | app/workers/cluster_platform_configure_worker.rb | 22 | ||||
-rw-r--r-- | app/workers/cluster_provision_worker.rb | 2 |
13 files changed, 246 insertions, 41 deletions
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 222e4217e67..2bd373e0950 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -19,6 +19,7 @@ module Clusters has_many :cluster_projects, class_name: 'Clusters::Project' has_many :projects, through: :cluster_projects, class_name: '::Project' + has_one :cluster_project, -> { order(id: :desc) }, class_name: 'Clusters::Project' has_many :cluster_groups, class_name: 'Clusters::Group' has_many :groups, through: :cluster_groups, class_name: '::Group' @@ -128,6 +129,13 @@ module Clusters platform_kubernetes.kubeclient if kubernetes? end + def find_or_initialize_kubernetes_namespace(cluster_project) + kubernetes_namespaces.find_or_initialize_by( + project: cluster_project.project, + cluster_project: cluster_project + ) + end + private def restrict_modification diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb index fb5f6b65d9d..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 @@ module Clusters validates :namespace, presence: true validates :namespace, uniqueness: { scope: :cluster_id } - before_validation :set_namespace_and_service_account_to_default, on: :create + delegate :ca_pem, to: :platform_kubernetes, allow_nil: true + delegate :api_url, to: :platform_kubernetes, allow_nil: true attr_encrypted :service_account_token, mode: :per_attribute_iv, @@ -23,14 +26,26 @@ module Clusters "#{namespace}-token" end - private + def configure_predefined_credentials + self.namespace = kubernetes_or_project_namespace + self.service_account_name = default_service_account_name + end + + def 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 @@ -45,5 +60,13 @@ module Clusters def project_slug "#{project.path}-#{project.id}".downcase end + + def kubeconfig + to_kubeconfig( + url: api_url, + namespace: namespace, + token: service_account_token, + ca_pem: ca_pem) + end end end diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index f0f791742f4..008e08d9914 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -6,6 +6,7 @@ module Clusters include Gitlab::Kubernetes include ReactiveCaching include EnumWithNil + include AfterCommitQueue RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze @@ -43,6 +44,7 @@ module Clusters validate :prevent_modification, on: :update after_save :clear_reactive_cache! + after_update :update_kubernetes_namespace alias_attribute :ca_pem, :ca_cert @@ -67,21 +69,31 @@ module Clusters end end - def predefined_variables - config = YAML.dump(kubeconfig) - + def predefined_variables(project:) 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 + + if kubernetes_namespace = cluster.kubernetes_namespaces.find_by(project: project) + variables.concat(kubernetes_namespace.predefined_variables) + else + # From 11.5, every Clusters::Project should have at least one + # Clusters::KubernetesNamespace, so once migration has been completed, + # this 'else' branch will be removed. For more information, please see + # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22433 + config = YAML.dump(kubeconfig) + + 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 @@ -199,6 +211,14 @@ module Clusters true end + + def update_kubernetes_namespace + return unless namespace_changed? + + run_after_commit do + ClusterPlatformConfigureWorker.perform_async(cluster_id) + end + end end end end diff --git a/app/models/project.rb b/app/models/project.rb index e2e309e8496..fa995b5b061 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1829,7 +1829,7 @@ class Project < ActiveRecord::Base end def deployment_variables(environment: nil) - deployment_platform(environment: environment)&.predefined_variables || [] + deployment_platform(environment: environment)&.predefined_variables(project: self) || [] end def auto_devops_variables diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 798944d0c06..3459ded7ccf 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -104,7 +104,12 @@ class KubernetesService < DeploymentService { success: false, result: err } end - def predefined_variables + # Project param was added on + # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011, + # as a way to keep this service compatible with + # Clusters::Platforms::Kubernetes, it won't be used on this method + # as it's only needed for Clusters::Cluster. + def predefined_variables(project:) config = YAML.dump(kubeconfig) Gitlab::Ci::Variables::Collection.new.tap do |variables| diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index 6ee63db8eb9..3df43657fa0 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -11,8 +11,9 @@ module Clusters configure_provider create_gitlab_service_account! configure_kubernetes - cluster.save! + configure_project_service_account + rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") rescue Kubeclient::HttpError => e @@ -24,7 +25,10 @@ module Clusters private def create_gitlab_service_account! - Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute + Clusters::Gcp::Kubernetes::CreateServiceAccountService.gitlab_creator( + kube_client, + rbac: create_rbac_cluster? + ).execute end def configure_provider @@ -44,7 +48,20 @@ module Clusters end def request_kubernetes_token - Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute + Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new( + kube_client, + Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, + Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE + ).execute + end + + def configure_project_service_account + kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace(cluster.cluster_project) + + Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( + cluster: cluster, + kubernetes_namespace: kubernetes_namespace + ).execute end def authorization_type diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb index d014d73b3e8..90ed529670c 100644 --- a/app/services/clusters/gcp/kubernetes.rb +++ b/app/services/clusters/gcp/kubernetes.rb @@ -3,11 +3,12 @@ module Clusters module Gcp module Kubernetes - SERVICE_ACCOUNT_NAME = 'gitlab' - SERVICE_ACCOUNT_NAMESPACE = 'default' - SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token' - CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' - CLUSTER_ROLE_NAME = 'cluster-admin' + GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab' + GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default' + GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token' + GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' + GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin' + PROJECT_CLUSTER_ROLE_NAME = 'edit' end end end 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 index d17744591e6..dfc4bf7a358 100644 --- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb @@ -4,46 +4,96 @@ module Clusters module Gcp module Kubernetes class CreateServiceAccountService - attr_reader :kubeclient, :rbac - - def initialize(kubeclient, rbac:) + def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil) @kubeclient = kubeclient + @service_account_name = service_account_name + @service_account_namespace = service_account_namespace + @token_name = token_name @rbac = rbac + @namespace_creator = namespace_creator + @role_binding_name = role_binding_name + end + + def self.gitlab_creator(kubeclient, rbac:) + self.new( + kubeclient, + service_account_name: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME, + service_account_namespace: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE, + token_name: Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, + rbac: rbac + ) + end + + def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:) + self.new( + kubeclient, + service_account_name: service_account_name, + service_account_namespace: service_account_namespace, + token_name: "#{service_account_namespace}-token", + rbac: rbac, + namespace_creator: true, + role_binding_name: "gitlab-#{service_account_namespace}" + ) end def execute + ensure_project_namespace_exists if namespace_creator kubeclient.create_service_account(service_account_resource) kubeclient.create_secret(service_account_token_resource) - kubeclient.create_cluster_role_binding(cluster_role_binding_resource) if rbac + create_role_or_cluster_role_binding if rbac end private + attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name + + def ensure_project_namespace_exists + Gitlab::Kubernetes::Namespace.new( + service_account_namespace, + kubeclient + ).ensure_exists! + end + + def create_role_or_cluster_role_binding + if namespace_creator + kubeclient.create_role_binding(role_binding_resource) + else + kubeclient.create_cluster_role_binding(cluster_role_binding_resource) + end + end + def service_account_resource - Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate + Gitlab::Kubernetes::ServiceAccount.new( + service_account_name, + service_account_namespace + ).generate end def service_account_token_resource Gitlab::Kubernetes::ServiceAccountToken.new( - SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_namespace).generate + token_name, + service_account_name, + service_account_namespace + ).generate end def cluster_role_binding_resource subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }] Gitlab::Kubernetes::ClusterRoleBinding.new( - CLUSTER_ROLE_BINDING_NAME, - CLUSTER_ROLE_NAME, + Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME, + Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME, subjects ).generate end - def service_account_name - SERVICE_ACCOUNT_NAME - end - - def service_account_namespace - SERVICE_ACCOUNT_NAMESPACE + def role_binding_resource + Gitlab::Kubernetes::RoleBinding.new( + name: role_binding_name, + role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME, + namespace: service_account_namespace, + service_account_name: service_account_name + ).generate end end end 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 @@ module Clusters private def get_secret - kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json + kubeclient.get_secret(service_account_token_name, namespace).as_json rescue Kubeclient::HttpError => err raise err unless err.error_code == 404 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..68e8335a09d --- /dev/null +++ b/app/workers/cluster_platform_configure_worker.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ClusterPlatformConfigureWorker + include ApplicationWorker + include ClusterQueue + + def perform(cluster_id) + Clusters::Cluster.find_by_id(cluster_id).try do |cluster| + next unless cluster.cluster_project + + kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace(cluster.cluster_project) + + Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( + cluster: cluster, + kubernetes_namespace: kubernetes_namespace + ).execute + end + + rescue ::Kubeclient::HttpError => err + Rails.logger.error "Failed to create/update Kubernetes Namespace. id: #{kubernetes_namespace.id} message: #{err.message}" + end +end 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 @@ class ClusterProvisionWorker cluster.provider.try do |provider| Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp? end + + ClusterPlatformConfigureWorker.perform_async(cluster.id) if cluster.user? end end end |