summaryrefslogtreecommitdiff
path: root/app/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/admin/set_feature_flag_service.rb85
-rw-r--r--app/services/alert_management/create_alert_issue_service.rb2
-rw-r--r--app/services/authorized_project_update/project_recalculate_service.rb7
-rw-r--r--app/services/boards/issues/list_service.rb2
-rw-r--r--app/services/boards/lists/generate_service.rb39
-rw-r--r--app/services/boards/lists/list_service.rb20
-rw-r--r--app/services/bulk_imports/create_pipeline_trackers_service.rb6
-rw-r--r--app/services/bulk_imports/create_service.rb2
-rw-r--r--app/services/bulk_imports/repository_bundle_export_service.rb8
-rw-r--r--app/services/bulk_imports/uploads_export_service.rb5
-rw-r--r--app/services/ci/after_requeue_job_service.rb2
-rw-r--r--app/services/ci/create_pipeline_service.rb4
-rw-r--r--app/services/ci/generate_kubeconfig_service.rb12
-rw-r--r--app/services/ci/job_artifacts/delete_service.rb18
-rw-r--r--app/services/ci/parse_dotenv_artifact_service.rb2
-rw-r--r--app/services/ci/pipeline_artifacts/coverage_report_service.rb11
-rw-r--r--app/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service.rb11
-rw-r--r--app/services/ci/pipeline_artifacts/destroy_all_expired_service.rb32
-rw-r--r--app/services/ci/process_build_service.rb2
-rw-r--r--app/services/ci/runners/register_runner_service.rb2
-rw-r--r--app/services/ci/unlock_artifacts_service.rb6
-rw-r--r--app/services/clusters/applications/destroy_service.rb23
-rw-r--r--app/services/clusters/applications/uninstall_service.rb29
-rw-r--r--app/services/concerns/users/participable_service.rb2
-rw-r--r--app/services/concerns/work_items/widgetable_service.rb12
-rw-r--r--app/services/google_cloud/enable_cloudsql_service.rb6
-rw-r--r--app/services/groups/import_export/import_service.rb2
-rw-r--r--app/services/import/github/cancel_project_import_service.rb36
-rw-r--r--app/services/import/github_service.rb28
-rw-r--r--app/services/incident_management/incidents/create_service.rb12
-rw-r--r--app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb2
-rw-r--r--app/services/incident_management/timeline_events/create_service.rb1
-rw-r--r--app/services/incident_management/timeline_events/destroy_service.rb2
-rw-r--r--app/services/incident_management/timeline_events/update_service.rb2
-rw-r--r--app/services/issuable/import_csv/base_service.rb2
-rw-r--r--app/services/issuable/process_assignees.rb10
-rw-r--r--app/services/issuable_base_service.rb47
-rw-r--r--app/services/issues/base_service.rb13
-rw-r--r--app/services/issues/clone_service.rb11
-rw-r--r--app/services/issues/create_service.rb23
-rw-r--r--app/services/issues/import_csv_service.rb4
-rw-r--r--app/services/issues/move_service.rb11
-rw-r--r--app/services/issues/update_service.rb10
-rw-r--r--app/services/jira_connect/create_asymmetric_jwt_service.rb51
-rw-r--r--app/services/labels/promote_service.rb4
-rw-r--r--app/services/members/create_service.rb4
-rw-r--r--app/services/members/destroy_service.rb25
-rw-r--r--app/services/merge_requests/approval_service.rb6
-rw-r--r--app/services/merge_requests/base_service.rb5
-rw-r--r--app/services/merge_requests/close_service.rb5
-rw-r--r--app/services/merge_requests/mark_reviewer_reviewed_service.rb2
-rw-r--r--app/services/merge_requests/merge_base_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb24
-rw-r--r--app/services/merge_requests/mergeability/logger.rb16
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb14
-rw-r--r--app/services/merge_requests/request_review_service.rb1
-rw-r--r--app/services/merge_requests/update_assignees_service.rb12
-rw-r--r--app/services/merge_requests/update_service.rb17
-rw-r--r--app/services/ml/experiment_tracking/candidate_repository.rb85
-rw-r--r--app/services/ml/experiment_tracking/experiment_repository.rb30
-rw-r--r--app/services/namespaces/package_settings/update_service.rb8
-rw-r--r--app/services/notes/create_service.rb38
-rw-r--r--app/services/notification_service.rb15
-rw-r--r--app/services/onboarding/progress_service.rb2
-rw-r--r--app/services/packages/debian/create_package_file_service.rb14
-rw-r--r--app/services/packages/mark_packages_for_destruction_service.rb79
-rw-r--r--app/services/packages/rpm/parse_package_service.rb84
-rw-r--r--app/services/packages/rpm/repository_metadata/base_builder.rb30
-rw-r--r--app/services/packages/rpm/repository_metadata/build_primary_xml.rb73
-rw-r--r--app/services/packages/rpm/repository_metadata/build_repomd_xml.rb5
-rw-r--r--app/services/pages_domains/create_acme_order_service.rb10
-rw-r--r--app/services/pages_domains/create_service.rb34
-rw-r--r--app/services/pages_domains/delete_service.rb32
-rw-r--r--app/services/pages_domains/update_service.rb34
-rw-r--r--app/services/personal_access_tokens/revoke_service.rb3
-rw-r--r--app/services/projects/autocomplete_service.rb20
-rw-r--r--app/services/projects/blame_service.rb8
-rw-r--r--app/services/projects/container_repository/cleanup_tags_base_service.rb17
-rw-r--r--app/services/projects/container_repository/cleanup_tags_service.rb110
-rw-r--r--app/services/projects/container_repository/gitlab/cleanup_tags_service.rb3
-rw-r--r--app/services/projects/container_repository/third_party/cleanup_tags_service.rb106
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/import_service.rb6
-rw-r--r--app/services/projects/update_service.rb40
-rw-r--r--app/services/releases/create_service.rb8
-rw-r--r--app/services/releases/destroy_service.rb4
-rw-r--r--app/services/releases/update_service.rb10
-rw-r--r--app/services/resource_access_tokens/create_service.rb13
-rw-r--r--app/services/search_service.rb10
-rw-r--r--app/services/users/build_service.rb2
-rw-r--r--app/services/users/dismiss_namespace_callout_service.rb11
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb4
-rw-r--r--app/services/web_hook_service.rb3
-rw-r--r--app/services/web_hooks/log_execution_service.rb2
-rw-r--r--app/services/work_items/create_service.rb12
-rw-r--r--app/services/work_items/update_service.rb21
-rw-r--r--app/services/work_items/widgets/base_service.rb5
-rw-r--r--app/services/work_items/widgets/labels_service/update_service.rb15
98 files changed, 1322 insertions, 442 deletions
diff --git a/app/services/admin/set_feature_flag_service.rb b/app/services/admin/set_feature_flag_service.rb
new file mode 100644
index 00000000000..d72a18a6a58
--- /dev/null
+++ b/app/services/admin/set_feature_flag_service.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Admin
+ class SetFeatureFlagService
+ def initialize(feature_flag_name:, params:)
+ @name = feature_flag_name
+ @params = params
+ end
+
+ def execute
+ unless params[:force]
+ error = validate_feature_flag_name
+ return ServiceResponse.error(message: error, reason: :invalid_feature_flag) if error
+ end
+
+ flag_target = Feature::Target.new(params)
+ value = gate_value(params)
+
+ case value
+ when true
+ enable!(flag_target)
+ when false
+ disable!(flag_target)
+ else
+ enable_partially!(value, params)
+ end
+
+ feature_flag = Feature.get(name) # rubocop:disable Gitlab/AvoidFeatureGet
+
+ ServiceResponse.success(payload: { feature_flag: feature_flag })
+ rescue Feature::Target::UnknowTargetError => e
+ ServiceResponse.error(message: e.message, reason: :actor_not_found)
+ end
+
+ private
+
+ attr_reader :name, :params
+
+ def enable!(flag_target)
+ if flag_target.gate_specified?
+ flag_target.targets.each { |target| Feature.enable(name, target) }
+ else
+ Feature.enable(name)
+ end
+ end
+
+ def disable!(flag_target)
+ if flag_target.gate_specified?
+ flag_target.targets.each { |target| Feature.disable(name, target) }
+ else
+ Feature.disable(name)
+ end
+ end
+
+ def enable_partially!(value, params)
+ if params[:key] == 'percentage_of_actors'
+ Feature.enable_percentage_of_actors(name, value)
+ else
+ Feature.enable_percentage_of_time(name, value)
+ end
+ end
+
+ def validate_feature_flag_name
+ # overridden in EE
+ end
+
+ def gate_value(params)
+ case params[:value]
+ when 'true'
+ true
+ when '0', 'false'
+ false
+ else
+ # https://github.com/jnunemaker/flipper/blob/master/lib/flipper/typecast.rb#L47
+ if params[:value].to_s.include?('.')
+ params[:value].to_f
+ else
+ params[:value].to_i
+ end
+ end
+ end
+ end
+end
+
+Admin::SetFeatureFlagService.prepend_mod
diff --git a/app/services/alert_management/create_alert_issue_service.rb b/app/services/alert_management/create_alert_issue_service.rb
index 34c2003bd01..28e312a6fa3 100644
--- a/app/services/alert_management/create_alert_issue_service.rb
+++ b/app/services/alert_management/create_alert_issue_service.rb
@@ -21,7 +21,7 @@ module AlertManagement
result = create_incident
return result unless result.success?
- issue = result.payload[:issue]
+ issue = result[:issue]
perform_after_create_tasks(issue)
result
diff --git a/app/services/authorized_project_update/project_recalculate_service.rb b/app/services/authorized_project_update/project_recalculate_service.rb
index e0b8158417c..8d60fffd959 100644
--- a/app/services/authorized_project_update/project_recalculate_service.rb
+++ b/app/services/authorized_project_update/project_recalculate_service.rb
@@ -64,7 +64,12 @@ module AuthorizedProjectUpdate
end
def refresh_authorizations
- project.remove_project_authorizations(user_ids_to_remove) if user_ids_to_remove.any?
+ if user_ids_to_remove.any?
+ ProjectAuthorization.delete_all_in_batches_for_project(
+ project: project,
+ user_ids: user_ids_to_remove)
+ end
+
ProjectAuthorization.insert_all_in_batches(authorizations_to_create) if authorizations_to_create.any?
end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 465025ef2e9..fcaa74555ca 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -50,7 +50,7 @@ module Boards
end
def set_issue_types
- params[:issue_types] ||= Issue::TYPES_FOR_LIST
+ params[:issue_types] ||= Issue::TYPES_FOR_BOARD_LIST
end
def item_model
diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb
deleted file mode 100644
index d74320e92a3..00000000000
--- a/app/services/boards/lists/generate_service.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-module Boards
- module Lists
- class GenerateService < Boards::BaseService
- def execute(board)
- return false unless board.lists.movable.empty?
-
- List.transaction do
- label_params.each do |params|
- response = create_list(board, params)
-
- raise ActiveRecord::Rollback unless response.success?
- end
- end
-
- true
- end
-
- private
-
- def create_list(board, params)
- label = find_or_create_label(params)
- Lists::CreateService.new(parent, current_user, label_id: label.id).execute(board)
- end
-
- def find_or_create_label(params)
- ::Labels::FindOrCreateService.new(current_user, parent, params).execute
- end
-
- def label_params
- [
- { name: 'To Do', color: '#F0AD4E' },
- { name: 'Doing', color: '#5CB85C' }
- ]
- end
- end
- end
-end
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
index e81ef467a4e..cf15db4314c 100644
--- a/app/services/boards/lists/list_service.rb
+++ b/app/services/boards/lists/list_service.rb
@@ -9,23 +9,27 @@ module Boards
end
lists = board.lists.preload_associated_models
+ lists = lists.with_types(available_list_types_for(board))
return lists.id_in(params[:list_id]) if params[:list_id].present?
- list_types = unavailable_list_types_for(board)
- lists.without_types(list_types)
+ lists
end
private
- def unavailable_list_types_for(board)
- hidden_lists_for(board)
+ def available_list_types_for(board)
+ licensed_list_types(board) + visible_lists(board)
end
- def hidden_lists_for(board)
- [].tap do |hidden|
- hidden << ::List.list_types[:backlog] if board.hide_backlog_list?
- hidden << ::List.list_types[:closed] if board.hide_closed_list?
+ def licensed_list_types(board)
+ [List.list_types[:label]]
+ end
+
+ def visible_lists(board)
+ [].tap do |visible|
+ visible << ::List.list_types[:backlog] unless board.hide_backlog_list?
+ visible << ::List.list_types[:closed] unless board.hide_closed_list?
end
end
end
diff --git a/app/services/bulk_imports/create_pipeline_trackers_service.rb b/app/services/bulk_imports/create_pipeline_trackers_service.rb
index af97aec09b5..f5b944e6df5 100644
--- a/app/services/bulk_imports/create_pipeline_trackers_service.rb
+++ b/app/services/bulk_imports/create_pipeline_trackers_service.rb
@@ -53,11 +53,13 @@ module BulkImports
def log_skipped_pipeline(pipeline, minimum_version, maximum_version)
logger.info(
message: 'Pipeline skipped as source instance version not compatible with pipeline',
- entity_id: entity.id,
+ bulk_import_entity_id: entity.id,
+ bulk_import_id: entity.bulk_import_id,
pipeline_name: pipeline[:pipeline],
minimum_source_version: minimum_version,
maximum_source_version: maximum_version,
- source_version: source_version.to_s
+ source_version: source_version.to_s,
+ importer: 'gitlab_migration'
)
end
diff --git a/app/services/bulk_imports/create_service.rb b/app/services/bulk_imports/create_service.rb
index 31e1a822e78..d3c6dcca588 100644
--- a/app/services/bulk_imports/create_service.rb
+++ b/app/services/bulk_imports/create_service.rb
@@ -38,6 +38,8 @@ module BulkImports
def execute
bulk_import = create_bulk_import
+ Gitlab::Tracking.event(self.class.name, 'create', label: 'bulk_import_group')
+
BulkImportWorker.perform_async(bulk_import.id)
ServiceResponse.success(payload: bulk_import)
diff --git a/app/services/bulk_imports/repository_bundle_export_service.rb b/app/services/bulk_imports/repository_bundle_export_service.rb
index 31a2ed6d1af..86159f5189d 100644
--- a/app/services/bulk_imports/repository_bundle_export_service.rb
+++ b/app/services/bulk_imports/repository_bundle_export_service.rb
@@ -9,13 +9,19 @@ module BulkImports
end
def execute
- repository.bundle_to_disk(bundle_filepath) if repository.exists?
+ return unless repository_exists?
+
+ repository.bundle_to_disk(bundle_filepath)
end
private
attr_reader :repository, :export_path, :export_filename
+ def repository_exists?
+ repository.exists? && !repository.empty?
+ end
+
def bundle_filepath
File.join(export_path, "#{export_filename}.bundle")
end
diff --git a/app/services/bulk_imports/uploads_export_service.rb b/app/services/bulk_imports/uploads_export_service.rb
index 7f5ee7b8624..315590bea31 100644
--- a/app/services/bulk_imports/uploads_export_service.rb
+++ b/app/services/bulk_imports/uploads_export_service.rb
@@ -22,8 +22,9 @@ module BulkImports
subdir_path = export_subdir_path(upload)
mkdir_p(subdir_path)
download_or_copy_upload(uploader, File.join(subdir_path, uploader.filename))
- rescue Errno::ENAMETOOLONG => e
- # Do not fail entire export process if downloaded file has filename that exceeds 255 characters.
+ rescue StandardError => e
+ # Do not fail entire project export if something goes wrong during file download
+ # (e.g. downloaded file has filename that exceeds 255 characters).
# Ignore raised exception, skip such upload, log the error and keep going with the export instead.
Gitlab::ErrorTracking.log_exception(e, portable_id: portable.id, portable_class: portable.class.name, upload_id: upload.id)
end
diff --git a/app/services/ci/after_requeue_job_service.rb b/app/services/ci/after_requeue_job_service.rb
index 634c547a623..9d54207d75d 100644
--- a/app/services/ci/after_requeue_job_service.rb
+++ b/app/services/ci/after_requeue_job_service.rb
@@ -26,7 +26,7 @@ module Ci
return legacy_dependent_jobs unless ::Feature.enabled?(:ci_requeue_with_dag_object_hierarchy, project)
ordered_by_dag(
- ::Ci::Processable
+ @processable.pipeline.processables
.from_union(needs_dependent_jobs, stage_dependent_jobs)
.skipped
.ordered_by_stage
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index af175b8da1c..0b49beffcb5 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -26,6 +26,7 @@ module Ci
Gitlab::Ci::Pipeline::Chain::AssignPartition,
Gitlab::Ci::Pipeline::Chain::Seed,
Gitlab::Ci::Pipeline::Chain::Limit::Size,
+ Gitlab::Ci::Pipeline::Chain::Limit::ActiveJobs,
Gitlab::Ci::Pipeline::Chain::Limit::Deployments,
Gitlab::Ci::Pipeline::Chain::Validate::External,
Gitlab::Ci::Pipeline::Chain::Populate,
@@ -36,7 +37,6 @@ module Ci
Gitlab::Ci::Pipeline::Chain::CreateDeployments,
Gitlab::Ci::Pipeline::Chain::CreateCrossDatabaseAssociations,
Gitlab::Ci::Pipeline::Chain::Limit::Activity,
- Gitlab::Ci::Pipeline::Chain::Limit::JobActivity,
Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines,
Gitlab::Ci::Pipeline::Chain::Metrics,
Gitlab::Ci::Pipeline::Chain::TemplateUsage,
@@ -140,7 +140,7 @@ module Ci
end
def create_namespace_onboarding_action
- Namespaces::OnboardingPipelineCreatedWorker.perform_async(project.namespace_id)
+ Onboarding::PipelineCreatedWorker.perform_async(project.namespace_id)
end
def extra_options(content: nil, dry_run: false)
diff --git a/app/services/ci/generate_kubeconfig_service.rb b/app/services/ci/generate_kubeconfig_service.rb
index 894ab8e8505..347bc99dbf5 100644
--- a/app/services/ci/generate_kubeconfig_service.rb
+++ b/app/services/ci/generate_kubeconfig_service.rb
@@ -14,7 +14,8 @@ module Ci
url: Gitlab::Kas.tunnel_url
)
- agents.each do |agent|
+ agent_authorizations.each do |authorization|
+ agent = authorization.agent
user = user_name(agent)
template.add_user(
@@ -24,6 +25,7 @@ module Ci
template.add_context(
name: context_name(agent),
+ namespace: context_namespace(authorization),
cluster: cluster_name,
user: user
)
@@ -36,8 +38,8 @@ module Ci
attr_reader :pipeline, :token, :template
- def agents
- pipeline.authorized_cluster_agents
+ def agent_authorizations
+ pipeline.cluster_agent_authorizations
end
def cluster_name
@@ -52,6 +54,10 @@ module Ci
[agent.project.full_path, agent.name].join(delimiter)
end
+ def context_namespace(authorization)
+ authorization.config['default_namespace']
+ end
+
def agent_token(agent)
['ci', agent.id, token].join(delimiter)
end
diff --git a/app/services/ci/job_artifacts/delete_service.rb b/app/services/ci/job_artifacts/delete_service.rb
index 65cae03312e..c9d590eccc4 100644
--- a/app/services/ci/job_artifacts/delete_service.rb
+++ b/app/services/ci/job_artifacts/delete_service.rb
@@ -15,13 +15,23 @@ module Ci
method: 'Ci::JobArtifacts::DeleteService#execute',
project_id: build.project_id
)
+ return ServiceResponse.error(
+ message: 'Action temporarily disabled. The project this job belongs to is undergoing stats refresh.',
+ reason: :project_stats_refresh
+ )
end
- # fix_expire_at is false because in this case we want to explicitly delete the job artifacts
- # this flag is a workaround that will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/355833
- Ci::JobArtifacts::DestroyBatchService.new(build.job_artifacts.erasable, fix_expire_at: false).execute
+ result = Ci::JobArtifacts::DestroyBatchService.new(build.job_artifacts.erasable).execute
- ServiceResponse.success
+ if result.fetch(:status) == :success
+ ServiceResponse.success(payload:
+ {
+ destroyed_artifacts_count: result.fetch(:destroyed_artifacts_count),
+ statistics_updates: result.fetch(:statistics_updates)
+ })
+ else
+ ServiceResponse.error(message: result.fetch(:message))
+ end
end
private
diff --git a/app/services/ci/parse_dotenv_artifact_service.rb b/app/services/ci/parse_dotenv_artifact_service.rb
index fd13ed245cf..14e8dc41cf5 100644
--- a/app/services/ci/parse_dotenv_artifact_service.rb
+++ b/app/services/ci/parse_dotenv_artifact_service.rb
@@ -40,7 +40,7 @@ module Ci
key, value = scan_line!(line)
variables[key] = Ci::JobVariable.new(job_id: artifact.job_id,
- source: :dotenv, key: key, value: value)
+ source: :dotenv, key: key, value: value, raw: false)
end
end
diff --git a/app/services/ci/pipeline_artifacts/coverage_report_service.rb b/app/services/ci/pipeline_artifacts/coverage_report_service.rb
index 99877603554..9c6fdb7a405 100644
--- a/app/services/ci/pipeline_artifacts/coverage_report_service.rb
+++ b/app/services/ci/pipeline_artifacts/coverage_report_service.rb
@@ -27,18 +27,13 @@ module Ci
end
def pipeline_artifact_params
- attributes = {
+ {
pipeline: pipeline,
file_type: :code_coverage,
file: carrierwave_file,
- size: carrierwave_file['tempfile'].size
+ size: carrierwave_file['tempfile'].size,
+ locked: pipeline.locked
}
-
- if ::Feature.enabled?(:ci_update_unlocked_pipeline_artifacts, pipeline.project)
- attributes[:locked] = pipeline.locked
- end
-
- attributes
end
def carrierwave_file
diff --git a/app/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service.rb b/app/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service.rb
index aeb68a75f88..a0746ef32b2 100644
--- a/app/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service.rb
+++ b/app/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service.rb
@@ -23,20 +23,15 @@ module Ci
def artifact_attributes
file = build_carrierwave_file!
- attributes = {
+ {
project_id: pipeline.project_id,
file_type: :code_quality_mr_diff,
file_format: Ci::PipelineArtifact::REPORT_TYPES.fetch(:code_quality_mr_diff),
size: file["tempfile"].size,
file: file,
- expire_at: Ci::PipelineArtifact::EXPIRATION_DATE.from_now
+ expire_at: Ci::PipelineArtifact::EXPIRATION_DATE.from_now,
+ locked: pipeline.locked
}
-
- if ::Feature.enabled?(:ci_update_unlocked_pipeline_artifacts, pipeline.project)
- attributes[:locked] = pipeline.locked
- end
-
- attributes
end
def merge_requests
diff --git a/app/services/ci/pipeline_artifacts/destroy_all_expired_service.rb b/app/services/ci/pipeline_artifacts/destroy_all_expired_service.rb
index 17c039885e5..8dddf3c3f6c 100644
--- a/app/services/ci/pipeline_artifacts/destroy_all_expired_service.rb
+++ b/app/services/ci/pipeline_artifacts/destroy_all_expired_service.rb
@@ -3,20 +3,26 @@
module Ci
module PipelineArtifacts
class DestroyAllExpiredService
+ include ::Gitlab::ExclusiveLeaseHelpers
include ::Gitlab::LoopHelpers
include ::Gitlab::Utils::StrongMemoize
BATCH_SIZE = 100
- LOOP_TIMEOUT = 5.minutes
LOOP_LIMIT = 1000
+ LOOP_TIMEOUT = 5.minutes
+ LOCK_TIMEOUT = 10.minutes
+ EXCLUSIVE_LOCK_KEY = 'expired_pipeline_artifacts:destroy:lock'
def initialize
@removed_artifacts_count = 0
+ @start_at = Time.current
end
def execute
- loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do
- destroy_artifacts_batch
+ in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
+ destroy_unlocked_pipeline_artifacts
+
+ legacy_destroy_pipeline_artifacts
end
@removed_artifacts_count
@@ -24,10 +30,30 @@ module Ci
private
+ def destroy_unlocked_pipeline_artifacts
+ loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do
+ artifacts = Ci::PipelineArtifact.expired_before(@start_at).artifact_unlocked.limit(BATCH_SIZE)
+
+ break if artifacts.empty?
+
+ destroy_batch(artifacts)
+ end
+ end
+
+ def legacy_destroy_pipeline_artifacts
+ loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do
+ destroy_artifacts_batch
+ end
+ end
+
def destroy_artifacts_batch
artifacts = ::Ci::PipelineArtifact.unlocked.expired.limit(BATCH_SIZE).to_a
return false if artifacts.empty?
+ destroy_batch(artifacts)
+ end
+
+ def destroy_batch(artifacts)
artifacts.each(&:destroy!)
increment_stats(artifacts.size)
diff --git a/app/services/ci/process_build_service.rb b/app/services/ci/process_build_service.rb
index e6ec65fcc91..22cd267806d 100644
--- a/app/services/ci/process_build_service.rb
+++ b/app/services/ci/process_build_service.rb
@@ -25,6 +25,8 @@ module Ci
end
def enqueue(build)
+ return build.drop!(:failed_outdated_deployment_job) if build.prevent_rollback_deployment?
+
build.enqueue
end
diff --git a/app/services/ci/runners/register_runner_service.rb b/app/services/ci/runners/register_runner_service.rb
index ae9b8bc8a16..abd32610cec 100644
--- a/app/services/ci/runners/register_runner_service.rb
+++ b/app/services/ci/runners/register_runner_service.rb
@@ -59,7 +59,7 @@ module Ci
end
def runner_registrar_valid?(type)
- Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
+ Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
end
def token_scope
diff --git a/app/services/ci/unlock_artifacts_service.rb b/app/services/ci/unlock_artifacts_service.rb
index 1fee31da4fc..574cdae6480 100644
--- a/app/services/ci/unlock_artifacts_service.rb
+++ b/app/services/ci/unlock_artifacts_service.rb
@@ -11,8 +11,6 @@ module Ci
unlocked_pipeline_artifacts: 0
}
- unlock_pipeline_artifacts_enabled = ::Feature.enabled?(:ci_update_unlocked_pipeline_artifacts, ci_ref.project)
-
if ::Feature.enabled?(:ci_update_unlocked_job_artifacts, ci_ref.project)
loop do
unlocked_pipelines = []
@@ -22,9 +20,7 @@ module Ci
unlocked_pipelines = unlock_pipelines(ci_ref, before_pipeline)
unlocked_job_artifacts = unlock_job_artifacts(unlocked_pipelines)
- if unlock_pipeline_artifacts_enabled
- results[:unlocked_pipeline_artifacts] += unlock_pipeline_artifacts(unlocked_pipelines)
- end
+ results[:unlocked_pipeline_artifacts] += unlock_pipeline_artifacts(unlocked_pipelines)
end
break if unlocked_pipelines.empty?
diff --git a/app/services/clusters/applications/destroy_service.rb b/app/services/clusters/applications/destroy_service.rb
deleted file mode 100644
index d666682487b..00000000000
--- a/app/services/clusters/applications/destroy_service.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Applications
- class DestroyService < ::Clusters::Applications::BaseService
- def execute(_request)
- instantiate_application.tap do |application|
- break unless application.can_uninstall?
-
- application.make_scheduled!
-
- Clusters::Applications::UninstallWorker.perform_async(application.name, application.id)
- end
- end
-
- private
-
- def builder
- cluster.public_send(application_class.association_name) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
- end
-end
diff --git a/app/services/clusters/applications/uninstall_service.rb b/app/services/clusters/applications/uninstall_service.rb
deleted file mode 100644
index 50c8d806c14..00000000000
--- a/app/services/clusters/applications/uninstall_service.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Applications
- class UninstallService < BaseHelmService
- def execute
- return unless app.scheduled?
-
- app.make_uninstalling!
- uninstall
- end
-
- private
-
- def uninstall
- helm_api.uninstall(app.uninstall_command)
-
- Clusters::Applications::WaitForUninstallAppWorker.perform_in(
- Clusters::Applications::WaitForUninstallAppWorker::INTERVAL, app.name, app.id)
- rescue Kubeclient::HttpError => e
- log_error(e)
- app.make_errored!("Kubernetes error: #{e.error_code}")
- rescue StandardError => e
- log_error(e)
- app.make_errored!('Failed to uninstall.')
- end
- end
- end
-end
diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb
index c1c93aa604e..281b2508090 100644
--- a/app/services/concerns/users/participable_service.rb
+++ b/app/services/concerns/users/participable_service.rb
@@ -32,6 +32,8 @@ module Users
end
def groups
+ return [] unless current_user
+
current_user.authorized_groups.with_route.sort_by(&:path)
end
diff --git a/app/services/concerns/work_items/widgetable_service.rb b/app/services/concerns/work_items/widgetable_service.rb
index beb614c7b76..24ade9336b2 100644
--- a/app/services/concerns/work_items/widgetable_service.rb
+++ b/app/services/concerns/work_items/widgetable_service.rb
@@ -2,18 +2,22 @@
module WorkItems
module WidgetableService
- def execute_widgets(work_item:, callback:, widget_params: {})
+ def execute_widgets(work_item:, callback:, widget_params: {}, service_params: {})
work_item.widgets.each do |widget|
- widget_service(widget).try(callback, params: widget_params[widget.class.api_symbol])
+ widget_service(widget, service_params).try(callback, params: widget_params[widget.class.api_symbol])
end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
- def widget_service(widget)
+ def widget_service(widget, service_params)
@widget_services ||= {}
return @widget_services[widget] if @widget_services.has_key?(widget)
- @widget_services[widget] = widget_service_class(widget)&.new(widget: widget, current_user: current_user)
+ @widget_services[widget] = widget_service_class(widget)&.new(
+ widget: widget,
+ current_user: current_user,
+ service_params: service_params
+ )
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/app/services/google_cloud/enable_cloudsql_service.rb b/app/services/google_cloud/enable_cloudsql_service.rb
index e4a411d0fab..911cccca5ca 100644
--- a/app/services/google_cloud/enable_cloudsql_service.rb
+++ b/app/services/google_cloud/enable_cloudsql_service.rb
@@ -3,7 +3,7 @@
module GoogleCloud
class EnableCloudsqlService < ::GoogleCloud::BaseService
def execute
- return no_projects_error if unique_gcp_project_ids.empty?
+ create_or_replace_project_vars(environment_name, 'GCP_PROJECT_ID', gcp_project_id, ci_var_protected?)
unique_gcp_project_ids.each do |gcp_project_id|
google_api_client.enable_cloud_sql_admin(gcp_project_id)
@@ -18,8 +18,8 @@ module GoogleCloud
private
- def no_projects_error
- error("No GCP projects found. Configure a service account or GCP_PROJECT_ID CI variable.")
+ def ci_var_protected?
+ ProtectedBranch.protected?(project, environment_name) || ProtectedTag.protected?(project, environment_name)
end
end
end
diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb
index db52a272bf2..4092ded67bc 100644
--- a/app/services/groups/import_export/import_service.rb
+++ b/app/services/groups/import_export/import_service.rb
@@ -26,6 +26,8 @@ module Groups
end
def execute
+ Gitlab::Tracking.event(self.class.name, 'create', label: 'import_group_from_file')
+
if valid_user_permissions? && import_file && restorers.all?(&:restore)
notify_success
diff --git a/app/services/import/github/cancel_project_import_service.rb b/app/services/import/github/cancel_project_import_service.rb
new file mode 100644
index 00000000000..5dce5e73662
--- /dev/null
+++ b/app/services/import/github/cancel_project_import_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Import
+ module Github
+ class CancelProjectImportService < ::BaseService
+ def execute
+ return error('Not Found', :not_found) unless authorized_to_read?
+ return error('Unauthorized access', :forbidden) unless authorized_to_cancel?
+
+ if project.import_in_progress?
+ project.import_state.cancel
+ success(project: project)
+ else
+ error(cannot_cancel_error_message, :bad_request)
+ end
+ end
+
+ private
+
+ def authorized_to_read?
+ can?(current_user, :read_project, project)
+ end
+
+ def authorized_to_cancel?
+ can?(current_user, :owner_access, project)
+ end
+
+ def cannot_cancel_error_message
+ format(
+ _('The import cannot be canceled because it is %{project_status}'),
+ project_status: project.import_state.status
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
index 53297d2412c..a60963e28c7 100644
--- a/app/services/import/github_service.rb
+++ b/app/services/import/github_service.rb
@@ -9,21 +9,13 @@ module Import
attr_reader :params, :current_user
def execute(access_params, provider)
- if blocked_url?
- return log_and_return_error("Invalid URL: #{url}", _("Invalid URL: %{url}") % { url: url }, :bad_request)
- end
-
- unless authorized?
- return error(_('This namespace has already been taken! Please choose another one.'), :unprocessable_entity)
- end
-
- if oversized?
- return error(oversize_error_message, :unprocessable_entity)
- end
+ context_error = validate_context
+ return context_error if context_error
project = create_project(access_params, provider)
if project.persisted?
+ store_import_settings(project)
success(project)
elsif project.errors[:import_source_disabled].present?
error(project.errors[:import_source_disabled], :forbidden)
@@ -108,6 +100,16 @@ module Import
private
+ def validate_context
+ if blocked_url?
+ log_and_return_error("Invalid URL: #{url}", _("Invalid URL: %{url}") % { url: url }, :bad_request)
+ elsif !authorized?
+ error(_('This namespace has already been taken. Choose a different one.'), :unprocessable_entity)
+ elsif oversized?
+ error(oversize_error_message, :unprocessable_entity)
+ end
+ end
+
def log_error(exception)
Gitlab::GithubImport::Logger.error(
message: 'Import failed due to a GitHub error',
@@ -126,6 +128,10 @@ module Import
error(translated_message, http_status)
end
+
+ def store_import_settings(project)
+ Gitlab::GithubImport::Settings.new(project).write(params[:optional_stages])
+ end
end
end
diff --git a/app/services/incident_management/incidents/create_service.rb b/app/services/incident_management/incidents/create_service.rb
index ef66325fdcc..f44842650b7 100644
--- a/app/services/incident_management/incidents/create_service.rb
+++ b/app/services/incident_management/incidents/create_service.rb
@@ -15,7 +15,7 @@ module IncidentManagement
end
def execute
- issue = Issues::CreateService.new(
+ create_result = Issues::CreateService.new(
project: project,
current_user: current_user,
params: {
@@ -29,22 +29,16 @@ module IncidentManagement
).execute
if alert
- return error(alert.errors.full_messages.to_sentence, issue) unless alert.valid?
+ return error(alert.errors.full_messages, create_result[:issue]) unless alert.valid?
end
- return error(issue.errors.full_messages.to_sentence, issue) unless issue.valid?
-
- success(issue)
+ create_result
end
private
attr_reader :title, :description, :severity, :alert
- def success(issue)
- ServiceResponse.success(payload: { issue: issue })
- end
-
def error(message, issue = nil)
ServiceResponse.error(payload: { issue: issue }, message: message)
end
diff --git a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
index 58777848151..d495ec5cab6 100644
--- a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
+++ b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
@@ -11,7 +11,7 @@ module IncidentManagement
@issuable = issuable
@param_errors = []
- super(project: issuable.project, current_user: current_user, params: Hash(params))
+ super(project: issuable.project, current_user: current_user, params: params)
end
def execute
diff --git a/app/services/incident_management/timeline_events/create_service.rb b/app/services/incident_management/timeline_events/create_service.rb
index 40ce9097c88..5422b4ad6d2 100644
--- a/app/services/incident_management/timeline_events/create_service.rb
+++ b/app/services/incident_management/timeline_events/create_service.rb
@@ -109,7 +109,6 @@ module IncidentManagement
def add_system_note(timeline_event)
return if auto_created
- return unless Feature.enabled?(:incident_timeline, project)
SystemNoteService.add_timeline_event(timeline_event)
end
diff --git a/app/services/incident_management/timeline_events/destroy_service.rb b/app/services/incident_management/timeline_events/destroy_service.rb
index 90e95ae8869..e1c6bbbdb85 100644
--- a/app/services/incident_management/timeline_events/destroy_service.rb
+++ b/app/services/incident_management/timeline_events/destroy_service.rb
@@ -30,8 +30,6 @@ module IncidentManagement
attr_reader :project, :timeline_event, :user, :incident
def add_system_note(incident, user)
- return unless Feature.enabled?(:incident_timeline, project)
-
SystemNoteService.delete_timeline_event(incident, user)
end
end
diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb
index 5c5de4717bc..012e2f0e260 100644
--- a/app/services/incident_management/timeline_events/update_service.rb
+++ b/app/services/incident_management/timeline_events/update_service.rb
@@ -38,8 +38,6 @@ module IncidentManagement
end
def add_system_note(timeline_event)
- return unless Feature.enabled?(:incident_timeline, incident.project)
-
changes = was_changed(timeline_event)
return if changes == :none
diff --git a/app/services/issuable/import_csv/base_service.rb b/app/services/issuable/import_csv/base_service.rb
index 822e3cd787c..e84d1032e41 100644
--- a/app/services/issuable/import_csv/base_service.rb
+++ b/app/services/issuable/import_csv/base_service.rb
@@ -23,7 +23,7 @@ module Issuable
with_csv_lines.each do |row, line_no|
attributes = issuable_attributes_for(row)
- if create_issuable(attributes).persisted?
+ if create_issuable(attributes)&.persisted?
@results[:success] += 1
else
@results[:error_lines].push(line_no)
diff --git a/app/services/issuable/process_assignees.rb b/app/services/issuable/process_assignees.rb
index 1ef6d3d9c42..72f727c134d 100644
--- a/app/services/issuable/process_assignees.rb
+++ b/app/services/issuable/process_assignees.rb
@@ -6,11 +6,11 @@
module Issuable
class ProcessAssignees
def initialize(assignee_ids:, add_assignee_ids:, remove_assignee_ids:, existing_assignee_ids: nil, extra_assignee_ids: nil)
- @assignee_ids = assignee_ids
- @add_assignee_ids = add_assignee_ids
- @remove_assignee_ids = remove_assignee_ids
- @existing_assignee_ids = existing_assignee_ids || []
- @extra_assignee_ids = extra_assignee_ids || []
+ @assignee_ids = assignee_ids&.map(&:to_i)
+ @add_assignee_ids = add_assignee_ids&.map(&:to_i)
+ @remove_assignee_ids = remove_assignee_ids&.map(&:to_i)
+ @existing_assignee_ids = existing_assignee_ids&.map(&:to_i) || []
+ @extra_assignee_ids = extra_assignee_ids&.map(&:to_i) || []
end
def execute
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 70ad97f8436..e24ae8f59f0 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -67,22 +67,14 @@ class IssuableBaseService < ::BaseProjectService
end
def filter_assignees(issuable)
- filter_assignees_with_key(issuable, :assignee_ids, :assignees)
- filter_assignees_with_key(issuable, :add_assignee_ids, :add_assignees)
- filter_assignees_with_key(issuable, :remove_assignee_ids, :remove_assignees)
+ filter_assignees_using_checks(issuable, :assignee_ids)
+ filter_assignees_using_checks(issuable, :add_assignee_ids)
+ filter_assignees_using_checks(issuable, :remove_assignee_ids)
end
- def filter_assignees_with_key(issuable, id_key, key)
- if params[key] && params[id_key].blank?
- params[id_key] = params[key].map(&:id)
- end
-
+ def filter_assignees_using_checks(issuable, id_key)
return if params[id_key].blank?
- filter_assignees_using_checks(issuable, id_key)
- end
-
- def filter_assignees_using_checks(issuable, id_key)
unless issuable.allows_multiple_assignees?
params[id_key] = params[id_key].first(1)
end
@@ -154,10 +146,13 @@ class IssuableBaseService < ::BaseProjectService
end
def filter_escalation_status(issuable)
+ status_params = params.delete(:escalation_status) || {}
+ status_params.permit! if status_params.respond_to?(:permit!)
+
result = ::IncidentManagement::IssuableEscalationStatuses::PrepareUpdateService.new(
issuable,
current_user,
- params.delete(:escalation_status)
+ status_params
).execute
return unless result.success? && result[:escalation_status].present?
@@ -266,11 +261,23 @@ class IssuableBaseService < ::BaseProjectService
# To be overridden by subclasses
end
- def after_update(issuable)
+ def prepare_update_params(issuable)
# To be overridden by subclasses
end
+ def after_update(issuable, old_associations)
+ handle_description_updated(issuable)
+ handle_label_changes(issuable, old_associations[:labels])
+ end
+
+ def handle_description_updated(issuable)
+ return unless issuable.previous_changes.include?('description')
+
+ GraphqlTriggers.issuable_description_updated(issuable)
+ end
+
def update(issuable)
+ prepare_update_params(issuable)
handle_quick_actions(issuable)
filter_params(issuable)
@@ -316,7 +323,7 @@ class IssuableBaseService < ::BaseProjectService
affected_assignees = (old_associations[:assignees] + new_assignees) - (old_associations[:assignees] & new_assignees)
invalidate_cache_counts(issuable, users: affected_assignees.compact)
- after_update(issuable)
+ after_update(issuable, old_associations)
issuable.create_new_cross_references!(current_user)
execute_hooks(
issuable,
@@ -356,7 +363,8 @@ class IssuableBaseService < ::BaseProjectService
handle_task_changes(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees.to_a)
- after_update(issuable)
+ # not passing old_associations here to keep `update_task` as fast as possible
+ after_update(issuable, {})
execute_hooks(issuable, 'update', old_associations: nil)
if issuable.is_a?(MergeRequest)
@@ -531,6 +539,8 @@ class IssuableBaseService < ::BaseProjectService
end
def has_label_changes?(issuable, old_labels)
+ return false if old_labels.nil?
+
Set.new(issuable.labels) != Set.new(old_labels)
end
@@ -542,12 +552,15 @@ class IssuableBaseService < ::BaseProjectService
# override if needed
def handle_label_changes(issuable, old_labels)
- return unless has_label_changes?(issuable, old_labels)
+ return false unless has_label_changes?(issuable, old_labels)
# reset to preserve the label sort order (title ASC)
issuable.labels.reset
GraphqlTriggers.issuable_labels_updated(issuable)
+
+ # return true here to avoid checking for label changes in sub classes
+ true
end
# override if needed
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index d75e74f3b19..28ea6b0ebf8 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -68,6 +68,19 @@ module Issues
rebalance_if_needed(issue)
end
+ def handle_escalation_status_change(issue)
+ return unless issue.supports_escalation?
+
+ if issue.escalation_status
+ ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.new(
+ issue,
+ current_user
+ ).execute
+ else
+ ::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute
+ end
+ end
+
def issuable_for_positioning(id, positioning_scope)
return unless id
diff --git a/app/services/issues/clone_service.rb b/app/services/issues/clone_service.rb
index 07dd9a98f89..8b05a1c2acd 100644
--- a/app/services/issues/clone_service.rb
+++ b/app/services/issues/clone_service.rb
@@ -75,7 +75,16 @@ module Issues
# Skip creation of system notes for existing attributes of the issue when cloning with notes.
# The system notes of the old issue are copied over so we don't want to end up with duplicate notes.
# When cloning without notes, we want to generate system notes for the attributes that were copied.
- CreateService.new(project: target_project, current_user: current_user, params: new_params, spam_params: spam_params).execute(skip_system_notes: with_notes)
+ create_result = CreateService.new(
+ project: target_project,
+ current_user: current_user,
+ params: new_params,
+ spam_params: spam_params
+ ).execute(skip_system_notes: with_notes)
+
+ raise CloneError, create_result.errors.join(', ') if create_result.error? && create_result[:issue].blank?
+
+ create_result[:issue]
end
def queue_copy_designs
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 92cf4811439..89b35bbab24 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -4,6 +4,7 @@ module Issues
class CreateService < Issues::BaseService
include ResolveDiscussions
prepend RateLimitedService
+ include ::Services::ReturnServiceResponses
rate_limit key: :issues_create,
opts: { scope: [:project, :current_user, :external_author] }
@@ -20,6 +21,8 @@ module Issues
end
def execute(skip_system_notes: false)
+ return error(_('Operation not allowed'), 403) unless @current_user.can?(authorization_action, @project)
+
@issue = @build_service.execute
handle_move_between_ids(@issue)
@@ -27,7 +30,13 @@ module Issues
@add_related_issue ||= params.delete(:add_related_issue)
filter_resolve_discussion_params
- create(@issue, skip_system_notes: skip_system_notes)
+ issue = create(@issue, skip_system_notes: skip_system_notes)
+
+ if issue.persisted?
+ success(issue: issue)
+ else
+ error(issue.errors.full_messages, 422, pass_back: { issue: issue })
+ end
end
def external_author
@@ -47,7 +56,7 @@ module Issues
issue.run_after_commit do
NewIssueWorker.perform_async(issue.id, user.id, issue.class.to_s)
Issues::PlacementWorker.perform_async(nil, issue.project_id)
- Namespaces::OnboardingIssueCreatedWorker.perform_async(issue.project.namespace_id)
+ Onboarding::IssueCreatedWorker.perform_async(issue.project.namespace_id)
end
end
@@ -56,7 +65,7 @@ module Issues
user_agent_detail_service.create
handle_add_related_issue(issue)
resolve_discussions_with_issue(issue)
- create_escalation_status(issue)
+ handle_escalation_status_change(issue)
create_timeline_event(issue)
try_to_associate_contacts(issue)
@@ -87,12 +96,12 @@ module Issues
private
- attr_reader :spam_params, :extra_params
-
- def create_escalation_status(issue)
- ::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation?
+ def authorization_action
+ :create_issue
end
+ attr_reader :spam_params, :extra_params
+
def create_timeline_event(issue)
return unless issue.incident?
diff --git a/app/services/issues/import_csv_service.rb b/app/services/issues/import_csv_service.rb
index bce3ecc8bef..83e550583f6 100644
--- a/app/services/issues/import_csv_service.rb
+++ b/app/services/issues/import_csv_service.rb
@@ -14,6 +14,10 @@ module Issues
private
+ def create_issuable(attributes)
+ super[:issue]
+ end
+
def create_issuable_class
Issues::CreateService
end
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index edab62b1fdf..6366ff4076b 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -83,7 +83,16 @@ module Issues
# 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(project: @target_project, current_user: @current_user, params: new_params, spam_params: spam_params).execute(skip_system_notes: true)
+ create_result = CreateService.new(
+ project: @target_project,
+ current_user: @current_user,
+ params: new_params,
+ spam_params: spam_params
+ ).execute(skip_system_notes: true)
+
+ raise MoveError, create_result.errors.join(', ') if create_result.error? && create_result[:issue].blank?
+
+ create_result[:issue]
end
def queue_copy_designs
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 46c28d82ddc..e5feb4422f6 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -63,7 +63,6 @@ module Issues
handle_assignee_changes(issue, old_assignees)
handle_confidential_change(issue)
- handle_label_changes(issue, old_labels)
handle_added_labels(issue, old_labels)
handle_milestone_change(issue)
handle_added_mentions(issue, old_mentioned_users)
@@ -201,15 +200,6 @@ module Issues
::IncidentManagement::AddSeveritySystemNoteWorker.perform_async(issue.id, current_user.id)
end
- def handle_escalation_status_change(issue)
- return unless issue.supports_escalation? && issue.escalation_status
-
- ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.new(
- issue,
- current_user
- ).execute
- end
-
def create_confidentiality_note(issue)
SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
end
diff --git a/app/services/jira_connect/create_asymmetric_jwt_service.rb b/app/services/jira_connect/create_asymmetric_jwt_service.rb
new file mode 100644
index 00000000000..71aba6feddd
--- /dev/null
+++ b/app/services/jira_connect/create_asymmetric_jwt_service.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module JiraConnect
+ class CreateAsymmetricJwtService
+ ARGUMENT_ERROR_MESSAGE = 'jira_connect_installation is not a proxy installation'
+
+ def initialize(jira_connect_installation)
+ raise ArgumentError, ARGUMENT_ERROR_MESSAGE unless jira_connect_installation.proxy?
+
+ @jira_connect_installation = jira_connect_installation
+ end
+
+ def execute
+ JWT.encode(jwt_claims, private_key, 'RS256', jwt_headers)
+ end
+
+ private
+
+ def jwt_claims
+ { aud: aud_claim, iss: iss_claim, qsh: qsh_claim }
+ end
+
+ def aud_claim
+ @jira_connect_installation.audience_url
+ end
+
+ def iss_claim
+ @jira_connect_installation.client_key
+ end
+
+ def qsh_claim
+ Atlassian::Jwt.create_query_string_hash(
+ @jira_connect_installation.audience_installed_event_url,
+ 'POST',
+ @jira_connect_installation.audience_url
+ )
+ end
+
+ def private_key
+ @private_key ||= OpenSSL::PKey::RSA.generate(3072)
+ end
+
+ def public_key_storage
+ @public_key_storage ||= JiraConnect::PublicKey.create!(key: private_key.public_key)
+ end
+
+ def jwt_headers
+ { kid: public_key_storage.uuid }
+ end
+ end
+end
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index e3b110f8f26..2786a2e357e 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -9,7 +9,7 @@ module Labels
return unless project.group &&
label.is_a?(ProjectLabel)
- Label.transaction do
+ ProjectLabel.transaction do
# use the existing group label if it exists
group_label = find_or_create_group_label(label)
@@ -50,7 +50,7 @@ module Labels
.new(current_user, title: group_label.title, group_id: project.group.id)
.execute(skip_authorization: true)
.where.not(id: group_label)
- .select(:id) # Can't use pluck() to avoid object-creation because of the batching
+ .select(:id, :project_id, :group_id, :type) # Can't use pluck() to avoid object-creation because of the batching
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb
index 38bebc1d09d..aba075c3644 100644
--- a/app/services/members/create_service.rb
+++ b/app/services/members/create_service.rb
@@ -179,7 +179,7 @@ module Members
def enqueue_onboarding_progress_action
return unless member_created_namespace_id
- Namespaces::OnboardingUserAddedWorker.perform_async(member_created_namespace_id)
+ Onboarding::UserAddedWorker.perform_async(member_created_namespace_id)
end
def result
@@ -195,6 +195,8 @@ module Members
end
def publish_event!
+ return unless member_created_namespace_id
+
Gitlab::EventStore.publish(
Members::MembersAddedEvent.new(data: {
source_id: source.id,
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 0a8344c58db..ce79907e8a8 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -2,6 +2,8 @@
module Members
class DestroyService < Members::BaseService
+ include Gitlab::ExclusiveLeaseHelpers
+
def execute(member, skip_authorization: false, skip_subresources: false, unassign_issuables: false, destroy_bot: false)
unless skip_authorization
raise Gitlab::Access::AccessDeniedError unless authorized?(member, destroy_bot)
@@ -11,13 +13,26 @@ module Members
end
@skip_auth = skip_authorization
+ last_owner = true
+
+ in_lock("delete_members:#{member.source.class}:#{member.source.id}") do
+ break if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
+
+ last_owner = false
+ member.destroy
+ member.user&.invalidate_cache_counts
+ end
- return member if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
+ unless last_owner
+ delete_member_associations(member, skip_subresources, unassign_issuables)
+ end
- member.destroy
+ member
+ end
- member.user&.invalidate_cache_counts
+ private
+ def delete_member_associations(member, skip_subresources, unassign_issuables)
if member.request? && member.user != current_user
notification_service.decline_access_request(member)
end
@@ -28,12 +43,8 @@ module Members
enqueue_unassign_issuables(member) if unassign_issuables
after_execute(member: member)
-
- member
end
- private
-
def authorized?(member, destroy_bot)
return can_destroy_bot_member?(member) if destroy_bot
diff --git a/app/services/merge_requests/approval_service.rb b/app/services/merge_requests/approval_service.rb
index 64ae33c9b15..5761e34caff 100644
--- a/app/services/merge_requests/approval_service.rb
+++ b/app/services/merge_requests/approval_service.rb
@@ -3,7 +3,7 @@
module MergeRequests
class ApprovalService < MergeRequests::BaseService
def execute(merge_request)
- return unless can_be_approved?(merge_request)
+ return unless eligible_for_approval?(merge_request)
approval = merge_request.approvals.new(user: current_user)
@@ -28,8 +28,8 @@ module MergeRequests
private
- def can_be_approved?(merge_request)
- merge_request.can_be_approved_by?(current_user)
+ def eligible_for_approval?(merge_request)
+ merge_request.eligible_for_approval_by?(current_user)
end
def save_approval(approval)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 6cefd9169f5..cfd7c645b7e 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -58,6 +58,7 @@ module MergeRequests
new_reviewers = merge_request.reviewers - old_reviewers
merge_request_activity_counter.track_users_review_requested(users: new_reviewers)
merge_request_activity_counter.track_reviewers_changed_action(user: current_user)
+ trigger_merge_request_reviewers_updated(merge_request)
end
def cleanup_environments(merge_request)
@@ -244,6 +245,10 @@ module MergeRequests
Milestones::MergeRequestsCountService.new(milestone).delete_cache
end
+
+ def trigger_merge_request_reviewers_updated(merge_request)
+ GraphqlTriggers.merge_request_reviewers_updated(merge_request)
+ end
end
end
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
index f83b14c7269..da3a9652d69 100644
--- a/app/services/merge_requests/close_service.rb
+++ b/app/services/merge_requests/close_service.rb
@@ -23,6 +23,7 @@ module MergeRequests
cleanup_environments(merge_request)
abort_auto_merge(merge_request, 'merge request was closed')
cleanup_refs(merge_request)
+ trigger_merge_request_merge_status_updated(merge_request)
end
merge_request
@@ -38,5 +39,9 @@ module MergeRequests
merge_request_metrics_service(merge_request).close(close_event)
end
end
+
+ def trigger_merge_request_merge_status_updated(merge_request)
+ GraphqlTriggers.merge_request_merge_status_updated(merge_request)
+ end
end
end
diff --git a/app/services/merge_requests/mark_reviewer_reviewed_service.rb b/app/services/merge_requests/mark_reviewer_reviewed_service.rb
index 766a4ca0a49..96747eabcf6 100644
--- a/app/services/merge_requests/mark_reviewer_reviewed_service.rb
+++ b/app/services/merge_requests/mark_reviewer_reviewed_service.rb
@@ -10,6 +10,8 @@ module MergeRequests
if reviewer
return error("Failed to update reviewer") unless reviewer.update(state: :reviewed)
+ trigger_merge_request_reviewers_updated(merge_request)
+
success
else
error("Reviewer not found")
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
index 3e630d40b3d..2a3c1e8bc26 100644
--- a/app/services/merge_requests/merge_base_service.rb
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -9,12 +9,12 @@ module MergeRequests
attr_reader :merge_request
# Overridden in EE.
- def hooks_validation_pass?(_merge_request)
+ def hooks_validation_pass?(merge_request, validate_squash_message: false)
true
end
# Overridden in EE.
- def hooks_validation_error(_merge_request)
+ def hooks_validation_error(merge_request, validate_squash_message: false)
# No-op
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 6d31a29f5a7..6b4f9dbe509 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -26,6 +26,7 @@ module MergeRequests
@merge_request = merge_request
@options = options
+ jid = merge_jid
validate!
@@ -37,7 +38,7 @@ module MergeRequests
end
end
- log_info("Merge process finished on JID #{merge_jid} with state #{state}")
+ log_info("Merge process finished on JID #{jid} with state #{state}")
rescue MergeError => e
handle_merge_error(log_message: e.message, save_message_on_model: true)
ensure
@@ -159,17 +160,32 @@ module MergeRequests
end
def handle_merge_error(log_message:, save_message_on_model: false)
- Gitlab::AppLogger.error("MergeService ERROR: #{merge_request_info} - #{log_message}")
+ log_error("MergeService ERROR: #{merge_request_info} - #{log_message}")
@merge_request.update(merge_error: log_message) if save_message_on_model
end
def log_info(message)
+ payload = log_payload("#{merge_request_info} - #{message}")
+ logger.info(**payload)
+ end
+
+ def log_error(message)
+ payload = log_payload(message)
+ logger.error(**payload)
+ end
+
+ def logger
@logger ||= Gitlab::AppLogger
- @logger.info("#{merge_request_info} - #{message}")
+ end
+
+ def log_payload(message)
+ Gitlab::ApplicationContext.current
+ .merge(merge_request_info: merge_request_info,
+ message: message)
end
def merge_request_info
- merge_request.to_reference(full: true)
+ @merge_request_info ||= merge_request.to_reference(full: true)
end
def source_matches?
diff --git a/app/services/merge_requests/mergeability/logger.rb b/app/services/merge_requests/mergeability/logger.rb
index 8b45d231e03..88ef6d81eaa 100644
--- a/app/services/merge_requests/mergeability/logger.rb
+++ b/app/services/merge_requests/mergeability/logger.rb
@@ -11,16 +11,12 @@ module MergeRequests
end
def commit
- return unless enabled?
-
commit_logs
end
def instrument(mergeability_name:)
raise ArgumentError, 'block not given' unless block_given?
- return yield unless enabled?
-
op_start_db_counters = current_db_counter_payload
op_started_at = current_monotonic_time
@@ -38,15 +34,11 @@ module MergeRequests
attr_reader :destination, :merge_request
def observe(name, value)
- return unless enabled?
-
observations[name.to_s].push(value)
end
def commit_logs
- attributes = Gitlab::ApplicationContext.current.merge({
- mergeability_project_id: merge_request.project.id
- })
+ attributes = Gitlab::ApplicationContext.current.merge({ mergeability_project_id: merge_request.project.id })
attributes[:mergeability_merge_request_id] = merge_request.id
attributes.merge!(observations_hash)
@@ -89,12 +81,6 @@ module MergeRequests
::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_payload
end
- def enabled?
- strong_memoize(:enabled) do
- ::Feature.enabled?(:mergeability_checks_logger, merge_request.project)
- end
- end
-
def current_monotonic_time
::Gitlab::Metrics::System.monotonic_time
end
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index ef251f121ae..aa52349b0ee 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -104,7 +104,7 @@ module MergeRequests
merge_request = ::MergeRequests::CreateService.new(
project: project,
current_user: current_user,
- params: merge_request.attributes.merge(assignees: merge_request.assignees,
+ params: merge_request.attributes.merge(assignee_ids: merge_request.assignee_ids,
label_ids: merge_request.label_ids)
).execute
end
@@ -140,8 +140,8 @@ module MergeRequests
params[:add_labels] = params.delete(:label).keys if params.has_key?(:label)
params[:remove_labels] = params.delete(:unlabel).keys if params.has_key?(:unlabel)
- params[:add_assignee_ids] = params.delete(:assign).keys if params.has_key?(:assign)
- params[:remove_assignee_ids] = params.delete(:unassign).keys if params.has_key?(:unassign)
+ params[:add_assignee_ids] = convert_to_user_ids(params.delete(:assign).keys) if params.has_key?(:assign)
+ params[:remove_assignee_ids] = convert_to_user_ids(params.delete(:unassign).keys) if params.has_key?(:unassign)
if push_options[:milestone]
milestone = Milestone.for_projects_and_groups(@project, @project.ancestors_upto)&.find_by_name(push_options[:milestone])
@@ -169,7 +169,7 @@ module MergeRequests
params = base_params
params.merge!(
- assignees: [current_user],
+ assignee_ids: [current_user.id],
source_branch: branch,
source_project: project,
target_project: target_project
@@ -186,6 +186,12 @@ module MergeRequests
base_params.merge(merge_params(merge_request.source_branch))
end
+ def convert_to_user_ids(ids_or_usernames)
+ ids, usernames = ids_or_usernames.partition { |id_or_username| id_or_username.is_a?(Numeric) || id_or_username.match?(/\A\d+\z/) }
+ ids += User.by_username(usernames).pluck(:id) unless usernames.empty? # rubocop:disable CodeReuse/ActiveRecord
+ ids
+ end
+
def collect_errors_from_merge_request(merge_request)
merge_request.errors.full_messages.each do |error|
errors << error
diff --git a/app/services/merge_requests/request_review_service.rb b/app/services/merge_requests/request_review_service.rb
index b061ed45fee..ebbae98352b 100644
--- a/app/services/merge_requests/request_review_service.rb
+++ b/app/services/merge_requests/request_review_service.rb
@@ -11,6 +11,7 @@ module MergeRequests
return error("Failed to update reviewer") unless reviewer.update(state: :unreviewed)
notify_reviewer(merge_request, user)
+ trigger_merge_request_reviewers_updated(merge_request)
success
else
diff --git a/app/services/merge_requests/update_assignees_service.rb b/app/services/merge_requests/update_assignees_service.rb
index a13db52e34b..79a3e9f3c22 100644
--- a/app/services/merge_requests/update_assignees_service.rb
+++ b/app/services/merge_requests/update_assignees_service.rb
@@ -18,7 +18,17 @@ module MergeRequests
return merge_request if old_ids.to_set == new_ids.to_set # no-change
attrs = update_attrs.merge(assignee_ids: new_ids)
- merge_request.update!(**attrs)
+
+ # We now have assignees validation on merge request
+ # If we use an update with bang, it will explode,
+ # instead we need to check if its valid then return if its not valid.
+ if Feature.enabled?(:limit_assignees_per_issuable)
+ merge_request.update(**attrs)
+
+ return merge_request unless merge_request.valid?
+ else
+ merge_request.update!(**attrs)
+ end
# Defer the more expensive operations (handle_assignee_changes) to the background
MergeRequests::HandleAssigneesChangeService
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 6d518edc88f..745647b727c 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -38,7 +38,6 @@ module MergeRequests
handle_target_branch_change(merge_request)
handle_milestone_change(merge_request)
handle_draft_status_change(merge_request, changed_fields)
- handle_label_changes(merge_request, old_labels)
track_title_and_desc_edits(changed_fields)
track_discussion_lock_toggle(merge_request, changed_fields)
@@ -71,7 +70,8 @@ module MergeRequests
MergeRequests::CloseService
end
- def after_update(issuable)
+ def after_update(issuable, old_associations)
+ super
issuable.cache_merge_request_closes_issues!(current_user)
end
@@ -179,9 +179,12 @@ module MergeRequests
old_title_draft = MergeRequest.draft?(old_title)
new_title_draft = MergeRequest.draft?(new_title)
- # notify the draft status changed. Added/removed message is handled in the
- # email template itself, see `change_in_merge_request_draft_status_email` template.
- notify_draft_status_changed(merge_request) if old_title_draft || new_title_draft
+ if old_title_draft || new_title_draft
+ # notify the draft status changed. Added/removed message is handled in the
+ # email template itself, see `change_in_merge_request_draft_status_email` template.
+ notify_draft_status_changed(merge_request)
+ trigger_merge_request_status_updated(merge_request)
+ end
if !old_title_draft && new_title_draft
# Marked as Draft
@@ -320,6 +323,10 @@ module MergeRequests
def filter_sentinel_values(param)
param.reject { _1 == 0 }
end
+
+ def trigger_merge_request_status_updated(merge_request)
+ GraphqlTriggers.merge_request_merge_status_updated(merge_request)
+ end
end
end
diff --git a/app/services/ml/experiment_tracking/candidate_repository.rb b/app/services/ml/experiment_tracking/candidate_repository.rb
new file mode 100644
index 00000000000..b6f87995185
--- /dev/null
+++ b/app/services/ml/experiment_tracking/candidate_repository.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Ml
+ module ExperimentTracking
+ class CandidateRepository
+ attr_accessor :project, :user, :experiment, :candidate
+
+ def initialize(project, user)
+ @project = project
+ @user = user
+ end
+
+ def by_iid(iid)
+ ::Ml::Candidate.with_project_id_and_iid(project.id, iid)
+ end
+
+ def create!(experiment, start_time)
+ experiment.candidates.create!(
+ user: user,
+ start_time: start_time || 0
+ )
+ end
+
+ def update(candidate, status, end_time)
+ candidate.status = status.downcase if status
+ candidate.end_time = end_time if end_time
+
+ candidate.save
+ end
+
+ def add_metric!(candidate, name, value, tracked_at, step)
+ candidate.metrics.create!(
+ name: name,
+ value: value,
+ tracked_at: tracked_at,
+ step: step
+ )
+ end
+
+ def add_param!(candidate, name, value)
+ candidate.params.create!(name: name, value: value)
+ end
+
+ def add_metrics(candidate, metric_definitions)
+ return unless candidate.present?
+
+ metrics = metric_definitions.map do |metric|
+ {
+ candidate_id: candidate.id,
+ name: metric[:key],
+ value: metric[:value],
+ tracked_at: metric[:timestamp],
+ step: metric[:step],
+ **timestamps
+ }
+ end
+
+ ::Ml::CandidateMetric.insert_all(metrics, returning: false) unless metrics.empty?
+ end
+
+ def add_params(candidate, param_definitions)
+ return unless candidate.present?
+
+ parameters = param_definitions.map do |p|
+ {
+ candidate_id: candidate.id,
+ name: p[:key],
+ value: p[:value],
+ **timestamps
+ }
+ end
+
+ ::Ml::CandidateParam.insert_all(parameters, returning: false) unless parameters.empty?
+ end
+
+ private
+
+ def timestamps
+ current_time = Time.zone.now
+
+ { created_at: current_time, updated_at: current_time }
+ end
+ end
+ end
+end
diff --git a/app/services/ml/experiment_tracking/experiment_repository.rb b/app/services/ml/experiment_tracking/experiment_repository.rb
new file mode 100644
index 00000000000..891674adc2a
--- /dev/null
+++ b/app/services/ml/experiment_tracking/experiment_repository.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Ml
+ module ExperimentTracking
+ class ExperimentRepository
+ attr_accessor :project, :user
+
+ def initialize(project, user = nil)
+ @project = project
+ @user = user
+ end
+
+ def by_iid_or_name(iid: nil, name: nil)
+ return ::Ml::Experiment.by_project_id_and_iid(project.id, iid) if iid
+
+ ::Ml::Experiment.by_project_id_and_name(project.id, name) if name
+ end
+
+ def all
+ ::Ml::Experiment.by_project_id(project.id)
+ end
+
+ def create!(name)
+ ::Ml::Experiment.create!(name: name,
+ user: user,
+ project: project)
+ end
+ end
+ end
+end
diff --git a/app/services/namespaces/package_settings/update_service.rb b/app/services/namespaces/package_settings/update_service.rb
index c0af0900450..0c6fcee9113 100644
--- a/app/services/namespaces/package_settings/update_service.rb
+++ b/app/services/namespaces/package_settings/update_service.rb
@@ -8,7 +8,13 @@ module Namespaces
ALLOWED_ATTRIBUTES = %i[maven_duplicates_allowed
maven_duplicate_exception_regex
generic_duplicates_allowed
- generic_duplicate_exception_regex].freeze
+ generic_duplicate_exception_regex
+ maven_package_requests_forwarding
+ npm_package_requests_forwarding
+ pypi_package_requests_forwarding
+ lock_maven_package_requests_forwarding
+ lock_npm_package_requests_forwarding
+ lock_pypi_package_requests_forwarding].freeze
def execute
return ServiceResponse.error(message: 'Access Denied', http_status: 403) unless allowed?
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index b7e6a50fa5c..1aaf7fb769a 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -88,12 +88,14 @@ module Notes
return if quick_actions_service.commands_executed_count.to_i == 0
if update_params.present?
- if check_for_reviewer_validity(message, update_params)
+ invalid_message = validate_commands(note, update_params)
+
+ if invalid_message
+ note.errors.add(:validation, invalid_message)
+ message = invalid_message
+ else
quick_actions_service.apply_updates(update_params, note)
note.commands_changes = update_params
- else
- message = "Reviewers #{MergeRequest.max_number_of_assignees_or_reviewers_message}"
- note.errors.add(:validation, message)
end
end
@@ -114,16 +116,36 @@ module Notes
}
end
- def check_for_reviewer_validity(message, update_params)
- return true unless Feature.enabled?(:limit_reviewer_and_assignee_size)
+ def validate_commands(note, update_params)
+ if invalid_reviewers?(update_params)
+ "Reviewers #{note.noteable.class.max_number_of_assignees_or_reviewers_message}"
+ elsif invalid_assignees?(update_params)
+ "Assignees #{note.noteable.class.max_number_of_assignees_or_reviewers_message}"
+ end
+ end
+
+ def invalid_reviewers?(update_params)
+ return false unless Feature.enabled?(:limit_reviewer_and_assignee_size)
if update_params.key?(:reviewer_ids)
possible_reviewers = update_params[:reviewer_ids]&.uniq&.size
- return false if possible_reviewers > MergeRequest::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS
+ possible_reviewers > ::Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS
+ else
+ false
end
+ end
+
+ def invalid_assignees?(update_params)
+ return false unless Feature.enabled?(:limit_assignees_per_issuable)
- true
+ if update_params.key?(:assignee_ids)
+ possible_assignees = update_params[:assignee_ids]&.uniq&.size
+
+ possible_assignees > ::Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS
+ else
+ false
+ end
end
def track_event(note, user)
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 5a92adfd25a..1224cf80b76 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -87,6 +87,13 @@ class NotificationService
mailer.access_token_expired_email(user).deliver_later
end
+ # Notify the user when one of their personal access tokens is revoked
+ def access_token_revoked(user, token_name)
+ return unless user.can?(:receive_notifications)
+
+ mailer.access_token_revoked_email(user, token_name).deliver_later
+ end
+
# Notify the user when at least one of their ssh key has expired today
def ssh_key_expired(user, fingerprints)
return unless user.can?(:receive_notifications)
@@ -109,6 +116,14 @@ class NotificationService
mailer.unknown_sign_in_email(user, ip, time).deliver_later
end
+ # Notify a user when a wrong 2FA OTP has been entered to
+ # try to sign in to their account
+ def two_factor_otp_attempt_failed(user, ip)
+ return unless user.can?(:receive_notifications)
+
+ mailer.two_factor_otp_attempt_failed_email(user, ip).deliver_later
+ end
+
# Notify a user when a new email address is added to the their account
def new_email_address_added(user, email)
return unless user.can?(:receive_notifications)
diff --git a/app/services/onboarding/progress_service.rb b/app/services/onboarding/progress_service.rb
index 66f7f2bc33d..c67669b49ab 100644
--- a/app/services/onboarding/progress_service.rb
+++ b/app/services/onboarding/progress_service.rb
@@ -12,7 +12,7 @@ module Onboarding
def execute(action:)
return unless Onboarding::Progress.not_completed?(namespace_id, action)
- Namespaces::OnboardingProgressWorker.perform_async(namespace_id, action)
+ Onboarding::ProgressWorker.perform_async(namespace_id, action)
end
end
diff --git a/app/services/packages/debian/create_package_file_service.rb b/app/services/packages/debian/create_package_file_service.rb
index 53275fdc9bb..19e68183ea2 100644
--- a/app/services/packages/debian/create_package_file_service.rb
+++ b/app/services/packages/debian/create_package_file_service.rb
@@ -5,18 +5,20 @@ module Packages
class CreatePackageFileService
include ::Packages::FIPS
- def initialize(package, params)
+ def initialize(package:, current_user:, params: {})
@package = package
+ @current_user = current_user
@params = params
end
def execute
raise DisabledError, 'Debian registry is not FIPS compliant' if Gitlab::FIPS.enabled?
raise ArgumentError, "Invalid package" unless package.present?
+ raise ArgumentError, "Invalid user" unless current_user.present?
# Debian package file are first uploaded to incoming with empty metadata,
# and are moved later by Packages::Debian::ProcessChangesService
- package.package_files.create!(
+ package_file = package.package_files.create!(
file: params[:file],
size: params[:file]&.size,
file_name: params[:file_name],
@@ -29,11 +31,17 @@ module Packages
fields: nil
}
)
+
+ if params[:file_name].end_with? '.changes'
+ ::Packages::Debian::ProcessChangesWorker.perform_async(package_file.id, current_user.id)
+ end
+
+ package_file
end
private
- attr_reader :package, :params
+ attr_reader :package, :current_user, :params
end
end
end
diff --git a/app/services/packages/mark_packages_for_destruction_service.rb b/app/services/packages/mark_packages_for_destruction_service.rb
new file mode 100644
index 00000000000..023392cf2d9
--- /dev/null
+++ b/app/services/packages/mark_packages_for_destruction_service.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+module Packages
+ class MarkPackagesForDestructionService
+ include BaseServiceUtility
+
+ BATCH_SIZE = 20
+
+ UNAUTHORIZED_RESPONSE = ServiceResponse.error(
+ message: "You don't have the permission to perform this action",
+ reason: :unauthorized
+ ).freeze
+
+ ERROR_RESPONSE = ServiceResponse.error(
+ message: 'Failed to mark the packages as pending destruction'
+ ).freeze
+
+ SUCCESS_RESPONSE = ServiceResponse.success(
+ message: 'Packages were successfully marked as pending destruction'
+ ).freeze
+
+ # Initialize this service with the given packages and user.
+ #
+ # * `packages`: must be an ActiveRecord relationship.
+ # * `current_user`: an User object. Could be nil.
+ def initialize(packages:, current_user: nil)
+ @packages = packages
+ @current_user = current_user
+ end
+
+ def execute(batch_size: BATCH_SIZE)
+ no_access = false
+ min_batch_size = [batch_size, BATCH_SIZE].min
+
+ @packages.each_batch(of: min_batch_size) do |batched_packages|
+ loaded_packages = batched_packages.including_project_route.to_a
+
+ break no_access = true unless can_destroy_packages?(loaded_packages)
+
+ ::Packages::Package.id_in(loaded_packages.map(&:id))
+ .update_all(status: :pending_destruction)
+
+ sync_maven_metadata(loaded_packages)
+ mark_package_files_for_destruction(loaded_packages)
+ end
+
+ return UNAUTHORIZED_RESPONSE if no_access
+
+ SUCCESS_RESPONSE
+ rescue StandardError
+ ERROR_RESPONSE
+ end
+
+ private
+
+ def mark_package_files_for_destruction(packages)
+ ::Packages::MarkPackageFilesForDestructionWorker.bulk_perform_async_with_contexts(
+ packages,
+ arguments_proc: -> (package) { package.id },
+ context_proc: -> (package) { { project: package.project, user: @current_user } }
+ )
+ end
+
+ def sync_maven_metadata(packages)
+ maven_packages_with_version = packages.select { |pkg| pkg.maven? && pkg.version? }
+ ::Packages::Maven::Metadata::SyncWorker.bulk_perform_async_with_contexts(
+ maven_packages_with_version,
+ arguments_proc: -> (package) { [@current_user.id, package.project_id, package.name] },
+ context_proc: -> (package) { { project: package.project, user: @current_user } }
+ )
+ end
+
+ def can_destroy_packages?(packages)
+ packages.all? do |package|
+ can?(@current_user, :destroy_package, package)
+ end
+ end
+ end
+end
diff --git a/app/services/packages/rpm/parse_package_service.rb b/app/services/packages/rpm/parse_package_service.rb
new file mode 100644
index 00000000000..689a161a81a
--- /dev/null
+++ b/app/services/packages/rpm/parse_package_service.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module Packages
+ module Rpm
+ class ParsePackageService
+ include ::Gitlab::Utils::StrongMemoize
+
+ BUILD_ATTRIBUTES_METHOD_NAMES = %i[changelogs requirements provides].freeze
+ STATIC_ATTRIBUTES = %i[name version release summary description arch
+ license sourcerpm group buildhost packager vendor].freeze
+
+ CHANGELOGS_RPM_KEYS = %i[changelogtext changelogtime].freeze
+ REQUIREMENTS_RPM_KEYS = %i[requirename requireversion requireflags].freeze
+ PROVIDES_RPM_KEYS = %i[providename provideflags provideversion].freeze
+
+ def initialize(package_file)
+ @rpm = RPM::File.new(package_file)
+ end
+
+ def execute
+ raise ArgumentError, 'Unable to parse package' unless valid_package?
+
+ {
+ files: rpm.files || [],
+ epoch: package_tags[:epoch] || '0',
+ changelogs: build_changelogs,
+ requirements: build_requirements,
+ provides: build_provides
+ }.merge(extract_static_attributes)
+ end
+
+ private
+
+ attr_reader :rpm
+
+ def valid_package?
+ rpm.files && package_tags && true
+ rescue RuntimeError
+ # if arr-pm throws an error due to an incorrect file format,
+ # we just want this validation to fail rather than throw an exception
+ false
+ end
+
+ def package_tags
+ strong_memoize(:package_tags) do
+ rpm.tags
+ end
+ end
+
+ def extract_static_attributes
+ STATIC_ATTRIBUTES.each_with_object({}) do |attribute, hash|
+ hash[attribute] = package_tags[attribute]
+ end
+ end
+
+ # Define methods for building RPM attribute data from parsed package
+ # Transform
+ # changelogtime: [123, 234],
+ # changelogname: ["First", "Second"]
+ # changelogtext: ["Work1", "Work2"]
+ # Into
+ # changelog: [
+ # {changelogname: "First", changelogtext: "Work1", changelogtime: 123},
+ # {changelogname: "Second", changelogtext: "Work2", changelogtime: 234}
+ # ]
+ BUILD_ATTRIBUTES_METHOD_NAMES.each do |resource|
+ define_method("build_#{resource}") do
+ resource_keys = self.class.const_get("#{resource.upcase}_RPM_KEYS", false).dup
+ return [] if resource_keys.any? { package_tags[_1].blank? }
+
+ first_attributes = package_tags[resource_keys.first]
+ zipped_data = first_attributes.zip(*resource_keys[1..].map { package_tags[_1] })
+ build_hashes(resource_keys, zipped_data)
+ end
+ end
+
+ def build_hashes(resource_keys, zipped_data)
+ zipped_data.map do |data|
+ resource_keys.zip(data).to_h
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/packages/rpm/repository_metadata/base_builder.rb b/app/services/packages/rpm/repository_metadata/base_builder.rb
index 9d76336d764..2c0a11457ec 100644
--- a/app/services/packages/rpm/repository_metadata/base_builder.rb
+++ b/app/services/packages/rpm/repository_metadata/base_builder.rb
@@ -3,17 +3,43 @@ module Packages
module Rpm
module RepositoryMetadata
class BaseBuilder
+ def initialize(xml: nil, data: {})
+ @xml = Nokogiri::XML(xml) if xml.present?
+ @data = data
+ end
+
def execute
- build_empty_structure
+ return build_empty_structure if xml.blank?
+
+ update_xml_document
+ update_package_count
+ xml.to_xml
end
private
+ attr_reader :xml, :data
+
def build_empty_structure
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
- xml.public_send(self.class::ROOT_TAG, self.class::ROOT_ATTRIBUTES) # rubocop:disable GitlabSecurity/PublicSend
+ xml.method_missing(self.class::ROOT_TAG, self.class::ROOT_ATTRIBUTES)
end.to_xml
end
+
+ def update_xml_document
+ # Add to the root xml element a new package metadata node
+ xml.at(self.class::ROOT_TAG).add_child(build_new_node)
+ end
+
+ def update_package_count
+ packages_count = xml.css("//#{self.class::ROOT_TAG}/package").count
+
+ xml.at(self.class::ROOT_TAG).attributes["packages"].value = packages_count.to_s
+ end
+
+ def build_new_node
+ raise NotImplementedError, "#{self.class} should implement #{__method__}"
+ end
end
end
end
diff --git a/app/services/packages/rpm/repository_metadata/build_primary_xml.rb b/app/services/packages/rpm/repository_metadata/build_primary_xml.rb
index affb41677c2..580bf844a0c 100644
--- a/app/services/packages/rpm/repository_metadata/build_primary_xml.rb
+++ b/app/services/packages/rpm/repository_metadata/build_primary_xml.rb
@@ -9,6 +9,79 @@ module Packages
'xmlns:rpm': 'http://linux.duke.edu/metadata/rpm',
packages: '0'
}.freeze
+
+ # Nodes that have only text without attributes
+ REQUIRED_BASE_ATTRIBUTES = %i[name arch summary description].freeze
+ NOT_REQUIRED_BASE_ATTRIBUTES = %i[url packager].freeze
+ FORMAT_NODE_BASE_ATTRIBUTES = %i[license vendor group buildhost sourcerpm].freeze
+
+ private
+
+ def build_new_node
+ builder = Nokogiri::XML::Builder.new do |xml|
+ xml.package(type: :rpm, 'xmlns:rpm': 'http://linux.duke.edu/metadata/rpm') do
+ build_required_base_attributes(xml)
+ build_not_required_base_attributes(xml)
+ xml.version epoch: data[:epoch], ver: data[:version], rel: data[:release]
+ xml.checksum data[:checksum], type: 'sha256', pkgid: 'YES'
+ xml.size package: data[:packagesize], installed: data[:installedsize], archive: data[:archivesize]
+ xml.time file: data[:filetime], build: data[:buildtime]
+ xml.location href: data[:location] if data[:location].present?
+ build_format_node(xml)
+ end
+ end
+
+ Nokogiri::XML(builder.to_xml).at('package')
+ end
+
+ def build_required_base_attributes(xml)
+ REQUIRED_BASE_ATTRIBUTES.each do |attribute|
+ xml.method_missing(attribute, data[attribute])
+ end
+ end
+
+ def build_not_required_base_attributes(xml)
+ NOT_REQUIRED_BASE_ATTRIBUTES.each do |attribute|
+ xml.method_missing(attribute, data[attribute]) if data[attribute].present?
+ end
+ end
+
+ def build_format_node(xml)
+ xml.format do
+ build_base_format_attributes(xml)
+ build_provides_node(xml)
+ build_requires_node(xml)
+ end
+ end
+
+ def build_base_format_attributes(xml)
+ FORMAT_NODE_BASE_ATTRIBUTES.each do |attribute|
+ xml[:rpm].method_missing(attribute, data[attribute]) if data[attribute].present?
+ end
+ end
+
+ def build_requires_node(xml)
+ xml[:rpm].requires do
+ data[:requirements].each do |requires|
+ xml[:rpm].entry(
+ name: requires[:requirename],
+ flags: requires[:requireflags],
+ ver: requires[:requireversion]
+ )
+ end
+ end
+ end
+
+ def build_provides_node(xml)
+ xml[:rpm].provides do
+ data[:provides].each do |provides|
+ xml[:rpm].entry(
+ name: provides[:providename],
+ flags: provides[:provideflags],
+ ver: provides[:provideversion])
+ end
+ end
+ end
end
end
end
diff --git a/app/services/packages/rpm/repository_metadata/build_repomd_xml.rb b/app/services/packages/rpm/repository_metadata/build_repomd_xml.rb
index c6cfd77815d..84614196254 100644
--- a/app/services/packages/rpm/repository_metadata/build_repomd_xml.rb
+++ b/app/services/packages/rpm/repository_metadata/build_repomd_xml.rb
@@ -9,6 +9,7 @@ module Packages
xmlns: 'http://linux.duke.edu/metadata/repo',
'xmlns:rpm': 'http://linux.duke.edu/metadata/rpm'
}.freeze
+ ALLOWED_DATA_VALUE_KEYS = %i[checksum open-checksum location timestamp size open-size].freeze
# Expected `data` structure
#
@@ -48,9 +49,9 @@ module Packages
end
def build_file_info(info, xml)
- info.each do |key, attributes|
+ info.slice(*ALLOWED_DATA_VALUE_KEYS).each do |key, attributes|
value = attributes.delete(:value)
- xml.public_send(key, value, attributes) # rubocop:disable GitlabSecurity/PublicSend
+ xml.method_missing(key, value, attributes)
end
end
end
diff --git a/app/services/pages_domains/create_acme_order_service.rb b/app/services/pages_domains/create_acme_order_service.rb
index e289a78091b..c600f497fa5 100644
--- a/app/services/pages_domains/create_acme_order_service.rb
+++ b/app/services/pages_domains/create_acme_order_service.rb
@@ -2,9 +2,6 @@
module PagesDomains
class CreateAcmeOrderService
- # elliptic curve algorithm to generate the private key
- ECDSA_CURVE = "prime256v1"
-
attr_reader :pages_domain
def initialize(pages_domain)
@@ -17,12 +14,7 @@ module PagesDomains
challenge = order.new_challenge
- private_key = if Feature.enabled?(:pages_lets_encrypt_ecdsa, pages_domain.project)
- OpenSSL::PKey::EC.generate(ECDSA_CURVE)
- else
- OpenSSL::PKey::RSA.new(4096)
- end
-
+ private_key = OpenSSL::PKey::RSA.new(4096)
saved_order = pages_domain.acme_orders.create!(
url: order.url,
expires_at: order.expires,
diff --git a/app/services/pages_domains/create_service.rb b/app/services/pages_domains/create_service.rb
new file mode 100644
index 00000000000..1f771ca3a05
--- /dev/null
+++ b/app/services/pages_domains/create_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class CreateService < BaseService
+ def execute
+ return unless authorized?
+
+ domain = project.pages_domains.create(params)
+
+ publish_event(domain) if domain.persisted?
+
+ domain
+ end
+
+ private
+
+ def authorized?
+ current_user.can?(:update_pages, project)
+ end
+
+ def publish_event(domain)
+ event = PagesDomainCreatedEvent.new(
+ data: {
+ project_id: project.id,
+ namespace_id: project.namespace_id,
+ root_namespace_id: project.root_namespace.id,
+ domain: domain.domain
+ }
+ )
+
+ Gitlab::EventStore.publish(event)
+ end
+ end
+end
diff --git a/app/services/pages_domains/delete_service.rb b/app/services/pages_domains/delete_service.rb
new file mode 100644
index 00000000000..af69e1845a9
--- /dev/null
+++ b/app/services/pages_domains/delete_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class DeleteService < BaseService
+ def execute(domain)
+ return unless authorized?
+
+ domain.destroy
+
+ publish_event(domain)
+ end
+
+ private
+
+ def authorized?
+ current_user.can?(:update_pages, project)
+ end
+
+ def publish_event(domain)
+ event = PagesDomainDeletedEvent.new(
+ data: {
+ project_id: project.id,
+ namespace_id: project.namespace_id,
+ root_namespace_id: project.root_namespace.id,
+ domain: domain.domain
+ }
+ )
+
+ Gitlab::EventStore.publish(event)
+ end
+ end
+end
diff --git a/app/services/pages_domains/update_service.rb b/app/services/pages_domains/update_service.rb
new file mode 100644
index 00000000000..b038aaa95b6
--- /dev/null
+++ b/app/services/pages_domains/update_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class UpdateService < BaseService
+ def execute(domain)
+ return unless authorized?
+
+ return false unless domain.update(params)
+
+ publish_event(domain)
+
+ true
+ end
+
+ private
+
+ def authorized?
+ current_user.can?(:update_pages, project)
+ end
+
+ def publish_event(domain)
+ event = PagesDomainUpdatedEvent.new(
+ data: {
+ project_id: project.id,
+ namespace_id: project.namespace_id,
+ root_namespace_id: project.root_namespace.id,
+ domain: domain.domain
+ }
+ )
+
+ Gitlab::EventStore.publish(event)
+ end
+ end
+end
diff --git a/app/services/personal_access_tokens/revoke_service.rb b/app/services/personal_access_tokens/revoke_service.rb
index 0275d03bcc9..732da75da3a 100644
--- a/app/services/personal_access_tokens/revoke_service.rb
+++ b/app/services/personal_access_tokens/revoke_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module PersonalAccessTokens
- class RevokeService
+ class RevokeService < BaseService
attr_reader :token, :current_user, :group
def initialize(current_user = nil, token: nil, group: nil )
@@ -15,6 +15,7 @@ module PersonalAccessTokens
if token.revoke!
log_event
+ notification_service.access_token_revoked(token.user, token.name)
ServiceResponse.success(message: success_message)
else
ServiceResponse.error(message: error_message)
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index e6b1b33a82a..ae5aae87a77 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -24,7 +24,7 @@ module Projects
end
def commands(noteable, type)
- return [] unless noteable
+ return [] unless noteable && current_user
QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
end
@@ -33,9 +33,21 @@ module Projects
SnippetsFinder.new(current_user, project: project).execute.select([:id, :title])
end
- def contacts
- Crm::ContactsFinder.new(current_user, group: project.group).execute
- .select([:id, :email, :first_name, :last_name])
+ def contacts(target)
+ available_contacts = Crm::ContactsFinder.new(current_user, group: project.group).execute
+ .select([:id, :email, :first_name, :last_name, :state])
+
+ contact_hashes = available_contacts.as_json
+
+ return contact_hashes unless target.is_a?(Issue)
+
+ ids = target.customer_relations_contacts.ids # rubocop:disable CodeReuse/ActiveRecord
+
+ contact_hashes.each do |hash|
+ hash[:set] = ids.include?(hash['id'])
+ end
+
+ contact_hashes
end
def labels_as_hash(target)
diff --git a/app/services/projects/blame_service.rb b/app/services/projects/blame_service.rb
index 57b913b04e6..58e146e5a32 100644
--- a/app/services/projects/blame_service.rb
+++ b/app/services/projects/blame_service.rb
@@ -27,6 +27,10 @@ module Projects
.page(page)
end
+ def per_page
+ PER_PAGE
+ end
+
private
attr_reader :blob, :commit, :pagination_enabled
@@ -48,10 +52,6 @@ module Projects
page
end
- def per_page
- PER_PAGE
- end
-
def pagination_state(params)
return false if Gitlab::Utils.to_boolean(params[:no_pagination], default: false)
diff --git a/app/services/projects/container_repository/cleanup_tags_base_service.rb b/app/services/projects/container_repository/cleanup_tags_base_service.rb
index 8ea4ae4830a..5393c2c080d 100644
--- a/app/services/projects/container_repository/cleanup_tags_base_service.rb
+++ b/app/services/projects/container_repository/cleanup_tags_base_service.rb
@@ -60,23 +60,6 @@ module Projects
service.execute(container_repository)
end
- def can_destroy?
- return true if container_expiration_policy
-
- can?(current_user, :destroy_container_image, project)
- end
-
- def valid_regex?
- %w[name_regex_delete name_regex name_regex_keep].each do |param_name|
- regex = params[param_name]
- ::Gitlab::UntrustedRegexp.new(regex) unless regex.blank?
- end
- true
- rescue RegexpError => e
- ::Gitlab::ErrorTracking.log_exception(e, project_id: project.id)
- false
- end
-
def older_than
params['older_than']
end
diff --git a/app/services/projects/container_repository/cleanup_tags_service.rb b/app/services/projects/container_repository/cleanup_tags_service.rb
index 285c3e252ef..cf2eb81e5f3 100644
--- a/app/services/projects/container_repository/cleanup_tags_service.rb
+++ b/app/services/projects/container_repository/cleanup_tags_service.rb
@@ -2,101 +2,57 @@
module Projects
module ContainerRepository
- class CleanupTagsService < CleanupTagsBaseService
- def initialize(container_repository:, current_user: nil, params: {})
- super
-
- @params = params.dup
- @counts = { cached_tags_count: 0 }
- end
-
+ class CleanupTagsService < BaseContainerRepositoryService
def execute
return error('access denied') unless can_destroy?
return error('invalid regex') unless valid_regex?
- tags = container_repository.tags
- @counts[:original_size] = tags.size
-
- filter_out_latest!(tags)
- filter_by_name!(tags)
-
- tags = truncate(tags)
- populate_from_cache(tags)
-
- tags = filter_keep_n(tags)
- tags = filter_by_older_than(tags)
-
- @counts[:before_delete_size] = tags.size
-
- delete_tags(tags).merge(@counts).tap do |result|
- result[:deleted_size] = result[:deleted]&.size
-
- result[:status] = :error if @counts[:before_truncate_size] != @counts[:after_truncate_size]
- end
+ cleanup_tags_service_class.new(container_repository: container_repository, current_user: current_user, params: params)
+ .execute
end
private
- def filter_keep_n(tags)
- tags, tags_to_keep = partition_by_keep_n(tags)
-
- cache_tags(tags_to_keep)
-
- tags
- end
-
- def filter_by_older_than(tags)
- tags, tags_to_keep = partition_by_older_than(tags)
-
- cache_tags(tags_to_keep)
-
- tags
+ def cleanup_tags_service_class
+ log_data = {
+ container_repository_id: container_repository.id,
+ container_repository_path: container_repository.path,
+ project_id: project.id
+ }
+
+ if use_gitlab_service?
+ log_info(log_data.merge(gitlab_cleanup_tags_service: true))
+ ::Projects::ContainerRepository::Gitlab::CleanupTagsService
+ else
+ log_info(log_data.merge(third_party_cleanup_tags_service: true))
+ ::Projects::ContainerRepository::ThirdParty::CleanupTagsService
+ end
end
- def pushed_at(tag)
- tag.created_at
+ def use_gitlab_service?
+ container_repository.migrated? &&
+ container_repository.gitlab_api_client.supports_gitlab_api?
end
- def truncate(tags)
- @counts[:before_truncate_size] = tags.size
- @counts[:after_truncate_size] = tags.size
-
- return tags if max_list_size == 0
-
- # truncate the list to make sure that after the #filter_keep_n
- # execution, the resulting list will be max_list_size
- truncated_size = max_list_size + keep_n_as_integer
-
- return tags if tags.size <= truncated_size
+ def can_destroy?
+ return true if container_expiration_policy
- tags = tags.sample(truncated_size)
- @counts[:after_truncate_size] = tags.size
- tags
+ can?(current_user, :destroy_container_image, project)
end
- def populate_from_cache(tags)
- @counts[:cached_tags_count] = cache.populate(tags) if caching_enabled?
- end
-
- def cache_tags(tags)
- cache.insert(tags, older_than_in_seconds) if caching_enabled?
- end
-
- def cache
- strong_memoize(:cache) do
- ::Gitlab::ContainerRepository::Tags::Cache.new(container_repository)
+ def valid_regex?
+ %w[name_regex_delete name_regex name_regex_keep].each do |param_name|
+ regex = params[param_name]
+ ::Gitlab::UntrustedRegexp.new(regex) unless regex.blank?
end
+ true
+ rescue RegexpError => e
+ ::Gitlab::ErrorTracking.log_exception(e, project_id: project.id)
+ false
end
- def caching_enabled?
- result = ::Gitlab::CurrentSettings.current_application_settings.container_registry_expiration_policies_caching &&
- container_expiration_policy &&
- older_than.present?
- !!result
- end
-
- def max_list_size
- ::Gitlab::CurrentSettings.current_application_settings.container_registry_cleanup_tags_service_max_list_size.to_i
+ def container_expiration_policy
+ params['container_expiration_policy']
end
end
end
diff --git a/app/services/projects/container_repository/gitlab/cleanup_tags_service.rb b/app/services/projects/container_repository/gitlab/cleanup_tags_service.rb
index 81bb94c867a..e947e9575e2 100644
--- a/app/services/projects/container_repository/gitlab/cleanup_tags_service.rb
+++ b/app/services/projects/container_repository/gitlab/cleanup_tags_service.rb
@@ -14,9 +14,6 @@ module Projects
end
def execute
- return error('access denied') unless can_destroy?
- return error('invalid regex') unless valid_regex?
-
with_timeout do |start_time, result|
container_repository.each_tags_page(page_size: TAGS_PAGE_SIZE) do |tags|
execute_for_tags(tags, result)
diff --git a/app/services/projects/container_repository/third_party/cleanup_tags_service.rb b/app/services/projects/container_repository/third_party/cleanup_tags_service.rb
new file mode 100644
index 00000000000..c6335629b52
--- /dev/null
+++ b/app/services/projects/container_repository/third_party/cleanup_tags_service.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+module Projects
+ module ContainerRepository
+ module ThirdParty
+ class CleanupTagsService < CleanupTagsBaseService
+ def initialize(container_repository:, current_user: nil, params: {})
+ super
+
+ @params = params.dup
+ @counts = { cached_tags_count: 0 }
+ end
+
+ def execute
+ tags = container_repository.tags
+ @counts[:original_size] = tags.size
+
+ filter_out_latest!(tags)
+ filter_by_name!(tags)
+
+ tags = truncate(tags)
+ populate_from_cache(tags)
+
+ tags = filter_keep_n(tags)
+ tags = filter_by_older_than(tags)
+
+ @counts[:before_delete_size] = tags.size
+
+ delete_tags(tags).merge(@counts).tap do |result|
+ result[:deleted_size] = result[:deleted]&.size
+
+ result[:status] = :error if @counts[:before_truncate_size] != @counts[:after_truncate_size]
+ end
+ end
+
+ private
+
+ def filter_keep_n(tags)
+ tags, tags_to_keep = partition_by_keep_n(tags)
+
+ cache_tags(tags_to_keep)
+
+ tags
+ end
+
+ def filter_by_older_than(tags)
+ tags, tags_to_keep = partition_by_older_than(tags)
+
+ cache_tags(tags_to_keep)
+
+ tags
+ end
+
+ def pushed_at(tag)
+ tag.created_at
+ end
+
+ def truncate(tags)
+ @counts[:before_truncate_size] = tags.size
+ @counts[:after_truncate_size] = tags.size
+
+ return tags if max_list_size == 0
+
+ # truncate the list to make sure that after the #filter_keep_n
+ # execution, the resulting list will be max_list_size
+ truncated_size = max_list_size + keep_n_as_integer
+
+ return tags if tags.size <= truncated_size
+
+ tags = tags.sample(truncated_size)
+ @counts[:after_truncate_size] = tags.size
+ tags
+ end
+
+ def populate_from_cache(tags)
+ @counts[:cached_tags_count] = cache.populate(tags) if caching_enabled?
+ end
+
+ def cache_tags(tags)
+ cache.insert(tags, older_than_in_seconds) if caching_enabled?
+ end
+
+ def cache
+ strong_memoize(:cache) do
+ ::Gitlab::ContainerRepository::Tags::Cache.new(container_repository)
+ end
+ end
+
+ def caching_enabled?
+ result = current_application_settings.container_registry_expiration_policies_caching &&
+ container_expiration_policy &&
+ older_than.present?
+ !!result
+ end
+
+ def max_list_size
+ current_application_settings.container_registry_cleanup_tags_service_max_list_size.to_i
+ end
+
+ def current_application_settings
+ ::Gitlab::CurrentSettings.current_application_settings
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index f1525ed9763..4e883f682fb 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -134,7 +134,7 @@ module Projects
destroy_ci_records!
destroy_mr_diff_relations!
- destroy_merge_request_diffs! if ::Feature.enabled?(:extract_mr_diff_deletions)
+ destroy_merge_request_diffs!
# Rails attempts to load all related records into memory before
# destroying: https://github.com/rails/rails/issues/22510
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 4979af6dfe1..de7ede4eabf 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -64,7 +64,11 @@ module Projects
def add_repository_to_project
if project.external_import? && !unknown_url?
begin
- Gitlab::UrlBlocker.validate!(project.import_url, ports: Project::VALID_IMPORT_PORTS)
+ Gitlab::UrlBlocker.validate!(
+ project.import_url,
+ schemes: Project::VALID_IMPORT_PROTOCOLS,
+ ports: Project::VALID_IMPORT_PORTS
+ )
rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise e, s_("ImportProjects|Blocked import URL: %{message}") % { message: e.message }
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index d757b0700b9..f9a2c825608 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -122,7 +122,7 @@ module Projects
update_pending_builds if runners_settings_toggled?
- publish_event
+ publish_events
end
def after_rename_service(project)
@@ -212,7 +212,13 @@ module Projects
end
end
- def publish_event
+ def publish_events
+ publish_project_archived_event
+ publish_project_attributed_changed_event
+ publish_project_features_changed_event
+ end
+
+ def publish_project_archived_event
return unless project.archived_previously_changed?
event = Projects::ProjectArchivedEvent.new(data: {
@@ -223,6 +229,36 @@ module Projects
Gitlab::EventStore.publish(event)
end
+
+ def publish_project_attributed_changed_event
+ changes = @project.previous_changes
+
+ return if changes.blank?
+
+ event = Projects::ProjectAttributesChangedEvent.new(data: {
+ project_id: @project.id,
+ namespace_id: @project.namespace_id,
+ root_namespace_id: @project.root_namespace.id,
+ attributes: changes.keys
+ })
+
+ Gitlab::EventStore.publish(event)
+ end
+
+ def publish_project_features_changed_event
+ changes = @project.project_feature.previous_changes
+
+ return if changes.blank?
+
+ event = Projects::ProjectFeaturesChangedEvent.new(data: {
+ project_id: @project.id,
+ namespace_id: @project.namespace_id,
+ root_namespace_id: @project.root_namespace.id,
+ features: changes.keys
+ })
+
+ Gitlab::EventStore.publish(event)
+ end
end
end
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index b7df201824a..01dd6323d94 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -3,10 +3,10 @@
module Releases
class CreateService < Releases::BaseService
def execute
- return error('Access Denied', 403) unless allowed?
- return error('You are not allowed to create this tag as it is protected.', 403) unless can_create_tag?
- return error('Release already exists', 409) if release
- return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any?
+ return error(_('Access Denied'), 403) unless allowed?
+ return error(_('You are not allowed to create this tag as it is protected.'), 403) unless can_create_tag?
+ return error(_('Release already exists'), 409) if release
+ return error(format(_("Milestone(s) not found: %{milestones}"), milestones: inexistent_milestones.join(', ')), 400) if inexistent_milestones.any? # rubocop:disable Layout/LineLength
# should be found before the creation of new tag
# because tag creation can spawn new pipeline
diff --git a/app/services/releases/destroy_service.rb b/app/services/releases/destroy_service.rb
index 8abf9308689..ff2b3a7bd18 100644
--- a/app/services/releases/destroy_service.rb
+++ b/app/services/releases/destroy_service.rb
@@ -3,8 +3,8 @@
module Releases
class DestroyService < Releases::BaseService
def execute
- return error('Release does not exist', 404) unless release
- return error('Access Denied', 403) unless allowed?
+ return error(_('Release does not exist'), 404) unless release
+ return error(_('Access Denied'), 403) unless allowed?
if release.destroy
success(tag: existing_tag, release: release)
diff --git a/app/services/releases/update_service.rb b/app/services/releases/update_service.rb
index 2e0a2f8488a..b9b2aba9805 100644
--- a/app/services/releases/update_service.rb
+++ b/app/services/releases/update_service.rb
@@ -31,11 +31,11 @@ module Releases
private
def validate
- return error('Tag does not exist', 404) unless existing_tag
- return error('Release does not exist', 404) unless release
- return error('Access Denied', 403) unless allowed?
- return error('params is empty', 400) if empty_params?
- return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any?
+ return error(_('Tag does not exist'), 404) unless existing_tag
+ return error(_('Release does not exist'), 404) unless release
+ return error(_('Access Denied'), 403) unless allowed?
+ return error(_('params is empty'), 400) if empty_params?
+ return error(format(_("Milestone(s) not found: %{milestones}"), milestones: inexistent_milestones.join(', ')), 400) if inexistent_milestones.any? # rubocop:disable Layout/LineLength
end
def allowed?
diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb
index eed03ba22fe..b8a210c0a95 100644
--- a/app/services/resource_access_tokens/create_service.rb
+++ b/app/services/resource_access_tokens/create_service.rb
@@ -13,7 +13,6 @@ module ResourceAccessTokens
return error("User does not have permission to create #{resource_type} access token") unless has_permission_to_create?
access_level = params[:access_level] || Gitlab::Access::MAINTAINER
- return error("Could not provision owner access to project access token") if do_not_allow_owner_access_level_for_project_bot?(access_level)
user = create_user
@@ -48,9 +47,9 @@ module ResourceAccessTokens
end
def create_user
- # Even project maintainers can create project access tokens, which in turn
+ # Even project maintainers/owners can create project access tokens, which in turn
# creates a bot user, and so it becomes necessary to have `skip_authorization: true`
- # since someone like a project maintainer does not inherently have the ability
+ # since someone like a project maintainer/owner does not inherently have the ability
# to create a new user in the system.
::Users::AuthorizedCreateService.new(current_user, default_user_params).execute
@@ -108,7 +107,7 @@ module ResourceAccessTokens
end
def create_membership(resource, user, access_level)
- resource.add_member(user, access_level, expires_at: params[:expires_at])
+ resource.add_member(user, access_level, current_user: current_user, expires_at: params[:expires_at])
end
def log_event(token)
@@ -122,12 +121,6 @@ module ResourceAccessTokens
def success(access_token)
ServiceResponse.success(payload: { access_token: access_token })
end
-
- def do_not_allow_owner_access_level_for_project_bot?(access_level)
- resource.is_a?(Project) &&
- access_level == Gitlab::Access::OWNER &&
- !current_user.can?(:manage_owners, resource)
- end
end
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index cea7fc5769e..f38522b9764 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -102,6 +102,16 @@ class SearchService
end
end
+ def show_elasticsearch_tabs?
+ # overridden in EE
+ false
+ end
+
+ def show_epics?
+ # overridden in EE
+ false
+ end
+
private
def page
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index ddb20a835e1..0fa1bb96b13 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -106,6 +106,8 @@ module Users
def build_user_params_for_non_admin
@user_params = params.slice(*signup_params)
+ # if skip_confirmation is set to `true`, devise will set confirmed_at
+ # see: https://github.com/heartcombo/devise/blob/8593801130f2df94a50863b5db535c272b00efe1/lib/devise/models/confirmable.rb#L156
@user_params[:skip_confirmation] = skip_user_confirmation_email_from_setting if assign_skip_confirmation_from_settings?
@user_params[:name] = fallback_name if use_fallback_name?
end
diff --git a/app/services/users/dismiss_namespace_callout_service.rb b/app/services/users/dismiss_namespace_callout_service.rb
deleted file mode 100644
index 51261a93e20..00000000000
--- a/app/services/users/dismiss_namespace_callout_service.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module Users
- class DismissNamespaceCalloutService < DismissCalloutService
- private
-
- def callout
- current_user.find_or_initialize_namespace_callout(params[:feature_name], params[:namespace_id])
- end
- end
-end
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index fe61335f3ed..b1ffd006795 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -62,12 +62,12 @@ module Users
# Updates the list of authorizations for the current user.
#
- # remove - The IDs of the authorization rows to remove.
+ # remove - The project IDs of the authorization rows to remove.
# add - Rows to insert in the form `[{ user_id: user_id, project_id: project_id, access_level: access_level}, ...]`
def update_authorizations(remove = [], add = [])
log_refresh_details(remove, add)
- user.remove_project_authorizations(remove) if remove.any?
+ ProjectAuthorization.delete_all_in_batches_for_user(user: user, project_ids: remove) if remove.any?
ProjectAuthorization.insert_all_in_batches(add) if add.any?
# Since we batch insert authorization rows, Rails' associations may get
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index cd2c7402713..e5e5e375198 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -194,7 +194,8 @@ class WebHookService
headers = {
'Content-Type' => 'application/json',
'User-Agent' => "GitLab/#{Gitlab::VERSION}",
- Gitlab::WebHooks::GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name)
+ Gitlab::WebHooks::GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name),
+ Gitlab::WebHooks::GITLAB_INSTANCE_HEADER => Gitlab.config.gitlab.base_url
}
headers['X-Gitlab-Token'] = Gitlab::Utils.remove_line_breaks(hook.token) if hook.token.present?
diff --git a/app/services/web_hooks/log_execution_service.rb b/app/services/web_hooks/log_execution_service.rb
index 5be8aee3ae8..1a40c877bda 100644
--- a/app/services/web_hooks/log_execution_service.rb
+++ b/app/services/web_hooks/log_execution_service.rb
@@ -17,7 +17,7 @@ module WebHooks
end
def execute
- update_hook_failure_state
+ update_hook_failure_state if WebHook.web_hooks_disable_failed?(hook)
log_execution
end
diff --git a/app/services/work_items/create_service.rb b/app/services/work_items/create_service.rb
index c2ceb701a2f..ebda043e873 100644
--- a/app/services/work_items/create_service.rb
+++ b/app/services/work_items/create_service.rb
@@ -2,7 +2,6 @@
module WorkItems
class CreateService < Issues::CreateService
- include ::Services::ReturnServiceResponses
include WidgetableService
def initialize(project:, current_user: nil, params: {}, spam_params:, widget_params: {})
@@ -17,11 +16,10 @@ module WorkItems
end
def execute
- unless @current_user.can?(:create_work_item, @project)
- return error(_('Operation not allowed'), :forbidden)
- end
+ result = super
+ return result if result.error?
- work_item = super
+ work_item = result[:issue]
if work_item.valid?
success(payload(work_item))
@@ -43,6 +41,10 @@ module WorkItems
private
+ def authorization_action
+ :create_work_item
+ end
+
def payload(work_item)
{ work_item: work_item }
end
diff --git a/app/services/work_items/update_service.rb b/app/services/work_items/update_service.rb
index 2deb8c82741..1351445f6f3 100644
--- a/app/services/work_items/update_service.rb
+++ b/app/services/work_items/update_service.rb
@@ -26,6 +26,17 @@ module WorkItems
private
+ def prepare_update_params(work_item)
+ execute_widgets(
+ work_item: work_item,
+ callback: :prepare_update_params,
+ widget_params: @widget_params,
+ service_params: params
+ )
+
+ super
+ end
+
def before_update(work_item, skip_spam_check: false)
execute_widgets(work_item: work_item, callback: :before_update_callback, widget_params: @widget_params)
@@ -38,7 +49,7 @@ module WorkItems
super
end
- def after_update(work_item)
+ def after_update(work_item, old_associations)
super
GraphqlTriggers.issuable_title_updated(work_item) if work_item.previous_changes.key?(:title)
@@ -47,5 +58,13 @@ module WorkItems
def payload(work_item)
{ work_item: work_item }
end
+
+ def handle_label_changes(issuable, old_labels)
+ return false unless super
+
+ Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter.track_work_item_labels_changed_action(
+ author: current_user
+ )
+ end
end
end
diff --git a/app/services/work_items/widgets/base_service.rb b/app/services/work_items/widgets/base_service.rb
index 37ed2bf4b05..1ff03a09f9f 100644
--- a/app/services/work_items/widgets/base_service.rb
+++ b/app/services/work_items/widgets/base_service.rb
@@ -5,12 +5,13 @@ module WorkItems
class BaseService < ::BaseService
WidgetError = Class.new(StandardError)
- attr_reader :widget, :work_item, :current_user
+ attr_reader :widget, :work_item, :current_user, :service_params
- def initialize(widget:, current_user:)
+ def initialize(widget:, current_user:, service_params: {})
@widget = widget
@work_item = widget.work_item
@current_user = current_user
+ @service_params = service_params
end
private
diff --git a/app/services/work_items/widgets/labels_service/update_service.rb b/app/services/work_items/widgets/labels_service/update_service.rb
new file mode 100644
index 00000000000..f00ea5c95ca
--- /dev/null
+++ b/app/services/work_items/widgets/labels_service/update_service.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Widgets
+ module LabelsService
+ class UpdateService < WorkItems::Widgets::BaseService
+ def prepare_update_params(params: {})
+ return if params.blank?
+
+ service_params.merge!(params.slice(:add_label_ids, :remove_label_ids))
+ end
+ end
+ end
+ end
+end