diff options
Diffstat (limited to 'app/models/clusters')
-rw-r--r-- | app/models/clusters/applications/cert_manager.rb | 42 | ||||
-rw-r--r-- | app/models/clusters/applications/helm.rb | 28 | ||||
-rw-r--r-- | app/models/clusters/applications/knative.rb | 60 | ||||
-rw-r--r-- | app/models/clusters/applications/prometheus.rb | 21 | ||||
-rw-r--r-- | app/models/clusters/applications/runner.rb | 17 | ||||
-rw-r--r-- | app/models/clusters/cluster.rb | 68 | ||||
-rw-r--r-- | app/models/clusters/clusters_hierarchy.rb | 4 | ||||
-rw-r--r-- | app/models/clusters/concerns/application_core.rb | 10 | ||||
-rw-r--r-- | app/models/clusters/concerns/application_status.rb | 24 | ||||
-rw-r--r-- | app/models/clusters/kubernetes_namespace.rb | 31 | ||||
-rw-r--r-- | app/models/clusters/platforms/kubernetes.rb | 32 |
11 files changed, 199 insertions, 138 deletions
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb index d6a7d1d2bdd..6bd7473c8ff 100644 --- a/app/models/clusters/applications/cert_manager.rb +++ b/app/models/clusters/applications/cert_manager.rb @@ -24,12 +24,6 @@ module Clusters 'stable/cert-manager' end - # We will implement this in future MRs. - # Need to reverse postinstall step - def allowed_to_uninstall? - false - end - def install_command Gitlab::Kubernetes::Helm::InstallCommand.new( name: 'certmanager', @@ -41,10 +35,44 @@ module Clusters ) end + def uninstall_command + Gitlab::Kubernetes::Helm::DeleteCommand.new( + name: 'certmanager', + rbac: cluster.platform_kubernetes_rbac?, + files: files, + postdelete: post_delete_script + ) + end + private def post_install_script - ["/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml"] + ["kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml"] + end + + def post_delete_script + [ + delete_private_key, + delete_crd('certificates.certmanager.k8s.io'), + delete_crd('clusterissuers.certmanager.k8s.io'), + delete_crd('issuers.certmanager.k8s.io') + ].compact + end + + def private_key_name + @private_key_name ||= cluster_issuer_content.dig('spec', 'acme', 'privateKeySecretRef', 'name') + end + + def delete_private_key + return unless private_key_name.present? + + args = %W(secret -n #{Gitlab::Kubernetes::Helm::NAMESPACE} #{private_key_name} --ignore-not-found) + + Gitlab::Kubernetes::KubectlCmd.delete(*args) + end + + def delete_crd(definition) + Gitlab::Kubernetes::KubectlCmd.delete("crd", definition, "--ignore-not-found") end def cluster_issuer_file diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb index a83d06c4b00..455cf200fbc 100644 --- a/app/models/clusters/applications/helm.rb +++ b/app/models/clusters/applications/helm.rb @@ -14,6 +14,7 @@ module Clusters include ::Clusters::Concerns::ApplicationCore include ::Clusters::Concerns::ApplicationStatus + include ::Gitlab::Utils::StrongMemoize default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION @@ -29,11 +30,22 @@ module Clusters self.status = 'installable' if cluster&.platform_kubernetes_active? end - # We will implement this in future MRs. - # Basically we need to check all other applications are not installed - # first. + # It can only be uninstalled if there are no other applications installed + # or with intermitent installation statuses in the database. def allowed_to_uninstall? - false + strong_memoize(:allowed_to_uninstall) do + applications = nil + + Clusters::Cluster::APPLICATIONS.each do |application_name, klass| + next if application_name == 'helm' + + extra_apps = Clusters::Applications::Helm.where('EXISTS (?)', klass.select(1).where(cluster_id: cluster_id)) + + applications = applications ? applications.or(extra_apps) : extra_apps + end + + !applications.exists? + end end def install_command @@ -44,6 +56,14 @@ module Clusters ) end + def uninstall_command + Gitlab::Kubernetes::Helm::ResetCommand.new( + name: name, + files: files, + rbac: cluster.platform_kubernetes_rbac? + ) + end + def has_ssl? ca_key.present? && ca_cert.present? end diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 5df4812bd25..244fe738396 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -7,6 +7,7 @@ module Clusters REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze FETCH_IP_ADDRESS_DELAY = 30.seconds + API_RESOURCES_PATH = 'config/knative/api_resources.yml' self.table_name = 'clusters_applications_knative' @@ -46,12 +47,6 @@ module Clusters { "domain" => hostname }.to_yaml end - # Handled in a new issue: - # https://gitlab.com/gitlab-org/gitlab-ce/issues/59369 - def allowed_to_uninstall? - false - end - def install_command Gitlab::Kubernetes::Helm::InstallCommand.new( name: name, @@ -76,10 +71,61 @@ module Clusters cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system') end + def uninstall_command + Gitlab::Kubernetes::Helm::DeleteCommand.new( + name: name, + rbac: cluster.platform_kubernetes_rbac?, + files: files, + predelete: delete_knative_services_and_metrics, + postdelete: delete_knative_istio_leftovers + ) + end + private + def delete_knative_services_and_metrics + delete_knative_services + delete_knative_istio_metrics + end + + def delete_knative_services + cluster.kubernetes_namespaces.map do |kubernetes_namespace| + Gitlab::Kubernetes::KubectlCmd.delete("ksvc", "--all", "-n", kubernetes_namespace.namespace) + end + end + + def delete_knative_istio_leftovers + delete_knative_namespaces + delete_knative_and_istio_crds + end + + def delete_knative_namespaces + [ + Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "ns", "knative-serving"), + Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "ns", "knative-build") + ] + end + + def delete_knative_and_istio_crds + api_resources.map do |crd| + Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "crd", "#{crd}") + end + end + + # returns an array of CRDs to be postdelete since helm does not + # manage the CRDs it creates. + def api_resources + @api_resources ||= YAML.safe_load(File.read(Rails.root.join(API_RESOURCES_PATH))) + end + def install_knative_metrics - ["kubectl apply -f #{METRICS_CONFIG}"] if cluster.application_prometheus_available? + return [] unless cluster.application_prometheus_available? + + [Gitlab::Kubernetes::KubectlCmd.apply_file(METRICS_CONFIG)] + end + + def delete_knative_istio_metrics + return [] unless cluster.application_prometheus_available? + + [Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "-f", METRICS_CONFIG)] end def verify_cluster? diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 805c8a73f8c..f31a6b8b50e 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -59,6 +59,15 @@ module Clusters ) end + def uninstall_command + Gitlab::Kubernetes::Helm::DeleteCommand.new( + name: name, + rbac: cluster.platform_kubernetes_rbac?, + files: files, + predelete: delete_knative_istio_metrics + ) + end + # Returns a copy of files where the values of 'values.yaml' # are replaced by the argument. # @@ -74,7 +83,7 @@ module Clusters # ensures headers containing auth data are appended to original k8s client options options = kube_client.rest_client.options.merge(headers: kube_client.headers) - RestClient::Resource.new(proxy_url, options) + Gitlab::PrometheusClient.new(proxy_url, options) rescue Kubeclient::HttpError # If users have mistakenly set parameters or removed the depended clusters, # `proxy_url` could raise an exception because gitlab can not communicate with the cluster. @@ -95,7 +104,15 @@ module Clusters end def install_knative_metrics - ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"] if cluster.application_knative_available? + return [] unless cluster.application_knative_available? + + [Gitlab::Kubernetes::KubectlCmd.apply_file(Clusters::Applications::Knative::METRICS_CONFIG)] + end + + def delete_knative_istio_metrics + return [] unless cluster.application_knative_available? + + [Gitlab::Kubernetes::KubectlCmd.delete("-f", Clusters::Applications::Knative::METRICS_CONFIG)] end end end diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index f0256ff4d41..6533b7a186e 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ApplicationRecord - VERSION = '0.6.0'.freeze + VERSION = '0.7.0'.freeze self.table_name = 'clusters_applications_runners' @@ -29,13 +29,6 @@ module Clusters content_values.to_yaml end - # Need to investigate if pipelines run by this runner will stop upon the - # executor pod stopping - # I.e.run a pipeline, and uninstall runner while pipeline is running - def allowed_to_uninstall? - false - end - def install_command Gitlab::Kubernetes::Helm::InstallCommand.new( name: name, @@ -47,6 +40,14 @@ module Clusters ) end + def prepare_uninstall + runner&.update!(active: false) + end + + def post_uninstall + runner.destroy! + end + private def ensure_runner diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 8c044c86c47..97d39491b73 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -53,6 +53,7 @@ module Clusters validates :name, cluster_name: true validates :cluster_type, presence: true validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true } + validates :namespace_per_environment, inclusion: { in: [true, false] } validate :restrict_modification, on: :update validate :no_groups, unless: :group_type? @@ -100,22 +101,6 @@ module Clusters scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) } - 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 - - scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.available) } - - scope :preload_knative, -> { - preload( - :kubernetes_namespaces, - :platform_kubernetes, - :application_knative - ) - } - def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc) return [] if clusterable.is_a?(Instance) @@ -161,16 +146,6 @@ module Clusters return platform_kubernetes if kubernetes? end - def all_projects - if project_type? - projects - elsif group_type? - first_group.all_projects - else - Project.none - end - end - def first_project strong_memoize(:first_project) do projects.first @@ -193,36 +168,15 @@ module Clusters platform_kubernetes.kubeclient if kubernetes? end - ## - # 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 + def kubernetes_namespace_for(environment) + project = environment.project + persisted_namespace = Clusters::KubernetesNamespaceFinder.new( + self, + project: project, + environment_slug: environment.slug + ).execute - ## - # 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 - end + persisted_namespace&.namespace || Gitlab::Kubernetes::DefaultNamespace.new(self, project: project).from_environment_slug(environment.slug) end def allow_user_defined_namespace? @@ -241,10 +195,6 @@ module Clusters end end - def knative_services_finder(project) - @knative_services_finder ||= KnativeServicesFinder.new(self, project) - end - private def instance_domain diff --git a/app/models/clusters/clusters_hierarchy.rb b/app/models/clusters/clusters_hierarchy.rb index dab034b7234..5556fc8d3f0 100644 --- a/app/models/clusters/clusters_hierarchy.rb +++ b/app/models/clusters/clusters_hierarchy.rb @@ -46,7 +46,7 @@ module Clusters def group_clusters_base_query group_parent_id_alias = alias_as_column(groups[:parent_id], 'group_parent_id') - join_sources = ::Group.left_joins(:clusters).join_sources + join_sources = ::Group.left_joins(:clusters).arel.join_sources model .unscoped @@ -59,7 +59,7 @@ module Clusters def project_clusters_base_query projects = ::Project.arel_table project_parent_id_alias = alias_as_column(projects[:namespace_id], 'group_parent_id') - join_sources = ::Project.left_joins(:clusters).join_sources + join_sources = ::Project.left_joins(:clusters).arel.join_sources model .unscoped diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb index 4514498b84b..803a65726d3 100644 --- a/app/models/clusters/concerns/application_core.rb +++ b/app/models/clusters/concerns/application_core.rb @@ -46,6 +46,16 @@ module Clusters command.version = version end end + + def prepare_uninstall + # Override if your application needs any action before + # being uninstalled by Helm + end + + def post_uninstall + # Override if your application needs any action after + # being uninstalled by Helm + end end end end diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb index 54a3dda6d75..342d766f723 100644 --- a/app/models/clusters/concerns/application_status.rb +++ b/app/models/clusters/concerns/application_status.rb @@ -59,29 +59,33 @@ module Clusters transition [:scheduled] => :uninstalling end - before_transition any => [:scheduled] do |app_status, _| - app_status.status_reason = nil + before_transition any => [:scheduled] do |application, _| + application.status_reason = nil end - before_transition any => [:errored] do |app_status, transition| + before_transition any => [:errored] do |application, transition| status_reason = transition.args.first - app_status.status_reason = status_reason if status_reason + application.status_reason = status_reason if status_reason end - before_transition any => [:updating] do |app_status, _| - app_status.status_reason = nil + before_transition any => [:updating] do |application, _| + application.status_reason = nil end - before_transition any => [:update_errored, :uninstall_errored] do |app_status, transition| + before_transition any => [:update_errored, :uninstall_errored] do |application, transition| status_reason = transition.args.first - app_status.status_reason = status_reason if status_reason + application.status_reason = status_reason if status_reason end - before_transition any => [:installed, :updated] do |app_status, _| + before_transition any => [:installed, :updated] do |application, _| # When installing any application we are also performing an update # of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so # therefore we need to reflect that in the database. - app_status.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) + application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) + end + + after_transition any => [:uninstalling], :use_transactions => false do |application, _| + application.prepare_uninstall end end end diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb index b0c4900546e..69a2b99fcb6 100644 --- a/app/models/clusters/kubernetes_namespace.rb +++ b/app/models/clusters/kubernetes_namespace.rb @@ -9,12 +9,12 @@ module Clusters belongs_to :cluster_project, class_name: 'Clusters::Project' belongs_to :cluster, class_name: 'Clusters::Cluster' belongs_to :project, class_name: '::Project' + belongs_to :environment, optional: true has_one :platform_kubernetes, through: :cluster - before_validation :set_defaults - validates :namespace, presence: true validates :namespace, uniqueness: { scope: :cluster_id } + validates :environment_id, uniqueness: { scope: [:cluster_id, :project_id] }, allow_nil: true validates :service_account_name, presence: true @@ -27,6 +27,7 @@ module Clusters algorithm: 'aes-256-cbc' scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) } + scope :with_environment_slug, -> (slug) { joins(:environment).where(environments: { slug: slug }) } def token_name "#{namespace}-token" @@ -42,34 +43,8 @@ module Clusters end end - def set_defaults - self.namespace ||= default_platform_kubernetes_namespace - self.namespace ||= default_project_namespace - self.service_account_name ||= default_service_account_name - end - private - def default_service_account_name - return unless namespace - - "#{namespace}-service-account" - end - - def default_platform_kubernetes_namespace - platform_kubernetes&.namespace.presence - end - - def default_project_namespace - Gitlab::NamespaceSanitizer.sanitize(project_slug) if project_slug - end - - def project_slug - return unless project - - "#{project.path}-#{project.id}".downcase - end - def kubeconfig to_kubeconfig( url: api_url, diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 9296c28776b..37614fbe3ca 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -51,11 +51,6 @@ module Clusters delegate :provided_by_user?, to: :cluster, allow_nil: true delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true - # This is just to maintain compatibility with KubernetesService, which - # will be removed in https://gitlab.com/gitlab-org/gitlab-ce/issues/39217. - # It can be removed once KubernetesService is gone. - delegate :kubernetes_namespace_for, to: :cluster, allow_nil: true - alias_method :active?, :enabled? enum_with_nil authorization_type: { @@ -66,7 +61,7 @@ module Clusters default_value_for :authorization_type, :rbac - def predefined_variables(project:) + def predefined_variables(project:, environment_name:) Gitlab::Ci::Variables::Collection.new.tap do |variables| variables.append(key: 'KUBE_URL', value: api_url) @@ -77,15 +72,14 @@ module Clusters end if !cluster.managed? - project_namespace = namespace.presence || "#{project.path}-#{project.id}".downcase + namespace = Gitlab::Kubernetes::DefaultNamespace.new(cluster, project: project).from_environment_name(environment_name) variables - .append(key: 'KUBE_URL', value: api_url) .append(key: 'KUBE_TOKEN', value: token, public: false, masked: true) - .append(key: 'KUBE_NAMESPACE', value: project_namespace) - .append(key: 'KUBECONFIG', value: kubeconfig(project_namespace), public: false, file: true) + .append(key: 'KUBE_NAMESPACE', value: namespace) + .append(key: 'KUBECONFIG', value: kubeconfig(namespace), public: false, file: true) - elsif kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project) + elsif kubernetes_namespace = find_persisted_namespace(project, environment_name: environment_name) variables.concat(kubernetes_namespace.predefined_variables) end @@ -111,6 +105,22 @@ module Clusters private + ## + # Environment slug can be predicted given an environment + # name, so even if the environment isn't persisted yet we + # still know what to look for. + def environment_slug(name) + Gitlab::Slug::Environment.new(name).generate + end + + def find_persisted_namespace(project, environment_name:) + Clusters::KubernetesNamespaceFinder.new( + cluster, + project: project, + environment_slug: environment_slug(environment_name) + ).execute + end + def kubeconfig(namespace) to_kubeconfig( url: api_url, |