gitlab-org--gitlab-foss/app/services/git/base_hooks_service.rb

182 lines
5 KiB
Ruby

# frozen_string_literal: true
module Git
class BaseHooksService < ::BaseService
include Gitlab::Utils::StrongMemoize
include ChangeParams
# The N most recent commits to process in a single push payload.
PROCESS_COMMIT_LIMIT = 100
def execute
create_events
create_pipelines
execute_project_hooks
# Not a hook, but it needs access to the list of changed commits
enqueue_invalidate_cache
success
end
private
def hook_name
raise NotImplementedError, "Please implement #{self.class}##{__method__}"
end
def commits
raise NotImplementedError, "Please implement #{self.class}##{__method__}"
end
def limited_commits
@limited_commits ||= commits.last(PROCESS_COMMIT_LIMIT)
end
def commits_count
commits.count
end
def event_message
nil
end
def invalidated_file_types
[]
end
# Push events in the activity feed only show information for the
# last commit.
def create_events
return unless params.fetch(:create_push_event, true)
EventCreateService.new.push(project, current_user, event_push_data)
end
def create_pipelines
return unless params.fetch(:create_pipelines, true)
Ci::CreatePipelineService
.new(project, current_user, pipeline_params)
.execute!(:push, pipeline_options)
rescue Ci::CreatePipelineService::CreateError => ex
log_pipeline_errors(ex)
end
def execute_project_hooks
return unless params.fetch(:execute_project_hooks, true)
# Creating push_data invokes one CommitDelta RPC per commit. Only
# build this data if we actually need it.
project.execute_hooks(push_data, hook_name) if project.has_active_hooks?(hook_name)
project.execute_services(push_data, hook_name) if project.has_active_services?(hook_name)
end
def enqueue_invalidate_cache
file_types = invalidated_file_types
return unless file_types.present?
ProjectCacheWorker.perform_async(project.id, file_types, [], false)
end
def pipeline_params
strong_memoize(:pipeline_params) do
{
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
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,
newrev: newrev,
ref: ref,
project: project,
user: current_user,
commits: commits,
message: event_message,
commits_count: commits_count,
with_changed_files: with_changed_files
}
end
def event_push_data
# We only need the last commit for the event push, and we don't
# need the full deltas either.
@event_push_data ||= Gitlab::DataBuilder::Push.build(
**push_data_params(commits: commits.last, with_changed_files: false)
)
end
def push_data
@push_data ||= Gitlab::DataBuilder::Push.build(**push_data_params(commits: limited_commits))
# Dependent code may modify the push data, so return a duplicate each time
@push_data.dup
end
# to be overridden in EE
def pipeline_options
{}
end
def log_pipeline_errors(exception)
data = {
class: self.class.name,
correlation_id: Labkit::Correlation::CorrelationId.current_id.to_s,
project_id: project.id,
project_path: project.full_path,
message: "Error creating pipeline",
errors: exception.to_s,
pipeline_params: sanitized_pipeline_params
}
logger.warn(data)
end
def sanitized_pipeline_params
pipeline_params.except(:push_options)
end
def logger
if Gitlab::Runtime.sidekiq?
Sidekiq.logger
else
# This service runs in Sidekiq, so this shouldn't ever be
# called, but this is included just in case.
Gitlab::ProjectServiceLogger
end
end
end
end