Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 14:59:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 14:59:07 +0300
commit8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca (patch)
tree544930fb309b30317ae9797a9683768705d664c4 /app/services
parent4b1de649d0168371549608993deac953eb692019 (diff)
Add latest changes from gitlab-org/gitlab@13-7-stable-eev13.7.0-rc42
Diffstat (limited to 'app/services')
-rw-r--r--app/services/admin/propagate_integration_service.rb2
-rw-r--r--app/services/alert_management/process_prometheus_alert_service.rb12
-rw-r--r--app/services/application_settings/update_service.rb26
-rw-r--r--app/services/auth/container_registry_authentication_service.rb12
-rw-r--r--app/services/auth/dependency_proxy_authentication_service.rb43
-rw-r--r--app/services/boards/lists/create_service.rb28
-rw-r--r--app/services/boards/lists/generate_service.rb6
-rw-r--r--app/services/ci/compare_codequality_reports_service.rb17
-rw-r--r--app/services/ci/create_pipeline_service.rb5
-rw-r--r--app/services/ci/list_config_variables_service.rb24
-rw-r--r--app/services/ci/test_cases_service.rb44
-rw-r--r--app/services/ci/test_failure_history_service.rb95
-rw-r--r--app/services/ci/update_build_state_service.rb20
-rw-r--r--app/services/clusters/applications/prometheus_health_check_service.rb6
-rw-r--r--app/services/clusters/aws/authorize_role_service.rb24
-rw-r--r--app/services/clusters/aws/fetch_credentials_service.rb9
-rw-r--r--app/services/concerns/exclusive_lease_guard.rb2
-rw-r--r--app/services/concerns/users/participable_service.rb59
-rw-r--r--app/services/container_expiration_policies/cleanup_service.rb3
-rw-r--r--app/services/dependency_proxy/auth_token_service.rb21
-rw-r--r--app/services/dependency_proxy/base_service.rb10
-rw-r--r--app/services/dependency_proxy/download_blob_service.rb10
-rw-r--r--app/services/dependency_proxy/find_or_create_manifest_service.rb52
-rw-r--r--app/services/dependency_proxy/head_manifest_service.rb29
-rw-r--r--app/services/dependency_proxy/pull_manifest_service.rb18
-rw-r--r--app/services/environments/canary_ingress/update_service.rb70
-rw-r--r--app/services/feature_flags/create_service.rb9
-rw-r--r--app/services/git/base_hooks_service.rb5
-rw-r--r--app/services/git/branch_hooks_service.rb2
-rw-r--r--app/services/groups/create_service.rb2
-rw-r--r--app/services/groups/transfer_service.rb13
-rw-r--r--app/services/import/bitbucket_server_service.rb8
-rw-r--r--app/services/import/github_service.rb13
-rw-r--r--app/services/incident_management/incidents/update_severity_service.rb2
-rw-r--r--app/services/integrations/test/project_service.rb6
-rw-r--r--app/services/issuable/import_csv/base_service.rb15
-rw-r--r--app/services/issues/base_service.rb16
-rw-r--r--app/services/issues/clone_service.rb90
-rw-r--r--app/services/issues/create_service.rb16
-rw-r--r--app/services/issues/export_csv_service.rb2
-rw-r--r--app/services/issues/update_service.rb15
-rw-r--r--app/services/jira/requests/base.rb7
-rw-r--r--app/services/jira_connect/sync_service.rb12
-rw-r--r--app/services/members/create_service.rb6
-rw-r--r--app/services/members/invitation_reminder_email_service.rb6
-rw-r--r--app/services/merge_requests/after_create_service.rb2
-rw-r--r--app/services/merge_requests/base_service.rb2
-rw-r--r--app/services/merge_requests/update_service.rb2
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/notification_service.rb16
-rw-r--r--app/services/onboarding_progress_service.rb11
-rw-r--r--app/services/packages/composer/create_package_service.rb2
-rw-r--r--app/services/packages/conan/create_package_file_service.rb9
-rw-r--r--app/services/packages/create_event_service.rb10
-rw-r--r--app/services/packages/create_package_service.rb16
-rw-r--r--app/services/packages/generic/create_package_file_service.rb5
-rw-r--r--app/services/packages/generic/find_or_create_package_service.rb6
-rw-r--r--app/services/packages/maven/find_or_create_package_service.rb2
-rw-r--r--app/services/packages/npm/create_package_service.rb8
-rw-r--r--app/services/packages/pypi/create_package_service.rb3
-rw-r--r--app/services/pages/legacy_storage_lease.rb28
-rw-r--r--app/services/pages/zip_directory_service.rb83
-rw-r--r--app/services/post_receive_service.rb6
-rw-r--r--app/services/projects/alerting/notify_service.rb14
-rw-r--r--app/services/projects/container_repository/delete_tags_service.rb1
-rw-r--r--app/services/projects/participants_service.rb2
-rw-r--r--app/services/projects/prometheus/alerts/notify_service.rb40
-rw-r--r--app/services/projects/schedule_bulk_repository_shard_moves_service.rb35
-rw-r--r--app/services/projects/transfer_service.rb11
-rw-r--r--app/services/projects/update_pages_service.rb16
-rw-r--r--app/services/releases/base_service.rb20
-rw-r--r--app/services/releases/create_service.rb22
-rw-r--r--app/services/resource_events/change_labels_service.rb2
-rw-r--r--app/services/service_desk_settings/update_service.rb2
-rw-r--r--app/services/submit_usage_ping_service.rb2
-rw-r--r--app/services/system_hooks_service.rb35
-rw-r--r--app/services/system_note_service.rb4
-rw-r--r--app/services/system_notes/issuables_service.rb23
-rw-r--r--app/services/upload_service.rb8
-rw-r--r--app/services/users/approve_service.rb9
-rw-r--r--app/services/users/reject_service.rb28
-rw-r--r--app/services/users/set_status_service.rb19
-rw-r--r--app/services/users/validate_otp_service.rb6
83 files changed, 1114 insertions, 290 deletions
diff --git a/app/services/admin/propagate_integration_service.rb b/app/services/admin/propagate_integration_service.rb
index ddd5add42bd..253c3a84fef 100644
--- a/app/services/admin/propagate_integration_service.rb
+++ b/app/services/admin/propagate_integration_service.rb
@@ -7,7 +7,7 @@ module Admin
def propagate
if integration.instance?
update_inherited_integrations
- create_integration_for_groups_without_integration if Feature.enabled?(:group_level_integrations, default_enabled: true)
+ create_integration_for_groups_without_integration
create_integration_for_projects_without_integration
else
update_inherited_descendant_integrations
diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb
index 28ce5401a6c..753162bfdbf 100644
--- a/app/services/alert_management/process_prometheus_alert_service.rb
+++ b/app/services/alert_management/process_prometheus_alert_service.rb
@@ -1,10 +1,16 @@
# frozen_string_literal: true
module AlertManagement
- class ProcessPrometheusAlertService < BaseService
+ class ProcessPrometheusAlertService
+ include BaseServiceUtility
include Gitlab::Utils::StrongMemoize
include ::IncidentManagement::Settings
+ def initialize(project, payload)
+ @project = project
+ @payload = payload
+ end
+
def execute
return bad_request unless incoming_payload.has_required_attributes?
@@ -19,6 +25,8 @@ module AlertManagement
private
+ attr_reader :project, :payload
+
def process_alert_management_alert
if incoming_payload.resolved?
process_resolved_alert_management_alert
@@ -127,7 +135,7 @@ module AlertManagement
strong_memoize(:incoming_payload) do
Gitlab::AlertManagement::Payload.parse(
project,
- params,
+ payload,
monitoring_tool: Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
)
end
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
index df9217bea32..7792b811b4e 100644
--- a/app/services/application_settings/update_service.rb
+++ b/app/services/application_settings/update_service.rb
@@ -9,6 +9,16 @@ module ApplicationSettings
MARKDOWN_CACHE_INVALIDATING_PARAMS = %w(asset_proxy_enabled asset_proxy_url asset_proxy_secret_key asset_proxy_whitelist).freeze
def execute
+ result = update_settings
+
+ auto_approve_blocked_users if result
+
+ result
+ end
+
+ private
+
+ def update_settings
validate_classification_label(application_setting, :external_authorization_service_default_label) unless bypass_external_auth?
if application_setting.errors.any?
@@ -40,8 +50,6 @@ module ApplicationSettings
@application_setting.save
end
- private
-
def usage_stats_updated?
params.key?(:usage_ping_enabled) || params.key?(:version_check_enabled)
end
@@ -95,6 +103,20 @@ module ApplicationSettings
def bypass_external_auth?
params.key?(:external_authorization_service_enabled) && !Gitlab::Utils.to_boolean(params[:external_authorization_service_enabled])
end
+
+ def auto_approve_blocked_users
+ return unless should_auto_approve_blocked_users?
+
+ ApproveBlockedPendingApprovalUsersWorker.perform_async(current_user.id)
+ end
+
+ def should_auto_approve_blocked_users?
+ return false unless application_setting.previous_changes.key?(:require_admin_approval_after_user_signup)
+
+ enabled_previous, enabled_current = application_setting.previous_changes[:require_admin_approval_after_user_signup]
+
+ enabled_previous && !enabled_current
+ end
end
end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 831a25a637e..d74f20511bd 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -130,6 +130,7 @@ module Auth
ContainerRepository.create_from_path!(path)
end
+ # Overridden in EE
def can_access?(requested_project, requested_action)
return false unless requested_project.container_registry_enabled?
return false if requested_project.repository_access_level == ::ProjectFeature::DISABLED
@@ -226,11 +227,16 @@ module Auth
end
end
+ # Overridden in EE
+ def extra_info
+ {}
+ end
+
def log_if_actions_denied(type, requested_project, requested_actions, authorized_actions)
return if requested_actions == authorized_actions
log_info = {
- message: "Denied container registry permissions",
+ message: 'Denied container registry permissions',
scope_type: type,
requested_project_path: requested_project.full_path,
requested_actions: requested_actions,
@@ -238,9 +244,11 @@ module Auth
username: current_user&.username,
user_id: current_user&.id,
project_path: project&.full_path
- }.compact
+ }.merge!(extra_info).compact
Gitlab::AuthLogger.warn(log_info)
end
end
end
+
+Auth::ContainerRegistryAuthenticationService.prepend_if_ee('EE::Auth::ContainerRegistryAuthenticationService')
diff --git a/app/services/auth/dependency_proxy_authentication_service.rb b/app/services/auth/dependency_proxy_authentication_service.rb
new file mode 100644
index 00000000000..1b8c16b7c79
--- /dev/null
+++ b/app/services/auth/dependency_proxy_authentication_service.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Auth
+ class DependencyProxyAuthenticationService < BaseService
+ AUDIENCE = 'dependency_proxy'
+ HMAC_KEY = 'gitlab-dependency-proxy'
+ DEFAULT_EXPIRE_TIME = 1.minute
+
+ def execute(authentication_abilities:)
+ return error('dependency proxy not enabled', 404) unless ::Gitlab.config.dependency_proxy.enabled
+ return error('access forbidden', 403) unless current_user
+
+ { token: authorized_token.encoded }
+ end
+
+ class << self
+ include ::Gitlab::Utils::StrongMemoize
+
+ def secret
+ strong_memoize(:secret) do
+ OpenSSL::HMAC.hexdigest(
+ 'sha256',
+ ::Settings.attr_encrypted_db_key_base,
+ HMAC_KEY
+ )
+ end
+ end
+
+ def token_expire_at
+ Time.current + Gitlab::CurrentSettings.container_registry_token_expire_delay.minutes
+ end
+ end
+
+ private
+
+ def authorized_token
+ JSONWebToken::HMACToken.new(self.class.secret).tap do |token|
+ token['user_id'] = current_user.id
+ token.expire_time = self.class.token_expire_at
+ end
+ end
+ end
+end
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index 9c7a165776e..a21ceee083f 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -6,17 +6,21 @@ module Boards
include Gitlab::Utils::StrongMemoize
def execute(board)
- List.transaction do
- case type
- when :backlog
- create_backlog(board)
- else
- target = target(board)
- position = next_position(board)
-
- create_list(board, type, target, position)
- end
- end
+ list = case type
+ when :backlog
+ create_backlog(board)
+ else
+ target = target(board)
+ position = next_position(board)
+
+ return ServiceResponse.error(message: _('%{board_target} not found') % { board_target: type.to_s.capitalize }) if target.blank?
+
+ create_list(board, type, target, position)
+ end
+
+ return ServiceResponse.error(message: list.errors.full_messages) unless list.persisted?
+
+ ServiceResponse.success(payload: { list: list })
end
private
@@ -33,7 +37,7 @@ module Boards
def target(board)
strong_memoize(:target) do
- available_labels.find(params[:label_id])
+ available_labels.find_by(id: params[:label_id]) # rubocop: disable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb
index 4fbf1026019..d74320e92a3 100644
--- a/app/services/boards/lists/generate_service.rb
+++ b/app/services/boards/lists/generate_service.rb
@@ -7,7 +7,11 @@ module Boards
return false unless board.lists.movable.empty?
List.transaction do
- label_params.each { |params| create_list(board, params) }
+ label_params.each do |params|
+ response = create_list(board, params)
+
+ raise ActiveRecord::Rollback unless response.success?
+ end
end
true
diff --git a/app/services/ci/compare_codequality_reports_service.rb b/app/services/ci/compare_codequality_reports_service.rb
new file mode 100644
index 00000000000..20f5378f051
--- /dev/null
+++ b/app/services/ci/compare_codequality_reports_service.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Ci
+ class CompareCodequalityReportsService < CompareReportsBaseService
+ def comparer_class
+ Gitlab::Ci::Reports::CodequalityReportsComparer
+ end
+
+ def serializer_class
+ CodequalityReportsComparerSerializer
+ end
+
+ def get_report(pipeline)
+ pipeline&.codequality_reports
+ end
+ end
+end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index e3bab2de44e..dbe81521cfc 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -18,6 +18,7 @@ module Ci
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
Gitlab::Ci::Pipeline::Chain::Seed,
Gitlab::Ci::Pipeline::Chain::Limit::Size,
+ Gitlab::Ci::Pipeline::Chain::Limit::Deployments,
Gitlab::Ci::Pipeline::Chain::Validate::External,
Gitlab::Ci::Pipeline::Chain::Populate,
Gitlab::Ci::Pipeline::Chain::StopDryRun,
@@ -90,7 +91,9 @@ module Ci
# rubocop: enable Metrics/ParameterLists
def execute!(*args, &block)
- execute(*args, &block).tap do |pipeline|
+ source, params = args[0], Hash(args[1])
+
+ execute(source, **params, &block).tap do |pipeline|
unless pipeline.persisted?
raise CreateError, pipeline.full_error_messages
end
diff --git a/app/services/ci/list_config_variables_service.rb b/app/services/ci/list_config_variables_service.rb
index 4a5b3a92a2c..88dac514bb9 100644
--- a/app/services/ci/list_config_variables_service.rb
+++ b/app/services/ci/list_config_variables_service.rb
@@ -2,7 +2,26 @@
module Ci
class ListConfigVariablesService < ::BaseService
+ include ReactiveCaching
+
+ self.reactive_cache_key = ->(service) { [service.class.name, service.id] }
+ self.reactive_cache_work_type = :external_dependency
+ self.reactive_cache_worker_finder = ->(id, *_args) { from_cache(id) }
+
+ def self.from_cache(id)
+ project_id, user_id = id.split('-')
+
+ project = Project.find(project_id)
+ user = User.find(user_id)
+
+ new(project, user)
+ end
+
def execute(sha)
+ with_reactive_cache(sha) { |result| result }
+ end
+
+ def calculate_reactive_cache(sha)
config = project.ci_config_for(sha)
return {} unless config
@@ -12,5 +31,10 @@ module Ci
result.valid? ? result.variables_with_data : {}
end
+
+ # Required for ReactiveCaching, it is also used in `reactive_cache_worker_finder`
+ def id
+ "#{project.id}-#{current_user.id}"
+ end
end
end
diff --git a/app/services/ci/test_cases_service.rb b/app/services/ci/test_cases_service.rb
deleted file mode 100644
index 3139b567571..00000000000
--- a/app/services/ci/test_cases_service.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-module Ci
- class TestCasesService
- MAX_TRACKABLE_FAILURES = 200
-
- def execute(build)
- return unless Feature.enabled?(:test_failure_history, build.project)
- return unless build.has_test_reports?
- return unless build.project.default_branch_or_master == build.ref
-
- test_suite = generate_test_suite_report(build)
-
- track_failures(build, test_suite)
- end
-
- private
-
- def generate_test_suite_report(build)
- build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
- end
-
- def track_failures(build, test_suite)
- return if test_suite.failed_count > MAX_TRACKABLE_FAILURES
-
- test_suite.failed.keys.each_slice(100) do |keys|
- Ci::TestCase.transaction do
- test_cases = Ci::TestCase.find_or_create_by_batch(build.project, keys)
- Ci::TestCaseFailure.insert_all(test_case_failures(test_cases, build))
- end
- end
- end
-
- def test_case_failures(test_cases, build)
- test_cases.map do |test_case|
- {
- test_case_id: test_case.id,
- build_id: build.id,
- failed_at: build.finished_at
- }
- end
- end
- end
-end
diff --git a/app/services/ci/test_failure_history_service.rb b/app/services/ci/test_failure_history_service.rb
new file mode 100644
index 00000000000..99a2592ec06
--- /dev/null
+++ b/app/services/ci/test_failure_history_service.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module Ci
+ class TestFailureHistoryService
+ class Async
+ attr_reader :service
+
+ def initialize(service)
+ @service = service
+ end
+
+ def perform_if_needed
+ TestFailureHistoryWorker.perform_async(service.pipeline.id) if service.should_track_failures?
+ end
+ end
+
+ MAX_TRACKABLE_FAILURES = 200
+
+ attr_reader :pipeline
+ delegate :project, to: :pipeline
+
+ def initialize(pipeline)
+ @pipeline = pipeline
+ end
+
+ def execute
+ return unless should_track_failures?
+
+ track_failures
+ end
+
+ def should_track_failures?
+ return false unless Feature.enabled?(:test_failure_history, project)
+ return false unless project.default_branch_or_master == pipeline.ref
+
+ # We fetch for up to MAX_TRACKABLE_FAILURES + 1 builds. So if ever we get
+ # 201 total number of builds with the assumption that each job has at least
+ # 1 failed test case, then we have at least 201 failed test cases which exceeds
+ # the MAX_TRACKABLE_FAILURES of 200. If this is the case, let's early exit so we
+ # don't have to parse each JUnit report of each of the 201 builds.
+ failed_builds.length <= MAX_TRACKABLE_FAILURES
+ end
+
+ def async
+ Async.new(self)
+ end
+
+ private
+
+ def failed_builds
+ @failed_builds ||= pipeline.builds_with_failed_tests(limit: MAX_TRACKABLE_FAILURES + 1)
+ end
+
+ def track_failures
+ failed_test_cases = gather_failed_test_cases(failed_builds)
+
+ return if failed_test_cases.size > MAX_TRACKABLE_FAILURES
+
+ failed_test_cases.keys.each_slice(100) do |key_hashes|
+ Ci::TestCase.transaction do
+ ci_test_cases = Ci::TestCase.find_or_create_by_batch(project, key_hashes)
+ failures = test_case_failures(ci_test_cases, failed_test_cases)
+
+ Ci::TestCaseFailure.insert_all(failures)
+ end
+ end
+ end
+
+ def gather_failed_test_cases(failed_builds)
+ failed_builds.each_with_object({}) do |build, failed_test_cases|
+ test_suite = generate_test_suite!(build)
+ test_suite.failed.keys.each do |key|
+ failed_test_cases[key] = build
+ end
+ end
+ end
+
+ def generate_test_suite!(build)
+ # Returns an instance of Gitlab::Ci::Reports::TestSuite
+ build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
+ end
+
+ def test_case_failures(ci_test_cases, failed_test_cases)
+ ci_test_cases.map do |test_case|
+ build = failed_test_cases[test_case.key_hash]
+
+ {
+ test_case_id: test_case.id,
+ build_id: build.id,
+ failed_at: build.finished_at
+ }
+ end
+ end
+ end
+end
diff --git a/app/services/ci/update_build_state_service.rb b/app/services/ci/update_build_state_service.rb
index fb67b0d2355..f01d41d9414 100644
--- a/app/services/ci/update_build_state_service.rb
+++ b/app/services/ci/update_build_state_service.rb
@@ -82,6 +82,10 @@ module Ci
unless checksum.valid?
metrics.increment_trace_operation(operation: :invalid)
+ if checksum.corrupted?
+ metrics.increment_trace_operation(operation: :corrupted)
+ end
+
next unless log_invalid_chunks?
::Gitlab::ErrorTracking.log_exception(InvalidTraceError.new,
@@ -89,7 +93,8 @@ module Ci
build_id: build.id,
state_crc32: checksum.state_crc32,
chunks_crc32: checksum.chunks_crc32,
- chunks_count: checksum.chunks_count
+ chunks_count: checksum.chunks_count,
+ chunks_corrupted: checksum.corrupted?
)
end
end
@@ -151,13 +156,21 @@ module Ci
end
def has_checksum?
- params.dig(:checksum).present?
+ trace_checksum.present?
end
def build_running?
build_state == 'running'
end
+ def trace_checksum
+ params.dig(:output, :checksum) || params.dig(:checksum)
+ end
+
+ def trace_bytesize
+ params.dig(:output, :bytesize)
+ end
+
def pending_state
strong_memoize(:pending_state) { ensure_pending_state }
end
@@ -166,7 +179,8 @@ module Ci
build_state = Ci::BuildPendingState.safe_find_or_create_by(
build_id: build.id,
state: params.fetch(:state),
- trace_checksum: params.fetch(:checksum),
+ trace_checksum: trace_checksum,
+ trace_bytesize: trace_bytesize,
failure_reason: params.dig(:failure_reason)
)
diff --git a/app/services/clusters/applications/prometheus_health_check_service.rb b/app/services/clusters/applications/prometheus_health_check_service.rb
index e609d9f0b7b..eda47f56e72 100644
--- a/app/services/clusters/applications/prometheus_health_check_service.rb
+++ b/app/services/clusters/applications/prometheus_health_check_service.rb
@@ -63,8 +63,10 @@ module Clusters
def send_notification(project)
notification_payload = build_notification_payload(project)
- token = project.alerts_service.data.token
- Projects::Alerting::NotifyService.new(project, nil, notification_payload).execute(token)
+ integration = project.alert_management_http_integrations.active.first
+
+ Projects::Alerting::NotifyService.new(project, notification_payload).execute(integration&.token, integration)
+
@logger.info(message: 'Successfully notified of Prometheus newly unhealthy', cluster_id: @cluster.id, project_id: project.id)
end
diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb
index 188c4aebc5f..7ca20289bf7 100644
--- a/app/services/clusters/aws/authorize_role_service.rb
+++ b/app/services/clusters/aws/authorize_role_service.rb
@@ -29,7 +29,7 @@ module Clusters
rescue *ERRORS => e
Gitlab::ErrorTracking.track_exception(e)
- Response.new(:unprocessable_entity, {})
+ Response.new(:unprocessable_entity, response_details(e))
end
private
@@ -47,6 +47,28 @@ module Clusters
def credentials
Clusters::Aws::FetchCredentialsService.new(role).execute
end
+
+ def response_details(exception)
+ message =
+ case exception
+ when ::Aws::STS::Errors::AccessDenied
+ _("Access denied: %{error}") % { error: exception.message }
+ when ::Aws::STS::Errors::ServiceError
+ _("AWS service error: %{error}") % { error: exception.message }
+ when ActiveRecord::RecordNotFound
+ _("Error: Unable to find AWS role for current user")
+ when ActiveRecord::RecordInvalid
+ exception.message
+ when Clusters::Aws::FetchCredentialsService::MissingRoleError
+ _("Error: No AWS provision role found for user")
+ when ::Aws::Errors::MissingCredentialsError
+ _("Error: No AWS credentials were supplied")
+ else
+ _('An error occurred while authorizing your role')
+ end
+
+ { message: message }.compact
+ end
end
end
end
diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb
index 96abbb43969..497e676f549 100644
--- a/app/services/clusters/aws/fetch_credentials_service.rb
+++ b/app/services/clusters/aws/fetch_credentials_service.rb
@@ -30,10 +30,17 @@ module Clusters
attr_reader :provider, :region
def client
- ::Aws::STS::Client.new(credentials: gitlab_credentials, region: region)
+ ::Aws::STS::Client.new(**client_args)
+ end
+
+ def client_args
+ { region: region, credentials: gitlab_credentials }.compact
end
def gitlab_credentials
+ # These are not needed for IAM instance profiles
+ return unless access_key_id.present? && secret_access_key.present?
+
::Aws::Credentials.new(access_key_id, secret_access_key)
end
diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb
index a58e9aefcec..76d59cf2159 100644
--- a/app/services/concerns/exclusive_lease_guard.rb
+++ b/app/services/concerns/exclusive_lease_guard.rb
@@ -21,7 +21,7 @@ module ExclusiveLeaseGuard
lease = exclusive_lease.try_obtain
unless lease
- log_error("Cannot obtain an exclusive lease for #{self.class.name}. There must be another instance already in execution.")
+ log_error("Cannot obtain an exclusive lease for #{lease_key}. There must be another instance already in execution.")
return
end
diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb
index 4f4032e77b9..c1c93aa604e 100644
--- a/app/services/concerns/users/participable_service.rb
+++ b/app/services/concerns/users/participable_service.rb
@@ -8,10 +8,14 @@ module Users
attr_reader :noteable
end
+ private
+
def noteable_owner
return [] unless noteable && noteable.author.present?
- [user_as_hash(noteable.author)]
+ [noteable.author].tap do |users|
+ preload_status(users)
+ end
end
def participants_in_noteable
@@ -22,23 +26,29 @@ module Users
end
def sorted(users)
- users.uniq.to_a.compact.sort_by(&:username).map do |user|
- user_as_hash(user)
+ users.uniq.to_a.compact.sort_by(&:username).tap do |users|
+ preload_status(users)
end
end
def groups
- group_counts = GroupMember
- .of_groups(current_user.authorized_groups)
- .non_request
- .count_users_by_group_id
+ current_user.authorized_groups.with_route.sort_by(&:path)
+ end
- current_user.authorized_groups.with_route.sort_by(&:path).map do |group|
- group_as_hash(group, group_counts)
- end
+ def render_participants_as_hash(participants)
+ participants.map(&method(:participant_as_hash))
end
- private
+ def participant_as_hash(participant)
+ case participant
+ when Group
+ group_as_hash(participant)
+ when User
+ user_as_hash(participant)
+ else
+ participant
+ end
+ end
def user_as_hash(user)
{
@@ -46,12 +56,11 @@ module Users
username: user.username,
name: user.name,
avatar_url: user.avatar_url,
- availability: nil
+ availability: lazy_user_availability(user).itself # calling #itself to avoid returning a BatchLoader instance
}
- # Return nil for availability for now due to https://gitlab.com/gitlab-org/gitlab/-/issues/285442
end
- def group_as_hash(group, group_counts)
+ def group_as_hash(group)
{
type: group.class.name,
username: group.full_path,
@@ -61,5 +70,27 @@ module Users
mentionsDisabled: group.mentions_disabled
}
end
+
+ def group_counts
+ @group_counts ||= GroupMember
+ .of_groups(current_user.authorized_groups)
+ .non_request
+ .count_users_by_group_id
+ end
+
+ def preload_status(users)
+ users.each { |u| lazy_user_availability(u) }
+ end
+
+ def lazy_user_availability(user)
+ BatchLoader.for(user.id).batch do |user_ids, loader|
+ user_ids.each_slice(1_000) do |sliced_user_ids|
+ UserStatus
+ .select(:user_id, :availability)
+ .primary_key_in(sliced_user_ids)
+ .each { |status| loader.call(status.user_id, status.availability) }
+ end
+ end
+ end
end
end
diff --git a/app/services/container_expiration_policies/cleanup_service.rb b/app/services/container_expiration_policies/cleanup_service.rb
index f2bc2beab63..4719c99af6d 100644
--- a/app/services/container_expiration_policies/cleanup_service.rb
+++ b/app/services/container_expiration_policies/cleanup_service.rb
@@ -20,7 +20,8 @@ module ContainerExpirationPolicies
if result[:status] == :success
repository.update!(
expiration_policy_cleanup_status: :cleanup_unscheduled,
- expiration_policy_started_at: nil
+ expiration_policy_started_at: nil,
+ expiration_policy_completed_at: Time.zone.now
)
success(:finished)
else
diff --git a/app/services/dependency_proxy/auth_token_service.rb b/app/services/dependency_proxy/auth_token_service.rb
new file mode 100644
index 00000000000..16279ed12b0
--- /dev/null
+++ b/app/services/dependency_proxy/auth_token_service.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module DependencyProxy
+ class AuthTokenService < DependencyProxy::BaseService
+ attr_reader :token
+
+ def initialize(token)
+ @token = token
+ end
+
+ def execute
+ JSONWebToken::HMACToken.decode(token, ::Auth::DependencyProxyAuthenticationService.secret).first
+ end
+
+ class << self
+ def decoded_token_payload(token)
+ self.new(token).execute
+ end
+ end
+ end
+end
diff --git a/app/services/dependency_proxy/base_service.rb b/app/services/dependency_proxy/base_service.rb
index 1b2d4b14a27..944877fd5f9 100644
--- a/app/services/dependency_proxy/base_service.rb
+++ b/app/services/dependency_proxy/base_service.rb
@@ -2,6 +2,16 @@
module DependencyProxy
class BaseService < ::BaseService
+ class DownloadError < StandardError
+ attr_reader :http_status
+
+ def initialize(message, http_status)
+ @http_status = http_status
+
+ super(message)
+ end
+ end
+
private
def registry
diff --git a/app/services/dependency_proxy/download_blob_service.rb b/app/services/dependency_proxy/download_blob_service.rb
index 3c690683bf6..b3548c8a126 100644
--- a/app/services/dependency_proxy/download_blob_service.rb
+++ b/app/services/dependency_proxy/download_blob_service.rb
@@ -2,16 +2,6 @@
module DependencyProxy
class DownloadBlobService < DependencyProxy::BaseService
- class DownloadError < StandardError
- attr_reader :http_status
-
- def initialize(message, http_status)
- @http_status = http_status
-
- super(message)
- end
- end
-
def initialize(image, blob_sha, token)
@image = image
@blob_sha = blob_sha
diff --git a/app/services/dependency_proxy/find_or_create_manifest_service.rb b/app/services/dependency_proxy/find_or_create_manifest_service.rb
new file mode 100644
index 00000000000..6b46f5e4c59
--- /dev/null
+++ b/app/services/dependency_proxy/find_or_create_manifest_service.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module DependencyProxy
+ class FindOrCreateManifestService < DependencyProxy::BaseService
+ def initialize(group, image, tag, token)
+ @group = group
+ @image = image
+ @tag = tag
+ @token = token
+ @file_name = "#{@image}:#{@tag}.json"
+ @manifest = nil
+ end
+
+ def execute
+ @manifest = @group.dependency_proxy_manifests
+ .find_or_initialize_by_file_name(@file_name)
+
+ head_result = DependencyProxy::HeadManifestService.new(@image, @tag, @token).execute
+
+ return success(manifest: @manifest) if cached_manifest_matches?(head_result)
+
+ pull_new_manifest
+ respond
+ rescue Timeout::Error, *Gitlab::HTTP::HTTP_ERRORS
+ respond
+ end
+
+ private
+
+ def pull_new_manifest
+ DependencyProxy::PullManifestService.new(@image, @tag, @token).execute_with_manifest do |new_manifest|
+ @manifest.update!(
+ digest: new_manifest[:digest],
+ file: new_manifest[:file],
+ size: new_manifest[:file].size
+ )
+ end
+ end
+
+ def cached_manifest_matches?(head_result)
+ @manifest && @manifest.digest == head_result[:digest]
+ end
+
+ def respond
+ if @manifest.persisted?
+ success(manifest: @manifest)
+ else
+ error('Failed to download the manifest from the external registry', 503)
+ end
+ end
+ end
+end
diff --git a/app/services/dependency_proxy/head_manifest_service.rb b/app/services/dependency_proxy/head_manifest_service.rb
new file mode 100644
index 00000000000..87d9c417c98
--- /dev/null
+++ b/app/services/dependency_proxy/head_manifest_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module DependencyProxy
+ class HeadManifestService < DependencyProxy::BaseService
+ def initialize(image, tag, token)
+ @image = image
+ @tag = tag
+ @token = token
+ end
+
+ def execute
+ response = Gitlab::HTTP.head(manifest_url, headers: auth_headers)
+
+ if response.success?
+ success(digest: response.headers['docker-content-digest'])
+ else
+ error(response.body, response.code)
+ end
+ rescue Timeout::Error => exception
+ error(exception.message, 599)
+ end
+
+ private
+
+ def manifest_url
+ registry.manifest_url(@image, @tag)
+ end
+ end
+end
diff --git a/app/services/dependency_proxy/pull_manifest_service.rb b/app/services/dependency_proxy/pull_manifest_service.rb
index fc54ef85c96..5c804489fd1 100644
--- a/app/services/dependency_proxy/pull_manifest_service.rb
+++ b/app/services/dependency_proxy/pull_manifest_service.rb
@@ -8,13 +8,25 @@ module DependencyProxy
@token = token
end
- def execute
+ def execute_with_manifest
+ raise ArgumentError, 'Block must be provided' unless block_given?
+
response = Gitlab::HTTP.get(manifest_url, headers: auth_headers)
if response.success?
- success(manifest: response.body)
+ file = Tempfile.new
+
+ begin
+ file.write(response)
+ file.flush
+
+ yield(success(file: file, digest: response.headers['docker-content-digest']))
+ ensure
+ file.close
+ file.unlink
+ end
else
- error(response.body, response.code)
+ yield(error(response.body, response.code))
end
rescue Timeout::Error => exception
error(exception.message, 599)
diff --git a/app/services/environments/canary_ingress/update_service.rb b/app/services/environments/canary_ingress/update_service.rb
new file mode 100644
index 00000000000..474c3de23d9
--- /dev/null
+++ b/app/services/environments/canary_ingress/update_service.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Environments
+ module CanaryIngress
+ class UpdateService < ::BaseService
+ def execute_async(environment)
+ result = validate(environment)
+
+ return result unless result[:status] == :success
+
+ Environments::CanaryIngress::UpdateWorker.perform_async(environment.id, params)
+
+ success
+ end
+
+ # This method actually executes the PATCH request to Kubernetes,
+ # that is used by internal processes i.e. sidekiq worker.
+ # You should always use `execute_async` to properly validate user's requests.
+ def execute(environment)
+ canary_ingress = environment.ingresses&.find(&:canary?)
+
+ unless canary_ingress.present?
+ return error(_('Canary Ingress does not exist in the environment.'))
+ end
+
+ if environment.patch_ingress(canary_ingress, patch_data)
+ success
+ else
+ error(_('Failed to update the Canary Ingress.'), :bad_request)
+ end
+ end
+
+ private
+
+ def validate(environment)
+ unless Feature.enabled?(:canary_ingress_weight_control, environment.project, default_enabled: true)
+ return error(_("Feature flag is not enabled on the environment's project."))
+ end
+
+ unless can?(current_user, :update_environment, environment)
+ return error(_('You do not have permission to update the environment.'))
+ end
+
+ unless params[:weight].is_a?(Integer) && (0..100).cover?(params[:weight])
+ return error(_('Canary weight must be specified and valid range (0..100).'))
+ end
+
+ if environment.has_running_deployments?
+ return error(_('There are running deployments on the environment. Please retry later.'))
+ end
+
+ if ::Gitlab::ApplicationRateLimiter.throttled?(:update_environment_canary_ingress, scope: [environment])
+ return error(_("This environment's canary ingress has been updated recently. Please retry later."))
+ end
+
+ success
+ end
+
+ def patch_data
+ {
+ metadata: {
+ annotations: {
+ Gitlab::Kubernetes::Ingress::ANNOTATION_KEY_CANARY_WEIGHT => params[:weight].to_s
+ }
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/app/services/feature_flags/create_service.rb b/app/services/feature_flags/create_service.rb
index b4ca90f7aae..de3a55d10fc 100644
--- a/app/services/feature_flags/create_service.rb
+++ b/app/services/feature_flags/create_service.rb
@@ -5,7 +5,6 @@ module FeatureFlags
def execute
return error('Access Denied', 403) unless can_create?
return error('Version is invalid', :bad_request) unless valid_version?
- return error('New version feature flags are not enabled for this project', :bad_request) unless flag_version_enabled?
ActiveRecord::Base.transaction do
feature_flag = project.operations_feature_flags.new(params)
@@ -40,13 +39,5 @@ module FeatureFlags
def valid_version?
!params.key?(:version) || Operations::FeatureFlag.versions.key?(params[:version])
end
-
- def flag_version_enabled?
- params[:version] != 'new_version_flag' || new_version_feature_flags_enabled?
- end
-
- def new_version_feature_flags_enabled?
- ::Feature.enabled?(:feature_flags_new_version, project, default_enabled: true)
- end
end
end
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index ea5b2f401b3..1ca1bfa0c05 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -135,11 +135,12 @@ module Git
# We only need the last commit for the event push, and we don't
# need the full deltas either.
@event_push_data ||= Gitlab::DataBuilder::Push.build(
- push_data_params(commits: commits.last, with_changed_files: false))
+ **push_data_params(commits: commits.last, with_changed_files: false)
+ )
end
def push_data
- @push_data ||= Gitlab::DataBuilder::Push.build(push_data_params(commits: limited_commits))
+ @push_data ||= Gitlab::DataBuilder::Push.build(**push_data_params(commits: limited_commits))
# Dependent code may modify the push data, so return a duplicate each time
@push_data.dup
diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb
index d00ca83441a..4edcff0e3d0 100644
--- a/app/services/git/branch_hooks_service.rb
+++ b/app/services/git/branch_hooks_service.rb
@@ -118,7 +118,7 @@ module Git
commits_to_sync = limited_commits.select { |commit| Atlassian::JiraIssueKeyExtractor.has_keys?(commit.safe_message) }.map(&:sha)
if branch_to_sync || commits_to_sync.any?
- JiraConnect::SyncBranchWorker.perform_async(project.id, branch_to_sync, commits_to_sync)
+ JiraConnect::SyncBranchWorker.perform_async(project.id, branch_to_sync, commits_to_sync, Atlassian::JiraConnect::Client.generate_update_sequence_id)
end
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index 016c31cbccc..52600f5b88f 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -34,7 +34,7 @@ module Groups
if @group.save
@group.add_owner(current_user)
@group.create_namespace_settings
- Service.create_from_active_default_integrations(@group, :group_id) if Feature.enabled?(:group_level_integrations, default_enabled: true)
+ Service.create_from_active_default_integrations(@group, :group_id)
end
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index aad574aeaf5..e800e546a45 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -28,9 +28,11 @@ module Groups
Group.transaction do
update_group_attributes
ensure_ownership
+ update_integrations
end
post_update_hooks(@updated_project_ids)
+ propagate_integrations
true
end
@@ -196,6 +198,17 @@ module Groups
raise TransferError, result[:message] unless result[:status] == :success
end
end
+
+ def update_integrations
+ @group.services.inherit.delete_all
+ Service.create_from_active_default_integrations(@group, :group_id)
+ end
+
+ def propagate_integrations
+ @group.services.inherit.each do |integration|
+ PropagateIntegrationWorker.perform_async(integration.id)
+ end
+ end
end
end
diff --git a/app/services/import/bitbucket_server_service.rb b/app/services/import/bitbucket_server_service.rb
index 86e8215821e..cdb23370ddc 100644
--- a/app/services/import/bitbucket_server_service.rb
+++ b/app/services/import/bitbucket_server_service.rb
@@ -81,11 +81,9 @@ module Import
def blocked_url?
Gitlab::UrlBlocker.blocked_url?(
url,
- {
- allow_localhost: allow_local_requests?,
- allow_local_network: allow_local_requests?,
- schemes: %w(http https)
- }
+ allow_localhost: allow_local_requests?,
+ allow_local_network: allow_local_requests?,
+ schemes: %w(http https)
)
end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
index 948dba2d206..847c5eb4397 100644
--- a/app/services/import/github_service.rb
+++ b/app/services/import/github_service.rb
@@ -31,9 +31,8 @@ module Import
project_name,
target_namespace,
current_user,
- access_params,
- type: provider
- ).execute(extra_project_attrs)
+ type: provider,
+ **access_params).execute(extra_project_attrs)
end
def repo
@@ -71,11 +70,9 @@ module Import
def blocked_url?
Gitlab::UrlBlocker.blocked_url?(
url,
- {
- allow_localhost: allow_local_requests?,
- allow_local_network: allow_local_requests?,
- schemes: %w(http https)
- }
+ allow_localhost: allow_local_requests?,
+ allow_local_network: allow_local_requests?,
+ schemes: %w(http https)
)
end
diff --git a/app/services/incident_management/incidents/update_severity_service.rb b/app/services/incident_management/incidents/update_severity_service.rb
index 5b150f3f02e..faa9277c469 100644
--- a/app/services/incident_management/incidents/update_severity_service.rb
+++ b/app/services/incident_management/incidents/update_severity_service.rb
@@ -12,7 +12,7 @@ module IncidentManagement
end
def execute
- return unless issuable.incident?
+ return unless issuable.supports_severity?
update_severity!
add_system_note
diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb
index 39471d373f9..d72ca928c34 100644
--- a/app/services/integrations/test/project_service.rb
+++ b/app/services/integrations/test/project_service.rb
@@ -16,9 +16,7 @@ module Integrations
def data
strong_memoize(:data) do
- next pipeline_events_data if integration.is_a?(::PipelinesEmailService)
-
- case event
+ case event || integration.default_test_event
when 'push', 'tag_push'
push_events_data
when 'note', 'confidential_note'
@@ -37,8 +35,6 @@ module Integrations
deployment_events_data
when 'release'
releases_events_data
- else
- push_events_data
end
end
end
diff --git a/app/services/issuable/import_csv/base_service.rb b/app/services/issuable/import_csv/base_service.rb
index bf5f643a51b..5a2665285de 100644
--- a/app/services/issuable/import_csv/base_service.rb
+++ b/app/services/issuable/import_csv/base_service.rb
@@ -38,20 +38,19 @@ module Issuable
def with_csv_lines
csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8)
- verify_headers!(csv_data)
+ validate_headers_presence!(csv_data.lines.first)
- csv_parsing_params = {
+ CSV.new(
+ csv_data,
col_sep: detect_col_sep(csv_data.lines.first),
headers: true,
header_converters: :symbol
- }
-
- CSV.new(csv_data, csv_parsing_params).each.with_index(2)
+ ).each.with_index(2)
end
- def verify_headers!(data)
- headers = data.lines.first.downcase
- return if headers.include?('title') && headers.include?('description')
+ def validate_headers_presence!(headers)
+ headers.downcase! if headers
+ return if headers && headers.include?('title') && headers.include?('description')
raise CSV::MalformedCSVError
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 978ea6fe9bc..25f319da03b 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -73,22 +73,6 @@ module Issues
Milestones::IssuesCountService.new(milestone).delete_cache
end
-
- # Applies label "incident" (creates it if missing) to incident issues.
- # Please use in "after" hooks only to ensure we are not appyling
- # labels prematurely.
- def add_incident_label(issue)
- return unless issue.incident?
-
- label = ::IncidentManagement::CreateIncidentLabelService
- .new(project, current_user)
- .execute
- .payload[:label]
-
- return if issue.label_ids.include?(label.id)
-
- issue.labels << label
- end
end
end
diff --git a/app/services/issues/clone_service.rb b/app/services/issues/clone_service.rb
new file mode 100644
index 00000000000..789da312958
--- /dev/null
+++ b/app/services/issues/clone_service.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+module Issues
+ class CloneService < Issuable::Clone::BaseService
+ CloneError = Class.new(StandardError)
+
+ def execute(issue, target_project, with_notes: false)
+ @target_project = target_project
+ @with_notes = with_notes
+
+ unless issue.can_clone?(current_user, target_project)
+ raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions!')
+ end
+
+ if target_project.pending_delete?
+ raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.')
+ end
+
+ super(issue, target_project)
+
+ notify_participants
+
+ queue_copy_designs
+
+ new_entity
+ end
+
+ private
+
+ attr_reader :target_project
+ attr_reader :with_notes
+
+ def update_new_entity
+ # we don't call `super` because we want to be able to decide whether or not to copy all comments over.
+ update_new_entity_description
+ update_new_entity_attributes
+ copy_award_emoji
+ copy_notes if with_notes
+ end
+
+ def update_old_entity
+ # no-op
+ # The base_service closes the old issue, we don't want that, so we override here so nothing happens.
+ end
+
+ def create_new_entity
+ new_params = {
+ id: nil,
+ iid: nil,
+ project: target_project,
+ author: current_user,
+ assignee_ids: original_entity.assignee_ids
+ }
+
+ new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
+
+ # Skip creation of system notes for existing attributes of the issue. The system notes of the old
+ # issue are copied over so we don't want to end up with duplicate notes.
+ CreateService.new(target_project, current_user, new_params).execute(skip_system_notes: true)
+ end
+
+ def queue_copy_designs
+ return unless original_entity.designs.present?
+
+ response = DesignManagement::CopyDesignCollection::QueueService.new(
+ current_user,
+ original_entity,
+ new_entity
+ ).execute
+
+ log_error(response.message) if response.error?
+ end
+
+ def notify_participants
+ notification_service.async.issue_cloned(original_entity, new_entity, current_user)
+ end
+
+ def add_note_from
+ SystemNoteService.noteable_cloned(new_entity, target_project,
+ original_entity, current_user,
+ direction: :from)
+ end
+
+ def add_note_to
+ SystemNoteService.noteable_cloned(original_entity, old_project,
+ new_entity, current_user,
+ direction: :to)
+ end
+ end
+end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index fb7683f940d..44de8eb6389 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -49,6 +49,22 @@ module Issues
def user_agent_detail_service
UserAgentDetailService.new(@issue, @request)
end
+
+ # Applies label "incident" (creates it if missing) to incident issues.
+ # For use in "after" hooks only to ensure we are not appyling
+ # labels prematurely.
+ def add_incident_label(issue)
+ return unless issue.incident?
+
+ label = ::IncidentManagement::CreateIncidentLabelService
+ .new(project, current_user)
+ .execute
+ .payload[:label]
+
+ return if issue.label_ids.include?(label.id)
+
+ issue.labels << label
+ end
end
end
diff --git a/app/services/issues/export_csv_service.rb b/app/services/issues/export_csv_service.rb
index 1dcdfb9faea..8f513632929 100644
--- a/app/services/issues/export_csv_service.rb
+++ b/app/services/issues/export_csv_service.rb
@@ -34,7 +34,7 @@ module Issues
private
def associations_to_preload
- %i(author assignees timelogs)
+ %i(author assignees timelogs milestone)
end
def header_to_value_hash
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index b9832400302..127ed04cf51 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -9,7 +9,7 @@ module Issues
handle_move_between_ids(issue)
filter_spam_check_params
change_issue_duplicate(issue)
- move_issue_to_new_project(issue) || update_task_event(issue) || update(issue)
+ move_issue_to_new_project(issue) || clone_issue(issue) || update_task_event(issue) || update(issue)
end
def update(issue)
@@ -34,7 +34,6 @@ module Issues
end
def after_update(issue)
- add_incident_label(issue)
IssuesChannel.broadcast_to(issue, event: 'updated') if Gitlab::ActionCable::Config.in_app? || Feature.enabled?(:broadcast_issue_updates, issue.project)
end
@@ -127,6 +126,18 @@ module Issues
private
+ def clone_issue(issue)
+ target_project = params.delete(:target_clone_project)
+ with_notes = params.delete(:clone_with_notes)
+
+ return unless target_project &&
+ issue.can_clone?(current_user, target_project)
+
+ # we've pre-empted this from running in #execute, so let's go ahead and update the Issue now.
+ update(issue)
+ Issues::CloneService.new(project, current_user).execute(issue, target_project, with_notes: with_notes)
+ end
+
def create_merge_request_from_quick_action
create_merge_request_params = params.delete(:create_merge_request)
return unless create_merge_request_params
diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb
index 4ed8df0f235..098aae9284c 100644
--- a/app/services/jira/requests/base.rb
+++ b/app/services/jira/requests/base.rb
@@ -18,14 +18,19 @@ module Jira
request
end
+ # We have to add the context_path here because the Jira client is not taking it into account
def base_api_url
- "/rest/api/#{api_version}"
+ "#{context_path}/rest/api/#{api_version}"
end
private
attr_reader :jira_service, :project
+ def context_path
+ client.options[:context_path].to_s
+ end
+
# override this method in the specific request class implementation if a differnt API version is required
def api_version
JIRA_API_VERSION
diff --git a/app/services/jira_connect/sync_service.rb b/app/services/jira_connect/sync_service.rb
index f8855fb6deb..b2af284f1f0 100644
--- a/app/services/jira_connect/sync_service.rb
+++ b/app/services/jira_connect/sync_service.rb
@@ -6,13 +6,15 @@ module JiraConnect
self.project = project
end
- def execute(commits: nil, branches: nil, merge_requests: nil, update_sequence_id: nil)
- JiraConnectInstallation.for_project(project).each do |installation|
+ # Parameters: see Atlassian::JiraConnect::Client#send_info
+ # Includes: update_sequence_id, commits, branches, merge_requests, pipelines
+ def execute(**args)
+ JiraConnectInstallation.for_project(project).flat_map do |installation|
client = Atlassian::JiraConnect::Client.new(installation.base_url, installation.shared_secret)
- response = client.store_dev_info(project: project, commits: commits, branches: branches, merge_requests: merge_requests, update_sequence_id: update_sequence_id)
+ responses = client.send_info(project: project, **args)
- log_response(response)
+ responses.each { |r| log_response(r) }
end
end
@@ -29,7 +31,7 @@ module JiraConnect
jira_response: response&.to_json
}
- if response && response['errorMessages']
+ if response && (response['errorMessages'] || response['rejectedBuilds'].present?)
logger.error(message)
else
logger.info(message)
diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb
index 088e6f031c8..3588cda180f 100644
--- a/app/services/members/create_service.rb
+++ b/app/services/members/create_service.rb
@@ -38,6 +38,8 @@ module Members
end
end
+ enqueue_onboarding_progress_action(source) if members.size > errors.size
+
return success unless errors.any?
error(errors.to_sentence)
@@ -50,6 +52,10 @@ module Members
limit && limit < 0 ? nil : limit
end
+
+ def enqueue_onboarding_progress_action(source)
+ Namespaces::OnboardingUserAddedWorker.perform_async(source.id)
+ end
end
end
diff --git a/app/services/members/invitation_reminder_email_service.rb b/app/services/members/invitation_reminder_email_service.rb
index e589cdc2fa3..688618ec4b4 100644
--- a/app/services/members/invitation_reminder_email_service.rb
+++ b/app/services/members/invitation_reminder_email_service.rb
@@ -14,8 +14,6 @@ module Members
end
def execute
- return unless experiment_enabled?
-
reminder_index = days_on_which_to_send_reminders.index(days_after_invitation_sent)
return unless reminder_index
@@ -24,10 +22,6 @@ module Members
private
- def experiment_enabled?
- Gitlab::Experimentation.enabled_for_attribute?(:invitation_reminders, invitation.invite_email)
- end
-
def days_after_invitation_sent
(Date.today - invitation.created_at.to_date).to_i
end
diff --git a/app/services/merge_requests/after_create_service.rb b/app/services/merge_requests/after_create_service.rb
index f0c85ae03c9..fbb9d5fa9dc 100644
--- a/app/services/merge_requests/after_create_service.rb
+++ b/app/services/merge_requests/after_create_service.rb
@@ -11,6 +11,8 @@ module MergeRequests
merge_request.diffs(include_stats: false).write_cache
merge_request.create_cross_references!(current_user)
+
+ NamespaceOnboardingAction.create_action(merge_request.target_project.namespace, :merge_request_created)
end
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index aa591312c6a..265b211066e 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -58,7 +58,7 @@ module MergeRequests
return unless project.jira_subscription_exists?
if Atlassian::JiraIssueKeyExtractor.has_keys?(merge_request.title, merge_request.description)
- JiraConnect::SyncMergeRequestWorker.perform_async(merge_request.id)
+ JiraConnect::SyncMergeRequestWorker.perform_async(merge_request.id, Atlassian::JiraConnect::Client.generate_update_sequence_id)
end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 8c069ea5bb0..bff7a43dd7b 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -11,7 +11,7 @@ module MergeRequests
params.delete(:target_project_id)
params.delete(:source_branch)
- if merge_request.closed_without_fork?
+ if merge_request.closed_or_merged_without_fork?
params.delete(:target_branch)
params.delete(:force_remove_source_branch)
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index b2826b5c905..9fffb6c372b 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -67,7 +67,7 @@ module Notes
track_event(note, current_user)
if Feature.enabled?(:notes_create_service_tracking, project)
- Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
+ Gitlab::Tracking.event('Notes::CreateService', 'execute', **tracking_data_for(note))
end
if note.for_merge_request? && note.diff_note? && note.start_of_discussion?
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 85113d3ca22..4ff462191fe 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -353,7 +353,7 @@ class NotificationService
issue = note.noteable
support_bot = User.support_bot
- return unless issue.service_desk_reply_to.present?
+ return unless issue.external_author.present?
return unless issue.project.service_desk_enabled?
return if note.author == support_bot
return unless issue.subscribed?(support_bot, issue.project)
@@ -380,6 +380,10 @@ class NotificationService
end
end
+ def user_admin_rejection(name, email)
+ mailer.user_admin_rejection_email(name, email).deliver_later
+ end
+
# Members
def new_access_request(member)
return true unless member.notifiable?(:subscription)
@@ -500,6 +504,16 @@ class NotificationService
end
end
+ def issue_cloned(issue, new_issue, current_user)
+ recipients = NotificationRecipients::BuildService.build_recipients(issue, current_user, action: 'cloned')
+
+ recipients.map do |recipient|
+ email = mailer.issue_cloned_email(recipient.user, issue, new_issue, current_user, recipient.reason)
+ email.deliver_later
+ email
+ end
+ end
+
def project_exported(project, current_user)
return true unless notifiable?(current_user, :mention, project: project)
diff --git a/app/services/onboarding_progress_service.rb b/app/services/onboarding_progress_service.rb
new file mode 100644
index 00000000000..ebe7caabdef
--- /dev/null
+++ b/app/services/onboarding_progress_service.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class OnboardingProgressService
+ def initialize(namespace)
+ @namespace = namespace.root_ancestor
+ end
+
+ def execute(action:)
+ NamespaceOnboardingAction.create_action(@namespace, action)
+ end
+end
diff --git a/app/services/packages/composer/create_package_service.rb b/app/services/packages/composer/create_package_service.rb
index 2d2f1568187..0f5429f667e 100644
--- a/app/services/packages/composer/create_package_service.rb
+++ b/app/services/packages/composer/create_package_service.rb
@@ -16,6 +16,8 @@ module Packages
composer_json: composer_json
})
end
+
+ created_package
end
private
diff --git a/app/services/packages/conan/create_package_file_service.rb b/app/services/packages/conan/create_package_file_service.rb
index 2db5c4e507b..1bde9606492 100644
--- a/app/services/packages/conan/create_package_file_service.rb
+++ b/app/services/packages/conan/create_package_file_service.rb
@@ -12,7 +12,7 @@ module Packages
end
def execute
- package.package_files.create!(
+ package_file = package.package_files.build(
file: file,
size: params['file.size'],
file_name: params[:file_name],
@@ -25,6 +25,13 @@ module Packages
conan_file_type: params[:conan_file_type]
}
)
+
+ if params[:build].present?
+ package_file.package_file_build_infos << package_file.package_file_build_infos.build(pipeline: params[:build].pipeline)
+ end
+
+ package_file.save!
+ package_file
end
end
end
diff --git a/app/services/packages/create_event_service.rb b/app/services/packages/create_event_service.rb
index c4492389da9..f0328ceb08a 100644
--- a/app/services/packages/create_event_service.rb
+++ b/app/services/packages/create_event_service.rb
@@ -4,7 +4,11 @@ module Packages
class CreateEventService < BaseService
def execute
if Feature.enabled?(:collect_package_events_redis) && redis_event_name
- ::Gitlab::UsageDataCounters::HLLRedisCounter.track_event(current_user.id, redis_event_name)
+ if guest?
+ ::Gitlab::UsageDataCounters::GuestPackageEventCounter.count(redis_event_name)
+ else
+ ::Gitlab::UsageDataCounters::HLLRedisCounter.track_event(current_user.id, redis_event_name)
+ end
end
if Feature.enabled?(:collect_package_events) && Gitlab::Database.read_write?
@@ -45,5 +49,9 @@ module Packages
:guest
end
end
+
+ def guest?
+ originator_type == :guest
+ end
end
end
diff --git a/app/services/packages/create_package_service.rb b/app/services/packages/create_package_service.rb
index e3b0ad218e2..fcf252cf971 100644
--- a/app/services/packages/create_package_service.rb
+++ b/app/services/packages/create_package_service.rb
@@ -8,9 +8,9 @@ module Packages
project
.packages
.with_package_type(package_type)
- .safe_find_or_create_by!(name: name, version: version) do |pkg|
- pkg.creator = package_creator
- yield pkg if block_given?
+ .safe_find_or_create_by!(name: name, version: version) do |package|
+ package.creator = package_creator
+ add_build_info(package)
end
end
@@ -18,7 +18,9 @@ module Packages
project
.packages
.with_package_type(package_type)
- .create!(package_attrs(attrs))
+ .create!(package_attrs(attrs)) do |package|
+ add_build_info(package)
+ end
end
private
@@ -34,5 +36,11 @@ module Packages
def package_creator
current_user if current_user.is_a?(User)
end
+
+ def add_build_info(package)
+ if params[:build].present?
+ package.build_infos.new(pipeline: params[:build].pipeline)
+ end
+ end
end
end
diff --git a/app/services/packages/generic/create_package_file_service.rb b/app/services/packages/generic/create_package_file_service.rb
index f25e8b0ae56..b14b1c193ec 100644
--- a/app/services/packages/generic/create_package_file_service.rb
+++ b/app/services/packages/generic/create_package_file_service.rb
@@ -18,9 +18,12 @@ module Packages
build: params[:build]
}
- ::Packages::Generic::FindOrCreatePackageService
+ package = ::Packages::Generic::FindOrCreatePackageService
.new(project, current_user, package_params)
.execute
+
+ package.build_infos.safe_find_or_create_by!(pipeline: params[:build].pipeline) if params[:build].present?
+ package
end
def create_package_file(package)
diff --git a/app/services/packages/generic/find_or_create_package_service.rb b/app/services/packages/generic/find_or_create_package_service.rb
index 97f774a836b..0a6099e4d35 100644
--- a/app/services/packages/generic/find_or_create_package_service.rb
+++ b/app/services/packages/generic/find_or_create_package_service.rb
@@ -4,11 +4,7 @@ module Packages
module Generic
class FindOrCreatePackageService < ::Packages::CreatePackageService
def execute
- find_or_create_package!(::Packages::Package.package_types['generic']) do |package|
- if params[:build].present?
- package.build_infos.new(pipeline: params[:build].pipeline)
- end
- end
+ find_or_create_package!(::Packages::Package.package_types['generic'])
end
end
end
diff --git a/app/services/packages/maven/find_or_create_package_service.rb b/app/services/packages/maven/find_or_create_package_service.rb
index a2a61ff8d93..f598b5e7cd4 100644
--- a/app/services/packages/maven/find_or_create_package_service.rb
+++ b/app/services/packages/maven/find_or_create_package_service.rb
@@ -46,7 +46,7 @@ module Packages
.execute
end
- package.build_infos.create!(pipeline: params[:build].pipeline) if params[:build].present?
+ package.build_infos.safe_find_or_create_by!(pipeline: params[:build].pipeline) if params[:build].present?
package
end
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index c4b75348bba..22396eb7687 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -17,10 +17,6 @@ module Packages
def create_npm_package!
package = create_package!(:npm, name: name, version: version)
- if build.present?
- package.build_infos.create!(pipeline: build.pipeline)
- end
-
::Packages::CreatePackageFileService.new(package, file_params).execute
::Packages::CreateDependencyService.new(package, package_dependencies).execute
::Packages::Npm::CreateTagService.new(package, dist_tag).execute
@@ -50,10 +46,6 @@ module Packages
params[:versions][version]
end
- def build
- params[:build]
- end
-
def dist_tag
params['dist-tags'].each_key.first
end
diff --git a/app/services/packages/pypi/create_package_service.rb b/app/services/packages/pypi/create_package_service.rb
index c49efca0fc5..cb8d9559dc9 100644
--- a/app/services/packages/pypi/create_package_service.rb
+++ b/app/services/packages/pypi/create_package_service.rb
@@ -19,6 +19,8 @@ module Packages
Packages::Pypi::Metadatum.upsert(meta.attributes)
::Packages::CreatePackageFileService.new(created_package, file_params).execute
+
+ created_package
end
end
@@ -32,6 +34,7 @@ module Packages
def file_params
{
+ build: params[:build],
file: params[:content],
file_name: params[:content].original_filename,
file_md5: params[:md5_digest],
diff --git a/app/services/pages/legacy_storage_lease.rb b/app/services/pages/legacy_storage_lease.rb
new file mode 100644
index 00000000000..3f42fc8c63b
--- /dev/null
+++ b/app/services/pages/legacy_storage_lease.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Pages
+ module LegacyStorageLease
+ extend ActiveSupport::Concern
+
+ include ::ExclusiveLeaseGuard
+
+ LEASE_TIMEOUT = 1.hour
+
+ # override method from exclusive lease guard to guard it by feature flag
+ # TODO: just remove this method after testing this in production
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/282464
+ def try_obtain_lease
+ return yield unless Feature.enabled?(:pages_use_legacy_storage_lease, project, default_enabled: true)
+
+ super
+ end
+
+ def lease_key
+ "pages_legacy_storage:#{project.id}"
+ end
+
+ def lease_timeout
+ LEASE_TIMEOUT
+ end
+ end
+end
diff --git a/app/services/pages/zip_directory_service.rb b/app/services/pages/zip_directory_service.rb
new file mode 100644
index 00000000000..a27ad5fda46
--- /dev/null
+++ b/app/services/pages/zip_directory_service.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+module Pages
+ class ZipDirectoryService
+ InvalidArchiveError = Class.new(RuntimeError)
+ InvalidEntryError = Class.new(RuntimeError)
+
+ PUBLIC_DIR = 'public'
+
+ def initialize(input_dir)
+ @input_dir = File.realpath(input_dir)
+ @output_file = File.join(@input_dir, "@migrated.zip") # '@' to avoid any name collision with groups or projects
+ end
+
+ def execute
+ FileUtils.rm_f(@output_file)
+
+ count = 0
+ ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
+ write_entry(zipfile, PUBLIC_DIR)
+ count = zipfile.entries.count
+ end
+
+ [@output_file, count]
+ end
+
+ private
+
+ def write_entry(zipfile, zipfile_path)
+ disk_file_path = File.join(@input_dir, zipfile_path)
+
+ unless valid_path?(disk_file_path)
+ # archive without public directory is completelly unusable
+ raise InvalidArchiveError if zipfile_path == PUBLIC_DIR
+
+ # archive with invalid entry will just have this entry missing
+ raise InvalidEntryError
+ end
+
+ case File.lstat(disk_file_path).ftype
+ when 'directory'
+ recursively_zip_directory(zipfile, disk_file_path, zipfile_path)
+ when 'file', 'link'
+ zipfile.add(zipfile_path, disk_file_path)
+ else
+ raise InvalidEntryError
+ end
+ rescue InvalidEntryError => e
+ Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir, disk_file_path: disk_file_path)
+ end
+
+ def recursively_zip_directory(zipfile, disk_file_path, zipfile_path)
+ zipfile.mkdir(zipfile_path)
+
+ entries = Dir.entries(disk_file_path) - %w[. ..]
+ entries = entries.map { |entry| File.join(zipfile_path, entry) }
+
+ write_entries(zipfile, entries)
+ end
+
+ def write_entries(zipfile, entries)
+ entries.each do |zipfile_path|
+ write_entry(zipfile, zipfile_path)
+ end
+ end
+
+ # that should never happen, but we want to be safer
+ # in theory without this we would allow to use symlinks
+ # to pack any directory on disk
+ # it isn't possible because SafeZip doesn't extract such archives
+ def valid_path?(disk_file_path)
+ realpath = File.realpath(disk_file_path)
+
+ realpath == File.join(@input_dir, PUBLIC_DIR) ||
+ realpath.start_with?(File.join(@input_dir, PUBLIC_DIR + "/"))
+ # happens if target of symlink isn't there
+ rescue => e
+ Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir, disk_file_path: disk_file_path)
+
+ false
+ end
+ end
+end
diff --git a/app/services/post_receive_service.rb b/app/services/post_receive_service.rb
index 79b613f6a88..bd9588844ad 100644
--- a/app/services/post_receive_service.rb
+++ b/app/services/post_receive_service.rb
@@ -40,6 +40,8 @@ class PostReceiveService
response.add_basic_message(redirect_message)
response.add_basic_message(project_created_message)
+
+ record_onboarding_progress
end
response
@@ -90,6 +92,10 @@ class PostReceiveService
banner&.message
end
+
+ def record_onboarding_progress
+ NamespaceOnboardingAction.create_action(project.namespace, :git_write)
+ end
end
PostReceiveService.prepend_if_ee('EE::PostReceiveService')
diff --git a/app/services/projects/alerting/notify_service.rb b/app/services/projects/alerting/notify_service.rb
index ab8f53a3757..014fb0e3ed3 100644
--- a/app/services/projects/alerting/notify_service.rb
+++ b/app/services/projects/alerting/notify_service.rb
@@ -2,10 +2,16 @@
module Projects
module Alerting
- class NotifyService < BaseService
+ class NotifyService
+ include BaseServiceUtility
include Gitlab::Utils::StrongMemoize
include ::IncidentManagement::Settings
+ def initialize(project, payload)
+ @project = project
+ @payload = payload
+ end
+
def execute(token, integration = nil)
@integration = integration
@@ -24,7 +30,7 @@ module Projects
private
- attr_reader :integration
+ attr_reader :project, :payload, :integration
def process_alert
if alert.persisted?
@@ -101,7 +107,7 @@ module Projects
def incoming_payload
strong_memoize(:incoming_payload) do
- Gitlab::AlertManagement::Payload.parse(project, params.to_h)
+ Gitlab::AlertManagement::Payload.parse(project, payload.to_h)
end
end
@@ -110,7 +116,7 @@ module Projects
end
def valid_payload_size?
- Gitlab::Utils::DeepSize.new(params).valid?
+ Gitlab::Utils::DeepSize.new(payload).valid?
end
def active_integration?
diff --git a/app/services/projects/container_repository/delete_tags_service.rb b/app/services/projects/container_repository/delete_tags_service.rb
index 505ddaf50e3..410cf6c624e 100644
--- a/app/services/projects/container_repository/delete_tags_service.rb
+++ b/app/services/projects/container_repository/delete_tags_service.rb
@@ -36,6 +36,7 @@ module Projects
def log_response(response)
log_data = LOG_DATA_BASE.merge(
container_repository_id: @container_repository.id,
+ project_id: @container_repository.project_id,
message: 'deleted tags',
deleted_tags_count: response[:deleted]&.size
).compact
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 1cd81fe37c7..228115d72b8 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -14,7 +14,7 @@ module Projects
groups +
project_members
- participants.uniq
+ render_participants_as_hash(participants.uniq)
end
def project_members
diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb
index 8ad4f59373d..93165a58470 100644
--- a/app/services/projects/prometheus/alerts/notify_service.rb
+++ b/app/services/projects/prometheus/alerts/notify_service.rb
@@ -3,7 +3,7 @@
module Projects
module Prometheus
module Alerts
- class NotifyService < BaseService
+ class NotifyService
include Gitlab::Utils::StrongMemoize
include ::IncidentManagement::Settings
@@ -17,28 +17,35 @@ module Projects
SUPPORTED_VERSION = '4'
- def execute(token, _integration = nil)
+ def initialize(project, payload)
+ @project = project
+ @payload = payload
+ end
+
+ def execute(token, integration = nil)
return bad_request unless valid_payload_size?
- return unprocessable_entity unless self.class.processable?(params)
- return unauthorized unless valid_alert_manager_token?(token)
+ return unprocessable_entity unless self.class.processable?(payload)
+ return unauthorized unless valid_alert_manager_token?(token, integration)
process_prometheus_alerts
ServiceResponse.success
end
- def self.processable?(params)
+ def self.processable?(payload)
# Workaround for https://gitlab.com/gitlab-org/gitlab/-/issues/220496
- return false unless params
+ return false unless payload
- REQUIRED_PAYLOAD_KEYS.subset?(params.keys.to_set) &&
- params['version'] == SUPPORTED_VERSION
+ REQUIRED_PAYLOAD_KEYS.subset?(payload.keys.to_set) &&
+ payload['version'] == SUPPORTED_VERSION
end
private
+ attr_reader :project, :payload
+
def valid_payload_size?
- Gitlab::Utils::DeepSize.new(params).valid?
+ Gitlab::Utils::DeepSize.new(payload).valid?
end
def firings
@@ -50,12 +57,12 @@ module Projects
end
def alerts
- params['alerts']
+ payload['alerts']
end
- def valid_alert_manager_token?(token)
+ def valid_alert_manager_token?(token, integration)
valid_for_manual?(token) ||
- valid_for_alerts_endpoint?(token) ||
+ valid_for_alerts_endpoint?(token, integration) ||
valid_for_managed?(token)
end
@@ -70,11 +77,10 @@ module Projects
end
end
- def valid_for_alerts_endpoint?(token)
- return false unless project.alerts_service_activated?
+ def valid_for_alerts_endpoint?(token, integration)
+ return false unless integration&.active?
- # Here we are enforcing the existence of the token
- compare_token(token, project.alerts_service.token)
+ compare_token(token, integration.token)
end
def valid_for_managed?(token)
@@ -122,7 +128,7 @@ module Projects
def process_prometheus_alerts
alerts.each do |alert|
AlertManagement::ProcessPrometheusAlertService
- .new(project, nil, alert.to_h)
+ .new(project, alert.to_h)
.execute
end
end
diff --git a/app/services/projects/schedule_bulk_repository_shard_moves_service.rb b/app/services/projects/schedule_bulk_repository_shard_moves_service.rb
new file mode 100644
index 00000000000..dd49910207f
--- /dev/null
+++ b/app/services/projects/schedule_bulk_repository_shard_moves_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Projects
+ # Tries to schedule a move for every project with repositories on the source shard
+ class ScheduleBulkRepositoryShardMovesService
+ include BaseServiceUtility
+
+ def execute(source_storage_name, destination_storage_name = nil)
+ shard = Shard.find_by_name!(source_storage_name)
+
+ ProjectRepository.for_shard(shard).each_batch(column: :project_id) do |relation|
+ Project.id_in(relation.select(:project_id)).each do |project|
+ project.with_lock do
+ next if project.repository_storage != source_storage_name
+
+ storage_move = project.repository_storage_moves.build(
+ source_storage_name: source_storage_name,
+ destination_storage_name: destination_storage_name
+ )
+
+ unless storage_move.schedule
+ log_info("Project #{project.full_path} (#{project.id}) was skipped: #{storage_move.errors.full_messages.to_sentence}")
+ end
+ end
+ end
+ end
+
+ success
+ end
+
+ def self.enqueue(source_storage_name, destination_storage_name = nil)
+ ::ProjectScheduleBulkRepositoryShardMovesWorker.perform_async(source_storage_name, destination_storage_name)
+ end
+ end
+end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 5178c76f0fc..1574c90d2ac 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -59,7 +59,7 @@ module Projects
raise TransferError.new(s_("TransferProject|Root namespace can't be updated if project has NPM packages"))
end
- attempt_transfer_transaction
+ proceed_to_transfer
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -67,7 +67,7 @@ module Projects
new_namespace.root_ancestor == project.namespace.root_ancestor
end
- def attempt_transfer_transaction
+ def proceed_to_transfer
Project.transaction do
project.expire_caches_before_rename(@old_path)
@@ -87,6 +87,8 @@ module Projects
# Move uploads
move_project_uploads(project)
+ update_integrations
+
project.old_path_with_namespace = @old_path
update_repository_configuration(@new_path)
@@ -214,6 +216,11 @@ module Projects
project.shared_runners_enabled = false
end
end
+
+ def update_integrations
+ project.services.inherit.delete_all
+ Service.create_from_active_default_integrations(project, :project_id)
+ end
end
end
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index b9c579a130f..53872c67f49 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -4,6 +4,9 @@ module Projects
class UpdatePagesService < BaseService
InvalidStateError = Class.new(StandardError)
FailedToExtractError = Class.new(StandardError)
+ ExclusiveLeaseTaken = Class.new(StandardError)
+
+ include ::Pages::LegacyStorageLease
BLOCK_SIZE = 32.kilobytes
PUBLIC_DIR = 'public'
@@ -109,6 +112,17 @@ module Projects
end
def deploy_page!(archive_public_path)
+ deployed = try_obtain_lease do
+ deploy_page_unsafe!(archive_public_path)
+ true
+ end
+
+ unless deployed
+ raise ExclusiveLeaseTaken, "Failed to deploy pages - other deployment is in progress"
+ end
+ end
+
+ def deploy_page_unsafe!(archive_public_path)
# Do atomic move of pages
# Move and removal may not be atomic, but they are significantly faster then extracting and removal
# 1. We move deployed public to previous public path (file removal is slow)
@@ -125,8 +139,6 @@ module Projects
end
def create_pages_deployment(artifacts_path, build)
- return unless Feature.enabled?(:zip_pages_deployments, project, default_enabled: true)
-
# we're using the full archive and pages daemon needs to read it
# so we want the total count from entries, not only "public/" directory
# because it better approximates work we need to do before we can serve the site
diff --git a/app/services/releases/base_service.rb b/app/services/releases/base_service.rb
index 38ef80ced56..d0e1577bd8d 100644
--- a/app/services/releases/base_service.rb
+++ b/app/services/releases/base_service.rb
@@ -11,8 +11,6 @@ module Releases
@project, @current_user, @params = project, user, params.dup
end
- delegate :repository, to: :project
-
def tag_name
params[:tag]
end
@@ -39,22 +37,18 @@ module Releases
end
end
- def existing_tag
- strong_memoize(:existing_tag) do
- repository.find_tag(tag_name)
- end
- end
-
- def tag_exist?
- existing_tag.present?
- end
-
def repository
strong_memoize(:repository) do
project.repository
end
end
+ def existing_tag
+ strong_memoize(:existing_tag) do
+ repository.find_tag(tag_name)
+ end
+ end
+
def milestones
return [] unless param_for_milestone_titles_provided?
@@ -78,7 +72,7 @@ module Releases
end
def param_for_milestone_titles_provided?
- params.key?(:milestones)
+ !!params[:milestones]
end
def execute_hooks(release, action = 'create')
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index deefe559d5d..11fdbaf3169 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -10,7 +10,7 @@ module Releases
# should be found before the creation of new tag
# because tag creation can spawn new pipeline
# which won't have any data for evidence yet
- evidence_pipeline = find_evidence_pipeline
+ evidence_pipeline = Releases::EvidencePipelineFinder.new(project, params).execute
tag = ensure_tag
@@ -78,26 +78,10 @@ module Releases
)
end
- def find_evidence_pipeline
- # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245
- return params[:evidence_pipeline] if params[:evidence_pipeline]
-
- sha = existing_tag&.dereferenced_target&.sha
- sha ||= repository.commit(ref)&.sha
-
- return unless sha
-
- project.ci_pipelines.for_sha(sha).last
- end
-
def create_evidence!(release, pipeline)
- return if release.historical_release?
+ return if release.historical_release? || release.upcoming_release?
- if release.upcoming_release?
- CreateEvidenceWorker.perform_at(release.released_at, release.id, pipeline&.id)
- else
- CreateEvidenceWorker.perform_async(release.id, pipeline&.id)
- end
+ ::Releases::CreateEvidenceWorker.perform_async(release.id, pipeline&.id)
end
end
end
diff --git a/app/services/resource_events/change_labels_service.rb b/app/services/resource_events/change_labels_service.rb
index dc23f727079..ddf3b05ac10 100644
--- a/app/services/resource_events/change_labels_service.rb
+++ b/app/services/resource_events/change_labels_service.rb
@@ -24,6 +24,8 @@ module ResourceEvents
Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, labels) # rubocop:disable Gitlab/BulkInsert
resource.expire_note_etag_cache
+
+ Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user) if resource.is_a?(Issue)
end
private
diff --git a/app/services/service_desk_settings/update_service.rb b/app/services/service_desk_settings/update_service.rb
index c837b75f439..32d1c5c1c87 100644
--- a/app/services/service_desk_settings/update_service.rb
+++ b/app/services/service_desk_settings/update_service.rb
@@ -5,7 +5,7 @@ module ServiceDeskSettings
def execute
settings = ServiceDeskSetting.safe_find_or_create_by!(project_id: project.id)
- unless ::Feature.enabled?(:service_desk_custom_address, project)
+ unless ::Feature.enabled?(:service_desk_custom_address, project, default_enabled: true)
params.delete(:project_key)
end
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 2fbeaf4405c..8ab1193b04f 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -43,8 +43,6 @@ class SubmitUsagePingService
private
def save_raw_usage_data(usage_data)
- return unless Feature.enabled?(:save_raw_usage_data)
-
RawUsageData.safe_find_or_create_by(recorded_at: usage_data[:recorded_at]) do |record|
record.payload = usage_data
end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index 0d369c23b57..881a139437a 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class SystemHooksService
+ BUILDER_DRIVEN_EVENT_DATA_AVAILABLE_FOR_CLASSES = [GroupMember].freeze
+
def execute_hooks_for(model, event)
data = build_event_data(model, event)
@@ -20,6 +22,9 @@ class SystemHooksService
private
def build_event_data(model, event)
+ # return entire event data from its builder class, if available.
+ return builder_driven_event_data(model, event) if builder_driven_event_data_available?(model)
+
data = {
event_name: build_event_name(model, event),
created_at: model.created_at&.xmlschema,
@@ -62,8 +67,6 @@ class SystemHooksService
old_full_path: model.full_path_before_last_save
)
end
- when GroupMember
- data.merge!(group_member_data(model))
end
data
@@ -75,10 +78,6 @@ class SystemHooksService
return "user_add_to_team" if event == :create
return "user_remove_from_team" if event == :destroy
return "user_update_for_team" if event == :update
- when GroupMember
- return 'user_add_to_group' if event == :create
- return 'user_remove_from_group' if event == :destroy
- return 'user_update_for_group' if event == :update
else
"#{model.class.name.downcase}_#{event}"
end
@@ -128,19 +127,6 @@ class SystemHooksService
}
end
- def group_member_data(model)
- {
- group_name: model.group.name,
- group_path: model.group.path,
- group_id: model.group.id,
- user_username: model.user.username,
- user_name: model.user.name,
- user_email: model.user.email,
- user_id: model.user.id,
- group_access: model.human_access
- }
- end
-
def user_data(model)
{
name: model.name,
@@ -149,6 +135,17 @@ class SystemHooksService
username: model.username
}
end
+
+ def builder_driven_event_data_available?(model)
+ model.class.in?(BUILDER_DRIVEN_EVENT_DATA_AVAILABLE_FOR_CLASSES)
+ end
+
+ def builder_driven_event_data(model, event)
+ case model
+ when GroupMember
+ Gitlab::HookData::GroupMemberBuilder.new(model).build(event)
+ end
+ end
end
SystemHooksService.prepend_if_ee('EE::SystemHooksService')
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index eacc88f98a3..58f72e9badc 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -226,6 +226,10 @@ module SystemNoteService
::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).noteable_moved(noteable_ref, direction)
end
+ def noteable_cloned(noteable, project, noteable_ref, author, direction:)
+ ::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).noteable_cloned(noteable_ref, direction)
+ end
+
def mark_duplicate_issue(noteable, project, author, canonical_issue)
::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).mark_duplicate_issue(canonical_issue)
end
diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb
index 7a73af0a81a..b344b240a07 100644
--- a/app/services/system_notes/issuables_service.rb
+++ b/app/services/system_notes/issuables_service.rb
@@ -242,6 +242,29 @@ module SystemNotes
create_note(NoteSummary.new(noteable, project, author, body, action: 'moved'))
end
+ # Called when noteable has been cloned
+ #
+ # noteable_ref - Referenced noteable
+ # direction - symbol, :to or :from
+ #
+ # Example Note text:
+ #
+ # "cloned to some_namespace/project_new#11"
+ #
+ # Returns the created Note object
+ def noteable_cloned(noteable_ref, direction)
+ unless [:to, :from].include?(direction)
+ raise ArgumentError, "Invalid direction `#{direction}`"
+ end
+
+ cross_reference = noteable_ref.to_reference(project)
+ body = "cloned #{direction} #{cross_reference}"
+
+ issue_activity_counter.track_issue_cloned_action(author: author) if noteable.is_a?(Issue) && direction == :to
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'cloned'))
+ end
+
# Called when the confidentiality changes
#
# Example Note text:
diff --git a/app/services/upload_service.rb b/app/services/upload_service.rb
index 403944557a2..ba6ead41836 100644
--- a/app/services/upload_service.rb
+++ b/app/services/upload_service.rb
@@ -6,16 +6,18 @@ class UploadService
end
def execute
- return unless @file && @file.size <= max_attachment_size
+ return unless file && file.size <= max_attachment_size
- uploader = @uploader_class.new(@model, nil, @uploader_context)
- uploader.store!(@file)
+ uploader = uploader_class.new(model, nil, **uploader_context)
+ uploader.store!(file)
uploader
end
private
+ attr_reader :model, :file, :uploader_class, :uploader_context
+
def max_attachment_size
Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i
end
diff --git a/app/services/users/approve_service.rb b/app/services/users/approve_service.rb
index 27668e9430e..debd1e8cd17 100644
--- a/app/services/users/approve_service.rb
+++ b/app/services/users/approve_service.rb
@@ -7,8 +7,9 @@ module Users
end
def execute(user)
- return error(_('You are not allowed to approve a user')) unless allowed?
- return error(_('The user you are trying to approve is not pending an approval')) unless approval_required?(user)
+ return error(_('You are not allowed to approve a user'), :forbidden) unless allowed?
+ return error(_('The user you are trying to approve is not pending an approval'), :conflict) if user.active?
+ return error(_('The user you are trying to approve is not pending an approval'), :conflict) unless approval_required?(user)
if user.activate
# Resends confirmation email if the user isn't confirmed yet.
@@ -18,9 +19,9 @@ module Users
DeviseMailer.user_admin_approval(user).deliver_later
after_approve_hook(user)
- success
+ success(message: 'Success', http_status: :created)
else
- error(user.errors.full_messages.uniq.join('. '))
+ error(user.errors.full_messages.uniq.join('. '), :unprocessable_entity)
end
end
diff --git a/app/services/users/reject_service.rb b/app/services/users/reject_service.rb
new file mode 100644
index 00000000000..dd72547c688
--- /dev/null
+++ b/app/services/users/reject_service.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Users
+ class RejectService < BaseService
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user)
+ return error(_('You are not allowed to reject a user')) unless allowed?
+ return error(_('This user does not have a pending request')) unless user.blocked_pending_approval?
+
+ user.delete_async(deleted_by: current_user, params: { hard_delete: true })
+
+ NotificationService.new.user_admin_rejection(user.name, user.email)
+
+ success
+ end
+
+ private
+
+ attr_reader :current_user
+
+ def allowed?
+ can?(current_user, :reject_user)
+ end
+ end
+end
diff --git a/app/services/users/set_status_service.rb b/app/services/users/set_status_service.rb
index 356c8782af1..a907937070f 100644
--- a/app/services/users/set_status_service.rb
+++ b/app/services/users/set_status_service.rb
@@ -14,10 +14,10 @@ module Users
def execute
return false unless can?(current_user, :update_user_status, target_user)
- if params[:emoji].present? || params[:message].present? || params[:availability].present?
- set_status
- else
+ if status_cleared?
remove_status
+ else
+ set_status
end
end
@@ -25,8 +25,7 @@ module Users
def set_status
params[:emoji] = UserStatus::DEFAULT_EMOJI if params[:emoji].blank?
- params.delete(:availability) if params[:availability].blank?
- return false if params[:availability].present? && UserStatus.availabilities.keys.exclude?(params[:availability])
+ params[:availability] = UserStatus.availabilities[:not_set] unless new_user_availability
user_status.update(params)
end
@@ -38,5 +37,15 @@ module Users
def user_status
target_user.status || target_user.build_status
end
+
+ def status_cleared?
+ params[:emoji].blank? &&
+ params[:message].blank? &&
+ (new_user_availability.blank? || new_user_availability == UserStatus.availabilities[:not_set])
+ end
+
+ def new_user_availability
+ UserStatus.availabilities[params[:availability]]
+ end
end
end
diff --git a/app/services/users/validate_otp_service.rb b/app/services/users/validate_otp_service.rb
index a9ce7959aea..c8a9f217d22 100644
--- a/app/services/users/validate_otp_service.rb
+++ b/app/services/users/validate_otp_service.rb
@@ -2,10 +2,14 @@
module Users
class ValidateOtpService < BaseService
+ include ::Gitlab::Auth::Otp::Fortinet
+
def initialize(current_user)
@current_user = current_user
- @strategy = if Feature.enabled?(:forti_authenticator, current_user)
+ @strategy = if forti_authenticator_enabled?(current_user)
::Gitlab::Auth::Otp::Strategies::FortiAuthenticator.new(current_user)
+ elsif forti_token_cloud_enabled?(current_user)
+ ::Gitlab::Auth::Otp::Strategies::FortiTokenCloud.new(current_user)
else
::Gitlab::Auth::Otp::Strategies::Devise.new(current_user)
end