2020-07-28 23:09:34 +05:30
# frozen_string_literal: true
module API
module Ci
2021-01-03 14:25:43 +05:30
class Runner < :: API :: Base
2021-10-27 15:23:28 +05:30
helpers :: API :: Ci :: Helpers :: Runner
2020-07-28 23:09:34 +05:30
2021-01-29 00:20:46 +05:30
content_type :txt , 'text/plain'
2020-07-28 23:09:34 +05:30
resource :runners do
2023-01-13 00:05:48 +05:30
desc 'Register a new runner' do
detail " Register a new runner for the instance "
2021-09-30 23:02:18 +05:30
success Entities :: Ci :: RunnerRegistrationDetails
2023-01-13 00:05:48 +05:30
failure [ [ 400 , 'Bad Request' ] , [ 403 , 'Forbidden' ] ]
2020-07-28 23:09:34 +05:30
end
params do
requires :token , type : String , desc : 'Registration token'
optional :description , type : String , desc : %q( Runner's description )
2023-01-13 00:05:48 +05:30
optional :maintainer_note , type : String , desc : %q( Deprecated: see `maintenance_note` )
optional :maintenance_note , type : String ,
desc : %q( Free-form maintenance notes for the runner ( 1024 characters ) )
optional :info , type : Hash , desc : %q( Runner's metadata ) do
optional :name , type : String , desc : %q( Runner's name )
optional :version , type : String , desc : %q( Runner's version )
optional :revision , type : String , desc : %q( Runner's revision )
optional :platform , type : String , desc : %q( Runner's platform )
optional :architecture , type : String , desc : %q( Runner's architecture )
end
optional :active , type : Boolean ,
desc : 'Deprecated: Use `paused` instead. Specifies whether the runner is allowed ' \
'to receive new jobs'
optional :paused , type : Boolean , desc : 'Specifies whether the runner should ignore new jobs'
optional :locked , type : Boolean , desc : 'Specifies whether the runner should be locked for the current project'
2020-07-28 23:09:34 +05:30
optional :access_level , type : String , values : :: Ci :: Runner . access_levels . keys ,
2023-01-13 00:05:48 +05:30
desc : 'The access level of the runner'
optional :run_untagged , type : Boolean , desc : 'Specifies whether the runner should handle untagged jobs'
optional :tag_list , type : Array [ String ] , coerce_with : :: API :: Validations :: Types :: CommaSeparatedToArray . coerce ,
desc : %q( A list of runner tags )
optional :maximum_timeout , type : Integer ,
desc : 'Maximum timeout that limits the amount of time (in seconds) ' \
'that runners can run jobs'
mutually_exclusive :maintainer_note , :maintenance_note
2022-04-04 11:22:00 +05:30
mutually_exclusive :active , :paused
2020-07-28 23:09:34 +05:30
end
2022-07-16 23:28:13 +05:30
post '/' , urgency : :low , feature_category : :runner do
2022-04-04 11:22:00 +05:30
attributes = attributes_for_keys ( % i [ description maintainer_note maintenance_note active paused locked run_untagged tag_list access_level maximum_timeout ] )
2020-07-28 23:09:34 +05:30
. merge ( get_runner_details_from_request )
2022-04-04 11:22:00 +05:30
# Pull in deprecated maintainer_note if that's the only note value available
deprecated_note = attributes . delete ( :maintainer_note )
attributes [ :maintenance_note ] || = deprecated_note if deprecated_note
attributes [ :active ] = ! attributes . delete ( :paused ) if attributes . include? ( :paused )
2022-08-27 11:52:29 +05:30
result = :: Ci :: Runners :: RegisterRunnerService . new . execute ( params [ :token ] , attributes )
@runner = result . success? ? result . payload [ :runner ] : nil
2022-03-02 08:16:31 +05:30
forbidden! unless @runner
2020-07-28 23:09:34 +05:30
2021-04-17 20:07:23 +05:30
if @runner . persisted?
2021-09-30 23:02:18 +05:30
present @runner , with : Entities :: Ci :: RunnerRegistrationDetails
2020-07-28 23:09:34 +05:30
else
2021-04-17 20:07:23 +05:30
render_validation_error! ( @runner )
2020-07-28 23:09:34 +05:30
end
end
2023-01-13 00:05:48 +05:30
desc 'Delete a registered runner' do
summary " Delete a runner by authentication token "
failure [ [ 403 , 'Forbidden' ] ]
2020-07-28 23:09:34 +05:30
end
params do
2023-01-13 00:05:48 +05:30
requires :token , type : String , desc : %q( The runner's authentication token )
2020-07-28 23:09:34 +05:30
end
2022-07-16 23:28:13 +05:30
delete '/' , urgency : :low , feature_category : :runner do
2020-07-28 23:09:34 +05:30
authenticate_runner!
2022-05-07 20:08:51 +05:30
destroy_conditionally! ( current_runner ) { :: Ci :: Runners :: UnregisterRunnerService . new ( current_runner , params [ :token ] ) . execute }
2020-07-28 23:09:34 +05:30
end
2023-01-13 00:05:48 +05:30
desc 'Validate authentication credentials' do
summary " Verify authentication for a registered runner "
2020-07-28 23:09:34 +05:30
http_codes [ [ 200 , 'Credentials are valid' ] , [ 403 , 'Forbidden' ] ]
end
params do
2023-01-13 00:05:48 +05:30
requires :token , type : String , desc : %q( The runner's authentication token )
2020-07-28 23:09:34 +05:30
end
2022-07-16 23:28:13 +05:30
post '/verify' , urgency : :low , feature_category : :runner do
2020-07-28 23:09:34 +05:30
authenticate_runner!
status 200
2021-01-03 14:25:43 +05:30
body " 200 "
2020-07-28 23:09:34 +05:30
end
2022-05-07 20:08:51 +05:30
desc 'Reset runner authentication token with current token' do
success Entities :: Ci :: ResetTokenResult
2023-01-13 00:05:48 +05:30
failure [ [ 403 , 'Forbidden' ] ]
2022-05-07 20:08:51 +05:30
end
params do
requires :token , type : String , desc : 'The current authentication token of the runner'
end
2022-07-16 23:28:13 +05:30
post '/reset_authentication_token' , urgency : :low , feature_category : :runner do
2022-05-07 20:08:51 +05:30
authenticate_runner!
current_runner . reset_token!
present current_runner . token_with_expiration , with : Entities :: Ci :: ResetTokenResult
end
2020-07-28 23:09:34 +05:30
end
resource :jobs do
2021-04-17 20:07:23 +05:30
before { set_application_context }
2020-07-28 23:09:34 +05:30
desc 'Request a job' do
2021-09-30 23:02:18 +05:30
success Entities :: Ci :: JobRequest :: Response
2020-07-28 23:09:34 +05:30
http_codes [ [ 201 , 'Job was scheduled' ] ,
[ 204 , 'No job for Runner' ] ,
2023-01-10 11:22:00 +05:30
[ 403 , 'Forbidden' ] ,
[ 409 , 'Conflict' ] ]
2020-07-28 23:09:34 +05:30
end
params do
requires :token , type : String , desc : %q( Runner's authentication token )
optional :last_update , type : String , desc : %q( Runner's queue last_update token )
optional :info , type : Hash , desc : %q( Runner's metadata ) do
optional :name , type : String , desc : %q( Runner's name )
optional :version , type : String , desc : %q( Runner's version )
optional :revision , type : String , desc : %q( Runner's revision )
optional :platform , type : String , desc : %q( Runner's platform )
optional :architecture , type : String , desc : %q( Runner's architecture )
optional :executor , type : String , desc : %q( Runner's executor )
optional :features , type : Hash , desc : %q( Runner's features )
2021-09-04 01:27:46 +05:30
optional :config , type : Hash , desc : %q( Runner's config ) do
optional :gpus , type : String , desc : %q( GPUs enabled )
end
2020-07-28 23:09:34 +05:30
end
optional :session , type : Hash , desc : %q( Runner's session data ) do
optional :url , type : String , desc : %q( Session's url )
optional :certificate , type : String , desc : %q( Session's certificate )
optional :authorization , type : String , desc : %q( Session's authorization )
end
optional :job_age , type : Integer , desc : %q( Job should be older than passed age in seconds to be ran on runner )
end
# Since we serialize the build output ourselves to ensure Gitaly
# gRPC calls succeed, we need a custom Grape format to handle
# this:
# 1. Grape will ordinarily call `JSON.dump` when Content-Type is set
# to application/json. To avoid this, we need to define a custom type in
# `content_type` and a custom formatter to go with it.
# 2. Grape will parse the request input with the parser defined for
# `content_type`. If no such parser exists, it will be treated as text. We
# reuse the existing JSON parser to preserve the previous behavior.
content_type :build_json , 'application/json'
formatter :build_json , - > ( object , _ ) { object }
parser :build_json , :: Grape :: Parser :: Json
2022-05-07 20:08:51 +05:30
post '/request' , urgency : :low , feature_category : :continuous_integration do
2020-07-28 23:09:34 +05:30
authenticate_runner!
unless current_runner . active?
header 'X-GitLab-Last-Update' , current_runner . ensure_runner_queue_value
break no_content!
end
runner_params = declared_params ( include_missing : false )
if current_runner . runner_queue_value_latest? ( runner_params [ :last_update ] )
header 'X-GitLab-Last-Update' , runner_params [ :last_update ]
Gitlab :: Metrics . add_event ( :build_not_found_cached )
break no_content!
end
new_update = current_runner . ensure_runner_queue_value
result = :: Ci :: RegisterJobService . new ( current_runner ) . execute ( runner_params )
if result . valid?
if result . build_json
Gitlab :: Metrics . add_event ( :build_found )
env [ 'api.format' ] = :build_json
body result . build_json
else
Gitlab :: Metrics . add_event ( :build_not_found )
header 'X-GitLab-Last-Update' , new_update
no_content!
end
else
# We received build that is invalid due to concurrency conflict
Gitlab :: Metrics . add_event ( :build_invalid )
conflict!
end
end
2023-01-13 00:05:48 +05:30
desc 'Update a job' do
2020-11-24 15:15:51 +05:30
http_codes [ [ 200 , 'Job was updated' ] ,
[ 202 , 'Update accepted' ] ,
[ 400 , 'Unknown parameters' ] ,
[ 403 , 'Forbidden' ] ]
2020-07-28 23:09:34 +05:30
end
params do
2023-01-13 00:05:48 +05:30
requires :token , type : String , desc : %q( Runner's authentication token )
2020-07-28 23:09:34 +05:30
requires :id , type : Integer , desc : %q( Job's ID )
optional :state , type : String , desc : %q( Job's status: success, failed )
2020-11-24 15:15:51 +05:30
optional :checksum , type : String , desc : %q( Job's trace CRC32 checksum )
2020-07-28 23:09:34 +05:30
optional :failure_reason , type : String , desc : %q( Job's failure_reason )
2021-02-22 17:27:13 +05:30
optional :output , type : Hash , desc : %q( Build log state ) do
optional :checksum , type : String , desc : %q( Job's trace CRC32 checksum )
optional :bytesize , type : Integer , desc : %q( Job's trace size in bytes )
end
2021-03-08 18:12:59 +05:30
optional :exit_code , type : Integer , desc : %q( Job's exit code )
2020-07-28 23:09:34 +05:30
end
2022-05-07 20:08:51 +05:30
put '/:id' , urgency : :low , feature_category : :continuous_integration do
2022-01-26 12:08:38 +05:30
job = authenticate_job! ( heartbeat_runner : true )
2020-07-28 23:09:34 +05:30
Gitlab :: Metrics . add_event ( :update_build )
2020-11-24 15:15:51 +05:30
service = :: Ci :: UpdateBuildStateService
. new ( job , declared_params ( include_missing : false ) )
service . execute . then do | result |
2021-06-08 01:23:25 +05:30
track_ci_minutes_usage! ( job , current_runner )
2021-01-03 14:25:43 +05:30
header 'X-GitLab-Trace-Update-Interval' , result . backoff
2020-11-24 15:15:51 +05:30
status result . status
2021-01-03 14:25:43 +05:30
body result . status . to_s
2020-07-28 23:09:34 +05:30
end
end
2023-01-13 00:05:48 +05:30
desc 'Append a patch to the job trace' do
2020-07-28 23:09:34 +05:30
http_codes [ [ 202 , 'Trace was patched' ] ,
[ 400 , 'Missing Content-Range header' ] ,
[ 403 , 'Forbidden' ] ,
[ 416 , 'Range not satisfiable' ] ]
end
params do
requires :id , type : Integer , desc : %q( Job's ID )
optional :token , type : String , desc : %q( Job's authentication token )
2023-03-04 22:38:38 +05:30
optional :debug_trace , type : Boolean , desc : %q( Enable or Disable the debug trace )
2020-07-28 23:09:34 +05:30
end
2022-07-16 23:28:13 +05:30
patch '/:id/trace' , urgency : :low , feature_category : :continuous_integration do
2022-01-26 12:08:38 +05:30
job = authenticate_job! ( heartbeat_runner : true )
2020-07-28 23:09:34 +05:30
error! ( '400 Missing header Content-Range' , 400 ) unless request . headers . key? ( 'Content-Range' )
content_range = request . headers [ 'Content-Range' ]
2023-03-04 22:38:38 +05:30
debug_trace = Gitlab :: Utils . to_boolean ( params [ :debug_trace ] )
2021-01-29 00:20:46 +05:30
result = :: Ci :: AppendBuildTraceService
2023-03-04 22:38:38 +05:30
. new ( job , content_range : content_range , debug_trace : debug_trace )
2021-01-29 00:20:46 +05:30
. execute ( request . body . read )
2021-09-30 23:02:18 +05:30
if result . status == 403
break error! ( '403 Forbidden' , 403 )
end
2021-01-29 00:20:46 +05:30
if result . status == 416
break error! ( '416 Range Not Satisfiable' , 416 , { 'Range' = > " 0- #{ result . stream_size } " } )
2020-07-28 23:09:34 +05:30
end
2021-06-08 01:23:25 +05:30
track_ci_minutes_usage! ( job , current_runner )
2021-01-29 00:20:46 +05:30
status result . status
2020-07-28 23:09:34 +05:30
header 'Job-Status' , job . status
2021-01-29 00:20:46 +05:30
header 'Range' , " 0- #{ result . stream_size } "
2020-07-28 23:09:34 +05:30
header 'X-GitLab-Trace-Update-Interval' , job . trace . update_interval . to_s
end
2023-03-04 22:38:38 +05:30
desc 'Authorize uploading job artifact' do
2020-07-28 23:09:34 +05:30
http_codes [ [ 200 , 'Upload allowed' ] ,
[ 403 , 'Forbidden' ] ,
[ 405 , 'Artifacts support not enabled' ] ,
[ 413 , 'File too large' ] ]
end
params do
requires :id , type : Integer , desc : %q( Job's ID )
optional :token , type : String , desc : %q( Job's authentication token )
# NOTE:
# In current runner, filesize parameter would be empty here. This is because archive is streamed by runner,
# so the archive size is not known ahead of time. Streaming is done to not use additional I/O on
# Runner to first save, and then send via Network.
2023-03-04 22:38:38 +05:30
optional :filesize , type : Integer , desc : %q( Size of artifact file )
2020-07-28 23:09:34 +05:30
optional :artifact_type , type : String , desc : %q( The type of artifact ) ,
2022-08-27 11:52:29 +05:30
default : 'archive' , values : :: Ci :: JobArtifact . file_types . keys
2020-07-28 23:09:34 +05:30
end
2022-04-04 11:22:00 +05:30
post '/:id/artifacts/authorize' , feature_category : :build_artifacts , urgency : :low do
2020-07-28 23:09:34 +05:30
not_allowed! unless Gitlab . config . artifacts . enabled
require_gitlab_workhorse!
job = authenticate_job!
2021-04-29 21:17:54 +05:30
result = :: Ci :: JobArtifacts :: CreateService . new ( job ) . authorize ( artifact_type : params [ :artifact_type ] , filesize : params [ :filesize ] )
2020-07-28 23:09:34 +05:30
if result [ :status ] == :success
content_type Gitlab :: Workhorse :: INTERNAL_API_CONTENT_TYPE
status :ok
result [ :headers ]
else
render_api_error! ( result [ :message ] , result [ :http_status ] )
end
end
2023-03-04 22:38:38 +05:30
desc 'Upload a job artifact' do
2021-09-30 23:02:18 +05:30
success Entities :: Ci :: JobRequest :: Response
2020-07-28 23:09:34 +05:30
http_codes [ [ 201 , 'Artifact uploaded' ] ,
[ 400 , 'Bad request' ] ,
[ 403 , 'Forbidden' ] ,
[ 405 , 'Artifacts support not enabled' ] ,
[ 413 , 'File too large' ] ]
end
params do
requires :id , type : Integer , desc : %q( Job's ID )
2023-01-13 00:05:48 +05:30
requires :file , type : :: API :: Validations :: Types :: WorkhorseFile , desc : %( The artifact file to store ( generated by Multipart middleware ) ) , documentation : { type : 'file' }
2020-07-28 23:09:34 +05:30
optional :token , type : String , desc : %q( Job's authentication token )
2023-03-04 22:38:38 +05:30
optional :expire_in , type : String , desc : %q( Specify when artifact should expire )
2020-07-28 23:09:34 +05:30
optional :artifact_type , type : String , desc : %q( The type of artifact ) ,
2022-08-27 11:52:29 +05:30
default : 'archive' , values : :: Ci :: JobArtifact . file_types . keys
2020-07-28 23:09:34 +05:30
optional :artifact_format , type : String , desc : %q( The format of artifact ) ,
2022-08-27 11:52:29 +05:30
default : 'zip' , values : :: Ci :: JobArtifact . file_formats . keys
2023-01-13 00:05:48 +05:30
optional :metadata , type : :: API :: Validations :: Types :: WorkhorseFile , desc : %( The artifact metadata to store ( generated by Multipart middleware ) ) , documentation : { type : 'file' }
2023-03-17 16:20:25 +05:30
optional :accessibility , type : String , desc : %q( Specify accessibility level of artifact private/public )
2020-07-28 23:09:34 +05:30
end
2022-04-04 11:22:00 +05:30
post '/:id/artifacts' , feature_category : :build_artifacts , urgency : :low do
2020-07-28 23:09:34 +05:30
not_allowed! unless Gitlab . config . artifacts . enabled
require_gitlab_workhorse!
job = authenticate_job!
artifacts = params [ :file ]
metadata = params [ :metadata ]
2021-04-29 21:17:54 +05:30
result = :: Ci :: JobArtifacts :: CreateService . new ( job ) . execute ( artifacts , params , metadata_file : metadata )
2020-07-28 23:09:34 +05:30
if result [ :status ] == :success
2022-08-27 11:52:29 +05:30
log_artifacts_filesize ( result [ :artifact ] )
2020-07-28 23:09:34 +05:30
status :created
2021-01-03 14:25:43 +05:30
body " 201 "
2020-07-28 23:09:34 +05:30
else
render_api_error! ( result [ :message ] , result [ :http_status ] )
end
end
desc 'Download the artifacts file for job' do
2023-03-04 22:38:38 +05:30
http_codes [ [ 200 , 'Download allowed' ] ,
2023-01-13 00:05:48 +05:30
[ 401 , 'Unauthorized' ] ,
2020-07-28 23:09:34 +05:30
[ 403 , 'Forbidden' ] ,
[ 404 , 'Artifact not found' ] ]
end
params do
requires :id , type : Integer , desc : %q( Job's ID )
optional :token , type : String , desc : %q( Job's authentication token )
optional :direct_download , default : false , type : Boolean , desc : %q( Perform direct download from remote storage instead of proxying artifacts )
end
2023-01-13 00:05:48 +05:30
route_setting :authentication , job_token_allowed : true
2021-10-27 15:23:28 +05:30
get '/:id/artifacts' , feature_category : :build_artifacts do
2023-01-13 00:05:48 +05:30
authenticate_job_via_dependent_job!
2020-07-28 23:09:34 +05:30
2023-01-13 00:05:48 +05:30
present_artifacts_file! ( current_job . artifacts_file , supports_direct_download : params [ :direct_download ] )
2020-07-28 23:09:34 +05:30
end
end
end
end
end