diff --git a/ee/lib/api/project_mirror.rb b/ee/lib/api/project_mirror.rb index 707ed4d4c8988e0a8318eee2959b62c58aa2d6ec..482f6fbf20ff510d7c7a0152fbb38c36264ad3ed 100644 --- a/ee/lib/api/project_mirror.rb +++ b/ee/lib/api/project_mirror.rb @@ -49,7 +49,7 @@ def project end def process_pull_request - external_pull_request = Ci::ExternalPullRequests::ProcessGithubEventService.new(project, mirror_user).execute(params) + external_pull_request = ::Ci::ExternalPullRequests::ProcessGithubEventService.new(project, mirror_user).execute(params) if external_pull_request render_validation_error!(external_pull_request) diff --git a/lib/api/api.rb b/lib/api/api.rb index fb67258f3312a9ef5013ad121356545ad3ca8787..1cdb500e6ac60aa177050a2e406eea05294b656c 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -131,6 +131,8 @@ class API < Grape::API mount ::API::Boards mount ::API::Branches mount ::API::BroadcastMessages + mount ::API::Ci::Runner + mount ::API::Ci::Runners mount ::API::Commits mount ::API::CommitStatuses mount ::API::ContainerRegistryEvent @@ -195,8 +197,6 @@ class API < Grape::API mount ::API::Release::Links mount ::API::RemoteMirrors mount ::API::Repositories - mount ::API::Runner - mount ::API::Runners mount ::API::Search mount ::API::Services mount ::API::Settings diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb new file mode 100644 index 0000000000000000000000000000000000000000..3116af24ff5d42b2f01c4d8f668f0f153700f530 --- /dev/null +++ b/lib/api/ci/runner.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +module API + module Ci + class Runner < Grape::API + helpers ::API::Helpers::Runner + + resource :runners do + desc 'Registers a new Runner' do + success Entities::RunnerRegistrationDetails + http_codes [[201, 'Runner was created'], [403, 'Forbidden']] + end + params do + requires :token, type: String, desc: 'Registration token' + optional :description, type: String, desc: %q(Runner's description) + optional :info, type: Hash, desc: %q(Runner's metadata) + optional :active, type: Boolean, desc: 'Should Runner be active' + optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' + optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, + desc: 'The access_level of the runner' + optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' + optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) + optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' + end + post '/' do + attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout]) + .merge(get_runner_details_from_request) + + attributes = + if runner_registration_token_valid? + # Create shared runner. Requires admin access + attributes.merge(runner_type: :instance_type) + elsif project = Project.find_by_runners_token(params[:token]) + # Create a specific runner for the project + attributes.merge(runner_type: :project_type, projects: [project]) + elsif group = Group.find_by_runners_token(params[:token]) + # Create a specific runner for the group + attributes.merge(runner_type: :group_type, groups: [group]) + else + forbidden! + end + + runner = ::Ci::Runner.create(attributes) + + if runner.persisted? + present runner, with: Entities::RunnerRegistrationDetails + else + render_validation_error!(runner) + end + end + + desc 'Deletes a registered Runner' do + http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']] + end + params do + requires :token, type: String, desc: %q(Runner's authentication token) + end + delete '/' do + authenticate_runner! + + runner = ::Ci::Runner.find_by_token(params[:token]) + + destroy_conditionally!(runner) + end + + desc 'Validates authentication credentials' do + http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']] + end + params do + requires :token, type: String, desc: %q(Runner's authentication token) + end + post '/verify' do + authenticate_runner! + status 200 + end + end + + resource :jobs do + before do + Gitlab::ApplicationContext.push( + user: -> { current_job&.user }, + project: -> { current_job&.project } + ) + end + + desc 'Request a job' do + success Entities::JobRequest::Response + http_codes [[201, 'Job was scheduled'], + [204, 'No job for Runner'], + [403, 'Forbidden']] + 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) + 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 + post '/request' do + 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 + Gitlab::Metrics.add_event(:build_found) + present ::Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response + 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 + + desc 'Updates a job' do + http_codes [[200, 'Job was updated'], [403, 'Forbidden']] + end + params do + requires :token, type: String, desc: %q(Runners's authentication token) + requires :id, type: Integer, desc: %q(Job's ID) + optional :trace, type: String, desc: %q(Job's full trace) + optional :state, type: String, desc: %q(Job's status: success, failed) + optional :failure_reason, type: String, desc: %q(Job's failure_reason) + end + put '/:id' do + job = authenticate_job! + + job.trace.set(params[:trace]) if params[:trace] + + Gitlab::Metrics.add_event(:update_build) + + case params[:state].to_s + when 'running' + job.touch if job.needs_touch? + when 'success' + job.success! + when 'failed' + job.drop!(params[:failure_reason] || :unknown_failure) + end + end + + desc 'Appends a patch to the job trace' do + 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) + end + patch '/:id/trace' do + job = authenticate_job! + + error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range') + content_range = request.headers['Content-Range'] + content_range = content_range.split('-') + + # TODO: + # it seems that `Content-Range` as formatted by runner is wrong, + # the `byte_end` should point to final byte, but it points byte+1 + # that means that we have to calculate end of body, + # as we cannot use `content_length[1]` + # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275 + + body_data = request.body.read + body_start = content_range[0].to_i + body_end = body_start + body_data.bytesize + + stream_size = job.trace.append(body_data, body_start) + unless stream_size == body_end + break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" }) + end + + status 202 + header 'Job-Status', job.status + header 'Range', "0-#{stream_size}" + header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s + end + + desc 'Authorize artifacts uploading for job' do + 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) + optional :filesize, type: Integer, desc: %q(Artifacts filesize) + optional :artifact_type, type: String, desc: %q(The type of artifact), + default: 'archive', values: ::Ci::JobArtifact.file_types.keys + end + post '/:id/artifacts/authorize' do + not_allowed! unless Gitlab.config.artifacts.enabled + require_gitlab_workhorse! + Gitlab::Workhorse.verify_api_request!(headers) + + job = authenticate_job! + + service = ::Ci::AuthorizeJobArtifactService.new(job, params, max_size: max_artifacts_size(job)) + + forbidden! if service.forbidden? + file_too_large! if service.too_large? + + status 200 + content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE + service.headers + end + + desc 'Upload artifacts for job' do + success Entities::JobRequest::Response + 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) + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware)) + 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)) + end + post '/:id/artifacts' do + not_allowed! unless Gitlab.config.artifacts.enabled + require_gitlab_workhorse! + + job = authenticate_job! + + artifacts = params[:file] + metadata = params[:metadata] + + file_too_large! unless artifacts.size < max_artifacts_size(job) + + result = ::Ci::CreateJobArtifactsService.new(job.project).execute(job, artifacts, params, metadata_file: metadata) + + if result[:status] == :success + status :created + else + render_api_error!(result[:message], result[:http_status]) + end + end + + desc 'Download the artifacts file for job' do + http_codes [[200, 'Upload allowed'], + [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 + get '/:id/artifacts' do + job = authenticate_job!(require_running: false) + + present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download]) + end + end + end + end +end diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb new file mode 100644 index 0000000000000000000000000000000000000000..a4ef54534bac0f45708b37e8c1a8e203f222f7b9 --- /dev/null +++ b/lib/api/ci/runners.rb @@ -0,0 +1,289 @@ +# frozen_string_literal: true + +module API + module Ci + class Runners < Grape::API + include PaginationParams + + before { authenticate! } + + resource :runners do + desc 'Get runners available for user' do + success Entities::Runner + 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 :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, + desc: 'The status of the runners to show' + optional :tag_list, type: Array[String], desc: 'The tags of the runners to show' + use :pagination + end + get do + runners = current_user.ci_owned_runners + runners = filter_runners(runners, params[:scope], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) + 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 = runners.tagged_with(params[:tag_list]) if params[:tag_list] + + present paginate(runners), with: Entities::Runner + end + + desc 'Get all runners - shared and specific' do + success Entities::Runner + 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 :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, + desc: 'The status of the runners to show' + optional :tag_list, type: Array[String], desc: 'The tags of the runners to show' + use :pagination + end + get 'all' do + authenticated_as_admin! + + runners = ::Ci::Runner.all + runners = filter_runners(runners, params[:scope]) + 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 = runners.tagged_with(params[:tag_list]) if params[:tag_list] + + present paginate(runners), with: Entities::Runner + end + + desc "Get runner's details" do + success Entities::RunnerDetails + end + params do + requires :id, type: Integer, desc: 'The ID of the runner' + end + get ':id' do + runner = get_runner(params[:id]) + authenticate_show_runner!(runner) + + present runner, with: Entities::RunnerDetails, current_user: current_user + end + + desc "Update runner's details" do + success Entities::RunnerDetails + end + params do + requires :id, type: Integer, desc: 'The ID of the runner' + optional :description, type: String, desc: 'The description of the runner' + optional :active, type: Boolean, desc: 'The state of a runner' + optional :tag_list, type: Array[String], desc: 'The list of tags for a runner' + optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs' + optional :locked, type: Boolean, desc: 'Flag indicating 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' + at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout + end + put ':id' do + runner = get_runner(params.delete(:id)) + authenticate_update_runner!(runner) + update_service = ::Ci::UpdateRunnerService.new(runner) + + if update_service.update(declared_params(include_missing: false)) + present runner, with: Entities::RunnerDetails, current_user: current_user + else + render_validation_error!(runner) + end + end + + desc 'Remove a runner' do + success Entities::Runner + end + params do + requires :id, type: Integer, desc: 'The ID of the runner' + end + delete ':id' do + runner = get_runner(params[:id]) + + authenticate_delete_runner!(runner) + + destroy_conditionally!(runner) + end + + desc 'List jobs running on a runner' do + success Entities::JobBasicWithProject + end + params do + requires :id, type: Integer, desc: 'The ID of the 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)' + use :pagination + end + get ':id/jobs' do + runner = get_runner(params[:id]) + authenticate_list_runners_jobs!(runner) + + jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute + + present paginate(jobs), with: Entities::JobBasicWithProject + end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before { authorize_admin_project } + + desc 'Get runners available for project' do + success Entities::Runner + 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 :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, + desc: 'The status of the runners to show' + optional :tag_list, type: Array[String], desc: 'The tags of the runners to show' + use :pagination + end + get ':id/runners' do + runners = ::Ci::Runner.owned_or_instance_wide(user_project.id) + # scope is deprecated (for project runners), however api documentation still supports it. + # Not including them in `apply_filter` method as it's not supported for group runners + runners = filter_runners(runners, params[:scope]) + runners = apply_filter(runners, params) + + present paginate(runners), with: Entities::Runner + end + + desc 'Enable a runner for a project' do + success Entities::Runner + end + params do + requires :runner_id, type: Integer, desc: 'The ID of the runner' + end + post ':id/runners' do + runner = get_runner(params[:runner_id]) + authenticate_enable_runner!(runner) + + if runner.assign_to(user_project) + present runner, with: Entities::Runner + else + render_validation_error!(runner) + end + end + + desc "Disable project's runner" do + success Entities::Runner + end + params do + requires :runner_id, type: Integer, desc: 'The ID of the runner' + end + # rubocop: disable CodeReuse/ActiveRecord + delete ':id/runners/:runner_id' do + runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id]) + not_found!('Runner') unless runner_project + + runner = runner_project.runner + forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1 + + destroy_conditionally!(runner_project) + end + # rubocop: enable CodeReuse/ActiveRecord + end + + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before { authorize_admin_group } + + desc 'Get runners available for group' do + success Entities::Runner + end + params do + optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, + desc: 'The type of the runners to show' + optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, + desc: 'The status of the runners to show' + optional :tag_list, type: Array[String], desc: 'The tags of the runners to show' + use :pagination + end + get ':id/runners' do + runners = ::Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true) + runners = apply_filter(runners, params) + + present paginate(runners), with: Entities::Runner + 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 = 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.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_runner, runner) + end + end + end + end +end diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index b4c5d7869a22417e377069236f49ef74481f05b1..fcd317b2daacbb3c690504bd9836a3d86d536ad9 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -60,7 +60,7 @@ class CommitStatuses < Grape::API not_found! 'Commit' unless commit - # Since the CommitStatus is attached to Ci::Pipeline (in the future Pipeline) + # Since the CommitStatus is attached to ::Ci::Pipeline (in the future Pipeline) # We need to always have the pipeline object # To have a valid pipeline object that can be attached to specific MR # Other CI service needs to send `ref` diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb index 916f89649a5ff2b8a0d06d4dacd7a4e1da6c33e9..95aa587d0c790073cc49b8b1f2dbc48263f14611 100644 --- a/lib/api/group_variables.rb +++ b/lib/api/group_variables.rb @@ -48,7 +48,7 @@ class GroupVariables < Grape::API requires :value, type: String, desc: 'The value of the variable' optional :protected, type: String, desc: 'Whether the variable is protected' optional :masked, type: String, desc: 'Whether the variable is masked' - optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' end post ':id/variables' do variable_params = declared_params(include_missing: false) @@ -70,7 +70,7 @@ class GroupVariables < Grape::API optional :value, type: String, desc: 'The value of the variable' optional :protected, type: String, desc: 'Whether the variable is protected' optional :masked, type: String, desc: 'Whether the variable is masked' - optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' + optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' end # rubocop: disable CodeReuse/ActiveRecord put ':id/variables/:key' do diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 293d7ed9a6a72088d9ff70501477be99d134d95a..d3824597d0844f28c88b798d35020076ef6f0f75 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -60,7 +60,7 @@ def authenticate_job!(require_running: true) def current_job strong_memoize(:current_job) do - Ci::Build.find_by_id(params[:id]) + ::Ci::Build.find_by_id(params[:id]) end end diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 61a7fc107efff05c0f5c2f399d742a04c9aa88e1..9432b992d745f793df65707bca660917b30a3a19 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -160,7 +160,7 @@ class Jobs < Grape::API authorize!(:update_build, build) break forbidden!('Job is not retryable') unless build.retryable? - build = Ci::Build.retry(build, current_user) + build = ::Ci::Build.retry(build, current_user) present build, with: Entities::Job end diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb index edc99590cdb2789c3cf839730537ec68f4d5e8fc..7486518cd4a018701ab2fd88ba2be9ad99c6eadc 100644 --- a/lib/api/pipeline_schedules.rb +++ b/lib/api/pipeline_schedules.rb @@ -22,7 +22,7 @@ class PipelineSchedules < Grape::API get ':id/pipeline_schedules' do authorize! :read_pipeline_schedule, user_project - schedules = Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope]) + schedules = ::Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope]) .preload([:owner, :last_pipeline]) present paginate(schedules), with: Entities::PipelineSchedule end @@ -51,7 +51,7 @@ class PipelineSchedules < Grape::API post ':id/pipeline_schedules' do authorize! :create_pipeline_schedule, user_project - pipeline_schedule = Ci::CreatePipelineScheduleService + pipeline_schedule = ::Ci::CreatePipelineScheduleService .new(user_project, current_user, declared_params(include_missing: false)) .execute @@ -137,7 +137,7 @@ class PipelineSchedules < Grape::API 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' + 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' end post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do authorize! :update_pipeline_schedule, pipeline_schedule @@ -158,7 +158,7 @@ class PipelineSchedules < Grape::API 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' + optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' end put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do authorize! :update_pipeline_schedule, pipeline_schedule diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 20ac121dffa95bbab2f0a7b82a9843363ead5201..b27ff2d24f8e54a0326ca94a4a84b4bf334ccfdd 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -18,7 +18,7 @@ class Pipelines < Grape::API use :pagination optional :scope, type: String, values: %w[running pending finished branches tags], desc: 'The scope of pipelines' - optional :status, type: String, values: Ci::HasStatus::AVAILABLE_STATUSES, + 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' @@ -27,7 +27,7 @@ class Pipelines < Grape::API 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' - optional :order_by, type: String, values: Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id', + optional :order_by, type: String, values: ::Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id', desc: 'Order pipelines' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort pipelines' @@ -36,7 +36,7 @@ class Pipelines < Grape::API authorize! :read_pipeline, user_project authorize! :read_build, user_project - pipelines = Ci::PipelinesFinder.new(user_project, current_user, params).execute + pipelines = ::Ci::PipelinesFinder.new(user_project, current_user, params).execute present paginate(pipelines), with: Entities::PipelineBasic end @@ -57,9 +57,9 @@ class Pipelines < Grape::API .merge(variables_attributes: params[:variables]) .except(:variables) - new_pipeline = Ci::CreatePipelineService.new(user_project, - current_user, - pipeline_params) + new_pipeline = ::Ci::CreatePipelineService.new(user_project, + current_user, + pipeline_params) .execute(:api, ignore_skip_ci: true, save_on_errors: false) if new_pipeline.persisted? diff --git a/lib/api/runner.rb b/lib/api/runner.rb deleted file mode 100644 index 5f08ebe4a065c671186edae4e83c7d3dc3a260ce..0000000000000000000000000000000000000000 --- a/lib/api/runner.rb +++ /dev/null @@ -1,297 +0,0 @@ -# frozen_string_literal: true - -module API - class Runner < Grape::API - helpers ::API::Helpers::Runner - - resource :runners do - desc 'Registers a new Runner' do - success Entities::RunnerRegistrationDetails - http_codes [[201, 'Runner was created'], [403, 'Forbidden']] - end - params do - requires :token, type: String, desc: 'Registration token' - optional :description, type: String, desc: %q(Runner's description) - optional :info, type: Hash, desc: %q(Runner's metadata) - optional :active, type: Boolean, desc: 'Should Runner be active' - optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' - optional :access_level, type: String, values: Ci::Runner.access_levels.keys, - desc: 'The access_level of the runner' - optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' - optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) - optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' - end - post '/' do - attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout]) - .merge(get_runner_details_from_request) - - attributes = - if runner_registration_token_valid? - # Create shared runner. Requires admin access - attributes.merge(runner_type: :instance_type) - elsif project = Project.find_by_runners_token(params[:token]) - # Create a specific runner for the project - attributes.merge(runner_type: :project_type, projects: [project]) - elsif group = Group.find_by_runners_token(params[:token]) - # Create a specific runner for the group - attributes.merge(runner_type: :group_type, groups: [group]) - else - forbidden! - end - - runner = Ci::Runner.create(attributes) - - if runner.persisted? - present runner, with: Entities::RunnerRegistrationDetails - else - render_validation_error!(runner) - end - end - - desc 'Deletes a registered Runner' do - http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']] - end - params do - requires :token, type: String, desc: %q(Runner's authentication token) - end - delete '/' do - authenticate_runner! - - runner = Ci::Runner.find_by_token(params[:token]) - - destroy_conditionally!(runner) - end - - desc 'Validates authentication credentials' do - http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']] - end - params do - requires :token, type: String, desc: %q(Runner's authentication token) - end - post '/verify' do - authenticate_runner! - status 200 - end - end - - resource :jobs do - before do - Gitlab::ApplicationContext.push( - user: -> { current_job&.user }, - project: -> { current_job&.project } - ) - end - - desc 'Request a job' do - success Entities::JobRequest::Response - http_codes [[201, 'Job was scheduled'], - [204, 'No job for Runner'], - [403, 'Forbidden']] - 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) - 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 - post '/request' do - 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 - Gitlab::Metrics.add_event(:build_found) - present Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response - 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 - - desc 'Updates a job' do - http_codes [[200, 'Job was updated'], [403, 'Forbidden']] - end - params do - requires :token, type: String, desc: %q(Runners's authentication token) - requires :id, type: Integer, desc: %q(Job's ID) - optional :trace, type: String, desc: %q(Job's full trace) - optional :state, type: String, desc: %q(Job's status: success, failed) - optional :failure_reason, type: String, desc: %q(Job's failure_reason) - end - put '/:id' do - job = authenticate_job! - - job.trace.set(params[:trace]) if params[:trace] - - Gitlab::Metrics.add_event(:update_build) - - case params[:state].to_s - when 'running' - job.touch if job.needs_touch? - when 'success' - job.success! - when 'failed' - job.drop!(params[:failure_reason] || :unknown_failure) - end - end - - desc 'Appends a patch to the job trace' do - 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) - end - patch '/:id/trace' do - job = authenticate_job! - - error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range') - content_range = request.headers['Content-Range'] - content_range = content_range.split('-') - - # TODO: - # it seems that `Content-Range` as formatted by runner is wrong, - # the `byte_end` should point to final byte, but it points byte+1 - # that means that we have to calculate end of body, - # as we cannot use `content_length[1]` - # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275 - - body_data = request.body.read - body_start = content_range[0].to_i - body_end = body_start + body_data.bytesize - - stream_size = job.trace.append(body_data, body_start) - unless stream_size == body_end - break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" }) - end - - status 202 - header 'Job-Status', job.status - header 'Range', "0-#{stream_size}" - header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s - end - - desc 'Authorize artifacts uploading for job' do - 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) - optional :filesize, type: Integer, desc: %q(Artifacts filesize) - optional :artifact_type, type: String, desc: %q(The type of artifact), - default: 'archive', values: Ci::JobArtifact.file_types.keys - end - post '/:id/artifacts/authorize' do - not_allowed! unless Gitlab.config.artifacts.enabled - require_gitlab_workhorse! - Gitlab::Workhorse.verify_api_request!(headers) - - job = authenticate_job! - - service = Ci::AuthorizeJobArtifactService.new(job, params, max_size: max_artifacts_size(job)) - - forbidden! if service.forbidden? - file_too_large! if service.too_large? - - status 200 - content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - service.headers - end - - desc 'Upload artifacts for job' do - success Entities::JobRequest::Response - 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) - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware)) - 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)) - end - post '/:id/artifacts' do - not_allowed! unless Gitlab.config.artifacts.enabled - require_gitlab_workhorse! - - job = authenticate_job! - - artifacts = params[:file] - metadata = params[:metadata] - - file_too_large! unless artifacts.size < max_artifacts_size(job) - - result = Ci::CreateJobArtifactsService.new(job.project).execute(job, artifacts, params, metadata_file: metadata) - - if result[:status] == :success - status :created - else - render_api_error!(result[:message], result[:http_status]) - end - end - - desc 'Download the artifacts file for job' do - http_codes [[200, 'Upload allowed'], - [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 - get '/:id/artifacts' do - job = authenticate_job!(require_running: false) - - present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download]) - end - end - end -end diff --git a/lib/api/runners.rb b/lib/api/runners.rb deleted file mode 100644 index 43ee1dd1f71e2279b37d930f32675c087946a059..0000000000000000000000000000000000000000 --- a/lib/api/runners.rb +++ /dev/null @@ -1,287 +0,0 @@ -# frozen_string_literal: true - -module API - class Runners < Grape::API - include PaginationParams - - before { authenticate! } - - resource :runners do - desc 'Get runners available for user' do - success Entities::Runner - 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 :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], desc: 'The tags of the runners to show' - use :pagination - end - get do - runners = current_user.ci_owned_runners - runners = filter_runners(runners, params[:scope], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES) - 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 = runners.tagged_with(params[:tag_list]) if params[:tag_list] - - present paginate(runners), with: Entities::Runner - end - - desc 'Get all runners - shared and specific' do - success Entities::Runner - 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 :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], desc: 'The tags of the runners to show' - use :pagination - end - get 'all' do - authenticated_as_admin! - - runners = Ci::Runner.all - runners = filter_runners(runners, params[:scope]) - 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 = runners.tagged_with(params[:tag_list]) if params[:tag_list] - - present paginate(runners), with: Entities::Runner - end - - desc "Get runner's details" do - success Entities::RunnerDetails - end - params do - requires :id, type: Integer, desc: 'The ID of the runner' - end - get ':id' do - runner = get_runner(params[:id]) - authenticate_show_runner!(runner) - - present runner, with: Entities::RunnerDetails, current_user: current_user - end - - desc "Update runner's details" do - success Entities::RunnerDetails - end - params do - requires :id, type: Integer, desc: 'The ID of the runner' - optional :description, type: String, desc: 'The description of the runner' - optional :active, type: Boolean, desc: 'The state of a runner' - optional :tag_list, type: Array[String], desc: 'The list of tags for a runner' - optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs' - optional :locked, type: Boolean, desc: 'Flag indicating 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' - at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout - end - put ':id' do - runner = get_runner(params.delete(:id)) - authenticate_update_runner!(runner) - update_service = Ci::UpdateRunnerService.new(runner) - - if update_service.update(declared_params(include_missing: false)) - present runner, with: Entities::RunnerDetails, current_user: current_user - else - render_validation_error!(runner) - end - end - - desc 'Remove a runner' do - success Entities::Runner - end - params do - requires :id, type: Integer, desc: 'The ID of the runner' - end - delete ':id' do - runner = get_runner(params[:id]) - - authenticate_delete_runner!(runner) - - destroy_conditionally!(runner) - end - - desc 'List jobs running on a runner' do - success Entities::JobBasicWithProject - end - params do - requires :id, type: Integer, desc: 'The ID of the 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)' - use :pagination - end - get ':id/jobs' do - runner = get_runner(params[:id]) - authenticate_list_runners_jobs!(runner) - - jobs = Ci::RunnerJobsFinder.new(runner, params).execute - - present paginate(jobs), with: Entities::JobBasicWithProject - end - end - - params do - requires :id, type: String, desc: 'The ID of a project' - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before { authorize_admin_project } - - desc 'Get runners available for project' do - success Entities::Runner - 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 :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], desc: 'The tags of the runners to show' - use :pagination - end - get ':id/runners' do - runners = Ci::Runner.owned_or_instance_wide(user_project.id) - # scope is deprecated (for project runners), however api documentation still supports it. - # Not including them in `apply_filter` method as it's not supported for group runners - runners = filter_runners(runners, params[:scope]) - runners = apply_filter(runners, params) - - present paginate(runners), with: Entities::Runner - end - - desc 'Enable a runner for a project' do - success Entities::Runner - end - params do - requires :runner_id, type: Integer, desc: 'The ID of the runner' - end - post ':id/runners' do - runner = get_runner(params[:runner_id]) - authenticate_enable_runner!(runner) - - if runner.assign_to(user_project) - present runner, with: Entities::Runner - else - render_validation_error!(runner) - end - end - - desc "Disable project's runner" do - success Entities::Runner - end - params do - requires :runner_id, type: Integer, desc: 'The ID of the runner' - end - # rubocop: disable CodeReuse/ActiveRecord - delete ':id/runners/:runner_id' do - runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id]) - not_found!('Runner') unless runner_project - - runner = runner_project.runner - forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1 - - destroy_conditionally!(runner_project) - end - # rubocop: enable CodeReuse/ActiveRecord - end - - params do - requires :id, type: String, desc: 'The ID of a group' - end - resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before { authorize_admin_group } - - desc 'Get runners available for group' do - success Entities::Runner - end - params do - optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], desc: 'The tags of the runners to show' - use :pagination - end - get ':id/runners' do - runners = Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true) - runners = apply_filter(runners, params) - - present paginate(runners), with: Entities::Runner - 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 = 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.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_runner, runner) - end - end - end -end diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index e1829403941d57232e6441e79fcfa6f31dfcd95d..65e9118e3ef126e61d74baed6c64e68209248cb1 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -32,7 +32,7 @@ class Triggers < Grape::API project = find_project(params[:id]) not_found! unless project - result = Ci::PipelineTriggerService.new(project, nil, params).execute + result = ::Ci::PipelineTriggerService.new(project, nil, params).execute not_found! unless result if result[:http_status] diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 192b06b8a1b06ba09c813a57ae8520af07a00461..f3def756fe8da117caef87883f9e06b14db0d83d 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -56,7 +56,7 @@ def filter_variable_parameters(params) requires :value, type: String, desc: 'The value of the 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 :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 :environment_scope, type: String, desc: 'The environment_scope of the variable' end post ':id/variables' do @@ -80,7 +80,7 @@ def filter_variable_parameters(params) optional :value, type: String, desc: 'The value of the 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 :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' end # rubocop: disable CodeReuse/ActiveRecord diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/ci/runner_spec.rb similarity index 98% rename from spec/requests/api/runner_spec.rb rename to spec/requests/api/ci/runner_spec.rb index c977908149c26609df080b881a4d64a3514f9d81..6323d1f68e344318f80d4909bc4a17bb6c4699bc 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/ci/runner_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do include StubGitlabCalls include RedisHelpers include WorkhorseHelpers @@ -13,7 +13,7 @@ stub_feature_flags(ci_enable_live_trace: true) stub_gitlab_calls stub_application_setting(runners_registration_token: registration_token) - allow_any_instance_of(Ci::Runner).to receive(:cache_attributes) + allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes) end describe '/api/v4/runners' do @@ -38,7 +38,7 @@ it 'creates runner with default values' do post api('/runners'), params: { token: registration_token } - runner = Ci::Runner.first + runner = ::Ci::Runner.first expect(response).to have_gitlab_http_status(:created) expect(json_response['id']).to eq(runner.id) @@ -57,7 +57,7 @@ expect(response).to have_gitlab_http_status(:created) expect(project.runners.size).to eq(1) - runner = Ci::Runner.first + runner = ::Ci::Runner.first expect(runner.token).not_to eq(registration_token) expect(runner.token).not_to eq(project.runners_token) expect(runner).to be_project_type @@ -72,7 +72,7 @@ expect(response).to have_gitlab_http_status(:created) expect(group.runners.reload.size).to eq(1) - runner = Ci::Runner.first + runner = ::Ci::Runner.first expect(runner.token).not_to eq(registration_token) expect(runner.token).not_to eq(group.runners_token) expect(runner).to be_group_type @@ -88,7 +88,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.description).to eq('server.hostname') + expect(::Ci::Runner.first.description).to eq('server.hostname') end end @@ -100,7 +100,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2)) + expect(::Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2)) end end @@ -114,8 +114,8 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.run_untagged).to be false - expect(Ci::Runner.first.tag_list.sort).to eq(['tag']) + expect(::Ci::Runner.first.run_untagged).to be false + expect(::Ci::Runner.first.tag_list.sort).to eq(['tag']) end end @@ -141,7 +141,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.locked).to be true + expect(::Ci::Runner.first.locked).to be true end end @@ -154,7 +154,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.active).to be true + expect(::Ci::Runner.first.active).to be true end end @@ -166,7 +166,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.active).to be false + expect(::Ci::Runner.first.active).to be false end end end @@ -180,7 +180,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.ref_protected?).to be true + expect(::Ci::Runner.first.ref_protected?).to be true end end @@ -192,7 +192,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.ref_protected?).to be false + expect(::Ci::Runner.first.ref_protected?).to be false end end end @@ -205,7 +205,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.maximum_timeout).to eq(9000) + expect(::Ci::Runner.first.maximum_timeout).to eq(9000) end context 'when maximum job timeout is empty' do @@ -216,7 +216,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.maximum_timeout).to be_nil + expect(::Ci::Runner.first.maximum_timeout).to be_nil end end end @@ -232,7 +232,7 @@ } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value) + expect(::Ci::Runner.first.read_attribute(param.to_sym)).to eq(value) end end end @@ -243,7 +243,7 @@ headers: { 'X-Forwarded-For' => '123.111.123.111' } expect(response).to have_gitlab_http_status(:created) - expect(Ci::Runner.first.ip_address).to eq('123.111.123.111') + expect(::Ci::Runner.first.ip_address).to eq('123.111.123.111') end end @@ -271,7 +271,7 @@ delete api('/runners'), params: { token: runner.token } expect(response).to have_gitlab_http_status(:no_content) - expect(Ci::Runner.count).to eq(0) + expect(::Ci::Runner.count).to eq(0) end it_behaves_like '412 response' do @@ -537,7 +537,7 @@ end it 'creates persistent ref' do - expect_any_instance_of(Ci::PersistentRef).to receive(:create_ref) + expect_any_instance_of(::Ci::PersistentRef).to receive(:create_ref) .with(job.sha, "refs/#{Repository::REF_PIPELINES}/#{job.commit_id}") request_job info: { platform: :darwin } @@ -749,7 +749,7 @@ context 'when concurrently updating a job' do before do - expect_any_instance_of(Ci::Build).to receive(:run!) + expect_any_instance_of(::Ci::Build).to receive(:run!) .and_raise(ActiveRecord::StaleObjectError.new(nil, nil)) end @@ -890,7 +890,7 @@ let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) } before do - project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value') + project.variables << ::Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value') end shared_examples 'expected variables behavior' do @@ -1099,7 +1099,7 @@ def request_job(token = runner.token, **params) let_it_be(:project) { create(:project, :repository) } let(:runner) { create(:ci_runner, :project, projects: [project]) } - let(:service) { Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute } + let(:service) { ::Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute } let(:pipeline) { service[:pipeline] } let(:build) { pipeline.builds.first } let(:job) { {} } @@ -2258,7 +2258,7 @@ def authorize_artifacts_with_token_in_headers(params = {}, request_headers = hea FileUtils.remove_entry(new_tmpdir) end - it' "fails to post artifacts for outside of tmp path"' do + it 'fails to post artifacts for outside of tmp path' do upload_artifacts(file_upload, headers_with_token) expect(response).to have_gitlab_http_status(:bad_request) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb similarity index 98% rename from spec/requests/api/runners_spec.rb rename to spec/requests/api/ci/runners_spec.rb index 0726ab5e3d8584637d3448d02a515e0b574f53d4..670456e5dba32abf137a749cebfb3d81b9512de8 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Runners do +RSpec.describe API::Ci::Runners do let_it_be(:admin) { create(:user, :admin) } let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } @@ -266,7 +266,7 @@ delete api("/runners/#{unused_project_runner.id}", admin) expect(response).to have_gitlab_http_status(:no_content) - end.to change { Ci::Runner.project_type.count }.by(-1) + end.to change { ::Ci::Runner.project_type.count }.by(-1) end end @@ -493,7 +493,7 @@ def update_runner(id, user, args) delete api("/runners/#{shared_runner.id}", admin) expect(response).to have_gitlab_http_status(:no_content) - end.to change { Ci::Runner.instance_type.count }.by(-1) + end.to change { ::Ci::Runner.instance_type.count }.by(-1) end it_behaves_like '412 response' do @@ -507,7 +507,7 @@ def update_runner(id, user, args) delete api("/runners/#{project_runner.id}", admin) expect(response).to have_gitlab_http_status(:no_content) - end.to change { Ci::Runner.project_type.count }.by(-1) + end.to change { ::Ci::Runner.project_type.count }.by(-1) end end @@ -542,7 +542,7 @@ def update_runner(id, user, args) delete api("/runners/#{project_runner.id}", user) expect(response).to have_gitlab_http_status(:no_content) - end.to change { Ci::Runner.project_type.count }.by(-1) + end.to change { ::Ci::Runner.project_type.count }.by(-1) end it 'does not delete group runner with guest access' do @@ -574,7 +574,7 @@ def update_runner(id, user, args) delete api("/runners/#{group_runner_a.id}", user) expect(response).to have_gitlab_http_status(:no_content) - end.to change { Ci::Runner.group_type.count }.by(-1) + end.to change { ::Ci::Runner.group_type.count }.by(-1) end it 'deletes inherited group runner with owner access' do @@ -582,7 +582,7 @@ def update_runner(id, user, args) delete api("/runners/#{group_runner_b.id}", user) expect(response).to have_gitlab_http_status(:no_content) - end.to change { Ci::Runner.group_type.count }.by(-1) + end.to change { ::Ci::Runner.group_type.count }.by(-1) end it_behaves_like '412 response' do @@ -968,7 +968,7 @@ def update_runner(id, user, args) end it 'does not enable locked runner' do - project_runner2.update(locked: true) + project_runner2.update!(locked: true) expect do post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }