diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /lib/api | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'lib/api')
50 files changed, 247 insertions, 121 deletions
diff --git a/lib/api/admin/plan_limits.rb b/lib/api/admin/plan_limits.rb index ab6a4e4a04a..d595b5b2e09 100644 --- a/lib/api/admin/plan_limits.rb +++ b/lib/api/admin/plan_limits.rb @@ -37,6 +37,7 @@ module API optional :conan_max_file_size, type: Integer, desc: 'Maximum Conan package file size in bytes' optional :generic_packages_max_file_size, type: Integer, desc: 'Maximum generic package file size in bytes' + optional :helm_max_file_size, type: Integer, desc: 'Maximum Helm chart file size in bytes' optional :maven_max_file_size, type: Integer, desc: 'Maximum Maven package file size in bytes' optional :npm_max_file_size, type: Integer, desc: 'Maximum NPM package file size in bytes' optional :nuget_max_file_size, type: Integer, desc: 'Maximum NuGet package file size in bytes' diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb index dabb6c7ab3a..72c388160b4 100644 --- a/lib/api/ci/helpers/runner.rb +++ b/lib/api/ci/helpers/runner.rb @@ -29,7 +29,7 @@ module API def get_runner_details_from_request return get_runner_ip unless params['info'].present? - attributes_for_keys(%w(name version revision platform architecture), params['info']) + attributes_for_keys(%w(name version revision platform architecture executor), params['info']) .merge(get_runner_config_from_request) .merge(get_runner_ip) end @@ -52,7 +52,7 @@ module API # HTTP status codes to terminate the job on GitLab Runner: # - 403 - def authenticate_job!(require_running: true) + def authenticate_job!(require_running: true, heartbeat_runner: false) job = current_job # 404 is not returned here because we want to terminate the job if it's @@ -70,7 +70,17 @@ module API job_forbidden!(job, 'Job is not running') unless job.running? end - job.runner&.heartbeat(get_runner_ip) + # Only some requests (like updating the job or patching the trace) should trigger + # runner heartbeat. Operations like artifacts uploading are executed in context of + # the running job and in the job environment, which in many cases will cause the IP + # to be updated to not the expected value. And operations like artifacts downloads can + # be done even after the job is finished and from totally different runners - while + # they would then update the connection status of not the runner that they should. + # Runner requests done in context of job authentication should explicitly define when + # the heartbeat should be triggered. + if heartbeat_runner + job.runner&.heartbeat(get_runner_ip) + end job end diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 03b59e7e6ad..4e5d6c264bf 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -166,7 +166,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring do + get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring, urgency: :low do authorize! :read_pipeline_variable, pipeline present pipeline.variables, with: Entities::Ci::Variable diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index aabcf34952c..4317789f7aa 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -176,7 +176,7 @@ module API optional :exit_code, type: Integer, desc: %q(Job's exit code) end put '/:id', feature_category: :continuous_integration do - job = authenticate_job! + job = authenticate_job!(heartbeat_runner: true) Gitlab::Metrics.add_event(:update_build) @@ -203,7 +203,7 @@ module API optional :token, type: String, desc: %q(Job's authentication token) end patch '/:id/trace', feature_category: :continuous_integration do - job = authenticate_job! + job = authenticate_job!(heartbeat_runner: true) error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range') content_range = request.headers['Content-Range'] diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 1785362656e..c89abf72e2d 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -78,13 +78,16 @@ module API name = params[:name] || params[:context] || 'default' - pipeline ||= user_project.ci_pipelines.create!( + pipeline ||= user_project.ci_pipelines.build( source: :external, sha: commit.sha, ref: ref, user: current_user, protected: user_project.protected_for?(ref)) + pipeline.ensure_project_iid! + pipeline.save! + authorize! :update_pipeline, pipeline status = GenericCommitStatus.running_or_pending.find_or_initialize_by( diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index 94cad7e6c65..0e6e04d2645 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -70,7 +70,7 @@ module API end desc 'Composer packages endpoint at group level' - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/packages' do presenter.root end @@ -79,7 +79,7 @@ module API params do requires :sha, type: String, desc: 'Shasum of current json' end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/p/:sha' do presenter.provider end @@ -88,7 +88,7 @@ module API params do requires :package_name, type: String, file_path: true, desc: 'The Composer package name' end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do not_found! if packages.empty? @@ -99,7 +99,7 @@ module API params do requires :package_name, type: String, file_path: true, desc: 'The Composer package name' end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do not_found! if packages.empty? not_found! if params[:sha].blank? @@ -119,7 +119,7 @@ module API desc 'Composer packages endpoint for registering packages' namespace ':id/packages/composer' do - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true params do optional :branch, type: String, desc: 'The name of the branch' diff --git a/lib/api/concerns/packages/conan_endpoints.rb b/lib/api/concerns/packages/conan_endpoints.rb index 3194cdebde8..edf20b6aebe 100644 --- a/lib/api/concerns/packages/conan_endpoints.rb +++ b/lib/api/concerns/packages/conan_endpoints.rb @@ -27,6 +27,7 @@ module API PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex + CONAN_REVISION_USER_CHANNEL_REGEX = Gitlab::Regex.conan_recipe_user_channel_regex CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze @@ -105,10 +106,14 @@ module API params do requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name' requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' - requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username' - requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel' + requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username' + requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel' end namespace 'conans/:package_name/:package_version/:package_username/:package_channel', requirements: PACKAGE_REQUIREMENTS do + after_validation do + check_username_channel + end + # Get the snapshot # # the snapshot is a hash of { filename: md5 hash } @@ -264,8 +269,8 @@ module API params do requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name' requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' - requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username' - requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel' + requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username' + requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel' requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision' end namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do @@ -273,6 +278,10 @@ module API authenticate_non_get! end + after_validation do + check_username_channel + end + params do requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES end diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb index 7a657be5bf3..d6e006df976 100644 --- a/lib/api/concerns/packages/npm_endpoints.rb +++ b/lib/api/concerns/packages/npm_endpoints.rb @@ -121,9 +121,7 @@ module API not_found!('Packages') if packages.empty? - include_metadata = Feature.enabled?(:packages_npm_abbreviated_metadata, project, default_enabled: :yaml) - - present ::Packages::Npm::PackagePresenter.new(package_name, packages, include_metadata: include_metadata), + present ::Packages::Npm::PackagePresenter.new(package_name, packages), with: ::API::Entities::NpmPackage end end diff --git a/lib/api/dependency_proxy.rb b/lib/api/dependency_proxy.rb index 185b8d5a15d..9d0b1bf4423 100644 --- a/lib/api/dependency_proxy.rb +++ b/lib/api/dependency_proxy.rb @@ -6,15 +6,6 @@ module API feature_category :dependency_proxy - helpers do - def obtain_new_purge_cache_lease - Gitlab::ExclusiveLease - .new("dependency_proxy:delete_group_blobs:#{user_group.id}", - timeout: 1.hour) - .try_obtain - end - end - after_validation do authorize! :admin_group, user_group end @@ -29,9 +20,6 @@ module API delete ':id/dependency_proxy/cache' do not_found! unless user_group.dependency_proxy_feature_available? - message = 'This request has already been made. It may take some time to purge the cache. You can run this at most once an hour for a given group' - render_api_error!(message, 409) unless obtain_new_purge_cache_lease - # rubocop:disable CodeReuse/Worker PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id) # rubocop:enable CodeReuse/Worker diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb index cf4b2348458..0709a8c2036 100644 --- a/lib/api/discussions.rb +++ b/lib/api/discussions.rb @@ -8,6 +8,13 @@ module API before { authenticate! } + urgency :low, [ + '/projects/:id/merge_requests/:noteable_id/discussions', + '/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id', + '/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id/notes', + '/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id/notes/:note_id' + ] + Helpers::DiscussionsHelpers.feature_category_per_noteable_type.each do |noteable_type, feature_category| parent_type = noteable_type.parent_class.to_s.underscore noteables_str = noteable_type.to_s.underscore.pluralize diff --git a/lib/api/entities/changelog.rb b/lib/api/entities/changelog.rb new file mode 100644 index 00000000000..f8ca5826418 --- /dev/null +++ b/lib/api/entities/changelog.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class Changelog < Grape::Entity + expose :to_s, as: :notes + end + end +end diff --git a/lib/api/entities/ci/job_basic.rb b/lib/api/entities/ci/job_basic.rb index c31340f1ff0..0badde4089e 100644 --- a/lib/api/entities/ci/job_basic.rb +++ b/lib/api/entities/ci/job_basic.rb @@ -13,6 +13,7 @@ module API expose :user, with: ::API::Entities::User expose :commit, with: ::API::Entities::Commit expose :pipeline, with: ::API::Entities::Ci::PipelineBasic + expose :failure_reason, if: -> (job) { job.failed? } expose :web_url do |job, _options| Gitlab::Routing.url_helpers.project_job_url(job.project, job) diff --git a/lib/api/entities/ci/pipeline.rb b/lib/api/entities/ci/pipeline.rb index 11336ae070d..a8033a21044 100644 --- a/lib/api/entities/ci/pipeline.rb +++ b/lib/api/entities/ci/pipeline.rb @@ -10,7 +10,9 @@ module API expose :created_at, :updated_at, :started_at, :finished_at, :committed_at expose :duration expose :queued_duration - expose :coverage + expose :coverage do |pipeline| + pipeline.present.coverage + end expose :detailed_status, using: DetailedStatusEntity do |pipeline, options| pipeline.detailed_status(options[:current_user]) end diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb index 4d56176bdb3..a2a5a98920a 100644 --- a/lib/api/entities/ci/pipeline_basic.rb +++ b/lib/api/entities/ci/pipeline_basic.rb @@ -4,7 +4,7 @@ module API module Entities module Ci class PipelineBasic < Grape::Entity - expose :id, :project_id, :sha, :ref, :status, :source + expose :id, :iid, :project_id, :sha, :ref, :status, :source expose :created_at, :updated_at expose :web_url do |pipeline, _options| diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb index 60193fe1df4..c17ff513479 100644 --- a/lib/api/entities/ci/runner.rb +++ b/lib/api/entities/ci/runner.rb @@ -14,7 +14,7 @@ module API expose :online?, as: :online # DEPRECATED # TODO Remove in %15.0 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648 - expose :status, as: :deprecated_rest_status + expose :deprecated_rest_status, as: :status end end end diff --git a/lib/api/entities/commit_signature.rb b/lib/api/entities/commit_signature.rb index 505ce462edf..0d8e977a9f5 100644 --- a/lib/api/entities/commit_signature.rb +++ b/lib/api/entities/commit_signature.rb @@ -6,9 +6,9 @@ module API expose :signature_type expose :signature, merge: true do |commit, options| - if commit.signature.is_a?(GpgSignature) || commit.raw_commit_from_rugged? + if commit.signature.is_a?(::CommitSignatures::GpgSignature) || commit.raw_commit_from_rugged? ::API::Entities::GpgCommitSignature.represent commit_signature(commit), options - elsif commit.signature.is_a?(X509CommitSignature) + elsif commit.signature.is_a?(::CommitSignatures::X509CommitSignature) ::API::Entities::X509Signature.represent commit.signature, options end end diff --git a/lib/api/entities/issue_basic.rb b/lib/api/entities/issue_basic.rb index ab248523028..6125dc05a6e 100644 --- a/lib/api/entities/issue_basic.rb +++ b/lib/api/entities/issue_basic.rb @@ -23,7 +23,7 @@ module API expose :issue_type, as: :type, format_with: :upcase, - documentation: { type: "String", desc: "One of #{::WorkItem::Type.base_types.keys.map(&:upcase)}" } + documentation: { type: "String", desc: "One of #{::WorkItem::Type.allowed_types_for_issues.map(&:upcase)}" } expose :assignee, using: ::API::Entities::UserBasic do |issue| issue.assignees.first diff --git a/lib/api/entities/personal_access_token.rb b/lib/api/entities/personal_access_token.rb index 3846929c903..55764daef9d 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, :user_id + expose :id, :name, :revoked, :created_at, :scopes, :user_id, :last_used_at 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/plan_limit.rb b/lib/api/entities/plan_limit.rb index 04ec44b5167..9f4d1635998 100644 --- a/lib/api/entities/plan_limit.rb +++ b/lib/api/entities/plan_limit.rb @@ -5,6 +5,7 @@ module API class PlanLimit < Grape::Entity expose :conan_max_file_size expose :generic_packages_max_file_size + expose :helm_max_file_size expose :maven_max_file_size expose :npm_max_file_size expose :nuget_max_file_size diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 662ca59852e..1b9299ed17e 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -117,6 +117,7 @@ module API expose :squash_option expose :suggestion_commit_message expose :merge_commit_template + expose :squash_commit_template expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) { options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project) } diff --git a/lib/api/entities/project_import_failed_relation.rb b/lib/api/entities/project_import_failed_relation.rb index b8f842c1646..26cfae7260c 100644 --- a/lib/api/entities/project_import_failed_relation.rb +++ b/lib/api/entities/project_import_failed_relation.rb @@ -10,6 +10,7 @@ module API end expose :relation_key, as: :relation_name + expose :relation_index, as: :line_number end end end diff --git a/lib/api/entities/project_import_status.rb b/lib/api/entities/project_import_status.rb index e79c1cdf1a2..5daae4a70f2 100644 --- a/lib/api/entities/project_import_status.rb +++ b/lib/api/entities/project_import_status.rb @@ -4,6 +4,7 @@ module API module Entities class ProjectImportStatus < ProjectIdentity expose :import_status + expose :import_type expose :correlation_id do |project, _options| project.import_state&.correlation_id end @@ -15,6 +16,12 @@ module API expose :import_error do |project, _options| project.import_state&.last_error end + + expose :stats do |project, _options| + if project.github_import? + ::Gitlab::GithubImport::ObjectCounter.summary(project) + end + end end end end diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb index 25cc4e53bd2..f0c0182a02f 100644 --- a/lib/api/group_export.rb +++ b/lib/api/group_export.rb @@ -18,7 +18,7 @@ module API detail 'This feature was introduced in GitLab 12.5.' end get ':id/export/download' do - check_rate_limit! :group_download_export, [current_user, user_group] + check_rate_limit! :group_download_export, scope: [current_user, user_group] if user_group.export_file_exists? if user_group.export_archive_exists? @@ -35,7 +35,7 @@ module API detail 'This feature was introduced in GitLab 12.5.' end post ':id/export' do - check_rate_limit! :group_export, [current_user] + check_rate_limit! :group_export, scope: current_user export_service = ::Groups::ImportExport::ExportService.new(group: user_group, user: current_user) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 680e3a6e994..d3d1f03585b 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -109,7 +109,7 @@ module API end def present_groups_with_pagination_strategies(params, groups) - return present_groups(params, groups) if current_user.present? || Feature.disabled?(:keyset_pagination_for_groups_api) + return present_groups(params, groups) if current_user.present? options = { with: Entities::Group, @@ -382,6 +382,28 @@ module API end end + desc 'Transfer a group to a new parent group or promote a subgroup to a root group' + params do + optional :group_id, type: Integer, + desc: 'The ID of the target group to which the group needs to be transferred to.'\ + 'If not provided, the source group will be promoted to a root group.' + end + post ':id/transfer' do + group = find_group!(params[:id]) + authorize! :admin_group, group + + new_parent_group = find_group!(params[:group_id]) if params[:group_id].present? + + service = ::Groups::TransferService.new(group, current_user) + + if service.execute(new_parent_group) + group.preload_shared_group_links + present group, with: Entities::GroupDetail, current_user: current_user + else + render_api_error!(service.error, 400) + end + end + desc 'Share a group with a group' do success Entities::GroupDetail end diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb index da0ee8f207e..02613cbf9b9 100644 --- a/lib/api/helpers/label_helpers.rb +++ b/lib/api/helpers/label_helpers.rb @@ -105,7 +105,11 @@ module API end def promote_label(parent) - authorize! :admin_label, parent + unless parent.group + render_api_error!('Failed to promote project label to group label', 400) + end + + authorize! :admin_label, parent.group label = find_label(parent, params[:name], include_ancestor_groups: false) diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 1e89f9f97a2..c2710be6c03 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -8,6 +8,9 @@ module API params :optional_filter_params_ee do end + params :optional_state_filter_ee do + end + def find_source(source_type, id) public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend end diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb index 4b6dac39348..031c29e7472 100644 --- a/lib/api/helpers/packages/conan/api_helpers.rb +++ b/lib/api/helpers/packages/conan/api_helpers.rb @@ -7,6 +7,21 @@ module API module ApiHelpers include Gitlab::Utils::StrongMemoize + def check_username_channel + username = declared(params)[:package_username] + channel = declared(params)[:package_channel] + + if username == ::Packages::Conan::Metadatum::NONE_VALUE && package_scope == :instance + # at the instance level, username must not be empty (naming convention) + # don't try to process the empty username and eagerly return not found. + not_found! + end + + ::Packages::Conan::Metadatum.validate_username_and_channel(username, channel) do |none_field| + bad_request!("#{none_field} can't be solely blank") + end + end + def present_download_urls(entity) authorize!(:read_package, project) diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 42d1c40dd11..d7de8bd8b8b 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -62,6 +62,7 @@ module API optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests' optional :suggestion_commit_message, type: String, desc: 'The commit message used to apply merge request suggestions' optional :merge_commit_template, type: String, desc: 'Template used to create merge commit message' + optional :squash_commit_template, type: String, desc: 'Template used to create squash commit message' optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md" optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning' optional :auto_devops_enabled, type: Boolean, desc: 'Flag indication if Auto DevOps is enabled' @@ -162,6 +163,7 @@ module API :avatar, :suggestion_commit_message, :merge_commit_template, + :squash_commit_template, :repository_storage, :compliance_framework_setting, :packages_enabled, @@ -181,9 +183,10 @@ module API def filter_attributes_using_license!(attrs) end - def validate_git_import_url!(import_url, import_enabled: true) + def validate_git_import_url!(import_url) return if import_url.blank? - return unless import_enabled + + yield if block_given? result = Import::ValidateRemoteGitEndpointService.new(url: import_url).execute # network call diff --git a/lib/api/helpers/rate_limiter.rb b/lib/api/helpers/rate_limiter.rb index 3a16aef6a74..7d87c74097d 100644 --- a/lib/api/helpers/rate_limiter.rb +++ b/lib/api/helpers/rate_limiter.rb @@ -2,26 +2,27 @@ module API module Helpers + # == RateLimiter + # + # Helper that checks if the rate limit for a given endpoint is throttled by calling the + # Gitlab::ApplicationRateLimiter class. If the action is throttled for the current user, the request + # will be logged and an error message will be rendered with a Too Many Requests response status. + # See app/controllers/concerns/check_rate_limit.rb for Rails controllers version module RateLimiter - def check_rate_limit!(key, scope, users_allowlist = nil) - if rate_limiter.throttled?(key, scope: scope, users_allowlist: users_allowlist) - log_request(key) - render_exceeded_limit_error! - end - end + def check_rate_limit!(key, scope:, **options) + return unless rate_limiter.throttled?(key, scope: scope, **options) - private + rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user) - def rate_limiter - ::Gitlab::ApplicationRateLimiter - end + return yield if block_given? - def render_exceeded_limit_error! render_api_error!({ error: _('This endpoint has been requested too many times. Try again later.') }, 429) end - def log_request(key) - rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user) + private + + def rate_limiter + ::Gitlab::ApplicationRateLimiter end end end diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index f7f5af07378..d78576b5d5b 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -24,7 +24,6 @@ module API requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api' - optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon' optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do' optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues' end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 9958526fa7f..4d67cbd1272 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -82,7 +82,7 @@ module API 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`' - optional :issue_type, type: String, values: WorkItem::Type.base_types.keys, desc: "The type of the issue. Accepts: #{WorkItem::Type.base_types.keys.join(', ')}" + optional :issue_type, type: String, values: WorkItem::Type.allowed_types_for_issues, desc: "The type of the issue. Accepts: #{WorkItem::Type.allowed_types_for_issues.join(', ')}" use :issues_stats_params use :pagination @@ -99,7 +99,7 @@ module API optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY' optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked" - optional :issue_type, type: String, values: WorkItem::Type.base_types.keys, desc: "The type of the issue. Accepts: #{WorkItem::Type.base_types.keys.join(', ')}" + optional :issue_type, type: String, values: WorkItem::Type.allowed_types_for_issues, desc: "The type of the issue. Accepts: #{WorkItem::Type.allowed_types_for_issues.join(', ')}" use :optional_issue_params_ee end @@ -262,7 +262,7 @@ module API post ':id/issues' do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21140') - check_rate_limit! :issues_create, [current_user] if Feature.disabled?("rate_limited_service_issues_create", user_project, default_enabled: :yaml) + check_rate_limit!(:issues_create, scope: current_user) if Feature.disabled?("rate_limited_service_issues_create", user_project, default_enabled: :yaml) authorize! :create_issue, user_project @@ -472,7 +472,7 @@ module API end get ':id/issues/:issue_iid/participants' do issue = find_project_issue(params[:issue_iid]) - participants = ::Kaminari.paginate_array(issue.participants) + participants = ::Kaminari.paginate_array(issue.visible_participants(current_user)) present paginate(participants), with: Entities::UserBasic, current_user: current_user, project: user_project end diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 3655cb56564..bfd457a3092 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -43,7 +43,7 @@ module API optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.' optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response' end - get ':id/ci/lint' do + get ':id/ci/lint', urgency: :low do authorize! :download_code, user_project content = user_project.repository.gitlab_ci_yml_for(user_project.commit.id, user_project.ci_config_path_or_default) @@ -64,7 +64,7 @@ module API optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.' optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response' end - post ':id/ci/lint' do + post ':id/ci/lint', urgency: :low do authorize! :create_pipeline, user_project result = Gitlab::Ci::Lint diff --git a/lib/api/members.rb b/lib/api/members.rb index f488c8c26fc..4798edc4ddf 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -41,6 +41,7 @@ module API optional :query, type: String, desc: 'A query string to search for members' optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of user ids to look up for membership' optional :show_seat_info, type: Boolean, desc: 'Show seat information for members' + use :optional_state_filter_ee use :pagination end @@ -94,7 +95,6 @@ module API requires :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api' - optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon' optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do' optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues' end diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb index 8fa7138af42..87623568a04 100644 --- a/lib/api/merge_request_diffs.rb +++ b/lib/api/merge_request_diffs.rb @@ -38,7 +38,7 @@ module API requires :version_id, type: Integer, desc: 'The ID of a merge request diff version' end - get ":id/merge_requests/:merge_request_iid/versions/:version_id" do + get ":id/merge_requests/:merge_request_iid/versions/:version_id", urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) present_cached merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull, cache_context: nil diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 96d1a69c03a..3f39af7f909 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -134,7 +134,7 @@ module API use :merge_requests_params use :optional_scope_param end - get feature_category: :code_review do + get feature_category: :code_review, urgency: :low do authenticate! unless params[:scope] == 'all' validate_anonymous_search_access! if params[:search].present? merge_requests = find_merge_requests @@ -155,7 +155,7 @@ module API optional :non_archived, type: Boolean, desc: 'Return merge requests from non archived projects', default: true end - get ":id/merge_requests", feature_category: :code_review do + get ":id/merge_requests", feature_category: :code_review, urgency: :low do validate_anonymous_search_access! if declared_params[:search].present? merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true) @@ -195,7 +195,7 @@ module API use :merge_requests_params optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of merge requests' end - get ":id/merge_requests", feature_category: :code_review do + get ":id/merge_requests", feature_category: :code_review, urgency: :low do authorize! :read_merge_request, user_project validate_anonymous_search_access! if declared_params[:search].present? @@ -222,7 +222,7 @@ module API desc: 'The target project of the merge request defaults to the :id of the project' use :optional_params end - post ":id/merge_requests", feature_category: :code_review do + post ":id/merge_requests", feature_category: :code_review, urgency: :low do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20770') authorize! :create_merge_request_from, user_project @@ -244,7 +244,7 @@ module API params do requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request' end - delete ":id/merge_requests/:merge_request_iid", feature_category: :code_review do + delete ":id/merge_requests/:merge_request_iid", feature_category: :code_review, urgency: :low do merge_request = find_project_merge_request(params[:merge_request_iid]) authorize!(:destroy_merge_request, merge_request) @@ -263,7 +263,7 @@ module API desc 'Get a single merge request' do success Entities::MergeRequest end - get ':id/merge_requests/:merge_request_iid', feature_category: :code_review do + get ':id/merge_requests/:merge_request_iid', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) present merge_request, @@ -279,10 +279,10 @@ module API desc 'Get the participants of a merge request' do success Entities::UserBasic end - get ':id/merge_requests/:merge_request_iid/participants', feature_category: :code_review do + get ':id/merge_requests/:merge_request_iid/participants', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) - participants = ::Kaminari.paginate_array(merge_request.participants) + participants = ::Kaminari.paginate_array(merge_request.visible_participants(current_user)) present paginate(participants), with: Entities::UserBasic end @@ -290,7 +290,7 @@ module API desc 'Get the commits of a merge request' do success Entities::Commit end - get ':id/merge_requests/:merge_request_iid/commits', feature_category: :code_review do + get ':id/merge_requests/:merge_request_iid/commits', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) commits = @@ -303,7 +303,7 @@ module API desc 'Get the context commits of a merge request' do success Entities::Commit end - get ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review do + get ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review, urgency: :high do merge_request = find_merge_request_with_access(params[:merge_request_iid]) project = merge_request.project @@ -371,7 +371,7 @@ module API desc 'Show the merge request changes' do success Entities::MergeRequestChanges end - get ':id/merge_requests/:merge_request_iid/changes', feature_category: :code_review do + get ':id/merge_requests/:merge_request_iid/changes', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) present merge_request, @@ -422,7 +422,7 @@ module API use :optional_params at_least_one_of(*::API::MergeRequests.update_params_at_least_one_of) end - put ':id/merge_requests/:merge_request_iid', feature_category: :code_review do + put ':id/merge_requests/:merge_request_iid', feature_category: :code_review, urgency: :low do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20772') merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request) @@ -454,7 +454,7 @@ module API optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' end - put ':id/merge_requests/:merge_request_iid/merge', feature_category: :code_review do + put ':id/merge_requests/:merge_request_iid/merge', feature_category: :code_review, urgency: :low do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/4796') merge_request = find_project_merge_request(params[:merge_request_iid]) @@ -524,7 +524,7 @@ module API params do optional :skip_ci, type: Boolean, desc: 'Do not create CI pipeline' end - put ':id/merge_requests/:merge_request_iid/rebase', feature_category: :code_review do + put ':id/merge_requests/:merge_request_iid/rebase', feature_category: :code_review, urgency: :low do merge_request = find_project_merge_request(params[:merge_request_iid]) authorize_push_to_merge_request!(merge_request) @@ -543,7 +543,7 @@ module API params do use :pagination end - get ':id/merge_requests/:merge_request_iid/closes_issues', feature_category: :code_review do + get ':id/merge_requests/:merge_request_iid/closes_issues', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) issues = ::Kaminari.paginate_array(merge_request.visible_closing_issues_for(current_user)) issues = paginate(issues) diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 656eaa2b2bb..93ef77d5a62 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -7,6 +7,11 @@ module API before { authenticate! } + urgency :low, [ + '/projects/:id/merge_requests/:noteable_id/notes', + '/projects/:id/merge_requests/:noteable_id/notes/:note_id' + ] + Helpers::NotesHelpers.feature_category_per_noteable_type.each do |noteable_type, feature_category| parent_type = noteable_type.parent_class.to_s.underscore noteables_str = noteable_type.to_s.underscore.pluralize @@ -74,7 +79,7 @@ module API post ":id/#{noteables_str}/:noteable_id/notes", feature_category: feature_category do allowlist = Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist - check_rate_limit! :notes_create, [current_user], allowlist + check_rate_limit! :notes_create, scope: current_user, users_allowlist: allowlist noteable = find_noteable(noteable_type, params[:noteable_id]) opts = { diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index e01c195dbc4..843f72c0e1d 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -25,7 +25,7 @@ module API detail 'This feature was introduced in GitLab 10.6.' end get ':id/export/download' do - check_rate_limit! :project_download_export, [current_user, user_project] + check_rate_limit! :project_download_export, scope: [current_user, user_project] if user_project.export_file_exists? if user_project.export_archive_exists? @@ -49,7 +49,7 @@ module API end end post ':id/export' do - check_rate_limit! :project_export, [current_user] + check_rate_limit! :project_export, scope: current_user user_project.remove_exports diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index e7c532e2483..7bdcaa5a26f 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -81,7 +81,7 @@ module API post 'import' do require_gitlab_workhorse! - check_rate_limit! :project_import, [current_user, :project_import] + check_rate_limit! :project_import, scope: [current_user, :project_import] Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21041') @@ -107,7 +107,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - desc 'Get a project export status' do + desc 'Get a project import status' do detail 'This feature was introduced in GitLab 10.6.' success Entities::ProjectImportStatus end @@ -135,7 +135,7 @@ module API post 'remote-import' do not_found! unless ::Feature.enabled?(:import_project_from_remote_file) - check_rate_limit! :project_import, [current_user, :project_import] + check_rate_limit! :project_import, scope: [current_user, :project_import] response = ::Import::GitlabProjects::CreateProjectFromRemoteFileService.new( current_user, diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 9f0077d23d8..67f0b7af7a9 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -270,7 +270,7 @@ module API attrs = translate_params_for_compatibility(attrs) filter_attributes_using_license!(attrs) - validate_git_import_url!(params[:import_url], import_enabled: check_import_by_url_is_enabled) + validate_git_import_url!(params[:import_url]) { check_import_by_url_is_enabled } project = ::Projects::CreateService.new(current_user, attrs).execute diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 2dd0e40afba..fc976c23726 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -10,6 +10,32 @@ module API helpers ::API::Helpers::HeadersHelpers + helpers do + params :release_params do + requires :version, + type: String, + regexp: Gitlab::Regex.unbounded_semver_regex, + desc: 'The version of the release, using the semantic versioning format' + + optional :from, + type: String, + desc: 'The first commit in the range of commits to use for the changelog' + + optional :to, + type: String, + desc: 'The last commit in the range of commits to use for the changelog' + + optional :date, + type: DateTime, + desc: 'The date and time of the release' + + optional :trailer, + type: String, + desc: 'The Git trailer to use for determining if commits are to be included in the changelog', + default: ::Repositories::ChangelogService::DEFAULT_TRAILER + end + end + before { authorize! :download_code, user_project } feature_category :source_code_management @@ -19,7 +45,7 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do helpers do - include ::Gitlab::RateLimitHelpers + include Gitlab::RepositoryArchiveRateLimiter def handle_project_member_errors(errors) if errors[:project_access].any? @@ -124,8 +150,8 @@ module API optional :path, type: String, desc: 'Subfolder of the repository to be downloaded' end get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do - if archive_rate_limit_reached?(current_user, user_project) - render_api_error!({ error: ::Gitlab::RateLimitHelpers::ARCHIVE_RATE_LIMIT_REACHED_MESSAGE }, 429) + check_archive_rate_limit!(current_user, user_project) do + render_api_error!({ error: _('This archive has been requested too many times. Try again later.') }, 429) end not_acceptable! if Gitlab::HotlinkingDetector.intercept_hotlinking?(request) @@ -208,36 +234,33 @@ module API end end - desc 'Generates a changelog section for a release' do - detail 'This feature was introduced in GitLab 13.9' + desc 'Generates a changelog section for a release and returns it' do + detail 'This feature was introduced in GitLab 14.6' end params do - requires :version, - type: String, - regexp: Gitlab::Regex.unbounded_semver_regex, - desc: 'The version of the release, using the semantic versioning format' - - optional :from, - type: String, - desc: 'The first commit in the range of commits to use for the changelog' + use :release_params + end + get ':id/repository/changelog' do + service = ::Repositories::ChangelogService.new( + user_project, + current_user, + **declared_params(include_missing: false) + ) + changelog = service.execute(commit_to_changelog: false) - optional :to, - type: String, - desc: 'The last commit in the range of commits to use for the changelog' + present changelog, with: Entities::Changelog + end - optional :date, - type: DateTime, - desc: 'The date and time of the release' + desc 'Generates a changelog section for a release and commits it in a changelog file' do + detail 'This feature was introduced in GitLab 13.9' + end + params do + use :release_params optional :branch, type: String, desc: 'The branch to commit the changelog changes to' - optional :trailer, - type: String, - desc: 'The Git trailer to use for determining if commits are to be included in the changelog', - default: ::Repositories::ChangelogService::DEFAULT_TRAILER - optional :file, type: String, desc: 'The file to commit the changelog changes to', @@ -261,7 +284,7 @@ module API **declared_params(include_missing: false) ) - service.execute + service.execute(commit_to_changelog: true) status(200) rescue Gitlab::Changelog::Error => ex render_api_error!("Failed to generate the changelog: #{ex.message}", 422) @@ -269,3 +292,5 @@ module API end end end + +API::Repositories.prepend_mod diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb index 33589f6c393..cd56809f45a 100644 --- a/lib/api/resource_label_events.rb +++ b/lib/api/resource_label_events.rb @@ -24,7 +24,7 @@ module API use :pagination end - get ":id/#{eventables_str}/:eventable_id/resource_label_events", feature_category: feature_category do + get ":id/#{eventables_str}/:eventable_id/resource_label_events", feature_category: feature_category, urgency: :low do eventable = find_noteable(eventable_type, params[:eventable_id]) events = eventable.resource_label_events.inc_relations diff --git a/lib/api/resource_milestone_events.rb b/lib/api/resource_milestone_events.rb index c0483ca59c2..04d71faa56a 100644 --- a/lib/api/resource_milestone_events.rb +++ b/lib/api/resource_milestone_events.rb @@ -26,7 +26,7 @@ module API use :pagination end - get ":id/#{eventables_str}/:eventable_id/resource_milestone_events", feature_category: feature_category do + get ":id/#{eventables_str}/:eventable_id/resource_milestone_events", feature_category: feature_category, urgency: :low do eventable = find_noteable(eventable_type, params[:eventable_id]) events = ResourceMilestoneEventFinder.new(current_user, eventable).execute diff --git a/lib/api/resource_state_events.rb b/lib/api/resource_state_events.rb index 9b6f6a954b4..4b92f320d6f 100644 --- a/lib/api/resource_state_events.rb +++ b/lib/api/resource_state_events.rb @@ -25,7 +25,7 @@ module API use :pagination end - get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events", feature_category: feature_category do + get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events", feature_category: feature_category, urgency: :low do eventable = find_noteable(eventable_class, params[:eventable_iid]) events = ResourceStateEventFinder.new(current_user, eventable).execute diff --git a/lib/api/search.rb b/lib/api/search.rb index 3c5801366a8..fbdbe3476db 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -8,6 +8,10 @@ module API feature_category :global_search + rescue_from ActiveRecord::QueryCanceled do |e| + render_api_error!({ error: 'Request timed out' }, 408) + end + helpers do SCOPE_ENTITY = { merge_requests: Entities::MergeRequestBasic, diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 12e1d21a00d..508ccdb4b33 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -154,7 +154,6 @@ module API optional :spam_check_endpoint_enabled, type: Boolean, desc: 'Enable Spam Check via external API endpoint' given spam_check_endpoint_enabled: ->(val) { val } do requires :spam_check_endpoint_url, type: String, desc: 'The URL of the external Spam Check service endpoint' - requires :spam_check_api_key, type: String, desc: 'The API key used by GitLab for accessing the Spam Check service endpoint' end optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb index 7921700e365..0697169b49a 100644 --- a/lib/api/suggestions.rb +++ b/lib/api/suggestions.rb @@ -14,7 +14,7 @@ module API requires :id, type: String, desc: 'The suggestion ID' optional :commit_message, type: String, desc: "A custom commit message to use instead of the default generated message or the project's default message" end - put ':id/apply' do + put ':id/apply', urgency: :low do suggestion = Suggestion.find_by_id(params[:id]) if suggestion @@ -31,7 +31,7 @@ module API requires :ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: "An array of suggestion ID's" optional :commit_message, type: String, desc: "A custom commit message to use instead of the default generated message or the project's default message" end - put 'batch_apply' do + put 'batch_apply', urgency: :low do ids = params[:ids] suggestions = Suggestion.id_in(ids) diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb index f6dfbcafbb6..29e71611092 100644 --- a/lib/api/terraform/state.rb +++ b/lib/api/terraform/state.rb @@ -11,6 +11,13 @@ module API default_format :json + rescue_from( + ::ActiveRecord::RecordNotUnique, + ::PG::UniqueViolation + ) do |e| + render_api_error!(e.message, 422) + end + before do authenticate! authorize! :read_terraform_state, user_project diff --git a/lib/api/topics.rb b/lib/api/topics.rb index bd28ebe58a9..b9c2bcc2da8 100644 --- a/lib/api/topics.rb +++ b/lib/api/topics.rb @@ -69,6 +69,8 @@ module API topic = ::Projects::Topic.find(params[:id]) + topic.remove_avatar! if params.key?(:avatar) && params[:avatar].nil? + if topic.update(declared_params(include_missing: false)) present topic, with: Entities::Projects::Topic else diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb index 677d0840208..d6c026963e1 100644 --- a/lib/api/v3/github.rb +++ b/lib/api/v3/github.rb @@ -101,8 +101,6 @@ module API # of time after a Gitaly timeout, to mitigate frequent Gitaly timeouts # for some Commit diffs. def diff_files(commit) - return commit.diffs.diff_files unless Feature.enabled?(:api_v3_commits_skip_diff_files, commit.project, default_enabled: :yaml) - cache_key = [ GITALY_TIMEOUT_CACHE_KEY, commit.project.id, diff --git a/lib/api/validations/types/workhorse_file.rb b/lib/api/validations/types/workhorse_file.rb index e65e94fc8db..23f402596d5 100644 --- a/lib/api/validations/types/workhorse_file.rb +++ b/lib/api/validations/types/workhorse_file.rb @@ -5,6 +5,7 @@ module API module Types class WorkhorseFile def self.parse(value) + return if value.blank? raise "#{value.class} is not an UploadedFile type" unless parsed?(value) value |