diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-24 09:09:25 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-24 09:09:25 +0000 |
commit | 6f7881ee9dcec34141a8f34fc814b56b366d2b48 (patch) | |
tree | 25f72a06874b32b1049b79a9d7f4f1b7bca43b9b /app | |
parent | 8c8bf44fa64f98114f7439f751c92d59a44b3218 (diff) | |
download | gitlab-ce-6f7881ee9dcec34141a8f34fc814b56b366d2b48.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/stylesheets/notify.scss | 6 | ||||
-rw-r--r-- | app/models/clusters/applications/ingress.rb | 6 | ||||
-rw-r--r-- | app/models/clusters/applications/prometheus.rb | 12 | ||||
-rw-r--r-- | app/models/project.rb | 6 | ||||
-rw-r--r-- | app/models/user.rb | 1 | ||||
-rw-r--r-- | app/models/user_canonical_email.rb | 8 | ||||
-rw-r--r-- | app/services/clusters/applications/check_upgrade_progress_service.rb | 71 | ||||
-rw-r--r-- | app/services/clusters/applications/prometheus_config_service.rb | 155 | ||||
-rw-r--r-- | app/services/clusters/applications/prometheus_update_service.rb | 35 | ||||
-rw-r--r-- | app/services/clusters/applications/schedule_update_service.rb | 38 | ||||
-rw-r--r-- | app/services/users/build_service.rb | 2 | ||||
-rw-r--r-- | app/services/users/update_canonical_email_service.rb | 56 | ||||
-rw-r--r-- | app/services/users/update_service.rb | 7 | ||||
-rw-r--r-- | app/workers/all_queues.yml | 14 | ||||
-rw-r--r-- | app/workers/cluster_update_app_worker.rb | 50 | ||||
-rw-r--r-- | app/workers/cluster_wait_for_app_update_worker.rb | 16 |
16 files changed, 477 insertions, 6 deletions
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index ea82ba3e879..b59b01f4086 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -20,7 +20,11 @@ pre.commit-message { } .gl-label-scoped { - box-shadow: 0 0 0 2px currentColor inset; + border: 2px solid currentColor; + box-sizing: border-box; + display: inline-block; + height: 17px; + line-height: 14px; } .gl-label-text { diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index 78c2a74da33..baf34e916f8 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -50,7 +50,7 @@ module Clusters end def allowed_to_uninstall? - external_ip_or_hostname? && application_jupyter_nil_or_installable? + external_ip_or_hostname? && !application_jupyter_installed? end def install_command @@ -161,8 +161,8 @@ module Clusters YAML.load_file(chart_values_file).deep_merge!(specification) end - def application_jupyter_nil_or_installable? - cluster.application_jupyter.nil? || cluster.application_jupyter&.installable? + def application_jupyter_installed? + cluster.application_jupyter&.installed? end def modsecurity_snippet_content diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 8297f653ea7..3183318690c 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -35,6 +35,16 @@ module Clusters .perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass end end + + after_transition any => :updating do |application| + application.update(last_update_started_at: Time.now) + end + end + + def updated_since?(timestamp) + last_update_started_at && + last_update_started_at > timestamp && + !update_errored? end def chart @@ -148,5 +158,3 @@ module Clusters end end end - -Clusters::Applications::Prometheus.prepend_if_ee('EE::Clusters::Applications::Prometheus') diff --git a/app/models/project.rb b/app/models/project.rb index 34c9c7320be..b9d8fd1e4d8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2411,6 +2411,12 @@ class Project < ApplicationRecord branch_protection.fully_protected? || branch_protection.developer_can_merge? end + def environments_for_scope(scope) + quoted_scope = ::Gitlab::SQL::Glob.q(scope) + + environments.where("name LIKE (#{::Gitlab::SQL::Glob.to_like(quoted_scope)})") # rubocop:disable GitlabSecurity/SqlInjection + end + private def closest_namespace_setting(name) diff --git a/app/models/user.rb b/app/models/user.rb index 7789326e8fa..4f484657f13 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -168,6 +168,7 @@ class User < ApplicationRecord has_one :user_preference has_one :user_detail has_one :user_highest_role + has_one :user_canonical_email # # Validations diff --git a/app/models/user_canonical_email.rb b/app/models/user_canonical_email.rb new file mode 100644 index 00000000000..044e4fd775e --- /dev/null +++ b/app/models/user_canonical_email.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class UserCanonicalEmail < ApplicationRecord + validates :canonical_email, presence: true + validates :canonical_email, format: { with: Devise.email_regexp } + + belongs_to :user, inverse_of: :user_canonical_email +end diff --git a/app/services/clusters/applications/check_upgrade_progress_service.rb b/app/services/clusters/applications/check_upgrade_progress_service.rb new file mode 100644 index 00000000000..8502ea69f27 --- /dev/null +++ b/app/services/clusters/applications/check_upgrade_progress_service.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class CheckUpgradeProgressService < BaseHelmService + def execute + return unless app.updating? + + case phase + when ::Gitlab::Kubernetes::Pod::SUCCEEDED + on_success + when ::Gitlab::Kubernetes::Pod::FAILED + on_failed + else + check_timeout + end + rescue ::Kubeclient::HttpError => e + app.make_update_errored!("Kubernetes error: #{e.message}") unless app.update_errored? + end + + private + + def on_success + app.make_installed! + ensure + remove_pod + end + + def on_failed + app.make_update_errored!(errors || 'Update silently failed') + ensure + remove_pod + end + + def check_timeout + if timed_out? + begin + app.make_update_errored!('Update timed out') + ensure + remove_pod + end + else + ::ClusterWaitForAppUpdateWorker.perform_in( + ::ClusterWaitForAppUpdateWorker::INTERVAL, app.name, app.id) + end + end + + def timed_out? + Time.now.utc - app.updated_at.to_time.utc > ::ClusterWaitForAppUpdateWorker::TIMEOUT + end + + def remove_pod + helm_api.delete_pod!(pod_name) + rescue + # no-op + end + + def phase + helm_api.status(pod_name) + end + + def errors + helm_api.log(pod_name) + end + + def pod_name + @pod_name ||= patch_command.pod_name + end + end + end +end diff --git a/app/services/clusters/applications/prometheus_config_service.rb b/app/services/clusters/applications/prometheus_config_service.rb new file mode 100644 index 00000000000..34d44ab881e --- /dev/null +++ b/app/services/clusters/applications/prometheus_config_service.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class PrometheusConfigService + def initialize(project, cluster, app) + @project = project + @cluster = cluster + @app = app + end + + def execute(config = {}) + if has_alerts? + generate_alert_manager(config) + else + reset_alert_manager(config) + end + end + + private + + attr_reader :project, :cluster, :app + + def reset_alert_manager(config) + config = set_alert_manager_enabled(config, false) + config.delete('alertmanagerFiles') + config['serverFiles'] ||= {} + config['serverFiles']['alerts'] = {} + + config + end + + def generate_alert_manager(config) + config = set_alert_manager_enabled(config, true) + config = set_alert_manager_files(config) + + set_alert_manager_groups(config) + end + + def set_alert_manager_enabled(config, enabled) + config['alertmanager'] ||= {} + config['alertmanager']['enabled'] = enabled + + config + end + + def set_alert_manager_files(config) + config['alertmanagerFiles'] = { + 'alertmanager.yml' => { + 'receivers' => alert_manager_receivers_params, + 'route' => alert_manager_route_params + } + } + + config + end + + def set_alert_manager_groups(config) + config['serverFiles'] ||= {} + config['serverFiles']['alerts'] ||= {} + config['serverFiles']['alerts']['groups'] ||= [] + + environments_with_alerts.each do |env_name, alerts| + index = config['serverFiles']['alerts']['groups'].find_index do |group| + group['name'] == env_name + end + + if index + config['serverFiles']['alerts']['groups'][index]['rules'] = alerts + else + config['serverFiles']['alerts']['groups'] << { + 'name' => env_name, + 'rules' => alerts + } + end + end + + config + end + + def alert_manager_receivers_params + [ + { + 'name' => 'gitlab', + 'webhook_configs' => [ + { + 'url' => notify_url, + 'send_resolved' => true, + 'http_config' => { + 'bearer_token' => alert_manager_token + } + } + ] + } + ] + end + + def alert_manager_token + app.generate_alert_manager_token! + + app.alert_manager_token + end + + def alert_manager_route_params + { + 'receiver' => 'gitlab', + 'group_wait' => '30s', + 'group_interval' => '5m', + 'repeat_interval' => '4h' + } + end + + def notify_url + ::Gitlab::Routing.url_helpers + .notify_project_prometheus_alerts_url(project, format: :json) + end + + def has_alerts? + environments_with_alerts.values.flatten(1).any? + end + + def environments_with_alerts + @environments_with_alerts ||= + environments.each_with_object({}) do |environment, hash| + name = rule_name(environment) + hash[name] = alerts(environment) + end + end + + def rule_name(environment) + "#{environment.name}.rules" + end + + def alerts(environment) + variables = Gitlab::Prometheus::QueryVariables.call(environment) + alerts = Projects::Prometheus::AlertsFinder + .new(environment: environment) + .execute + + alerts.map do |alert| + substitute_query_variables(alert.to_param, variables) + end + end + + def substitute_query_variables(hash, variables) + hash['expr'] %= variables + hash + end + + def environments + project.environments_for_scope(cluster.environment_scope) + end + end + end +end diff --git a/app/services/clusters/applications/prometheus_update_service.rb b/app/services/clusters/applications/prometheus_update_service.rb new file mode 100644 index 00000000000..437f6ab1202 --- /dev/null +++ b/app/services/clusters/applications/prometheus_update_service.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class PrometheusUpdateService < BaseHelmService + attr_accessor :project + + def initialize(app, project) + super(app) + @project = project + end + + def execute + app.make_updating! + + helm_api.update(patch_command(values)) + + ::ClusterWaitForAppUpdateWorker.perform_in(::ClusterWaitForAppUpdateWorker::INTERVAL, app.name, app.id) + rescue ::Kubeclient::HttpError => ke + app.make_update_errored!("Kubernetes error: #{ke.message}") + rescue StandardError => e + app.make_update_errored!(e.message) + end + + private + + def values + PrometheusConfigService + .new(project, cluster, app) + .execute + .to_yaml + end + end + end +end diff --git a/app/services/clusters/applications/schedule_update_service.rb b/app/services/clusters/applications/schedule_update_service.rb new file mode 100644 index 00000000000..b7639c771a8 --- /dev/null +++ b/app/services/clusters/applications/schedule_update_service.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class ScheduleUpdateService + BACKOFF_DELAY = 2.minutes + + attr_accessor :application, :project + + def initialize(application, project) + @application = application + @project = project + end + + def execute + return unless application + + if recently_scheduled? + worker_class.perform_in(BACKOFF_DELAY, application.name, application.id, project.id, Time.now) + else + worker_class.perform_async(application.name, application.id, project.id, Time.now) + end + end + + private + + def worker_class + ::ClusterUpdateAppWorker + end + + def recently_scheduled? + return false unless application.last_update_started_at + + application.last_update_started_at.utc >= Time.now.utc - BACKOFF_DELAY + end + end + end +end diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb index 4c3ae2d204d..6f9f307c322 100644 --- a/app/services/users/build_service.rb +++ b/app/services/users/build_service.rb @@ -30,6 +30,8 @@ module Users build_identity(user) + Users::UpdateCanonicalEmailService.new(user: user).execute + user end diff --git a/app/services/users/update_canonical_email_service.rb b/app/services/users/update_canonical_email_service.rb new file mode 100644 index 00000000000..1400fd58eb4 --- /dev/null +++ b/app/services/users/update_canonical_email_service.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Users + class UpdateCanonicalEmailService + extend ActiveSupport::Concern + + INCLUDED_DOMAINS_PATTERN = [/gmail.com/].freeze + + def initialize(user:) + raise ArgumentError.new("Please provide a user") unless user&.is_a?(User) + + @user = user + end + + def execute + return unless user.email + return unless user.email.match? Devise.email_regexp + + canonical_email = canonicalize_email + + unless canonical_email + # the canonical email doesn't exist, probably because the domain doesn't match + # destroy any UserCanonicalEmail record associated with this user + user.user_canonical_email&.delete + # nothing else to do here + return + end + + if user.user_canonical_email + # update to the new value + user.user_canonical_email.canonical_email = canonical_email + else + user.build_user_canonical_email(canonical_email: canonical_email) + end + end + + private + + attr_reader :user + + def canonicalize_email + email = user.email + + portions = email.split('@') + username = portions.shift + rest = portions.join + + regex = Regexp.union(INCLUDED_DOMAINS_PATTERN) + return unless regex.match?(rest) + + no_dots = username.tr('.', '') + before_plus = no_dots.split('+')[0] + "#{before_plus}@#{rest}" + end + end +end diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb index 57209043e3b..f0e9f2b7656 100644 --- a/app/services/users/update_service.rb +++ b/app/services/users/update_service.rb @@ -21,6 +21,7 @@ module Users discard_read_only_attributes assign_attributes assign_identity + build_canonical_email if @user.save(validate: validate) && update_status notify_success(user_exists) @@ -40,6 +41,12 @@ module Users private + def build_canonical_email + return unless @user.email_changed? + + Users::UpdateCanonicalEmailService.new(user: @user).execute + end + def update_status return true unless @status_params diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index f4d8483db84..cae4bb73e04 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -325,6 +325,13 @@ :resource_boundary: :unknown :weight: 1 :idempotent: +- :name: gcp_cluster:cluster_update_app + :feature_category: :kubernetes_management + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: - :name: gcp_cluster:cluster_upgrade_app :feature_category: :kubernetes_management :has_external_dependencies: true @@ -339,6 +346,13 @@ :resource_boundary: :cpu :weight: 1 :idempotent: +- :name: gcp_cluster:cluster_wait_for_app_update + :feature_category: :kubernetes_management + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: - :name: gcp_cluster:cluster_wait_for_ingress_ip_address :feature_category: :kubernetes_management :has_external_dependencies: true diff --git a/app/workers/cluster_update_app_worker.rb b/app/workers/cluster_update_app_worker.rb new file mode 100644 index 00000000000..7ceeb167b33 --- /dev/null +++ b/app/workers/cluster_update_app_worker.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class ClusterUpdateAppWorker # rubocop:disable Scalability/IdempotentWorker + UpdateAlreadyInProgressError = Class.new(StandardError) + + include ApplicationWorker + include ClusterQueue + include ClusterApplications + include ExclusiveLeaseGuard + + sidekiq_options retry: 3, dead: false + + LEASE_TIMEOUT = 10.minutes.to_i + + def perform(app_name, app_id, project_id, scheduled_time) + @app_id = app_id + + try_obtain_lease do + execute(app_name, app_id, project_id, scheduled_time) + end + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def execute(app_name, app_id, project_id, scheduled_time) + project = Project.find_by(id: project_id) + return unless project + + find_application(app_name, app_id) do |app| + update_prometheus(app, scheduled_time, project) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + def update_prometheus(app, scheduled_time, project) + return if app.updated_since?(scheduled_time) + return if app.update_in_progress? + + Clusters::Applications::PrometheusUpdateService.new(app, project).execute + end + + def lease_key + @lease_key ||= "#{self.class.name.underscore}-#{@app_id}" + end + + def lease_timeout + LEASE_TIMEOUT + end +end diff --git a/app/workers/cluster_wait_for_app_update_worker.rb b/app/workers/cluster_wait_for_app_update_worker.rb new file mode 100644 index 00000000000..9f1d83c2c7b --- /dev/null +++ b/app/workers/cluster_wait_for_app_update_worker.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class ClusterWaitForAppUpdateWorker # rubocop:disable Scalability/IdempotentWorker + include ApplicationWorker + include ClusterQueue + include ClusterApplications + + INTERVAL = 10.seconds + TIMEOUT = 20.minutes + + def perform(app_name, app_id) + find_application(app_name, app_id) do |app| + ::Clusters::Applications::CheckUpgradeProgressService.new(app).execute + end + end +end |