summaryrefslogtreecommitdiff
path: root/app/services/clusters
diff options
context:
space:
mode:
Diffstat (limited to 'app/services/clusters')
-rw-r--r--app/services/clusters/aws/authorize_role_service.rb74
-rw-r--r--app/services/clusters/aws/fetch_credentials_service.rb80
-rw-r--r--app/services/clusters/aws/finalize_creation_service.rb139
-rw-r--r--app/services/clusters/aws/provision_service.rb85
-rw-r--r--app/services/clusters/aws/verify_provision_status_service.rb50
-rw-r--r--app/services/clusters/create_service.rb4
-rw-r--r--app/services/clusters/gcp/fetch_operation_service.rb31
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb127
-rw-r--r--app/services/clusters/gcp/provision_service.rb56
-rw-r--r--app/services/clusters/gcp/verify_provision_status_service.rb50
10 files changed, 1 insertions, 695 deletions
diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb
deleted file mode 100644
index 7ca20289bf7..00000000000
--- a/app/services/clusters/aws/authorize_role_service.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class AuthorizeRoleService
- attr_reader :user
-
- Response = Struct.new(:status, :body)
-
- ERRORS = [
- ActiveRecord::RecordInvalid,
- ActiveRecord::RecordNotFound,
- Clusters::Aws::FetchCredentialsService::MissingRoleError,
- ::Aws::Errors::MissingCredentialsError,
- ::Aws::STS::Errors::ServiceError
- ].freeze
-
- def initialize(user, params:)
- @user = user
- @role_arn = params[:role_arn]
- @region = params[:region]
- end
-
- def execute
- ensure_role_exists!
- update_role_arn!
-
- Response.new(:ok, credentials)
- rescue *ERRORS => e
- Gitlab::ErrorTracking.track_exception(e)
-
- Response.new(:unprocessable_entity, response_details(e))
- end
-
- private
-
- attr_reader :role, :role_arn, :region
-
- def ensure_role_exists!
- @role = ::Aws::Role.find_by_user_id!(user.id)
- end
-
- def update_role_arn!
- role.update!(role_arn: role_arn, region: region)
- end
-
- def credentials
- Clusters::Aws::FetchCredentialsService.new(role).execute
- end
-
- def response_details(exception)
- message =
- case exception
- when ::Aws::STS::Errors::AccessDenied
- _("Access denied: %{error}") % { error: exception.message }
- when ::Aws::STS::Errors::ServiceError
- _("AWS service error: %{error}") % { error: exception.message }
- when ActiveRecord::RecordNotFound
- _("Error: Unable to find AWS role for current user")
- when ActiveRecord::RecordInvalid
- exception.message
- when Clusters::Aws::FetchCredentialsService::MissingRoleError
- _("Error: No AWS provision role found for user")
- when ::Aws::Errors::MissingCredentialsError
- _("Error: No AWS credentials were supplied")
- else
- _('An error occurred while authorizing your role')
- end
-
- { message: message }.compact
- end
- end
- end
-end
diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb
deleted file mode 100644
index e38852c7ec7..00000000000
--- a/app/services/clusters/aws/fetch_credentials_service.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class FetchCredentialsService
- attr_reader :provision_role
-
- MissingRoleError = Class.new(StandardError)
-
- def initialize(provision_role, provider: nil)
- @provision_role = provision_role
- @provider = provider
- @region = provider&.region || provision_role&.region || Clusters::Providers::Aws::DEFAULT_REGION
- end
-
- def execute
- raise MissingRoleError, 'AWS provisioning role not configured' unless provision_role.present?
-
- ::Aws::AssumeRoleCredentials.new(
- client: client,
- role_arn: provision_role.role_arn,
- role_session_name: session_name,
- external_id: provision_role.role_external_id,
- policy: session_policy
- ).credentials
- end
-
- private
-
- attr_reader :provider, :region
-
- def client
- ::Aws::STS::Client.new(**client_args)
- end
-
- def client_args
- { region: region, credentials: gitlab_credentials }.compact
- end
-
- def gitlab_credentials
- # These are not needed for IAM instance profiles
- return unless access_key_id.present? && secret_access_key.present?
-
- ::Aws::Credentials.new(access_key_id, secret_access_key)
- end
-
- def access_key_id
- Gitlab::CurrentSettings.eks_access_key_id
- end
-
- def secret_access_key
- Gitlab::CurrentSettings.eks_secret_access_key
- end
-
- ##
- # If we haven't created a provider record yet,
- # we restrict ourselves to read-only access so
- # that we can safely expose credentials to the
- # frontend (to be used when populating the
- # creation form).
- def session_policy
- if provider.nil?
- File.read(read_only_policy)
- end
- end
-
- def read_only_policy
- Rails.root.join('vendor', 'aws', 'iam', "eks_cluster_read_only_policy.json")
- end
-
- def session_name
- if provider.present?
- "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provision_role.user_id}"
- else
- "gitlab-eks-autofill-user-#{provision_role.user_id}"
- end
- end
- end
- end
-end
diff --git a/app/services/clusters/aws/finalize_creation_service.rb b/app/services/clusters/aws/finalize_creation_service.rb
deleted file mode 100644
index 54f07e1d44c..00000000000
--- a/app/services/clusters/aws/finalize_creation_service.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class FinalizeCreationService
- include Gitlab::Utils::StrongMemoize
-
- attr_reader :provider
-
- delegate :cluster, to: :provider
-
- def execute(provider)
- @provider = provider
-
- configure_provider
- create_gitlab_service_account!
- configure_platform_kubernetes
- configure_node_authentication!
-
- cluster.save!
- rescue ::Aws::CloudFormation::Errors::ServiceError => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to fetch CloudFormation stack: %{message}') % { message: e.message })
- rescue Kubeclient::HttpError => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message })
- rescue ActiveRecord::RecordInvalid => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to configure EKS provider: %{message}') % { message: e.message })
- end
-
- private
-
- def create_gitlab_service_account!
- Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator(
- kube_client,
- rbac: true
- ).execute
- end
-
- def configure_provider
- provider.status_event = :make_created
- end
-
- def configure_platform_kubernetes
- cluster.build_platform_kubernetes(
- api_url: cluster_endpoint,
- ca_cert: cluster_certificate,
- token: request_kubernetes_token)
- end
-
- def request_kubernetes_token
- Clusters::Kubernetes::FetchKubernetesTokenService.new(
- kube_client,
- Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
- Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
- ).execute
- end
-
- def kube_client
- @kube_client ||= build_kube_client!(
- cluster_endpoint,
- cluster_certificate
- )
- end
-
- def build_kube_client!(api_url, ca_pem)
- raise "Incomplete settings" unless api_url
-
- Gitlab::Kubernetes::KubeClient.new(
- api_url,
- auth_options: kubeclient_auth_options,
- ssl_options: kubeclient_ssl_options(ca_pem),
- http_proxy_uri: ENV['http_proxy']
- )
- end
-
- def kubeclient_auth_options
- { bearer_token: Kubeclient::AmazonEksCredentials.token(provider.credentials, cluster.name) }
- end
-
- def kubeclient_ssl_options(ca_pem)
- opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
-
- if ca_pem.present?
- opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
- end
-
- opts
- end
-
- def cluster_stack
- @cluster_stack ||= provider.api_client.describe_stacks(stack_name: provider.cluster.name).stacks.first
- end
-
- def stack_output_value(key)
- cluster_stack.outputs.detect { |output| output.output_key == key }.output_value
- end
-
- def node_instance_role_arn
- stack_output_value('NodeInstanceRole')
- end
-
- def cluster_endpoint
- strong_memoize(:cluster_endpoint) do
- stack_output_value('ClusterEndpoint')
- end
- end
-
- def cluster_certificate
- strong_memoize(:cluster_certificate) do
- Base64.decode64(stack_output_value('ClusterCertificate'))
- end
- end
-
- def configure_node_authentication!
- kube_client.create_config_map(node_authentication_config)
- end
-
- def node_authentication_config
- Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth.new(node_instance_role_arn).generate
- end
-
- def logger
- @logger ||= Gitlab::Kubernetes::Logger.build
- end
-
- def log_service_error(exception, provider_id, message)
- logger.error(
- exception: exception.class.name,
- service: self.class.name,
- provider_id: provider_id,
- message: message
- )
- end
- end
- end
-end
diff --git a/app/services/clusters/aws/provision_service.rb b/app/services/clusters/aws/provision_service.rb
deleted file mode 100644
index b454a7a5f59..00000000000
--- a/app/services/clusters/aws/provision_service.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class ProvisionService
- attr_reader :provider
-
- def execute(provider)
- @provider = provider
-
- configure_provider_credentials
- provision_cluster
-
- if provider.make_creating
- WaitForClusterCreationWorker.perform_in(
- Clusters::Aws::VerifyProvisionStatusService::INITIAL_INTERVAL,
- provider.cluster_id
- )
- else
- provider.make_errored!("Failed to update provider record; #{provider.errors.full_messages}")
- end
- rescue Clusters::Aws::FetchCredentialsService::MissingRoleError
- provider.make_errored!('Amazon role is not configured')
- rescue ::Aws::Errors::MissingCredentialsError
- provider.make_errored!('Amazon credentials are not configured')
- rescue ::Aws::STS::Errors::ServiceError => e
- provider.make_errored!("Amazon authentication failed; #{e.message}")
- rescue ::Aws::CloudFormation::Errors::ServiceError => e
- provider.make_errored!("Amazon CloudFormation request failed; #{e.message}")
- end
-
- private
-
- def provision_role
- provider.created_by_user&.aws_role
- end
-
- def credentials
- @credentials ||= Clusters::Aws::FetchCredentialsService.new(
- provision_role,
- provider: provider
- ).execute
- end
-
- def configure_provider_credentials
- provider.update!(
- access_key_id: credentials.access_key_id,
- secret_access_key: credentials.secret_access_key,
- session_token: credentials.session_token
- )
- end
-
- def provision_cluster
- provider.api_client.create_stack(
- stack_name: provider.cluster.name,
- template_body: stack_template,
- parameters: parameters,
- capabilities: ["CAPABILITY_IAM"]
- )
- end
-
- def parameters
- [
- parameter('ClusterName', provider.cluster.name),
- parameter('ClusterRole', provider.role_arn),
- parameter('KubernetesVersion', provider.kubernetes_version),
- parameter('ClusterControlPlaneSecurityGroup', provider.security_group_id),
- parameter('VpcId', provider.vpc_id),
- parameter('Subnets', provider.subnet_ids.join(',')),
- parameter('NodeAutoScalingGroupDesiredCapacity', provider.num_nodes.to_s),
- parameter('NodeInstanceType', provider.instance_type),
- parameter('KeyName', provider.key_name)
- ]
- end
-
- def parameter(key, value)
- { parameter_key: key, parameter_value: value }
- end
-
- def stack_template
- File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
- end
- end
- end
-end
diff --git a/app/services/clusters/aws/verify_provision_status_service.rb b/app/services/clusters/aws/verify_provision_status_service.rb
deleted file mode 100644
index 99532662bc4..00000000000
--- a/app/services/clusters/aws/verify_provision_status_service.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class VerifyProvisionStatusService
- attr_reader :provider
-
- INITIAL_INTERVAL = 5.minutes
- POLL_INTERVAL = 1.minute
- TIMEOUT = 30.minutes
-
- def execute(provider)
- @provider = provider
-
- case cluster_stack.stack_status
- when 'CREATE_IN_PROGRESS'
- continue_creation
- when 'CREATE_COMPLETE'
- finalize_creation
- else
- provider.make_errored!("Unexpected status; #{cluster_stack.stack_status}")
- end
- rescue ::Aws::CloudFormation::Errors::ServiceError => e
- provider.make_errored!("Amazon CloudFormation request failed; #{e.message}")
- end
-
- private
-
- def cluster_stack
- @cluster_stack ||= provider.api_client.describe_stacks(stack_name: provider.cluster.name).stacks.first
- end
-
- def continue_creation
- if timeout_threshold.future?
- WaitForClusterCreationWorker.perform_in(POLL_INTERVAL, provider.cluster_id)
- else
- provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT })
- end
- end
-
- def timeout_threshold
- cluster_stack.creation_time + TIMEOUT
- end
-
- def finalize_creation
- Clusters::Aws::FinalizeCreationService.new.execute(provider)
- end
- end
- end
-end
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index cb2de8b943c..4c7384806ad 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -24,9 +24,7 @@ module Clusters
return cluster if cluster.errors.present?
- cluster.tap do |cluster|
- cluster.save && ClusterProvisionWorker.perform_async(cluster.id)
- end
+ cluster.tap(&:save)
end
private
diff --git a/app/services/clusters/gcp/fetch_operation_service.rb b/app/services/clusters/gcp/fetch_operation_service.rb
deleted file mode 100644
index 6c648b443a0..00000000000
--- a/app/services/clusters/gcp/fetch_operation_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class FetchOperationService
- def execute(provider)
- operation = provider.api_client.projects_zones_operations(
- provider.gcp_project_id,
- provider.zone,
- provider.operation_id)
-
- yield(operation) if block_given?
- rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
- logger.error(
- exception: e.class.name,
- service: self.class.name,
- provider_id: provider.id,
- message: e.message
- )
-
- provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
- end
-
- private
-
- def logger
- @logger ||= Gitlab::Kubernetes::Logger.build
- end
- end
- end
-end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
deleted file mode 100644
index 73d6fc4dc8f..00000000000
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class FinalizeCreationService
- attr_reader :provider
-
- def execute(provider)
- @provider = provider
-
- configure_provider
- create_gitlab_service_account!
- configure_kubernetes
- configure_pre_installed_knative if provider.knative_pre_installed?
- cluster.save!
- rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to request to Google Cloud Platform: %{message}') % { message: e.message })
- rescue Kubeclient::HttpError => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message })
- rescue ActiveRecord::RecordInvalid => e
- log_service_error(e.class.name, provider.id, e.message)
- provider.make_errored!(s_('ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}') % { message: e.message })
- end
-
- private
-
- def create_gitlab_service_account!
- Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator(
- kube_client,
- rbac: create_rbac_cluster?
- ).execute
- end
-
- def configure_provider
- provider.endpoint = gke_cluster.endpoint
- provider.status_event = :make_created
- end
-
- def configure_kubernetes
- cluster.platform_type = :kubernetes
- cluster.build_platform_kubernetes(
- api_url: 'https://' + gke_cluster.endpoint,
- ca_cert: Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
- authorization_type: authorization_type,
- token: request_kubernetes_token)
- end
-
- def configure_pre_installed_knative
- knative = cluster.build_application_knative(
- hostname: 'example.com'
- )
- knative.make_pre_installed!
- end
-
- def request_kubernetes_token
- Clusters::Kubernetes::FetchKubernetesTokenService.new(
- kube_client,
- Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
- Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
- ).execute
- end
-
- def authorization_type
- create_rbac_cluster? ? 'rbac' : 'abac'
- end
-
- def create_rbac_cluster?
- !provider.legacy_abac?
- end
-
- def kube_client
- @kube_client ||= build_kube_client!(
- 'https://' + gke_cluster.endpoint,
- Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate)
- )
- end
-
- def build_kube_client!(api_url, ca_pem)
- raise "Incomplete settings" unless api_url
-
- Gitlab::Kubernetes::KubeClient.new(
- api_url,
- auth_options: { bearer_token: provider.access_token },
- ssl_options: kubeclient_ssl_options(ca_pem),
- http_proxy_uri: ENV['http_proxy']
- )
- end
-
- def kubeclient_ssl_options(ca_pem)
- opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
-
- if ca_pem.present?
- opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
- end
-
- opts
- end
-
- def gke_cluster
- @gke_cluster ||= provider.api_client.projects_zones_clusters_get(
- provider.gcp_project_id,
- provider.zone,
- cluster.name)
- end
-
- def cluster
- @cluster ||= provider.cluster
- end
-
- def logger
- @logger ||= Gitlab::Kubernetes::Logger.build
- end
-
- def log_service_error(exception, provider_id, message)
- logger.error(
- exception: exception.class.name,
- service: self.class.name,
- provider_id: provider_id,
- message: message
- )
- end
- end
- end
-end
diff --git a/app/services/clusters/gcp/provision_service.rb b/app/services/clusters/gcp/provision_service.rb
deleted file mode 100644
index 7dc2d3c32f1..00000000000
--- a/app/services/clusters/gcp/provision_service.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class ProvisionService
- CLOUD_RUN_ADDONS = %i[http_load_balancing istio_config cloud_run_config].freeze
-
- attr_reader :provider
-
- def execute(provider)
- @provider = provider
-
- get_operation_id do |operation_id|
- if provider.make_creating(operation_id)
- WaitForClusterCreationWorker.perform_in(
- Clusters::Gcp::VerifyProvisionStatusService::INITIAL_INTERVAL,
- provider.cluster_id)
- else
- provider.make_errored!("Failed to update provider record; #{provider.errors}")
- end
- end
- end
-
- private
-
- def get_operation_id
- enable_addons = provider.cloud_run? ? CLOUD_RUN_ADDONS : []
-
- operation = provider.api_client.projects_zones_clusters_create(
- provider.gcp_project_id,
- provider.zone,
- provider.cluster.name,
- provider.num_nodes,
- machine_type: provider.machine_type,
- legacy_abac: provider.legacy_abac,
- enable_addons: enable_addons
- )
-
- unless operation.status == 'PENDING' || operation.status == 'RUNNING'
- return provider.make_errored!("Operation status is unexpected; #{operation.status_message}")
- end
-
- operation_id = provider.api_client.parse_operation_id(operation.self_link)
-
- unless operation_id
- return provider.make_errored!('Can not find operation_id from self_link')
- end
-
- yield(operation_id)
-
- rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
- provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
- end
- end
- end
-end
diff --git a/app/services/clusters/gcp/verify_provision_status_service.rb b/app/services/clusters/gcp/verify_provision_status_service.rb
deleted file mode 100644
index ddb2832aae6..00000000000
--- a/app/services/clusters/gcp/verify_provision_status_service.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class VerifyProvisionStatusService
- attr_reader :provider
-
- INITIAL_INTERVAL = 2.minutes
- EAGER_INTERVAL = 10.seconds
- TIMEOUT = 20.minutes
-
- def execute(provider)
- @provider = provider
-
- request_operation do |operation|
- case operation.status
- when 'PENDING', 'RUNNING'
- continue_creation(operation)
- when 'DONE'
- finalize_creation
- else
- provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
- end
- end
- end
-
- private
-
- def continue_creation(operation)
- if elapsed_time_from_creation(operation) < TIMEOUT
- WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id)
- else
- provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT })
- end
- end
-
- def elapsed_time_from_creation(operation)
- Time.current.utc - operation.start_time.to_time.utc
- end
-
- def finalize_creation
- Clusters::Gcp::FinalizeCreationService.new.execute(provider)
- end
-
- def request_operation(&blk)
- Clusters::Gcp::FetchOperationService.new.execute(provider, &blk)
- end
- end
- end
-end