Module: API::Ci::Helpers::Runner

Includes:
Gitlab::Utils::StrongMemoize
Defined in:
lib/api/ci/helpers/runner.rb

Constant Summary collapse

JOB_TOKEN_HEADER =
'HTTP_JOB_TOKEN'
JOB_TOKEN_PARAM =
:token
LEGACY_SYSTEM_XID =
'<legacy>'

Instance Method Summary collapse

Instance Method Details

#authenticate_job!(heartbeat_runner: false) ⇒ Object

HTTP status codes to terminate the job on GitLab Runner:

  • 403



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/api/ci/helpers/runner.rb', line 72

def authenticate_job!(heartbeat_runner: false)
  job = current_job

  # 404 is not returned here because we want to terminate the job if it's
  # running. A 404 can be returned from anywhere in the networking stack which is why
  # we are explicit about a 403, we should improve this in
  # https://gitlab.com/gitlab-org/gitlab/-/issues/327703
  forbidden! unless job

  forbidden! unless job.valid_token?(job_token)

  forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete?
  forbidden!('Job has been erased!') if job.erased?
  job_forbidden!(job, 'Job is not running') unless job.running?

  # Only some requests (like updating the job or patching the trace) should trigger
  # runner heartbeat. Operations like artifacts uploading are executed in context of
  # the running job and in the job environment, which in many cases will cause the IP
  # to be updated to not the expected value. And operations like artifacts downloads can
  # be done even after the job is finished and from totally different runners - while
  # they would then update the connection status of not the runner that they should.
  # Runner requests done in context of job authentication should explicitly define when
  # the heartbeat should be triggered.
  if heartbeat_runner
    job.runner&.heartbeat(get_runner_ip)
    job.runner_manager&.heartbeat(get_runner_ip)
  end

  job
end

#authenticate_job_via_dependent_job!Object



103
104
105
106
107
# File 'lib/api/ci/helpers/runner.rb', line 103

def authenticate_job_via_dependent_job!
  authenticate!
  forbidden! unless current_job
  forbidden! unless can?(current_user, :read_build, current_job)
end

#authenticate_runner!(ensure_runner_manager: true, update_contacted_at: true) ⇒ Object



15
16
17
18
19
20
21
22
23
24
# File 'lib/api/ci/helpers/runner.rb', line 15

def authenticate_runner!(ensure_runner_manager: true, update_contacted_at: true)
  track_runner_authentication
  forbidden! unless current_runner

  runner_details = get_runner_details_from_request
  current_runner.heartbeat(runner_details, update_contacted_at: update_contacted_at)
  return unless ensure_runner_manager

  current_runner_manager&.heartbeat(runner_details, update_contacted_at: update_contacted_at)
end

#check_if_backoff_required!Object



143
144
145
146
147
# File 'lib/api/ci/helpers/runner.rb', line 143

def check_if_backoff_required!
  return unless Gitlab::Database::Migrations::RunnerBackoff::Communicator.backoff_runner?

  too_many_requests!('Executing database migrations. Please retry later.', retry_after: 1.minute)
end

#current_jobObject



109
110
111
112
113
114
115
116
117
# File 'lib/api/ci/helpers/runner.rb', line 109

def current_job
  id = params[:id]

  load_balancer_stick_request(::Ci::Build, :build, id) if id

  strong_memoize(:current_job) do
    ::Ci::Build.find_by_id(id)
  end
end

#current_runnerObject



45
46
47
48
49
50
51
52
53
# File 'lib/api/ci/helpers/runner.rb', line 45

def current_runner
  token = params[:token]

  load_balancer_stick_request(::Ci::Runner, :runner, token) if token

  strong_memoize(:current_runner) do
    ::Ci::Runner.find_by_token(token.to_s)
  end
end

#current_runner_managerObject



55
56
57
58
59
60
# File 'lib/api/ci/helpers/runner.rb', line 55

def current_runner_manager
  strong_memoize(:current_runner_manager) do
    system_xid = params.fetch(:system_id, LEGACY_SYSTEM_XID)
    current_runner&.ensure_manager(system_xid) { |m| m.contacted_at = Time.current }
  end
end

#get_runner_details_from_requestObject



26
27
28
29
30
31
32
33
# File 'lib/api/ci/helpers/runner.rb', line 26

def get_runner_details_from_request
  return get_runner_ip unless params['info'].present?

  attributes_for_keys(%w[name version revision platform architecture executor], params['info'])
    .merge(get_system_id_from_request)
    .merge(get_runner_config_from_request)
    .merge(get_runner_ip)
end

#get_runner_ipObject



41
42
43
# File 'lib/api/ci/helpers/runner.rb', line 41

def get_runner_ip
  { ip_address: ip_address }
end

#get_system_id_from_requestObject



35
36
37
38
39
# File 'lib/api/ci/helpers/runner.rb', line 35

def get_system_id_from_request
  return { system_id: params[:system_id] } if params.include?(:system_id)

  {}
end

#job_forbidden!(job, reason) ⇒ Object



128
129
130
131
# File 'lib/api/ci/helpers/runner.rb', line 128

def job_forbidden!(job, reason)
  header 'Job-Status', job.status
  forbidden!(reason)
end

#job_tokenObject

The token used by runner to authenticate a request. In most cases, the runner uses the token belonging to the requested job. However, when requesting for job artifacts, the runner would use the token that belongs to downstream jobs that depend on the job that owns the artifacts.



124
125
126
# File 'lib/api/ci/helpers/runner.rb', line 124

def job_token
  @job_token ||= (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
end

#set_application_contextObject



133
134
135
136
137
# File 'lib/api/ci/helpers/runner.rb', line 133

def set_application_context
  return unless current_job

  Gitlab::ApplicationContext.push(job: current_job, runner: current_runner)
end

#track_ci_minutes_usage!(_build, _runner) ⇒ Object



139
140
141
# File 'lib/api/ci/helpers/runner.rb', line 139

def track_ci_minutes_usage!(_build, _runner)
  # noop: overridden in EE
end

#track_runner_authenticationObject



62
63
64
65
66
67
68
# File 'lib/api/ci/helpers/runner.rb', line 62

def track_runner_authentication
  if current_runner
    metrics.increment_runner_authentication_success_counter(runner_type: current_runner.runner_type)
  else
    metrics.increment_runner_authentication_failure_counter
  end
end