diff options
Diffstat (limited to 'lib/api')
85 files changed, 842 insertions, 411 deletions
diff --git a/lib/api/admin/ci/variables.rb b/lib/api/admin/ci/variables.rb index 6b0ff5e9395..8721d94d642 100644 --- a/lib/api/admin/ci/variables.rb +++ b/lib/api/admin/ci/variables.rb @@ -12,7 +12,7 @@ module API namespace 'ci' do namespace 'variables' do desc 'Get instance-level variables' do - success Entities::Variable + success Entities::Ci::Variable end params do use :pagination @@ -20,11 +20,11 @@ module API get '/' do variables = ::Ci::InstanceVariable.all - present paginate(variables), with: Entities::Variable + present paginate(variables), with: Entities::Ci::Variable end desc 'Get a specific variable from a group' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, type: String, desc: 'The key of the variable' @@ -35,11 +35,11 @@ module API break not_found!('InstanceVariable') unless variable - present variable, with: Entities::Variable + present variable, with: Entities::Ci::Variable end desc 'Create a new instance-level variable' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, @@ -69,14 +69,14 @@ module API variable = ::Ci::InstanceVariable.new(variable_params) if variable.save - present variable, with: Entities::Variable + present variable, with: Entities::Ci::Variable else render_validation_error!(variable) end end desc 'Update an existing instance-variable' do - success Entities::Variable + success Entities::Ci::Variable end params do optional :key, @@ -108,14 +108,14 @@ module API variable_params = declared_params(include_missing: false).except(:key) if variable.update(variable_params) - present variable, with: Entities::Variable + present variable, with: Entities::Ci::Variable else render_validation_error!(variable) end end desc 'Delete an existing instance-level variable' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, type: String, desc: 'The key of the variable' diff --git a/lib/api/api.rb b/lib/api/api.rb index a89dc0fa6fa..2be6792af5f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -56,6 +56,10 @@ module API ) end + before do + set_peek_enabled_for_current_request + end + # The locale is set to the current user's locale when `current_user` is loaded after { Gitlab::I18n.use_default_locale } @@ -116,6 +120,7 @@ module API # Ensure the namespace is right, otherwise we might load Grape::API::Helpers helpers ::API::Helpers helpers ::API::Helpers::CommonHelpers + helpers ::API::Helpers::PerformanceBarHelpers namespace do after do @@ -237,6 +242,7 @@ module API mount ::API::Internal::Base mount ::API::Internal::Pages + mount ::API::Internal::Kubernetes route :any, '*path' do error!('404 Not Found', 404) diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 4b87861a3de..59978962b1d 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -7,6 +7,7 @@ require 'rack/oauth2' module API module APIGuard extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize included do |base| # OAuth2 Resource Server Authentication @@ -64,10 +65,12 @@ module API end def find_user_from_sources - deploy_token_from_request || - find_user_from_bearer_token || - find_user_from_job_token || - find_user_from_warden + strong_memoize(:find_user_from_sources) do + deploy_token_from_request || + find_user_from_bearer_token || + find_user_from_job_token || + find_user_from_warden + end end private diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 5e9c2caf8f5..44f7610384e 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -40,12 +40,8 @@ module API repository = user_project.repository - if Feature.enabled?(:branch_list_keyset_pagination, user_project) - branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute(gitaly_pagination: true) - else - branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute - branches = paginate(::Kaminari.paginate_array(branches)) - end + branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false)) + branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder) merged_branch_names = repository.merged_branch_names(branches.map(&:name)) diff --git a/lib/api/ci/pipeline_schedules.rb b/lib/api/ci/pipeline_schedules.rb index 80ad8aa04dd..1afdb0ad34c 100644 --- a/lib/api/ci/pipeline_schedules.rb +++ b/lib/api/ci/pipeline_schedules.rb @@ -12,7 +12,7 @@ module API end resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get all pipeline schedules' do - success Entities::PipelineSchedule + success Entities::Ci::PipelineSchedule end params do use :pagination @@ -25,22 +25,22 @@ module API schedules = ::Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope]) .preload([:owner, :last_pipeline]) - present paginate(schedules), with: Entities::PipelineSchedule + present paginate(schedules), with: Entities::Ci::PipelineSchedule end # rubocop: enable CodeReuse/ActiveRecord desc 'Get a single pipeline schedule' do - success Entities::PipelineScheduleDetails + success Entities::Ci::PipelineScheduleDetails end params do requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' end get ':id/pipeline_schedules/:pipeline_schedule_id' do - present pipeline_schedule, with: Entities::PipelineScheduleDetails + present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails end desc 'Create a new pipeline schedule' do - success Entities::PipelineScheduleDetails + success Entities::Ci::PipelineScheduleDetails end params do requires :description, type: String, desc: 'The description of pipeline schedule' @@ -57,14 +57,14 @@ module API .execute if pipeline_schedule.persisted? - present pipeline_schedule, with: Entities::PipelineScheduleDetails + present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails else render_validation_error!(pipeline_schedule) end end desc 'Edit a pipeline schedule' do - success Entities::PipelineScheduleDetails + success Entities::Ci::PipelineScheduleDetails end params do requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' @@ -78,14 +78,14 @@ module API authorize! :update_pipeline_schedule, pipeline_schedule if pipeline_schedule.update(declared_params(include_missing: false)) - present pipeline_schedule, with: Entities::PipelineScheduleDetails + present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails else render_validation_error!(pipeline_schedule) end end desc 'Take ownership of a pipeline schedule' do - success Entities::PipelineScheduleDetails + success Entities::Ci::PipelineScheduleDetails end params do requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' @@ -94,14 +94,14 @@ module API authorize! :update_pipeline_schedule, pipeline_schedule if pipeline_schedule.own!(current_user) - present pipeline_schedule, with: Entities::PipelineScheduleDetails + present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails else render_validation_error!(pipeline_schedule) end end desc 'Delete a pipeline schedule' do - success Entities::PipelineScheduleDetails + success Entities::Ci::PipelineScheduleDetails end params do requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' @@ -132,7 +132,7 @@ module API end desc 'Create a new pipeline schedule variable' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' @@ -146,14 +146,14 @@ module API variable_params = declared_params(include_missing: false) variable = pipeline_schedule.variables.create(variable_params) if variable.persisted? - present variable, with: Entities::Variable + present variable, with: Entities::Ci::Variable else render_validation_error!(variable) end end desc 'Edit a pipeline schedule variable' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' @@ -165,14 +165,14 @@ module API authorize! :update_pipeline_schedule, pipeline_schedule if pipeline_schedule_variable.update(declared_params(include_missing: false)) - present pipeline_schedule_variable, with: Entities::Variable + present pipeline_schedule_variable, with: Entities::Ci::Variable else render_validation_error!(pipeline_schedule_variable) end end desc 'Delete a pipeline schedule variable' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' @@ -182,7 +182,7 @@ module API authorize! :admin_pipeline_schedule, pipeline_schedule status :accepted - present pipeline_schedule_variable.destroy, with: Entities::Variable + present pipeline_schedule_variable.destroy, with: Entities::Ci::Variable end end diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 33bb8b38d92..a010e0dd761 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -13,7 +13,7 @@ module API 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::PipelineBasic + success Entities::Ci::PipelineBasic end params do use :pagination @@ -38,12 +38,12 @@ module API authorize! :read_build, user_project pipelines = ::Ci::PipelinesFinder.new(user_project, current_user, params).execute - present paginate(pipelines), with: Entities::PipelineBasic + present paginate(pipelines), with: Entities::Ci::PipelineBasic end desc 'Create a new pipeline' do detail 'This feature was introduced in GitLab 8.14' - success Entities::Pipeline + success Entities::Ci::Pipeline end params do requires :ref, type: String, desc: 'Reference' @@ -64,7 +64,7 @@ module API .execute(:api, ignore_skip_ci: true, save_on_errors: false) if new_pipeline.persisted? - present new_pipeline, with: Entities::Pipeline + present new_pipeline, with: Entities::Ci::Pipeline else render_validation_error!(new_pipeline) end @@ -72,7 +72,7 @@ module API desc 'Gets a the latest pipeline for the project branch' do detail 'This feature was introduced in GitLab 12.3' - success Entities::Pipeline + success Entities::Ci::Pipeline end params do optional :ref, type: String, desc: 'branch ref of pipeline' @@ -80,12 +80,12 @@ module API get ':id/pipelines/latest' do authorize! :read_pipeline, latest_pipeline - present latest_pipeline, with: Entities::Pipeline + present latest_pipeline, with: Entities::Ci::Pipeline end desc 'Gets a specific pipeline for the project' do detail 'This feature was introduced in GitLab 8.11' - success Entities::Pipeline + success Entities::Ci::Pipeline end params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' @@ -93,12 +93,12 @@ module API get ':id/pipelines/:pipeline_id' do authorize! :read_pipeline, pipeline - present pipeline, with: Entities::Pipeline + present pipeline, with: Entities::Ci::Pipeline end desc 'Gets the variables for a given pipeline' do detail 'This feature was introduced in GitLab 11.11' - success Entities::Variable + success Entities::Ci::Variable end params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' @@ -106,19 +106,17 @@ module API get ':id/pipelines/:pipeline_id/variables' do authorize! :read_pipeline_variable, pipeline - present pipeline.variables, with: Entities::Variable + present pipeline.variables, with: Entities::Ci::Variable end desc 'Gets the test report for a given pipeline' do - detail 'This feature was introduced in GitLab 13.0. Disabled by default behind feature flag `junit_pipeline_view`' + detail 'This feature was introduced in GitLab 13.0.' success TestReportEntity end params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end get ':id/pipelines/:pipeline_id/test_report' do - not_found! unless Feature.enabled?(:junit_pipeline_view, user_project) - authorize! :read_build, pipeline present pipeline.test_reports, with: TestReportEntity, details: true @@ -141,7 +139,7 @@ module API desc 'Retry builds in the pipeline' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Pipeline + success Entities::Ci::Pipeline end params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' @@ -151,12 +149,12 @@ module API pipeline.retry_failed(current_user) - present pipeline, with: Entities::Pipeline + present pipeline, with: Entities::Ci::Pipeline end desc 'Cancel all builds in the pipeline' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Pipeline + success Entities::Ci::Pipeline end params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' @@ -167,14 +165,14 @@ module API pipeline.cancel_running status 200 - present pipeline.reset, with: Entities::Pipeline + present pipeline.reset, with: Entities::Ci::Pipeline end end helpers do def pipeline strong_memoize(:pipeline) do - user_project.ci_pipelines.find(params[:pipeline_id]) + user_project.all_pipelines.find(params[:pipeline_id]) end end diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 2c156a71160..7bca72f8028 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -111,7 +111,7 @@ module API end desc 'List jobs running on a runner' do - success Entities::JobBasicWithProject + success Entities::Ci::JobBasicWithProject end params do requires :id, type: Integer, desc: 'The ID of the runner' @@ -126,7 +126,7 @@ module API jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute - present paginate(jobs), with: Entities::JobBasicWithProject + present paginate(jobs), with: Entities::Ci::JobBasicWithProject end end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 1a0fe393753..3c7ed2a25a0 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -13,7 +13,7 @@ module API helpers do def user_access - @user_access ||= Gitlab::UserAccess.new(current_user, project: user_project) + @user_access ||= Gitlab::UserAccess.new(current_user, container: user_project) end def authorize_push_to_branch!(branch) @@ -203,6 +203,7 @@ module API params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked' requires :branch, type: String, desc: 'The name of the branch', allow_blank: false + optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes" end post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do authorize_push_to_branch!(params[:branch]) @@ -215,7 +216,8 @@ module API commit_params = { commit: commit, start_branch: params[:branch], - branch_name: params[:branch] + branch_name: params[:branch], + dry_run: params[:dry_run] } result = ::Commits::CherryPickService @@ -223,10 +225,18 @@ module API .execute if result[:status] == :success - present user_project.repository.commit(result[:result]), - with: Entities::Commit + if params[:dry_run] + present dry_run: :success + status :ok + else + present user_project.repository.commit(result[:result]), + with: Entities::Commit + end else - error!(result.slice(:message, :error_code), 400, header) + response = result.slice(:message, :error_code) + response[:dry_run] = :error if params[:dry_run] + + error!(response, 400, header) end end @@ -237,6 +247,7 @@ module API params do requires :sha, type: String, desc: 'Commit SHA to revert' requires :branch, type: String, desc: 'Target branch name', allow_blank: false + optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes" end post ':id/repository/commits/:sha/revert', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do authorize_push_to_branch!(params[:branch]) @@ -249,7 +260,8 @@ module API commit_params = { commit: commit, start_branch: params[:branch], - branch_name: params[:branch] + branch_name: params[:branch], + dry_run: params[:dry_run] } result = ::Commits::RevertService @@ -257,10 +269,18 @@ module API .execute if result[:status] == :success - present user_project.repository.commit(result[:result]), - with: Entities::Commit + if params[:dry_run] + present dry_run: :success + status :ok + else + present user_project.repository.commit(result[:result]), + with: Entities::Commit + end else - error!(result.slice(:message, :error_code), 400, header) + response = result.slice(:message, :error_code) + response[:dry_run] = :error if params[:dry_run] + + error!(response, 400, header) end end diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index 726dc89271a..05887e58425 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -59,7 +59,7 @@ module API desc 'Composer packages endpoint at group level' - route_setting :authentication, job_token_allowed: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true get ':id/-/packages/composer/packages' do presenter.root @@ -71,7 +71,7 @@ module API requires :sha, type: String, desc: 'Shasum of current json' end - route_setting :authentication, job_token_allowed: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true get ':id/-/packages/composer/p/:sha' do presenter.provider @@ -83,7 +83,7 @@ module API requires :package_name, type: String, file_path: true, desc: 'The Composer package name' end - route_setting :authentication, job_token_allowed: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do not_found! if packages.empty? @@ -104,7 +104,7 @@ module API desc 'Composer packages endpoint for registering packages' namespace ':id/packages/composer' do - route_setting :authentication, job_token_allowed: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true params do optional :branch, type: String, desc: 'The name of the branch' @@ -123,7 +123,7 @@ module API bad_request! end - track_event('register_package') + track_event('push_package') ::Packages::Composer::CreatePackageService .new(authorized_user_project, current_user, declared_params) diff --git a/lib/api/conan_packages.rb b/lib/api/conan_packages.rb index 1d941e422a7..6923d252fbd 100644 --- a/lib/api/conan_packages.rb +++ b/lib/api/conan_packages.rb @@ -38,7 +38,9 @@ module API desc 'Ping the Conan API' do detail 'This feature was introduced in GitLab 12.2' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'ping' do header 'X-Conan-Server-Capabilities', [].join(',') end @@ -46,10 +48,13 @@ module API desc 'Search for packages' do detail 'This feature was introduced in GitLab 12.4' end + params do requires :q, type: String, desc: 'Search query' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'conans/search' do service = ::Packages::Conan::SearchService.new(current_user, query: params[:q]).execute service.payload @@ -61,7 +66,9 @@ module API desc 'Authenticate user against conan CLI' do detail 'This feature was introduced in GitLab 12.2' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'authenticate' do unauthorized! unless token @@ -71,7 +78,9 @@ module API desc 'Check for valid user credentials per conan CLI' do detail 'This feature was introduced in GitLab 12.4' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'check_credentials' do authenticate! :ok @@ -93,10 +102,13 @@ module API desc 'Package Snapshot' do detail 'This feature was introduced in GitLab 12.5' end + params do requires :conan_package_reference, type: String, desc: 'Conan package ID' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'packages/:conan_package_reference' do authorize!(:read_package, project) @@ -113,7 +125,9 @@ module API desc 'Recipe Snapshot' do detail 'This feature was introduced in GitLab 12.5' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get do authorize!(:read_package, project) @@ -133,7 +147,9 @@ module API params do requires :conan_package_reference, type: String, desc: 'Conan package ID' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'packages/:conan_package_reference/digest' do present_package_download_urls end @@ -141,7 +157,9 @@ module API desc 'Recipe Digest' do detail 'This feature was introduced in GitLab 12.5' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'digest' do present_recipe_download_urls end @@ -155,10 +173,13 @@ module API desc 'Package Download Urls' do detail 'This feature was introduced in GitLab 12.5' end + params do requires :conan_package_reference, type: String, desc: 'Conan package ID' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'packages/:conan_package_reference/download_urls' do present_package_download_urls end @@ -166,7 +187,9 @@ module API desc 'Recipe Download Urls' do detail 'This feature was introduced in GitLab 12.5' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get 'download_urls' do present_recipe_download_urls end @@ -181,36 +204,39 @@ module API desc 'Package Upload Urls' do detail 'This feature was introduced in GitLab 12.4' end + params do requires :conan_package_reference, type: String, desc: 'Conan package ID' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + post 'packages/:conan_package_reference/upload_urls' do authorize!(:read_package, project) status 200 - upload_urls = package_upload_urls(::Packages::Conan::FileMetadatum::PACKAGE_FILES) - - present upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls + present package_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls end desc 'Recipe Upload Urls' do detail 'This feature was introduced in GitLab 12.4' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + post 'upload_urls' do authorize!(:read_package, project) status 200 - upload_urls = recipe_upload_urls(::Packages::Conan::FileMetadatum::RECIPE_FILES) - - present upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls + present recipe_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls end desc 'Delete Package' do detail 'This feature was introduced in GitLab 12.5' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + delete do authorize!(:destroy_package, project) @@ -239,7 +265,9 @@ module API desc 'Download recipe files' do detail 'This feature was introduced in GitLab 12.6' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get do download_package_file(:recipe_file) end @@ -247,10 +275,13 @@ module API desc 'Upload recipe package files' do detail 'This feature was introduced in GitLab 12.6' end + params do use :workhorse_upload_params end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + put do upload_package_file(:recipe_file) end @@ -258,7 +289,9 @@ module API desc 'Workhorse authorize the conan recipe file' do detail 'This feature was introduced in GitLab 12.6' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + put 'authorize' do authorize_workhorse!(subject: project) end @@ -273,7 +306,9 @@ module API desc 'Download package files' do detail 'This feature was introduced in GitLab 12.5' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get do download_package_file(:package_file) end @@ -281,7 +316,9 @@ module API desc 'Workhorse authorize the conan package file' do detail 'This feature was introduced in GitLab 12.6' end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + put 'authorize' do authorize_workhorse!(subject: project) end @@ -289,10 +326,13 @@ module API desc 'Upload package files' do detail 'This feature was introduced in GitLab 12.6' end + params do use :workhorse_upload_params end - route_setting :authentication, job_token_allowed: true + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + put do upload_package_file(:package_file) end diff --git a/lib/api/entities/branch.rb b/lib/api/entities/branch.rb index f9d06082ad6..6a75dcddeda 100644 --- a/lib/api/entities/branch.rb +++ b/lib/api/entities/branch.rb @@ -32,7 +32,7 @@ module API end expose :can_push do |repo_branch, options| - Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name) + Gitlab::UserAccess.new(options[:current_user], container: options[:project]).can_push_to_branch?(repo_branch.name) end expose :default do |repo_branch, options| diff --git a/lib/api/entities/bridge.rb b/lib/api/entities/bridge.rb deleted file mode 100644 index 8f0ee69399a..00000000000 --- a/lib/api/entities/bridge.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class Bridge < Entities::JobBasic - expose :downstream_pipeline, with: Entities::PipelineBasic - end - end -end diff --git a/lib/api/entities/ci/bridge.rb b/lib/api/entities/ci/bridge.rb new file mode 100644 index 00000000000..502d97fff90 --- /dev/null +++ b/lib/api/entities/ci/bridge.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class Bridge < JobBasic + expose :downstream_pipeline, with: ::API::Entities::Ci::PipelineBasic + end + end + end +end diff --git a/lib/api/entities/ci/job.rb b/lib/api/entities/ci/job.rb new file mode 100644 index 00000000000..7fe1a802e24 --- /dev/null +++ b/lib/api/entities/ci/job.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class Job < JobBasic + # artifacts_file is included in job_artifacts, but kept for backward compatibility (remove in api/v5) + expose :artifacts_file, using: ::API::Entities::Ci::JobArtifactFile, if: -> (job, opts) { job.artifacts? } + expose :job_artifacts, as: :artifacts, using: ::API::Entities::Ci::JobArtifact + expose :runner, with: ::API::Entities::Runner + expose :artifacts_expire_at + end + end + end +end diff --git a/lib/api/entities/ci/job_artifact.rb b/lib/api/entities/ci/job_artifact.rb new file mode 100644 index 00000000000..9e504aee383 --- /dev/null +++ b/lib/api/entities/ci/job_artifact.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class JobArtifact < Grape::Entity + expose :file_type, :size, :filename, :file_format + end + end + end +end diff --git a/lib/api/entities/ci/job_artifact_file.rb b/lib/api/entities/ci/job_artifact_file.rb new file mode 100644 index 00000000000..418eb408ab6 --- /dev/null +++ b/lib/api/entities/ci/job_artifact_file.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class JobArtifactFile < Grape::Entity + expose :filename + expose :cached_size, as: :size + end + end + end +end diff --git a/lib/api/entities/ci/job_basic.rb b/lib/api/entities/ci/job_basic.rb new file mode 100644 index 00000000000..a29788c7abf --- /dev/null +++ b/lib/api/entities/ci/job_basic.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class JobBasic < Grape::Entity + expose :id, :status, :stage, :name, :ref, :tag, :coverage, :allow_failure + expose :created_at, :started_at, :finished_at + expose :duration + expose :user, with: ::API::Entities::User + expose :commit, with: ::API::Entities::Commit + expose :pipeline, with: ::API::Entities::Ci::PipelineBasic + + expose :web_url do |job, _options| + Gitlab::Routing.url_helpers.project_job_url(job.project, job) + end + end + end + end +end diff --git a/lib/api/entities/ci/job_basic_with_project.rb b/lib/api/entities/ci/job_basic_with_project.rb new file mode 100644 index 00000000000..736e611e5b1 --- /dev/null +++ b/lib/api/entities/ci/job_basic_with_project.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class JobBasicWithProject < Entities::Ci::JobBasic + expose :project, with: Entities::ProjectIdentity + end + end + end +end diff --git a/lib/api/entities/ci/pipeline.rb b/lib/api/entities/ci/pipeline.rb new file mode 100644 index 00000000000..3dd3b9c9eff --- /dev/null +++ b/lib/api/entities/ci/pipeline.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class Pipeline < PipelineBasic + expose :before_sha, :tag, :yaml_errors + + expose :user, with: Entities::UserBasic + expose :created_at, :updated_at, :started_at, :finished_at, :committed_at + expose :duration + expose :coverage + expose :detailed_status, using: DetailedStatusEntity do |pipeline, options| + pipeline.detailed_status(options[:current_user]) + end + end + end + end +end diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb new file mode 100644 index 00000000000..dbb9b828757 --- /dev/null +++ b/lib/api/entities/ci/pipeline_basic.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class PipelineBasic < Grape::Entity + expose :id, :sha, :ref, :status + expose :created_at, :updated_at + + expose :web_url do |pipeline, _options| + Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline) + end + end + end + end +end diff --git a/lib/api/entities/ci/pipeline_schedule.rb b/lib/api/entities/ci/pipeline_schedule.rb new file mode 100644 index 00000000000..f1596b7d285 --- /dev/null +++ b/lib/api/entities/ci/pipeline_schedule.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class PipelineSchedule < Grape::Entity + expose :id + expose :description, :ref, :cron, :cron_timezone, :next_run_at, :active + expose :created_at, :updated_at + expose :owner, using: ::API::Entities::UserBasic + end + end + end +end diff --git a/lib/api/entities/ci/pipeline_schedule_details.rb b/lib/api/entities/ci/pipeline_schedule_details.rb new file mode 100644 index 00000000000..b233728b95b --- /dev/null +++ b/lib/api/entities/ci/pipeline_schedule_details.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class PipelineScheduleDetails < PipelineSchedule + expose :last_pipeline, using: ::API::Entities::Ci::PipelineBasic + expose :variables, using: ::API::Entities::Ci::Variable + end + end + end +end diff --git a/lib/api/entities/ci/variable.rb b/lib/api/entities/ci/variable.rb new file mode 100644 index 00000000000..f4d5248245a --- /dev/null +++ b/lib/api/entities/ci/variable.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class Variable < Grape::Entity + expose :variable_type, :key, :value + expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) } + expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) } + expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) } + end + end + end +end diff --git a/lib/api/entities/commit_detail.rb b/lib/api/entities/commit_detail.rb index 22424b38bb9..61238102e9d 100644 --- a/lib/api/entities/commit_detail.rb +++ b/lib/api/entities/commit_detail.rb @@ -9,7 +9,7 @@ module API expose :last_pipeline do |commit, options| pipeline = commit.last_pipeline if can_read_pipeline? - ::API::Entities::PipelineBasic.represent(pipeline, options) + ::API::Entities::Ci::PipelineBasic.represent(pipeline, options) end private diff --git a/lib/api/entities/deployment.rb b/lib/api/entities/deployment.rb index 3a97d3e3c09..4e3a4c289d9 100644 --- a/lib/api/entities/deployment.rb +++ b/lib/api/entities/deployment.rb @@ -6,7 +6,7 @@ module API expose :id, :iid, :ref, :sha, :created_at, :updated_at expose :user, using: Entities::UserBasic expose :environment, using: Entities::EnvironmentBasic - expose :deployable, using: Entities::Job + expose :deployable, using: Entities::Ci::Job expose :status end end diff --git a/lib/api/entities/event.rb b/lib/api/entities/event.rb index 8fd0bac13f4..f750d728e03 100644 --- a/lib/api/entities/event.rb +++ b/lib/api/entities/event.rb @@ -3,6 +3,7 @@ module API module Entities class Event < Grape::Entity + expose :id expose :project_id, :action_name expose :target_id, :target_iid, :target_type, :author_id expose :target_title diff --git a/lib/api/entities/feature.rb b/lib/api/entities/feature.rb index 3c9182340ea..618a7be9c7b 100644 --- a/lib/api/entities/feature.rb +++ b/lib/api/entities/feature.rb @@ -10,7 +10,7 @@ module API value = model.gate_values[gate.key] # By default all gate values are populated. Only show relevant ones. - if (value.is_a?(Integer) && value.zero?) || (value.is_a?(Set) && value.empty?) + if (value.is_a?(Integer) && value == 0) || (value.is_a?(Set) && value.empty?) next end diff --git a/lib/api/entities/job.rb b/lib/api/entities/job.rb deleted file mode 100644 index cbee8794007..00000000000 --- a/lib/api/entities/job.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class Job < Entities::JobBasic - # artifacts_file is included in job_artifacts, but kept for backward compatibility (remove in api/v5) - expose :artifacts_file, using: Entities::JobArtifactFile, if: -> (job, opts) { job.artifacts? } - expose :job_artifacts, as: :artifacts, using: Entities::JobArtifact - expose :runner, with: Entities::Runner - expose :artifacts_expire_at - end - end -end diff --git a/lib/api/entities/job_artifact.rb b/lib/api/entities/job_artifact.rb deleted file mode 100644 index 94dbdb38fee..00000000000 --- a/lib/api/entities/job_artifact.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class JobArtifact < Grape::Entity - expose :file_type, :size, :filename, :file_format - end - end -end diff --git a/lib/api/entities/job_artifact_file.rb b/lib/api/entities/job_artifact_file.rb deleted file mode 100644 index fa2851a7f0e..00000000000 --- a/lib/api/entities/job_artifact_file.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class JobArtifactFile < Grape::Entity - expose :filename - expose :cached_size, as: :size - end - end -end diff --git a/lib/api/entities/job_basic.rb b/lib/api/entities/job_basic.rb deleted file mode 100644 index a8541039934..00000000000 --- a/lib/api/entities/job_basic.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class JobBasic < Grape::Entity - expose :id, :status, :stage, :name, :ref, :tag, :coverage, :allow_failure - expose :created_at, :started_at, :finished_at - expose :duration - expose :user, with: Entities::User - expose :commit, with: Entities::Commit - expose :pipeline, with: Entities::PipelineBasic - - expose :web_url do |job, _options| - Gitlab::Routing.url_helpers.project_job_url(job.project, job) - end - end - end -end diff --git a/lib/api/entities/job_basic_with_project.rb b/lib/api/entities/job_basic_with_project.rb deleted file mode 100644 index 09387e045ec..00000000000 --- a/lib/api/entities/job_basic_with_project.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class JobBasicWithProject < Entities::JobBasic - expose :project, with: Entities::ProjectIdentity - end - end -end diff --git a/lib/api/entities/job_request/dependency.rb b/lib/api/entities/job_request/dependency.rb index 64d779f6575..7d6ec832ba1 100644 --- a/lib/api/entities/job_request/dependency.rb +++ b/lib/api/entities/job_request/dependency.rb @@ -5,7 +5,7 @@ module API module JobRequest class Dependency < Grape::Entity expose :id, :name, :token - expose :artifacts_file, using: Entities::JobArtifactFile, if: ->(job, _) { job.artifacts? } + expose :artifacts_file, using: Entities::Ci::JobArtifactFile, if: ->(job, _) { job.artifacts? } end end end diff --git a/lib/api/entities/job_request/response.rb b/lib/api/entities/job_request/response.rb index fdacd3af2da..8db9aff3dc9 100644 --- a/lib/api/entities/job_request/response.rb +++ b/lib/api/entities/job_request/response.rb @@ -33,3 +33,5 @@ module API end end end + +API::Entities::JobRequest::Response.prepend_if_ee('EE::API::Entities::JobRequest::Response') diff --git a/lib/api/entities/merge_request.rb b/lib/api/entities/merge_request.rb index 7fc76a4071e..05ae041c7a9 100644 --- a/lib/api/entities/merge_request.rb +++ b/lib/api/entities/merge_request.rb @@ -23,11 +23,11 @@ module API merge_request.metrics&.first_deployed_to_production_at end - expose :pipeline, using: Entities::PipelineBasic, if: -> (_, options) { build_available?(options) } do |merge_request, _options| + expose :pipeline, using: Entities::Ci::PipelineBasic, if: -> (_, options) { build_available?(options) } do |merge_request, _options| merge_request.metrics&.pipeline end - expose :head_pipeline, using: 'API::Entities::Pipeline', if: -> (_, options) do + expose :head_pipeline, using: '::API::Entities::Ci::Pipeline', if: -> (_, options) do Ability.allowed?(options[:current_user], :read_pipeline, options[:project]) end diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb index 73473f16da9..670965b225c 100644 --- a/lib/api/entities/package.rb +++ b/lib/api/entities/package.rb @@ -13,9 +13,7 @@ module API expose :_links do expose :web_path do |package| - if ::Gitlab.ee? - ::Gitlab::Routing.url_helpers.project_package_path(package.project, package) - end + ::Gitlab::Routing.url_helpers.project_package_path(package.project, package) end expose :delete_api_path, if: can_destroy(:package, &:project) do |package| diff --git a/lib/api/entities/package/pipeline.rb b/lib/api/entities/package/pipeline.rb index e91a12e47fa..0aa888e30ee 100644 --- a/lib/api/entities/package/pipeline.rb +++ b/lib/api/entities/package/pipeline.rb @@ -3,7 +3,7 @@ module API module Entities class Package < Grape::Entity - class Pipeline < ::API::Entities::PipelineBasic + class Pipeline < ::API::Entities::Ci::PipelineBasic expose :user, using: ::API::Entities::UserBasic end end diff --git a/lib/api/entities/personal_access_token.rb b/lib/api/entities/personal_access_token.rb index d6fb9af6ab3..3846929c903 100644 --- a/lib/api/entities/personal_access_token.rb +++ b/lib/api/entities/personal_access_token.rb @@ -3,7 +3,7 @@ module API module Entities class PersonalAccessToken < Grape::Entity - expose :id, :name, :revoked, :created_at, :scopes + expose :id, :name, :revoked, :created_at, :scopes, :user_id expose :active?, as: :active expose :expires_at do |personal_access_token| personal_access_token.expires_at ? personal_access_token.expires_at.strftime("%Y-%m-%d") : nil diff --git a/lib/api/entities/pipeline.rb b/lib/api/entities/pipeline.rb deleted file mode 100644 index 778efbe4bcc..00000000000 --- a/lib/api/entities/pipeline.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class Pipeline < Entities::PipelineBasic - expose :before_sha, :tag, :yaml_errors - - expose :user, with: Entities::UserBasic - expose :created_at, :updated_at, :started_at, :finished_at, :committed_at - expose :duration - expose :coverage - expose :detailed_status, using: DetailedStatusEntity do |pipeline, options| - pipeline.detailed_status(options[:current_user]) - end - end - end -end diff --git a/lib/api/entities/pipeline_basic.rb b/lib/api/entities/pipeline_basic.rb deleted file mode 100644 index 359f6a447ab..00000000000 --- a/lib/api/entities/pipeline_basic.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class PipelineBasic < Grape::Entity - expose :id, :sha, :ref, :status - expose :created_at, :updated_at - - expose :web_url do |pipeline, _options| - Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline) - end - end - end -end diff --git a/lib/api/entities/pipeline_schedule.rb b/lib/api/entities/pipeline_schedule.rb deleted file mode 100644 index a72fe3f3141..00000000000 --- a/lib/api/entities/pipeline_schedule.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class PipelineSchedule < Grape::Entity - expose :id - expose :description, :ref, :cron, :cron_timezone, :next_run_at, :active - expose :created_at, :updated_at - expose :owner, using: Entities::UserBasic - end - end -end diff --git a/lib/api/entities/pipeline_schedule_details.rb b/lib/api/entities/pipeline_schedule_details.rb deleted file mode 100644 index 5e54489a0f9..00000000000 --- a/lib/api/entities/pipeline_schedule_details.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class PipelineScheduleDetails < Entities::PipelineSchedule - expose :last_pipeline, using: Entities::PipelineBasic - expose :variables, using: Entities::Variable - end - end -end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index e3c5177cd0b..fb599d68d72 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -35,6 +35,7 @@ module API end end + expose :packages_enabled expose :empty_repo?, as: :empty_repo expose :archived?, as: :archived expose :visibility diff --git a/lib/api/entities/project_hook.rb b/lib/api/entities/project_hook.rb index cdd3714ed64..751f9500252 100644 --- a/lib/api/entities/project_hook.rb +++ b/lib/api/entities/project_hook.rb @@ -4,7 +4,7 @@ module API module Entities class ProjectHook < Hook expose :project_id, :issues_events, :confidential_issues_events - expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events + expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events, :deployment_events expose :job_events expose :push_events_branch_filter end diff --git a/lib/api/entities/user_details_with_admin.rb b/lib/api/entities/user_details_with_admin.rb index 22a842983e2..e48b1da7859 100644 --- a/lib/api/entities/user_details_with_admin.rb +++ b/lib/api/entities/user_details_with_admin.rb @@ -6,6 +6,7 @@ module API expose :highest_role expose :current_sign_in_ip expose :last_sign_in_ip + expose :sign_in_count end end end diff --git a/lib/api/entities/variable.rb b/lib/api/entities/variable.rb deleted file mode 100644 index 6705df30b2e..00000000000 --- a/lib/api/entities/variable.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class Variable < Grape::Entity - expose :variable_type, :key, :value - expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) } - expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) } - expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) } - end - end -end diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb index d3ca1c79e73..e7b8cd10197 100644 --- a/lib/api/group_variables.rb +++ b/lib/api/group_variables.rb @@ -13,18 +13,18 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get group-level variables' do - success Entities::Variable + success Entities::Ci::Variable end params do use :pagination end get ':id/variables' do variables = user_group.variables - present paginate(variables), with: Entities::Variable + present paginate(variables), with: Entities::Ci::Variable end desc 'Get a specific variable from a group' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, type: String, desc: 'The key of the variable' @@ -36,12 +36,12 @@ module API break not_found!('GroupVariable') unless variable - present variable, with: Entities::Variable + present variable, with: Entities::Ci::Variable end # rubocop: enable CodeReuse/ActiveRecord desc 'Create a new variable in a group' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, type: String, desc: 'The key of the variable' @@ -51,19 +51,21 @@ module API 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) - - variable = user_group.variables.create(variable_params) + variable = ::Ci::ChangeVariableService.new( + container: user_group, + current_user: current_user, + params: { action: :create, variable_params: declared_params(include_missing: false) } + ).execute if variable.valid? - present variable, with: Entities::Variable + present variable, with: Entities::Ci::Variable else render_validation_error!(variable) end end desc 'Update an existing variable from a group' do - success Entities::Variable + success Entities::Ci::Variable end params do optional :key, type: String, desc: 'The key of the variable' @@ -74,32 +76,41 @@ module API end # rubocop: disable CodeReuse/ActiveRecord put ':id/variables/:key' do - variable = user_group.variables.find_by(key: params[:key]) - - break not_found!('GroupVariable') unless variable - - variable_params = declared_params(include_missing: false).except(:key) + variable = ::Ci::ChangeVariableService.new( + container: user_group, + current_user: current_user, + params: { action: :update, variable_params: declared_params(include_missing: false) } + ).execute - if variable.update(variable_params) - present variable, with: Entities::Variable + if variable.valid? + present variable, with: Entities::Ci::Variable else render_validation_error!(variable) end + rescue ::ActiveRecord::RecordNotFound + not_found!('GroupVariable') end # rubocop: enable CodeReuse/ActiveRecord desc 'Delete an existing variable from a group' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, type: String, desc: 'The key of the variable' end # rubocop: disable CodeReuse/ActiveRecord delete ':id/variables/:key' do - variable = user_group.variables.find_by(key: params[:key]) - not_found!('GroupVariable') unless variable - - destroy_conditionally!(variable) + variable = user_group.variables.find_by!(key: params[:key]) + + destroy_conditionally!(variable) do |target_variable| + ::Ci::ChangeVariableService.new( + container: user_group, + current_user: current_user, + params: { action: :destroy, variable_params: declared_params(include_missing: false) } + ).execute + end + rescue ::ActiveRecord::RecordNotFound + not_found!('GroupVariable') end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 9ac3ac818fc..813e41b4d39 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -76,7 +76,7 @@ module API params: project_finder_params, options: finder_options ).execute - projects = reorder_projects(projects) + projects = reorder_projects_with_similarity_order_support(group, projects) paginate(projects) end @@ -112,6 +112,24 @@ module API accepted! end + + def reorder_projects_with_similarity_order_support(group, projects) + return handle_similarity_order(group, projects) if params[:order_by] == 'similarity' + + reorder_projects(projects) + end + + # rubocop: disable CodeReuse/ActiveRecord + def handle_similarity_order(group, projects) + if params[:search].present? && Feature.enabled?(:similarity_search, group, default_enabled: true) + projects.sorted_by_similarity_desc(params[:search]) + else + order_options = { name: :asc } + order_options['id'] ||= params[:sort] || 'asc' + projects.reorder(order_options) + end + end + # rubocop: enable CodeReuse/ActiveRecord end resource :groups do @@ -222,7 +240,7 @@ module API optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'Limit by visibility' optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' - optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at similarity], default: 'created_at', desc: 'Return projects ordered by field' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return projects sorted in ascending and descending order' diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 01b89959c14..599d5bd0baf 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -404,6 +404,10 @@ module API render_api_error!(message || '409 Conflict', 409) end + def unprocessable_entity!(message = nil) + render_api_error!(message || '422 Unprocessable Entity', :unprocessable_entity) + end + def file_too_large! render_api_error!('413 Request Entity Too Large', 413) end @@ -555,8 +559,8 @@ module API finder_params[:search_namespaces] = true if params[:search_namespaces].present? finder_params[:user] = params.delete(:user) if params[:user] finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes] - finder_params[:id_after] = params[:id_after] if params[:id_after] - finder_params[:id_before] = params[:id_before] if params[:id_before] + finder_params[:id_after] = sanitize_id_param(params[:id_after]) if params[:id_after] + finder_params[:id_before] = sanitize_id_param(params[:id_before]) if params[:id_before] finder_params[:last_activity_after] = params[:last_activity_after] if params[:last_activity_after] finder_params[:last_activity_before] = params[:last_activity_before] if params[:last_activity_before] finder_params[:repository_storage] = params[:repository_storage] if params[:repository_storage] @@ -655,6 +659,10 @@ module API def ip_address env["action_dispatch.remote_ip"].to_s || request.ip end + + def sanitize_id_param(id) + id.present? ? id.to_i : nil + end end end diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index b69930b447c..b7ce1eba3f9 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -117,7 +117,7 @@ module API return unless %w[git-receive-pack git-upload-pack git-upload-archive].include?(action) { - repository: repository.gitaly_repository, + repository: repository.gitaly_repository.to_h, address: Gitlab::GitalyClient.address(repository.shard), token: Gitlab::GitalyClient.token(repository.shard), features: Feature::Gitaly.server_feature_flags diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb index 4d5350498a7..e4163c63575 100644 --- a/lib/api/helpers/merge_requests_helpers.rb +++ b/lib/api/helpers/merge_requests_helpers.rb @@ -4,6 +4,9 @@ module API module Helpers module MergeRequestsHelpers extend Grape::API::Helpers + extend ActiveSupport::Concern + + UNPROCESSABLE_ERROR_KEYS = [:project_access, :branch_conflict, :validate_fork, :base].freeze params :merge_requests_negatable_params do optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' @@ -79,6 +82,20 @@ module API default: 'created_by_me', desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' end + + def handle_merge_request_errors!(merge_request) + return if merge_request.valid? + + errors = merge_request.errors + + UNPROCESSABLE_ERROR_KEYS.each do |error| + unprocessable_entity!(errors[error]) if errors.has_key?(error) + end + + conflict!(errors[:validate_branches]) if errors.has_key?(:validate_branches) + + render_validation_error!(merge_request) + end end end end diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index f88624ed63e..f61bcfe963e 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -17,8 +17,9 @@ module API authorize! :admin_note, note opts = { - note: params[:body] - } + note: params[:body], + confidential: params[:confidential] + }.compact parent = noteable_parent(noteable) project = parent if parent.is_a?(Project) diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb index 835b5f4614c..e35a8712131 100644 --- a/lib/api/helpers/packages/basic_auth_helpers.rb +++ b/lib/api/helpers/packages/basic_auth_helpers.rb @@ -4,6 +4,8 @@ module API module Helpers module Packages module BasicAuthHelpers + extend ::Gitlab::Utils::Override + module Constants AUTHENTICATE_REALM_HEADER = 'Www-Authenticate: Basic realm' AUTHENTICATE_REALM_NAME = 'GitLab Packages Registry' @@ -11,10 +13,6 @@ module API include Constants - def find_personal_access_token - find_personal_access_token_from_http_basic_auth - end - def unauthorized_user_project @unauthorized_user_project ||= find_project(params[:id]) end @@ -44,12 +42,13 @@ module API end def unauthorized_or! - current_user ? yield : unauthorized_with_header! + current_user ? yield : unauthorized! end - def unauthorized_with_header! + override :unauthorized! + def unauthorized! header(AUTHENTICATE_REALM_HEADER, AUTHENTICATE_REALM_NAME) - unauthorized! + super end end end diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb index 30e690a5a1d..a5fde1af41e 100644 --- a/lib/api/helpers/packages/conan/api_helpers.rb +++ b/lib/api/helpers/packages/conan/api_helpers.rb @@ -28,22 +28,30 @@ module API present_download_urls(::API::Entities::ConanPackage::ConanRecipeManifest, &:recipe_urls) end - def recipe_upload_urls(file_names) + def recipe_upload_urls { upload_urls: Hash[ - file_names.collect do |file_name| + file_names.select(&method(:recipe_file?)).map do |file_name| [file_name, recipe_file_upload_url(file_name)] end ] } end - def package_upload_urls(file_names) + def package_upload_urls { upload_urls: Hash[ - file_names.collect do |file_name| + file_names.select(&method(:package_file?)).map do |file_name| [file_name, package_file_upload_url(file_name)] end ] } end + def recipe_file?(file_name) + file_name.in?(::Packages::Conan::FileMetadatum::RECIPE_FILES) + end + + def package_file?(file_name) + file_name.in?(::Packages::Conan::FileMetadatum::PACKAGE_FILES) + end + def package_file_upload_url(file_name) expose_url( api_v4_packages_conan_v1_files_package_path( @@ -130,6 +138,14 @@ module API end end + def file_names + json_payload = Gitlab::Json.parse(request.body.string) + + bad_request!(nil) unless json_payload.is_a?(Hash) + + json_payload.keys + end + def create_package_file_with_type(file_type, current_package) unless params['file.size'] == 0 # conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0 diff --git a/lib/api/helpers/packages_manager_clients_helpers.rb b/lib/api/helpers/packages_manager_clients_helpers.rb index 7b5d0dd708d..ae16b65aaa8 100644 --- a/lib/api/helpers/packages_manager_clients_helpers.rb +++ b/lib/api/helpers/packages_manager_clients_helpers.rb @@ -16,16 +16,6 @@ module API optional 'file.sha256', type: String, desc: 'SHA256 checksum of the file (generated by Workhorse)' end - def find_personal_access_token_from_http_basic_auth - return unless headers - - token = decode_token - - return unless token - - PersonalAccessToken.find_by_token(token) - end - def find_job_from_http_basic_auth return unless headers diff --git a/lib/api/helpers/pagination_strategies.rb b/lib/api/helpers/pagination_strategies.rb index 823891d6fe7..61cff37e4ab 100644 --- a/lib/api/helpers/pagination_strategies.rb +++ b/lib/api/helpers/pagination_strategies.rb @@ -48,7 +48,7 @@ module API end def offset_limit_exceeded?(offset_limit) - offset_limit.positive? && params[:page] * params[:per_page] > offset_limit + offset_limit > 0 && params[:page] * params[:per_page] > offset_limit end end end diff --git a/lib/api/helpers/performance_bar_helpers.rb b/lib/api/helpers/performance_bar_helpers.rb new file mode 100644 index 00000000000..8430e889dff --- /dev/null +++ b/lib/api/helpers/performance_bar_helpers.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module API + module Helpers + module PerformanceBarHelpers + def set_peek_enabled_for_current_request + Gitlab::SafeRequestStore.fetch(:peek_enabled) { perf_bar_cookie_enabled? && perf_bar_enabled_for_user? } + end + + def perf_bar_cookie_enabled? + cookies[:perf_bar_enabled] == 'true' + end + + def perf_bar_enabled_for_user? + # We cannot use `current_user` here because that method raises an exception when the user + # is unauthorized and some API endpoints require that `current_user` is not called. + Gitlab::PerformanceBar.enabled_for_user?(find_user_from_sources) + end + end + end +end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 76e5bb95c4d..8c20f5b8fc2 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -61,6 +61,7 @@ module API optional :auto_devops_deploy_strategy, type: String, values: %w(continuous manual timed_incremental), desc: 'Auto Deploy strategy' optional :autoclose_referenced_issues, type: Boolean, desc: 'Flag indication if referenced issues auto-closing is enabled' optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins' + optional :packages_enabled, type: Boolean, desc: 'Enable project packages feature' end params :optional_project_params_ee do @@ -137,6 +138,7 @@ module API :suggestion_commit_message, :repository_storage, :compliance_framework_setting, + :packages_enabled, :service_desk_enabled, # TODO: remove in API v5, replaced by *_access_level diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index d4870b96575..ff938358439 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -247,15 +247,15 @@ module API required: true, name: :project_url, type: String, - desc: 'The buildkite project URL' + desc: 'The Buildkite pipeline URL' }, { required: false, name: :enable_ssl_verification, type: Boolean, - desc: 'Enable SSL verification for communication' + desc: 'DEPRECATED: This parameter has no effect since SSL verification will always be enabled' } - ], + ], 'campfire' => [ { required: true, diff --git a/lib/api/helpers/snippets_helpers.rb b/lib/api/helpers/snippets_helpers.rb index f95d066bd7c..79367da8d1f 100644 --- a/lib/api/helpers/snippets_helpers.rb +++ b/lib/api/helpers/snippets_helpers.rb @@ -10,6 +10,23 @@ module API requires :ref, type: String, desc: 'The name of branch, tag or commit' end + params :create_file_params do + optional :files, type: Array, desc: 'An array of files' do + requires :file_path, type: String, file_path: true, allow_blank: false, desc: 'The path of a snippet file' + requires :content, type: String, allow_blank: false, desc: 'The content of a snippet file' + end + + optional :content, type: String, allow_blank: false, desc: 'The content of a snippet' + + given :content do + requires :file_name, type: String, desc: 'The name of a snippet file' + end + + mutually_exclusive :files, :content + + exactly_one_of :files, :content + end + def content_for(snippet) if snippet.empty_repo? env['api.format'] = :txt @@ -35,5 +52,12 @@ module API send_git_blob(repo, blob) end end + + def process_file_args(args) + args[:snippet_actions] = args.delete(:files)&.map do |file| + file[:action] = :create + file.symbolize_keys + end + end end end diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb index 1e839816006..0bab891eada 100644 --- a/lib/api/import_github.rb +++ b/lib/api/import_github.rb @@ -10,7 +10,11 @@ module API helpers do def client - @client ||= Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options) + @client ||= if Feature.enabled?(:remove_legacy_github_client) + Gitlab::GithubImport::Client.new(params[:personal_access_token]) + else + Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options) + end end def access_params diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index 6d4a4fc9c8b..17599c72243 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -18,6 +18,10 @@ module API UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze + VALID_PAT_SCOPES = Set.new( + Gitlab::Auth::API_SCOPES + Gitlab::Auth::REPOSITORY_SCOPES + Gitlab::Auth::REGISTRY_SCOPES + ).freeze + helpers do def response_with_status(code: 200, success: true, message: nil, **extra_options) status code @@ -67,7 +71,7 @@ module API "uploadpack.allowAnySHA1InWant=true"], gitaly: gitaly_payload(params[:action]), gl_console_messages: check_result.console_messages - } + }.merge!(actor.key_details) # Custom option for git-receive-pack command @@ -92,7 +96,7 @@ module API # If we have created a project directly from a git push # we have to assign its value to both @project and @container - @project = @container = access_checker.project + @project = @container = access_checker.container end end end @@ -194,6 +198,60 @@ module API { success: true, recovery_codes: codes } end + post '/personal_access_token' do + status 200 + + actor.update_last_used_at! + user = actor.user + + if params[:key_id] + unless actor.key + break { success: false, message: 'Could not find the given key' } + end + + if actor.key.is_a?(DeployKey) + break { success: false, message: 'Deploy keys cannot be used to create personal access tokens' } + end + + unless user + break { success: false, message: 'Could not find a user for the given key' } + end + elsif params[:user_id] && user.nil? + break { success: false, message: 'Could not find the given user' } + end + + if params[:name].blank? + break { success: false, message: "No token name specified" } + end + + if params[:scopes].blank? + break { success: false, message: "No token scopes specified" } + end + + invalid_scope = params[:scopes].find { |scope| VALID_PAT_SCOPES.exclude?(scope.to_sym) } + + if invalid_scope + valid_scopes = VALID_PAT_SCOPES.map(&:to_s).sort + break { success: false, message: "Invalid scope: '#{invalid_scope}'. Valid scopes are: #{valid_scopes}" } + end + + begin + expires_at = params[:expires_at].presence && Date.parse(params[:expires_at]) + rescue ArgumentError + break { success: false, message: "Invalid token expiry date: '#{params[:expires_at]}'" } + end + + access_token = nil + + ::Users::UpdateService.new(current_user, user: user).execute! do |user| + access_token = user.personal_access_tokens.create!( + name: params[:name], scopes: params[:scopes], expires_at: expires_at + ) + end + + { success: true, token: access_token.token, scopes: access_token.scopes, expires_at: access_token.expires_at } + end + post '/pre_receive' do status 200 diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb new file mode 100644 index 00000000000..7f64fd7efe3 --- /dev/null +++ b/lib/api/internal/kubernetes.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module API + # Kubernetes Internal API + module Internal + class Kubernetes < Grape::API::Instance + helpers do + def agent_token + @agent_token ||= cluster_agent_token_from_authorization_token + end + + def agent + @agent ||= agent_token.agent + end + + def repo_type + Gitlab::GlRepository::PROJECT + end + + def gitaly_info(project) + shard = repo_type.repository_for(project).shard + { + address: Gitlab::GitalyClient.address(shard), + token: Gitlab::GitalyClient.token(shard), + features: Feature::Gitaly.server_feature_flags + } + end + + def gitaly_repository(project) + { + storage_name: project.repository_storage, + relative_path: project.disk_path + '.git', + gl_repository: repo_type.identifier_for_container(project), + gl_project_path: repo_type.repository_for(project).full_path + } + end + + def check_feature_enabled + not_found! unless Feature.enabled?(:kubernetes_agent_internal_api) + end + + def check_agent_token + forbidden! unless agent_token + end + end + + namespace 'internal' do + namespace 'kubernetes' do + before do + check_feature_enabled + check_agent_token + end + + desc 'Gets agent info' do + detail 'Retrieves agent info for the given token' + end + route_setting :authentication, cluster_agent_token_allowed: true + get '/agent_info' do + project = agent.project + + status 200 + { + project_id: project.id, + agent_id: agent.id, + agent_name: agent.name, + gitaly_info: gitaly_info(project), + gitaly_repository: gitaly_repository(project) + } + end + + desc 'Gets project info' do + detail 'Retrieves project info (if authorized)' + end + route_setting :authentication, cluster_agent_token_allowed: true + get '/project_info' do + project = find_project(params[:id]) + + # TODO sort out authorization for real + # https://gitlab.com/gitlab-org/gitlab/-/issues/220912 + if !project || !project.public? + not_found! + end + + status 200 + { + project_id: project.id, + gitaly_info: gitaly_info(project), + gitaly_repository: gitaly_repository(project) + } + end + end + end + end + end +end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 455511caabb..1694a967f26 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -55,6 +55,8 @@ module API desc: 'Return issues ordered by `created_at` or `updated_at` fields.' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return issues sorted in `asc` or `desc` order.' + optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '', + desc: 'Return issues that have no due date (`0`), or whose due date is this week, this month, between two weeks ago and next month, or which are overdue. Accepts: `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`, `0`' use :issues_stats_params use :pagination diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb index 61c279a76e9..bc7bc956580 100644 --- a/lib/api/job_artifacts.rb +++ b/lib/api/job_artifacts.rb @@ -94,7 +94,7 @@ module API end desc 'Keep the artifacts to prevent them from being deleted' do - success Entities::Job + success ::API::Entities::Ci::Job end params do requires :job_id, type: Integer, desc: 'The ID of a job' @@ -109,7 +109,7 @@ module API build.keep_artifacts! status 200 - present build, with: Entities::Job + present build, with: ::API::Entities::Ci::Job end desc 'Delete the artifacts files from a job' do diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index bcc00429dd6..084c146abe7 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -30,7 +30,7 @@ module API end desc 'Get a projects jobs' do - success Entities::Job + success Entities::Ci::Job end params do use :optional_scope @@ -44,12 +44,12 @@ module API builds = filter_builds(builds, params[:scope]) builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, pipeline: :project) - present paginate(builds), with: Entities::Job + present paginate(builds), with: Entities::Ci::Job end # rubocop: enable CodeReuse/ActiveRecord desc 'Get pipeline jobs' do - success Entities::Job + success Entities::Ci::Job end params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' @@ -59,19 +59,19 @@ module API # rubocop: disable CodeReuse/ActiveRecord get ':id/pipelines/:pipeline_id/jobs' do authorize!(:read_pipeline, user_project) - pipeline = user_project.ci_pipelines.find(params[:pipeline_id]) + pipeline = user_project.all_pipelines.find(params[:pipeline_id]) authorize!(:read_build, pipeline) builds = pipeline.builds builds = filter_builds(builds, params[:scope]) builds = builds.preload(:job_artifacts_archive, :job_artifacts, project: [:namespace]) - present paginate(builds), with: Entities::Job + present paginate(builds), with: Entities::Ci::Job end # rubocop: enable CodeReuse/ActiveRecord desc 'Get pipeline bridge jobs' do - success Entities::Bridge + success ::API::Entities::Ci::Bridge end params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' @@ -92,12 +92,12 @@ module API project: [:namespace] ) - present paginate(bridges), with: Entities::Bridge + present paginate(bridges), with: ::API::Entities::Ci::Bridge end # rubocop: enable CodeReuse/ActiveRecord desc 'Get a specific job of a project' do - success Entities::Job + success Entities::Ci::Job end params do requires :job_id, type: Integer, desc: 'The ID of a job' @@ -107,7 +107,7 @@ module API build = find_build!(params[:job_id]) - present build, with: Entities::Job + present build, with: Entities::Ci::Job end # TODO: We should use `present_disk_file!` and leave this implementation for backward compatibility (when build trace @@ -131,7 +131,7 @@ module API end desc 'Cancel a specific job of a project' do - success Entities::Job + success Entities::Ci::Job end params do requires :job_id, type: Integer, desc: 'The ID of a job' @@ -144,11 +144,11 @@ module API build.cancel - present build, with: Entities::Job + present build, with: Entities::Ci::Job end desc 'Retry a specific build of a project' do - success Entities::Job + success Entities::Ci::Job end params do requires :job_id, type: Integer, desc: 'The ID of a build' @@ -162,11 +162,11 @@ module API build = ::Ci::Build.retry(build, current_user) - present build, with: Entities::Job + present build, with: Entities::Ci::Job end desc 'Erase job (remove artifacts and the trace)' do - success Entities::Job + success Entities::Ci::Job end params do requires :job_id, type: Integer, desc: 'The ID of a build' @@ -179,11 +179,11 @@ module API break forbidden!('Job is not erasable!') unless build.erasable? build.erase(erased_by: current_user) - present build, with: Entities::Job + present build, with: Entities::Ci::Job end desc 'Trigger a actionable job (manual, delayed, etc)' do - success Entities::Job + success Entities::Ci::Job detail 'This feature was added in GitLab 8.11' end params do @@ -200,7 +200,7 @@ module API build.play(current_user) status 200 - present build, with: Entities::Job + present build, with: Entities::Ci::Job end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 2e6ac40a593..6f25df720c4 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -97,7 +97,7 @@ module API user_access = Gitlab::UserAccess.new( current_user, - project: merge_request.source_project + container: merge_request.source_project ) forbidden!('Cannot push to source branch') unless @@ -153,22 +153,6 @@ module API include TimeTrackingEndpoints helpers do - def handle_merge_request_errors!(errors) - if errors[:project_access].any? - error!(errors[:project_access], 422) - elsif errors[:branch_conflict].any? - error!(errors[:branch_conflict], 422) - elsif errors[:validate_fork].any? - error!(errors[:validate_fork], 422) - elsif errors[:validate_branches].any? - conflict!(errors[:validate_branches]) - elsif errors[:base].any? - error!(errors[:base], 422) - end - - render_api_error!(errors, 400) - end - params :optional_params do optional :description, type: String, desc: 'The description of the merge request' optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request' @@ -226,11 +210,9 @@ module API merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute - if merge_request.valid? - present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project - else - handle_merge_request_errors! merge_request.errors - end + handle_merge_request_errors!(merge_request) + + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Delete a merge request' @@ -370,16 +352,16 @@ module API end desc 'Get the merge request pipelines' do - success Entities::PipelineBasic + success Entities::Ci::PipelineBasic end get ':id/merge_requests/:merge_request_iid/pipelines' do pipelines = merge_request_pipelines_with_access - present paginate(pipelines), with: Entities::PipelineBasic + present paginate(pipelines), with: Entities::Ci::PipelineBasic end desc 'Create a pipeline for merge request' do - success Entities::Pipeline + success ::API::Entities::Ci::Pipeline end post ':id/merge_requests/:merge_request_iid/pipelines' do pipeline = ::MergeRequests::CreatePipelineService @@ -390,7 +372,7 @@ module API not_allowed! elsif pipeline.persisted? status :ok - present pipeline, with: Entities::Pipeline + present pipeline, with: ::API::Entities::Ci::Pipeline else render_validation_error!(pipeline) end @@ -420,11 +402,9 @@ module API merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) - if merge_request.valid? - present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project - else - handle_merge_request_errors! merge_request.errors - end + handle_merge_request_errors!(merge_request) + + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Merge a merge request' do diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb index 8ff885983bc..b337b992841 100644 --- a/lib/api/milestone_responses.rb +++ b/lib/api/milestone_responses.rb @@ -14,10 +14,12 @@ module API params :list_params do optional :state, type: String, values: %w[active closed all], default: 'all', - desc: 'Return "active", "closed", or "all" milestones' + desc: 'Return "active", "closed", or "all" milestones' optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IIDs of the milestones' optional :title, type: String, desc: 'The title of the milestones' optional :search, type: String, desc: 'The search criteria for the title or description of the milestone' + optional :include_parent_milestones, type: Grape::API::Boolean, default: false, + desc: 'Include group milestones from parent and its ancestors' use :pagination end @@ -25,15 +27,18 @@ module API requires :milestone_id, type: Integer, desc: 'The milestone ID number' optional :title, type: String, desc: 'The title of the milestone' optional :state_event, type: String, values: %w[close activate], - desc: 'The state event of the milestone ' + desc: 'The state event of the milestone ' use :optional_params at_least_one_of :title, :description, :start_date, :due_date, :state_event end def list_milestones_for(parent) - milestones = parent.milestones.order_id_desc + milestones = init_milestones_collection(parent) milestones = Milestone.filter_by_state(milestones, params[:state]) - milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present? + if params[:iids].present? && !params[:include_parent_milestones] + milestones = filter_by_iid(milestones, params[:iids]) + end + milestones = filter_by_title(milestones, params[:title]) if params[:title] milestones = filter_by_search(milestones, params[:search]) if params[:search] @@ -96,6 +101,41 @@ module API [MergeRequestsFinder, Entities::MergeRequestBasic] end end + + def init_milestones_collection(parent) + milestones = if params[:include_parent_milestones].present? + parent_and_ancestors_milestones(parent) + else + parent.milestones + end + + milestones.order_id_desc + end + + def parent_and_ancestors_milestones(parent) + project_id, group_ids = if parent.is_a?(Project) + [parent.id, project_group_ids(parent)] + else + [nil, parent_group_ids(parent)] + end + + Milestone.for_projects_and_groups(project_id, group_ids) + end + + def project_group_ids(parent) + group = parent.group + return unless group.present? + + group.self_and_ancestors.select(:id) + end + + def parent_group_ids(group) + return unless group.present? + + group.self_and_ancestors + .public_or_visible_to_user(current_user) + .select(:id) + end end end end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index bfd09dcd496..e4989243f3d 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -101,7 +101,8 @@ module API params do requires :noteable_id, type: Integer, desc: 'The ID of the noteable' requires :note_id, type: Integer, desc: 'The ID of a note' - requires :body, type: String, desc: 'The content of a note' + optional :body, type: String, allow_blank: false, desc: 'The content of a note' + optional :confidential, type: Boolean, desc: 'Confidentiality note flag' end put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do noteable = find_noteable(noteable_type, params[:noteable_id]) diff --git a/lib/api/nuget_packages.rb b/lib/api/nuget_packages.rb index eb7d320a0f5..56c4de2071d 100644 --- a/lib/api/nuget_packages.rb +++ b/lib/api/nuget_packages.rb @@ -54,7 +54,9 @@ module API params do requires :id, type: String, desc: 'The ID of a project', regexp: POSITIVE_INTEGER_REGEX end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before do authorized_user_project @@ -65,7 +67,9 @@ module API desc 'The NuGet Service Index' do detail 'This feature was introduced in GitLab 12.6' end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + get 'index', format: :json do authorize_read_package!(authorized_user_project) @@ -79,10 +83,13 @@ module API desc 'The NuGet Package Publish endpoint' do detail 'This feature was introduced in GitLab 12.6' end + params do requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + put do authorize_upload!(authorized_user_project) @@ -107,7 +114,9 @@ module API forbidden! end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + put 'authorize' do authorize_workhorse!(subject: authorized_user_project, has_length: false) end @@ -124,7 +133,9 @@ module API desc 'The NuGet Metadata Service - Package name level' do detail 'This feature was introduced in GitLab 12.8' end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + get 'index', format: :json do present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages), with: ::API::Entities::Nuget::PackagesMetadata @@ -136,7 +147,9 @@ module API params do requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + get '*package_version', format: :json do present ::Packages::Nuget::PackageMetadataPresenter.new(find_package), with: ::API::Entities::Nuget::PackageMetadata @@ -155,7 +168,9 @@ module API desc 'The NuGet Content Service - index request' do detail 'This feature was introduced in GitLab 12.8' end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + get 'index', format: :json do present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages), with: ::API::Entities::Nuget::PackagesVersions @@ -168,7 +183,9 @@ module API requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + get '*package_version/*package_filename', format: :nupkg do filename = "#{params[:package_filename]}.#{params[:format]}" package_file = ::Packages::PackageFileFinder.new(find_package, filename, with_file_name_like: true) @@ -198,7 +215,9 @@ module API desc 'The NuGet Search Service' do detail 'This feature was introduced in GitLab 12.8' end - route_setting :authentication, deploy_token_allowed: true + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + get format: :json do search_options = { include_prerelease_versions: params[:prerelease], diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index d11c47f8d78..377d61689b3 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -47,6 +47,8 @@ module API post ':id/export' do check_rate_limit! :project_export, [current_user] + user_project.remove_exports + project_export_params = declared_params(include_missing: false) after_export_params = project_export_params.delete(:upload) || {} diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 7cea44e6304..e68a3b106b1 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -20,6 +20,7 @@ module API optional :job_events, type: Boolean, desc: "Trigger hook on job events" optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events" optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events" + optional :deployment_events, type: Boolean, desc: "Trigger hook on deployment events" optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response" optional :push_events_branch_filter, type: String, desc: "Trigger hook on specified branch only" diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 09934502e85..fba4c60504f 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -56,16 +56,20 @@ module API end params do requires :title, type: String, allow_blank: false, desc: 'The title of the snippet' - requires :file_name, type: String, desc: 'The file name of the snippet' - requires :content, type: String, allow_blank: false, desc: 'The content of the snippet' optional :description, type: String, desc: 'The description of a snippet' requires :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the snippet' + use :create_file_params end post ":id/snippets" do authorize! :create_snippet, user_project - snippet_params = declared_params(include_missing: false).merge(request: request, api: true) + snippet_params = declared_params(include_missing: false).tap do |create_args| + create_args[:request] = request + create_args[:api] = true + + process_file_args(create_args) + end service_response = ::Snippets::CreateService.new(user_project, current_user, snippet_params).execute snippet = service_response.payload[:snippet] diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb index f0fe4d85c8f..48c3dbed3b0 100644 --- a/lib/api/project_templates.rb +++ b/lib/api/project_templates.rb @@ -4,7 +4,7 @@ module API class ProjectTemplates < Grape::API::Instance include PaginationParams - TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses].freeze + TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses metrics_dashboard_ymls issues merge_requests].freeze # The regex is needed to ensure a period (e.g. agpl-3.0) # isn't confused with a format type. We also need to allow encoded # values (e.g. C%2B%2B for C++), so allow % and + as well. @@ -14,7 +14,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' - requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses) of the template' + requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses|metrics_dashboard_ymls|issues|merge_requests) of the template' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a list of templates available to this project' do @@ -42,9 +42,13 @@ module API end get ':id/templates/:type/:name', requirements: TEMPLATE_NAMES_ENDPOINT_REQUIREMENTS do - template = TemplateFinder - .build(params[:type], user_project, name: params[:name]) - .execute + begin + template = TemplateFinder + .build(params[:type], user_project, name: params[:name]) + .execute + rescue ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError + not_found!('Template') + end not_found!('Template') unless template.present? diff --git a/lib/api/projects.rb b/lib/api/projects.rb index d24dab63bd9..abbdb11a3f7 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -448,7 +448,7 @@ module API .execute.map { |lang| [lang.name, lang.share] }.to_h end - desc 'Remove a project' + desc 'Delete a project' delete ":id" do authorize! :remove_project, user_project diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index a6caacd7df8..739928a61ed 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -22,10 +22,6 @@ module API render_api_error!(e.message, 400) end - rescue_from ActiveRecord::RecordInvalid do |e| - render_api_error!(e.message, 400) - end - helpers do def packages_finder(project = authorized_user_project) project @@ -68,7 +64,7 @@ module API requires :sha256, type: String, desc: 'The PyPi package sha256 check sum' end - route_setting :authentication, deploy_token_allowed: true + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true get 'files/:sha256/*file_identifier' do project = unauthorized_user_project! @@ -91,7 +87,7 @@ module API # An Api entry point but returns an HTML file instead of JSON. # PyPi simple API returns the package descriptor as a simple HTML file. - route_setting :authentication, deploy_token_allowed: true + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true get 'simple/*package_name', format: :txt do authorize_read_package!(authorized_user_project) @@ -121,7 +117,7 @@ module API optional :sha256_digest, type: String end - route_setting :authentication, deploy_token_allowed: true + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true post do authorize_upload!(authorized_user_project) @@ -138,7 +134,7 @@ module API forbidden! end - route_setting :authentication, deploy_token_allowed: true + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true post 'authorize' do authorize_workhorse!(subject: authorized_user_project, has_length: false) end diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index 7e1815480a5..9624b8924e5 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -40,7 +40,7 @@ module API requires :name, type: String, desc: 'The name of the link' requires :url, type: String, desc: 'The URL of the link' optional :filepath, type: String, desc: 'The filepath of the link' - optional :link_type, type: String, desc: 'The link type' + optional :link_type, type: String, desc: 'The link type, one of: "runbook", "image", "package" or "other"' end post 'links' do authorize! :create_release, release diff --git a/lib/api/releases.rb b/lib/api/releases.rb index 30c5e06053e..3c38721129f 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -50,8 +50,10 @@ module API optional :ref, type: String, desc: 'The commit sha or branch name' optional :assets, type: Hash do optional :links, type: Array do - requires :name, type: String - requires :url, type: String + requires :name, type: String, desc: 'The name of the link' + requires :url, type: String, desc: 'The URL of the link' + optional :filepath, type: String, desc: 'The filepath of the link' + optional :link_type, type: String, desc: 'The link type, one of: "runbook", "image", "package" or "other"' end end optional :milestones, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The titles of the related milestones', default: [] diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index 118045e3af2..1a3283aed98 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -66,18 +66,23 @@ module API end params do requires :title, type: String, allow_blank: false, desc: 'The title of a snippet' - requires :file_name, type: String, desc: 'The name of a snippet file' - requires :content, type: String, allow_blank: false, desc: 'The content of a snippet' optional :description, type: String, desc: 'The description of a snippet' optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, default: 'internal', desc: 'The visibility of the snippet' + use :create_file_params end post do authorize! :create_snippet - attrs = declared_params(include_missing: false).merge(request: request, api: true) + attrs = declared_params(include_missing: false).tap do |create_args| + create_args[:request] = request + create_args[:api] = true + + process_file_args(create_args) + end + service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute snippet = service_response.payload[:snippet] diff --git a/lib/api/support/git_access_actor.rb b/lib/api/support/git_access_actor.rb index cb9bf4472eb..39730da1251 100644 --- a/lib/api/support/git_access_actor.rb +++ b/lib/api/support/git_access_actor.rb @@ -39,6 +39,15 @@ module API def update_last_used_at! key&.update_last_used_at end + + def key_details + return {} unless key + + { + gl_key_type: key.model_name.singular, + gl_key_id: key.id + } + end end end end diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index de67a149274..f398bbf3e32 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -11,7 +11,7 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Trigger a GitLab project pipeline' do - success Entities::Pipeline + success Entities::Ci::Pipeline end params do requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false @@ -38,7 +38,7 @@ module API if result[:http_status] render_api_error!(result[:message], result[:http_status]) else - present result[:pipeline], with: Entities::Pipeline + present result[:pipeline], with: Entities::Ci::Pipeline end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 7942777287b..335624963aa 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -218,7 +218,7 @@ module API user_params = declared_params(include_missing: false) - user_params[:password_expires_at] = Time.now if user_params[:password].present? + user_params[:password_expires_at] = Time.current if user_params[:password].present? result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute if result[:status] == :success diff --git a/lib/api/validations/validators/file_path.rb b/lib/api/validations/validators/file_path.rb index fee71373170..8a815c3b2b8 100644 --- a/lib/api/validations/validators/file_path.rb +++ b/lib/api/validations/validators/file_path.rb @@ -5,10 +5,12 @@ module API module Validators class FilePath < Grape::Validations::Base def validate_param!(attr_name, params) + options = @option.is_a?(Hash) ? @option : {} + path_allowlist = options.fetch(:allowlist, []) path = params[attr_name] - - Gitlab::Utils.check_path_traversal!(path) - rescue ::Gitlab::Utils::PathTraversalAttackError + path = Gitlab::Utils.check_path_traversal!(path) + Gitlab::Utils.check_allowed_absolute_path!(path, path_allowlist) + rescue raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "should be a valid file path" end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 50d137ec7c1..cea0cb3a19c 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -30,18 +30,18 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get project variables' do - success Entities::Variable + success Entities::Ci::Variable end params do use :pagination end get ':id/variables' do variables = user_project.variables - present paginate(variables), with: Entities::Variable + present paginate(variables), with: Entities::Ci::Variable end desc 'Get a specific variable from a project' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, type: String, desc: 'The key of the variable' @@ -51,12 +51,12 @@ module API variable = find_variable(params) not_found!('Variable') unless variable - present variable, with: Entities::Variable + present variable, with: Entities::Ci::Variable end # rubocop: enable CodeReuse/ActiveRecord desc 'Create a new variable in a project' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, type: String, desc: 'The key of the variable' @@ -67,20 +67,21 @@ module API optional :environment_scope, type: String, desc: 'The environment_scope of the variable' end post ':id/variables' do - variable_params = declared_params(include_missing: false) - variable_params = filter_variable_parameters(variable_params) - - variable = user_project.variables.create(variable_params) + variable = ::Ci::ChangeVariableService.new( + container: user_project, + current_user: current_user, + params: { action: :create, variable_params: filter_variable_parameters(declared_params(include_missing: false)) } + ).execute if variable.valid? - present variable, with: Entities::Variable + present variable, with: Entities::Ci::Variable else render_validation_error!(variable) end end desc 'Update an existing variable from a project' do - success Entities::Variable + success Entities::Ci::Variable end params do optional :key, type: String, desc: 'The key of the variable' @@ -96,11 +97,18 @@ module API variable = find_variable(params) not_found!('Variable') unless variable - variable_params = declared_params(include_missing: false).except(:key, :filter) - variable_params = filter_variable_parameters(variable_params) + variable_params = filter_variable_parameters( + declared_params(include_missing: false) + .except(:key, :filter) + ) + variable = ::Ci::ChangeVariableService.new( + container: user_project, + current_user: current_user, + params: { action: :update, variable: variable, variable_params: variable_params } + ).execute - if variable.update(variable_params) - present variable, with: Entities::Variable + if variable.valid? + present variable, with: Entities::Ci::Variable else render_validation_error!(variable) end @@ -108,7 +116,7 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Delete an existing variable from a project' do - success Entities::Variable + success Entities::Ci::Variable end params do requires :key, type: String, desc: 'The key of the variable' @@ -119,8 +127,11 @@ module API variable = find_variable(params) not_found!('Variable') unless variable - # Variables don't have a timestamp. Therefore, destroy unconditionally. - variable.destroy + ::Ci::ChangeVariableService.new( + container: user_project, + current_user: current_user, + params: { action: :destroy, variable: variable } + ).execute no_content! end diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index 713136e0887..95afa36113c 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -61,9 +61,10 @@ module API post ':id/wikis' do authorize! :create_wiki, container - page = WikiPages::CreateService.new(container: container, current_user: current_user, params: params).execute + response = WikiPages::CreateService.new(container: container, current_user: current_user, params: params).execute + page = response.payload[:page] - if page.valid? + if response.success? present page, with: Entities::WikiPage else render_validation_error!(page) |