Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-11-01 06:06:26 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-11-01 06:06:26 +0300
commit56d96ad7fab4d4b95f5529d8080b3cc2873794a0 (patch)
tree7fe93fc8ff4d82d815000781ffb9c98d7259211a /app/services
parent8078bd185fd9fce86cb5a8d9a6b6209e0c23ae44 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/services')
-rw-r--r--app/services/clusters/aws/fetch_credentials_service.rb60
-rw-r--r--app/services/clusters/aws/finalize_creation_service.rb139
-rw-r--r--app/services/clusters/aws/provision_service.rb77
-rw-r--r--app/services/clusters/aws/verify_provision_status_service.rb50
4 files changed, 326 insertions, 0 deletions
diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb
new file mode 100644
index 00000000000..e1d04fac976
--- /dev/null
+++ b/app/services/clusters/aws/fetch_credentials_service.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Aws
+ class FetchCredentialsService
+ attr_reader :provider
+
+ MissingRoleError = Class.new(StandardError)
+
+ def initialize(provider)
+ @provider = provider
+ end
+
+ def execute
+ raise MissingRoleError.new('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
+ ).credentials
+ end
+
+ private
+
+ def provision_role
+ provider.created_by_user.aws_role
+ end
+
+ def client
+ ::Aws::STS::Client.new(credentials: gitlab_credentials, region: provider.region)
+ end
+
+ def gitlab_credentials
+ ::Aws::Credentials.new(access_key_id, secret_access_key)
+ end
+
+ ##
+ # This setting is not yet configurable or documented as these
+ # services are not currently used. This will be addressed in
+ # https://gitlab.com/gitlab-org/gitlab/merge_requests/18307
+ def access_key_id
+ Gitlab.config.kubernetes.provisioners.aws.access_key_id
+ end
+
+ ##
+ # This setting is not yet configurable or documented as these
+ # services are not currently used. This will be addressed in
+ # https://gitlab.com/gitlab-org/gitlab/merge_requests/18307
+ def secret_access_key
+ Gitlab.config.kubernetes.provisioners.aws.secret_access_key
+ end
+
+ def session_name
+ "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provider.created_by_user_id}"
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/aws/finalize_creation_service.rb b/app/services/clusters/aws/finalize_creation_service.rb
new file mode 100644
index 00000000000..54f07e1d44c
--- /dev/null
+++ b/app/services/clusters/aws/finalize_creation_service.rb
@@ -0,0 +1,139 @@
+# 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
new file mode 100644
index 00000000000..6a3b0dffae2
--- /dev/null
+++ b/app/services/clusters/aws/provision_service.rb
@@ -0,0 +1,77 @@
+# 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, Settingslogic::MissingSetting
+ 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 credentials
+ @credentials ||= Clusters::Aws::FetchCredentialsService.new(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('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
new file mode 100644
index 00000000000..99532662bc4
--- /dev/null
+++ b/app/services/clusters/aws/verify_provision_status_service.rb
@@ -0,0 +1,50 @@
+# 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