diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-19 17:16:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-19 17:16:28 +0300 |
commit | e4384360a16dd9a19d4d2d25d0ef1f2b862ed2a6 (patch) | |
tree | 2fcdfa7dcdb9db8f5208b2562f4b4e803d671243 /lib/api | |
parent | ffda4e7bcac36987f936b4ba515995a6698698f0 (diff) |
Add latest changes from gitlab-org/gitlab@16-2-stable-eev16.2.0-rc42
Diffstat (limited to 'lib/api')
51 files changed, 541 insertions, 233 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 090fbaa7f93..7da5f21b21f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -316,6 +316,7 @@ module API mount ::API::UsageDataQueries mount ::API::Users mount ::API::UserCounts + mount ::API::UserRunners mount ::API::Wikis add_open_api_documentation! diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index f7a39db7249..aa7468723b7 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -82,7 +82,9 @@ module API unauthorized! unless award.user == current_user || current_user&.can_admin_all_resources? - destroy_conditionally!(award) + destroy_conditionally!(award) do + AwardEmojis::DestroyService.new(awardable, award.name, award.user).execute + end end end end diff --git a/lib/api/badges.rb b/lib/api/badges.rb index 84c9f780a53..62eecdbd5e5 100644 --- a/lib/api/badges.rb +++ b/lib/api/badges.rb @@ -22,7 +22,9 @@ module API %w[group project].each do |source_type| params do - requires :id, type: String, desc: "The ID of a #{source_type}" + requires :id, + type: String, + desc: "The ID or URL-encoded path of the #{source_type} owned by the authenticated user." end resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc "Gets a list of #{source_type} badges viewable by the authenticated user." do diff --git a/lib/api/ci/pipeline_schedules.rb b/lib/api/ci/pipeline_schedules.rb index e27ec24fb44..1606d5ba649 100644 --- a/lib/api/ci/pipeline_schedules.rb +++ b/lib/api/ci/pipeline_schedules.rb @@ -90,14 +90,28 @@ module API post ':id/pipeline_schedules' do authorize! :create_pipeline_schedule, user_project - pipeline_schedule = ::Ci::CreatePipelineScheduleService - .new(user_project, current_user, declared_params(include_missing: false)) - .execute + if ::Feature.enabled?(:ci_refactoring_pipeline_schedule_create_service, @project) + response = ::Ci::PipelineSchedules::CreateService + .new(user_project, current_user, declared_params(include_missing: false)) + .execute - if pipeline_schedule.persisted? - present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails + pipeline_schedule = response.payload + + if response.success? + present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails + else + render_validation_error!(pipeline_schedule) + end else - render_validation_error!(pipeline_schedule) + pipeline_schedule = ::Ci::CreatePipelineScheduleService + .new(user_project, current_user, declared_params(include_missing: false)) + .execute + + if pipeline_schedule.persisted? + present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails + else + render_validation_error!(pipeline_schedule) + end end end @@ -121,10 +135,22 @@ module API put ':id/pipeline_schedules/:pipeline_schedule_id' do authorize! :update_pipeline_schedule, pipeline_schedule - if pipeline_schedule.update(declared_params(include_missing: false)) - present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails + if ::Feature.enabled?(:ci_refactoring_pipeline_schedule_create_service, @project) + response = ::Ci::PipelineSchedules::UpdateService + .new(pipeline_schedule, current_user, declared_params(include_missing: false)) + .execute + + if response.success? + present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails + else + render_validation_error!(pipeline_schedule) + end else - render_validation_error!(pipeline_schedule) + if pipeline_schedule.update(declared_params(include_missing: false)) # rubocop:disable Style/IfInsideElse + present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails + else + render_validation_error!(pipeline_schedule) + end end end diff --git a/lib/api/ci/variables.rb b/lib/api/ci/variables.rb index f5331eb75da..49a6ec279fb 100644 --- a/lib/api/ci/variables.rb +++ b/lib/api/ci/variables.rb @@ -63,6 +63,7 @@ module API optional :raw, type: Boolean, desc: 'Whether the variable will be expanded' optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of the variable. Default: env_var' optional :environment_scope, type: String, desc: 'The environment_scope of the variable' + optional :description, type: String, desc: 'The description of the variable' end post ':id/variables' do variable = ::Ci::ChangeVariableService.new( @@ -95,6 +96,7 @@ module API optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production' do optional :environment_scope, type: String, desc: 'The environment scope of a variable' end + optional :description, type: String, desc: 'The description of the variable' end # rubocop: disable CodeReuse/ActiveRecord put ':id/variables/:key' do diff --git a/lib/api/concerns/packages/debian_package_endpoints.rb b/lib/api/concerns/packages/debian_package_endpoints.rb index 25c97932e31..45290cb3e44 100644 --- a/lib/api/concerns/packages/debian_package_endpoints.rb +++ b/lib/api/concerns/packages/debian_package_endpoints.rb @@ -13,7 +13,6 @@ module API component: ::Packages::Debian::COMPONENT_REGEX, architecture: ::Packages::Debian::ARCHITECTURE_REGEX }.freeze - LIST_PACKAGE = 'list_package' included do feature_category :package_registry @@ -41,8 +40,6 @@ module API package_file = distribution_from!(project).package_files.with_file_name(params[:file_name]).last! - track_debian_package_event 'pull_package' - present_package_file!(package_file) end @@ -73,22 +70,8 @@ module API no_content! # empty component files are not always persisted in DB end - track_debian_package_event LIST_PACKAGE - present_carrierwave_file!(component_file.file) end - - def track_debian_package_event(action) - if project_or_group.is_a?(Project) - project = project_or_group - namespace = project_or_group.namespace - else - project = nil - namespace = project_or_group - end - - track_package_event(action, :debian, project: project, namespace: namespace, user: current_user) - end end rescue_from ArgumentError do |e| @@ -146,7 +129,6 @@ module API get 'Release' do distribution = distribution_from!(project_or_group) - track_debian_package_event LIST_PACKAGE present_carrierwave_file!(distribution.file) end @@ -166,7 +148,6 @@ module API get 'InRelease' do distribution = distribution_from!(project_or_group) - track_debian_package_event LIST_PACKAGE present_carrierwave_file!(distribution.signed_file) end diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb index 74ad3bb296f..ec20440f013 100644 --- a/lib/api/concerns/packages/npm_endpoints.rb +++ b/lib/api/concerns/packages/npm_endpoints.rb @@ -21,12 +21,18 @@ module API included do helpers ::API::Helpers::Packages::DependencyProxyHelpers + rescue_from ActiveRecord::RecordInvalid do |e| + render_structured_api_error!({ message: e.message, error: e.message }, 400) + end + before do require_packages_enabled! authenticate_non_get! end helpers do + include Gitlab::Utils::StrongMemoize + params :package_name do requires :package_name, type: String, file_path: true, desc: 'Package name', documentation: { example: 'mypackage' } @@ -51,6 +57,12 @@ module API def generate_metadata_service(packages) ::Packages::Npm::GenerateMetadataService.new(params[:package_name], packages) end + + def metadata_cache + ::Packages::Npm::MetadataCache + .find_by_package_name_and_project_id(params[:package_name], project.id) + end + strong_memoize_attr :metadata_cache end params do @@ -80,7 +92,7 @@ module API packages = ::Packages::Npm::PackageFinder.new(package_name, project: project) .execute - not_found! if packages.empty? + not_found!('Package') if packages.empty? track_package_event(:list_tags, :npm, project: project, namespace: project.namespace) @@ -122,6 +134,8 @@ module API track_package_event(:create_tag, :npm, project: project, namespace: project.namespace) + enqueue_sync_metadata_cache_worker(project, package_name) + ::Packages::Npm::CreateTagService.new(package, tag).execute no_content! @@ -156,6 +170,8 @@ module API track_package_event(:delete_tag, :npm, project: project, namespace: project.namespace) + enqueue_sync_metadata_cache_worker(project, package_name) + ::Packages::RemoveTagService.new(package_tag).execute no_content! @@ -202,6 +218,17 @@ module API not_found!('Packages') if packages.empty? + if endpoint_scope == :project && Feature.enabled?(:npm_metadata_cache, project) + if metadata_cache&.file&.exists? + metadata_cache.touch_last_downloaded_at + present_carrierwave_file!(metadata_cache.file) + + break + end + + enqueue_sync_metadata_cache_worker(project, package_name) + end + present ::Packages::Npm::PackagePresenter.new(generate_metadata_service(packages).execute), with: ::API::Entities::NpmPackage end diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index e1531847b87..a2b2d781797 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -111,8 +111,6 @@ module API ::Packages::Debian::CreatePackageFileService.new(package: package, current_user: current_user, params: file_params).execute - track_debian_package_event 'push_package' - created! rescue ObjectStorage::RemoteStoreError => e Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id }) diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index 3a0eea677b8..8161c2b850f 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -68,6 +68,7 @@ module API desc: 'The status to filter deployments by. One of `created`, `running`, `success`, `failed`, `canceled`, or `blocked`' end + route_setting :authentication, job_token_allowed: true get ':id/deployments' do authorize! :read_deployment, user_project @@ -92,6 +93,7 @@ module API params do requires :deployment_id, type: Integer, desc: 'The ID of the deployment' end + route_setting :authentication, job_token_allowed: true get ':id/deployments/:deployment_id' do authorize! :read_deployment, user_project @@ -129,9 +131,10 @@ module API requires :status, type: String, - desc: 'The status to filter deployments by. One of `running`, `success`, `failed`, or `canceled`', + desc: 'The status of the deployment that is created. One of `running`, `success`, `failed`, or `canceled`', values: %w[running success failed canceled] end + route_setting :authentication, job_token_allowed: true post ':id/deployments' do authorize!(:create_deployment, user_project) authorize!(:create_environment, user_project) @@ -175,6 +178,7 @@ module API desc: 'The new status of the deployment. One of `running`, `success`, `failed`, or `canceled`', values: %w[running success failed canceled] end + route_setting :authentication, job_token_allowed: true put ':id/deployments/:deployment_id' do authorize!(:read_deployment, user_project) @@ -207,6 +211,7 @@ module API params do requires :deployment_id, type: Integer, desc: 'The ID of the deployment' end + route_setting :authentication, job_token_allowed: true delete ':id/deployments/:deployment_id' do deployment = user_project.deployments.find(params[:deployment_id]) @@ -240,7 +245,7 @@ module API use :merge_requests_base_params end - + route_setting :authentication, job_token_allowed: true get ':id/deployments/:deployment_id/merge_requests' do authorize! :read_deployment, user_project diff --git a/lib/api/entities/blob.rb b/lib/api/entities/blob.rb index 12700d99865..b4206679ac9 100644 --- a/lib/api/entities/blob.rb +++ b/lib/api/entities/blob.rb @@ -15,6 +15,13 @@ module API expose :ref expose :startline expose :project_id + expose :group_id, if: ->(object) { object.is_a?(Gitlab::Search::FoundWikiPage) } + + private + + def group_id + object.group&.id + end end end end diff --git a/lib/api/entities/bulk_imports/export_batch_status.rb b/lib/api/entities/bulk_imports/export_batch_status.rb new file mode 100644 index 00000000000..6e4a2fc8f93 --- /dev/null +++ b/lib/api/entities/bulk_imports/export_batch_status.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module API + module Entities + module BulkImports + class ExportBatchStatus < Grape::Entity + expose :status, documentation: { type: 'string', example: 'started', values: %w[started finished failed] } + expose :batch_number, documentation: { type: 'integer', example: 1 } + expose :objects_count, documentation: { type: 'integer', example: 100 } + expose :error, documentation: { type: 'string', example: 'Error message' } + expose :updated_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + end + end + end +end diff --git a/lib/api/entities/bulk_imports/export_status.rb b/lib/api/entities/bulk_imports/export_status.rb index fee983c6fd8..1e5ee6ec210 100644 --- a/lib/api/entities/bulk_imports/export_status.rb +++ b/lib/api/entities/bulk_imports/export_status.rb @@ -8,6 +8,10 @@ module API expose :status, documentation: { type: 'string', example: 'started', values: %w[started finished failed] } expose :error, documentation: { type: 'string', example: 'Error message' } expose :updated_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :batched, documentation: { type: 'boolean', example: true } + expose :batches_count, documentation: { type: 'integer', example: 2 } + expose :total_objects_count, documentation: { type: 'integer', example: 100 } + expose :batches, if: ->(export, _options) { export.batched? }, using: ExportBatchStatus end end end diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb index 9361709b6ed..441e1dc1117 100644 --- a/lib/api/entities/ci/runner.rb +++ b/lib/api/entities/ci/runner.rb @@ -6,6 +6,7 @@ module API class Runner < Grape::Entity expose :id, documentation: { type: 'integer', example: 8 } expose :description, documentation: { type: 'string', example: 'test-1-20150125' } + # TODO: return null in 17.0 and remove in v5 https://gitlab.com/gitlab-org/gitlab/-/issues/415159 expose :ip_address, documentation: { type: 'string', example: '127.0.0.1' } # TODO Remove in v5 in favor of `paused` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/375709 expose :active, documentation: { type: 'boolean', example: true } diff --git a/lib/api/entities/ci/variable.rb b/lib/api/entities/ci/variable.rb index 47597cb77be..4336f82fb5e 100644 --- a/lib/api/entities/ci/variable.rb +++ b/lib/api/entities/ci/variable.rb @@ -14,6 +14,8 @@ module API expose :raw?, as: :raw, if: -> (entity, _) { entity.respond_to?(:raw?) }, documentation: { type: 'boolean' } expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) }, documentation: { type: 'string', example: '*' } + expose :description, if: -> (entity, _) { entity.respond_to?(:description) }, + documentation: { type: 'string', example: 'This variable is being used for ...' } end end end diff --git a/lib/api/entities/dictionary/table.rb b/lib/api/entities/dictionary/table.rb index 8d4e3fb959d..93e82d34b14 100644 --- a/lib/api/entities/dictionary/table.rb +++ b/lib/api/entities/dictionary/table.rb @@ -5,7 +5,7 @@ module API module Dictionary class Table < Grape::Entity expose :table_name, documentation: { type: :string, example: 'users' } - expose :feature_categories, documentation: { type: :array, example: ['database'] } + expose :feature_categories, documentation: { type: :string, is_array: true, example: 'database' } end end end diff --git a/lib/api/entities/group.rb b/lib/api/entities/group.rb index 246fb819890..9296617dac9 100644 --- a/lib/api/entities/group.rb +++ b/lib/api/entities/group.rb @@ -21,6 +21,7 @@ module API expose :full_name, :full_path expose :created_at expose :parent_id + expose :shared_runners_setting expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes diff --git a/lib/api/entities/plan_limit.rb b/lib/api/entities/plan_limit.rb index 753c595d65f..27b24a60305 100644 --- a/lib/api/entities/plan_limit.rb +++ b/lib/api/entities/plan_limit.rb @@ -14,6 +14,11 @@ module API expose :enforcement_limit, documentation: { type: 'integer', example: 15000 } expose :generic_packages_max_file_size, documentation: { type: 'integer', example: 5368709120 } expose :helm_max_file_size, documentation: { type: 'integer', example: 5242880 } + expose :limits_history, documentation: { + type: 'object', + example: '{"enforcement_limit"=>[{"timestamp"=>1686909124, "user_id"=>1, "username"=>"x", "value"=>5}], + "notification_limit"=>[{"timestamp"=>1686909124, "user_id"=>2, "username"=>"y", "value"=>7}]}' + } expose :maven_max_file_size, documentation: { type: 'integer', example: 3221225472 } expose :notification_limit, documentation: { type: 'integer', example: 15000 } expose :npm_max_file_size, documentation: { type: 'integer', example: 524288000 } diff --git a/lib/api/entities/project_hook.rb b/lib/api/entities/project_hook.rb index bffb057abed..b85d2747226 100644 --- a/lib/api/entities/project_hook.rb +++ b/lib/api/entities/project_hook.rb @@ -14,6 +14,7 @@ module API expose :job_events, documentation: { type: 'boolean' } expose :releases_events, documentation: { type: 'boolean' } expose :push_events_branch_filter, documentation: { type: 'string', example: 'my-branch-*' } + expose :emoji_events, documentation: { type: 'boolean' } end end end diff --git a/lib/api/entities/protected_ref_access.rb b/lib/api/entities/protected_ref_access.rb index 28e0ef540d5..3cac91ccddc 100644 --- a/lib/api/entities/protected_ref_access.rb +++ b/lib/api/entities/protected_ref_access.rb @@ -5,12 +5,9 @@ module API class ProtectedRefAccess < Grape::Entity expose :id, documentation: { type: 'integer', example: 1 } expose :access_level, documentation: { type: 'integer', example: 40 } - expose :access_level_description, - documentation: { type: 'string', example: 'Maintainers' } do |protected_ref_access| - protected_ref_access.humanize - end + expose :humanize, as: :access_level_description, documentation: { type: 'string', example: 'Maintainers' } expose :deploy_key_id, documentation: { type: 'integer', example: 1 }, - if: ->(access) { access.has_attribute?(:deploy_key_id) && access.deploy_key_id } + if: ->(access) { access.has_attribute?(:deploy_key_id) } end end end diff --git a/lib/api/environments.rb b/lib/api/environments.rb index bb261079d2a..b94391359ed 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -38,6 +38,7 @@ module API desc: 'List all environments that match a specific state. Accepted values: `available`, `stopping`, or `stopped`. If no state value given, returns all environments' mutually_exclusive :name, :search, message: 'cannot be used together' end + route_setting :authentication, job_token_allowed: true get ':id/environments' do authorize! :read_environment, user_project @@ -66,6 +67,7 @@ module API optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true } optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`' end + route_setting :authentication, job_token_allowed: true post ':id/environments' do authorize! :create_environment, user_project @@ -94,6 +96,7 @@ module API optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true } optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`' end + route_setting :authentication, job_token_allowed: true put ':id/environments/:environment_id' do authorize! :update_environment, user_project @@ -126,6 +129,7 @@ module API optional :limit, type: Integer, desc: "Maximum number of environments to delete. Defaults to 100", default: 100, values: 1..1000 optional :dry_run, type: Boolean, desc: "Defaults to true for safety reasons. It performs a dry run where no actual deletion will be performed. Set to false to actually delete the environment", default: true end + route_setting :authentication, job_token_allowed: true delete ":id/environments/review_apps" do authorize! :read_environment, user_project @@ -156,6 +160,7 @@ module API params do requires :environment_id, type: Integer, desc: 'The ID of the environment' end + route_setting :authentication, job_token_allowed: true delete ':id/environments/:environment_id' do authorize! :read_environment, user_project @@ -178,6 +183,7 @@ module API requires :environment_id, type: Integer, desc: 'The ID of the environment' optional :force, type: Boolean, default: false, desc: 'Force environment to stop without executing `on_stop` actions' end + route_setting :authentication, job_token_allowed: true post ':id/environments/:environment_id/stop' do authorize! :read_environment, user_project @@ -202,6 +208,7 @@ module API type: DateTime, desc: 'Stop all environments that were last modified or deployed to before this date.' end + route_setting :authentication, job_token_allowed: true post ':id/environments/stop_stale' do authorize! :stop_environment, user_project @@ -229,6 +236,7 @@ module API params do requires :environment_id, type: Integer, desc: 'The ID of the environment' end + route_setting :authentication, job_token_allowed: true get ':id/environments/:environment_id' do authorize! :read_environment, user_project diff --git a/lib/api/files.rb b/lib/api/files.rb index 45e935d7ea2..c140cec658d 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -49,7 +49,14 @@ module API end def content_sha - cache_client.fetch("blob_content_sha256:#{user_project.full_path}:#{@blob.id}") do + cache_client.fetch( + "blob_content_sha256:#{user_project.full_path}:#{@blob.id}", + nil, + { + cache_identifier: 'API::Files#content_sha', + backing_resource: :gitaly + } + ) do @blob.load_all_data! Digest::SHA256.hexdigest(@blob.data) @@ -57,10 +64,8 @@ module API end def cache_client - Gitlab::Cache::Client.build_with_metadata( - cache_identifier: 'API::Files#content_sha', - feature_category: :source_code_management, - backing_resource: :gitaly + @cache_client ||= Gitlab::Cache::Client.new( + Gitlab::Cache::Metrics.new(Gitlab::Cache::Metadata.new(feature_category: :source_code_management)) ) end diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb index 37dfbfdb925..4cac707ff66 100644 --- a/lib/api/group_export.rb +++ b/lib/api/group_export.rb @@ -80,8 +80,13 @@ module API { code: 503, message: 'Service unavailable' } ] end + params do + optional :batched, type: Boolean, desc: 'Whether to export in batches' + end post ':id/export_relations' do - response = ::BulkImports::ExportService.new(portable: user_group, user: current_user).execute + response = ::BulkImports::ExportService + .new(portable: user_group, user: current_user, batched: params[:batched]) + .execute if response.success? accepted! @@ -104,15 +109,32 @@ module API end params do requires :relation, type: String, desc: 'Group relation name' + optional :batched, type: Boolean, desc: 'Whether to download in batches' + optional :batch_number, type: Integer, desc: 'Batch number to download' + + all_or_none_of :batched, :batch_number end get ':id/export_relations/download' do export = user_group.bulk_import_exports.find_by_relation(params[:relation]) - file = export&.upload&.export_file - if file - present_carrierwave_file!(file) + break render_api_error!('Export not found', 404) unless export + + if params[:batched] + batch = export.batches.find_by_batch_number(params[:batch_number]) + batch_file = batch&.upload&.export_file + + break render_api_error!('Export is not batched', 400) unless export.batched? + break render_api_error!('Batch not found', 404) unless batch + break render_api_error!('Batch file not found', 404) unless batch_file + + present_carrierwave_file!(batch_file) else - render_api_error!('404 Not found', 404) + file = export&.upload&.export_file + + break render_api_error!('Export is batched', 400) if export.batched? + break render_api_error!('Export file not found', 404) unless file + + present_carrierwave_file!(file) end end @@ -128,8 +150,19 @@ module API { code: 503, message: 'Service unavailable' } ] end + params do + optional :relation, type: String, desc: 'Group relation name' + end get ':id/export_relations/status' do - present user_group.bulk_import_exports, with: Entities::BulkImports::ExportStatus + if params[:relation] + export = user_group.bulk_import_exports.find_by_relation(params[:relation]) + + break render_api_error!('Export not found', 404) unless export + + present export, with: Entities::BulkImports::ExportStatus + else + present user_group.bulk_import_exports, with: Entities::BulkImports::ExportStatus + end end end end diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb index 295bee475c3..f320fa06394 100644 --- a/lib/api/group_variables.rb +++ b/lib/api/group_variables.rb @@ -59,6 +59,8 @@ module API optional :raw, type: String, desc: 'Whether the variable will be expanded' optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var' optional :environment_scope, type: String, desc: 'The environment scope of a variable' + optional :description, type: String, desc: 'The description of the variable' + use :optional_group_variable_params_ee end post ':id/variables' do @@ -94,6 +96,7 @@ module API optional :raw, type: String, desc: 'Whether the variable will be expanded' optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var' optional :environment_scope, type: String, desc: 'The environment scope of a variable' + optional :description, type: String, desc: 'The description of the variable' use :optional_group_variable_params_ee end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index df080c8e666..b616f1b35b3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -657,6 +657,19 @@ module API Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}") end + def track_event(event_name, user_id:, namespace_id: nil, project_id: nil) + return unless user_id.present? + + Gitlab::InternalEvents.track_event( + event_name, + user_id: user_id, + namespace_id: namespace_id, + project_id: project_id + ) + rescue StandardError => error + Gitlab::AppLogger.warn("Internal Event tracking event failed for event: #{event_name}, message: #{error.message}") + end + def order_by_similarity?(allow_unauthorized: true) params[:order_by] == 'similarity' && params[:search].present? && (allow_unauthorized || current_user.present?) end diff --git a/lib/api/helpers/custom_attributes.rb b/lib/api/helpers/custom_attributes.rb index 88208226c40..fc3f42f0d58 100644 --- a/lib/api/helpers/custom_attributes.rb +++ b/lib/api/helpers/custom_attributes.rb @@ -8,7 +8,7 @@ module API included do helpers do params :with_custom_attributes do - optional :with_custom_attributes, type: Boolean, default: false, desc: 'Include custom attributes in the response' + optional :with_custom_attributes, type: ::Grape::API::Boolean, default: false, desc: 'Include custom attributes in the response' optional :custom_attributes, type: Hash, desc: 'Filter with custom attributes' diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index 850cc61af2c..09dd69ef03b 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -41,7 +41,7 @@ module API { required: false, name: :notify_only_broken_pipelines, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Send notifications for broken pipelines' } ].freeze @@ -129,85 +129,85 @@ module API { required: false, name: :commit_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for commit_events' }, { required: false, name: :push_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for push_events' }, { required: false, name: :issues_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for issues_events' }, { required: false, name: :incident_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for incident_events' }, { required: false, name: :alert_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for alert_events' }, { required: false, name: :confidential_issues_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for confidential_issues_events' }, { required: false, name: :merge_requests_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for merge_requests_events' }, { required: false, name: :note_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for note_events' }, { required: false, name: :confidential_note_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for confidential_note_events' }, { required: false, name: :tag_push_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for tag_push_events' }, { required: false, name: :deployment_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for deployment_events' }, { required: false, name: :job_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for job_events' }, { required: false, name: :pipeline_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for pipeline_events' }, { required: false, name: :wiki_page_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable notifications for wiki_page_events' } ].freeze @@ -243,7 +243,7 @@ module API { required: false, name: :app_store_protected_refs, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Only enable for protected refs' } ], @@ -285,7 +285,7 @@ module API { required: false, name: :enable_ssl_verification, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable SSL verification' }, { @@ -343,7 +343,7 @@ module API { required: false, name: :enable_ssl_verification, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'DEPRECATED: This parameter has no effect since SSL verification will always be enabled' } ], @@ -417,7 +417,7 @@ module API { required: false, name: :archive_trace_events, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'When enabled, job logs will be collected by Datadog and shown along pipeline execution traces' }, { @@ -471,7 +471,7 @@ module API { required: false, name: :enable_ssl_verification, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable SSL verification' } ], @@ -485,13 +485,13 @@ module API { required: false, name: :disable_diffs, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Disable code diffs' }, { required: false, name: :send_from_committer_email, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Send from committer' }, { @@ -598,7 +598,7 @@ module API { required: false, name: :colorize_messages, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Colorize messages' } ], @@ -612,7 +612,7 @@ module API { required: false, name: :enable_ssl_verification, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable SSL verification' }, { @@ -668,7 +668,7 @@ module API { required: false, name: :jira_issue_transition_automatic, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable automatic issue transitions' }, { @@ -692,7 +692,7 @@ module API { required: false, name: :comment_on_event_enabled, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable comments inside Jira issues on each GitLab event (commit / merge request)' } ], @@ -750,13 +750,13 @@ module API { required: false, name: :notify_only_broken_pipelines, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Notify only broken pipelines' }, { required: false, name: :notify_only_default_branch, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Send notifications only for the default branch' }, { @@ -946,7 +946,7 @@ module API { required: false, name: :enable_ssl_verification, - type: Boolean, + type: ::Grape::API::Boolean, desc: 'Enable SSL verification' }, { diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index b3c79486465..a82aed507fd 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -29,6 +29,7 @@ module API # rubocop: disable CodeReuse/ActiveRecord def retrieve_members(source, params:, deep: false) members = deep ? find_all_members(source) : source_members(source).connected_to_user + members = members.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/417456") members = members.includes(:user) 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? diff --git a/lib/api/helpers/packages/maven.rb b/lib/api/helpers/packages/maven.rb new file mode 100644 index 00000000000..694a1ec6436 --- /dev/null +++ b/lib/api/helpers/packages/maven.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module API + module Helpers + module Packages + module Maven + extend Grape::API::Helpers + + params :path_and_file_name do + requires :path, + type: String, + desc: 'Package path', + documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' } + requires :file_name, + type: String, + desc: 'Package file name', + documentation: { example: 'mypkg-1.0-SNAPSHOT.jar' } + end + end + end + end +end diff --git a/lib/api/helpers/packages/npm.rb b/lib/api/helpers/packages/npm.rb index be7f57fda0c..a80122c5309 100644 --- a/lib/api/helpers/packages/npm.rb +++ b/lib/api/helpers/packages/npm.rb @@ -6,6 +6,7 @@ module API module Npm include Gitlab::Utils::StrongMemoize include ::API::Helpers::PackagesHelpers + extend ::Gitlab::Utils::Override NPM_ENDPOINT_REQUIREMENTS = { package_name: API::NO_SLASH_URL_PART_REGEX @@ -55,8 +56,7 @@ module API when :group finder = ::Packages::Npm::PackageFinder.new( params[:package_name], - namespace: group, - last_of_each_version: false + namespace: group ) finder.last&.project_id @@ -77,8 +77,7 @@ module API finder = ::Packages::Npm::PackageFinder.new( package_name, - namespace: namespace, - last_of_each_version: false + namespace: namespace ) finder.last&.project_id @@ -86,6 +85,12 @@ module API end strong_memoize_attr :project_id_or_nil + def enqueue_sync_metadata_cache_worker(project, package_name) + return unless Feature.enabled?(:npm_metadata_cache, project) + + ::Packages::Npm::CreateMetadataCacheWorker.perform_async(project.id, package_name) + end + private def top_namespace_from(package_name) @@ -101,6 +106,20 @@ module API group end strong_memoize_attr :group + + override :not_found! + def not_found!(resource = nil) + reason = "#{resource} not found" + message = "404 #{reason}".titleize + render_structured_api_error!({ message: message, error: reason }, 404) + end + + override :bad_request_missing_attribute! + def bad_request_missing_attribute!(attribute) + reason = "\"#{attribute}\" not given" + message = "400 Bad request - #{reason}" + render_structured_api_error!({ message: message, error: reason }, 400) + end end end end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index b96e8efba61..642963768f8 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -15,7 +15,6 @@ module API optional :auto_cancel_pending_pipelines, type: String, values: %w(disabled enabled), desc: 'Auto-cancel pending pipelines' optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`' optional :service_desk_enabled, type: Boolean, desc: 'Disable or enable the service desk' - optional :keep_latest_artifact, type: Boolean, desc: 'Indicates if the latest artifact should be kept for this project.' # TODO: remove in API v5, replaced by *_access_level optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled' @@ -71,7 +70,6 @@ module API optional :squash_commit_template, type: String, desc: 'Template used to create squash commit message' optional :issue_branch_template, type: String, desc: 'Template used to create a branch from an issue' 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' optional :auto_devops_deploy_strategy, type: String, values: %w(continuous manual timed_incremental), desc: 'Auto Deploy strategy' optional :autoclose_referenced_issues, type: Boolean, desc: 'Flag indication if referenced issues auto-closing is enabled' @@ -101,6 +99,8 @@ module API end params :optional_update_params_ce do + optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning' + optional :keep_latest_artifact, type: Boolean, desc: 'Indicates if the latest artifact should be kept for this project.' optional :ci_forward_deployment_enabled, type: Boolean, desc: 'Prevent older deployment jobs that are still pending' optional :ci_allow_fork_pipelines_to_run_in_parent_project, type: Boolean, desc: 'Allow fork merge request pipelines to run in parent project' optional :ci_separated_caches, type: Boolean, desc: 'Enable or disable separated caches based on branch protection.' @@ -205,9 +205,6 @@ module API def filter_attributes_using_license!(attrs) end - def filter_attributes_under_feature_flag!(attrs, project) - end - def validate_git_import_url!(import_url) return if import_url.blank? diff --git a/lib/api/helpers/remote_mirrors_helpers.rb b/lib/api/helpers/remote_mirrors_helpers.rb index efd81a5ac5a..2c00f7cdb14 100644 --- a/lib/api/helpers/remote_mirrors_helpers.rb +++ b/lib/api/helpers/remote_mirrors_helpers.rb @@ -18,7 +18,7 @@ module API use :mirror_branches_setting_ee end - def verify_mirror_branches_setting(attrs, project); end + def verify_mirror_branches_setting(attrs); end end end end diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb index 6550808a563..ab7ac6624a8 100644 --- a/lib/api/import_github.rb +++ b/lib/api/import_github.rb @@ -20,7 +20,10 @@ module API end def access_params - { github_access_token: params[:personal_access_token] } + { + github_access_token: params[:personal_access_token], + additional_access_tokens: params[:additional_access_tokens] + } end def client_options @@ -59,6 +62,11 @@ module API requires :target_namespace, type: String, allow_blank: false, desc: 'Namespace or group to import repository into' optional :github_hostname, type: String, desc: 'Custom GitHub enterprise hostname' optional :optional_stages, type: Hash, desc: 'Optional stages of import to be performed' + optional :additional_access_tokens, + type: Array[String], + coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: 'Additional list of personal access tokens', + documentation: { example: 'foo,bar' } end post 'import/github' do result = Import::GithubService.new(client, current_user, params).execute(access_params, provider) diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index 5592207c4b5..8783a8dd57c 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -29,21 +29,13 @@ module API end def gitaly_info(project) - shard = repo_type.repository_for(project).shard - { - address: Gitlab::GitalyClient.address(shard), - token: Gitlab::GitalyClient.token(shard), - features: Feature::Gitaly.server_feature_flags - } + gitaly_features = Feature::Gitaly.server_feature_flags + + Gitlab::GitalyClient.connection_data(project.repository_storage).merge(features: gitaly_features) end def gitaly_repository(project) - { - storage_name: project.repository_storage, - relative_path: project.disk_path + '.git', - gl_repository: repo_type.identifier_for_container(project), - gl_project_path: repo_type.repository_for(project).full_path - } + project.repository.gitaly_repository.to_h end def check_feature_enabled @@ -61,7 +53,12 @@ module API end def increment_unique_events - events = params[:unique_counters]&.slice(:agent_users_using_ci_tunnel) + events = params[:unique_counters]&.slice( + :agent_users_using_ci_tunnel, + :k8s_api_proxy_requests_unique_users_via_ci_access, :k8s_api_proxy_requests_unique_agents_via_ci_access, + :k8s_api_proxy_requests_unique_users_via_user_access, :k8s_api_proxy_requests_unique_agents_via_user_access, + :flux_git_push_notified_unique_projects + ) events&.each do |event, entity_ids| increment_unique_values(event, entity_ids) @@ -69,7 +66,10 @@ module API end def increment_count_events - events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request, :flux_git_push_notifications_total) + events = params[:counters]&.slice( + :gitops_sync, :k8s_api_proxy_request, :flux_git_push_notifications_total, + :k8s_api_proxy_requests_via_ci_access, :k8s_api_proxy_requests_via_user_access + ) Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(events) end @@ -185,7 +185,7 @@ module API # Load agent agent = ::Clusters::Agent.find(params[:agent_id]) - unauthorized!('Feature disabled for agent') unless ::Gitlab::Kas::UserAccess.enabled_for?(agent) + unauthorized!('Feature disabled for agent') unless ::Gitlab::Kas::UserAccess.enabled? service_response = ::Clusters::Agents::AuthorizeProxyUserService.new(user, agent).execute render_api_error!(service_response[:message], service_response[:reason]) unless service_response.success? @@ -203,10 +203,17 @@ module API optional :gitops_sync, type: Integer, desc: 'The count to increment the gitops_sync metric by' optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request metric by' optional :flux_git_push_notifications_total, type: Integer, desc: 'The count to increment the flux_git_push_notifications_total metrics by' + optional :k8s_api_proxy_requests_via_ci_access, type: Integer, desc: 'The count to increment the k8s_api_proxy_requests_via_ci_access metric by' + optional :k8s_api_proxy_requests_via_user_access, type: Integer, desc: 'The count to increment the k8s_api_proxy_requests_via_user_access metric by' end optional :unique_counters, type: Hash do optional :agent_users_using_ci_tunnel, type: Array[Integer], desc: 'An array of user ids that have interacted with CI Tunnel' + optional :k8s_api_proxy_requests_unique_users_via_ci_access, type: Array[Integer], desc: 'An array of users that have interacted with the CI tunnel via `ci_access`' + optional :k8s_api_proxy_requests_unique_agents_via_ci_access, type: Array[Integer], desc: 'An array of agents that have interacted with the CI tunnel via `ci_access`' + optional :k8s_api_proxy_requests_unique_users_via_user_access, type: Array[Integer], desc: 'An array of users that have interacted with the CI tunnel via `user_access`' + optional :k8s_api_proxy_requests_unique_agents_via_user_access, type: Array[Integer], desc: 'An array of agents that have interacted with the CI tunnel via `user_access`' + optional :flux_git_push_notified_unique_projects, type: Array[Integer], desc: 'An array of projects that have been notified to reconcile their Flux workloads' end end post '/', feature_category: :deployment_management do diff --git a/lib/api/lint.rb b/lib/api/lint.rb index dee04b6bb00..71965fc05c9 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -4,34 +4,6 @@ module API class Lint < ::API::Base feature_category :pipeline_composition - helpers do - def can_lint_ci? - signup_unrestricted = Gitlab::CurrentSettings.signup_enabled? && !Gitlab::CurrentSettings.signup_limited? - internal_user = current_user.present? && !current_user.external? - is_developer = current_user.present? && current_user.projects.any? { |p| p.member?(current_user, Gitlab::Access::DEVELOPER) } - - signup_unrestricted || internal_user || is_developer - end - end - - namespace :ci do - desc 'REMOVED: Validates the .gitlab-ci.yml content' do - detail 'Checks if CI/CD YAML configuration is valid' - success code: 200, model: Entities::Ci::Lint::Result - tags %w[ci_lint] - end - params do - requires :content, type: String, desc: 'The CI/CD configuration content' - optional :include_merged_yaml, type: Boolean, desc: 'If the expanded CI/CD configuration should be included in the response' - optional :include_jobs, type: Boolean, desc: 'If the list of jobs should be included in the response. This is - false by default' - end - - post '/lint', urgency: :low do - render_api_error!('410 Gone', 410) - end - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Validates a CI YAML configuration with a namespace' do detail 'Checks if a project’s latest (HEAD of the project’s default branch) .gitlab-ci.yml configuration is diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index 5ef60ab0b94..08b172cd608 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -40,10 +40,7 @@ module API context[:skip_project_check] = true end - # Disable comments in markdown for IE browsers because comments in IE - # could allow script execution. - browser = Browser.new(headers['User-Agent']) - context[:allow_comments] = !browser.ie? + context[:allow_comments] = false present({ html: Banzai.render_and_post_process(params[:text], context) }, with: Entities::Markdown) end diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb index 241cd93f380..eccc55ed158 100644 --- a/lib/api/maven_packages.rb +++ b/lib/api/maven_packages.rb @@ -23,14 +23,10 @@ module API helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::Packages::DependencyProxyHelpers + helpers ::API::Helpers::Packages::Maven helpers ::API::Helpers::Packages::Maven::BasicAuthHelpers helpers do - params :path_and_file_name do - requires :path, type: String, desc: 'Package path', documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' } - requires :file_name, type: String, desc: 'Package file name', documentation: { example: 'mypkg-1.0-SNAPSHOT.jar' } - end - def path_exists?(path) return false if path.blank? diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index c29a7eee923..ff9d0e2c371 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -124,6 +124,10 @@ module API merge_requests.each { |mr| mr.check_mergeability(async: true) } end + def batch_process_mergeability_checks(merge_requests) + ::MergeRequests::MergeabilityCheckBatchService.new(merge_requests, current_user).execute + end + params :merge_requests_params do use :merge_requests_base_params use :optional_merge_requests_search_params @@ -177,8 +181,16 @@ module API get ":id/merge_requests", feature_category: :code_review_workflow, urgency: :low do validate_search_rate_limit! if declared_params[:search].present? merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true) + options = serializer_options_for(merge_requests).merge(group: user_group) + + if !options[:skip_merge_status_recheck] && ::Feature.enabled?(:batched_api_mergeability_checks, user_group) + batch_process_mergeability_checks(merge_requests) + + # NOTE: skipping individual mergeability checks in the presenter + options[:skip_merge_status_recheck] = true + end - present merge_requests, serializer_options_for(merge_requests).merge(group: user_group) + present merge_requests, options end end diff --git a/lib/api/ml_model_packages.rb b/lib/api/ml_model_packages.rb index fec72b03ffd..35d231d9fe1 100644 --- a/lib/api/ml_model_packages.rb +++ b/lib/api/ml_model_packages.rb @@ -6,7 +6,7 @@ module API include ::API::Helpers::Authentication ML_MODEL_PACKAGES_REQUIREMENTS = { - package_name: API::NO_SLASH_URL_PART_REGEX, + model_name: API::NO_SLASH_URL_PART_REGEX, file_name: API::NO_SLASH_URL_PART_REGEX }.freeze @@ -47,15 +47,15 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/packages/ml_models' do params do - requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.ml_model_name_regex, + requires :model_name, type: String, desc: 'Model name', regexp: Gitlab::Regex.ml_model_name_regex, file_path: true - requires :package_version, type: String, desc: 'Package version', + requires :model_version, type: String, desc: 'Model version', regexp: Gitlab::Regex.ml_model_version_regex requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.ml_model_file_name_regex, file_path: true optional :status, type: String, values: ALLOWED_STATUSES, desc: 'Package status' end - namespace ':package_name/*package_version/:file_name', requirements: ML_MODEL_PACKAGES_REQUIREMENTS do + namespace ':model_name/*model_version/:file_name', requirements: ML_MODEL_PACKAGES_REQUIREMENTS do desc 'Workhorse authorize model package file' do detail 'Introduced in GitLab 16.1' success code: 200 @@ -71,7 +71,7 @@ module API end desc 'Workhorse upload model package file' do - detail 'Introduced in GitLab 16.1' + detail 'Introduced in GitLab 16.2' success code: 201 failure [ { code: 401, message: 'Unauthorized' }, @@ -91,7 +91,12 @@ module API bad_request!('File is too large') if max_file_size_exceeded? - create_package_file_params = declared(params).merge(build: current_authenticated_job) + create_package_file_params = declared(params).merge( + build: current_authenticated_job, + package_name: params[:model_name], + package_version: params[:model_version] + ) + package_file = ::Packages::MlModel::CreatePackageFileService .new(project, current_user, create_package_file_params) .execute @@ -104,6 +109,26 @@ module API forbidden! end + + desc 'Download an ml_model package file' do + detail 'This feature was introduced in GitLab 16.2' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[ml_model_registry] + end + get do + authorize_read_package!(project) + + package = ::Packages::MlModel::PackageFinder.new(project) + .execute!(params[:model_name], params[:model_version]) + package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute! + + present_package_file!(package_file) + end end end end diff --git a/lib/api/npm_instance_packages.rb b/lib/api/npm_instance_packages.rb index 8215296b617..ea92818e76c 100644 --- a/lib/api/npm_instance_packages.rb +++ b/lib/api/npm_instance_packages.rb @@ -6,10 +6,6 @@ module API feature_category :package_registry urgency :low - rescue_from ActiveRecord::RecordInvalid do |e| - render_api_error!(e.message, 400) - end - helpers do def endpoint_scope :instance diff --git a/lib/api/npm_project_packages.rb b/lib/api/npm_project_packages.rb index 61409909b06..e1d0455b1e2 100644 --- a/lib/api/npm_project_packages.rb +++ b/lib/api/npm_project_packages.rb @@ -7,7 +7,7 @@ module API urgency :low rescue_from ActiveRecord::RecordInvalid do |e| - render_api_error!(e.message, 400) + render_structured_api_error!({ message: e.message, error: e.message }, 400) end helpers do @@ -78,8 +78,9 @@ module API .new(project, current_user, params.merge(build: current_authenticated_job)).execute if created_package[:status] == :error - render_api_error!(created_package[:message], created_package[:http_status]) + render_structured_api_error!({ message: created_package[:message], error: created_package[:message] }, created_package[:http_status]) else + enqueue_sync_metadata_cache_worker(project, created_package.name) track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, namespace: project.namespace) created_package end diff --git a/lib/api/package_files.rb b/lib/api/package_files.rb index 7ff49f326d9..6a02769519f 100644 --- a/lib/api/package_files.rb +++ b/lib/api/package_files.rb @@ -14,6 +14,7 @@ module API urgency :low helpers ::API::Helpers::PackagesHelpers + helpers ::API::Helpers::Packages::Npm params do requires :id, types: [String, Integer], desc: 'ID or URL-encoded path of the project' @@ -70,6 +71,8 @@ module API destroy_conditionally!(package_file) do |package_file| package_file.pending_destruction! + + enqueue_sync_metadata_cache_worker(user_project, package.name) if package.npm? end end end diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 19e5ed3f9e0..8a72ec051dc 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -127,8 +127,13 @@ module API ] tags ['project_export'] end + params do + optional :batched, type: Boolean, desc: 'Whether to export in batches' + end post ':id/export_relations' do - response = ::BulkImports::ExportService.new(portable: user_project, user: current_user).execute + response = ::BulkImports::ExportService + .new(portable: user_project, user: current_user, batched: params[:batched]) + .execute if response.success? accepted! @@ -152,19 +157,33 @@ module API produces %w[application/octet-stream application/json] end params do - requires :relation, - type: String, - project_portable: true, - desc: 'Project relation name' + requires :relation, type: String, project_portable: true, desc: 'Project relation name' + optional :batched, type: Boolean, desc: 'Whether to download in batches' + optional :batch_number, type: Integer, desc: 'Batch number to download' + + all_or_none_of :batched, :batch_number end get ':id/export_relations/download' do export = user_project.bulk_import_exports.find_by_relation(params[:relation]) - file = export&.upload&.export_file - if file - present_carrierwave_file!(file) + break render_api_error!('Export not found', 404) unless export + + if params[:batched] + batch = export.batches.find_by_batch_number(params[:batch_number]) + batch_file = batch&.upload&.export_file + + break render_api_error!('Export is not batched', 400) unless export.batched? + break render_api_error!('Batch not found', 404) unless batch + break render_api_error!('Batch file not found', 404) unless batch_file + + present_carrierwave_file!(batch_file) else - render_api_error!('404 Not found', 404) + file = export&.upload&.export_file + + break render_api_error!('Export is batched', 400) if export.batched? + break render_api_error!('Export file not found', 404) unless file + + present_carrierwave_file!(file) end end @@ -180,8 +199,19 @@ module API ] tags ['project_export'] end + params do + optional :relation, type: String, desc: 'Project relation name' + end get ':id/export_relations/status' do - present user_project.bulk_import_exports, with: Entities::BulkImports::ExportStatus + if params[:relation] + export = user_project.bulk_import_exports.find_by_relation(params[:relation]) + + break render_api_error!('Export not found', 404) unless export + + present export, with: Entities::BulkImports::ExportStatus + else + present user_project.bulk_import_exports, with: Entities::BulkImports::ExportStatus + end end end end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index db97f4988e1..c9cba397f5c 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -31,6 +31,7 @@ module API optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events" optional :deployment_events, type: Boolean, desc: "Trigger hook on deployment events" optional :releases_events, type: Boolean, desc: "Trigger hook on release events" + optional :emoji_events, type: Boolean, desc: "Trigger hook on emoji events" optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response" optional :push_events_branch_filter, type: String, desc: "Trigger hook on specified branch only" diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb index 43bd15931ef..2aa6858e41d 100644 --- a/lib/api/project_packages.rb +++ b/lib/api/project_packages.rb @@ -15,6 +15,7 @@ module API urgency :low helpers ::API::Helpers::PackagesHelpers + helpers ::API::Helpers::Packages::Npm helpers do def package strong_memoize(:package) do # rubocop:disable Gitlab/StrongMemoizeAttr @@ -133,6 +134,8 @@ module API destroy_conditionally!(package) do |package| ::Packages::MarkPackageForDestructionService.new(container: package, current_user: current_user).execute + + enqueue_sync_metadata_cache_worker(user_project, package.name) if package.npm? end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 7ec9f72e0b2..468f284f136 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -251,6 +251,28 @@ module API present_projects load_projects end + desc 'Get projects that a user has contributed to' do + success code: 200, model: Entities::BasicProjectDetails + failure [{ code: 404, message: '404 User Not Found' }] + tags %w[projects] + is_array true + end + params do + requires :user_id, type: String, desc: 'The ID or username of the user' + use :sort_params + use :pagination + + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + end + get ":user_id/contributed_projects", feature_category: :groups_and_projects, urgency: :low do + user = find_user(params[:user_id]) + not_found!('User') unless user + + contributed_projects = ContributedProjectsFinder.new(user).execute(current_user).joined(user) + present_projects contributed_projects + end + desc 'Get projects starred by a user' do success code: 200, model: Entities::BasicProjectDetails failure [{ code: 404, message: '404 User Not Found' }] @@ -534,7 +556,6 @@ module API attrs = translate_params_for_compatibility(attrs) attrs = add_import_params(attrs) filter_attributes_using_license!(attrs) - filter_attributes_under_feature_flag!(attrs, user_project) verify_update_project_attrs!(user_project, attrs) user_project.remove_avatar! if attrs.key?(:avatar) && attrs[:avatar].nil? diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb index c3c7d9370e0..5346476ed0d 100644 --- a/lib/api/remote_mirrors.rb +++ b/lib/api/remote_mirrors.rb @@ -67,7 +67,7 @@ module API end post ':id/remote_mirrors' do create_params = declared_params(include_missing: false) - verify_mirror_branches_setting(create_params, user_project) + verify_mirror_branches_setting(create_params) new_mirror = user_project.remote_mirrors.create(create_params) if new_mirror.persisted? @@ -99,7 +99,7 @@ module API mirror_params = declared_params(include_missing: false) mirror_params[:id] = mirror_params.delete(:mirror_id) - verify_mirror_branches_setting(mirror_params, user_project) + verify_mirror_branches_setting(mirror_params) update_params = { remote_mirrors_attributes: mirror_params } result = ::Projects::UpdateService diff --git a/lib/api/search.rb b/lib/api/search.rb index 954c3cd9f9e..b14fce13f5e 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -59,6 +59,8 @@ module API end def search(additional_params = {}) + return Kaminari.paginate_array([]) if @project.present? && !project_scope_allowed? + search_service = search_service(additional_params) if search_service.global_search? && !search_service.global_search_enabled_for_scope? forbidden!('Global Search is disabled for this scope') @@ -95,6 +97,10 @@ module API ) end + def project_scope_allowed? + ::Search::Navigation.new(user: current_user, project: @project).tab_enabled_for_project?(params[:scope].to_sym) + end + def snippets? %w(snippet_titles).include?(params[:scope]).to_s end @@ -108,9 +114,7 @@ module API end def verify_search_scope!(resource:) - # In EE we have additional validation requirements for searches. - # Defining this method here as a noop allows us to easily extend it in - # EE, without having to modify this file directly. + # no-op end def search_type(additional_params = {}) diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 5d20444cb54..b12ca48829b 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -203,6 +203,13 @@ module API optional :allow_runner_registration_token, type: Boolean, desc: 'Allow registering runners using a registration token' optional :ci_max_includes, type: Integer, desc: 'Maximum number of includes per pipeline' optional :security_policy_global_group_approvers_enabled, type: Boolean, desc: 'Query scan result policy approval groups globally' + optional :slack_app_enabled, type: Grape::API::Boolean, desc: 'Enable the GitLab for Slack app' + given slack_app_enabled: -> (val) { val } do + requires :slack_app_id, type: String, desc: 'The client ID of the GitLab for Slack app' + requires :slack_app_secret, type: String, desc: 'The client secret of the GitLab for Slack app. Used for authenticating OAuth requests from the app' + requires :slack_app_signing_secret, type: String, desc: 'The signing secret of the GitLab for Slack app. Used for authenticating API requests from the app' + requires :slack_app_verification_token, type: String, desc: 'The verification token of the GitLab for Slack app. This method of authentication is deprecated by Slack and used only for authenticating slash commands from the app' + end Gitlab::SSHPublicKey.supported_types.each do |type| optional :"#{type}_key_restriction", diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb index 3e2023d769f..0a343093c33 100644 --- a/lib/api/usage_data.rb +++ b/lib/api/usage_data.rb @@ -53,6 +53,38 @@ module API status :ok end + desc 'Track gitlab internal events' do + detail 'This feature was introduced in GitLab 16.2.' + success code: 200 + failure [ + { code: 403, message: 'Invalid CSRF token is provided' }, + { code: 404, message: 'Not found' } + ] + tags %w[usage_data] + end + params do + requires :event, type: String, desc: 'The event name that should be tracked', + documentation: { example: 'i_quickactions_page' } + optional :namespace_id, type: Integer, desc: 'Namespace ID', + documentation: { example: 1234 } + optional :project_id, type: Integer, desc: 'Project ID', + documentation: { example: 1234 } + end + post 'track_event', urgency: :low do + event_name = params[:event] + namespace_id = params[:namespace_id] + project_id = params[:project_id] + + track_event( + event_name, + user_id: current_user.id, + namespace_id: namespace_id, + project_id: project_id + ) + + status :ok + end + desc 'Get a list of all metric definitions' do detail 'This feature was introduced in GitLab 13.11.' success code: 200 diff --git a/lib/api/user_runners.rb b/lib/api/user_runners.rb new file mode 100644 index 00000000000..edbd0214bb8 --- /dev/null +++ b/lib/api/user_runners.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module API + class UserRunners < ::API::Base + include APIGuard + + resource :user do + before do + authenticate! + end + + allow_access_with_scope :create_runner, if: ->(request) { request.post? } + + desc 'Create a runner owned by currently authenticated user' do + detail 'Create a new runner' + success Entities::Ci::RunnerRegistrationDetails + failure [[400, 'Bad Request'], [403, 'Forbidden']] + tags %w[user runners] + end + params do + requires :runner_type, type: String, values: ::Ci::Runner.runner_types.keys, + desc: %q(Specifies the scope of the runner) + given runner_type: ->(runner_type) { runner_type == 'group_type' } do + requires :group_id, type: Integer, + desc: 'The ID of the group that the runner is created in', + documentation: { example: 1 } + end + given runner_type: ->(runner_type) { runner_type == 'project_type' } do + requires :project_id, type: Integer, + desc: 'The ID of the project that the runner is created in', + documentation: { example: 1 } + end + optional :description, type: String, desc: %q(Description of the runner) + optional :maintenance_note, type: String, + desc: %q(Free-form maintenance notes for the runner (1024 characters)) + optional :paused, type: Boolean, desc: 'Specifies if the runner should ignore new jobs (defaults to false)' + optional :locked, type: Boolean, + desc: 'Specifies if the runner should be locked for the current project (defaults to false)' + optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, + desc: 'The access level of the runner' + optional :run_untagged, type: Boolean, + desc: 'Specifies if the runner should handle untagged jobs (defaults to true)' + optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: %q(A list of runner tags) + optional :maximum_timeout, type: Integer, + desc: 'Maximum timeout that limits the amount of time (in seconds) that runners can run jobs' + end + post 'runners', urgency: :low, feature_category: :runner_fleet do + attributes = attributes_for_keys( + %i[runner_type group_id project_id description maintenance_note paused locked run_untagged tag_list + access_level maximum_timeout] + ) + + case attributes[:runner_type] + when 'group_type' + attributes[:scope] = ::Group.find_by_id(attributes.delete(:group_id)) + when 'project_type' + attributes[:scope] = ::Project.find_by_id(attributes.delete(:project_id)) + end + + result = ::Ci::Runners::CreateRunnerService.new(user: current_user, params: attributes).execute + if result.error? + message = result.errors.to_sentence + forbidden!(message) if result.reason == :forbidden + bad_request!(message) + end + + present result.payload[:runner], with: Entities::Ci::RunnerRegistrationDetails + end + end + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb index ff36a4cfe95..fff0e9fee06 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -6,7 +6,7 @@ module API include APIGuard include Helpers::CustomAttributes - allow_access_with_scope :read_user, if: -> (request) { request.get? || request.head? } + allow_access_with_scope :read_user, if: ->(request) { request.get? || request.head? } feature_category :user_profile, %w[ @@ -134,7 +134,7 @@ module API entity = current_user&.can_read_all_resources? ? Entities::UserWithAdmin : Entities::UserBasic if entity == Entities::UserWithAdmin - users = users.preload(:identities, :webauthn_registrations, :namespace, :followers, :followees, :user_preference) + users = users.preload(:identities, :webauthn_registrations, :namespace, :followers, :followees, :user_preference, :user_detail) end users, options = with_custom_attributes(users, { with: entity, current_user: current_user }) @@ -1369,63 +1369,6 @@ module API get 'status', feature_category: :user_profile do present current_user.status || {}, with: Entities::UserStatus end - - desc 'Create a runner owned by currently authenticated user' do - detail 'Create a new runner' - success Entities::Ci::RunnerRegistrationDetails - failure [[400, 'Bad Request'], [403, 'Forbidden']] - tags %w[user runners] - end - params do - requires :runner_type, type: String, values: ::Ci::Runner.runner_types.keys, - desc: %q(Specifies the scope of the runner) - given runner_type: ->(runner_type) { runner_type == 'group_type' } do - requires :group_id, type: Integer, - desc: 'The ID of the group that the runner is created in', - documentation: { example: 1 } - end - given runner_type: ->(runner_type) { runner_type == 'project_type' } do - requires :project_id, type: Integer, - desc: 'The ID of the project that the runner is created in', - documentation: { example: 1 } - end - optional :description, type: String, desc: %q(Description of the runner) - optional :maintenance_note, type: String, - desc: %q(Free-form maintenance notes for the runner (1024 characters)) - optional :paused, type: Boolean, desc: 'Specifies if the runner should ignore new jobs (defaults to false)' - optional :locked, type: Boolean, - desc: 'Specifies if the runner should be locked for the current project (defaults to false)' - optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, - desc: 'The access level of the runner' - optional :run_untagged, type: Boolean, - desc: 'Specifies if the runner should handle untagged jobs (defaults to true)' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, - desc: %q(A list of runner tags) - optional :maximum_timeout, type: Integer, - desc: 'Maximum timeout that limits the amount of time (in seconds) that runners can run jobs' - end - post 'runners', urgency: :low, feature_category: :runner_fleet do - attributes = attributes_for_keys( - %i[runner_type group_id project_id description maintenance_note paused locked run_untagged tag_list - access_level maximum_timeout] - ) - - case attributes[:runner_type] - when 'group_type' - attributes[:scope] = ::Group.find_by_id(attributes.delete(:group_id)) - when 'project_type' - attributes[:scope] = ::Project.find_by_id(attributes.delete(:project_id)) - end - - result = ::Ci::Runners::CreateRunnerService.new(user: current_user, params: attributes).execute - if result.error? - message = result.errors.to_sentence - forbidden!(message) if result.reason == :forbidden - bad_request!(message) - end - - present result.payload[:runner], with: Entities::Ci::RunnerRegistrationDetails - end end end end |