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>'
RUNNER_TOKEN_HEADER =
'Runner-Token'

Instance Method Summary collapse

Instance Method Details

#audit_download(build, filename) ⇒ Object



195
196
197
# File 'lib/api/ci/helpers/runner.rb', line 195

def audit_download(build, filename)
  # noop: overridden in EE
end

#authenticate_job!(heartbeat_runner: false) ⇒ Object

HTTP status codes to terminate the job on GitLab Runner:

  • 403



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/api/ci/helpers/runner.rb', line 91

def authenticate_job!(heartbeat_runner: false)
  # 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 current_job

  # Ensure we go through the Ci::AuthJobFinder as part of this authentication
  begin
    job = job_from_token

    forbidden! unless job
  rescue ::Ci::AuthJobFinder::DeletedProjectError
    forbidden!('Project has been deleted!')
  rescue ::Ci::AuthJobFinder::ErasedJobError
    forbidden!('Job has been erased!')
  rescue ::Ci::AuthJobFinder::NotRunningJobError
    # Pass current_job solely to load actual status of the job.
    # AuthJobFinder currently returns no details.
    job_forbidden!(current_job, 'Job is not processing on runner')
  end

  # Make sure that composite identity is propagated to `PipelineProcessWorker`
  # when the build's status change.
  # TODO: Once https://gitlab.com/gitlab-org/gitlab/-/issues/490992 is done we should
  # remove this because it will be embedded in `Ci::AuthJobFinder`.
  ::Gitlab::Auth::Identity.link_from_job(job)

  # 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
    job.runner_manager&.heartbeat(get_runner_ip)
  end

  job
end

#authenticate_job_via_dependent_job!Object



135
136
137
138
139
140
141
142
143
144
# File 'lib/api/ci/helpers/runner.rb', line 135

def authenticate_job_via_dependent_job!
  # Use primary for both main and non-main databases as authenticating in the scope of runners will load
  # Ci::Build model and other standard authn related models like License, Project and User.
  ::Gitlab::Database::LoadBalancing::SessionMap
    .with_sessions.use_primary { authenticate! }

  forbidden! unless current_job
  forbidden! unless can?(current_user, :read_build, current_job)
  forbidden! unless current_authenticated_job
end

#authenticate_runner!(ensure_runner_manager: true, creation_state: nil) ⇒ Object



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

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

  current_runner.heartbeat(creation_state: creation_state) if ensure_runner_manager
  return unless ensure_runner_manager

  runner_details = get_runner_details_from_request
  current_runner_manager&.heartbeat(runner_details)
end

#authenticate_runner_from_header!Object



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

def authenticate_runner_from_header!
  track_runner_authentication
  forbidden! unless current_runner_from_header
end

#check_if_backoff_required!Object



199
200
201
202
203
# File 'lib/api/ci/helpers/runner.rb', line 199

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

current_job is queried by URL :id param with no authentication



147
148
149
150
151
152
153
154
155
# File 'lib/api/ci/helpers/runner.rb', line 147

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



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

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_from_headerObject



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

def current_runner_from_header
  token = headers[RUNNER_TOKEN_HEADER]

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

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

#current_runner_managerObject



72
73
74
75
76
77
# File 'lib/api/ci/helpers/runner.rb', line 72

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)
  end
end

#get_runner_details_from_requestObject



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

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

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

#get_runner_ipObject



48
49
50
# File 'lib/api/ci/helpers/runner.rb', line 48

def get_runner_ip
  { ip_address: ip_address }
end

#get_system_id_from_requestObject



42
43
44
45
46
# File 'lib/api/ci/helpers/runner.rb', line 42

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

  {}
end

#job_forbidden!(job, reason) ⇒ Object



180
181
182
183
# File 'lib/api/ci/helpers/runner.rb', line 180

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

#job_from_tokenObject



166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/api/ci/helpers/runner.rb', line 166

def job_from_token
  # Uses the Ci::AuthJobFinder, which we want to use
  # as the sole centralized job token authentication service.
  #
  # If the token does not link to the URL-specified job,
  # return a generic auth error with no build details.

  return unless current_job
  return unless current_job == ::Ci::AuthJobFinder.new(token: job_token).execute!

  current_job
end

#job_router_enabled?(runner) ⇒ Boolean

Returns:

  • (Boolean)


205
206
207
208
209
210
211
# File 'lib/api/ci/helpers/runner.rb', line 205

def job_router_enabled?(runner)
  if runner.instance_type?
    return Feature.enabled?(:job_router_instance_runners, runner) || Feature.enabled?(:job_router, :instance)
  end

  Feature.enabled?(:job_router, runner.owner&.root_ancestor)
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.



162
163
164
# File 'lib/api/ci/helpers/runner.rb', line 162

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

#set_application_contextObject



185
186
187
188
189
# File 'lib/api/ci/helpers/runner.rb', line 185

def set_application_context
  return unless current_job

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

#track_ci_minutes_usage!(_build) ⇒ Object



191
192
193
# File 'lib/api/ci/helpers/runner.rb', line 191

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

#track_runner_authenticationObject



79
80
81
82
83
84
85
86
87
# File 'lib/api/ci/helpers/runner.rb', line 79

def track_runner_authentication
  runner = current_runner || current_runner_from_header

  if runner
    metrics.increment_runner_authentication_success_counter(runner_type: runner.runner_type)
  else
    metrics.increment_runner_authentication_failure_counter
  end
end