diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-18 09:45:46 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-18 09:45:46 +0000 |
commit | a7b3560714b4d9cc4ab32dffcd1f74a284b93580 (patch) | |
tree | 7452bd5c3545c2fa67a28aa013835fb4fa071baf /lib/api | |
parent | ee9173579ae56a3dbfe5afe9f9410c65bb327ca7 (diff) | |
download | gitlab-ce-a7b3560714b4d9cc4ab32dffcd1f74a284b93580.tar.gz |
Add latest changes from gitlab-org/gitlab@14-8-stable-eev14.8.0-rc42
Diffstat (limited to 'lib/api')
42 files changed, 356 insertions, 108 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 5984879413f..5100ec9ec9d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -76,6 +76,10 @@ module API Gitlab::UsageDataCounters::VSCodeExtensionActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user) end + after do + Gitlab::UsageDataCounters::JetBrainsPluginActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user) + end + # The locale is set to the current user's locale when `current_user` is loaded after { Gitlab::I18n.use_default_locale } @@ -171,6 +175,7 @@ module API mount ::API::Ci::ResourceGroups mount ::API::Ci::Runner mount ::API::Ci::Runners + mount ::API::Ci::SecureFiles mount ::API::Ci::Triggers mount ::API::Ci::Variables mount ::API::Commits @@ -300,6 +305,7 @@ module API mount ::API::Internal::Pages mount ::API::Internal::Kubernetes mount ::API::Internal::MailRoom + mount ::API::Internal::ContainerRegistry::Migration version 'v3', using: :path do # Although the following endpoints are kept behind V3 namespace, diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 462c4a3de4c..a2c9020ac84 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -24,7 +24,7 @@ module API helpers do params :filter_params do optional :search, type: String, desc: 'Return list of branches matching the search criteria' - optional :sort, type: String, desc: 'Return list of branches sorted by the given field' + optional :sort, type: String, desc: 'Return list of branches sorted by the given field', values: %w[name_asc updated_asc updated_desc] end end @@ -42,9 +42,7 @@ module API optional :page_token, type: String, desc: 'Name of branch to start the paginaition from' end 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 + cache_action([user_project, :branches, current_user, declared_params], expires_in: 30.seconds) do user_project.preload_protected_branches repository = user_project.repository diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb index ca76d2664f8..9f59eea5013 100644 --- a/lib/api/ci/job_artifacts.rb +++ b/lib/api/ci/job_artifacts.rb @@ -47,7 +47,7 @@ module API requires :artifact_path, type: String, desc: 'Artifact path' end route_setting :authentication, job_token_allowed: true - get ':id/jobs/artifacts/:ref_name/raw/*artifact_path', + get ':id/jobs/artifacts/:ref_name/raw/*artifact_path', urgency: :low, format: false, requirements: { ref_name: /.+/ } do authorize_download_artifacts! @@ -70,7 +70,7 @@ module API requires :job_id, type: Integer, desc: 'The ID of a job' end route_setting :authentication, job_token_allowed: true - get ':id/jobs/:job_id/artifacts' do + get ':id/jobs/:job_id/artifacts', urgency: :low do authorize_download_artifacts! build = find_build!(params[:job_id]) diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 4e5d6c264bf..e0086f624a8 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -179,7 +179,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - get ':id/pipelines/:pipeline_id/test_report', feature_category: :code_testing do + get ':id/pipelines/:pipeline_id/test_report', feature_category: :code_testing, urgency: :low do authorize! :read_build, pipeline present pipeline.test_reports, with: TestReportEntity, details: true diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index fef6a7891c2..4df9600322c 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -15,20 +15,29 @@ module API params do requires :token, type: String, desc: 'Registration token' optional :description, type: String, desc: %q(Runner's description) - optional :maintainer_note, type: String, desc: %q(Runner's maintainer notes) + optional :maintainer_note, type: String, desc: %q(Deprecated: Use :maintenance_note instead. Runner's maintenance notes) + optional :maintenance_note, type: String, desc: %q(Runner's maintenance notes) optional :info, type: Hash, desc: %q(Runner's metadata) - optional :active, type: Boolean, desc: 'Should Runner be active' - optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' + optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Should runner be active' + optional :paused, type: Boolean, desc: 'Whether the runner should ignore new jobs' + optional :locked, type: Boolean, desc: 'Whether the runner should be locked for current project' optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, - desc: 'The access_level of the runner' - optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' + desc: 'The access_level of the runner; `not_protected` or `ref_protected`' + optional :run_untagged, type: Boolean, desc: 'Whether the runner should handle untagged jobs' optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: %q(List of Runner's tags) - optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' + optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this runner handles the job' + mutually_exclusive :maintainer_note, :maintainer_note + mutually_exclusive :active, :paused end post '/', feature_category: :runner do - attributes = attributes_for_keys(%i[description maintainer_note active locked run_untagged tag_list access_level maximum_timeout]) + attributes = attributes_for_keys(%i[description maintainer_note maintenance_note active paused locked run_untagged tag_list access_level maximum_timeout]) .merge(get_runner_details_from_request) + # Pull in deprecated maintainer_note if that's the only note value available + deprecated_note = attributes.delete(:maintainer_note) + attributes[:maintenance_note] ||= deprecated_note if deprecated_note + attributes[:active] = !attributes.delete(:paused) if attributes.include?(:paused) + @runner = ::Ci::RegisterRunnerService.new.execute(params[:token], attributes) forbidden! unless @runner @@ -48,7 +57,7 @@ module API delete '/', feature_category: :runner do authenticate_runner! - destroy_conditionally!(current_runner) + destroy_conditionally!(current_runner) { ::Ci::UnregisterRunnerService.new(current_runner).execute } end desc 'Validates authentication credentials' do @@ -235,7 +244,7 @@ module API optional :artifact_type, type: String, desc: %q(The type of artifact), default: 'archive', values: ::Ci::JobArtifact.file_types.keys end - post '/:id/artifacts/authorize', feature_category: :build_artifacts do + post '/:id/artifacts/authorize', feature_category: :build_artifacts, urgency: :low do not_allowed! unless Gitlab.config.artifacts.enabled require_gitlab_workhorse! @@ -271,7 +280,7 @@ module API default: 'zip', values: ::Ci::JobArtifact.file_formats.keys optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware)) end - post '/:id/artifacts', feature_category: :build_artifacts do + post '/:id/artifacts', feature_category: :build_artifacts, urgency: :low do not_allowed! unless Gitlab.config.artifacts.enabled require_gitlab_workhorse! diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index f21782a698f..8a7ffab97dd 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -18,6 +18,7 @@ module API desc: 'The scope of specific runners to show' optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, desc: 'The type of the runners to show' + optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, desc: 'The status of the runners to show' optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' @@ -26,9 +27,7 @@ module API get do runners = current_user.ci_owned_runners runners = filter_runners(runners, params[:scope], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) - runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES) - runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) - runners = runners.tagged_with(params[:tag_list]) if params[:tag_list] + runners = apply_filter(runners, params) present paginate(runners), with: Entities::Ci::Runner end @@ -41,6 +40,7 @@ module API desc: 'The scope of specific runners to show' optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, desc: 'The type of the runners to show' + optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, desc: 'The status of the runners to show' optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' @@ -51,9 +51,7 @@ module API runners = ::Ci::Runner.all runners = filter_runners(runners, params[:scope]) - runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES) - runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) - runners = runners.tagged_with(params[:tag_list]) if params[:tag_list] + runners = apply_filter(runners, params) present paginate(runners), with: Entities::Ci::Runner end @@ -77,18 +75,21 @@ module API params do requires :id, type: Integer, desc: 'The ID of the runner' optional :description, type: String, desc: 'The description of the runner' - optional :active, type: Boolean, desc: 'The state of a runner' + optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Flag indicating whether the runner is allowed to receive jobs' + optional :paused, type: Boolean, desc: 'Flag indicating whether the runner should ignore new jobs' optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a runner' - optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs' + optional :run_untagged, type: Boolean, desc: 'Flag indicating whether the runner can execute untagged jobs' optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, desc: 'The access_level of the runner' optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' - at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout + at_least_one_of :description, :active, :paused, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout + mutually_exclusive :active, :paused end put ':id' do runner = get_runner(params.delete(:id)) authenticate_update_runner!(runner) + params[:active] = !params.delete(:paused) if params.include?(:paused) update_service = ::Ci::UpdateRunnerService.new(runner) if update_service.update(declared_params(include_missing: false)) @@ -109,7 +110,7 @@ module API authenticate_delete_runner!(runner) - destroy_conditionally!(runner) + destroy_conditionally!(runner) { ::Ci::UnregisterRunnerService.new(runner).execute } end desc 'List jobs running on a runner' do @@ -142,7 +143,7 @@ module API authenticate_update_runner!(runner) runner.reset_token! - present runner.token, with: Entities::Ci::ResetTokenResult + present runner.token_with_expiration, with: Entities::Ci::ResetTokenResult end end @@ -160,6 +161,7 @@ module API desc: 'The scope of specific runners to show' optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, desc: 'The type of the runners to show' + optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, desc: 'The status of the runners to show' optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' @@ -223,18 +225,14 @@ module API params do optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, desc: 'The type of the runners to show' + optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, desc: 'The status of the runners to show' optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' use :pagination end get ':id/runners' do - runners = if ::Feature.enabled?(:ci_find_runners_by_ci_mirrors, user_group, default_enabled: :yaml) - ::Ci::Runner.belonging_to_group_and_ancestors(user_group.id) - else - ::Ci::Runner.legacy_belonging_to_group(user_group.id, include_ancestors: true) - end - + runners = ::Ci::Runner.group_or_instance_wide(user_group) runners = apply_filter(runners, params) present paginate(runners), with: Entities::Ci::Runner @@ -251,7 +249,7 @@ module API authorize! :update_runners_registration_token ApplicationSetting.current.reset_runners_registration_token! - present ApplicationSetting.current_without_cache.runners_registration_token, with: Entities::Ci::ResetTokenResult + present ApplicationSetting.current_without_cache.runners_registration_token_with_expiration, with: Entities::Ci::ResetTokenResult end end @@ -269,7 +267,7 @@ module API authorize! :update_runners_registration_token, project project.reset_runners_token! - present project.runners_token, with: Entities::Ci::ResetTokenResult + present project.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult end end @@ -287,7 +285,7 @@ module API authorize! :update_runners_registration_token, group group.reset_runners_token! - present group.runners_token, with: Entities::Ci::ResetTokenResult + present group.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult end end @@ -310,6 +308,7 @@ module API def apply_filter(runners, params) runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES) runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) + runners = filter_runners(runners, params[:paused] ? 'paused' : 'active', allowed_scopes: %w[paused active]) if params.include?(:paused) runners = runners.tagged_with(params[:tag_list]) if params[:tag_list] runners diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb new file mode 100644 index 00000000000..715a8b37fae --- /dev/null +++ b/lib/api/ci/secure_files.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +module API + module Ci + class SecureFiles < ::API::Base + include PaginationParams + + before do + authenticate! + authorize! :admin_build, user_project + feature_flag_enabled? + end + + feature_category :pipeline_authoring + + default_format :json + + params do + requires :id, type: String, desc: 'The ID of a project' + end + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'List all Secure Files for a Project' + params do + use :pagination + end + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true + get ':id/secure_files' do + secure_files = user_project.secure_files + present paginate(secure_files), with: Entities::Ci::SecureFile + end + + desc 'Get an individual Secure File' + params do + requires :id, type: Integer, desc: 'The Secure File ID' + end + + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true + get ':id/secure_files/:secure_file_id' do + secure_file = user_project.secure_files.find(params[:secure_file_id]) + present secure_file, with: Entities::Ci::SecureFile + end + + desc 'Download a Secure File' + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true + get ':id/secure_files/:secure_file_id/download' do + secure_file = user_project.secure_files.find(params[:secure_file_id]) + + content_type 'application/octet-stream' + env['api.format'] = :binary + header['Content-Disposition'] = "attachment; filename=#{secure_file.name}" + body secure_file.file.read + end + + desc 'Upload a Secure File' + params do + requires :name, type: String, desc: 'The name of the file' + requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The secure file to be uploaded' + optional :permissions, type: String, desc: 'The file permissions', default: 'read_only', values: %w[read_only read_write execute] + end + + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true + post ':id/secure_files' do + secure_file = user_project.secure_files.new( + name: params[:name], + permissions: params[:permissions] || :read_only + ) + + secure_file.file = params[:file] + + file_too_large! unless secure_file.file.size < ::Ci::SecureFile::FILE_SIZE_LIMIT.to_i + + if secure_file.save + present secure_file, with: Entities::Ci::SecureFile + else + render_validation_error!(secure_file) + end + end + + desc 'Delete an individual Secure File' + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true + delete ':id/secure_files/:secure_file_id' do + secure_file = user_project.secure_files.find(params[:secure_file_id]) + + secure_file.destroy! + + no_content! + end + end + + helpers do + def feature_flag_enabled? + service_unavailable! unless Feature.enabled?(:ci_secure_files, user_project, default_enabled: :yaml) + end + end + end + end +end diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index 486ff5d89bc..6939853c06b 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -47,7 +47,7 @@ module API desc 'Gets a specific deployment' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Deployment + success Entities::DeploymentExtended end params do requires :deployment_id, type: Integer, desc: 'The deployment ID' @@ -57,12 +57,12 @@ module API deployment = user_project.deployments.find(params[:deployment_id]) - present deployment, with: Entities::Deployment + present deployment, with: Entities::DeploymentExtended end desc 'Creates a new deployment' do detail 'This feature was introduced in GitLab 12.4' - success Entities::Deployment + success Entities::DeploymentExtended end params do requires :environment, @@ -106,7 +106,7 @@ module API deployment = service.execute if deployment.persisted? - present(deployment, with: Entities::Deployment, current_user: current_user) + present(deployment, with: Entities::DeploymentExtended, current_user: current_user) else render_validation_error!(deployment) end @@ -114,7 +114,7 @@ module API desc 'Updates an existing deployment' do detail 'This feature was introduced in GitLab 12.4' - success Entities::Deployment + success Entities::DeploymentExtended end params do requires :status, @@ -136,7 +136,7 @@ module API service = ::Deployments::UpdateService.new(deployment, declared_params) if service.execute - present(deployment, with: Entities::Deployment, current_user: current_user) + present(deployment, with: Entities::DeploymentExtended, current_user: current_user) else render_validation_error!(deployment) end diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb index 0709a8c2036..f73e4b621ab 100644 --- a/lib/api/discussions.rb +++ b/lib/api/discussions.rb @@ -252,7 +252,7 @@ module API .fresh # Without RendersActions#prepare_notes_for_rendering, - # Note#system_note_with_references_visible_for? will attempt to render + # Note#system_note_visible_for? will attempt to render # Markdown references mentioned in the note to see whether they # should be redacted. For notes that reference a commit, this # would also incur a Gitaly call to verify the commit exists. diff --git a/lib/api/entities/ci/reset_token_result.rb b/lib/api/entities/ci/reset_token_result.rb index 4dbf831582b..f0b1de6a5a7 100644 --- a/lib/api/entities/ci/reset_token_result.rb +++ b/lib/api/entities/ci/reset_token_result.rb @@ -4,7 +4,8 @@ module API module Entities module Ci class ResetTokenResult < Grape::Entity - expose(:token) {|object| object} + expose(:token) + expose(:token_expires_at, if: -> (object, options) { object.expirable? }) end end end diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb index c17ff513479..a6944b8c925 100644 --- a/lib/api/entities/ci/runner.rb +++ b/lib/api/entities/ci/runner.rb @@ -7,7 +7,10 @@ module API expose :id expose :description expose :ip_address - expose :active + expose :active # TODO Remove in %15.0 in favor of `paused` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/351109 + expose :paused do |runner| + !runner.active + end expose :instance_type?, as: :is_shared expose :runner_type expose :name diff --git a/lib/api/entities/ci/runner_details.rb b/lib/api/entities/ci/runner_details.rb index 6ded1296f2a..9b1decca274 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.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') + runner.projects else - options[:current_user].authorized_projects.where(id: runner.projects).allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') + options[:current_user].authorized_projects.where(id: runner.runner_projects.pluck(:project_id)) 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.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') + runner.groups else - options[:current_user].authorized_groups.where(id: runner.groups).allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') + options[:current_user].authorized_groups.where(id: runner.runner_namespaces.pluck(:namespace_id)) end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/api/entities/ci/runner_registration_details.rb b/lib/api/entities/ci/runner_registration_details.rb index fa7e44c9e40..53be918406f 100644 --- a/lib/api/entities/ci/runner_registration_details.rb +++ b/lib/api/entities/ci/runner_registration_details.rb @@ -4,7 +4,7 @@ module API module Entities module Ci class RunnerRegistrationDetails < Grape::Entity - expose :id, :token + expose :id, :token, :token_expires_at end end end diff --git a/lib/api/entities/ci/secure_file.rb b/lib/api/entities/ci/secure_file.rb new file mode 100644 index 00000000000..041c864156b --- /dev/null +++ b/lib/api/entities/ci/secure_file.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class SecureFile < Grape::Entity + expose :id + expose :name + expose :permissions + expose :checksum + expose :checksum_algorithm + end + end + end +end diff --git a/lib/api/entities/deployment_extended.rb b/lib/api/entities/deployment_extended.rb new file mode 100644 index 00000000000..74cfb61388b --- /dev/null +++ b/lib/api/entities/deployment_extended.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module API + module Entities + class DeploymentExtended < Deployment + end + end +end + +API::Entities::DeploymentExtended.prepend_mod diff --git a/lib/api/entities/user_safe.rb b/lib/api/entities/user_safe.rb index 6006a076020..c7349026a88 100644 --- a/lib/api/entities/user_safe.rb +++ b/lib/api/entities/user_safe.rb @@ -7,7 +7,7 @@ module API expose :name do |user| next user.name unless user.project_bot? - next user.name if options[:current_user]&.can?(:read_resource_access_tokens, user.projects.first) + next user.name if options[:current_user]&.can?(:read_project, user.projects.first) # If the requester does not have permission to read the project bot name, # the API returns an arbitrary string. UI changes will be addressed in a follow up issue: diff --git a/lib/api/groups.rb b/lib/api/groups.rb index d3d1f03585b..5fbf222be5d 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -84,10 +84,11 @@ module API paginate(projects) end - def present_projects(params, projects) + def present_projects(params, projects, single_hierarchy: false) options = { with: params[:simple] ? Entities::BasicProjectDetails : Entities::Project, - current_user: current_user + current_user: current_user, + single_hierarchy: single_hierarchy } projects, options = with_custom_attributes(projects, options) @@ -292,6 +293,7 @@ module API optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' optional :with_shared, type: Boolean, default: true, desc: 'Include projects shared to this group' optional :include_subgroups, type: Boolean, default: false, desc: 'Includes projects in subgroups of this group' + optional :include_ancestor_groups, type: Boolean, default: false, desc: 'Includes projects in ancestors of this group' optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user on projects' use :pagination @@ -301,12 +303,13 @@ module API get ":id/projects" do finder_options = { only_owned: !params[:with_shared], - include_subgroups: params[:include_subgroups] + include_subgroups: params[:include_subgroups], + include_ancestor_groups: params[:include_ancestor_groups] } projects = find_group_projects(params, finder_options) - present_projects(params, projects) + present_projects(params, projects, single_hierarchy: true) end desc 'Get a list of shared projects in this group' do diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 76840091112..184fe7868a5 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -117,6 +117,8 @@ module API # rubocop: disable CodeReuse/ActiveRecord def find_project(id) + return unless id + projects = Project.without_deleted if id.is_a?(Integer) || id =~ /^\d+$/ @@ -561,7 +563,7 @@ module API def increment_counter(event_name) feature_name = "usage_data_#{event_name}" - return unless Feature.enabled?(feature_name) + return unless Feature.enabled?(feature_name, default_enabled: :yaml) Gitlab::UsageDataCounters.count(event_name) rescue StandardError => error diff --git a/lib/api/helpers/container_registry_helpers.rb b/lib/api/helpers/container_registry_helpers.rb index 9c844e364eb..78daf2c8cb1 100644 --- a/lib/api/helpers/container_registry_helpers.rb +++ b/lib/api/helpers/container_registry_helpers.rb @@ -6,7 +6,7 @@ module API extend ActiveSupport::Concern included do - rescue_from Faraday::Error, ContainerRegistry::Path::InvalidRegistryPathError do |e| + rescue_from Faraday::Error, ::ContainerRegistry::Path::InvalidRegistryPathError do |e| service_unavailable!('We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.') end end diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index 72b16a23dd6..86dedc12fca 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -346,7 +346,13 @@ module API required: false, name: :datadog_env, type: String, - desc: 'For self-managed deployments, set the env tag for all the data sent to Datadog. How do I use tags?' + desc: 'For self-managed deployments, set the env tag for all the data sent to Datadog' + }, + { + required: false, + name: :datadog_tags, + type: String, + desc: 'Custom tags in Datadog. Specify one tag per line in the format: "key:value\nkey2:value2"' } ], 'discord' => [ diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index e03f029a6ef..46685df0989 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -124,7 +124,8 @@ module API repository: repository.gitaly_repository.to_h, address: Gitlab::GitalyClient.address(repository.shard), token: Gitlab::GitalyClient.token(repository.shard), - features: Feature::Gitaly.server_feature_flags(repository.project) + features: Feature::Gitaly.server_feature_flags(repository.project), + use_sidechannel: Feature.enabled?(:gitlab_shell_upload_pack_sidechannel, repository.project, default_enabled: :yaml) } end end diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 6c20993431d..f26ac1318b1 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -23,7 +23,7 @@ module API def retrieve_members(source, params:, deep: false) members = deep ? find_all_members(source) : source_members(source).connected_to_user members = members.includes(:user) - members = members.references(:user).merge(User.search(params[:query])) if params[:query].present? + members = members.references(:user).merge(User.search(params[:query], use_minimum_char_limit: false)) if params[:query].present? members = members.where(user_id: params[:user_ids]) if params[:user_ids].present? members end diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb index f8fe40f7135..00d9f49adf0 100644 --- a/lib/api/helpers/merge_requests_helpers.rb +++ b/lib/api/helpers/merge_requests_helpers.rb @@ -47,9 +47,9 @@ module API desc: 'Return opened, closed, locked, merged, or all merge requests' optional :order_by, type: String, - values: %w[created_at updated_at], + values: Helpers::MergeRequestsHelpers.sort_options, default: 'created_at', - desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.' + desc: "Return merge requests ordered by #{Helpers::MergeRequestsHelpers.sort_options_help} fields." optional :sort, type: String, values: %w[asc desc], @@ -115,6 +115,22 @@ module API render_validation_error!(merge_request) end + + def self.sort_options + %w[ + created_at + label_priority + milestone_due + popularity + priority + title + updated_at + ] + end + + def self.sort_options_help + sort_options.map {|y| "`#{y}`" }.to_sentence(last_word_connector: ' or ') + end end end end diff --git a/lib/api/helpers/rate_limiter.rb b/lib/api/helpers/rate_limiter.rb index 0ad4f089907..03f3cd649b1 100644 --- a/lib/api/helpers/rate_limiter.rb +++ b/lib/api/helpers/rate_limiter.rb @@ -5,7 +5,7 @@ module API # == 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 + # Gitlab::ApplicationRateLimiter module. 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 diff --git a/lib/api/internal/container_registry/migration.rb b/lib/api/internal/container_registry/migration.rb new file mode 100644 index 00000000000..b84e14c6f31 --- /dev/null +++ b/lib/api/internal/container_registry/migration.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module API + module Internal + module ContainerRegistry + class Migration < ::API::Base + feature_category :container_registry + + STATUS_PRE_IMPORT_COMPLETE = 'pre_import_complete' + STATUS_PRE_IMPORT_FAILED = 'pre_import_failed' + STATUS_IMPORT_COMPLETE = 'import_complete' + STATUS_IMPORT_FAILED = 'import_failed' + POSSIBLE_VALUES = [ + STATUS_PRE_IMPORT_COMPLETE, + STATUS_PRE_IMPORT_FAILED, + STATUS_IMPORT_COMPLETE, + STATUS_IMPORT_FAILED + ].freeze + + before { authenticate! } + + helpers do + def authenticate! + secret_token = Gitlab.config.registry.notification_secret + + unauthorized! unless Devise.secure_compare(secret_token, headers['Authorization']) + end + + def find_repository!(path) + ::ContainerRepository.find_by_path!(::ContainerRegistry::Path.new(path)) + end + end + + params do + requires :repository_path, type: String, desc: 'The container repository path' + requires :status, type: String, values: POSSIBLE_VALUES, desc: 'The migration step status' + end + put 'internal/registry/repositories/*repository_path/migration/status' do + repository = find_repository!(declared_params[:repository_path]) + + unless repository.migration_in_active_state? + bad_request!("Wrong migration state (#{repository.migration_state})") + end + + case declared_params[:status] + when STATUS_PRE_IMPORT_COMPLETE + unless repository.finish_pre_import_and_start_import + bad_request!("Couldn't transition from pre_importing to importing") + end + when STATUS_IMPORT_COMPLETE + unless repository.finish_import + bad_request!("Couldn't transition from importing to import_done") + end + when STATUS_IMPORT_FAILED, STATUS_PRE_IMPORT_FAILED + repository.abort_import + end + + status 200 + end + end + end + end +end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 46124a74e9d..a5d6a6d7cf3 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -80,7 +80,7 @@ module API desc: 'Return issues ordered by `created_at`, `due_date`, `label_priority`, `milestone_due`, `popularity`, `priority`, `relative_position`, `title`, or `updated_at` fields.' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return issues sorted in `asc` or `desc` order.' - optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '', + optional :due_date, type: String, values: %w[0 any today tomorrow 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: WorkItems::Type.allowed_types_for_issues, desc: "The type of the issue. Accepts: #{WorkItems::Type.allowed_types_for_issues.join(', ')}" diff --git a/lib/api/lint.rb b/lib/api/lint.rb index bfd457a3092..6de78c81cac 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -42,14 +42,18 @@ module API 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' + optional :ref, type: String, desc: 'Branch or tag used to execute a dry run. Defaults to the default branch of the project. Only used when dry_run is true' end 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) + if user_project.commit.present? + content = user_project.repository.gitlab_ci_yml_for(user_project.commit.id, user_project.ci_config_path_or_default) + end + result = Gitlab::Ci::Lint .new(project: user_project, current_user: current_user) - .validate(content, dry_run: params[:dry_run]) + .validate(content, dry_run: params[:dry_run], ref: params[:ref] || user_project.default_branch) present result, with: Entities::Ci::Lint::Result, current_user: current_user, include_jobs: params[:include_jobs] end @@ -63,13 +67,14 @@ module API 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' + optional :ref, type: String, desc: 'Branch or tag used to execute a dry run. Defaults to the default branch of the project. Only used when dry_run is true' end post ':id/ci/lint', urgency: :low do authorize! :create_pipeline, user_project result = Gitlab::Ci::Lint .new(project: user_project, current_user: current_user) - .validate(params[:content], dry_run: params[:dry_run]) + .validate(params[:content], dry_run: params[:dry_run], ref: params[:ref] || user_project.default_branch) status 200 present result, with: Entities::Ci::Lint::Result, current_user: current_user, include_jobs: params[:include_jobs] diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 3f39af7f909..f7df8d33418 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -117,6 +117,9 @@ module API forbidden!('Cannot push to source branch') unless user_access.can_push_to_branch?(merge_request.source_branch) + + forbidden!('Source branch is protected from force push') unless + merge_request.permits_force_push? end params :merge_requests_params do @@ -204,11 +207,7 @@ module API options = serializer_options_for(merge_requests).merge(project: user_project) options[:project] = user_project - if Feature.enabled?(:api_caching_merge_requests, user_project, type: :development, default_enabled: :yaml) - present_cached merge_requests, expires_in: 2.days, **options - else - present merge_requests, options - end + present_cached merge_requests, expires_in: 2.days, **options end desc 'Create a merge request' do diff --git a/lib/api/package_files.rb b/lib/api/package_files.rb index 3bf47fe1e8b..e80355e80c7 100644 --- a/lib/api/package_files.rb +++ b/lib/api/package_files.rb @@ -28,13 +28,8 @@ module API package = ::Packages::PackageFinder .new(user_project, params[:package_id]).execute - package_files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml) - package.installable_package_files - else - package.package_files - end - - package_files = package_files.preload_pipelines + package_files = package.installable_package_files + .preload_pipelines present paginate(package_files), with: ::API::Entities::PackageFile end @@ -55,13 +50,8 @@ module API not_found! unless package - package_files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml) - package.installable_package_files - else - package.package_files - end - - package_file = package_files.find_by_id(params[:package_file_id]) + package_file = package.installable_package_files + .find_by_id(params[:package_file_id]) not_found! unless package_file diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb index a232b58d3f7..85ac50d5bec 100644 --- a/lib/api/pagination_params.rb +++ b/lib/api/pagination_params.rb @@ -18,7 +18,7 @@ module API helpers do params :pagination do optional :page, type: Integer, default: 1, desc: 'Current page number' - optional :per_page, type: Integer, default: 20, desc: 'Number of items per page' + optional :per_page, type: Integer, default: 20, desc: 'Number of items per page', except_values: [0] end end end diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 7bdcaa5a26f..a3d76e571a9 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -133,7 +133,7 @@ module API success Entities::ProjectImportStatus end post 'remote-import' do - not_found! unless ::Feature.enabled?(:import_project_from_remote_file) + not_found! unless ::Feature.enabled?(:import_project_from_remote_file, default_enabled: :yaml) check_rate_limit! :project_import, scope: [current_user, :project_import] diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb index a4bd06aec10..aabecb43653 100644 --- a/lib/api/projects_relation_builder.rb +++ b/lib/api/projects_relation_builder.rb @@ -13,6 +13,7 @@ module API preload_repository_cache(projects_relation) Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user] + Preloaders::SingleHierarchyProjectGroupPlansPreloader.new(projects_relation).execute if options[:single_hierarchy] projects_relation end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index fc976c23726..c3632c812f3 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -171,7 +171,6 @@ module API 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', 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) if target_project.blank? @@ -180,7 +179,7 @@ module API 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 + cache_action(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/rubygem_packages.rb b/lib/api/rubygem_packages.rb index 3effa370e84..6ac5ad0518b 100644 --- a/lib/api/rubygem_packages.rb +++ b/lib/api/rubygem_packages.rb @@ -69,9 +69,7 @@ module API package_files = ::Packages::PackageFile .for_rubygem_with_file_name(user_project, params[:file_name]) - package_files = package_files.installable if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml) - - package_file = package_files.last! + package_file = package_files.installable.last! track_package_event('pull_package', :rubygems, project: user_project, namespace: user_project.namespace) diff --git a/lib/api/scope.rb b/lib/api/scope.rb index 707775e5d15..62aefcceb4b 100644 --- a/lib/api/scope.rb +++ b/lib/api/scope.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true # Encapsulate a scope used for authorization, such as `api`, or `read_user` +# See Gitlab::Auth for the set of available scopes, and their purposes. module API class Scope attr_reader :name, :if diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 508ccdb4b33..b256432fbf1 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -177,6 +177,10 @@ module API optional :floc_enabled, type: Grape::API::Boolean, desc: 'Enable FloC (Federated Learning of Cohorts)' optional :user_deactivation_emails_enabled, type: Boolean, desc: 'Send emails to users upon account deactivation' optional :suggest_pipeline_enabled, type: Boolean, desc: 'Enable pipeline suggestion banner' + optional :users_get_by_id_limit, type: Integer, desc: "Maximum number of calls to the /users/:id API per 10 minutes per user. Set to 0 for unlimited requests." + optional :runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for shared runners, in seconds' + optional :group_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for group runners, in seconds' + optional :project_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for project runners, in seconds' ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| optional :"#{type}_key_restriction", diff --git a/lib/api/support/token_with_expiration.rb b/lib/api/support/token_with_expiration.rb new file mode 100644 index 00000000000..2cbd562c608 --- /dev/null +++ b/lib/api/support/token_with_expiration.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module API + module Support + class TokenWithExpiration + def initialize(strategy, instance) + @strategy = strategy + @instance = instance + end + + def token + @strategy.get_token(@instance) + end + + def token_expires_at + @strategy.expires_at(@instance) + end + + def expirable? + @strategy.expirable? + end + end + end +end diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 1b37d38ef06..0fa8c21f8d7 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -33,11 +33,7 @@ module API 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 + present_cached paginated_tags, with: Entities::Tag, project: user_project, cache_context: -> (_tag) { user_project.cache_key } rescue Gitlab::Git::InvalidPageToken => e unprocessable_entity!(e.message) diff --git a/lib/api/terraform/modules/v1/packages.rb b/lib/api/terraform/modules/v1/packages.rb index 970fdeba734..797b4aad033 100644 --- a/lib/api/terraform/modules/v1/packages.rb +++ b/lib/api/terraform/modules/v1/packages.rb @@ -21,7 +21,7 @@ module API module_version: SEMVER_REGEX }.freeze - feature_category :package_registry + feature_category :infrastructure_as_code after_validation do require_packages_enabled! @@ -71,11 +71,7 @@ module API def package_file strong_memoize(:package_file) do - if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml) - package.installable_package_files.first - else - package.package_files.first - end + package.installable_package_files.first end end end diff --git a/lib/api/usage_data_non_sql_metrics.rb b/lib/api/usage_data_non_sql_metrics.rb index d9e0d153e58..983038e0263 100644 --- a/lib/api/usage_data_non_sql_metrics.rb +++ b/lib/api/usage_data_non_sql_metrics.rb @@ -18,7 +18,7 @@ module API get 'non_sql_metrics' do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/325534') - data = Gitlab::UsageDataNonSqlMetrics.uncached_data + data = Gitlab::Usage::ServicePingReport.for(output: :non_sql_metrics_values) present data end diff --git a/lib/api/usage_data_queries.rb b/lib/api/usage_data_queries.rb index 22e83fe0294..3432e71eb28 100644 --- a/lib/api/usage_data_queries.rb +++ b/lib/api/usage_data_queries.rb @@ -18,7 +18,7 @@ module API get 'queries' do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/325534') - queries = Gitlab::UsageDataQueries.uncached_data + queries = Gitlab::Usage::ServicePingReport.for(output: :metrics_queries) present queries end diff --git a/lib/api/users.rb b/lib/api/users.rb index eeb5244466a..d540978931e 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -143,7 +143,12 @@ module API forbidden!('Not authorized!') unless current_user if Feature.enabled?(:rate_limit_user_by_id_endpoint, type: :development) - check_rate_limit! :users_get_by_id, scope: current_user unless current_user.admin? + unless current_user.admin? + check_rate_limit!(:users_get_by_id, + scope: current_user, + users_allowlist: Gitlab::CurrentSettings.current_application_settings.users_get_by_id_limit_allowlist + ) + end end user = User.find_by(id: params[:id]) |