diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-18 16:16:36 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-18 16:16:36 +0300 |
commit | 311b0269b4eb9839fa63f80c8d7a58f32b8138a0 (patch) | |
tree | 07e7870bca8aed6d61fdcc810731c50d2c40af47 /lib/api | |
parent | 27909cef6c4170ed9205afa7426b8d3de47cbb0c (diff) |
Add latest changes from gitlab-org/gitlab@14-5-stable-eev14.5.0-rc42
Diffstat (limited to 'lib/api')
69 files changed, 463 insertions, 133 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index a4d42c735cb..dcecaeae558 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -27,7 +27,8 @@ module API Gitlab::GrapeLogging::Loggers::PerfLogger.new, Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new, Gitlab::GrapeLogging::Loggers::ContextLogger.new, - Gitlab::GrapeLogging::Loggers::ContentLogger.new + Gitlab::GrapeLogging::Loggers::ContentLogger.new, + Gitlab::GrapeLogging::Loggers::UrgencyLogger.new ] allow_access_with_scope :api @@ -283,6 +284,7 @@ module API mount ::API::Tags mount ::API::Templates mount ::API::Todos + mount ::API::Topics mount ::API::Unleash mount ::API::UsageData mount ::API::UsageDataQueries diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 9e829dd5e05..56633c07774 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -7,7 +7,7 @@ module API prepend_mod_with('API::BoardsResponses') # rubocop: disable Cop/InjectEnterpriseEditionModule - feature_category :boards + feature_category :team_planning before { authenticate! } diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 0db5bb82296..462c4a3de4c 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -41,7 +41,7 @@ module API optional :page_token, type: String, desc: 'Name of branch to start the paginaition from' end - get ':id/repository/branches' do + get ':id/repository/branches', urgency: :low do ff_enabled = Feature.enabled?(:api_caching_rate_limit_branches, user_project, default_enabled: :yaml) cache_action_if(ff_enabled, [user_project, :branches, current_user, declared_params], expires_in: 30.seconds) do @@ -86,7 +86,7 @@ module API head do user_project.repository.branch_exists?(params[:branch]) ? no_content! : not_found! end - get do + get '/', urgency: :low do branch = find_branch!(params[:branch]) present branch, with: Entities::Branch, current_user: current_user, project: user_project diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index eea1637c32a..30ce1454419 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -177,6 +177,39 @@ module API present current_authenticated_job, with: Entities::Ci::Job end + + desc 'Get current agents' do + detail 'Retrieves a list of agents for the given job token' + end + route_setting :authentication, job_token_allowed: true + get '/allowed_agents', feature_category: :kubernetes_management do + validate_current_authenticated_job + + status 200 + + pipeline = current_authenticated_job.pipeline + project = current_authenticated_job.project + agent_authorizations = Clusters::AgentAuthorizationsFinder.new(project).execute + project_groups = project.group&.self_and_ancestor_ids&.map { |id| { id: id } } || [] + user_access_level = project.team.max_member_access(current_user.id) + roles_in_project = Gitlab::Access.sym_options_with_owner + .select { |_role, role_access_level| role_access_level <= user_access_level } + .map(&:first) + + environment = if environment_slug = current_authenticated_job.deployment&.environment&.slug + { slug: environment_slug } + end + + # See https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/kubernetes_ci_access.md#apiv4joballowed_agents-api + { + allowed_agents: Entities::Clusters::AgentAuthorization.represent(agent_authorizations), + job: { id: current_authenticated_job.id }, + pipeline: { id: pipeline.id }, + project: { id: project.id, groups: project_groups }, + user: { id: current_user.id, username: current_user.username, roles_in_project: roles_in_project }, + environment: environment + }.compact + end end helpers do @@ -202,5 +235,3 @@ module API end end end - -API::Ci::Jobs.prepend_mod_with('API::Ci::Jobs') diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 10dc51556b9..8b8d8192524 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -27,7 +27,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, urgency: :low do desc 'Get a project repository commits' do success Entities::Commit end @@ -43,7 +43,7 @@ module API optional :trailers, type: Boolean, desc: 'Parse and include Git trailers for every commit', default: false use :pagination end - get ':id/repository/commits' do + get ':id/repository/commits', urgency: :low do path = params[:path] before = params[:until] after = params[:since] @@ -169,7 +169,7 @@ module API requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' use :pagination end - get ':id/repository/commits/:sha/diff', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do + get ':id/repository/commits/:sha/diff', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS, urgency: :low do commit = user_project.commit(params[:sha]) not_found! 'Commit' unless commit @@ -295,7 +295,7 @@ module API optional :type, type: String, values: %w[branch tag all], default: 'all', desc: 'Scope' use :pagination end - get ':id/repository/commits/:sha/refs', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do + get ':id/repository/commits/:sha/refs', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS, urgency: :low do commit = user_project.commit(params[:sha]) not_found!('Commit') unless commit @@ -363,7 +363,7 @@ module API requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to find Merge Requests' use :pagination end - get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do + get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS, urgency: :low do authorize! :read_merge_request, user_project commit = user_project.commit(params[:sha]) diff --git a/lib/api/concerns/packages/debian_distribution_endpoints.rb b/lib/api/concerns/packages/debian_distribution_endpoints.rb index 798e583b87a..ddc83d0f747 100644 --- a/lib/api/concerns/packages/debian_distribution_endpoints.rb +++ b/lib/api/concerns/packages/debian_distribution_endpoints.rb @@ -15,6 +15,12 @@ module API helpers ::API::Helpers::Packages::BasicAuthHelpers include ::API::Helpers::Authentication + helpers do + def distribution + ::Packages::Debian::DistributionsFinder.new(project_or_group, codename: params[:codename]).execute.last || not_found!('Distribution') + end + end + namespace 'debian_distributions' do helpers do params :optional_distribution_params do @@ -36,9 +42,18 @@ module API end end + rescue_from ArgumentError do |e| + render_api_error!(e.message, 400) + end + + rescue_from ActiveRecord::RecordInvalid do |e| + render_api_error!(e.message, 400) + end + authenticate_with do |accept| - accept.token_types(:personal_access_token, :deploy_token, :job_token) - .sent_through(:http_basic_auth) + accept.token_types(:personal_access_token).sent_through(:http_private_token_header) + accept.token_types(:deploy_token).sent_through(:http_deploy_token_header) + accept.token_types(:job_token).sent_through(:http_job_token_header) end content_type :json, 'application/json' @@ -59,12 +74,12 @@ module API distribution_params = declared_params(include_missing: false) result = ::Packages::Debian::CreateDistributionService.new(project_or_group, current_user, distribution_params).execute - distribution = result.payload[:distribution] + created_distribution = result.payload[:distribution] if result.success? - present distribution, with: ::API::Entities::Packages::Debian::Distribution + present created_distribution, with: ::API::Entities::Packages::Debian::Distribution else - render_validation_error!(distribution) + render_validation_error!(created_distribution) end end @@ -100,11 +115,28 @@ module API get '/:codename' do authorize_read_package!(project_or_group) - distribution = ::Packages::Debian::DistributionsFinder.new(project_or_group, codename: params[:codename]).execute.last! - present distribution, with: ::API::Entities::Packages::Debian::Distribution end + # GET {projects|groups}/:id/debian_distributions/:codename/key + desc 'Get a Debian Distribution Key' do + detail 'This feature was introduced in 14.4' + success ::API::Entities::Packages::Debian::Distribution + end + + params do + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename' + end + get '/:codename/key.asc' do + authorize_read_package!(project_or_group) + + content_type 'text/plain' + env['api.format'] = :binary + header 'Content-Disposition', "attachment; filename*=UTF-8''#{CGI.escape(params[:codename])}.asc" + + distribution.key&.public_key || not_found!('Distribution key') + end + # PUT {projects|groups}/:id/debian_distributions/:codename desc 'Update a Debian Distribution' do detail 'This feature was introduced in 14.0' @@ -118,15 +150,14 @@ module API put '/:codename' do authorize_create_package!(project_or_group) - distribution = ::Packages::Debian::DistributionsFinder.new(project_or_group, codename: params[:codename]).execute.last! distribution_params = declared_params(include_missing: false).except(:codename) result = ::Packages::Debian::UpdateDistributionService.new(distribution, distribution_params).execute - distribution = result.payload[:distribution] + updated_distribution = result.payload[:distribution] if result.success? - present distribution, with: ::API::Entities::Packages::Debian::Distribution + present updated_distribution, with: ::API::Entities::Packages::Debian::Distribution else - render_validation_error!(distribution) + render_validation_error!(updated_distribution) end end @@ -142,8 +173,6 @@ module API delete '/:codename' do authorize_destroy_package!(project_or_group) - distribution = ::Packages::Debian::DistributionsFinder.new(project_or_group, codename: params[:codename]).execute.last! - accepted! if distribution.destroy render_api_error!('Failed to delete distribution', 400) diff --git a/lib/api/concerns/packages/debian_package_endpoints.rb b/lib/api/concerns/packages/debian_package_endpoints.rb index 0acc015f366..d083643f3d0 100644 --- a/lib/api/concerns/packages/debian_package_endpoints.rb +++ b/lib/api/concerns/packages/debian_package_endpoints.rb @@ -43,11 +43,6 @@ module API end end - authenticate_with do |accept| - accept.token_types(:personal_access_token, :deploy_token, :job_token) - .sent_through(:http_basic_auth) - end - rescue_from ArgumentError do |e| render_api_error!(e.message, 400) end @@ -56,6 +51,11 @@ module API render_api_error!(e.message, 400) end + authenticate_with do |accept| + accept.token_types(:personal_access_token, :deploy_token, :job_token) + .sent_through(:http_basic_auth) + end + format :txt content_type :txt, 'text/plain' diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb index d6e006df976..7a657be5bf3 100644 --- a/lib/api/concerns/packages/npm_endpoints.rb +++ b/lib/api/concerns/packages/npm_endpoints.rb @@ -121,7 +121,9 @@ module API not_found!('Packages') if packages.empty? - present ::Packages::Npm::PackagePresenter.new(package_name, packages), + include_metadata = Feature.enabled?(:packages_npm_abbreviated_metadata, project, default_enabled: :yaml) + + present ::Packages::Npm::PackagePresenter.new(package_name, packages, include_metadata: include_metadata), with: ::API::Entities::NpmPackage end end diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb index 29f5047230a..1f640cc17d0 100644 --- a/lib/api/debian_group_packages.rb +++ b/lib/api/debian_group_packages.rb @@ -32,7 +32,7 @@ module API namespace ':id/-/packages/debian' do include ::API::Concerns::Packages::DebianPackageEndpoints - # GET groups/:id/packages/debian/pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name + # GET groups/:id/-/packages/debian/pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name params do requires :project_id, type: Integer, desc: 'The Project Id' use :shared_package_file_params diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 9f0f569b711..0ab9fe6644c 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -23,11 +23,14 @@ module API desc 'Return all deploy keys' params do use :pagination + optional :public, type: Boolean, default: false, desc: "Only return deploy keys that are public" end get "deploy_keys" do authenticated_as_admin! - present paginate(DeployKey.all), with: Entities::DeployKey + deploy_keys = params[:public] ? DeployKey.are_public : DeployKey.all + + present paginate(deploy_keys.including_projects_with_write_access), with: Entities::DeployKey, include_projects_with_write_access: true end params do diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb index 580d546b360..cf4b2348458 100644 --- a/lib/api/discussions.rb +++ b/lib/api/discussions.rb @@ -239,7 +239,7 @@ module API # rubocop: disable CodeReuse/ActiveRecord def readable_discussion_notes(noteable, discussion_ids) notes = noteable.notes - .where(discussion_id: discussion_ids) + .with_discussion_ids(discussion_ids) .inc_relations_for_view .includes(:noteable) .fresh diff --git a/lib/api/entities/alert_management/alert.rb b/lib/api/entities/alert_management/alert.rb new file mode 100644 index 00000000000..664cd53293e --- /dev/null +++ b/lib/api/entities/alert_management/alert.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + module AlertManagement + class Alert < Grape::Entity + expose :iid + expose :title + end + end + end +end diff --git a/lib/api/entities/ci/job_request/service.rb b/lib/api/entities/ci/job_request/service.rb index f89b95c1d5c..0dae5d5a933 100644 --- a/lib/api/entities/ci/job_request/service.rb +++ b/lib/api/entities/ci/job_request/service.rb @@ -6,6 +6,7 @@ module API module JobRequest class Service < Entities::Ci::JobRequest::Image expose :alias, :command + expose :variables end end end diff --git a/lib/api/entities/ci/lint/result.rb b/lib/api/entities/ci/lint/result.rb index 0e4aa238ba2..39039868bba 100644 --- a/lib/api/entities/ci/lint/result.rb +++ b/lib/api/entities/ci/lint/result.rb @@ -9,6 +9,7 @@ module API expose :errors expose :warnings expose :merged_yaml + expose :jobs, if: -> (result, options) { options[:include_jobs] } end end end diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb index ede698696de..60193fe1df4 100644 --- a/lib/api/entities/ci/runner.rb +++ b/lib/api/entities/ci/runner.rb @@ -12,7 +12,9 @@ module API expose :runner_type expose :name expose :online?, as: :online - expose :status + # 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 end end end diff --git a/lib/api/entities/ci/runner_details.rb b/lib/api/entities/ci/runner_details.rb index 9d44da7e5b3..6ded1296f2a 100644 --- a/lib/api/entities/ci/runner_details.rb +++ b/lib/api/entities/ci/runner_details.rb @@ -15,18 +15,18 @@ module API # rubocop: disable CodeReuse/ActiveRecord expose :projects, with: Entities::BasicProjectDetails do |runner, options| if options[:current_user].admin? # rubocop: disable Cop/UserAdmin - runner.projects + runner.projects.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') else - options[:current_user].authorized_projects.where(id: runner.projects) + options[:current_user].authorized_projects.where(id: runner.projects).allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') end end # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord expose :groups, with: Entities::BasicGroupDetails do |runner, options| if options[:current_user].admin? # rubocop: disable Cop/UserAdmin - runner.groups + runner.groups.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') else - options[:current_user].authorized_groups.where(id: runner.groups) + options[:current_user].authorized_groups.where(id: runner.groups).allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/api/entities/deploy_key.rb b/lib/api/entities/deploy_key.rb index ed922c24eda..e8537c4c677 100644 --- a/lib/api/entities/deploy_key.rb +++ b/lib/api/entities/deploy_key.rb @@ -4,6 +4,9 @@ module API module Entities class DeployKey < Entities::SSHKey expose :key + expose :fingerprint + + expose :projects_with_write_access, using: Entities::ProjectIdentity, if: -> (_, options) { options[:include_projects_with_write_access] } end end end diff --git a/lib/api/entities/group.rb b/lib/api/entities/group.rb index 048b7a3c15a..246fb819890 100644 --- a/lib/api/entities/group.rb +++ b/lib/api/entities/group.rb @@ -31,7 +31,10 @@ module API expose :wiki_size expose :lfs_objects_size expose :build_artifacts_size, as: :job_artifacts_size + expose :pipeline_artifacts_size + expose :packages_size expose :snippets_size + expose :uploads_size end end end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 41320d184f9..e3f1e90b80f 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -114,6 +114,7 @@ module API expose :merge_method expose :squash_option expose :suggestion_commit_message + expose :merge_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_statistics.rb b/lib/api/entities/project_statistics.rb index 70980e670b0..6544e8bc8ff 100644 --- a/lib/api/entities/project_statistics.rb +++ b/lib/api/entities/project_statistics.rb @@ -9,8 +9,10 @@ module API expose :wiki_size expose :lfs_objects_size expose :build_artifacts_size, as: :job_artifacts_size - expose :snippets_size + expose :pipeline_artifacts_size expose :packages_size + expose :snippets_size + expose :uploads_size end end end diff --git a/lib/api/entities/projects/topic.rb b/lib/api/entities/projects/topic.rb new file mode 100644 index 00000000000..d3d1cbec81c --- /dev/null +++ b/lib/api/entities/projects/topic.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module API + module Entities + module Projects + class Topic < Grape::Entity + expose :id + expose :name + expose :description + expose :total_projects_count + expose :avatar_url do |topic, options| + topic.avatar_url(only_path: false) + end + end + end + end +end diff --git a/lib/api/entities/todo.rb b/lib/api/entities/todo.rb index 8d222db488a..5bbbb59f565 100644 --- a/lib/api/entities/todo.rb +++ b/lib/api/entities/todo.rb @@ -33,7 +33,7 @@ module API def todo_target_url(todo) return design_todo_target_url(todo) if todo.for_design? - target_type = todo.target_type.underscore + target_type = todo.target_type.gsub('::', '_').underscore target_url = "#{todo.resource_parent.class.to_s.underscore}_#{target_type}_url" Gitlab::Routing diff --git a/lib/api/error_tracking/collector.rb b/lib/api/error_tracking/collector.rb index 22fbd3a1118..13fda356257 100644 --- a/lib/api/error_tracking/collector.rb +++ b/lib/api/error_tracking/collector.rb @@ -12,6 +12,10 @@ module API content_type :txt, 'text/plain' default_format :envelope + rescue_from ActiveRecord::RecordInvalid do |e| + render_api_error!(e.message, 400) + end + before do not_found!('Project') unless project not_found! unless feature_enabled? @@ -50,6 +54,12 @@ module API bad_request!('Failed to parse sentry request') end end + + def validate_payload(payload) + unless ::ErrorTracking::Collector::PayloadValidator.new.valid?(payload) + bad_request!('Unsupported sentry payload') + end + end end desc 'Submit error tracking event to the project as envelope' do @@ -88,6 +98,8 @@ module API # We don't have use for transaction request yet, # so we record only event one. if type == 'event' + validate_payload(parsed_request[:event]) + ::ErrorTracking::CollectErrorService .new(project, nil, event: parsed_request[:event]) .execute @@ -96,7 +108,10 @@ module API # Collector should never return any information back. # Because DSN and public key are designed for public use, # it is safe only for submission of new events. - no_content! + # + # Some clients sdk require status 200 OK to work correctly. + # See https://gitlab.com/gitlab-org/gitlab/-/issues/343531. + status 200 end desc 'Submit error tracking event to the project' do @@ -122,6 +137,8 @@ module API bad_request!('Failed to parse sentry request') end + validate_payload(parsed_body) + ::ErrorTracking::CollectErrorService .new(project, nil, event: parsed_body) .execute @@ -129,7 +146,10 @@ module API # Collector should never return any information back. # Because DSN and public key are designed for public use, # it is safe only for submission of new events. - no_content! + # + # Some clients sdk require status 200 OK to work correctly. + # See https://gitlab.com/gitlab-org/gitlab/-/issues/343531. + status 200 end end end diff --git a/lib/api/features.rb b/lib/api/features.rb index 2ce2f7c518f..398e57794c8 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -14,7 +14,12 @@ module API when '0', 'false' false else - params[:value].to_i + # https://github.com/jnunemaker/flipper/blob/master/lib/flipper/typecast.rb#L47 + if params[:value].to_s.include?('.') + params[:value].to_f + else + params[:value].to_i + end end end @@ -59,7 +64,7 @@ module API success Entities::Feature end params do - requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time' + requires :value, type: String, desc: '`true` or `false` to enable/disable, a float for percentage of time' optional :key, type: String, desc: '`percentage_of_actors` or the default `percentage_of_time`' optional :feature_group, type: String, desc: 'A Feature group name' optional :user, type: String, desc: 'A GitLab username' diff --git a/lib/api/files.rb b/lib/api/files.rb index 9d2b7cce837..39b3904ec90 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -122,7 +122,7 @@ module API requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' optional :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false end - head ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do + head ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do assign_file_vars! set_http_headers(blob_data) @@ -133,7 +133,7 @@ module API requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' optional :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false end - get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do + get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do assign_file_vars! no_cache_headers @@ -147,7 +147,7 @@ module API requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false end - head ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do + head ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do assign_file_vars! set_http_headers(blob_data) @@ -174,7 +174,7 @@ module API params do use :extended_file_params end - post ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do + post ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do authorize! :push_code, user_project file_params = declared_params(include_missing: false) @@ -192,7 +192,7 @@ module API params do use :extended_file_params end - put ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do + put ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do authorize! :push_code, user_project file_params = declared_params(include_missing: false) diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 5e184d35255..8cca3378eec 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -54,6 +54,7 @@ module API requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true optional :status, type: String, values: ALLOWED_STATUSES, desc: 'Package status' requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + optional :select, type: String, values: %w[package_file] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true @@ -65,11 +66,15 @@ module API track_package_event('push_package', :generic, project: project, user: current_user, namespace: project.namespace) create_package_file_params = declared_params.merge(build: current_authenticated_job) - ::Packages::Generic::CreatePackageFileService + package_file = ::Packages::Generic::CreatePackageFileService .new(project, current_user, create_package_file_params) .execute - created! + if params[:select] == 'package_file' + present package_file + else + created! + end rescue ObjectStorage::RemoteStoreError => e Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project.id }) diff --git a/lib/api/github/entities.rb b/lib/api/github/entities.rb index fe228c9a2d2..125985f0e23 100644 --- a/lib/api/github/entities.rb +++ b/lib/api/github/entities.rb @@ -59,8 +59,8 @@ module API expose :parents do |commit| commit.parent_ids.map { |id| { sha: id } } end - expose :files do |commit| - commit.diffs.diff_files.flat_map do |diff| + expose :files do |_commit, options| + options[:diff_files].flat_map do |diff| additions = diff.added_lines deletions = diff.removed_lines diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb index 92869f8fbba..e9350da555c 100644 --- a/lib/api/group_boards.rb +++ b/lib/api/group_boards.rb @@ -7,7 +7,7 @@ module API prepend_mod_with('API::BoardsResponses') # rubocop: disable Cop/InjectEnterpriseEditionModule - feature_category :boards + feature_category :team_planning before { authenticate! } diff --git a/lib/api/group_debian_distributions.rb b/lib/api/group_debian_distributions.rb index 01a8774bd97..f0376fe2c9c 100644 --- a/lib/api/group_debian_distributions.rb +++ b/lib/api/group_debian_distributions.rb @@ -7,14 +7,6 @@ module API end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - rescue_from ArgumentError do |e| - render_api_error!(e.message, 400) - end - - rescue_from ActiveRecord::RecordInvalid do |e| - render_api_error!(e.message, 400) - end - after_validation do require_packages_enabled! diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb index bea538441ee..7c1f23be828 100644 --- a/lib/api/group_labels.rb +++ b/lib/api/group_labels.rb @@ -7,7 +7,7 @@ module API before { authenticate! } - feature_category :issue_tracking + feature_category :team_planning params do requires :id, type: String, desc: 'The ID of a group' diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb index 061d0410a9c..b097022e9c1 100644 --- a/lib/api/group_milestones.rb +++ b/lib/api/group_milestones.rb @@ -7,7 +7,7 @@ module API before { authenticate! } - feature_category :issue_tracking + feature_category :team_planning params do requires :id, type: String, desc: 'The ID of a group' diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index f9ba5ba8186..76840091112 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -174,9 +174,9 @@ module API # rubocop: disable CodeReuse/ActiveRecord def find_namespace(id) if id.to_s =~ /^\d+$/ - Namespace.find_by(id: id) + Namespace.without_project_namespaces.find_by(id: id) else - Namespace.find_by_full_path(id) + find_namespace_by_path(id) end end # rubocop: enable CodeReuse/ActiveRecord @@ -186,7 +186,7 @@ module API end def find_namespace_by_path(path) - Namespace.find_by_full_path(path) + Namespace.without_project_namespaces.find_by_full_path(path) end def find_namespace_by_path!(path) @@ -488,7 +488,7 @@ module API def handle_api_exception(exception) if report_exception?(exception) define_params_for_grape_middleware - Gitlab::ApplicationContext.push(user: current_user) + Gitlab::ApplicationContext.push(user: current_user, remote_ip: request.ip) Gitlab::ErrorTracking.track_exception(exception) end @@ -681,20 +681,27 @@ module API def send_git_blob(repository, blob) env['api.format'] = :txt content_type 'text/plain' + header['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'inline', filename: blob.name) # Let Workhorse examine the content and determine the better content disposition header[Gitlab::Workhorse::DETECT_HEADER] = "true" header(*Gitlab::Workhorse.send_git_blob(repository, blob)) + + body '' end def send_git_archive(repository, **kwargs) header(*Gitlab::Workhorse.send_git_archive(repository, **kwargs)) + + body '' end def send_artifacts_entry(file, entry) header(*Gitlab::Workhorse.send_artifacts_entry(file, entry)) + + body '' end # The Grape Error Middleware only has access to `env` but not `params` nor diff --git a/lib/api/helpers/award_emoji.rb b/lib/api/helpers/award_emoji.rb index 5b659c4dde7..3ea35381c97 100644 --- a/lib/api/helpers/award_emoji.rb +++ b/lib/api/helpers/award_emoji.rb @@ -5,7 +5,7 @@ module API module AwardEmoji def self.awardables [ - { type: 'issue', resource: :projects, find_by: :iid, feature_category: :issue_tracking }, + { type: 'issue', resource: :projects, find_by: :iid, feature_category: :team_planning }, { type: 'merge_request', resource: :projects, find_by: :iid, feature_category: :code_review }, { type: 'snippet', resource: :projects, find_by: :id, feature_category: :snippets } ] diff --git a/lib/api/helpers/discussions_helpers.rb b/lib/api/helpers/discussions_helpers.rb index cb2feeda1e1..c94199b17bc 100644 --- a/lib/api/helpers/discussions_helpers.rb +++ b/lib/api/helpers/discussions_helpers.rb @@ -7,7 +7,7 @@ module API # This is a method instead of a constant, allowing EE to more easily # extend it. { - Issue => :issue_tracking, + Issue => :team_planning, Snippet => :snippets, MergeRequest => :code_review, Commit => :code_review diff --git a/lib/api/helpers/file_upload_helpers.rb b/lib/api/helpers/file_upload_helpers.rb index dd551ec2976..751972b44f0 100644 --- a/lib/api/helpers/file_upload_helpers.rb +++ b/lib/api/helpers/file_upload_helpers.rb @@ -5,7 +5,7 @@ module API module FileUploadHelpers def file_is_valid? filename = params[:file]&.original_filename - filename && ImportExportUploader::EXTENSION_WHITELIST.include?(File.extname(filename).delete('.')) + filename && ImportExportUploader::EXTENSION_ALLOWLIST.include?(File.extname(filename).delete('.')) end def validate_file! diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index e0ef9099104..e7fdb6645a5 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -254,7 +254,7 @@ module API type: Boolean, desc: 'DEPRECATED: This parameter has no effect since SSL verification will always be enabled' } - ], + ], 'campfire' => [ { required: true, @@ -530,6 +530,14 @@ module API desc: 'The Mattermost token' } ], + 'shimo' => [ + { + required: true, + name: :external_wiki_url, + type: String, + desc: 'Shimo workspace URL' + } + ], 'slack-slash-commands' => [ { required: true, @@ -768,7 +776,33 @@ module API desc: 'The Webex Teams webhook. For example, https://api.ciscospark.com/v1/webhooks/incoming/...' }, chat_notification_events - ].flatten + ].flatten, + 'zentao' => [ + { + required: true, + name: :url, + type: String, + desc: 'The base URL to the ZenTao instance web interface which is being linked to this GitLab project. For example, https://www.zentao.net' + }, + { + required: false, + name: :api_url, + type: String, + desc: 'The base URL to the ZenTao instance API. Web URL value will be used if not set. For example, https://www.zentao.net' + }, + { + required: true, + name: :api_token, + type: String, + desc: 'The API token created from ZenTao dashboard' + }, + { + required: true, + name: :zentao_product_xid, + type: String, + desc: 'The product ID of ZenTao project' + } + ] } end @@ -805,7 +839,8 @@ module API ::Integrations::Slack, ::Integrations::SlackSlashCommands, ::Integrations::Teamcity, - ::Integrations::Youtrack + ::Integrations::Youtrack, + ::Integrations::Zentao ] end diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index 356e4a98c97..45671b09be9 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -7,7 +7,7 @@ module API def self.feature_category_per_noteable_type { - Issue => :issue_tracking, + Issue => :team_planning, MergeRequest => :code_review, Snippet => :snippets } diff --git a/lib/api/helpers/project_snapshots_helpers.rb b/lib/api/helpers/project_snapshots_helpers.rb index 0b10641571a..4b48661eeca 100644 --- a/lib/api/helpers/project_snapshots_helpers.rb +++ b/lib/api/helpers/project_snapshots_helpers.rb @@ -11,6 +11,8 @@ module API def send_git_snapshot(repository) header(*Gitlab::Workhorse.send_git_snapshot(repository)) + + body '' end def snapshot_project diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 30edbe91125..42d1c40dd11 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -61,6 +61,7 @@ module API optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line' 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 :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' @@ -160,6 +161,7 @@ module API :wiki_access_level, :avatar, :suggestion_commit_message, + :merge_commit_template, :repository_storage, :compliance_framework_setting, :packages_enabled, @@ -178,6 +180,17 @@ module API def filter_attributes_using_license!(attrs) end + + def validate_git_import_url!(import_url, import_enabled: true) + return if import_url.blank? + return unless import_enabled + + result = Import::ValidateRemoteGitEndpointService.new(url: import_url).execute # network call + + if result.error? + render_api_error!(result.message, 422) + end + end end end end diff --git a/lib/api/helpers/resource_label_events_helpers.rb b/lib/api/helpers/resource_label_events_helpers.rb index 7e641130062..eeb68362c1d 100644 --- a/lib/api/helpers/resource_label_events_helpers.rb +++ b/lib/api/helpers/resource_label_events_helpers.rb @@ -7,7 +7,7 @@ module API # This is a method instead of a constant, allowing EE to more easily # extend it. { - Issue => :issue_tracking, + Issue => :team_planning, MergeRequest => :code_review } end diff --git a/lib/api/integrations.rb b/lib/api/integrations.rb index 926cde340a0..bab8e556a73 100644 --- a/lib/api/integrations.rb +++ b/lib/api/integrations.rb @@ -153,7 +153,7 @@ module API requires setting[:name], type: setting[:type], desc: setting[:desc] end end - post "#{path}/#{integration_slug.underscore}/trigger" do + post "#{path}/#{integration_slug.underscore}/trigger", urgency: :low do project = find_project(params[:id]) # This is not accurate, but done to prevent leakage of the project names diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index dc9257ebd62..d8e39d089e4 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -145,7 +145,7 @@ module API check_allowed(params) end - post "/lfs_authenticate", feature_category: :source_code_management do + post "/lfs_authenticate", feature_category: :source_code_management, urgency: :high do not_found! unless container&.lfs_enabled? status 200 diff --git a/lib/api/internal/lfs.rb b/lib/api/internal/lfs.rb index 66baa4f1034..e94da8d34e0 100644 --- a/lib/api/internal/lfs.rb +++ b/lib/api/internal/lfs.rb @@ -24,7 +24,7 @@ module API requires :oid, type: String, desc: 'The object ID to query' requires :gl_repository, type: String, desc: "Project identifier (e.g. project-1)" end - get "/" do + get "/", urgency: :high do lfs_object = find_lfs_object(params[:oid]) not_found! unless lfs_object diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index 5cade301d81..f7f5af07378 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -25,6 +25,8 @@ module API 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 post ":id/invitations" do params[:source] = find_source(source_type, params[:id]) diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb index 0b4f4e06d0b..98451afb12d 100644 --- a/lib/api/issue_links.rb +++ b/lib/api/issue_links.rb @@ -6,7 +6,7 @@ module API before { authenticate! } - feature_category :issue_tracking + feature_category :team_planning params do requires :id, type: String, desc: 'The ID of a project' diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 43e83bd58fe..9958526fa7f 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -7,7 +7,7 @@ module API before { authenticate_non_get! } - feature_category :issue_tracking + feature_category :team_planning helpers do params :negatable_issue_filter_params do diff --git a/lib/api/labels.rb b/lib/api/labels.rb index aa3746dae42..e3253d15c15 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -7,7 +7,7 @@ module API before { authenticate! } - feature_category :issue_tracking + feature_category :team_planning LABEL_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( name: API::NO_SLASH_URL_PART_REGEX, diff --git a/lib/api/lint.rb b/lib/api/lint.rb index fa871b4bc57..f1e19e9c3c5 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -9,6 +9,7 @@ module API params do requires :content, type: String, desc: 'Content of .gitlab-ci.yml' optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response' + optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response' end post '/lint' do unauthorized! if (Gitlab::CurrentSettings.signup_disabled? || Gitlab::CurrentSettings.signup_limited?) && current_user.nil? @@ -17,7 +18,7 @@ module API .validate(params[:content], dry_run: false) status 200 - Entities::Ci::Lint::Result.represent(result, current_user: current_user).serializable_hash.tap do |presented_result| + Entities::Ci::Lint::Result.represent(result, current_user: current_user, include_jobs: params[:include_jobs]).serializable_hash.tap do |presented_result| presented_result[:status] = presented_result[:valid] ? 'valid' : 'invalid' presented_result.delete(:merged_yaml) unless params[:include_merged_yaml] end @@ -30,6 +31,7 @@ module API end params do 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 authorize! :download_code, user_project @@ -39,7 +41,7 @@ module API .new(project: user_project, current_user: current_user) .validate(content, dry_run: params[:dry_run]) - present result, with: Entities::Ci::Lint::Result, current_user: current_user + present result, with: Entities::Ci::Lint::Result, current_user: current_user, include_jobs: params[:include_jobs] end end @@ -50,6 +52,7 @@ module API params do requires :content, type: String, desc: 'Content of .gitlab-ci.yml' 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 authorize! :create_pipeline, user_project @@ -59,7 +62,7 @@ module API .validate(params[:content], dry_run: params[:dry_run]) status 200 - present result, with: Entities::Ci::Lint::Result, current_user: current_user + present result, with: Entities::Ci::Lint::Result, current_user: current_user, include_jobs: params[:include_jobs] end end end diff --git a/lib/api/members.rb b/lib/api/members.rb index 332520ccd26..f488c8c26fc 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -95,6 +95,8 @@ module API 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 post ":id/members" do diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb index 83150bb51ca..dd49624c74f 100644 --- a/lib/api/merge_request_approvals.rb +++ b/lib/api/merge_request_approvals.rb @@ -25,7 +25,7 @@ module API # Examples: # GET /projects/:id/merge_requests/:merge_request_iid/approvals desc 'List approvals for merge request' - get 'approvals' do + get 'approvals', urgency: :low do not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project) merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -47,7 +47,7 @@ module API use :ee_approval_params end - post 'approve' do + post 'approve', urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request) check_sha_param!(params, merge_request) @@ -63,7 +63,7 @@ module API end desc 'Remove an approval from a merge request' - post 'unapprove' do + post 'unapprove', urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request) success = ::MergeRequests::RemoveApprovalService diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index c2d839571a6..d2468fb1c2e 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -37,7 +37,7 @@ module API namespaces = current_user.admin ? Namespace.all : current_user.namespaces(owned_only: owned_only) - namespaces = namespaces.include_route + namespaces = namespaces.without_project_namespaces.include_route namespaces = namespaces.include_gitlab_subscription_with_hosted_plan if Gitlab.ee? @@ -70,7 +70,7 @@ module API get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace_path = params[:namespace] - exists = Namespace.by_parent(params[:parent_id]).filter_by_path(namespace_path).exists? + exists = Namespace.without_project_namespaces.by_parent(params[:parent_id]).filter_by_path(namespace_path).exists? suggestions = exists ? [Namespace.clean_path(namespace_path)] : [] present :exists, exists diff --git a/lib/api/package_files.rb b/lib/api/package_files.rb index 6d0c1f44a36..79ebf18ff27 100644 --- a/lib/api/package_files.rb +++ b/lib/api/package_files.rb @@ -28,7 +28,10 @@ module API package = ::Packages::PackageFinder .new(user_project, params[:package_id]).execute - present paginate(package.package_files), with: ::API::Entities::PackageFile + files = package.package_files + .preload_pipelines + + present paginate(files), with: ::API::Entities::PackageFile end desc 'Remove a package file' do diff --git a/lib/api/project_debian_distributions.rb b/lib/api/project_debian_distributions.rb index f057251fb6b..2ba1ff85adb 100644 --- a/lib/api/project_debian_distributions.rb +++ b/lib/api/project_debian_distributions.rb @@ -7,14 +7,6 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - rescue_from ArgumentError do |e| - render_api_error!(e.message, 400) - end - - rescue_from ActiveRecord::RecordInvalid do |e| - render_api_error!(e.message, 400) - end - after_validation do require_packages_enabled! diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb index 107311ea446..435e4bed776 100644 --- a/lib/api/project_milestones.rb +++ b/lib/api/project_milestones.rb @@ -7,7 +7,7 @@ module API before { authenticate! } - feature_category :issue_tracking + feature_category :team_planning params do requires :id, type: String, desc: 'The ID of a project' diff --git a/lib/api/projects.rb b/lib/api/projects.rb index bb74849a98a..9f0077d23d8 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -91,7 +91,7 @@ module API end def check_import_by_url_is_enabled - forbidden! unless Gitlab::CurrentSettings.import_sources&.include?('git') + Gitlab::CurrentSettings.import_sources&.include?('git') || forbidden! end end @@ -269,7 +269,9 @@ module API attrs = declared_params(include_missing: false) attrs = translate_params_for_compatibility(attrs) filter_attributes_using_license!(attrs) - check_import_by_url_is_enabled if params[:import_url].present? + + validate_git_import_url!(params[:import_url], import_enabled: check_import_by_url_is_enabled) + project = ::Projects::CreateService.new(current_user, attrs).execute if project.saved? @@ -307,6 +309,8 @@ module API attrs = declared_params(include_missing: false) attrs = translate_params_for_compatibility(attrs) filter_attributes_using_license!(attrs) + validate_git_import_url!(params[:import_url]) + project = ::Projects::CreateService.new(user, attrs).execute if project.saved? @@ -400,7 +404,7 @@ module API use :collection_params use :with_custom_attributes end - get ':id/forks', feature_category: :source_code_management do + get ':id/forks', feature_category: :source_code_management, urgency: :low do forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute present_projects forks, request_scope: user_project @@ -510,7 +514,7 @@ module API end desc 'Get languages in project repository' - get ':id/languages', feature_category: :source_code_management do + get ':id/languages', feature_category: :source_code_management, urgency: :medium do ::Projects::RepositoryLanguagesService .new(user_project, current_user) .execute.to_h { |lang| [lang.name, lang.share] } diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb index 3cebc308f51..a4f5dfefae6 100644 --- a/lib/api/protected_branches.rb +++ b/lib/api/protected_branches.rb @@ -91,7 +91,7 @@ module API requires :name, type: String, desc: 'The name of the protected branch' end # rubocop: disable CodeReuse/ActiveRecord - delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do + delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS, urgency: :low do protected_branch = user_project.protected_branches.find_by!(name: params[:name]) destroy_conditionally!(protected_branch) do diff --git a/lib/api/releases.rb b/lib/api/releases.rb index 3b7e2b4bd27..7b89a177fd9 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -32,6 +32,7 @@ module API optional :include_html_description, type: Boolean, desc: 'If `true`, a response includes HTML rendered markdown of the release description.' end + route_setting :authentication, job_token_allowed: true get ':id/releases' do releases = ::ReleasesFinder.new(user_project, current_user, declared_params.slice(:order_by, :sort)).execute @@ -59,6 +60,7 @@ module API optional :include_html_description, type: Boolean, desc: 'If `true`, a response includes HTML rendered markdown of the release description.' end + route_setting :authentication, job_token_allowed: true get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_download_code! @@ -117,6 +119,7 @@ module API optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.' optional :milestones, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The titles of the related milestones' end + route_setting :authentication, job_token_allowed: true put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_update_release! @@ -142,6 +145,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag', as: :tag end + route_setting :authentication, job_token_allowed: true delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_destroy_release! diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 1aa76906b3d..2dd0e40afba 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -42,6 +42,26 @@ module API not_found! 'Blob' unless @blob end + + def fetch_target_project(current_user, user_project, params) + return user_project unless params[:from_project_id].present? + + MergeRequestTargetProjectFinder + .new(current_user: current_user, source_project: user_project, project_feature: :repository) + .execute(include_routes: true).find_by_id(params[:from_project_id]) + end + + def compare_cache_key(current_user, user_project, target_project, params) + [ + user_project, + target_project, + current_user, + :repository_compare, + target_project.repository.commit(params[:from]), + user_project.repository.commit(params[:to]), + params + ] + end end desc 'Get a project repository tree' do @@ -59,7 +79,7 @@ module API optional :page_token, type: String, desc: 'Record from which to start the keyset pagination' end end - get ':id/repository/tree' do + get ':id/repository/tree', urgency: :low do tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false)) not_found!("Tree") unless tree_finder.commit_exists? @@ -124,22 +144,17 @@ module API optional :from_project_id, type: String, desc: 'The project to compare from' optional :straight, type: Boolean, desc: 'Comparison method, `true` for direct comparison between `from` and `to` (`from`..`to`), `false` to compare using merge base (`from`...`to`)', default: false end - get ':id/repository/compare' do + get ':id/repository/compare', urgency: :low do ff_enabled = Feature.enabled?(:api_caching_rate_limit_repository_compare, user_project, default_enabled: :yaml) + target_project = fetch_target_project(current_user, user_project, params) - cache_action_if(ff_enabled, [user_project, :repository_compare, current_user, declared_params], expires_in: 1.minute) do - if params[:from_project_id].present? - target_project = MergeRequestTargetProjectFinder - .new(current_user: current_user, source_project: user_project, project_feature: :repository) - .execute(include_routes: true).find_by_id(params[:from_project_id]) + if target_project.blank? + render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400) + end - if target_project.blank? - render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400) - end - else - target_project = user_project - end + cache_key = compare_cache_key(current_user, user_project, target_project, declared_params) + cache_action_if(ff_enabled, cache_key, expires_in: 1.minute) do compare = CompareService.new(user_project, params[:to]).execute(target_project, params[:from], straight: params[:straight]) if compare diff --git a/lib/api/resource_milestone_events.rb b/lib/api/resource_milestone_events.rb index aeedd7ad109..c0483ca59c2 100644 --- a/lib/api/resource_milestone_events.rb +++ b/lib/api/resource_milestone_events.rb @@ -8,7 +8,7 @@ module API before { authenticate! } { - Issue => :issue_tracking, + Issue => :team_planning, MergeRequest => :code_review }.each do |eventable_type, feature_category| parent_type = eventable_type.parent_class.to_s.underscore diff --git a/lib/api/resource_state_events.rb b/lib/api/resource_state_events.rb index 3460aa2c00e..9b6f6a954b4 100644 --- a/lib/api/resource_state_events.rb +++ b/lib/api/resource_state_events.rb @@ -8,7 +8,7 @@ module API before { authenticate! } { - Issue => :issue_tracking, + Issue => :team_planning, MergeRequest => :code_review }.each do |eventable_class, feature_category| eventable_name = eventable_class.to_s.underscore diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index f1ec1024492..c4b17a62b59 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -43,7 +43,7 @@ module API params do use :pagination end - get 'public' do + get 'public', urgency: :low do authenticate! present paginate(public_snippets), with: Entities::PersonalSnippet, current_user: current_user diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index 87dc1358a51..cda30dc957f 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -22,21 +22,21 @@ module API entity: Entities::Issue, source: Project, finder: ->(id) { find_project_issue(id) }, - feature_category: :issue_tracking + feature_category: :team_planning }, { type: 'labels', entity: Entities::ProjectLabel, source: Project, finder: ->(id) { find_label(user_project, id) }, - feature_category: :issue_tracking + feature_category: :team_planning }, { type: 'labels', entity: Entities::GroupLabel, source: Group, finder: ->(id) { find_label(user_group, id) }, - feature_category: :issue_tracking + feature_category: :team_planning } ] diff --git a/lib/api/tags.rb b/lib/api/tags.rb index f018b421edd..1b37d38ef06 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -21,20 +21,28 @@ module API optional :order_by, type: String, values: %w[name updated], default: 'updated', desc: 'Return tags ordered by `name` or `updated` fields.' optional :search, type: String, desc: 'Return list of tags matching the search criteria' + optional :page_token, type: String, desc: 'Name of tag to start the paginaition from' use :pagination end - get ':id/repository/tags', feature_category: :source_code_management do - tags, _ = ::TagsFinder.new(user_project.repository, + get ':id/repository/tags', feature_category: :source_code_management, urgency: :low do + tags_finder = ::TagsFinder.new(user_project.repository, sort: "#{params[:order_by]}_#{params[:sort]}", - search: params[:search]).execute + search: params[:search], + page_token: params[:page_token], + per_page: params[:per_page]) - paginated_tags = paginate(::Kaminari.paginate_array(tags)) + paginated_tags = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(tags_finder) if Feature.enabled?(:api_caching_tags, user_project, type: :development) present_cached paginated_tags, with: Entities::Tag, project: user_project, cache_context: -> (_tag) { user_project.cache_key } else present paginated_tags, with: Entities::Tag, project: user_project end + + rescue Gitlab::Git::InvalidPageToken => e + unprocessable_entity!(e.message) + rescue Gitlab::Git::CommandError + service_unavailable! end desc 'Get a single repository tag' do diff --git a/lib/api/terraform/modules/v1/packages.rb b/lib/api/terraform/modules/v1/packages.rb index aa59b6a4fee..ad5a4ae7ea6 100644 --- a/lib/api/terraform/modules/v1/packages.rb +++ b/lib/api/terraform/modules/v1/packages.rb @@ -46,7 +46,8 @@ module API def finder_params { package_type: :terraform_module, - package_name: "#{params[:module_name]}/#{params[:module_system]}" + package_name: "#{params[:module_name]}/#{params[:module_system]}", + exact_name: true }.tap do |finder_params| finder_params[:package_version] = params[:module_version] if params.has_key?(:module_version) end diff --git a/lib/api/todos.rb b/lib/api/todos.rb index e0e5ca615ac..57a6ee0bebb 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -6,7 +6,7 @@ module API before { authenticate! } - feature_category :issue_tracking + feature_category :team_planning ISSUABLE_TYPES = { 'merge_requests' => ->(iid) { find_merge_request_with_access(iid) }, diff --git a/lib/api/topics.rb b/lib/api/topics.rb new file mode 100644 index 00000000000..bd28ebe58a9 --- /dev/null +++ b/lib/api/topics.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module API + class Topics < ::API::Base + include PaginationParams + + feature_category :projects + + desc 'Get topics' do + detail 'This feature was introduced in GitLab 14.5.' + success Entities::Projects::Topic + end + params do + optional :search, type: String, desc: 'Return list of topics matching the search criteria' + use :pagination + end + get 'topics' do + topics = ::Projects::TopicsFinder.new(params: declared_params(include_missing: false)).execute + + present paginate(topics), with: Entities::Projects::Topic + end + + desc 'Get topic' do + detail 'This feature was introduced in GitLab 14.5.' + success Entities::Projects::Topic + end + params do + requires :id, type: Integer, desc: 'ID of project topic' + end + get 'topics/:id' do + topic = ::Projects::Topic.find(params[:id]) + + present topic, with: Entities::Projects::Topic + end + + desc 'Create a topic' do + detail 'This feature was introduced in GitLab 14.5.' + success Entities::Projects::Topic + end + params do + requires :name, type: String, desc: 'Name' + optional :description, type: String, desc: 'Description' + optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for topic' + end + post 'topics' do + authenticated_as_admin! + + topic = ::Projects::Topic.new(declared_params(include_missing: false)) + + if topic.save + present topic, with: Entities::Projects::Topic + else + render_validation_error!(topic) + end + end + + desc 'Update a topic' do + detail 'This feature was introduced in GitLab 14.5.' + success Entities::Projects::Topic + end + params do + requires :id, type: Integer, desc: 'ID of project topic' + optional :name, type: String, desc: 'Name' + optional :description, type: String, desc: 'Description' + optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for topic' + end + put 'topics/:id' do + authenticated_as_admin! + + topic = ::Projects::Topic.find(params[:id]) + + if topic.update(declared_params(include_missing: false)) + present topic, with: Entities::Projects::Topic + else + render_validation_error!(topic) + end + end + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb index f16e1148618..ce0a0e9b502 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1062,6 +1062,7 @@ module API requires :credit_card_expiration_year, type: Integer, desc: 'The year the credit card expires' requires :credit_card_holder_name, type: String, desc: 'The credit card holder name' requires :credit_card_mask_number, type: String, desc: 'The last 4 digits of credit card number' + requires :credit_card_type, type: String, desc: 'The credit card network name' end put ":user_id/credit_card_validation", feature_category: :users do authenticated_as_admin! diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb index 310054c298a..677d0840208 100644 --- a/lib/api/v3/github.rb +++ b/lib/api/v3/github.rb @@ -20,6 +20,9 @@ module API # Jira Server user agent format: Jira DVCS Connector/version JIRA_DVCS_CLOUD_USER_AGENT = 'Jira DVCS Connector Vertigo' + GITALY_TIMEOUT_CACHE_KEY = 'api:v3:Gitaly-timeout-cache-key' + GITALY_TIMEOUT_CACHE_EXPIRY = 1.day + include PaginationParams feature_category :integrations @@ -93,6 +96,32 @@ module API notes.select { |n| n.readable_by?(current_user) } end # rubocop: enable CodeReuse/ActiveRecord + + # Returns an empty Array instead of the Commit diff files for a period + # 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, + commit.cache_key + ].join(':') + + return [] if Rails.cache.read(cache_key).present? + + begin + commit.diffs.diff_files + rescue GRPC::DeadlineExceeded => error + # Gitaly fails to load diffs consistently for some commits. The other information + # is still valuable for Jira. So we skip the loading and respond with a 200 excluding diffs + # Remove this when https://gitlab.com/gitlab-org/gitaly/-/issues/3741 is fixed. + Rails.cache.write(cache_key, 1, expires_in: GITALY_TIMEOUT_CACHE_EXPIRY) + Gitlab::ErrorTracking.track_exception(error) + [] + end + end end resource :orgs do @@ -228,10 +257,9 @@ module API user_project = find_project_with_access(params) commit = user_project.commit(params[:sha]) - not_found! 'Commit' unless commit - present commit, with: ::API::Github::Entities::RepoCommit + present commit, with: ::API::Github::Entities::RepoCommit, diff_files: diff_files(commit) end end end diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index 8441aeb10ab..fdce3c5ce18 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -32,7 +32,7 @@ module API params do optional :with_content, type: Boolean, default: false, desc: "Include pages' content" end - get ':id/wikis' do + get ':id/wikis', urgency: :low do authorize! :read_wiki, container entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic |