Read about our upcoming Code of Conduct on this issue

This instance will be upgraded to Heptapod 0.28.1 on 2022-01-26 at 16:00 UTC+1 (a few minutes of down time)

Commit 8450dadf authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 311ae7e0a63c
......@@ -331,8 +331,21 @@ RSpec/LeakyConstantDeclaration:
Enabled: true
Exclude:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
- 'qa/spec/**/*.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/logger_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_helpers_spec.rb'
- 'ee/spec/lib/gitlab/geo/replicator_spec.rb'
- 'ee/spec/mailers/emails/service_desk_spec.rb'
- 'ee/spec/migrations/remove_creations_in_gitlab_subscription_histories_spec.rb'
- 'ee/spec/migrations/set_resolved_state_on_vulnerabilities_spec.rb'
- 'ee/spec/models/repository_spec.rb'
- 'ee/spec/presenters/security/vulnerable_project_presenter_spec.rb'
- 'ee/spec/serializers/vulnerable_project_entity_spec.rb'
- 'ee/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb'
- 'ee/spec/services/dashboard/projects/list_service_spec.rb'
- 'ee/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb'
- 'ee/spec/support/shared_contexts/epic_aggregate_constants.rb'
- 'ee/spec/workers/elastic_namespace_rollout_worker_spec.rb'
RSpec/EmptyLineAfterHook:
Enabled: false
......
......@@ -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 {
......
......@@ -50,7 +50,7 @@ def values
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 @@ def content_values
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
......
......@@ -35,6 +35,16 @@ class Prometheus < ApplicationRecord
.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 @@ def delete_knative_istio_metrics
end
end
end
Clusters::Applications::Prometheus.prepend_if_ee('EE::Clusters::Applications::Prometheus')
......@@ -2411,6 +2411,12 @@ def default_branch_protected?
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)
......
......@@ -168,6 +168,7 @@ def update_tracked_fields!(request)
has_one :user_preference
has_one :user_detail
has_one :user_highest_role
has_one :user_canonical_email
#
# Validations
......
# 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
# 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
# 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
# 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
# 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
......@@ -30,6 +30,8 @@ def execute(skip_authorization: false)
build_identity(user)
Users::UpdateCanonicalEmailService.new(user: user).execute
user
end
......
# 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
......@@ -21,6 +21,7 @@ def execute(validate: true, &block)
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 @@ def execute!(*args, &block)
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
......
......@@ -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
......
# 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
# 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