summaryrefslogtreecommitdiff
path: root/app/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/auto_merge/merge_when_pipeline_succeeds_service.rb2
-rw-r--r--app/services/boards/lists/list_service.rb2
-rw-r--r--app/services/branches/create_service.rb39
-rw-r--r--app/services/branches/delete_merged_service.rb34
-rw-r--r--app/services/branches/delete_service.rb32
-rw-r--r--app/services/branches/validate_new_service.rb31
-rw-r--r--app/services/ci/archive_trace_service.rb6
-rw-r--r--app/services/ci/create_pipeline_service.rb5
-rw-r--r--app/services/ci/generate_exposed_artifacts_report_service.rb2
-rw-r--r--app/services/ci/prepare_build_service.rb2
-rw-r--r--app/services/ci/process_pipeline_service.rb12
-rw-r--r--app/services/ci/register_job_service.rb4
-rw-r--r--app/services/ci/retry_pipeline_service.rb16
-rw-r--r--app/services/clusters/applications/base_helm_service.rb13
-rw-r--r--app/services/clusters/applications/ingress_modsecurity_usage_service.rb69
-rw-r--r--app/services/clusters/aws/authorize_role_service.rb49
-rw-r--r--app/services/clusters/aws/fetch_credentials_service.rb28
-rw-r--r--app/services/clusters/aws/proxy_service.rb134
-rw-r--r--app/services/clusters/cleanup/app_service.rb33
-rw-r--r--app/services/clusters/cleanup/base_service.rb43
-rw-r--r--app/services/clusters/cleanup/project_namespace_service.rb44
-rw-r--r--app/services/clusters/cleanup/service_account_service.rb27
-rw-r--r--app/services/clusters/kubernetes.rb (renamed from app/services/clusters/kubernetes/kubernetes.rb)3
-rw-r--r--app/services/clusters/kubernetes/create_or_update_service_account_service.rb41
-rw-r--r--app/services/cohorts_service.rb2
-rw-r--r--app/services/commits/commit_patch_service.rb2
-rw-r--r--app/services/commits/create_service.rb2
-rw-r--r--app/services/concerns/users/participable_service.rb3
-rw-r--r--app/services/create_branch_service.rb38
-rw-r--r--app/services/create_snippet_service.rb6
-rw-r--r--app/services/delete_branch_service.rb30
-rw-r--r--app/services/delete_merged_branches_service.rb32
-rw-r--r--app/services/deployments/after_create_service.rb7
-rw-r--r--app/services/deployments/create_service.rb17
-rw-r--r--app/services/deployments/update_service.rb17
-rw-r--r--app/services/environments/reset_auto_stop_service.rb22
-rw-r--r--app/services/error_tracking/list_issues_service.rb15
-rw-r--r--app/services/git/base_hooks_service.rb24
-rw-r--r--app/services/issuable/bulk_update_service.rb20
-rw-r--r--app/services/issuable/clone/attributes_rewriter.rb8
-rw-r--r--app/services/issuable/common_system_notes_service.rb26
-rw-r--r--app/services/issuable_base_service.rb28
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/services/issues/duplicate_service.rb2
-rw-r--r--app/services/issues/zoom_link_service.rb50
-rw-r--r--app/services/merge_requests/create_from_issue_service.rb2
-rw-r--r--app/services/merge_requests/merge_service.rb4
-rw-r--r--app/services/merge_requests/refresh_service.rb2
-rw-r--r--app/services/merge_requests/update_service.rb2
-rw-r--r--app/services/metrics/dashboard/base_embed_service.rb2
-rw-r--r--app/services/metrics/dashboard/custom_metric_embed_service.rb2
-rw-r--r--app/services/metrics/dashboard/grafana_metric_embed_service.rb2
-rw-r--r--app/services/metrics/dashboard/pod_dashboard_service.rb10
-rw-r--r--app/services/metrics/dashboard/predefined_dashboard_service.rb45
-rw-r--r--app/services/metrics/dashboard/system_dashboard_service.rb37
-rw-r--r--app/services/metrics/sample_metrics_service.rb26
-rw-r--r--app/services/notes/base_service.rb2
-rw-r--r--app/services/notes/build_service.rb2
-rw-r--r--app/services/notes/create_service.rb12
-rw-r--r--app/services/notes/update_service.rb6
-rw-r--r--app/services/notification_service.rb8
-rw-r--r--app/services/pages/delete_service.rb10
-rw-r--r--app/services/projects/container_repository/cleanup_tags_service.rb4
-rw-r--r--app/services/projects/container_repository/delete_tags_service.rb34
-rw-r--r--app/services/projects/destroy_service.rb7
-rw-r--r--app/services/projects/fork_service.rb18
-rw-r--r--app/services/projects/hashed_storage/base_repository_service.rb31
-rw-r--r--app/services/projects/hashed_storage/migrate_repository_service.rb6
-rw-r--r--app/services/projects/hashed_storage/rollback_repository_service.rb6
-rw-r--r--app/services/projects/import_service.rb4
-rw-r--r--app/services/projects/overwrite_project_service.rb4
-rw-r--r--app/services/projects/unlink_fork_service.rb61
-rw-r--r--app/services/projects/update_service.rb7
-rw-r--r--app/services/prometheus/proxy_variable_substitution_service.rb51
-rw-r--r--app/services/repair_ldap_blocked_user_service.rb19
-rw-r--r--app/services/submit_usage_ping_service.rb2
-rw-r--r--app/services/todo_service.rb13
-rw-r--r--app/services/update_snippet_service.rb8
-rw-r--r--app/services/users/build_service.rb2
-rw-r--r--app/services/users/repair_ldap_blocked_service.rb21
-rw-r--r--app/services/validate_new_branch_service.rb21
-rw-r--r--app/services/web_hook_service.rb3
82 files changed, 1024 insertions, 496 deletions
diff --git a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
index 6a33ec071db..7c0e9228b28 100644
--- a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
+++ b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
@@ -11,7 +11,7 @@ module AutoMerge
end
def process(merge_request)
- return unless merge_request.actual_head_pipeline&.success?
+ return unless merge_request.actual_head_pipeline_success?
return unless merge_request.mergeable?
merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params)
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
index 82cba1b68c4..c96ea970943 100644
--- a/app/services/boards/lists/list_service.rb
+++ b/app/services/boards/lists/list_service.rb
@@ -6,7 +6,7 @@ module Boards
def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
- board.lists.preload_associations
+ board.lists.preload_associated_models
end
end
end
diff --git a/app/services/branches/create_service.rb b/app/services/branches/create_service.rb
new file mode 100644
index 00000000000..c8afd97e6bf
--- /dev/null
+++ b/app/services/branches/create_service.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Branches
+ class CreateService < BaseService
+ def execute(branch_name, ref, create_master_if_empty: true)
+ create_master_branch if create_master_if_empty && project.empty_repo?
+
+ result = ::Branches::ValidateNewService.new(project).execute(branch_name)
+
+ return result if result[:status] == :error
+
+ new_branch = repository.add_branch(current_user, branch_name, ref)
+
+ if new_branch
+ success(new_branch)
+ else
+ error("Invalid reference name: #{branch_name}")
+ end
+ rescue Gitlab::Git::PreReceiveError => ex
+ error(ex.message)
+ end
+
+ def success(branch)
+ super().merge(branch: branch)
+ end
+
+ private
+
+ def create_master_branch
+ project.repository.create_file(
+ current_user,
+ '/README.md',
+ '',
+ message: 'Add README.md',
+ branch_name: 'master'
+ )
+ end
+ end
+end
diff --git a/app/services/branches/delete_merged_service.rb b/app/services/branches/delete_merged_service.rb
new file mode 100644
index 00000000000..9fd5964bf94
--- /dev/null
+++ b/app/services/branches/delete_merged_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Branches
+ class DeleteMergedService < BaseService
+ def async_execute
+ DeleteMergedBranchesWorker.perform_async(project.id, current_user.id)
+ end
+
+ def execute
+ raise Gitlab::Access::AccessDeniedError unless can?(current_user, :push_code, project)
+
+ branches = project.repository.merged_branch_names
+ # Prevent deletion of branches relevant to open merge requests
+ branches -= merge_request_branch_names
+ # Prevent deletion of protected branches
+ branches = branches.reject { |branch| ProtectedBranch.protected?(project, branch) }
+
+ branches.each do |branch|
+ ::Branches::DeleteService.new(project, current_user).execute(branch)
+ end
+ end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def merge_request_branch_names
+ # reorder(nil) is necessary for SELECT DISTINCT because default scope adds an ORDER BY
+ source_names = project.origin_merge_requests.opened.reorder(nil).distinct.pluck(:source_branch)
+ target_names = project.merge_requests.opened.reorder(nil).distinct.pluck(:target_branch)
+ (source_names + target_names).uniq
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/services/branches/delete_service.rb b/app/services/branches/delete_service.rb
new file mode 100644
index 00000000000..ca2b4556b58
--- /dev/null
+++ b/app/services/branches/delete_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Branches
+ class DeleteService < BaseService
+ def execute(branch_name)
+ repository = project.repository
+ branch = repository.find_branch(branch_name)
+
+ unless current_user.can?(:push_code, project)
+ return ServiceResponse.error(
+ message: 'You dont have push access to repo',
+ http_status: 405)
+ end
+
+ unless branch
+ return ServiceResponse.error(
+ message: 'No such branch',
+ http_status: 404)
+ end
+
+ if repository.rm_branch(current_user, branch_name)
+ ServiceResponse.success(message: 'Branch was deleted')
+ else
+ ServiceResponse.error(
+ message: 'Failed to remove branch',
+ http_status: 400)
+ end
+ rescue Gitlab::Git::PreReceiveError => ex
+ ServiceResponse.error(message: ex.message, http_status: 400)
+ end
+ end
+end
diff --git a/app/services/branches/validate_new_service.rb b/app/services/branches/validate_new_service.rb
new file mode 100644
index 00000000000..e45183d160f
--- /dev/null
+++ b/app/services/branches/validate_new_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Branches
+ class ValidateNewService < BaseService
+ def initialize(project)
+ @project = project
+ end
+
+ def execute(branch_name, force: false)
+ return error('Branch name is invalid') unless valid_name?(branch_name)
+
+ if branch_exist?(branch_name) && !force
+ return error('Branch already exists')
+ end
+
+ success
+ rescue Gitlab::Git::PreReceiveError => ex
+ error(ex.message)
+ end
+
+ private
+
+ def valid_name?(branch_name)
+ Gitlab::GitRefValidator.validate(branch_name)
+ end
+
+ def branch_exist?(branch_name)
+ project.repository.branch_exists?(branch_name)
+ end
+ end
+end
diff --git a/app/services/ci/archive_trace_service.rb b/app/services/ci/archive_trace_service.rb
index 8fad9e9c869..f143736ddc1 100644
--- a/app/services/ci/archive_trace_service.rb
+++ b/app/services/ci/archive_trace_service.rb
@@ -46,10 +46,10 @@ module Ci
message: "Failed to archive trace. message: #{error.message}.",
job_id: job.id)
- Gitlab::Sentry
- .track_exception(error,
+ Gitlab::ErrorTracking
+ .track_and_raise_for_dev_exception(error,
issue_url: 'https://gitlab.com/gitlab-org/gitlab-foss/issues/51502',
- extra: { job_id: job.id })
+ job_id: job.id )
end
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 5778a48bce6..ce3a9eb0772 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -16,6 +16,7 @@ module Ci
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
Gitlab::Ci::Pipeline::Chain::Seed,
Gitlab::Ci::Pipeline::Chain::Limit::Size,
+ Gitlab::Ci::Pipeline::Chain::Validate::External,
Gitlab::Ci::Pipeline::Chain::Populate,
Gitlab::Ci::Pipeline::Chain::Create,
Gitlab::Ci::Pipeline::Chain::Limit::Activity,
@@ -57,7 +58,9 @@ module Ci
cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
pipeline_created_counter.increment(source: source)
- pipeline.process!
+ Ci::ProcessPipelineService
+ .new(pipeline)
+ .execute
end
end
diff --git a/app/services/ci/generate_exposed_artifacts_report_service.rb b/app/services/ci/generate_exposed_artifacts_report_service.rb
index b9bf580bcbc..1dbcd192279 100644
--- a/app/services/ci/generate_exposed_artifacts_report_service.rb
+++ b/app/services/ci/generate_exposed_artifacts_report_service.rb
@@ -15,7 +15,7 @@ module Ci
data: data
}
rescue => e
- Gitlab::Sentry.track_acceptable_exception(e, extra: { project_id: project.id })
+ Gitlab::ErrorTracking.track_exception(e, project_id: project.id)
{
status: :error,
key: key(base_pipeline, head_pipeline),
diff --git a/app/services/ci/prepare_build_service.rb b/app/services/ci/prepare_build_service.rb
index 3722faeb020..5d024c45e5f 100644
--- a/app/services/ci/prepare_build_service.rb
+++ b/app/services/ci/prepare_build_service.rb
@@ -13,7 +13,7 @@ module Ci
build.enqueue!
rescue => e
- Gitlab::Sentry.track_acceptable_exception(e, extra: { build_id: build.id })
+ Gitlab::ErrorTracking.track_exception(e, build_id: build.id)
build.drop(:unmet_prerequisites)
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 039670f58c8..f33cbf7ab29 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -1,14 +1,16 @@
# frozen_string_literal: true
module Ci
- class ProcessPipelineService < BaseService
+ class ProcessPipelineService
include Gitlab::Utils::StrongMemoize
attr_reader :pipeline
- def execute(pipeline, trigger_build_ids = nil)
+ def initialize(pipeline)
@pipeline = pipeline
+ end
+ def execute(trigger_build_ids = nil)
update_retried
success = process_stages_without_needs
@@ -72,7 +74,7 @@ module Ci
def process_build(build, current_status)
Gitlab::OptimisticLocking.retry_lock(build) do |subject|
- Ci::ProcessBuildService.new(project, @user)
+ Ci::ProcessBuildService.new(project, build.user)
.execute(subject, current_status)
end
end
@@ -129,5 +131,9 @@ module Ci
.update_all(retried: true) if latest_statuses.any?
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def project
+ pipeline.project
+ end
end
end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 30e2a66e04a..57c0cdd0602 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -128,13 +128,13 @@ module Ci
end
def track_exception_for_build(ex, build)
- Gitlab::Sentry.track_acceptable_exception(ex, extra: {
+ Gitlab::ErrorTracking.track_exception(ex,
build_id: build.id,
build_name: build.name,
build_stage: build.stage,
pipeline_id: build.pipeline_id,
project_id: build.project_id
- })
+ )
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb
index 42a13367a99..7d01de9ee68 100644
--- a/app/services/ci/retry_pipeline_service.rb
+++ b/app/services/ci/retry_pipeline_service.rb
@@ -9,13 +9,23 @@ module Ci
raise Gitlab::Access::AccessDeniedError
end
- pipeline.retryable_builds.find_each do |build|
+ needs = Set.new
+
+ pipeline.retryable_builds.preload_needs.find_each do |build|
next unless can?(current_user, :update_build, build)
Ci::RetryBuildService.new(project, current_user)
.reprocess!(build)
+
+ needs += build.needs.map(&:name)
end
+ # In a DAG, the dependencies may have already completed. Figure out
+ # which builds have succeeded and use them to update the pipeline. If we don't
+ # do this, then builds will be stuck in the created state since their dependencies
+ # will never run.
+ completed_build_ids = pipeline.find_successful_build_ids_by_names(needs) if needs.any?
+
pipeline.builds.latest.skipped.find_each do |skipped|
retry_optimistic_lock(skipped) { |build| build.process }
end
@@ -24,7 +34,9 @@ module Ci
.new(project, current_user)
.close_all(pipeline)
- pipeline.process!
+ Ci::ProcessPipelineService
+ .new(pipeline)
+ .execute(completed_build_ids)
end
end
end
diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb
index 3e7f55f0c63..57bc8bc0d9b 100644
--- a/app/services/clusters/applications/base_helm_service.rb
+++ b/app/services/clusters/applications/base_helm_service.rb
@@ -21,14 +21,7 @@ module Clusters
group_ids: app.cluster.group_ids
}
- logger_meta = meta.merge(
- exception: error.class.name,
- message: error.message,
- backtrace: Gitlab::Profiler.clean_backtrace(error.backtrace)
- )
-
- logger.error(logger_meta)
- Gitlab::Sentry.track_acceptable_exception(error, extra: meta)
+ Gitlab::ErrorTracking.track_exception(error, meta)
end
def log_event(event)
@@ -68,8 +61,8 @@ module Clusters
@update_command ||= app.update_command
end
- def upgrade_command(new_values = "")
- app.upgrade_command(new_values)
+ def patch_command(new_values = "")
+ app.patch_command(new_values)
end
end
end
diff --git a/app/services/clusters/applications/ingress_modsecurity_usage_service.rb b/app/services/clusters/applications/ingress_modsecurity_usage_service.rb
new file mode 100644
index 00000000000..4aac8bb3cbd
--- /dev/null
+++ b/app/services/clusters/applications/ingress_modsecurity_usage_service.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+# rubocop: disable CodeReuse/ActiveRecord
+module Clusters
+ module Applications
+ ##
+ # This service measures usage of the Modsecurity Web Application Firewall across the entire
+ # instance's deployed environments.
+ #
+ # The default configuration is`AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE=DetectionOnly` so we
+ # measure non-default values via definition of either ci_variables or ci_pipeline_variables.
+ # Since both these values are encrypted, we must decrypt and count them in memory.
+ #
+ # NOTE: this service is an approximation as it does not yet take into account `environment_scope` or `ci_group_variables`.
+ ##
+ class IngressModsecurityUsageService
+ ADO_MODSEC_KEY = "AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE"
+
+ def initialize(blocking_count: 0, disabled_count: 0)
+ @blocking_count = blocking_count
+ @disabled_count = disabled_count
+ end
+
+ def execute
+ conditions = -> { merge(::Environment.available).merge(::Deployment.success).where(key: ADO_MODSEC_KEY) }
+
+ ci_pipeline_var_enabled =
+ ::Ci::PipelineVariable
+ .joins(pipeline: { environments: :last_visible_deployment })
+ .merge(conditions)
+ .order('deployments.environment_id, deployments.id DESC')
+
+ ci_var_enabled =
+ ::Ci::Variable
+ .joins(project: { environments: :last_visible_deployment })
+ .merge(conditions)
+ .merge(
+ # Give priority to pipeline variables by excluding from dataset
+ ::Ci::Variable.joins(project: :environments).where.not(
+ environments: { id: ci_pipeline_var_enabled.select('DISTINCT ON (deployments.environment_id) deployments.environment_id') }
+ )
+ ).select('DISTINCT ON (deployments.environment_id) ci_variables.*')
+
+ sum_modsec_config_counts(
+ ci_pipeline_var_enabled.select('DISTINCT ON (deployments.environment_id) ci_pipeline_variables.*')
+ )
+ sum_modsec_config_counts(ci_var_enabled)
+
+ {
+ ingress_modsecurity_blocking: @blocking_count,
+ ingress_modsecurity_disabled: @disabled_count
+ }
+ end
+
+ private
+
+ # These are encrypted so we must decrypt and count in memory
+ def sum_modsec_config_counts(dataset)
+ dataset.each do |var|
+ case var.value
+ when "On" then @blocking_count += 1
+ when "Off" then @disabled_count += 1
+ # `else` could be default or any unsupported user input
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb
new file mode 100644
index 00000000000..6eafce0597e
--- /dev/null
+++ b/app/services/clusters/aws/authorize_role_service.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Aws
+ class AuthorizeRoleService
+ attr_reader :user
+
+ Response = Struct.new(:status, :body)
+
+ ERRORS = [
+ ActiveRecord::RecordInvalid,
+ Clusters::Aws::FetchCredentialsService::MissingRoleError,
+ ::Aws::Errors::MissingCredentialsError,
+ ::Aws::STS::Errors::ServiceError
+ ].freeze
+
+ def initialize(user, params:)
+ @user = user
+ @params = params
+ end
+
+ def execute
+ @role = create_or_update_role!
+
+ Response.new(:ok, credentials)
+ rescue *ERRORS
+ Response.new(:unprocessable_entity, {})
+ end
+
+ private
+
+ attr_reader :role, :params
+
+ def create_or_update_role!
+ if role = user.aws_role
+ role.update!(params)
+
+ role
+ else
+ user.create_aws_role!(params)
+ end
+ end
+
+ def credentials
+ Clusters::Aws::FetchCredentialsService.new(role).execute
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb
index 2724d4b657b..33efc4cc120 100644
--- a/app/services/clusters/aws/fetch_credentials_service.rb
+++ b/app/services/clusters/aws/fetch_credentials_service.rb
@@ -7,9 +7,8 @@ module Clusters
MissingRoleError = Class.new(StandardError)
- def initialize(provision_role, region:, provider: nil)
+ def initialize(provision_role, provider: nil)
@provision_role = provision_role
- @region = region
@provider = provider
end
@@ -20,13 +19,14 @@ module Clusters
client: client,
role_arn: provision_role.role_arn,
role_session_name: session_name,
- external_id: provision_role.role_external_id
+ external_id: provision_role.role_external_id,
+ policy: session_policy
).credentials
end
private
- attr_reader :provider, :region
+ attr_reader :provider
def client
::Aws::STS::Client.new(credentials: gitlab_credentials, region: region)
@@ -44,6 +44,26 @@ module Clusters
Gitlab::CurrentSettings.eks_secret_access_key
end
+ def region
+ provider&.region || Clusters::Providers::Aws::DEFAULT_REGION
+ end
+
+ ##
+ # If we haven't created a provider record yet,
+ # we restrict ourselves to read only access so
+ # that we can safely expose credentials to the
+ # frontend (to be used when populating the
+ # creation form).
+ def session_policy
+ if provider.nil?
+ File.read(read_only_policy)
+ end
+ end
+
+ def read_only_policy
+ Rails.root.join('vendor', 'aws', 'iam', "eks_cluster_read_only_policy.json")
+ end
+
def session_name
if provider.present?
"gitlab-eks-cluster-#{provider.cluster_id}-user-#{provision_role.user_id}"
diff --git a/app/services/clusters/aws/proxy_service.rb b/app/services/clusters/aws/proxy_service.rb
deleted file mode 100644
index df8fc480005..00000000000
--- a/app/services/clusters/aws/proxy_service.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Aws
- class ProxyService
- DEFAULT_REGION = 'us-east-1'
-
- BadRequest = Class.new(StandardError)
- Response = Struct.new(:status, :body)
-
- def initialize(role, params:)
- @role = role
- @params = params
- end
-
- def execute
- api_response = request_from_api!
-
- Response.new(:ok, api_response.to_hash)
- rescue *service_errors
- Response.new(:bad_request, {})
- end
-
- private
-
- attr_reader :role, :params
-
- def request_from_api!
- case requested_resource
- when 'key_pairs'
- ec2_client.describe_key_pairs
-
- when 'instance_types'
- instance_types
-
- when 'roles'
- iam_client.list_roles
-
- when 'regions'
- ec2_client.describe_regions
-
- when 'security_groups'
- raise BadRequest unless vpc_id.present?
-
- ec2_client.describe_security_groups(vpc_filter)
-
- when 'subnets'
- raise BadRequest unless vpc_id.present?
-
- ec2_client.describe_subnets(vpc_filter)
-
- when 'vpcs'
- ec2_client.describe_vpcs
-
- else
- raise BadRequest
- end
- end
-
- def requested_resource
- params[:resource]
- end
-
- def vpc_id
- params[:vpc_id]
- end
-
- def region
- params[:region] || DEFAULT_REGION
- end
-
- def vpc_filter
- {
- filters: [{
- name: "vpc-id",
- values: [vpc_id]
- }]
- }
- end
-
- ##
- # Unfortunately the EC2 API doesn't provide a list of
- # possible instance types. There is a workaround, using
- # the Pricing API, but instead of requiring the
- # user to grant extra permissions for this we use the
- # values that validate the CloudFormation template.
- def instance_types
- {
- instance_types: cluster_stack_instance_types.map { |type| Hash(instance_type_name: type) }
- }
- end
-
- def cluster_stack_instance_types
- YAML.safe_load(stack_template).dig('Parameters', 'NodeInstanceType', 'AllowedValues')
- end
-
- def stack_template
- File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
- end
-
- def ec2_client
- ::Aws::EC2::Client.new(client_options)
- end
-
- def iam_client
- ::Aws::IAM::Client.new(client_options)
- end
-
- def credentials
- Clusters::Aws::FetchCredentialsService.new(role, region: region).execute
- end
-
- def client_options
- {
- credentials: credentials,
- region: region,
- http_open_timeout: 5,
- http_read_timeout: 10
- }
- end
-
- def service_errors
- [
- BadRequest,
- Clusters::Aws::FetchCredentialsService::MissingRoleError,
- ::Aws::Errors::MissingCredentialsError,
- ::Aws::EC2::Errors::ServiceError,
- ::Aws::IAM::Errors::ServiceError,
- ::Aws::STS::Errors::ServiceError
- ]
- end
- end
- end
-end
diff --git a/app/services/clusters/cleanup/app_service.rb b/app/services/clusters/cleanup/app_service.rb
new file mode 100644
index 00000000000..a7e29c78ea0
--- /dev/null
+++ b/app/services/clusters/cleanup/app_service.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Cleanup
+ class AppService < Clusters::Cleanup::BaseService
+ def execute
+ persisted_applications = @cluster.persisted_applications
+
+ persisted_applications.each do |app|
+ next unless app.available?
+ next unless app.can_uninstall?
+
+ log_event(:uninstalling_app, application: app.class.application_name)
+ uninstall_app_async(app)
+ end
+
+ # Keep calling the worker untill all dependencies are uninstalled
+ return schedule_next_execution(Clusters::Cleanup::AppWorker) if persisted_applications.any?
+
+ log_event(:schedule_remove_project_namespaces)
+ cluster.continue_cleanup!
+ end
+
+ private
+
+ def uninstall_app_async(application)
+ application.make_scheduled!
+
+ Clusters::Applications::UninstallWorker.perform_async(application.name, application.id)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/cleanup/base_service.rb b/app/services/clusters/cleanup/base_service.rb
new file mode 100644
index 00000000000..f99e54cfc40
--- /dev/null
+++ b/app/services/clusters/cleanup/base_service.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Cleanup
+ class BaseService
+ DEFAULT_EXECUTION_INTERVAL = 1.minute
+
+ def initialize(cluster, execution_count = 0)
+ @cluster = cluster
+ @execution_count = execution_count
+ end
+
+ private
+
+ attr_reader :cluster
+
+ def logger
+ @logger ||= Gitlab::Kubernetes::Logger.build
+ end
+
+ def log_event(event, extra_data = {})
+ meta = {
+ service: self.class.name,
+ cluster_id: cluster.id,
+ execution_count: @execution_count,
+ event: event
+ }
+
+ logger.info(meta.merge(extra_data))
+ end
+
+ def schedule_next_execution(worker_class)
+ log_event(:scheduling_execution, next_execution: @execution_count + 1)
+ worker_class.perform_in(execution_interval, cluster.id, @execution_count + 1)
+ end
+
+ # Override this method to customize the execution interval
+ def execution_interval
+ DEFAULT_EXECUTION_INTERVAL
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/cleanup/project_namespace_service.rb b/app/services/clusters/cleanup/project_namespace_service.rb
new file mode 100644
index 00000000000..7621be565ff
--- /dev/null
+++ b/app/services/clusters/cleanup/project_namespace_service.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Cleanup
+ class ProjectNamespaceService < BaseService
+ KUBERNETES_NAMESPACE_BATCH_SIZE = 100
+
+ def execute
+ delete_project_namespaces_in_batches
+
+ # Keep calling the worker untill all namespaces are deleted
+ if cluster.kubernetes_namespaces.exists?
+ return schedule_next_execution(Clusters::Cleanup::ProjectNamespaceWorker)
+ end
+
+ cluster.continue_cleanup!
+ end
+
+ private
+
+ def delete_project_namespaces_in_batches
+ kubernetes_namespaces_batch = cluster.kubernetes_namespaces.first(KUBERNETES_NAMESPACE_BATCH_SIZE)
+
+ kubernetes_namespaces_batch.each do |kubernetes_namespace|
+ log_event(:deleting_project_namespace, namespace: kubernetes_namespace.namespace)
+
+ begin
+ kubeclient_delete_namespace(kubernetes_namespace)
+ rescue Kubeclient::HttpError
+ next
+ end
+
+ kubernetes_namespace.destroy!
+ end
+ end
+
+ def kubeclient_delete_namespace(kubernetes_namespace)
+ cluster.kubeclient.delete_namespace(kubernetes_namespace.namespace)
+ rescue Kubeclient::ResourceNotFoundError
+ # no-op: nothing to delete
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/cleanup/service_account_service.rb b/app/services/clusters/cleanup/service_account_service.rb
new file mode 100644
index 00000000000..d60bd76d388
--- /dev/null
+++ b/app/services/clusters/cleanup/service_account_service.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Cleanup
+ class ServiceAccountService < BaseService
+ def execute
+ delete_gitlab_service_account
+
+ log_event(:destroying_cluster)
+
+ cluster.destroy!
+ end
+
+ private
+
+ def delete_gitlab_service_account
+ log_event(:deleting_gitlab_service_account)
+
+ cluster.kubeclient.delete_service_account(
+ ::Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME,
+ ::Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
+ )
+ rescue Kubeclient::ResourceNotFoundError
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/kubernetes/kubernetes.rb b/app/services/clusters/kubernetes.rb
index d29519999b2..59cb1c4b3a9 100644
--- a/app/services/clusters/kubernetes/kubernetes.rb
+++ b/app/services/clusters/kubernetes.rb
@@ -12,5 +12,8 @@ module Clusters
GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role'
GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding'
+ GITLAB_KNATIVE_VERSION_ROLE_NAME = 'gitlab-knative-version-role'
+ GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME = 'gitlab-knative-version-rolebinding'
+ KNATIVE_SERVING_NAMESPACE = 'knative-serving'
end
end
diff --git a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
index d798dcdcfd3..046046bf5a3 100644
--- a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
+++ b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
@@ -49,8 +49,14 @@ module Clusters
create_or_update_knative_serving_role
create_or_update_knative_serving_role_binding
+
create_or_update_crossplane_database_role
create_or_update_crossplane_database_role_binding
+
+ return unless knative_serving_namespace
+
+ create_or_update_knative_version_role
+ create_or_update_knative_version_role_binding
end
private
@@ -64,6 +70,12 @@ module Clusters
).ensure_exists!
end
+ def knative_serving_namespace
+ kubeclient.get_namespace(Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE)
+ rescue Kubeclient::ResourceNotFoundError
+ nil
+ end
+
def create_role_or_cluster_role_binding
if namespace_creator
kubeclient.create_or_update_role_binding(role_binding_resource)
@@ -88,6 +100,14 @@ module Clusters
kubeclient.update_role_binding(crossplane_database_role_binding_resource)
end
+ def create_or_update_knative_version_role
+ kubeclient.update_cluster_role(knative_version_role_resource)
+ end
+
+ def create_or_update_knative_version_role_binding
+ kubeclient.update_cluster_role_binding(knative_version_role_binding_resource)
+ end
+
def service_account_resource
Gitlab::Kubernetes::ServiceAccount.new(
service_account_name,
@@ -166,6 +186,27 @@ module Clusters
service_account_name: service_account_name
).generate
end
+
+ def knative_version_role_resource
+ Gitlab::Kubernetes::ClusterRole.new(
+ name: Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME,
+ rules: [{
+ apiGroups: %w(apps),
+ resources: %w(deployments),
+ verbs: %w(list get)
+ }]
+ ).generate
+ end
+
+ def knative_version_role_binding_resource
+ subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
+
+ Gitlab::Kubernetes::ClusterRoleBinding.new(
+ Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME,
+ Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME,
+ subjects
+ ).generate
+ end
end
end
end
diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb
index dbbe89ef260..03be87f4cc1 100644
--- a/app/services/cohorts_service.rb
+++ b/app/services/cohorts_service.rb
@@ -38,7 +38,7 @@ class CohortsService
{
registration_month: registration_month,
- activity_months: activity_months,
+ activity_months: activity_months[1..-1],
total: activity_months.first[:total],
inactive: inactive
}
diff --git a/app/services/commits/commit_patch_service.rb b/app/services/commits/commit_patch_service.rb
index 49113c3c691..4fa6c30e901 100644
--- a/app/services/commits/commit_patch_service.rb
+++ b/app/services/commits/commit_patch_service.rb
@@ -32,7 +32,7 @@ module Commits
end
def prepare_branch!
- branch_result = CreateBranchService.new(project, current_user)
+ branch_result = ::Branches::CreateService.new(project, current_user)
.execute(@branch_name, @start_branch)
if branch_result[:status] != :success
diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb
index b42494563b2..bd238605ac1 100644
--- a/app/services/commits/create_service.rb
+++ b/app/services/commits/create_service.rb
@@ -101,7 +101,7 @@ module Commits
end
def validate_new_branch_name!
- result = ValidateNewBranchService.new(project, current_user).execute(@branch_name, force: force?)
+ result = ::Branches::ValidateNewService.new(project).execute(@branch_name, force: force?)
if result[:status] == :error
raise_error("Something went wrong when we tried to create '#{@branch_name}' for you: #{result[:message]}")
diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb
index 1c828234f1b..6fde9abfdb0 100644
--- a/app/services/concerns/users/participable_service.rb
+++ b/app/services/concerns/users/participable_service.rb
@@ -55,7 +55,8 @@ module Users
username: group.full_path,
name: group.full_name,
avatar_url: group.avatar_url,
- count: group_counts.fetch(group.id, 0)
+ count: group_counts.fetch(group.id, 0),
+ mentionsDisabled: group.mentions_disabled
}
end
end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
deleted file mode 100644
index d58cb0f9e2b..00000000000
--- a/app/services/create_branch_service.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-class CreateBranchService < BaseService
- def execute(branch_name, ref, create_master_if_empty: true)
- create_master_branch if create_master_if_empty && project.empty_repo?
-
- result = ValidateNewBranchService.new(project, current_user)
- .execute(branch_name)
-
- return result if result[:status] == :error
-
- new_branch = repository.add_branch(current_user, branch_name, ref)
-
- if new_branch
- success(new_branch)
- else
- error("Invalid reference name: #{branch_name}")
- end
- rescue Gitlab::Git::PreReceiveError => ex
- error(ex.message)
- end
-
- def success(branch)
- super().merge(branch: branch)
- end
-
- private
-
- def create_master_branch
- project.repository.create_file(
- current_user,
- '/README.md',
- '',
- message: 'Add README.md',
- branch_name: 'master'
- )
- end
-end
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 0aa76df35ba..eacea7d94c7 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -21,7 +21,11 @@ class CreateSnippetService < BaseService
spam_check(snippet, current_user)
- if snippet.save
+ snippet_saved = snippet.with_transaction_returning_status do
+ snippet.save && snippet.store_mentions!
+ end
+
+ if snippet_saved
UserAgentDetailService.new(snippet, @request).create
Gitlab::UsageDataCounters::SnippetCounter.count(:create)
end
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
deleted file mode 100644
index fd41ce54486..00000000000
--- a/app/services/delete_branch_service.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-class DeleteBranchService < BaseService
- def execute(branch_name)
- repository = project.repository
- branch = repository.find_branch(branch_name)
-
- unless current_user.can?(:push_code, project)
- return ServiceResponse.error(
- message: 'You dont have push access to repo',
- http_status: 405)
- end
-
- unless branch
- return ServiceResponse.error(
- message: 'No such branch',
- http_status: 404)
- end
-
- if repository.rm_branch(current_user, branch_name)
- ServiceResponse.success(message: 'Branch was deleted')
- else
- ServiceResponse.error(
- message: 'Failed to remove branch',
- http_status: 400)
- end
- rescue Gitlab::Git::PreReceiveError => ex
- ServiceResponse.error(message: ex.message, http_status: 400)
- end
-end
diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb
deleted file mode 100644
index 80de897e94b..00000000000
--- a/app/services/delete_merged_branches_service.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-class DeleteMergedBranchesService < BaseService
- def async_execute
- DeleteMergedBranchesWorker.perform_async(project.id, current_user.id)
- end
-
- def execute
- raise Gitlab::Access::AccessDeniedError unless can?(current_user, :push_code, project)
-
- branches = project.repository.merged_branch_names
- # Prevent deletion of branches relevant to open merge requests
- branches -= merge_request_branch_names
- # Prevent deletion of protected branches
- branches = branches.reject { |branch| ProtectedBranch.protected?(project, branch) }
-
- branches.each do |branch|
- DeleteBranchService.new(project, current_user).execute(branch)
- end
- end
-
- private
-
- # rubocop: disable CodeReuse/ActiveRecord
- def merge_request_branch_names
- # reorder(nil) is necessary for SELECT DISTINCT because default scope adds an ORDER BY
- source_names = project.origin_merge_requests.opened.reorder(nil).distinct.pluck(:source_branch)
- target_names = project.merge_requests.opened.reorder(nil).distinct.pluck(:target_branch)
- (source_names + target_names).uniq
- end
- # rubocop: enable CodeReuse/ActiveRecord
-end
diff --git a/app/services/deployments/after_create_service.rb b/app/services/deployments/after_create_service.rb
index e0a4e5419cc..1d9cb666cff 100644
--- a/app/services/deployments/after_create_service.rb
+++ b/app/services/deployments/after_create_service.rb
@@ -29,6 +29,7 @@ module Deployments
environment.external_url = url
end
+ renew_auto_stop_in
environment.fire_state_event(action)
if environment.save && !environment.stopped?
@@ -63,6 +64,12 @@ module Deployments
def action
environment_options[:action] || 'start'
end
+
+ def renew_auto_stop_in
+ return unless deployable
+
+ environment.auto_stop_in = deployable.environment_auto_stop_in
+ end
end
end
diff --git a/app/services/deployments/create_service.rb b/app/services/deployments/create_service.rb
index 89e3f7c8b83..7355747d778 100644
--- a/app/services/deployments/create_service.rb
+++ b/app/services/deployments/create_service.rb
@@ -11,15 +11,17 @@ module Deployments
end
def execute
- create_deployment.tap do |deployment|
- AfterCreateService.new(deployment).execute if deployment.persisted?
+ environment.deployments.build(deployment_attributes).tap do |deployment|
+ # Deployment#change_status already saves the model, so we only need to
+ # call #save ourselves if no status is provided.
+ if (status = params[:status])
+ deployment.update_status(status)
+ else
+ deployment.save
+ end
end
end
- def create_deployment
- environment.deployments.create(deployment_attributes)
- end
-
def deployment_attributes
# We use explicit parameters here so we never by accident allow parameters
# to be set that one should not be able to set (e.g. the row ID).
@@ -31,8 +33,7 @@ module Deployments
tag: params[:tag],
sha: params[:sha],
user: current_user,
- on_stop: params[:on_stop],
- status: params[:status]
+ on_stop: params[:on_stop]
}
end
end
diff --git a/app/services/deployments/update_service.rb b/app/services/deployments/update_service.rb
index 97b233f16a7..b8f8740c9b9 100644
--- a/app/services/deployments/update_service.rb
+++ b/app/services/deployments/update_service.rb
@@ -10,22 +10,7 @@ module Deployments
end
def execute
- # A regular update() does not trigger the state machine transitions, which
- # we need to ensure merge requests are linked when changing the status to
- # success. To work around this we use this case statment, using the right
- # event methods to trigger the transition hooks.
- case params[:status]
- when 'running'
- deployment.run
- when 'success'
- deployment.succeed
- when 'failed'
- deployment.drop
- when 'canceled'
- deployment.cancel
- else
- false
- end
+ deployment.update_status(params[:status])
end
end
end
diff --git a/app/services/environments/reset_auto_stop_service.rb b/app/services/environments/reset_auto_stop_service.rb
new file mode 100644
index 00000000000..237629fda79
--- /dev/null
+++ b/app/services/environments/reset_auto_stop_service.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Environments
+ class ResetAutoStopService < ::BaseService
+ def execute(environment)
+ return error(_('Failed to cancel auto stop because you do not have permission to update the environment.')) unless can_update_environment?(environment)
+ return error(_('Failed to cancel auto stop because the environment is not set as auto stop.')) unless environment.auto_stop_at?
+
+ if environment.reset_auto_stop
+ success
+ else
+ error(_('Failed to cancel auto stop because failed to update the environment.'))
+ end
+ end
+
+ private
+
+ def can_update_environment?(environment)
+ can?(current_user, :update_environment, environment)
+ end
+ end
+end
diff --git a/app/services/error_tracking/list_issues_service.rb b/app/services/error_tracking/list_issues_service.rb
index 2e8c401b8ef..132e9dfa7bd 100644
--- a/app/services/error_tracking/list_issues_service.rb
+++ b/app/services/error_tracking/list_issues_service.rb
@@ -4,6 +4,7 @@ module ErrorTracking
class ListIssuesService < ErrorTracking::BaseService
DEFAULT_ISSUE_STATUS = 'unresolved'
DEFAULT_LIMIT = 20
+ DEFAULT_SORT = 'last_seen'
def external_url
project_error_tracking_setting&.sentry_external_url
@@ -12,11 +13,17 @@ module ErrorTracking
private
def fetch
- project_error_tracking_setting.list_sentry_issues(issue_status: issue_status, limit: limit)
+ project_error_tracking_setting.list_sentry_issues(
+ issue_status: issue_status,
+ limit: limit,
+ search_term: params[:search_term].presence,
+ sort: sort,
+ cursor: params[:cursor].presence
+ )
end
def parse_response(response)
- { issues: response[:issues] }
+ response.slice(:issues, :pagination)
end
def issue_status
@@ -26,5 +33,9 @@ module ErrorTracking
def limit
params[:limit] || DEFAULT_LIMIT
end
+
+ def sort
+ params[:sort] || DEFAULT_SORT
+ end
end
end
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index 0801fd4d03f..d935d9e8cdc 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -85,12 +85,36 @@ module Git
before: oldrev,
after: newrev,
ref: ref,
+ variables_attributes: generate_vars_from_push_options || [],
push_options: params[:push_options] || {},
checkout_sha: Gitlab::DataBuilder::Push.checkout_sha(
project.repository, newrev, ref)
}
end
+ def ci_variables_from_push_options
+ strong_memoize(:ci_variables_from_push_options) do
+ params[:push_options]&.deep_symbolize_keys&.dig(:ci, :variable)
+ end
+ end
+
+ def generate_vars_from_push_options
+ return [] unless ci_variables_from_push_options
+
+ ci_variables_from_push_options.map do |var_definition, _count|
+ key, value = var_definition.to_s.split("=", 2)
+
+ # Accept only valid format. We ignore the following formats
+ # 1. "=123". In this case, `key` will be an empty string
+ # 2. "FOO". In this case, `value` will be nil.
+ # However, the format "FOO=" will result in key beign `FOO` and value
+ # being an empty string. This is acceptable.
+ next if key.blank? || value.nil?
+
+ { "key" => key, "variable_type" => "env_var", "secret_value" => value }
+ end.compact
+ end
+
def push_data_params(commits:, with_changed_files: true)
{
oldrev: oldrev,
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
index 273a12f386a..bbb3c2ad050 100644
--- a/app/services/issuable/bulk_update_service.rb
+++ b/app/services/issuable/bulk_update_service.rb
@@ -4,19 +4,18 @@ module Issuable
class BulkUpdateService
include Gitlab::Allowable
- attr_accessor :current_user, :params
+ attr_accessor :parent, :current_user, :params
- def initialize(user = nil, params = {})
- @current_user, @params = user, params.dup
+ def initialize(parent, user = nil, params = {})
+ @parent, @current_user, @params = parent, user, params.dup
end
- # rubocop: disable CodeReuse/ActiveRecord
def execute(type)
model_class = type.classify.constantize
update_class = type.classify.pluralize.constantize::UpdateService
ids = params.delete(:issuable_ids).split(",")
- items = model_class.where(id: ids)
+ items = find_issuables(parent, model_class, ids)
permitted_attrs(type).each do |key|
params.delete(key) unless params[key].present?
@@ -37,7 +36,6 @@ module Issuable
success: !items.count.zero?
}
end
- # rubocop: enable CodeReuse/ActiveRecord
private
@@ -50,5 +48,15 @@ module Issuable
attrs.push(:assignee_id)
end
end
+
+ def find_issuables(parent, model_class, ids)
+ if parent.is_a?(Project)
+ model_class.id_in(ids).of_projects(parent)
+ elsif parent.is_a?(Group)
+ model_class.id_in(ids).of_projects(parent.all_projects)
+ end
+ end
end
end
+
+Issuable::BulkUpdateService.prepend_if_ee('EE::Issuable::BulkUpdateService')
diff --git a/app/services/issuable/clone/attributes_rewriter.rb b/app/services/issuable/clone/attributes_rewriter.rb
index 10c89c62bf1..1f5d83917cc 100644
--- a/app/services/issuable/clone/attributes_rewriter.rb
+++ b/app/services/issuable/clone/attributes_rewriter.rb
@@ -10,7 +10,13 @@ module Issuable
end
def execute
- new_entity.update(milestone: cloneable_milestone, labels: cloneable_labels)
+ update_attributes = { labels: cloneable_labels }
+
+ milestone = cloneable_milestone
+ update_attributes[:milestone] = milestone if milestone.present?
+
+ new_entity.update(update_attributes)
+
copy_resource_label_events
end
diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb
index a170a4dcae2..846b881e819 100644
--- a/app/services/issuable/common_system_notes_service.rb
+++ b/app/services/issuable/common_system_notes_service.rb
@@ -7,20 +7,24 @@ module Issuable
def execute(issuable, old_labels: [], is_update: true)
@issuable = issuable
- if is_update
- if issuable.previous_changes.include?('title')
- create_title_change_note(issuable.previous_changes['title'].first)
+ # We disable touch so that created system notes do not update
+ # the noteable's updated_at field
+ ActiveRecord::Base.no_touching do
+ if is_update
+ if issuable.previous_changes.include?('title')
+ create_title_change_note(issuable.previous_changes['title'].first)
+ end
+
+ handle_description_change_note
+
+ handle_time_tracking_note if issuable.is_a?(TimeTrackable)
+ create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked')
end
- handle_description_change_note
-
- handle_time_tracking_note if issuable.is_a?(TimeTrackable)
- create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked')
+ create_due_date_note if issuable.previous_changes.include?('due_date')
+ create_milestone_note if issuable.previous_changes.include?('milestone_id')
+ create_labels_note(old_labels) if old_labels && issuable.labels != old_labels
end
-
- create_due_date_note if issuable.previous_changes.include?('due_date')
- create_milestone_note if issuable.previous_changes.include?('milestone_id')
- create_labels_note(old_labels) if old_labels && issuable.labels != old_labels
end
private
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 8a79c5f889d..6cb84458d9b 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -163,10 +163,12 @@ class IssuableBaseService < BaseService
before_create(issuable)
- if issuable.save
- ActiveRecord::Base.no_touching do
- Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, is_update: false)
- end
+ issuable_saved = issuable.with_transaction_returning_status do
+ issuable.save && issuable.store_mentions!
+ end
+
+ if issuable_saved
+ Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, is_update: false)
after_create(issuable)
execute_hooks(issuable)
@@ -226,11 +228,12 @@ class IssuableBaseService < BaseService
update_project_counters = issuable.project && update_project_counter_caches?(issuable)
ensure_milestone_available(issuable)
- if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) }
- # We do not touch as it will affect a update on updated_at field
- ActiveRecord::Base.no_touching do
- Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels])
- end
+ issuable_saved = issuable.with_transaction_returning_status do
+ issuable.save(touch: should_touch) && issuable.store_mentions!
+ end
+
+ if issuable_saved
+ Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels])
handle_changes(issuable, old_associations: old_associations)
@@ -264,10 +267,7 @@ class IssuableBaseService < BaseService
before_update(issuable, skip_spam_check: true)
if issuable.with_transaction_returning_status { issuable.save }
- # We do not touch as it will affect a update on updated_at field
- ActiveRecord::Base.no_touching do
- Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: nil)
- end
+ Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: nil)
handle_task_changes(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees.to_a)
@@ -397,7 +397,7 @@ class IssuableBaseService < BaseService
end
def update_project_counter_caches?(issuable)
- issuable.state_changed?
+ issuable.state_id_changed?
end
def parent
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 48ed5afbc2a..974f7e598ca 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -36,3 +36,5 @@ module Issues
end
end
end
+
+Issues::BaseService.prepend_if_ee('EE::Issues::BaseService')
diff --git a/app/services/issues/duplicate_service.rb b/app/services/issues/duplicate_service.rb
index 82c226f601e..c936d75e277 100644
--- a/app/services/issues/duplicate_service.rb
+++ b/app/services/issues/duplicate_service.rb
@@ -25,3 +25,5 @@ module Issues
end
end
end
+
+Issues::DuplicateService.prepend_if_ee('EE::Issues::DuplicateService')
diff --git a/app/services/issues/zoom_link_service.rb b/app/services/issues/zoom_link_service.rb
index 023d7080e88..9572cf50564 100644
--- a/app/services/issues/zoom_link_service.rb
+++ b/app/services/issues/zoom_link_service.rb
@@ -13,30 +13,29 @@ module Issues
if can_add_link? && (link = parse_link(link))
begin
add_zoom_meeting(link)
- success(_('Zoom meeting added'))
rescue ActiveRecord::RecordNotUnique
- error(_('Failed to add a Zoom meeting'))
+ error(message: _('Failed to add a Zoom meeting'))
end
else
- error(_('Failed to add a Zoom meeting'))
+ error(message: _('Failed to add a Zoom meeting'))
end
end
def remove_link
if can_remove_link?
remove_zoom_meeting
- success(_('Zoom meeting removed'))
+ success(message: _('Zoom meeting removed'))
else
- error(_('Failed to remove a Zoom meeting'))
+ error(message: _('Failed to remove a Zoom meeting'))
end
end
def can_add_link?
- can_update_issue? && !@added_meeting
+ can_change_link? && !@added_meeting
end
def can_remove_link?
- can_update_issue? && !!@added_meeting
+ can_change_link? && @issue.persisted? && !!@added_meeting
end
def parse_link(link)
@@ -56,14 +55,29 @@ module Issues
end
def add_zoom_meeting(link)
- ZoomMeeting.create(
+ zoom_meeting = new_zoom_meeting(link)
+ response =
+ if @issue.persisted?
+ # Save the meeting directly since we only want to update one meeting, not all
+ zoom_meeting.save
+ success(message: _('Zoom meeting added'))
+ else
+ success(message: _('Zoom meeting added'), payload: { zoom_meetings: [zoom_meeting] })
+ end
+
+ track_meeting_added_event
+ SystemNoteService.zoom_link_added(@issue, @project, current_user)
+
+ response
+ end
+
+ def new_zoom_meeting(link)
+ ZoomMeeting.new(
issue: @issue,
- project: @issue.project,
+ project: @project,
issue_status: :added,
url: link
)
- track_meeting_added_event
- SystemNoteService.zoom_link_added(@issue, @project, current_user)
end
def remove_zoom_meeting
@@ -72,16 +86,20 @@ module Issues
SystemNoteService.zoom_link_removed(@issue, @project, current_user)
end
- def success(message)
- ServiceResponse.success(message: message)
+ def success(message:, payload: nil)
+ ServiceResponse.success(message: message, payload: payload)
end
- def error(message)
+ def error(message:)
ServiceResponse.error(message: message)
end
- def can_update_issue?
- can?(current_user, :update_issue, project)
+ def can_change_link?
+ if @issue.persisted?
+ can?(current_user, :update_issue, @project)
+ else
+ can?(current_user, :create_issue, @project)
+ end
end
end
end
diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb
index 200a34cae04..95fb99d3e7a 100644
--- a/app/services/merge_requests/create_from_issue_service.rb
+++ b/app/services/merge_requests/create_from_issue_service.rb
@@ -19,7 +19,7 @@ module MergeRequests
return error('Not allowed to create merge request') unless can_create_merge_request?
return error('Invalid issue iid') unless @issue_iid.present? && issue.present?
- result = CreateBranchService.new(target_project, current_user).execute(branch_name, ref)
+ result = ::Branches::CreateService.new(target_project, current_user).execute(branch_name, ref)
return result if result[:status] == :error
new_merge_request = create(merge_request)
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index a45b4f1142e..4a109fe4e16 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -62,8 +62,6 @@ module MergeRequests
end
def updated_check!
- return unless Feature.enabled?(:validate_merge_sha, merge_request.target_project, default_enabled: false)
-
unless source_matches?
raise_error('Branch has been updated since the merge was requested. '\
'Please review the changes.')
@@ -101,7 +99,7 @@ module MergeRequests
log_info("Post merge finished on JID #{merge_jid} with state #{state}")
if delete_source_branch?
- DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
+ ::Branches::DeleteService.new(@merge_request.source_project, branch_deletion_user)
.execute(merge_request.source_branch)
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index bd3fcf85a62..396ddec6383 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -106,7 +106,7 @@ module MergeRequests
filter_merge_requests(merge_requests).each do |merge_request|
if branch_and_project_match?(merge_request) || @push.force_push?
merge_request.reload_diff(current_user)
- elsif merge_request.includes_any_commits?(push_commit_ids)
+ elsif merge_request.merge_request_diff.includes_any_commits?(push_commit_ids)
merge_request.reload_diff(current_user)
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 8a6a7119508..1dc5503d368 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -87,7 +87,7 @@ module MergeRequests
merge_request.update(merge_error: nil)
- if merge_request.head_pipeline && merge_request.head_pipeline.active?
+ if merge_request.head_pipeline_active?
AutoMergeService.new(project, current_user, { sha: last_diff_sha }).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
merge_request.merge_async(current_user.id, { sha: last_diff_sha })
diff --git a/app/services/metrics/dashboard/base_embed_service.rb b/app/services/metrics/dashboard/base_embed_service.rb
index 8bb5f4892cb..8aef9873ac1 100644
--- a/app/services/metrics/dashboard/base_embed_service.rb
+++ b/app/services/metrics/dashboard/base_embed_service.rb
@@ -13,7 +13,7 @@ module Metrics
def dashboard_path
params[:dashboard_path].presence ||
- ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH
+ ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH
end
def group
diff --git a/app/services/metrics/dashboard/custom_metric_embed_service.rb b/app/services/metrics/dashboard/custom_metric_embed_service.rb
index 79a556b1695..9e616f4e379 100644
--- a/app/services/metrics/dashboard/custom_metric_embed_service.rb
+++ b/app/services/metrics/dashboard/custom_metric_embed_service.rb
@@ -40,7 +40,7 @@ module Metrics
# All custom metrics are displayed on the system dashboard.
# Nil is acceptable as we'll default to the system dashboard.
def valid_dashboard?(dashboard)
- dashboard.nil? || ::Metrics::Dashboard::SystemDashboardService.system_dashboard?(dashboard)
+ dashboard.nil? || ::Metrics::Dashboard::SystemDashboardService.matching_dashboard?(dashboard)
end
end
diff --git a/app/services/metrics/dashboard/grafana_metric_embed_service.rb b/app/services/metrics/dashboard/grafana_metric_embed_service.rb
index 60591e9a6f3..44b58ad9729 100644
--- a/app/services/metrics/dashboard/grafana_metric_embed_service.rb
+++ b/app/services/metrics/dashboard/grafana_metric_embed_service.rb
@@ -133,7 +133,7 @@ module Metrics
def uid_regex
base_url = @project.grafana_integration.grafana_url.chomp('/')
- %r{(#{Regexp.escape(base_url)}\/d\/(?<uid>\w+)\/)}x
+ %r{^(#{Regexp.escape(base_url)}\/d\/(?<uid>.+)\/)}x
end
end
diff --git a/app/services/metrics/dashboard/pod_dashboard_service.rb b/app/services/metrics/dashboard/pod_dashboard_service.rb
new file mode 100644
index 00000000000..16b87d2d587
--- /dev/null
+++ b/app/services/metrics/dashboard/pod_dashboard_service.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Metrics
+ module Dashboard
+ class PodDashboardService < ::Metrics::Dashboard::PredefinedDashboardService
+ DASHBOARD_PATH = 'config/prometheus/pod_metrics.yml'
+ DASHBOARD_NAME = 'Pod Health'
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/predefined_dashboard_service.rb b/app/services/metrics/dashboard/predefined_dashboard_service.rb
new file mode 100644
index 00000000000..1be1a000854
--- /dev/null
+++ b/app/services/metrics/dashboard/predefined_dashboard_service.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Metrics
+ module Dashboard
+ class PredefinedDashboardService < ::Metrics::Dashboard::BaseService
+ # These constants should be overridden in the inheriting class. For Ex:
+ # DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
+ # DASHBOARD_NAME = 'Default'
+ DASHBOARD_PATH = nil
+ DASHBOARD_NAME = nil
+
+ SEQUENCE = [
+ STAGES::EndpointInserter,
+ STAGES::Sorter
+ ].freeze
+
+ class << self
+ def matching_dashboard?(filepath)
+ filepath == self::DASHBOARD_PATH
+ end
+ end
+
+ private
+
+ def cache_key
+ "metrics_dashboard_#{dashboard_path}"
+ end
+
+ def dashboard_path
+ self.class::DASHBOARD_PATH
+ end
+
+ # Returns the base metrics shipped with every GitLab service.
+ def get_raw_dashboard
+ yml = File.read(Rails.root.join(dashboard_path))
+
+ YAML.safe_load(yml)
+ end
+
+ def sequence
+ self.class::SEQUENCE
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb
index f8dbb8a705c..bef65dbe1c2 100644
--- a/app/services/metrics/dashboard/system_dashboard_service.rb
+++ b/app/services/metrics/dashboard/system_dashboard_service.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
# Fetches the system metrics dashboard and formats the output.
-# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+# Use Gitlab::Metrics::Dashboard::Finder to retrieve dashboards.
module Metrics
module Dashboard
- class SystemDashboardService < ::Metrics::Dashboard::BaseService
- SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
- SYSTEM_DASHBOARD_NAME = 'Default'
+ class SystemDashboardService < ::Metrics::Dashboard::PredefinedDashboardService
+ DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
+ DASHBOARD_NAME = 'Default'
SEQUENCE = [
STAGES::CommonMetricsInserter,
@@ -18,37 +18,12 @@ module Metrics
class << self
def all_dashboard_paths(_project)
[{
- path: SYSTEM_DASHBOARD_PATH,
- display_name: SYSTEM_DASHBOARD_NAME,
+ path: DASHBOARD_PATH,
+ display_name: DASHBOARD_NAME,
default: true,
system_dashboard: true
}]
end
-
- def system_dashboard?(filepath)
- filepath == SYSTEM_DASHBOARD_PATH
- end
- end
-
- private
-
- def cache_key
- "metrics_dashboard_#{dashboard_path}"
- end
-
- def dashboard_path
- SYSTEM_DASHBOARD_PATH
- end
-
- # Returns the base metrics shipped with every GitLab service.
- def get_raw_dashboard
- yml = File.read(Rails.root.join(dashboard_path))
-
- YAML.safe_load(yml)
- end
-
- def sequence
- SEQUENCE
end
end
end
diff --git a/app/services/metrics/sample_metrics_service.rb b/app/services/metrics/sample_metrics_service.rb
new file mode 100644
index 00000000000..719bc6614e4
--- /dev/null
+++ b/app/services/metrics/sample_metrics_service.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Metrics
+ class SampleMetricsService
+ DIRECTORY = "sample_metrics"
+
+ attr_reader :identifier
+
+ def initialize(identifier)
+ @identifier = identifier
+ end
+
+ def query
+ return unless identifier && File.exist?(file_location)
+
+ YAML.load_file(File.expand_path(file_location, __dir__))
+ end
+
+ private
+
+ def file_location
+ sanitized_string = identifier.gsub(/[^0-9A-Za-z_]/, '')
+ File.join(Rails.root, DIRECTORY, "#{sanitized_string}.yml")
+ end
+ end
+end
diff --git a/app/services/notes/base_service.rb b/app/services/notes/base_service.rb
index b4d04c47cc0..87f7cb0e8ac 100644
--- a/app/services/notes/base_service.rb
+++ b/app/services/notes/base_service.rb
@@ -4,7 +4,7 @@ module Notes
class BaseService < ::BaseService
def clear_noteable_diffs_cache(note)
if note.is_a?(DiffNote) &&
- note.discussion_first_note? &&
+ note.start_of_discussion? &&
note.position.unfolded_diff?(project.repository)
note.noteable.diffs.clear_cache
end
diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb
index 541f3e0d23c..cf21818a886 100644
--- a/app/services/notes/build_service.rb
+++ b/app/services/notes/build_service.rb
@@ -11,7 +11,7 @@ module Notes
unless discussion && can?(current_user, :create_note, discussion.noteable)
note = Note.new
- note.errors.add(:base, 'Discussion to reply to cannot be found')
+ note.errors.add(:base, _('Discussion to reply to cannot be found'))
return note
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 9e6cbfa06fe..accfdb5b863 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -2,6 +2,7 @@
module Notes
class CreateService < ::Notes::BaseService
+ # rubocop:disable Metrics/CyclomaticComplexity
def execute
merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
@@ -9,7 +10,9 @@ module Notes
# n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/37440
note_valid = Gitlab::GitalyClient.allow_n_plus_1_calls do
- note.valid?
+ # We may set errors manually in Notes::BuildService for this reason
+ # we also need to check for already existing errors.
+ note.errors.empty? && note.valid?
end
return note unless note_valid
@@ -33,7 +36,11 @@ module Notes
NewNoteWorker.perform_async(note.id)
end
- if !only_commands && note.save
+ note_saved = note.with_transaction_returning_status do
+ !only_commands && note.save && note.store_mentions!
+ end
+
+ if note_saved
if note.part_of_discussion? && note.discussion.can_convert_to_discussion?
note.discussion.convert_to_discussion!(save: true)
end
@@ -63,6 +70,7 @@ module Notes
note
end
+ # rubocop:enable Metrics/CyclomaticComplexity
private
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 573be8fbe8b..15c556498ec 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -7,7 +7,11 @@ module Notes
old_mentioned_users = note.mentioned_users(current_user).to_a
- note.update(params.merge(updated_by: current_user))
+ note.assign_attributes(params.merge(updated_by: current_user))
+
+ note.with_transaction_returning_status do
+ note.save && note.store_mentions!
+ end
only_commands = false
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 1709474a6c7..a75eaa99c23 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -58,6 +58,14 @@ class NotificationService
end
end
+ # Notify the owner of the personal access token, when it is about to expire
+ # And mark the token with about_to_expire_delivered
+ def access_token_about_to_expire(user)
+ return unless user.can?(:receive_notifications)
+
+ mailer.access_token_about_to_expire_email(user).deliver_later
+ end
+
# When create an issue we should send an email to:
#
# * issue assignee if their notification level is not Disabled
diff --git a/app/services/pages/delete_service.rb b/app/services/pages/delete_service.rb
new file mode 100644
index 00000000000..d4de6bb750d
--- /dev/null
+++ b/app/services/pages/delete_service.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Pages
+ class DeleteService < BaseService
+ def execute
+ project.remove_pages
+ project.pages_domains.destroy_all # rubocop: disable DestroyAll
+ end
+ end
+end
diff --git a/app/services/projects/container_repository/cleanup_tags_service.rb b/app/services/projects/container_repository/cleanup_tags_service.rb
index 1b880a7aab1..b995df12e56 100644
--- a/app/services/projects/container_repository/cleanup_tags_service.rb
+++ b/app/services/projects/container_repository/cleanup_tags_service.rb
@@ -26,13 +26,13 @@ module Projects
def delete_tags(tags_to_delete, tags_by_digest)
deleted_digests = group_by_digest(tags_to_delete).select do |digest, tags|
- delete_tag_digest(digest, tags, tags_by_digest[digest])
+ delete_tag_digest(tags, tags_by_digest[digest])
end
deleted_digests.values.flatten
end
- def delete_tag_digest(digest, tags, other_tags)
+ def delete_tag_digest(tags, other_tags)
# Issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/21405
# we have to remove all tags due
# to Docker Distribution bug unable
diff --git a/app/services/projects/container_repository/delete_tags_service.rb b/app/services/projects/container_repository/delete_tags_service.rb
index 48bd9394dc5..88ff3c2c9df 100644
--- a/app/services/projects/container_repository/delete_tags_service.rb
+++ b/app/services/projects/container_repository/delete_tags_service.rb
@@ -24,32 +24,36 @@ module Projects
dummy_manifest = container_repository.client.generate_empty_manifest(container_repository.path)
return error('could not generate manifest') if dummy_manifest.nil?
- # update the manifests of the tags with the new dummy image
- deleted_tags = []
- tag_digests = []
+ deleted_tags = replace_tag_manifests(container_repository, dummy_manifest, tag_names)
+
+ # Deletes the dummy image
+ # All created tag digests are the same since they all have the same dummy image.
+ # a single delete is sufficient to remove all tags with it
+ if deleted_tags.any? && container_repository.delete_tag_by_digest(deleted_tags.values.first)
+ success(deleted: deleted_tags.keys)
+ else
+ error('could not delete tags')
+ end
+ end
+
+ # update the manifests of the tags with the new dummy image
+ def replace_tag_manifests(container_repository, dummy_manifest, tag_names)
+ deleted_tags = {}
tag_names.each do |name|
digest = container_repository.client.put_tag(container_repository.path, name, dummy_manifest)
next unless digest
- deleted_tags << name
- tag_digests << digest
+ deleted_tags[name] = digest
end
# make sure the digests are the same (it should always be)
- tag_digests.uniq!
+ digests = deleted_tags.values.uniq
# rubocop: disable CodeReuse/ActiveRecord
- Gitlab::Sentry.track_exception(ArgumentError.new('multiple tag digests')) if tag_digests.many?
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(ArgumentError.new('multiple tag digests')) if digests.many?
- # Deletes the dummy image
- # All created tag digests are the same since they all have the same dummy image.
- # a single delete is sufficient to remove all tags with it
- if tag_digests.any? && container_repository.delete_tag_by_digest(tag_digests.first)
- success(deleted: deleted_tags)
- else
- error('could not delete tags')
- end
+ deleted_tags
end
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 90e703e7050..cbed794f92e 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -31,13 +31,6 @@ module Projects
Projects::UnlinkForkService.new(project, current_user).execute
- # The project is not necessarily a fork, so update the fork network originating
- # from this project
- if fork_network = project.root_of_fork_network
- fork_network.update(root_project: nil,
- deleted_root_project_name: project.full_name)
- end
-
attempt_destroy_transaction(project)
system_hook_service.execute_hooks_for(project, :destroy)
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 47ab7f9a8a0..e66a0ed181a 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -3,11 +3,16 @@
module Projects
class ForkService < BaseService
def execute(fork_to_project = nil)
- if fork_to_project
- link_existing_project(fork_to_project)
- else
- fork_new_project
- end
+ forked_project =
+ if fork_to_project
+ link_existing_project(fork_to_project)
+ else
+ fork_new_project
+ end
+
+ refresh_forks_count if forked_project&.saved?
+
+ forked_project
end
private
@@ -92,8 +97,7 @@ module Projects
def link_fork_network(fork_to_project)
return if fork_to_project.errors.any?
- fork_to_project.fork_network_member.save &&
- refresh_forks_count
+ fork_to_project.fork_network_member.save
end
def refresh_forks_count
diff --git a/app/services/projects/hashed_storage/base_repository_service.rb b/app/services/projects/hashed_storage/base_repository_service.rb
index 8b1bcaf17b7..09de8d9f0da 100644
--- a/app/services/projects/hashed_storage/base_repository_service.rb
+++ b/app/services/projects/hashed_storage/base_repository_service.rb
@@ -8,13 +8,12 @@ module Projects
class BaseRepositoryService < BaseService
include Gitlab::ShellAdapter
- attr_reader :old_disk_path, :new_disk_path, :old_wiki_disk_path, :old_storage_version, :logger, :move_wiki
+ attr_reader :old_disk_path, :new_disk_path, :old_storage_version, :logger, :move_wiki
def initialize(project:, old_disk_path:, logger: nil)
@project = project
@logger = logger || Gitlab::AppLogger
@old_disk_path = old_disk_path
- @old_wiki_disk_path = "#{old_disk_path}.wiki"
@move_wiki = has_wiki?
end
@@ -44,9 +43,21 @@ module Projects
gitlab_shell.mv_repository(project.repository_storage, from_name, to_name)
end
+ def move_repositories
+ result = move_repository(old_disk_path, new_disk_path)
+ project.reload_repository!
+
+ if move_wiki
+ result &&= move_repository(old_wiki_disk_path, new_wiki_disk_path)
+ project.clear_memoization(:wiki)
+ end
+
+ result
+ end
+
def rollback_folder_move
move_repository(new_disk_path, old_disk_path)
- move_repository("#{new_disk_path}.wiki", old_wiki_disk_path)
+ move_repository(new_wiki_disk_path, old_wiki_disk_path)
end
def try_to_set_repository_read_only!
@@ -58,6 +69,20 @@ module Projects
raise RepositoryInUseError, migration_error
end
end
+
+ def wiki_path_suffix
+ @wiki_path_suffix ||= Gitlab::GlRepository::WIKI.path_suffix
+ end
+
+ def old_wiki_disk_path
+ @old_wiki_disk_path ||= "#{old_disk_path}#{wiki_path_suffix}"
+ end
+
+ def new_wiki_disk_path
+ @new_wiki_disk_path ||= "#{new_disk_path}#{wiki_path_suffix}"
+ end
end
end
end
+
+Projects::HashedStorage::BaseRepositoryService.prepend_if_ee('EE::Projects::HashedStorage::BaseRepositoryService')
diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb
index 0a0bd90cd20..fd62ac37d27 100644
--- a/app/services/projects/hashed_storage/migrate_repository_service.rb
+++ b/app/services/projects/hashed_storage/migrate_repository_service.rb
@@ -11,11 +11,7 @@ module Projects
@new_disk_path = project.disk_path
- result = move_repository(old_disk_path, new_disk_path)
-
- if move_wiki
- result &&= move_repository(old_wiki_disk_path, "#{new_disk_path}.wiki")
- end
+ result = move_repositories
if result
project.write_repository_config
diff --git a/app/services/projects/hashed_storage/rollback_repository_service.rb b/app/services/projects/hashed_storage/rollback_repository_service.rb
index a705112ebe3..d6646e3765e 100644
--- a/app/services/projects/hashed_storage/rollback_repository_service.rb
+++ b/app/services/projects/hashed_storage/rollback_repository_service.rb
@@ -11,11 +11,7 @@ module Projects
@new_disk_path = project.disk_path
- result = move_repository(old_disk_path, new_disk_path)
-
- if move_wiki
- result &&= move_repository(old_wiki_disk_path, "#{new_disk_path}.wiki")
- end
+ result = move_repositories
if result
project.write_repository_config
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 073c14040ce..cc12aacaf02 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -25,13 +25,13 @@ module Projects
success
rescue Gitlab::UrlBlocker::BlockedUrlError => e
- Gitlab::Sentry.track_acceptable_exception(e, extra: { project_path: project.full_path, importer: project.import_type })
+ Gitlab::ErrorTracking.track_exception(e, project_path: project.full_path, importer: project.import_type)
error(s_("ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}") % { project_safe_import_url: project.safe_import_url, project_full_path: project.full_path, message: e.message })
rescue => e
message = Projects::ImportErrorFilter.filter_message(e.message)
- Gitlab::Sentry.track_acceptable_exception(e, extra: { project_path: project.full_path, importer: project.import_type })
+ Gitlab::ErrorTracking.track_exception(e, project_path: project.full_path, importer: project.import_type)
error(s_("ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}") % { project_safe_import_url: project.safe_import_url, project_full_path: project.full_path, message: message })
end
diff --git a/app/services/projects/overwrite_project_service.rb b/app/services/projects/overwrite_project_service.rb
index 696e1b665b2..c5e38f166da 100644
--- a/app/services/projects/overwrite_project_service.rb
+++ b/app/services/projects/overwrite_project_service.rb
@@ -7,7 +7,9 @@ module Projects
Project.transaction do
move_before_destroy_relationships(source_project)
- destroy_old_project(source_project)
+ # Reset is required in order to get the proper
+ # uncached fork network method calls value.
+ destroy_old_project(source_project.reset)
rename_project(source_project.name, source_project.path)
@project
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index 1b8a920268f..e7e0141099e 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -2,34 +2,67 @@
module Projects
class UnlinkForkService < BaseService
- # rubocop: disable CodeReuse/ActiveRecord
+ # If a fork is given, it:
+ #
+ # - Saves LFS objects to the root project
+ # - Close existing MRs coming from it
+ # - Is removed from the fork network
+ #
+ # If a root of fork(s) is given, it does the same,
+ # but not updating LFS objects (there'll be no related root to cache it).
def execute
- return unless @project.forked?
+ fork_network = @project.fork_network
- if fork_source = @project.fork_source
- fork_source.lfs_objects.find_each do |lfs_object|
- lfs_object.projects << @project unless lfs_object.projects.include?(@project)
- end
+ return unless fork_network
- refresh_forks_count(fork_source)
- end
+ save_lfs_objects
- merge_requests = @project.fork_network
+ merge_requests = fork_network
.merge_requests
.opened
- .where.not(target_project: @project)
- .from_project(@project)
+ .from_and_to_forks(@project)
- merge_requests.each do |mr|
+ merge_requests.find_each do |mr|
::MergeRequests::CloseService.new(@project, @current_user).execute(mr)
end
- @project.fork_network_member.destroy
+ Project.transaction do
+ # Get out of the fork network as a member and
+ # remove references from all its direct forks.
+ @project.fork_network_member.destroy
+ @project.forked_to_members.update_all(forked_from_project_id: nil)
+
+ # The project is not necessarily a fork, so update the fork network originating
+ # from this project
+ if fork_network = @project.root_of_fork_network
+ fork_network.update(root_project: nil, deleted_root_project_name: @project.full_name)
+ end
+ end
+
+ # When the project getting out of the network is a node with parent
+ # and children, both the parent and the node needs a cache refresh.
+ [@project.forked_from_project, @project].compact.each do |project|
+ refresh_forks_count(project)
+ end
end
- # rubocop: enable CodeReuse/ActiveRecord
+
+ private
def refresh_forks_count(project)
Projects::ForksCountService.new(project).refresh_cache
end
+
+ def save_lfs_objects
+ return unless @project.forked?
+
+ lfs_storage_project = @project.lfs_storage_project
+
+ return unless lfs_storage_project
+ return if lfs_storage_project == @project # that project is being unlinked
+
+ lfs_storage_project.lfs_objects.find_each do |lfs_object|
+ lfs_object.projects << @project unless lfs_object.projects.include?(@project)
+ end
+ end
end
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 2dad1d05a2c..aedd7252f63 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -65,7 +65,7 @@ module Projects
)
project_changed_feature_keys = project.project_feature.previous_changes.keys
- if project.previous_changes.include?(:visibility_level) && project.private?
+ if project.visibility_level_previous_changes && project.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id)
TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
@@ -79,6 +79,11 @@ module Projects
system_hook_service.execute_hooks_for(project, :update)
end
+ if project.visibility_level_decreased? && project.unlink_forks_upon_visibility_decrease_enabled?
+ # It's a system-bounded operation, so no extra authorization check is required.
+ Projects::UnlinkForkService.new(project, current_user).execute
+ end
+
update_pages_config if changing_pages_related_config?
end
diff --git a/app/services/prometheus/proxy_variable_substitution_service.rb b/app/services/prometheus/proxy_variable_substitution_service.rb
new file mode 100644
index 00000000000..ca56292e9d6
--- /dev/null
+++ b/app/services/prometheus/proxy_variable_substitution_service.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Prometheus
+ class ProxyVariableSubstitutionService < BaseService
+ include Stepable
+
+ steps :add_params_to_result, :substitute_ruby_variables
+
+ def initialize(environment, params = {})
+ @environment, @params = environment, params.deep_dup
+ end
+
+ def execute
+ execute_steps
+ end
+
+ private
+
+ def add_params_to_result(result)
+ result[:params] = params
+
+ success(result)
+ end
+
+ def substitute_ruby_variables(result)
+ return success(result) unless query
+
+ # The % operator doesn't replace variables if the hash contains string
+ # keys.
+ result[:params][:query] = query % predefined_context.symbolize_keys
+
+ success(result)
+ rescue TypeError, ArgumentError => exception
+ log_error(exception.message)
+ Gitlab::ErrorTracking.track_exception(exception, extra: {
+ template_string: query,
+ variables: predefined_context
+ })
+
+ error(_('Malformed string'))
+ end
+
+ def predefined_context
+ @predefined_context ||= Gitlab::Prometheus::QueryVariables.call(@environment)
+ end
+
+ def query
+ params[:query]
+ end
+ end
+end
diff --git a/app/services/repair_ldap_blocked_user_service.rb b/app/services/repair_ldap_blocked_user_service.rb
deleted file mode 100644
index 6ed42054ac3..00000000000
--- a/app/services/repair_ldap_blocked_user_service.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class RepairLdapBlockedUserService
- attr_accessor :user
-
- def initialize(user)
- @user = user
- end
-
- def execute
- user.block if ldap_hard_blocked?
- end
-
- private
-
- def ldap_hard_blocked?
- user.ldap_blocked? && !user.ldap_user?
- end
-end
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 415a02ab337..7927ab265c5 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -38,7 +38,7 @@ class SubmitUsagePingService
def store_metrics(response)
return unless response['conv_index'].present?
- ConversationalDevelopmentIndex::Metric.create!(
+ DevOpsScore::Metric.create!(
response['conv_index'].slice(*METRICS)
)
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 2299a02fea1..55f888d5664 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -174,6 +174,19 @@ class TodoService
mark_todos_as_done(todos, current_user)
end
+ def mark_all_todos_as_done_by_user(current_user)
+ todos = TodosFinder.new(current_user).execute
+ mark_todos_as_done(todos, current_user)
+ end
+
+ def mark_todo_as_done(todo, current_user)
+ return if todo.done?
+
+ todo.update(state: :done)
+
+ current_user.update_todos_count_cache
+ end
+
# When user marks some todos as pending
def mark_todos_as_pending(todos, current_user)
update_todos_state(todos, current_user, :pending)
diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb
index a294812ef9e..ac7f8e9b1f5 100644
--- a/app/services/update_snippet_service.rb
+++ b/app/services/update_snippet_service.rb
@@ -25,8 +25,12 @@ class UpdateSnippetService < BaseService
snippet.assign_attributes(params)
spam_check(snippet, current_user)
- snippet.save.tap do |succeeded|
- Gitlab::UsageDataCounters::SnippetCounter.count(:update) if succeeded
+ snippet_saved = snippet.with_transaction_returning_status do
+ snippet.save && snippet.store_mentions!
+ end
+
+ if snippet_saved
+ Gitlab::UsageDataCounters::SnippetCounter.count(:update)
end
end
end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index 8c85ad9ffd8..ea4d11e728e 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -23,7 +23,7 @@ module Users
@reset_token = user.generate_reset_token if params[:reset_password]
if user_params[:force_random_password]
- random_password = Devise.friendly_token.first(Devise.password_length.min)
+ random_password = Devise.friendly_token.first(User.password_length.min)
user.password = user.password_confirmation = random_password
end
end
diff --git a/app/services/users/repair_ldap_blocked_service.rb b/app/services/users/repair_ldap_blocked_service.rb
new file mode 100644
index 00000000000..378145a65b3
--- /dev/null
+++ b/app/services/users/repair_ldap_blocked_service.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Users
+ class RepairLdapBlockedService
+ attr_accessor :user
+
+ def initialize(user)
+ @user = user
+ end
+
+ def execute
+ user.block if ldap_hard_blocked?
+ end
+
+ private
+
+ def ldap_hard_blocked?
+ user.ldap_blocked? && !user.ldap_user?
+ end
+ end
+end
diff --git a/app/services/validate_new_branch_service.rb b/app/services/validate_new_branch_service.rb
deleted file mode 100644
index 3f4a59e5cee..00000000000
--- a/app/services/validate_new_branch_service.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'base_service'
-
-class ValidateNewBranchService < BaseService
- def execute(branch_name, force: false)
- valid_branch = Gitlab::GitRefValidator.validate(branch_name)
-
- unless valid_branch
- return error('Branch name is invalid')
- end
-
- if project.repository.branch_exists?(branch_name) && !force
- return error('Branch already exists')
- end
-
- success
- rescue Gitlab::Git::PreReceiveError => ex
- error(ex.message)
- end
-end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 8c294218708..87edac36e33 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -92,9 +92,6 @@ class WebHookService
end
def log_execution(trigger:, url:, request_data:, response:, execution_duration:, error_message: nil)
- # logging for ServiceHook's is not available
- return if hook.is_a?(ServiceHook)
-
WebHookLog.create(
web_hook: hook,
trigger: trigger,