diff options
Diffstat (limited to 'lib/api/ci')
-rw-r--r-- | lib/api/ci/helpers/runner.rb | 30 | ||||
-rw-r--r-- | lib/api/ci/job_artifacts.rb | 6 | ||||
-rw-r--r-- | lib/api/ci/jobs.rb | 94 | ||||
-rw-r--r-- | lib/api/ci/pipeline_schedules.rb | 144 | ||||
-rw-r--r-- | lib/api/ci/pipelines.rb | 151 | ||||
-rw-r--r-- | lib/api/ci/resource_groups.rb | 54 | ||||
-rw-r--r-- | lib/api/ci/runner.rb | 75 | ||||
-rw-r--r-- | lib/api/ci/runners.rb | 292 | ||||
-rw-r--r-- | lib/api/ci/secure_files.rb | 4 | ||||
-rw-r--r-- | lib/api/ci/triggers.rb | 66 | ||||
-rw-r--r-- | lib/api/ci/variables.rb | 39 |
11 files changed, 643 insertions, 312 deletions
diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb index 269f2fa7ddc..be4d82bc500 100644 --- a/lib/api/ci/helpers/runner.rb +++ b/lib/api/ci/helpers/runner.rb @@ -53,7 +53,7 @@ module API # HTTP status codes to terminate the job on GitLab Runner: # - 403 - def authenticate_job!(require_running: true, heartbeat_runner: false) + def authenticate_job!(heartbeat_runner: false) job = current_job # 404 is not returned here because we want to terminate the job if it's @@ -66,10 +66,7 @@ module API forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete? forbidden!('Job has been erased!') if job.erased? - - if require_running - job_forbidden!(job, 'Job is not running') unless job.running? - end + 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 @@ -87,9 +84,9 @@ module API end def authenticate_job_via_dependent_job! - forbidden! unless current_authenticated_job + authenticate! forbidden! unless current_job - forbidden! unless can?(current_authenticated_job.user, :read_build, current_job) + forbidden! unless can?(current_user, :read_build, current_job) end def current_job @@ -106,21 +103,6 @@ module API end end - # TODO: Replace this with `#current_authenticated_job from API::Helpers` - # after the feature flag `ci_authenticate_running_job_token_for_artifacts` - # is removed. - # - # For the time being, this needs to be overridden because the API - # GET api/v4/jobs/:id/artifacts - # needs to allow requests using token whose job is not running. - # - # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83713#note_942368526 - def current_authenticated_job - strong_memoize(:current_authenticated_job) do - ::Ci::AuthJobFinder.new(token: job_token).execute - end - end - # 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 @@ -151,10 +133,6 @@ module API { config: attributes_for_keys(%w(gpus), params.dig('info', 'config')) } end - def request_using_running_job_token? - current_job.present? && current_authenticated_job.present? && current_job != current_authenticated_job - end - def metrics strong_memoize(:metrics) { ::Gitlab::Ci::Runner::Metrics.new } end diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb index 37c7cc73c46..352ad04c982 100644 --- a/lib/api/ci/job_artifacts.rb +++ b/lib/api/ci/job_artifacts.rb @@ -19,7 +19,7 @@ module API prepend_mod_with('API::Ci::JobArtifacts') # rubocop: disable Cop/InjectEnterpriseEditionModule params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Download the artifacts archive from a job' do @@ -38,7 +38,7 @@ module API latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name]) authorize_read_job_artifacts!(latest_build) - present_artifacts_file!(latest_build.artifacts_file, project: latest_build.project) + present_artifacts_file!(latest_build.artifacts_file) end desc 'Download a specific file from artifacts archive from a ref' do @@ -80,7 +80,7 @@ module API build = find_build!(params[:job_id]) authorize_read_job_artifacts!(build) - present_artifacts_file!(build.artifacts_file, project: build.project) + present_artifacts_file!(build.artifacts_file) end desc 'Download a specific file from artifacts archive' do diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index 6049993bf6f..9e41e1c0d8f 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -11,12 +11,12 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end helpers do params :optional_scope do - optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show', + optional :scope, type: Array[String], desc: 'The scope of builds to show', values: ::CommitStatus::AVAILABLE_STATUSES, coerce_with: ->(scope) { case scope @@ -29,12 +29,19 @@ module API else ['unknown'] end - } + }, + documentation: { example: %w[pending running] } end end desc 'Get a projects jobs' do - success Entities::Ci::Job + success code: 200, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do use :optional_scope @@ -53,10 +60,15 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Get a specific job of a project' do - success Entities::Ci::Job + success code: 200, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a job' + requires :job_id, type: Integer, desc: 'The ID of a job', documentation: { example: 88 } end get ':id/jobs/:job_id', urgency: :low, feature_category: :continuous_integration do authorize_read_builds! @@ -69,9 +81,16 @@ module API # TODO: We should use `present_disk_file!` and leave this implementation for backward compatibility (when build trace # is saved in the DB instead of file). But before that, we need to consider how to replace the value of # `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse. - desc 'Get a trace of a specific job of a project' + desc 'Get a trace of a specific job of a project' do + success code: 200, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + end params do - requires :job_id, type: Integer, desc: 'The ID of a job' + requires :job_id, type: Integer, desc: 'The ID of a job', documentation: { example: 88 } end get ':id/jobs/:job_id/trace', urgency: :low, feature_category: :continuous_integration do authorize_read_builds! @@ -90,10 +109,15 @@ module API end desc 'Cancel a specific job of a project' do - success Entities::Ci::Job + success code: 201, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a job' + requires :job_id, type: Integer, desc: 'The ID of a job', documentation: { example: 88 } end post ':id/jobs/:job_id/cancel', urgency: :low, feature_category: :continuous_integration do authorize_update_builds! @@ -107,10 +131,15 @@ module API end desc 'Retry a specific build of a project' do - success Entities::Ci::Job + success code: 201, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a build' + requires :job_id, type: Integer, desc: 'The ID of a build', documentation: { example: 88 } end post ':id/jobs/:job_id/retry', urgency: :low, feature_category: :continuous_integration do authorize_update_builds! @@ -128,10 +157,16 @@ module API end desc 'Erase job (remove artifacts and the trace)' do - success Entities::Ci::Job + success code: 201, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a build' + requires :job_id, type: Integer, desc: 'The ID of a build', documentation: { example: 88 } end post ':id/jobs/:job_id/erase', urgency: :low, feature_category: :continuous_integration do authorize_update_builds! @@ -148,15 +183,21 @@ module API end desc 'Trigger an actionable job (manual, delayed, etc)' do - success Entities::Ci::JobBasic detail 'This feature was added in GitLab 8.11' + success code: 200, model: Entities::Ci::JobBasic + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a Job' + requires :job_id, type: Integer, desc: 'The ID of a Job', documentation: { example: 88 } optional :job_variables_attributes, type: Array, desc: 'User defined variables that will be included when running the job' do - requires :key, type: String, desc: 'The name of the variable' - requires :value, type: String, desc: 'The value of the variable' + requires :key, type: String, desc: 'The name of the variable', documentation: { example: 'foo' } + requires :value, type: String, desc: 'The value of the variable', documentation: { example: 'bar' } end end @@ -183,7 +224,12 @@ module API resource :job do desc 'Get current job using job token' do - success Entities::Ci::Job + success code: 200, model: Entities::Ci::Job + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] end route_setting :authentication, job_token_allowed: true get '', feature_category: :continuous_integration, urgency: :low do @@ -194,6 +240,12 @@ module API desc 'Get current agents' do detail 'Retrieves a list of agents for the given job token' + success code: 200, model: Entities::Ci::Job + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] end route_setting :authentication, job_token_allowed: true get '/allowed_agents', urgency: :low, feature_category: :kubernetes_management do @@ -210,7 +262,7 @@ module API .select { |_role, role_access_level| role_access_level <= user_access_level } .map(&:first) - environment = if persisted_environment = current_authenticated_job.persisted_environment + environment = if persisted_environment = current_authenticated_job.actual_persisted_environment { tier: persisted_environment.tier, slug: persisted_environment.slug } end @@ -244,6 +296,8 @@ module API # current_authenticated_job will be nil if user is using # a valid authentication (like PRIVATE-TOKEN) that is not CI_JOB_TOKEN not_found!('Job') unless current_authenticated_job + + ::Gitlab::ApplicationContext.push(job: current_authenticated_job) end end end diff --git a/lib/api/ci/pipeline_schedules.rb b/lib/api/ci/pipeline_schedules.rb index 886c3509c51..afb3754f2ae 100644 --- a/lib/api/ci/pipeline_schedules.rb +++ b/lib/api/ci/pipeline_schedules.rb @@ -11,16 +11,24 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', + documentation: { example: 18 } end resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get all pipeline schedules' do - success Entities::Ci::PipelineSchedule + success code: 200, model: Entities::Ci::PipelineSchedule + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do use :pagination optional :scope, type: String, values: %w[active inactive], - desc: 'The scope of pipeline schedules' + desc: 'The scope of pipeline schedules', + documentation: { example: 'active' } end # rubocop: disable CodeReuse/ActiveRecord get ':id/pipeline_schedules' do @@ -33,34 +41,51 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Get a single pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 200, model: Entities::Ci::PipelineScheduleDetails + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } end get ':id/pipeline_schedules/:pipeline_schedule_id' do present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails, user: current_user end desc 'Get all pipelines triggered from a pipeline schedule' do - success Entities::Ci::PipelineBasic + success code: 200, model: Entities::Ci::PipelineBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule ID' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule ID', documentation: { example: 13 } end get ':id/pipeline_schedules/:pipeline_schedule_id/pipelines' do present paginate(pipeline_schedule.pipelines), with: Entities::Ci::PipelineBasic end desc 'Create a new pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 201, model: Entities::Ci::PipelineScheduleDetails + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :description, type: String, desc: 'The description of pipeline schedule' - requires :ref, type: String, desc: 'The branch/tag name will be triggered', allow_blank: false - requires :cron, type: String, desc: 'The cron' - optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone' - optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule' + requires :description, type: String, desc: 'The description of pipeline schedule', documentation: { example: 'Test schedule pipeline' } + requires :ref, type: String, desc: 'The branch/tag name will be triggered', allow_blank: false, documentation: { example: 'develop' } + requires :cron, type: String, desc: 'The cron', documentation: { example: '* * * * *' } + optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone', documentation: { example: 'Asia/Tokyo' } + optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule', documentation: { example: true } end post ':id/pipeline_schedules' do authorize! :create_pipeline_schedule, user_project @@ -77,15 +102,21 @@ module API end desc 'Edit a pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 200, model: Entities::Ci::PipelineScheduleDetails + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' - optional :description, type: String, desc: 'The description of pipeline schedule' - optional :ref, type: String, desc: 'The branch/tag name will be triggered' - optional :cron, type: String, desc: 'The cron' - optional :cron_timezone, type: String, desc: 'The timezone' - optional :active, type: Boolean, desc: 'The activation of pipeline schedule' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } + optional :description, type: String, desc: 'The description of pipeline schedule', documentation: { example: 'Test schedule pipeline' } + optional :ref, type: String, desc: 'The branch/tag name will be triggered', documentation: { example: 'develop' } + optional :cron, type: String, desc: 'The cron', documentation: { example: '* * * * *' } + optional :cron_timezone, type: String, desc: 'The timezone', documentation: { example: 'Asia/Tokyo' } + optional :active, type: Boolean, desc: 'The activation of pipeline schedule', documentation: { example: true } end put ':id/pipeline_schedules/:pipeline_schedule_id' do authorize! :update_pipeline_schedule, pipeline_schedule @@ -98,10 +129,16 @@ module API end desc 'Take ownership of a pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 201, model: Entities::Ci::PipelineScheduleDetails + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } end post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do authorize! :take_ownership_pipeline_schedule, pipeline_schedule @@ -114,10 +151,16 @@ module API end desc 'Delete a pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 204 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 412, message: 'Precondition Failed' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } end delete ':id/pipeline_schedules/:pipeline_schedule_id' do authorize! :admin_pipeline_schedule, pipeline_schedule @@ -127,9 +170,15 @@ module API desc 'Play a scheduled pipeline immediately' do detail 'This feature was added in GitLab 12.8' + success code: 201 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } end post ':id/pipeline_schedules/:pipeline_schedule_id/play' do authorize! :play_pipeline_schedule, pipeline_schedule @@ -145,13 +194,20 @@ module API end desc 'Create a new pipeline schedule variable' do - success Entities::Ci::Variable + success code: 201, model: Entities::Ci::Variable + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' - requires :key, type: String, desc: 'The key of the variable' - requires :value, type: String, desc: 'The value of the variable' - optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } + requires :key, type: String, desc: 'The key of the variable', documentation: { example: 'NEW_VARIABLE' } + requires :value, type: String, desc: 'The value of the variable', documentation: { example: 'new value' } + optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var', + documentation: { default: 'env_var' } end post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do authorize! :update_pipeline_schedule, pipeline_schedule @@ -166,13 +222,20 @@ module API end desc 'Edit a pipeline schedule variable' do - success Entities::Ci::Variable + success code: 200, model: Entities::Ci::Variable + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' - requires :key, type: String, desc: 'The key of the variable' - optional :value, type: String, desc: 'The value of the variable' - optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } + requires :key, type: String, desc: 'The key of the variable', documentation: { example: 'NEW_VARIABLE' } + optional :value, type: String, desc: 'The value of the variable', documentation: { example: 'new value' } + optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file', + documentation: { default: 'env_var' } end put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do authorize! :update_pipeline_schedule, pipeline_schedule @@ -185,11 +248,16 @@ module API end desc 'Delete a pipeline schedule variable' do - success Entities::Ci::Variable + success code: 202, model: Entities::Ci::Variable + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' - requires :key, type: String, desc: 'The key of the variable' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } + requires :key, type: String, desc: 'The key of the variable', documentation: { example: 'NEW_VARIABLE' } end delete ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do authorize! :admin_pipeline_schedule, pipeline_schedule diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 72a81330e71..c055512e54e 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -10,12 +10,17 @@ module API before { authenticate_non_get! } params do - requires :id, type: String, desc: 'The project ID' + requires :id, type: String, desc: 'The project ID or URL-encoded path', documentation: { example: 11 } end resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get all Pipelines of the project' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Ci::PipelineBasic + success status: 200, model: Entities::Ci::PipelineBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + is_array true end helpers do @@ -31,27 +36,39 @@ module API else ['unknown'] end - } + }, + documentation: { example: %w[pending running] } end end params do use :pagination optional :scope, type: String, values: %w[running pending finished branches tags], - desc: 'The scope of pipelines' + desc: 'The scope of pipelines', + documentation: { example: 'pending' } optional :status, type: String, values: ::Ci::HasStatus::AVAILABLE_STATUSES, - desc: 'The status of pipelines' - optional :ref, type: String, desc: 'The ref of pipelines' - optional :sha, type: String, desc: 'The sha of pipelines' - optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations' - optional :username, type: String, desc: 'The username of the user who triggered pipelines' - optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' - optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' + desc: 'The status of pipelines', + documentation: { example: 'pending' } + optional :ref, type: String, desc: 'The ref of pipelines', + documentation: { example: 'develop' } + optional :sha, type: String, desc: 'The sha of pipelines', + documentation: { example: 'a91957a858320c0e17f3a0eca7cfacbff50ea29a' } + optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations', + documentation: { example: false } + optional :username, type: String, desc: 'The username of the user who triggered pipelines', + documentation: { example: 'root' } + optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ', + documentation: { example: '2015-12-24T15:51:21.880Z' } + optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ', + documentation: { example: '2015-12-24T15:51:21.880Z' } optional :order_by, type: String, values: ::Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id', - desc: 'Order pipelines' + desc: 'Order pipelines', + documentation: { example: 'status' } optional :sort, type: String, values: %w[asc desc], default: 'desc', - desc: 'Sort pipelines' - optional :source, type: String, values: ::Ci::Pipeline.sources.keys + desc: 'Sort pipelines', + documentation: { example: 'asc' } + optional :source, type: String, values: ::Ci::Pipeline.sources.keys, + documentation: { example: 'push' } end get ':id/pipelines', urgency: :low, feature_category: :continuous_integration do authorize! :read_pipeline, user_project @@ -63,11 +80,22 @@ module API desc 'Create a new pipeline' do detail 'This feature was introduced in GitLab 8.14' - success Entities::Ci::Pipeline + success status: 201, model: Entities::Ci::Pipeline + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :ref, type: String, desc: 'Reference' - optional :variables, Array, desc: 'Array of variables available in the pipeline' + requires :ref, type: String, desc: 'Reference', + documentation: { example: 'develop' } + optional :variables, type: Array, desc: 'Array of variables available in the pipeline' do + optional :key, type: String, desc: 'The key of the variable', documentation: { example: 'UPLOAD_TO_S3' } + optional :value, type: String, desc: 'The value of the variable', documentation: { example: 'true' } + optional :variable_type, type: String, values: ::Ci::PipelineVariable.variable_types.keys, default: 'env_var', desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + end end post ':id/pipeline', urgency: :low, feature_category: :continuous_integration do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20711') @@ -89,12 +117,18 @@ module API end end - desc 'Gets a the latest pipeline for the project branch' do + desc 'Gets the latest pipeline for the project branch' do detail 'This feature was introduced in GitLab 12.3' - success Entities::Ci::Pipeline + success status: 200, model: Entities::Ci::Pipeline + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - optional :ref, type: String, desc: 'branch ref of pipeline' + optional :ref, type: String, desc: 'Branch ref of pipeline. Uses project default branch if not specified.', + documentation: { example: 'develop' } end get ':id/pipelines/latest', urgency: :low, feature_category: :continuous_integration do authorize! :read_pipeline, latest_pipeline @@ -104,10 +138,15 @@ module API desc 'Gets a specific pipeline for the project' do detail 'This feature was introduced in GitLab 8.11' - success Entities::Ci::Pipeline + success status: 200, model: Entities::Ci::Pipeline + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end get ':id/pipelines/:pipeline_id', urgency: :low, feature_category: :continuous_integration do authorize! :read_pipeline, pipeline @@ -116,10 +155,16 @@ module API end desc 'Get pipeline jobs' do - success Entities::Ci::Job + success status: 200, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } optional :include_retried, type: Boolean, default: false, desc: 'Includes retried jobs' use :optional_scope use :pagination @@ -140,10 +185,16 @@ module API end desc 'Get pipeline bridge jobs' do - success Entities::Ci::Bridge + success status: 200, model: Entities::Ci::Bridge + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } use :optional_scope use :pagination end @@ -163,10 +214,16 @@ module API desc 'Gets the variables for a given pipeline' do detail 'This feature was introduced in GitLab 11.11' - success Entities::Ci::Variable + success status: 200, model: Entities::Ci::Variable + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring, urgency: :low do authorize! :read_pipeline_variable, pipeline @@ -176,10 +233,15 @@ module API desc 'Gets the test report for a given pipeline' do detail 'This feature was introduced in GitLab 13.0.' - success TestReportEntity + success status: 200, model: TestReportEntity + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end get ':id/pipelines/:pipeline_id/test_report', feature_category: :code_testing, urgency: :low do authorize! :read_build, pipeline @@ -189,10 +251,15 @@ module API desc 'Gets the test report summary for a given pipeline' do detail 'This feature was introduced in GitLab 14.2' - success TestReportSummaryEntity + success status: 200, model: TestReportSummaryEntity + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end get ':id/pipelines/:pipeline_id/test_report_summary', feature_category: :code_testing do authorize! :read_build, pipeline @@ -205,7 +272,7 @@ module API http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end delete ':id/pipelines/:pipeline_id', urgency: :low, feature_category: :continuous_integration do authorize! :destroy_pipeline, pipeline @@ -219,10 +286,15 @@ module API desc 'Retry builds in the pipeline' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Ci::Pipeline + success status: 201, model: Entities::Ci::Pipeline + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end post ':id/pipelines/:pipeline_id/retry', urgency: :low, feature_category: :continuous_integration do authorize! :update_pipeline, pipeline @@ -238,10 +310,15 @@ module API desc 'Cancel all builds in the pipeline' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Ci::Pipeline + success status: 200, model: Entities::Ci::Pipeline + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end post ':id/pipelines/:pipeline_id/cancel', urgency: :low, feature_category: :continuous_integration do authorize! :update_pipeline, pipeline diff --git a/lib/api/ci/resource_groups.rb b/lib/api/ci/resource_groups.rb index ea6d3cc8fd4..79a9fe58a7d 100644 --- a/lib/api/ci/resource_groups.rb +++ b/lib/api/ci/resource_groups.rb @@ -5,17 +5,30 @@ module API class ResourceGroups < ::API::Base include PaginationParams + ci_resource_groups_tags = %w[ci_resource_groups] + + RESOURCE_GROUP_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS + .merge(key: API::NO_SLASH_URL_PART_REGEX) + before { authenticate! } feature_category :continuous_delivery urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, + types: [String, Integer], + desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all resource groups for this project' do + desc 'Get all resource groups for a project' do success Entities::Ci::ResourceGroup + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags ci_resource_groups_tags end params do use :pagination @@ -26,27 +39,38 @@ module API present paginate(user_project.resource_groups), with: Entities::Ci::ResourceGroup end - desc 'Get a single resource group' do + desc 'Get a specific resource group' do success Entities::Ci::ResourceGroup + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ci_resource_groups_tags end params do requires :key, type: String, desc: 'The key of the resource group' end - get ':id/resource_groups/:key' do + get ':id/resource_groups/:key', requirements: RESOURCE_GROUP_ENDPOINT_REQUIREMENTS do authorize! :read_resource_group, resource_group present resource_group, with: Entities::Ci::ResourceGroup end - desc 'List upcoming jobs of a resource group' do + desc 'List upcoming jobs for a specific resource group' do success Entities::Ci::JobBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags ci_resource_groups_tags end params do requires :key, type: String, desc: 'The key of the resource group' use :pagination end - get ':id/resource_groups/:key/upcoming_jobs' do + get ':id/resource_groups/:key/upcoming_jobs', requirements: RESOURCE_GROUP_ENDPOINT_REQUIREMENTS do authorize! :read_resource_group, resource_group authorize! :read_build, user_project @@ -57,15 +81,25 @@ module API present paginate(upcoming_processables), with: Entities::Ci::JobBasic end - desc 'Edit a resource group' do + desc 'Edit an existing resource group' do + detail "Updates an existing resource group's properties." success Entities::Ci::ResourceGroup + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ci_resource_groups_tags end params do requires :key, type: String, desc: 'The key of the resource group' - optional :process_mode, type: String, desc: 'The process mode', - values: ::Ci::ResourceGroup.process_modes.keys + + optional :process_mode, + type: String, + desc: 'The process mode of the resource group', + values: ::Ci::ResourceGroup.process_modes.keys end - put ':id/resource_groups/:key' do + put ':id/resource_groups/:key', requirements: RESOURCE_GROUP_ENDPOINT_REQUIREMENTS do authorize! :update_resource_group, resource_group if resource_group.update(declared_params(include_missing: false)) diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 2d2dcc544f9..c7d1887638a 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -8,25 +8,38 @@ module API content_type :txt, 'text/plain' resource :runners do - desc 'Registers a new Runner' do + desc 'Register a new runner' do + detail "Register a new runner for the instance" success Entities::Ci::RunnerRegistrationDetails - http_codes [[201, 'Runner was created'], [403, 'Forbidden']] + failure [[400, 'Bad Request'], [403, 'Forbidden']] end params do requires :token, type: String, desc: 'Registration token' optional :description, type: String, desc: %q(Runner's description) - optional :maintainer_note, type: String, desc: %q(Deprecated: Use :maintenance_note instead. Runner's maintenance notes) - optional :maintenance_note, type: String, desc: %q(Runner's maintenance notes) - optional :info, type: Hash, desc: %q(Runner's metadata) - optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Should runner be active' - optional :paused, type: Boolean, desc: 'Whether the runner should ignore new jobs' - optional :locked, type: Boolean, desc: 'Whether the runner should be locked for current project' + 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' optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, - desc: 'The access_level of the runner; `not_protected` or `ref_protected`' - optional :run_untagged, type: Boolean, desc: 'Whether the runner should handle untagged jobs' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: %q(List of Runner's tags) - optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this runner handles the job' - mutually_exclusive :maintainer_note, :maintainer_note + 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 mutually_exclusive :active, :paused end post '/', urgency: :low, feature_category: :runner do @@ -49,11 +62,12 @@ module API end end - desc 'Deletes a registered Runner' do - http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']] + desc 'Delete a registered runner' do + summary "Delete a runner by authentication token" + failure [[403, 'Forbidden']] end params do - requires :token, type: String, desc: %q(Runner's authentication token) + requires :token, type: String, desc: %q(The runner's authentication token) end delete '/', urgency: :low, feature_category: :runner do authenticate_runner! @@ -61,11 +75,12 @@ module API destroy_conditionally!(current_runner) { ::Ci::Runners::UnregisterRunnerService.new(current_runner, params[:token]).execute } end - desc 'Validates authentication credentials' do + desc 'Validate authentication credentials' do + summary "Verify authentication for a registered runner" http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']] end params do - requires :token, type: String, desc: %q(Runner's authentication token) + requires :token, type: String, desc: %q(The runner's authentication token) end post '/verify', urgency: :low, feature_category: :runner do authenticate_runner! @@ -75,6 +90,7 @@ module API desc 'Reset runner authentication token with current token' do success Entities::Ci::ResetTokenResult + failure [[403, 'Forbidden']] end params do requires :token, type: String, desc: 'The current authentication token of the runner' @@ -94,7 +110,8 @@ module API success Entities::Ci::JobRequest::Response http_codes [[201, 'Job was scheduled'], [204, 'No job for Runner'], - [403, 'Forbidden']] + [403, 'Forbidden'], + [409, 'Conflict']] end params do requires :token, type: String, desc: %q(Runner's authentication token) @@ -168,14 +185,14 @@ module API end end - desc 'Updates a job' do + desc 'Update a job' do http_codes [[200, 'Job was updated'], [202, 'Update accepted'], [400, 'Unknown parameters'], [403, 'Forbidden']] end params do - requires :token, type: String, desc: %q(Runners's authentication token) + requires :token, type: String, desc: %q(Runner's authentication token) requires :id, type: Integer, desc: %q(Job's ID) optional :state, type: String, desc: %q(Job's status: success, failed) optional :checksum, type: String, desc: %q(Job's trace CRC32 checksum) @@ -203,7 +220,7 @@ module API end end - desc 'Appends a patch to the job trace' do + desc 'Append a patch to the job trace' do http_codes [[202, 'Trace was patched'], [400, 'Missing Content-Range header'], [403, 'Forbidden'], @@ -285,14 +302,14 @@ module API end params do requires :id, type: Integer, desc: %q(Job's ID) - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware)) + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware)), documentation: { type: 'file' } optional :token, type: String, desc: %q(Job's authentication token) optional :expire_in, type: String, desc: %q(Specify when artifacts should expire) optional :artifact_type, type: String, desc: %q(The type of artifact), default: 'archive', values: ::Ci::JobArtifact.file_types.keys optional :artifact_format, type: String, desc: %q(The format of artifact), default: 'zip', values: ::Ci::JobArtifact.file_formats.keys - optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware)) + optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware)), documentation: { type: 'file' } end post '/:id/artifacts', feature_category: :build_artifacts, urgency: :low do not_allowed! unless Gitlab.config.artifacts.enabled @@ -317,6 +334,7 @@ module API desc 'Download the artifacts file for job' do http_codes [[200, 'Upload allowed'], + [401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Artifact not found']] end @@ -325,14 +343,11 @@ module API 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 + route_setting :authentication, job_token_allowed: true get '/:id/artifacts', feature_category: :build_artifacts do - if request_using_running_job_token? - authenticate_job_via_dependent_job! - else - authenticate_job!(require_running: false) - end + authenticate_job_via_dependent_job! - present_artifacts_file!(current_job.artifacts_file, project: current_job.project, supports_direct_download: params[:direct_download]) + present_artifacts_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download]) end end end diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 4b578f8b7e5..988c3f4f566 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -10,20 +10,98 @@ module API feature_category :runner urgency :low + helpers do + params :deprecated_filter_params do + optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES, + desc: 'Deprecated: Use `type` or `status` instead. The scope of specific runners to return' + end + + params :filter_params do + optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, desc: 'The type of runners to return' + optional :paused, type: Boolean, + desc: 'Whether to include only runners that are accepting or ignoring new jobs' + optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, + desc: 'The status of runners to return' + optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: 'A list of runner tags', documentation: { example: "['macos', 'shell']" } + use :pagination + end + + def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES) + return runners unless scope.present? + + unless allowed_scopes.include?(scope) + render_api_error!('Scope contains invalid value', 400) + end + + # Support deprecated scopes + if runners.respond_to?("deprecated_#{scope}") + scope = "deprecated_#{scope}" + end + + runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend + end + + def apply_filter(runners, params) + runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES) + runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) + runners = filter_runners(runners, params[:paused] ? 'paused' : 'active', allowed_scopes: %w[paused active]) if params.include?(:paused) + runners = runners.tagged_with(params[:tag_list]) if params[:tag_list] + + runners + end + + def get_runner(id) + runner = ::Ci::Runner.find(id) + not_found!('Runner') unless runner + runner + end + + def authenticate_show_runner!(runner) + return if runner.instance_type? || current_user.admin? + + forbidden!("No access granted") unless can?(current_user, :read_runner, runner) + end + + def authenticate_update_runner!(runner) + return if current_user.admin? + + forbidden!("No access granted") unless can?(current_user, :update_runner, runner) + end + + def authenticate_delete_runner!(runner) + return if current_user.admin? + + forbidden!("Runner associated with more than one project") if runner.runner_projects.count > 1 + forbidden!("No access granted") unless can?(current_user, :delete_runner, runner) + end + + def authenticate_enable_runner!(runner) + forbidden!("Runner is a group runner") if runner.group_type? + + return if current_user.admin? + + forbidden!("Runner is locked") if runner.locked? + forbidden!("No access granted") unless can?(current_user, :assign_runner, runner) + end + + def authenticate_list_runners_jobs!(runner) + return if current_user.admin? + + forbidden!("No access granted") unless can?(current_user, :read_builds, runner) + end + end + resource :runners do desc 'Get runners available for user' do + summary 'List owned runners' success Entities::Ci::Runner + failure [[400, 'Scope contains invalid value'], [401, 'Unauthorized']] + tags %w[runners] end params do - optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The scope of specific runners to show' - optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' - optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' - use :pagination + use :deprecated_filter_params + use :filter_params end get do runners = current_user.ci_owned_runners @@ -34,18 +112,16 @@ module API end desc 'Get all runners - shared and specific' do + summary 'List all runners' + detail 'Get a list of all runners in the GitLab instance (specific and shared). ' \ + 'Access is restricted to users with administrator access.' success Entities::Ci::Runner + failure [[400, 'Scope contains invalid value'], [401, 'Unauthorized']] + tags %w[runners] end params do - optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES, - desc: 'The scope of specific runners to show' - optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' - optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' - use :pagination + use :deprecated_filter_params + use :filter_params end get 'all' do authenticated_as_admin! @@ -58,10 +134,13 @@ module API end desc "Get runner's details" do + detail 'At least the Maintainer role is required to get runner details at the project and group level. ' \ + 'Instance-level runner details via this endpoint are available to all signed in users.' success Entities::Ci::RunnerDetails + failure [[401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']] end params do - requires :id, type: Integer, desc: 'The ID of the runner' + requires :id, type: Integer, desc: 'The ID of a runner' end get ':id' do runner = get_runner(params[:id]) @@ -71,19 +150,24 @@ module API end desc "Update runner's details" do + summary "Update details of a runner" success Entities::Ci::RunnerDetails + failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']] end params do - requires :id, type: Integer, desc: 'The ID of the runner' + requires :id, type: Integer, desc: 'The ID of a runner' optional :description, type: String, desc: 'The description of the runner' - optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Flag indicating whether the runner is allowed to receive jobs' - optional :paused, type: Boolean, desc: 'Flag indicating whether the runner should ignore new jobs' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a runner' - optional :run_untagged, type: Boolean, desc: 'Flag indicating whether the runner can execute untagged jobs' - optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' + optional :active, type: Boolean, desc: 'Deprecated: Use `paused` instead. Flag indicating whether the runner is allowed to receive jobs' + optional :paused, type: Boolean, desc: 'Specifies whether the runner should ignore new jobs' + optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: 'The list of tags for a runner', documentation: { example: "['macos', 'shell']" } + optional :run_untagged, type: Boolean, desc: 'Specifies whether the runner can execute untagged jobs' + optional :locked, type: Boolean, desc: 'Specifies whether the runner is locked' optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, - desc: 'The access_level of the runner' - optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' + desc: 'The access level of the runner' + optional :maximum_timeout, type: Integer, + desc: 'Maximum timeout that limits the amount of time (in seconds) ' \ + 'that runners can run jobs' at_least_one_of :description, :active, :paused, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout mutually_exclusive :active, :paused end @@ -101,10 +185,15 @@ module API end desc 'Remove a runner' do + summary 'Delete a runner' success Entities::Ci::Runner + failure [[401, 'Unauthorized'], [403, 'No access granted'], + [403, 'Runner associated with more than one project'], [404, 'Runner not found'], + [412, 'Precondition Failed']] + tags %w[runners] end params do - requires :id, type: Integer, desc: 'The ID of the runner' + requires :id, type: Integer, desc: 'The ID of a runner' end delete ':id' do runner = get_runner(params[:id]) @@ -115,13 +204,19 @@ module API end desc 'List jobs running on a runner' do + summary "List runner's jobs" + detail 'List jobs that are being processed or were processed by the specified runner. ' \ + 'The list of jobs is limited to projects where the user has at least the Reporter role.' success Entities::Ci::JobBasicWithProject + failure [[401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']] + tags %w[runners jobs] end params do - requires :id, type: Integer, desc: 'The ID of the runner' + requires :id, type: Integer, desc: 'The ID of a runner' optional :status, type: String, desc: 'Status of the job', values: ::Ci::Build::AVAILABLE_STATUSES - optional :order_by, type: String, desc: 'Order by `id` or not', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS - optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)' + optional :order_by, type: String, desc: 'Order by `id`', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS + optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by `asc` or `desc` order. ' \ + 'Specify `order_by` as well, including for `id`' use :pagination end get ':id/jobs' do @@ -143,7 +238,10 @@ module API end desc 'Reset runner authentication token' do + summary "Reset runner's authentication token" success Entities::Ci::ResetTokenResult + failure [[403, 'No access granted'], [404, 'Runner not found']] + tags %w[runners] end params do requires :id, type: Integer, desc: 'The ID of the runner' @@ -158,24 +256,24 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, + types: [String, Integer], + desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authorize_admin_project } desc 'Get runners available for project' do + summary "List project's runners" + detail 'List all runners available in the project, including from ancestor groups ' \ + 'and any allowed shared runners.' success Entities::Ci::Runner + failure [[400, 'Scope contains invalid value'], [403, 'No access granted']] + tags %w[runners projects] end params do - optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES, - desc: 'The scope of specific runners to show' - optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' - optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' - use :pagination + use :deprecated_filter_params + use :filter_params end get ':id/runners' do runners = ::Ci::Runner.owned_or_instance_wide(user_project.id) @@ -187,11 +285,16 @@ module API present paginate(runners), with: Entities::Ci::Runner end - desc 'Enable a runner for a project' do + desc 'Enable a runner in project' do + detail "Enable an available specific runner in the project." success Entities::Ci::Runner + failure [[400, 'Bad Request'], + [403, 'No access granted'], [403, 'Runner is a group runner'], [403, 'Runner is locked'], + [404, 'Runner not found']] + tags %w[runners projects] end params do - requires :runner_id, type: Integer, desc: 'The ID of the runner' + requires :runner_id, type: Integer, desc: 'The ID of a runner' end post ':id/runners' do runner = get_runner(params[:runner_id]) @@ -205,10 +308,17 @@ module API end desc "Disable project's runner" do + summary "Disable a specific runner from the project" + detail "It works only if the project isn't the only project associated with the specified runner. " \ + "If so, an error is returned. Use the call to delete a runner instead." success Entities::Ci::Runner + failure [[400, 'Bad Request'], + [403, 'Only one project associated with the runner. Please remove the runner instead'], + [404, 'Runner not found'], [412, 'Precondition Failed']] + tags %w[runners projects] end params do - requires :runner_id, type: Integer, desc: 'The ID of the runner' + requires :runner_id, type: Integer, desc: 'The ID of a runner' end # rubocop: disable CodeReuse/ActiveRecord delete ':id/runners/:runner_id' do @@ -230,16 +340,15 @@ module API before { authorize_admin_group } desc 'Get runners available for group' do + summary "List group's runners" + detail 'List all runners available in the group as well as its ancestor groups, ' \ + 'including any allowed shared runners.' success Entities::Ci::Runner + failure [[400, 'Scope contains invalid value'], [403, 'Forbidden']] + tags %w[runners groups] end params do - optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' - optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' - use :pagination + use :filter_params end get ':id/runners' do runners = ::Ci::Runner.group_or_instance_wide(user_group) @@ -252,8 +361,11 @@ module API resource :runners do before { authenticate_non_get! } - desc 'Resets runner registration token' do + desc 'Reset runner registration token' do + summary "Reset instance's runner registration token" success Entities::Ci::ResetTokenResult + failure [[403, 'Forbidden']] + tags %w[runners groups] end post 'reset_registration_token' do authorize! :update_runners_registration_token, ApplicationSetting.current @@ -269,8 +381,11 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authenticate_non_get! } - desc 'Resets runner registration token' do + desc 'Reset runner registration token' do + summary "Reset the runner registration token for a project" success Entities::Ci::ResetTokenResult + failure [[401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Project Not Found']] + tags %w[runners projects] end post ':id/runners/reset_registration_token' do project = find_project! user_project.id @@ -287,8 +402,11 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authenticate_non_get! } - desc 'Resets runner registration token' do + desc 'Reset runner registration token' do + summary "Reset the runner registration token for a group" success Entities::Ci::ResetTokenResult + failure [[401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Group Not Found']] + tags %w[runners groups] end post ':id/runners/reset_registration_token' do group = find_group! user_group.id @@ -298,72 +416,6 @@ module API present group.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult end end - - helpers do - def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES) - return runners unless scope.present? - - unless allowed_scopes.include?(scope) - render_api_error!('Scope contains invalid value', 400) - end - - # Support deprecated scopes - if runners.respond_to?("deprecated_#{scope}") - scope = "deprecated_#{scope}" - end - - runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend - end - - def apply_filter(runners, params) - runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES) - runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) - runners = filter_runners(runners, params[:paused] ? 'paused' : 'active', allowed_scopes: %w[paused active]) if params.include?(:paused) - runners = runners.tagged_with(params[:tag_list]) if params[:tag_list] - - runners - end - - def get_runner(id) - runner = ::Ci::Runner.find(id) - not_found!('Runner') unless runner - runner - end - - def authenticate_show_runner!(runner) - return if runner.instance_type? || current_user.admin? - - forbidden!("No access granted") unless can?(current_user, :read_runner, runner) - end - - def authenticate_update_runner!(runner) - return if current_user.admin? - - forbidden!("No access granted") unless can?(current_user, :update_runner, runner) - end - - def authenticate_delete_runner!(runner) - return if current_user.admin? - - forbidden!("Runner associated with more than one project") if runner.runner_projects.count > 1 - forbidden!("No access granted") unless can?(current_user, :delete_runner, runner) - end - - def authenticate_enable_runner!(runner) - forbidden!("Runner is a group runner") if runner.group_type? - - return if current_user.admin? - - forbidden!("Runner is locked") if runner.locked? - forbidden!("No access granted") unless can?(current_user, :assign_runner, runner) - end - - def authenticate_list_runners_jobs!(runner) - return if current_user.admin? - - forbidden!("No access granted") unless can?(current_user, :read_builds, runner) - end - end end end end diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb index 511b6e06cd3..dd628a3413f 100644 --- a/lib/api/ci/secure_files.rb +++ b/lib/api/ci/secure_files.rb @@ -16,7 +16,7 @@ module API default_format :json params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -61,7 +61,7 @@ module API desc 'Upload a Secure File' params do requires :name, type: String, desc: 'The name of the file' - requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The secure file to be uploaded' + requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The secure file to be uploaded', documentation: { type: 'file' } end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true post ':id/secure_files' do diff --git a/lib/api/ci/triggers.rb b/lib/api/ci/triggers.rb index c49f1c9e9e1..c202d188e43 100644 --- a/lib/api/ci/triggers.rb +++ b/lib/api/ci/triggers.rb @@ -11,16 +11,26 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', + documentation: { example: 18 } end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Trigger a GitLab project pipeline' do - success Entities::Ci::Pipeline + success code: 201, model: Entities::Ci::Pipeline + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false - requires :token, type: String, desc: 'The unique token of trigger or job token' - optional :variables, type: Hash, desc: 'The list of variables to be injected into build' + requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false, + documentation: { example: 'develop' } + requires :token, type: String, desc: 'The unique token of trigger or job token', + documentation: { example: '6d056f63e50fe6f8c5f8f4aa10edb7' } + optional :variables, type: Hash, desc: 'The list of variables to be injected into build', + documentation: { example: { VAR1: "value1", VAR2: "value2" } } end post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20758') @@ -47,7 +57,13 @@ module API end desc 'Get triggers list' do - success Entities::Trigger + success code: 200, model: Entities::Trigger + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do use :pagination @@ -64,10 +80,15 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Get specific trigger of a project' do - success Entities::Trigger + success code: 200, model: Entities::Trigger + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :trigger_id, type: Integer, desc: 'The trigger ID' + requires :trigger_id, type: Integer, desc: 'The trigger ID', documentation: { example: 10 } end get ':id/triggers/:trigger_id' do authenticate! @@ -80,10 +101,17 @@ module API end desc 'Create a trigger' do - success Entities::Trigger + success code: 201, model: Entities::Trigger + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :description, type: String, desc: 'The trigger description' + requires :description, type: String, desc: 'The trigger description', + documentation: { example: 'my trigger description' } end post ':id/triggers' do authenticate! @@ -100,7 +128,13 @@ module API end desc 'Update a trigger' do - success Entities::Trigger + success code: 200, model: Entities::Trigger + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do requires :trigger_id, type: Integer, desc: 'The trigger ID' @@ -123,10 +157,16 @@ module API end desc 'Delete a trigger' do - success Entities::Trigger + success code: 204 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 412, message: 'Precondition Failed' } + ] end params do - requires :trigger_id, type: Integer, desc: 'The trigger ID' + requires :trigger_id, type: Integer, desc: 'The trigger ID', documentation: { example: 10 } end delete ':id/triggers/:trigger_id' do authenticate! diff --git a/lib/api/ci/variables.rb b/lib/api/ci/variables.rb index c9e1d115d03..5a6b5987228 100644 --- a/lib/api/ci/variables.rb +++ b/lib/api/ci/variables.rb @@ -13,12 +13,13 @@ module API helpers ::API::Helpers::VariablesHelpers params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID of a project or URL-encoded NAMESPACE/PROJECT_NAME of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get project variables' do success Entities::Ci::Variable + tags %w[ci_variables] end params do use :pagination @@ -28,13 +29,15 @@ module API present paginate(variables), with: Entities::Ci::Variable end - desc 'Get a specific variable from a project' do + desc 'Get the details of a single variable from a project' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Variable Not Found' }] + tags %w[ci_variables] end params do - requires :key, type: String, desc: 'The key of the variable' + requires :key, type: String, desc: 'The key of a variable' optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production' do - optional :environment_scope, type: String, desc: 'The environment scope of the variable' + optional :environment_scope, type: String, desc: 'The environment scope of a variable' end end # rubocop: disable CodeReuse/ActiveRecord @@ -48,13 +51,17 @@ module API desc 'Create a new variable in a project' do success Entities::Ci::Variable + failure [{ code: 400, message: '400 Bad Request' }] + tags %w[ci_variables] end + route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do - requires :key, type: String, desc: 'The key of the variable' - requires :value, type: String, desc: 'The value of the variable' + requires :key, type: String, desc: 'The key of a variable' + requires :value, type: String, desc: 'The value of a variable' optional :protected, type: Boolean, desc: 'Whether the variable is protected' optional :masked, type: Boolean, desc: 'Whether the variable is masked' - optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + optional :raw, type: Boolean, desc: 'Whether the variable will be expanded' + optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of the variable. Default: env_var' optional :environment_scope, type: String, desc: 'The environment_scope of the variable' end post ':id/variables' do @@ -73,16 +80,20 @@ module API desc 'Update an existing variable from a project' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Variable Not Found' }] + tags %w[ci_variables] end + route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do - optional :key, type: String, desc: 'The key of the variable' - optional :value, type: String, desc: 'The value of the variable' + optional :key, type: String, desc: 'The key of a variable' + optional :value, type: String, desc: 'The value of a variable' optional :protected, type: Boolean, desc: 'Whether the variable is protected' optional :masked, type: Boolean, desc: 'Whether the variable is masked' - optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' - optional :environment_scope, type: String, desc: 'The environment_scope of the variable' + optional :environment_scope, type: String, desc: 'The environment_scope of a variable' + optional :raw, type: Boolean, desc: 'Whether the variable will be expanded' + optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of the variable. Default: env_var' optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production' do - optional :environment_scope, type: String, desc: 'The environment scope of the variable' + optional :environment_scope, type: String, desc: 'The environment scope of a variable' end end # rubocop: disable CodeReuse/ActiveRecord @@ -106,9 +117,11 @@ module API desc 'Delete an existing variable from a project' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Variable Not Found' }] + tags %w[ci_variables] end params do - requires :key, type: String, desc: 'The key of the variable' + requires :key, type: String, desc: 'The key of a variable' optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production' do optional :environment_scope, type: String, desc: 'The environment scope of the variable' end |