cluster.rb 8.46 KB
Newer Older
1 2
# frozen_string_literal: true

3 4 5
module Clusters
  class Cluster < ActiveRecord::Base
    include Presentable
6
    include Gitlab::Utils::StrongMemoize
7
    include FromUnion
8

Shinya Maeda's avatar
Shinya Maeda committed
9 10
    self.table_name = 'clusters'

11
    APPLICATIONS = {
Kamil Trzcinski's avatar
Kamil Trzcinski committed
12
      Applications::Helm.application_name => Applications::Helm,
13
      Applications::Ingress.application_name => Applications::Ingress,
Amit Rathi's avatar
Amit Rathi committed
14
      Applications::CertManager.application_name => Applications::CertManager,
15
      Applications::Prometheus.application_name => Applications::Prometheus,
16
      Applications::Runner.application_name => Applications::Runner,
Chris Baumbauer's avatar
Chris Baumbauer committed
17 18
      Applications::Jupyter.application_name => Applications::Jupyter,
      Applications::Knative.application_name => Applications::Knative
Alessio Caiazza's avatar
Alessio Caiazza committed
19
    }.freeze
20
    DEFAULT_ENVIRONMENT = '*'.freeze
21
    KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze
22

23 24
    belongs_to :user

Shinya Maeda's avatar
Shinya Maeda committed
25
    has_many :cluster_projects, class_name: 'Clusters::Project'
26
    has_many :projects, through: :cluster_projects, class_name: '::Project'
27
    has_one :cluster_project, -> { order(id: :desc) }, class_name: 'Clusters::Project'
28

29 30 31
    has_many :cluster_groups, class_name: 'Clusters::Group'
    has_many :groups, through: :cluster_groups, class_name: '::Group'

32 33 34
    # we force autosave to happen when we save `Cluster` model
    has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true

35
    has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', inverse_of: :cluster, autosave: true
36

37
    has_one :application_helm, class_name: 'Clusters::Applications::Helm'
Kamil Trzcinski's avatar
Kamil Trzcinski committed
38
    has_one :application_ingress, class_name: 'Clusters::Applications::Ingress'
Amit Rathi's avatar
Amit Rathi committed
39
    has_one :application_cert_manager, class_name: 'Clusters::Applications::CertManager'
40
    has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
41
    has_one :application_runner, class_name: 'Clusters::Applications::Runner'
42
    has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter'
Chris Baumbauer's avatar
Chris Baumbauer committed
43
    has_one :application_knative, class_name: 'Clusters::Applications::Knative'
44

45 46 47
    has_many :kubernetes_namespaces
    has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace'

48
    accepts_nested_attributes_for :provider_gcp, update_only: true
49
    accepts_nested_attributes_for :platform_kubernetes, update_only: true
50

Shinya Maeda's avatar
Shinya Maeda committed
51
    validates :name, cluster_name: true
52
    validates :cluster_type, presence: true
53
    validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
54

55
    validate :restrict_modification, on: :update
56 57 58
    validate :no_groups, unless: :group_type?
    validate :no_projects, unless: :project_type?

Kamil Trzcinski's avatar
Kamil Trzcinski committed
59
    delegate :status, to: :provider, allow_nil: true
60
    delegate :status_reason, to: :provider, allow_nil: true
Shinya Maeda's avatar
Shinya Maeda committed
61
    delegate :on_creation?, to: :provider, allow_nil: true
62

63
    delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
64
    delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
65 66 67
    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
68
    delegate :available?, to: :application_knative, prefix: true, allow_nil: true
69
    delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true
70
    delegate :external_hostname, to: :application_ingress, prefix: true, allow_nil: true
71

72 73
    alias_attribute :base_domain, :domain

74 75 76 77 78 79
    enum cluster_type: {
      instance_type: 1,
      group_type: 2,
      project_type: 3
    }

80 81 82 83 84 85 86 87 88 89 90
    enum platform_type: {
      kubernetes: 1
    }

    enum provider_type: {
      user: 0,
      gcp: 1
    }

    scope :enabled, -> { where(enabled: true) }
    scope :disabled, -> { where(enabled: false) }
91 92
    scope :user_provided, -> { where(provider_type: ::Clusters::Cluster.provider_types[:user]) }
    scope :gcp_provided, -> { where(provider_type: ::Clusters::Cluster.provider_types[:gcp]) }
93 94
    scope :gcp_installed, -> { gcp_provided.includes(:provider_gcp).where(cluster_providers_gcp: { status: ::Clusters::Providers::Gcp.state_machines[:status].states[:created].value }) }

95
    scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
96

97 98 99 100 101 102
    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

103
    scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.available) }
104 105 106 107 108 109 110 111 112

    scope :preload_knative, -> {
      preload(
        :kubernetes_namespace,
        :platform_kubernetes,
        :application_knative
      )
    }

113
    def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
114 115
      hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order).eager_load(:clusters)
      hierarchy_groups = hierarchy_groups.merge(current_scope) if current_scope
116 117 118 119

      hierarchy_groups.flat_map(&:clusters)
    end

120 121 122 123 124 125 126 127
    def status_name
      if provider
        provider.status_name
      else
        :created
      end
    end

128 129 130 131
    def created?
      status_name == :created
    end

132
    def applications
133
      [
Kamil Trzcinski's avatar
Kamil Trzcinski committed
134
        application_helm || build_application_helm,
135
        application_ingress || build_application_ingress,
Amit Rathi's avatar
Amit Rathi committed
136
        application_cert_manager || build_application_cert_manager,
137
        application_prometheus || build_application_prometheus,
138
        application_runner || build_application_runner,
Chris Baumbauer's avatar
Chris Baumbauer committed
139 140
        application_jupyter || build_application_jupyter,
        application_knative || build_application_knative
141 142 143
      ]
    end

144
    def provider
Shinya Maeda's avatar
Shinya Maeda committed
145
      return provider_gcp if gcp?
146 147 148
    end

    def platform
Shinya Maeda's avatar
Shinya Maeda committed
149 150 151
      return platform_kubernetes if kubernetes?
    end

152 153 154 155
    def managed?
      !user?
    end

156 157 158 159 160 161 162 163 164 165
    def all_projects
      if project_type?
        projects
      elsif group_type?
        first_group.all_projects
      else
        Project.none
      end
    end

166
    def first_project
167 168 169
      strong_memoize(:first_project) do
        projects.first
      end
170
    end
171
    alias_method :project, :first_project
Shinya Maeda's avatar
Shinya Maeda committed
172

173 174 175 176 177 178 179
    def first_group
      strong_memoize(:first_group) do
        groups.first
      end
    end
    alias_method :group, :first_group

180 181 182 183
    def kubeclient
      platform_kubernetes.kubeclient if kubernetes?
    end

184 185 186 187 188 189 190 191 192 193 194
    def find_or_initialize_kubernetes_namespace_for_project(project)
      if project_type?
        kubernetes_namespaces.find_or_initialize_by(
          project: project,
          cluster_project: cluster_project
        )
      else
        kubernetes_namespaces.find_or_initialize_by(
          project: project
        )
      end
195 196
    end

197 198 199 200
    def allow_user_defined_namespace?
      project_type?
    end

201 202
    def kube_ingress_domain
      @kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain
203 204 205 206
    end

    def predefined_variables
      Gitlab::Ci::Variables::Collection.new.tap do |variables|
207
        break variables unless kube_ingress_domain
208

209
        variables.append(key: KUBE_INGRESS_BASE_DOMAIN, value: kube_ingress_domain)
210 211 212
      end
    end

Shinya Maeda's avatar
Shinya Maeda committed
213 214
    private

215
    def instance_domain
216 217 218 219 220 221 222 223 224
      @instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
    end

    # 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
    #
225 226
    # This method should is scheduled to be removed on
    # https://gitlab.com/gitlab-org/gitlab-ce/issues/56959
227 228 229 230 231 232 233 234
    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
235 236
    end

Shinya Maeda's avatar
Shinya Maeda committed
237 238 239 240 241 242 243 244
    def restrict_modification
      if provider&.on_creation?
        errors.add(:base, "cannot modify during creation")
        return false
      end

      true
    end
245 246 247 248 249 250 251 252 253 254 255 256

    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
257 258
  end
end