Commit 6f7881ee authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖

Add latest changes from gitlab-org/[email protected]

parent 8c8bf44f
Pipeline #129117742 passed with stages
in 89 minutes and 47 seconds
......@@ -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 @@ 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
......
......@@ -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')
......@@ -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)
......
......@@ -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
......
# 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 @@ module Users
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 @@ 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
......
......@@ -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
end
---
title: Fix logic for ingress can_uninstall?
merge_request: 27729
author:
type: fixed
---
title: Update Gitaly to 12.9.0-rc5
merge_request: 27631
author:
type: added
# frozen_string_literal: true
class AddCanonicalEmails < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
create_table :user_canonical_emails do |t|
t.timestamps_with_timezone
t.references :user, index: false, null: false, foreign_key: { on_delete: :cascade }
t.string :canonical_email, null: false, index: true # rubocop:disable Migration/AddLimitToStringColumns
end
end
add_index :user_canonical_emails, [:user_id, :canonical_email], unique: true
add_index :user_canonical_emails, :user_id, unique: true
end
def down
with_lock_retries do
drop_table(:user_canonical_emails)
end
end
end
......@@ -6110,6 +6110,23 @@ CREATE SEQUENCE public.user_callouts_id_seq
ALTER SEQUENCE public.user_callouts_id_seq OWNED BY public.user_callouts.id;
CREATE TABLE public.user_canonical_emails (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
user_id bigint NOT NULL,
canonical_email character varying NOT NULL
);
CREATE SEQUENCE public.user_canonical_emails_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.user_canonical_emails_id_seq OWNED BY public.user_canonical_emails.id;
CREATE TABLE public.user_custom_attributes (
id integer NOT NULL,
created_at timestamp without time zone NOT NULL,
......@@ -7302,6 +7319,8 @@ ALTER TABLE ONLY public.user_agent_details ALTER COLUMN id SET DEFAULT nextval('
ALTER TABLE ONLY public.user_callouts ALTER COLUMN id SET DEFAULT nextval('public.user_callouts_id_seq'::regclass);
ALTER TABLE ONLY public.user_canonical_emails ALTER COLUMN id SET DEFAULT nextval('public.user_canonical_emails_id_seq'::regclass);
ALTER TABLE ONLY public.user_custom_attributes ALTER COLUMN id SET DEFAULT nextval('public.user_custom_attributes_id_seq'::regclass);
ALTER TABLE ONLY public.user_details ALTER COLUMN user_id SET DEFAULT nextval('public.user_details_user_id_seq'::regclass);
......@@ -8206,6 +8225,9 @@ ALTER TABLE ONLY public.user_agent_details
ALTER TABLE ONLY public.user_callouts
ADD CONSTRAINT user_callouts_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.user_canonical_emails
ADD CONSTRAINT user_canonical_emails_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.user_custom_attributes
ADD CONSTRAINT user_custom_attributes_pkey PRIMARY KEY (id);
......@@ -9963,6 +9985,12 @@ CREATE INDEX index_user_callouts_on_user_id ON public.user_callouts USING btree
CREATE UNIQUE INDEX index_user_callouts_on_user_id_and_feature_name ON public.user_callouts USING btree (user_id, feature_name);
CREATE INDEX index_user_canonical_emails_on_canonical_email ON public.user_canonical_emails USING btree (canonical_email);
CREATE UNIQUE INDEX index_user_canonical_emails_on_user_id ON public.user_canonical_emails USING btree (user_id);
CREATE UNIQUE INDEX index_user_canonical_emails_on_user_id_and_canonical_email ON public.user_canonical_emails USING btree (user_id, canonical_email);
CREATE INDEX index_user_custom_attributes_on_key_and_value ON public.user_custom_attributes USING btree (key, value);