debian-mirror-gitlab/app/models/clusters/cluster.rb

321 lines
11 KiB
Ruby
Raw Normal View History

2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2018-03-17 18:26:18 +05:30
module Clusters
2019-07-07 11:18:12 +05:30
class Cluster < ApplicationRecord
2018-03-17 18:26:18 +05:30
include Presentable
2018-12-13 13:39:08 +05:30
include Gitlab::Utils::StrongMemoize
2019-02-15 15:39:39 +05:30
include FromUnion
2019-09-04 21:01:54 +05:30
include ReactiveCaching
2018-03-17 18:26:18 +05:30
self.table_name = 'clusters'
2019-07-07 11:18:12 +05:30
PROJECT_ONLY_APPLICATIONS = {
Applications::Jupyter.application_name => Applications::Jupyter,
2019-07-31 22:56:46 +05:30
Applications::Knative.application_name => Applications::Knative
2019-07-07 11:18:12 +05:30
}.freeze
2018-03-17 18:26:18 +05:30
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress,
2019-02-15 15:39:39 +05:30
Applications::CertManager.application_name => Applications::CertManager,
2019-07-31 22:56:46 +05:30
Applications::Runner.application_name => Applications::Runner,
Applications::Prometheus.application_name => Applications::Prometheus
2019-07-07 11:18:12 +05:30
}.merge(PROJECT_ONLY_APPLICATIONS).freeze
2018-05-01 15:08:00 +05:30
DEFAULT_ENVIRONMENT = '*'.freeze
2019-03-02 22:35:43 +05:30
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze
2018-03-17 18:26:18 +05:30
belongs_to :user
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
2018-12-13 13:39:08 +05:30
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'
2018-03-17 18:26:18 +05:30
# we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
2019-02-15 15:39:39 +05:30
has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', inverse_of: :cluster, autosave: true
2018-03-17 18:26:18 +05:30
has_one :application_helm, class_name: 'Clusters::Applications::Helm'
has_one :application_ingress, class_name: 'Clusters::Applications::Ingress'
2019-02-15 15:39:39 +05:30
has_one :application_cert_manager, class_name: 'Clusters::Applications::CertManager'
2018-03-17 18:26:18 +05:30
has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
2018-03-27 19:54:05 +05:30
has_one :application_runner, class_name: 'Clusters::Applications::Runner'
2018-11-08 19:23:39 +05:30
has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter'
2018-12-13 13:39:08 +05:30
has_one :application_knative, class_name: 'Clusters::Applications::Knative'
has_many :kubernetes_namespaces
2018-03-17 18:26:18 +05:30
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
validates :name, cluster_name: true
2018-12-13 13:39:08 +05:30
validates :cluster_type, presence: true
2019-03-02 22:35:43 +05:30
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
2018-03-17 18:26:18 +05:30
2019-03-02 22:35:43 +05:30
validate :restrict_modification, on: :update
2018-12-13 13:39:08 +05:30
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
2019-09-04 21:01:54 +05:30
after_save :clear_reactive_cache!
2018-03-17 18:26:18 +05:30
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
2018-11-20 20:47:30 +05:30
delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
2018-12-13 13:39:08 +05:30
delegate :available?, to: :application_helm, prefix: true, allow_nil: true
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
2019-02-15 15:39:39 +05:30
delegate :available?, to: :application_knative, prefix: true, allow_nil: true
2019-03-02 22:35:43 +05:30
delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true
2019-07-07 11:18:12 +05:30
delegate :external_hostname, to: :application_ingress, prefix: true, allow_nil: true
2019-03-02 22:35:43 +05:30
alias_attribute :base_domain, :domain
2019-07-07 11:18:12 +05:30
alias_attribute :provided_by_user?, :user?
2018-12-13 13:39:08 +05:30
enum cluster_type: {
instance_type: 1,
group_type: 2,
project_type: 3
}
2018-03-17 18:26:18 +05:30
enum platform_type: {
kubernetes: 1
}
enum provider_type: {
user: 0,
gcp: 1
}
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
2018-05-09 12:01:36 +05:30
scope :user_provided, -> { where(provider_type: ::Clusters::Cluster.provider_types[:user]) }
scope :gcp_provided, -> { where(provider_type: ::Clusters::Cluster.provider_types[:gcp]) }
scope :gcp_installed, -> { gcp_provided.includes(:provider_gcp).where(cluster_providers_gcp: { status: ::Clusters::Providers::Gcp.state_machines[:status].states[:created].value }) }
2019-07-31 22:56:46 +05:30
scope :managed, -> { where(managed: true) }
2018-05-09 12:01:36 +05:30
2018-05-01 15:08:00 +05:30
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
2018-03-17 18:26:18 +05:30
2019-02-15 15:39:39 +05:30
scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.cluster_id = clusters.id')
where('NOT EXISTS (?)', subquery)
end
2019-07-07 11:18:12 +05:30
scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.available) }
2019-02-15 15:39:39 +05:30
scope :preload_knative, -> {
preload(
2019-09-04 21:01:54 +05:30
:kubernetes_namespaces,
2019-02-15 15:39:39 +05:30
:platform_kubernetes,
:application_knative
)
}
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
2019-07-31 22:56:46 +05:30
return [] if clusterable.is_a?(Instance)
2019-02-15 15:39:39 +05:30
hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order).eager_load(:clusters)
hierarchy_groups = hierarchy_groups.merge(current_scope) if current_scope
2019-07-31 22:56:46 +05:30
hierarchy_groups.flat_map(&:clusters) + Instance.new.clusters
2019-02-15 15:39:39 +05:30
end
2018-03-17 18:26:18 +05:30
def status_name
2019-09-04 21:01:54 +05:30
provider&.status_name || connection_status.presence || :created
end
def connection_status
with_reactive_cache do |data|
data[:connection_status]
2018-03-17 18:26:18 +05:30
end
end
2019-09-04 21:01:54 +05:30
def calculate_reactive_cache
return unless enabled?
{ connection_status: retrieve_connection_status }
2018-03-17 18:26:18 +05:30
end
def applications
[
application_helm || build_application_helm,
application_ingress || build_application_ingress,
2019-02-15 15:39:39 +05:30
application_cert_manager || build_application_cert_manager,
2018-03-27 19:54:05 +05:30
application_prometheus || build_application_prometheus,
2018-11-08 19:23:39 +05:30
application_runner || build_application_runner,
2018-12-13 13:39:08 +05:30
application_jupyter || build_application_jupyter,
application_knative || build_application_knative
2018-03-17 18:26:18 +05:30
]
end
def provider
return provider_gcp if gcp?
end
def platform
return platform_kubernetes if kubernetes?
end
2019-02-15 15:39:39 +05:30
def all_projects
if project_type?
projects
elsif group_type?
first_group.all_projects
else
Project.none
end
end
2018-03-17 18:26:18 +05:30
def first_project
2018-12-13 13:39:08 +05:30
strong_memoize(:first_project) do
projects.first
end
2018-03-17 18:26:18 +05:30
end
alias_method :project, :first_project
2018-12-13 13:39:08 +05:30
def first_group
strong_memoize(:first_group) do
groups.first
end
end
alias_method :group, :first_group
2019-07-31 22:56:46 +05:30
def instance
Instance.new if instance_type?
end
2018-03-17 18:26:18 +05:30
def kubeclient
platform_kubernetes.kubeclient if kubernetes?
end
2019-09-04 21:01:54 +05:30
##
# This is subtly different to #find_or_initialize_kubernetes_namespace_for_project
# below because it will ignore any namespaces that have not got a service account
# token. This provides a guarantee that any namespace selected here can be used
# for cluster operations - a namespace needs to have a service account configured
# before it it can be used.
#
# This is used for selecting a namespace to use when querying a cluster, or
# generating variables to pass to CI.
def kubernetes_namespace_for(project)
find_or_initialize_kubernetes_namespace_for_project(
project, scope: kubernetes_namespaces.has_service_account_token
).namespace
end
##
# This is subtly different to #kubernetes_namespace_for because it will include
# namespaces that have yet to receive a service account token. This allows
# the namespace configuration process to be repeatable - if a namespace has
# already been created without a token we don't need to create another
# record entirely, just set the token on the pre-existing namespace.
#
# This is used for configuring cluster namespaces.
def find_or_initialize_kubernetes_namespace_for_project(project, scope: kubernetes_namespaces)
attributes = { project: project }
attributes[:cluster_project] = cluster_project if project_type?
scope.find_or_initialize_by(attributes).tap do |namespace|
namespace.set_defaults
2019-02-15 15:39:39 +05:30
end
end
def allow_user_defined_namespace?
2019-09-04 21:01:54 +05:30
project_type? || !managed?
2018-12-13 13:39:08 +05:30
end
2019-03-02 22:35:43 +05:30
def kube_ingress_domain
2019-09-04 21:01:54 +05:30
@kube_ingress_domain ||= domain.presence || instance_domain
2019-03-02 22:35:43 +05:30
end
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless kube_ingress_domain
variables.append(key: KUBE_INGRESS_BASE_DOMAIN, value: kube_ingress_domain)
end
end
2019-09-04 21:01:54 +05:30
def knative_services_finder(project)
@knative_services_finder ||= KnativeServicesFinder.new(self, project)
end
2018-03-17 18:26:18 +05:30
private
2019-03-02 22:35:43 +05:30
def instance_domain
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end
2019-09-04 21:01:54 +05:30
def retrieve_connection_status
kubeclient.core_client.discover
rescue *Gitlab::Kubernetes::Errors::CONNECTION
:unreachable
rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION
:authentication_failure
rescue Kubeclient::HttpError => e
kubeclient_error_status(e.message)
rescue => e
Gitlab::Sentry.track_acceptable_exception(e, extra: { cluster_id: id })
:unknown_failure
else
:connected
end
# KubeClient uses the same error class
# For connection errors (eg. timeout) and
# for Kubernetes errors.
def kubeclient_error_status(message)
if message&.match?(/timed out|timeout/i)
:unreachable
else
:authentication_failure
end
end
2019-03-02 22:35:43 +05:30
# To keep backward compatibility with AUTO_DEVOPS_DOMAIN
# environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
# is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
# ProjectAutoDevops#Domain, project variables or group variables,
# as the AUTO_DEVOPS_DOMAIN is needed for CI_ENVIRONMENT_URL
#
# This method should is scheduled to be removed on
# https://gitlab.com/gitlab-org/gitlab-ce/issues/56959
def legacy_auto_devops_domain
if project_type?
project&.auto_devops&.domain.presence ||
project.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence ||
project.group&.variables&.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
elsif group_type?
group.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
end
end
2018-03-17 18:26:18 +05:30
def restrict_modification
if provider&.on_creation?
errors.add(:base, "cannot modify during creation")
return false
end
true
end
2018-12-13 13:39:08 +05:30
def no_groups
if groups.any?
errors.add(:cluster, 'cannot have groups assigned')
end
end
def no_projects
if projects.any?
errors.add(:cluster, 'cannot have projects assigned')
end
end
2018-03-17 18:26:18 +05:30
end
end