summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-20 09:40:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-20 09:40:42 +0000
commitee664acb356f8123f4f6b00b73c1e1cf0866c7fb (patch)
treef8479f94a28f66654c6a4f6fb99bad6b4e86a40e /lib/api
parent62f7d5c5b69180e82ae8196b7b429eeffc8e7b4f (diff)
downloadgitlab-ce-ee664acb356f8123f4f6b00b73c1e1cf0866c7fb.tar.gz
Add latest changes from gitlab-org/gitlab@15-5-stable-eev15.5.0-rc42
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/access_requests.rb6
-rw-r--r--lib/api/admin/batched_background_migrations.rb10
-rw-r--r--lib/api/alert_management_alerts.rb3
-rw-r--r--lib/api/api.rb12
-rw-r--r--lib/api/applications.rb3
-rw-r--r--lib/api/badges.rb1
-rw-r--r--lib/api/branches.rb8
-rw-r--r--lib/api/bulk_imports.rb12
-rw-r--r--lib/api/ci/job_artifacts.rb4
-rw-r--r--lib/api/ci/resource_groups.rb19
-rw-r--r--lib/api/ci/runner.rb2
-rw-r--r--lib/api/ci/secure_files.rb4
-rw-r--r--lib/api/ci/variables.rb11
-rw-r--r--lib/api/commit_statuses.rb4
-rw-r--r--lib/api/concerns/packages/npm_endpoints.rb7
-rw-r--r--lib/api/debian_project_packages.rb6
-rw-r--r--lib/api/deploy_keys.rb4
-rw-r--r--lib/api/entities/bulk_imports/entity_failure.rb9
-rw-r--r--lib/api/entities/ci/job_basic.rb2
-rw-r--r--lib/api/entities/ci/runner.rb4
-rw-r--r--lib/api/entities/license.rb1
-rw-r--r--lib/api/entities/license_basic.rb8
-rw-r--r--lib/api/entities/merge_request_approvals.rb2
-rw-r--r--lib/api/entities/metadata.rb15
-rw-r--r--lib/api/entities/ml/mlflow/experiment.rb20
-rw-r--r--lib/api/entities/ml/mlflow/get_experiment.rb13
-rw-r--r--lib/api/entities/ml/mlflow/list_experiment.rb13
-rw-r--r--lib/api/entities/ml/mlflow/metric.rb16
-rw-r--r--lib/api/entities/ml/mlflow/run.rb5
-rw-r--r--lib/api/entities/ml/mlflow/run_param.rb14
-rw-r--r--lib/api/entities/ml/mlflow/update_run.rb2
-rw-r--r--lib/api/entities/project.rb3
-rw-r--r--lib/api/entities/user_with_admin.rb1
-rw-r--r--lib/api/environments.rb4
-rw-r--r--lib/api/features.rb62
-rw-r--r--lib/api/generic_packages.rb12
-rw-r--r--lib/api/group_import.rb2
-rw-r--r--lib/api/groups.rb6
-rw-r--r--lib/api/helm_packages.rb14
-rw-r--r--lib/api/helpers.rb54
-rw-r--r--lib/api/helpers/groups_helpers.rb2
-rw-r--r--lib/api/helpers/open_api.rb19
-rw-r--r--lib/api/helpers/packages/basic_auth_helpers.rb22
-rw-r--r--lib/api/helpers/packages/dependency_proxy_helpers.rb18
-rw-r--r--lib/api/helpers/personal_access_tokens_helpers.rb13
-rw-r--r--lib/api/helpers/projects_helpers.rb6
-rw-r--r--lib/api/import_github.rb16
-rw-r--r--lib/api/internal/pages.rb3
-rw-r--r--lib/api/issues.rb18
-rw-r--r--lib/api/maven_packages.rb8
-rw-r--r--lib/api/merge_requests.rb3
-rw-r--r--lib/api/metadata.rb73
-rw-r--r--lib/api/ml/mlflow.rb191
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/pages_domains.rb12
-rw-r--r--lib/api/personal_access_tokens.rb10
-rw-r--r--lib/api/personal_access_tokens/self_information.rb (renamed from lib/api/personal_access_tokens/self_revocation.rb)10
-rw-r--r--lib/api/project_export.rb3
-rw-r--r--lib/api/project_import.rb2
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/pypi_packages.rb7
-rw-r--r--lib/api/search.rb10
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/snippets.rb5
-rw-r--r--lib/api/support/git_access_actor.rb2
-rw-r--r--lib/api/templates.rb24
-rw-r--r--lib/api/todos.rb26
-rw-r--r--lib/api/users.rb11
-rw-r--r--lib/api/version.rb34
-rw-r--r--lib/api/wikis.rb2
70 files changed, 661 insertions, 293 deletions
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index e6ce62a1c6e..74f6515f07f 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -12,7 +12,8 @@ module API
%w[group project].each do |source_type|
params do
- requires :id, type: String, desc: "The #{source_type} ID"
+ 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 access requests for a #{source_type}." do
@@ -54,7 +55,8 @@ module API
end
params do
requires :user_id, type: Integer, desc: 'The user ID of the access requester'
- optional :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)'
+ optional :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, the Developer role)',
+ default: 30
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/access_requests/:user_id/approve' do
diff --git a/lib/api/admin/batched_background_migrations.rb b/lib/api/admin/batched_background_migrations.rb
index 675f3365bd3..e8cc08a23be 100644
--- a/lib/api/admin/batched_background_migrations.rb
+++ b/lib/api/admin/batched_background_migrations.rb
@@ -61,6 +61,11 @@ module API
end
put do
Gitlab::Database::SharedModel.using_connection(base_model.connection) do
+ unless batched_background_migration.paused?
+ msg = 'You can resume only `paused` batched background migrations.'
+ render_api_error!(msg, 422)
+ end
+
batched_background_migration.execute!
present_entity(batched_background_migration)
end
@@ -81,6 +86,11 @@ module API
end
put do
Gitlab::Database::SharedModel.using_connection(base_model.connection) do
+ unless batched_background_migration.active?
+ msg = 'You can pause only `active` batched background migrations.'
+ render_api_error!(msg, 422)
+ end
+
batched_background_migration.pause!
present_entity(batched_background_migration)
end
diff --git a/lib/api/alert_management_alerts.rb b/lib/api/alert_management_alerts.rb
index bbb7e7280c9..f03f133f6f7 100644
--- a/lib/api/alert_management_alerts.rb
+++ b/lib/api/alert_management_alerts.rb
@@ -32,7 +32,8 @@ module API
success Entities::MetricImage
end
params do
- requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded'
+ requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded',
+ documentation: { type: 'file' }
optional :url, type: String, desc: 'The url to view more metric info'
optional :url_text, type: String, desc: 'A description of the image or URL'
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 443bf1d649a..933c3f69075 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -3,6 +3,7 @@
module API
class API < ::API::Base
include APIGuard
+ include Helpers::OpenApi
LOG_FILENAME = Rails.root.join("log", "api_json.log")
@@ -165,6 +166,13 @@ module API
::Users::ActivityService.new(@current_user).execute
end
+ # Mount endpoints to include in the OpenAPI V2 documentation here
+ namespace do
+ mount ::API::Metadata
+
+ add_open_api_documentation!
+ end
+
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::Admin::BatchedBackgroundMigrations
@@ -250,7 +258,6 @@ module API
mount ::API::MergeRequestApprovals
mount ::API::MergeRequestDiffs
mount ::API::MergeRequests
- mount ::API::Metadata
mount ::API::Metrics::Dashboard::Annotations
mount ::API::Metrics::UserStarredDashboards
mount ::API::Namespaces
@@ -263,7 +270,7 @@ module API
mount ::API::PackageFiles
mount ::API::Pages
mount ::API::PagesDomains
- mount ::API::PersonalAccessTokens::SelfRevocation
+ mount ::API::PersonalAccessTokens::SelfInformation
mount ::API::PersonalAccessTokens
mount ::API::ProjectClusters
mount ::API::ProjectContainerRepositories
@@ -316,7 +323,6 @@ module API
mount ::API::UsageDataQueries
mount ::API::UserCounts
mount ::API::Users
- mount ::API::Version
mount ::API::Wikis
mount ::API::Ml::Mlflow
end
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
index 70488621f33..4048215160f 100644
--- a/lib/api/applications.rb
+++ b/lib/api/applications.rb
@@ -41,6 +41,9 @@ module API
end
desc 'Delete an application'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the application (not the application_id)'
+ end
delete ':id' do
application = ApplicationsFinder.new(params).execute
break not_found!('Application') unless application
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
index f969eec8431..0a3f247ffd6 100644
--- a/lib/api/badges.rb
+++ b/lib/api/badges.rb
@@ -31,6 +31,7 @@ module API
end
params do
use :pagination
+ optional :name, type: String, desc: 'Name for the badge'
end
get ":id/badges", urgency: :low do
source = find_source(source_type, params[:id])
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 5588818cbaf..7e6b0214c03 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -52,19 +52,13 @@ module API
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
- expiry_time = if Feature.enabled?(:increase_branch_cache_expiry, type: :ops)
- 60.minutes
- else
- 10.minutes
- end
-
present_cached(
branches,
with: Entities::Branch,
current_user: current_user,
project: user_project,
merged_branch_names: merged_branch_names,
- expires_in: expiry_time,
+ expires_in: 60.minutes,
cache_context: -> (branch) { [current_user&.cache_key, merged_branch_names.include?(branch.name)] }
)
end
diff --git a/lib/api/bulk_imports.rb b/lib/api/bulk_imports.rb
index 2c6adc0f37b..c54632919be 100644
--- a/lib/api/bulk_imports.rb
+++ b/lib/api/bulk_imports.rb
@@ -32,11 +32,16 @@ module API
end
end
- before { authenticate! }
+ before do
+ not_found! unless ::BulkImports::Features.enabled?
+
+ authenticate!
+ end
resource :bulk_imports do
desc 'Start a new GitLab Migration' do
detail 'This feature was introduced in GitLab 14.2.'
+ success Entities::BulkImport
end
params do
requires :configuration, type: Hash, desc: 'The source GitLab instance configuration' do
@@ -83,6 +88,7 @@ module API
desc 'List all GitLab Migrations' do
detail 'This feature was introduced in GitLab 14.1.'
+ success Entities::BulkImport
end
params do
use :pagination
@@ -97,6 +103,7 @@ module API
desc "List all GitLab Migrations' entities" do
detail 'This feature was introduced in GitLab 14.1.'
+ success Entities::BulkImports::Entity
end
params do
use :pagination
@@ -116,6 +123,7 @@ module API
desc 'Get GitLab Migration details' do
detail 'This feature was introduced in GitLab 14.1.'
+ success Entities::BulkImport
end
params do
requires :import_id, type: Integer, desc: "The ID of user's GitLab Migration"
@@ -126,6 +134,7 @@ module API
desc "List GitLab Migration entities" do
detail 'This feature was introduced in GitLab 14.1.'
+ success Entities::BulkImports::Entity
end
params do
requires :import_id, type: Integer, desc: "The ID of user's GitLab Migration"
@@ -139,6 +148,7 @@ module API
desc 'Get GitLab Migration entity details' do
detail 'This feature was introduced in GitLab 14.1.'
+ success Entities::BulkImports::Entity
end
params do
requires :import_id, type: Integer, desc: "The ID of user's GitLab Migration"
diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb
index b3a0a9ef54a..37c7cc73c46 100644
--- a/lib/api/ci/job_artifacts.rb
+++ b/lib/api/ci/job_artifacts.rb
@@ -38,7 +38,7 @@ module API
latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
authorize_read_job_artifacts!(latest_build)
- present_artifacts_file!(latest_build.artifacts_file)
+ present_artifacts_file!(latest_build.artifacts_file, project: latest_build.project)
end
desc 'Download a specific file from artifacts archive from a ref' do
@@ -80,7 +80,7 @@ module API
build = find_build!(params[:job_id])
authorize_read_job_artifacts!(build)
- present_artifacts_file!(build.artifacts_file)
+ present_artifacts_file!(build.artifacts_file, project: build.project)
end
desc 'Download a specific file from artifacts archive' do
diff --git a/lib/api/ci/resource_groups.rb b/lib/api/ci/resource_groups.rb
index e3fd887475a..ea6d3cc8fd4 100644
--- a/lib/api/ci/resource_groups.rb
+++ b/lib/api/ci/resource_groups.rb
@@ -38,6 +38,25 @@ module API
present resource_group, with: Entities::Ci::ResourceGroup
end
+ desc 'List upcoming jobs of a resource group' do
+ success Entities::Ci::JobBasic
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the resource group'
+
+ use :pagination
+ end
+ get ':id/resource_groups/:key/upcoming_jobs' do
+ authorize! :read_resource_group, resource_group
+ authorize! :read_build, user_project
+
+ upcoming_processables = resource_group
+ .upcoming_processables
+ .preload(:user, pipeline: :project) # rubocop:disable CodeReuse/ActiveRecord
+
+ present paginate(upcoming_processables), with: Entities::Ci::JobBasic
+ end
+
desc 'Edit a resource group' do
success Entities::Ci::ResourceGroup
end
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index 9e4a700d0f3..2d2dcc544f9 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -332,7 +332,7 @@ module API
authenticate_job!(require_running: false)
end
- present_artifacts_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download])
+ present_artifacts_file!(current_job.artifacts_file, project: current_job.project, supports_direct_download: params[:direct_download])
end
end
end
diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb
index c1f47dd67ce..68431df203b 100644
--- a/lib/api/ci/secure_files.rb
+++ b/lib/api/ci/secure_files.rb
@@ -74,6 +74,10 @@ module API
file_too_large! unless secure_file.file.size < ::Ci::SecureFile::FILE_SIZE_LIMIT.to_i
if secure_file.save
+ if Feature.enabled?(:secure_files_metadata_parsers, user_project)
+ ::Ci::ParseSecureFileMetadataWorker.perform_async(secure_file.id) # rubocop:disable CodeReuse/Worker
+ end
+
present secure_file, with: Entities::Ci::SecureFile
else
render_validation_error!(secure_file)
diff --git a/lib/api/ci/variables.rb b/lib/api/ci/variables.rb
index f9707960b9d..c9e1d115d03 100644
--- a/lib/api/ci/variables.rb
+++ b/lib/api/ci/variables.rb
@@ -33,6 +33,9 @@ module API
end
params do
requires :key, type: String, desc: 'The key of the variable'
+ 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 the variable'
+ end
end
# rubocop: disable CodeReuse/ActiveRecord
get ':id/variables/:key', urgency: :low do
@@ -78,7 +81,9 @@ module API
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
- optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production'
+ 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 the variable'
+ end
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
@@ -104,7 +109,9 @@ module API
end
params do
requires :key, type: String, desc: 'The key of the variable'
- optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production'
+ 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 the variable'
+ end
end
# rubocop: disable CodeReuse/ActiveRecord
delete ':id/variables/:key' do
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 5a6d06dcdd9..7d8b58fd7b6 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -52,8 +52,8 @@ module API
optional :ref, type: String, desc: 'The ref'
optional :target_url, type: String, desc: 'The target URL to associate with this status'
optional :description, type: String, desc: 'A short description of the status'
- optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
- optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
+ optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"', documentation: { default: 'default' }
+ optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"', documentation: { default: 'default' }
optional :coverage, type: Float, desc: 'The total code coverage'
optional :pipeline_id, type: Integer, desc: 'An existing pipeline ID, when multiple pipelines on the same commit SHA have been triggered'
end
diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb
index d6e006df976..4cc680068b6 100644
--- a/lib/api/concerns/packages/npm_endpoints.rb
+++ b/lib/api/concerns/packages/npm_endpoints.rb
@@ -116,7 +116,12 @@ module API
redirect_request = project_or_nil.blank? || packages.empty?
- redirect_registry_request(redirect_request, :npm, package_name: package_name) do
+ redirect_registry_request(
+ forward_to_registry: redirect_request,
+ package_type: :npm,
+ target: project_or_nil,
+ package_name: package_name
+ ) do
authorize_read_package!(project)
not_found!('Packages') if packages.empty?
diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb
index 9dedc4390f7..03f0f97b805 100644
--- a/lib/api/debian_project_packages.rb
+++ b/lib/api/debian_project_packages.rb
@@ -81,11 +81,7 @@ module API
package = ::Packages::Debian::FindOrCreateIncomingService.new(authorized_user_project, current_user).execute
- package_file = ::Packages::Debian::CreatePackageFileService.new(package, file_params).execute
-
- if params['file_name'].end_with? '.changes'
- ::Packages::Debian::ProcessChangesWorker.perform_async(package_file.id, current_user.id) # rubocop:disable CodeReuse/Worker
- end
+ ::Packages::Debian::CreatePackageFileService.new(package: package, current_user: current_user, params: file_params).execute
created!
rescue ObjectStorage::RemoteStoreError => e
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index ca13db8701e..c53f4bca5a7 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -161,9 +161,7 @@ module API
end
end
- desc 'Delete deploy key for a project' do
- success Key
- end
+ desc 'Delete deploy key for a project'
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
diff --git a/lib/api/entities/bulk_imports/entity_failure.rb b/lib/api/entities/bulk_imports/entity_failure.rb
index a3dbe3280ee..56312745868 100644
--- a/lib/api/entities/bulk_imports/entity_failure.rb
+++ b/lib/api/entities/bulk_imports/entity_failure.rb
@@ -4,11 +4,16 @@ module API
module Entities
module BulkImports
class EntityFailure < Grape::Entity
- expose :pipeline_class
- expose :pipeline_step
+ expose :relation
+ expose :pipeline_step, as: :step
+ expose :exception_message do |failure|
+ ::Projects::ImportErrorFilter.filter_message(failure.exception_message.truncate(72))
+ end
expose :exception_class
expose :correlation_id_value
expose :created_at
+ expose :pipeline_class
+ expose :pipeline_step
end
end
end
diff --git a/lib/api/entities/ci/job_basic.rb b/lib/api/entities/ci/job_basic.rb
index 3d9318ec428..fb975475cf5 100644
--- a/lib/api/entities/ci/job_basic.rb
+++ b/lib/api/entities/ci/job_basic.rb
@@ -21,7 +21,7 @@ module API
expose :project do
expose :ci_job_token_scope_enabled do |job|
- job.project.ci_job_token_scope_enabled?
+ job.project.ci_outbound_job_token_scope_enabled?
end
end
end
diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb
index e29d55771f2..f034eb5c94c 100644
--- a/lib/api/entities/ci/runner.rb
+++ b/lib/api/entities/ci/runner.rb
@@ -7,7 +7,7 @@ module API
expose :id
expose :description
expose :ip_address
- expose :active # TODO Remove in %16.0 in favor of `paused` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/351109
+ expose :active # TODO Remove in v5 in favor of `paused` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/375709
expose :paused do |runner|
!runner.active
end
@@ -16,7 +16,7 @@ module API
expose :name
expose :online?, as: :online
# DEPRECATED
- # TODO Remove in %16.0 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648
+ # TODO Remove in v5 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/375709
expose :deprecated_rest_status, as: :status
end
end
diff --git a/lib/api/entities/license.rb b/lib/api/entities/license.rb
index d7a414344c1..8ecf8a430fe 100644
--- a/lib/api/entities/license.rb
+++ b/lib/api/entities/license.rb
@@ -2,6 +2,7 @@
module API
module Entities
+ # Serializes a Licensee::License
class License < Entities::LicenseBasic
expose :popular?, as: :popular
expose(:description) { |license| license.meta['description'] }
diff --git a/lib/api/entities/license_basic.rb b/lib/api/entities/license_basic.rb
index 08af68785a9..0916738d21d 100644
--- a/lib/api/entities/license_basic.rb
+++ b/lib/api/entities/license_basic.rb
@@ -2,10 +2,16 @@
module API
module Entities
+ # Serializes a Gitlab::Git::DeclaredLicense
class LicenseBasic < Grape::Entity
expose :key, :name, :nickname
expose :url, as: :html_url
- expose(:source_url) { |license| license.meta['source'] }
+
+ # This was dropped:
+ # https://github.com/github/choosealicense.com/commit/325806b42aa3d5b78e84120327ec877bc936dbdd#diff-66df8f1997786f7052d29010f2cbb4c66391d60d24ca624c356acc0ab986f139
+ expose :source_url do |_|
+ nil
+ end
end
end
end
diff --git a/lib/api/entities/merge_request_approvals.rb b/lib/api/entities/merge_request_approvals.rb
index 0c464844ae7..6810952b2fc 100644
--- a/lib/api/entities/merge_request_approvals.rb
+++ b/lib/api/entities/merge_request_approvals.rb
@@ -8,7 +8,7 @@ module API
end
expose :user_can_approve do |merge_request, options|
- merge_request.can_be_approved_by?(options[:current_user])
+ merge_request.eligible_for_approval_by?(options[:current_user])
end
expose :approved do |merge_request|
diff --git a/lib/api/entities/metadata.rb b/lib/api/entities/metadata.rb
new file mode 100644
index 00000000000..daa491ec42a
--- /dev/null
+++ b/lib/api/entities/metadata.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Metadata < Grape::Entity
+ expose :version
+ expose :revision
+ expose :kas do
+ expose :enabled, documentation: { type: 'boolean' }
+ expose :externalUrl
+ expose :version
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ml/mlflow/experiment.rb b/lib/api/entities/ml/mlflow/experiment.rb
index cfe366feaab..54e0fe63985 100644
--- a/lib/api/entities/ml/mlflow/experiment.rb
+++ b/lib/api/entities/ml/mlflow/experiment.rb
@@ -5,22 +5,10 @@ module API
module Ml
module Mlflow
class Experiment < Grape::Entity
- expose :experiment do
- expose :experiment_id
- expose :name
- expose :lifecycle_stage
- expose :artifact_location
- end
-
- private
-
- def lifecycle_stage
- object.deleted_on? ? 'deleted' : 'active'
- end
-
- def experiment_id
- object.iid.to_s
- end
+ expose(:experiment_id) { |experiment| experiment.iid.to_s }
+ expose :name
+ expose(:lifecycle_stage) { |experiment| experiment.deleted_on? ? 'deleted' : 'active' }
+ expose(:artifact_location) { |experiment| 'not_implemented' }
end
end
end
diff --git a/lib/api/entities/ml/mlflow/get_experiment.rb b/lib/api/entities/ml/mlflow/get_experiment.rb
new file mode 100644
index 00000000000..f28d2ce76f6
--- /dev/null
+++ b/lib/api/entities/ml/mlflow/get_experiment.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ml
+ module Mlflow
+ class GetExperiment < Grape::Entity
+ expose :itself, using: Experiment, as: :experiment
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ml/mlflow/list_experiment.rb b/lib/api/entities/ml/mlflow/list_experiment.rb
new file mode 100644
index 00000000000..515015bf4b7
--- /dev/null
+++ b/lib/api/entities/ml/mlflow/list_experiment.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ml
+ module Mlflow
+ class ListExperiment < Grape::Entity
+ expose :experiments, with: Experiment
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ml/mlflow/metric.rb b/lib/api/entities/ml/mlflow/metric.rb
new file mode 100644
index 00000000000..963aaa5f144
--- /dev/null
+++ b/lib/api/entities/ml/mlflow/metric.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ml
+ module Mlflow
+ class Metric < Grape::Entity
+ expose :name, as: :key
+ expose :value
+ expose :tracked_at, as: :timestamp
+ expose :step, expose_nil: false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ml/mlflow/run.rb b/lib/api/entities/ml/mlflow/run.rb
index c679330206e..a8e1cfe08dd 100644
--- a/lib/api/entities/ml/mlflow/run.rb
+++ b/lib/api/entities/ml/mlflow/run.rb
@@ -7,7 +7,10 @@ module API
class Run < Grape::Entity
expose :run do
expose(:info) { |candidate| RunInfo.represent(candidate) }
- expose(:data) { |candidate| {} }
+ expose :data do
+ expose :metrics, using: Metric
+ expose :params, using: RunParam
+ end
end
end
end
diff --git a/lib/api/entities/ml/mlflow/run_param.rb b/lib/api/entities/ml/mlflow/run_param.rb
new file mode 100644
index 00000000000..75fee738f8b
--- /dev/null
+++ b/lib/api/entities/ml/mlflow/run_param.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ml
+ module Mlflow
+ class RunParam < Grape::Entity
+ expose :name, as: :key
+ expose :value
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ml/mlflow/update_run.rb b/lib/api/entities/ml/mlflow/update_run.rb
index 5acdaab0e33..090d69b8895 100644
--- a/lib/api/entities/ml/mlflow/update_run.rb
+++ b/lib/api/entities/ml/mlflow/update_run.rb
@@ -10,7 +10,7 @@ module API
private
def run_info
- ::API::Entities::Ml::Mlflow::RunInfo.represent object
+ RunInfo.represent object
end
end
end
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 1739bdd639e..f158695f605 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -80,6 +80,7 @@ module API
expose(:analytics_access_level) { |project, options| project_feature_string_access_level(project, :analytics) }
expose(:container_registry_access_level) { |project, options| project_feature_string_access_level(project, :container_registry) }
expose(:security_and_compliance_access_level) { |project, options| project_feature_string_access_level(project, :security_and_compliance) }
+ expose(:releases_access_level) { |project, options| project_feature_string_access_level(project, :releases) }
expose :emails_disabled
expose :shared_runners_enabled
@@ -103,7 +104,7 @@ module API
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :ci_default_git_depth
expose :ci_forward_deployment_enabled
- expose :ci_job_token_scope_enabled
+ expose(:ci_job_token_scope_enabled) { |p, _| p.ci_outbound_job_token_scope_enabled? }
expose :ci_separated_caches
expose :ci_opt_in_jwt
expose :ci_allow_fork_pipelines_to_run_in_parent_project
diff --git a/lib/api/entities/user_with_admin.rb b/lib/api/entities/user_with_admin.rb
index f9c1a646a4f..53fef7a46e2 100644
--- a/lib/api/entities/user_with_admin.rb
+++ b/lib/api/entities/user_with_admin.rb
@@ -6,6 +6,7 @@ module API
expose :admin?, as: :is_admin
expose :note
expose :namespace_id
+ expose :created_by, with: UserBasic
end
end
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index c4b67f83941..42d5e6a73b3 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -40,7 +40,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the environment to be created'
optional :external_url, type: String, desc: 'URL on which this deployment is viewable'
- optional :slug, absence: { message: "is automatically generated and cannot be changed" }
+ 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 environment to be created'
end
post ':id/environments' do
@@ -64,7 +64,7 @@ module API
# TODO: disallow renaming via the API https://gitlab.com/gitlab-org/gitlab/-/issues/338897
optional :name, type: String, desc: 'DEPRECATED: Renaming environment can lead to errors, this will be removed in 15.0'
optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable'
- optional :slug, absence: { message: "is automatically generated and cannot be changed" }
+ 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 environment to be created'
end
put ':id/environments/:environment_id' do
diff --git a/lib/api/features.rb b/lib/api/features.rb
index f89da48acea..9d4e6eee82c 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -7,6 +7,7 @@ module API
feature_category :feature_flags
urgency :low
+ # TODO: remove these helpers with feature flag set_feature_flag_service
helpers do
def gate_value(params)
case params[:value]
@@ -87,35 +88,49 @@ module API
mutually_exclusive :key, :project
end
post ':name' do
- validate_feature_flag_name!(params[:name]) unless params[:force]
-
- targets = gate_targets(params)
- value = gate_value(params)
- key = gate_key(params)
-
- case value
- when true
- if gate_specified?(params)
- targets.each { |target| Feature.enable(params[:name], target) }
- else
- Feature.enable(params[:name])
- end
- when false
- if gate_specified?(params)
- targets.each { |target| Feature.disable(params[:name], target) }
+ if Feature.enabled?(:set_feature_flag_service)
+ flag_params = declared_params(include_missing: false)
+ response = ::Admin::SetFeatureFlagService
+ .new(feature_flag_name: params[:name], params: flag_params)
+ .execute
+
+ if response.success?
+ present response.payload[:feature_flag],
+ with: Entities::Feature, current_user: current_user
else
- Feature.disable(params[:name])
+ bad_request!(response.message)
end
else
- if key == :percentage_of_actors
- Feature.enable_percentage_of_actors(params[:name], value)
+ validate_feature_flag_name!(params[:name]) unless params[:force]
+
+ targets = gate_targets(params)
+ value = gate_value(params)
+ key = gate_key(params)
+
+ case value
+ when true
+ if gate_specified?(params)
+ targets.each { |target| Feature.enable(params[:name], target) }
+ else
+ Feature.enable(params[:name])
+ end
+ when false
+ if gate_specified?(params)
+ targets.each { |target| Feature.disable(params[:name], target) }
+ else
+ Feature.disable(params[:name])
+ end
else
- Feature.enable_percentage_of_time(params[:name], value)
+ if key == :percentage_of_actors
+ Feature.enable_percentage_of_actors(params[:name], value)
+ else
+ Feature.enable_percentage_of_time(params[:name], value)
+ end
end
- end
- present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet
- with: Entities::Feature, current_user: current_user
+ present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet
+ with: Entities::Feature, current_user: current_user
+ end
rescue Feature::Target::UnknowTargetError => e
bad_request!(e.message)
end
@@ -128,6 +143,7 @@ module API
end
end
+ # TODO: remove this helper with feature flag set_feature_flag_service
helpers do
def validate_feature_flag_name!(name)
# no-op
diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb
index ad5455c5de6..0098b074f05 100644
--- a/lib/api/generic_packages.rb
+++ b/lib/api/generic_packages.rb
@@ -40,6 +40,8 @@ module API
end
put 'authorize' do
+ project = authorized_user_project
+
authorize_workhorse!(subject: project, maximum_size: project.actual_limits.generic_packages_max_file_size)
end
@@ -59,6 +61,8 @@ module API
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
put do
+ project = authorized_user_project
+
authorize_upload!(project)
bad_request!('File is too large') if max_file_size_exceeded?
@@ -95,6 +99,8 @@ module API
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get do
+ project = authorized_user_project(action: :read_package)
+
authorize_read_package!(project)
package = ::Packages::Generic::PackageFinder.new(project).execute!(params[:package_name], params[:package_version])
@@ -112,12 +118,8 @@ module API
include ::API::Helpers::PackagesHelpers
include ::API::Helpers::Packages::BasicAuthHelpers
- def project
- authorized_user_project
- end
-
def max_file_size_exceeded?
- project.actual_limits.exceeded?(:generic_packages_max_file_size, params[:file].size)
+ authorized_user_project.actual_limits.exceeded?(:generic_packages_max_file_size, params[:file].size)
end
end
end
diff --git a/lib/api/group_import.rb b/lib/api/group_import.rb
index abb8c10efc6..cef9b542c9e 100644
--- a/lib/api/group_import.rb
+++ b/lib/api/group_import.rb
@@ -54,7 +54,7 @@ module API
params do
requires :path, type: String, desc: 'Group path'
requires :name, type: String, desc: 'Group name'
- requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The group export file to be imported'
+ requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The group export file to be imported', documentation: { type: 'file' }
optional :parent_id, type: Integer, desc: "The ID of the parent group that the group will be imported into. Defaults to the current user's namespace."
end
post 'import' do
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 6b1fc0d4279..ca99e30fbf7 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -123,6 +123,12 @@ module API
end
def present_groups_with_pagination_strategies(params, groups)
+ # Prevent Rails from optimizing the count query and inadvertadly creating a poor performing databse query.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/368969
+ if Feature.enabled?(:present_groups_select_all)
+ groups = groups.select(groups.arel_table[Arel.star])
+ end
+
return present_groups(params, groups) if current_user.present?
options = {
diff --git a/lib/api/helm_packages.rb b/lib/api/helm_packages.rb
index f90084a7e57..fa2537bcfc4 100644
--- a/lib/api/helm_packages.rb
+++ b/lib/api/helm_packages.rb
@@ -44,9 +44,10 @@ module API
end
get ":channel/index.yaml" do
- authorize_read_package!(authorized_user_project)
+ project = authorized_user_project(action: :read_package)
+ authorize_read_package!(project)
- packages = Packages::Helm::PackagesFinder.new(authorized_user_project, params[:channel]).execute
+ packages = Packages::Helm::PackagesFinder.new(project, params[:channel]).execute
env['api.format'] = :yaml
present ::Packages::Helm::IndexPresenter.new(params[:id], params[:channel], packages),
@@ -61,11 +62,12 @@ module API
requires :file_name, type: String, desc: 'Helm package file name'
end
get ":channel/charts/:file_name.tgz" do
- authorize_read_package!(authorized_user_project)
+ project = authorized_user_project(action: :read_package)
+ authorize_read_package!(project)
- package_file = Packages::Helm::PackageFilesFinder.new(authorized_user_project, params[:channel], file_name: "#{params[:file_name]}.tgz").most_recent!
+ package_file = Packages::Helm::PackageFilesFinder.new(project, params[:channel], file_name: "#{params[:file_name]}.tgz").most_recent!
- track_package_event('pull_package', :helm, project: authorized_user_project, namespace: authorized_user_project.namespace)
+ track_package_event('pull_package', :helm, project: project, namespace: project.namespace)
present_package_file!(package_file)
end
@@ -89,7 +91,7 @@ module API
end
params do
requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex
- requires :chart, type: ::API::Validations::Types::WorkhorseFile, desc: 'The chart file to be published (generated by Multipart middleware)'
+ requires :chart, type: ::API::Validations::Types::WorkhorseFile, desc: 'The chart file to be published (generated by Multipart middleware)', documentation: { type: 'file' }
end
post "api/:channel/charts" do
authorize_upload!(authorized_user_project)
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index e29d76a5950..0eb4fbb196c 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -18,6 +18,7 @@ module API
API_TOKEN_ENV = 'gitlab.api.token'
API_EXCEPTION_ENV = 'gitlab.api.exception'
API_RESPONSE_STATUS_CODE = 'gitlab.api.response_status_code'
+ INTEGER_ID_REGEX = /^-?\d+$/.freeze
def declared_params(options = {})
options = { include_parent_namespaces: false }.merge(options)
@@ -139,7 +140,7 @@ module API
projects = Project.without_deleted.not_hidden
- if id.is_a?(Integer) || id =~ /^\d+$/
+ if id.is_a?(Integer) || id =~ INTEGER_ID_REGEX
projects.find_by(id: id)
elsif id.include?("/")
projects.find_by_full_path(id)
@@ -168,7 +169,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def find_group(id)
- if id.to_s =~ /^\d+$/
+ if id.to_s =~ INTEGER_ID_REGEX
Group.find_by(id: id)
else
Group.find_by_full_path(id)
@@ -203,7 +204,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def find_namespace(id)
- if id.to_s =~ /^\d+$/
+ if id.to_s =~ INTEGER_ID_REGEX
Namespace.without_project_namespaces.find_by(id: id)
else
find_namespace_by_path(id)
@@ -286,22 +287,11 @@ module API
end
def authenticate_by_gitlab_shell_token!
- if Feature.enabled?(:gitlab_shell_jwt_token)
- begin
- payload, _ = JSONWebToken::HMACToken.decode(headers[GITLAB_SHELL_API_HEADER], secret_token)
- unauthorized! unless payload['iss'] == GITLAB_SHELL_JWT_ISSUER
- rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature => ex
- Gitlab::ErrorTracking.track_exception(ex)
- unauthorized!
- end
- else
- input = params['secret_token']
- input ||= Base64.decode64(headers[GITLAB_SHARED_SECRET_HEADER]) if headers.key?(GITLAB_SHARED_SECRET_HEADER)
-
- input&.chomp!
-
- unauthorized! unless Devise.secure_compare(secret_token, input)
- end
+ payload, _ = JSONWebToken::HMACToken.decode(headers[GITLAB_SHELL_API_HEADER], secret_token)
+ unauthorized! unless payload['iss'] == GITLAB_SHELL_JWT_ISSUER
+ rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature => ex
+ Gitlab::ErrorTracking.track_exception(ex)
+ unauthorized!
end
def authenticated_with_can_read_all_resources!
@@ -602,19 +592,19 @@ module API
end
end
- def present_artifacts_file!(file, **args)
+ def present_artifacts_file!(file, project:, **args)
log_artifacts_filesize(file&.model)
- present_carrierwave_file!(file, **args)
+ present_carrierwave_file!(file, project: project, **args)
end
- def present_carrierwave_file!(file, supports_direct_download: true)
+ def present_carrierwave_file!(file, project: nil, supports_direct_download: true)
return not_found! unless file&.exists?
if file.file_storage?
present_disk_file!(file.path, file.filename)
elsif supports_direct_download && file.class.direct_download_enabled?
- redirect(file.url)
+ redirect(cdn_fronted_url(file, project))
else
header(*Gitlab::Workhorse.send_url(file.url))
status :ok
@@ -622,6 +612,16 @@ module API
end
end
+ def cdn_fronted_url(file, project)
+ if file.respond_to?(:cdn_enabled_url)
+ result = file.cdn_enabled_url(project, ip_address)
+ Gitlab::ApplicationContext.push(artifact_used_cdn: result.used_cdn)
+ result.url
+ else
+ file.url
+ end
+ end
+
def increment_counter(event_name)
Gitlab::UsageDataCounters.count(event_name)
rescue StandardError => error
@@ -732,13 +732,7 @@ module API
end
def secret_token
- if Feature.enabled?(:gitlab_shell_jwt_token)
- strong_memoize(:secret_token) do
- File.read(Gitlab.config.gitlab_shell.secret_file)
- end
- else
- Gitlab::Shell.secret_token
- end
+ Gitlab::Shell.secret_token
end
def authenticate_non_public?
diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb
index e9af50b80be..74c8b582fde 100644
--- a/lib/api/helpers/groups_helpers.rb
+++ b/lib/api/helpers/groups_helpers.rb
@@ -11,7 +11,7 @@ module API
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the group'
- optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for the group'
+ optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for the group', documentation: { type: 'file' }
optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users in this group to setup Two-factor authentication'
optional :two_factor_grace_period, type: Integer, desc: 'Time before Two-factor authentication is enforced'
diff --git a/lib/api/helpers/open_api.rb b/lib/api/helpers/open_api.rb
new file mode 100644
index 00000000000..11602244b57
--- /dev/null
+++ b/lib/api/helpers/open_api.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module OpenApi
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def add_open_api_documentation!
+ return if Rails.env.production?
+
+ open_api_config = YAML.load_file(Rails.root.join('config/open_api.yml'))['metadata'].deep_symbolize_keys
+
+ add_swagger_documentation(open_api_config)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb
index ebedb3b7563..a62bb1d4991 100644
--- a/lib/api/helpers/packages/basic_auth_helpers.rb
+++ b/lib/api/helpers/packages/basic_auth_helpers.rb
@@ -14,15 +14,27 @@ module API
include Constants
include Gitlab::Utils::StrongMemoize
- def authorized_user_project
- @authorized_user_project ||= authorized_project_find!
+ def authorized_user_project(action: :read_project)
+ strong_memoize("authorized_user_project_#{action}") do
+ authorized_project_find!(action: action)
+ end
end
- def authorized_project_find!
+ def authorized_project_find!(action: :read_project)
project = find_project(params[:id])
- unless project && can?(current_user, :read_project, project)
- return unauthorized_or! { not_found! }
+ return unauthorized_or! { not_found! } unless project
+
+ case action
+ when :read_package
+ unless can?(current_user, :read_package, project&.packages_policy_subject)
+ # guest users can have :read_project but not :read_package
+ return forbidden! if can?(current_user, :read_project, project)
+
+ return unauthorized_or! { not_found! }
+ end
+ else
+ return unauthorized_or! { not_found! } unless can?(current_user, action, project)
end
project
diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb
index a09499e00d7..dc81e5e1b51 100644
--- a/lib/api/helpers/packages/dependency_proxy_helpers.rb
+++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb
@@ -16,8 +16,8 @@ module API
maven: 'maven_package_requests_forwarding'
}.freeze
- def redirect_registry_request(forward_to_registry, package_type, options)
- if forward_to_registry && redirect_registry_request_available?(package_type) && maven_forwarding_ff_enabled?(package_type, options[:target])
+ def redirect_registry_request(forward_to_registry: false, package_type: nil, target: nil, **options)
+ if forward_to_registry && redirect_registry_request_available?(package_type, target) && maven_forwarding_ff_enabled?(package_type, target)
::Gitlab::Tracking.event(self.options[:for].name, "#{package_type}_request_forward")
redirect(registry_url(package_type, options))
else
@@ -40,15 +40,19 @@ module API
end
end
- def redirect_registry_request_available?(package_type)
+ def redirect_registry_request_available?(package_type, target)
application_setting_name = APPLICATION_SETTING_NAMES[package_type]
raise ArgumentError, "Can't find application setting for package_type #{package_type}" unless application_setting_name
- ::Gitlab::CurrentSettings
- .current_application_settings
- .attributes
- .fetch(application_setting_name, false)
+ if target.present? && Feature.enabled?(:cascade_package_forwarding_settings, target)
+ target.public_send(application_setting_name) # rubocop:disable GitlabSecurity/PublicSend
+ else
+ ::Gitlab::CurrentSettings
+ .current_application_settings
+ .attributes
+ .fetch(application_setting_name, false)
+ end
end
private
diff --git a/lib/api/helpers/personal_access_tokens_helpers.rb b/lib/api/helpers/personal_access_tokens_helpers.rb
index db28daa5396..4fd72d89f4c 100644
--- a/lib/api/helpers/personal_access_tokens_helpers.rb
+++ b/lib/api/helpers/personal_access_tokens_helpers.rb
@@ -4,11 +4,14 @@ module API
module Helpers
module PersonalAccessTokensHelpers
def finder_params(current_user)
- if current_user.can_admin_all_resources?
- { user: user(params[:user_id]) }
- else
- { user: current_user, impersonation: false }
- end
+ user_param =
+ if current_user.can_admin_all_resources?
+ { user: user(params[:user_id]) }
+ else
+ { user: current_user, impersonation: false }
+ end
+
+ declared(params, include_missing: false).merge(user_param)
end
def user(user_id)
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 7ca3f55b5a2..9839828a5b4 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -36,6 +36,7 @@ module API
optional :analytics_access_level, type: String, values: %w(disabled private enabled), desc: 'Analytics access level. One of `disabled`, `private` or `enabled`'
optional :container_registry_access_level, type: String, values: %w(disabled private enabled), desc: 'Controls visibility of the container registry. One of `disabled`, `private` or `enabled`. `private` will make the container registry accessible only to project members (reporter role and above). `enabled` will make the container registry accessible to everyone who has access to the project. `disabled` will disable the container registry'
optional :security_and_compliance_access_level, type: String, values: %w(disabled private enabled), desc: 'Security and compliance access level. One of `disabled`, `private` or `enabled`'
+ optional :releases_access_level, type: String, values: %w(disabled private enabled), desc: 'Releases access level. One of `disabled`, `private` or `enabled`'
optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
@@ -58,7 +59,7 @@ module API
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all threads are resolved'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Deprecated: Use :topics instead'
optional :topics, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of topics for a project'
- optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for project'
+ optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for project', documentation: { type: 'file' }
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
optional :suggestion_commit_message, type: String, desc: 'The commit message used to apply merge request suggestions'
@@ -72,7 +73,7 @@ module API
optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
optional :packages_enabled, type: Boolean, desc: 'Enable project packages feature'
optional :squash_option, type: String, values: %w(never always default_on default_off), desc: 'Squash default for project. One of `never`, `always`, `default_on`, or `default_off`.'
- optional :mr_default_target_self, Boolean, desc: 'Merge requests of this forked project targets itself by default'
+ optional :mr_default_target_self, type: Boolean, desc: 'Merge requests of this forked project targets itself by default'
end
params :optional_project_params_ee do
@@ -179,6 +180,7 @@ module API
:keep_latest_artifact,
:mr_default_target_self,
:enforce_auth_checks_on_uploads,
+ :releases_access_level,
# TODO: remove in API v5, replaced by *_access_level
:issues_enabled,
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index 46ca8e4c428..493cc038f46 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -43,6 +43,7 @@ module API
optional :new_name, type: String, desc: 'New repo name'
requires :target_namespace, type: String, desc: 'Namespace to import repo into'
optional :github_hostname, type: String, desc: 'Custom GitHub enterprise hostname'
+ optional :optional_stages, type: Hash, desc: 'Optional stages of import to be performed'
end
post 'import/github' do
result = Import::GithubService.new(client, current_user, params).execute(access_params, provider)
@@ -54,5 +55,20 @@ module API
{ errors: result[:message] }
end
end
+
+ params do
+ requires :project_id, type: Integer, desc: 'ID of importing project to be canceled'
+ end
+ post 'import/github/cancel' do
+ project = Project.imported_from(provider.to_s).find(params[:project_id])
+ result = Import::Github::CancelProjectImportService.new(project, current_user).execute
+
+ if result[:status] == :success
+ status :ok
+ present ProjectSerializer.new.represent(project, serializer: :import)
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
end
end
diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb
index 20ca7038471..6be2679af14 100644
--- a/lib/api/internal/pages.rb
+++ b/lib/api/internal/pages.rb
@@ -59,7 +59,8 @@ module API
# Gitlab::Pages::CacheControl
present_cached virtual_domain,
cache_context: nil,
- with: Entities::Internal::Pages::VirtualDomain
+ with: Entities::Internal::Pages::VirtualDomain,
+ expires_in: ::Gitlab::Pages::CacheControl::EXPIRE
else
present virtual_domain, with: Entities::Internal::Pages::VirtualDomain
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index b6ad34424a6..b8b4019765d 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -272,17 +272,21 @@ module API
begin
spam_params = ::Spam::SpamParams.new_from_request(request: request)
- issue = ::Issues::CreateService.new(project: user_project,
- current_user: current_user,
- params: issue_params,
- spam_params: spam_params).execute
+ result = ::Issues::CreateService.new(project: user_project,
+ current_user: current_user,
+ params: issue_params,
+ spam_params: spam_params).execute
+
+ if result.success?
+ present result[:issue], with: Entities::Issue, current_user: current_user, project: user_project
+ elsif result[:issue]
+ issue = result[:issue]
- if issue.valid?
- present issue, with: Entities::Issue, current_user: current_user, project: user_project
- else
with_captcha_check_rest_api(spammable: issue) do
render_validation_error!(issue)
end
+ else
+ render_api_error!(result.errors.join(', '), result.http_status || 422)
end
rescue ::ActiveRecord::RecordNotUnique
render_api_error!('Duplicated issue', 409)
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
index a3a25ec1696..72313d6a588 100644
--- a/lib/api/maven_packages.rb
+++ b/lib/api/maven_packages.rb
@@ -125,7 +125,13 @@ module API
no_package_found = package_file ? false : true
- redirect_registry_request(no_package_found, :maven, path: params[:path], file_name: params[:file_name], target: params[:target]) do
+ redirect_registry_request(
+ forward_to_registry: no_package_found,
+ package_type: :maven,
+ target: params[:target],
+ path: params[:path],
+ file_name: params[:file_name]
+ ) do
not_found!('Package') if no_package_found
case format
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 1dc0e1f0d22..a0e7d0b10cd 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -218,6 +218,7 @@ module API
[
current_user&.cache_key,
mr.merge_status,
+ mr.labels.map(&:cache_key),
mr.merge_request_assignees.map(&:cache_key),
mr.merge_request_reviewers.map(&:cache_key)
].join(":")
@@ -560,7 +561,7 @@ module API
put ':id/merge_requests/:merge_request_iid/reset_approvals', feature_category: :code_review, urgency: :low do
merge_request = find_project_merge_request(params[:merge_request_iid])
- unauthorized! unless current_user.bot? && merge_request.can_be_approved_by?(current_user)
+ unauthorized! unless current_user.bot? && merge_request.eligible_for_approval_by?(current_user)
merge_request.approvals.delete_all
diff --git a/lib/api/metadata.rb b/lib/api/metadata.rb
index c4984f0e7f0..3e42ffe336a 100644
--- a/lib/api/metadata.rb
+++ b/lib/api/metadata.rb
@@ -25,15 +25,76 @@ module API
}
EOF
- desc 'Get the metadata information of the GitLab instance.' do
+ helpers do
+ def run_metadata_query
+ run_graphql!(
+ query: METADATA_QUERY,
+ context: { current_user: current_user },
+ transform: ->(result) { result.dig('data', 'metadata') }
+ )
+ end
+ end
+
+ desc 'Retrieve metadata information for this GitLab instance.' do
detail 'This feature was introduced in GitLab 15.2.'
+ success [
+ {
+ code: 200,
+ model: Entities::Metadata,
+ message: 'successful operation',
+ examples: {
+ successful_response: {
+ 'value' => {
+ version: "15.0-pre",
+ revision: "c401a659d0c",
+ kas: {
+ enabled: true,
+ externalUrl: "grpc://gitlab.example.com:8150",
+ version: "15.0.0"
+ }
+ }
+ }
+ }
+ }
+ ]
+ failure [{ code: 401, message: 'unauthorized operation' }]
+ tags %w[metadata]
end
get '/metadata' do
- run_graphql!(
- query: METADATA_QUERY,
- context: { current_user: current_user },
- transform: ->(result) { result.dig('data', 'metadata') }
- )
+ run_metadata_query
+ end
+
+ # Support the deprecated `/version` route.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/366287
+ desc 'Get the version information of the GitLab instance.' do
+ detail 'This feature was introduced in GitLab 8.13 and deprecated in 15.5. ' \
+ 'We recommend you instead use the Metadata API.'
+ success [
+ {
+ code: 200,
+ model: Entities::Metadata,
+ message: 'successful operation',
+ examples: {
+ 'Example' => {
+ 'value' => {
+ version: "15.0-pre",
+ revision: "c401a659d0c",
+ kas: {
+ enabled: true,
+ externalUrl: "grpc://gitlab.example.com:8150",
+ version: "15.0.0"
+ }
+ }
+ }
+ }
+ }
+ ]
+ failure [{ code: 401, message: 'unauthorized operation' }]
+ tags %w[metadata]
+ end
+
+ get '/version' do
+ run_metadata_query
end
end
end
diff --git a/lib/api/ml/mlflow.rb b/lib/api/ml/mlflow.rb
index 4f5bd42f8f9..2ffb04ebcbd 100644
--- a/lib/api/ml/mlflow.rb
+++ b/lib/api/ml/mlflow.rb
@@ -9,20 +9,28 @@ module API
include APIGuard
# The first part of the url is the namespace, the second part of the URL is what the MLFlow client calls
- MLFLOW_API_PREFIX = ':id/ml/mflow/api/2.0/mlflow/'
+ MLFLOW_API_PREFIX = ':id/ml/mlflow/api/2.0/mlflow/'
allow_access_with_scope :api
allow_access_with_scope :read_api, if: -> (request) { request.get? || request.head? }
+ feature_category :mlops
+
+ content_type :json, 'application/json'
+ default_format :json
+
before do
+ # MLFlow Client considers any status code different than 200 an error, even 201
+ status 200
+
authenticate!
+
not_found! unless Feature.enabled?(:ml_experiment_tracking, user_project)
end
- feature_category :mlops
-
- content_type :json, 'application/json'
- default_format :json
+ rescue_from ActiveRecord::ActiveRecordError do |e|
+ invalid_parameter!(e.message)
+ end
helpers do
def resource_not_found!
@@ -32,6 +40,34 @@ module API
def resource_already_exists!
render_structured_api_error!({ error_code: 'RESOURCE_ALREADY_EXISTS' }, 400)
end
+
+ def invalid_parameter!(message = nil)
+ render_structured_api_error!({ error_code: 'INVALID_PARAMETER_VALUE', message: message }, 400)
+ end
+
+ def experiment_repository
+ ::Ml::ExperimentTracking::ExperimentRepository.new(user_project, current_user)
+ end
+
+ def candidate_repository
+ ::Ml::ExperimentTracking::CandidateRepository.new(user_project, current_user)
+ end
+
+ def experiment
+ @experiment ||= find_experiment!(params[:experiment_id], params[:experiment_name])
+ end
+
+ def candidate
+ @candidate ||= find_candidate!(params[:run_id])
+ end
+
+ def find_experiment!(iid, name)
+ experiment_repository.by_iid_or_name(iid: iid, name: name) || resource_not_found!
+ end
+
+ def find_candidate!(iid)
+ candidate_repository.by_iid(iid) || resource_not_found!
+ end
end
params do
@@ -44,33 +80,35 @@ module API
namespace MLFLOW_API_PREFIX do
resource :experiments do
desc 'Fetch experiment by experiment_id' do
- success Entities::Ml::Mlflow::Experiment
+ success Entities::Ml::Mlflow::GetExperiment
detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-experiment'
end
params do
optional :experiment_id, type: String, default: '', desc: 'Experiment ID, in reference to the project'
end
get 'get', urgency: :low do
- experiment = ::Ml::Experiment.by_project_id_and_iid(user_project.id, params[:experiment_id])
-
- resource_not_found! unless experiment
-
- present experiment, with: Entities::Ml::Mlflow::Experiment
+ present experiment, with: Entities::Ml::Mlflow::GetExperiment
end
desc 'Fetch experiment by experiment_name' do
- success Entities::Ml::Mlflow::Experiment
+ success Entities::Ml::Mlflow::GetExperiment
detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-experiment-by-name'
end
params do
optional :experiment_name, type: String, default: '', desc: 'Experiment name'
end
get 'get-by-name', urgency: :low do
- experiment = ::Ml::Experiment.by_project_id_and_name(user_project, params[:experiment_name])
+ present experiment, with: Entities::Ml::Mlflow::GetExperiment
+ end
- resource_not_found! unless experiment
+ desc 'List experiments' do
+ success Entities::Ml::Mlflow::ListExperiment
+ detail 'https://www.mlflow.org/docs/latest/rest-api.html#list-experiments'
+ end
+ get 'list', urgency: :low do
+ response = { experiments: experiment_repository.all }
- present experiment, with: Entities::Ml::Mlflow::Experiment
+ present response, with: Entities::Ml::Mlflow::ListExperiment
end
desc 'Create experiment' do
@@ -83,33 +121,13 @@ module API
optional :tags, type: Array, desc: 'This will be ignored'
end
post 'create', urgency: :low do
- resource_already_exists! if ::Ml::Experiment.has_record?(user_project.id, params[:name])
-
- experiment = ::Ml::Experiment.create!(name: params[:name],
- user: current_user,
- project: user_project)
-
- present experiment, with: Entities::Ml::Mlflow::NewExperiment
+ present experiment_repository.create!(params[:name]), with: Entities::Ml::Mlflow::NewExperiment
+ rescue ActiveRecord::RecordInvalid
+ resource_already_exists!
end
end
resource :runs do
- desc 'Gets an MLFlow Run, which maps to GitLab Candidates' do
- success Entities::Ml::Mlflow::Run
- detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-run'
- end
- params do
- optional :run_id, type: String, desc: 'UUID of the candidate.'
- optional :run_uuid, type: String, desc: 'This parameter is ignored'
- end
- get 'get', urgency: :low do
- candidate = ::Ml::Candidate.with_project_id_and_iid(user_project.id, params[:run_id])
-
- resource_not_found! unless candidate
-
- present candidate, with: Entities::Ml::Mlflow::Run
- end
-
desc 'Creates a Run.' do
success Entities::Ml::Mlflow::Run
detail ['https://www.mlflow.org/docs/1.28.0/rest-api.html#create-run',
@@ -125,16 +143,18 @@ module API
optional :tags, type: Array, desc: 'This will be ignored'
end
post 'create', urgency: :low do
- experiment = ::Ml::Experiment.by_project_id_and_iid(user_project.id, params[:experiment_id].to_i)
-
- resource_not_found! unless experiment
-
- candidate = ::Ml::Candidate.create!(
- experiment: experiment,
- user: current_user,
- start_time: params[:start_time] || 0
- )
+ present candidate_repository.create!(experiment, params[:start_time]), with: Entities::Ml::Mlflow::Run
+ end
+ desc 'Gets an MLFlow Run, which maps to GitLab Candidates' do
+ success Entities::Ml::Mlflow::Run
+ detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-run'
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the candidate.'
+ optional :run_uuid, type: String, desc: 'This parameter is ignored'
+ end
+ get 'get', urgency: :low do
present candidate, with: Entities::Ml::Mlflow::Run
end
@@ -144,7 +164,7 @@ module API
'MLFlow Runs map to GitLab Candidates']
end
params do
- optional :run_id, type: String, desc: 'UUID of the candidate.'
+ requires :run_id, type: String, desc: 'UUID of the candidate.'
optional :status, type: String,
values: ::Ml::Candidate.statuses.keys.map(&:upcase),
desc: "Status of the run. Accepts: " \
@@ -152,16 +172,79 @@ module API
optional :end_time, type: Integer, desc: 'Ending time of the run'
end
post 'update', urgency: :low do
- candidate = ::Ml::Candidate.with_project_id_and_iid(user_project.id, params[:run_id])
+ candidate_repository.update(candidate, params[:status], params[:end_time])
- resource_not_found! unless candidate
+ present candidate, with: Entities::Ml::Mlflow::UpdateRun
+ end
- candidate.status = params[:status].downcase if params[:status]
- candidate.end_time = params[:end_time] if params[:end_time]
+ desc 'Logs a metric to a run.' do
+ summary 'Log a metric for a run. A metric is a key-value pair (string key, float value) with an '\
+ 'associated timestamp. Examples include the various metrics that represent ML model accuracy. '\
+ 'A metric can be logged multiple times.'
+ detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-metric'
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the run.'
+ requires :key, type: String, desc: 'Name for the metric.'
+ requires :value, type: Float, desc: 'Value of the metric.'
+ requires :timestamp, type: Integer, desc: 'Unix timestamp in milliseconds when metric was recorded'
+ optional :step, type: Integer, desc: 'Step at which the metric was recorded'
+ end
+ post 'log-metric', urgency: :low do
+ candidate_repository.add_metric!(
+ candidate,
+ params[:key],
+ params[:value],
+ params[:timestamp],
+ params[:step]
+ )
+
+ {}
+ end
- candidate.save if candidate.valid?
+ desc 'Logs a parameter to a run.' do
+ summary 'Log a param used for a run. A param is a key-value pair (string key, string value). '\
+ 'Examples include hyperparameters used for ML model training and constant dates and values '\
+ 'used in an ETL pipeline. A param can be logged only once for a run, duplicate will be .'\
+ 'ignored'
- present candidate, with: Entities::Ml::Mlflow::UpdateRun
+ detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-param'
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the run.'
+ requires :key, type: String, desc: 'Name for the parameter.'
+ requires :value, type: String, desc: 'Value for the parameter.'
+ end
+ post 'log-parameter', urgency: :low do
+ bad_request! unless candidate_repository.add_param!(candidate, params[:key], params[:value])
+
+ {}
+ end
+
+ desc 'Logs multiple parameters and metrics.' do
+ summary 'Log a batch of metrics and params for a run. Validation errors will block the entire batch, '\
+ 'duplicate errors will be ignored.'
+
+ detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-param'
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the run.'
+ optional :metrics, type: Array, default: [] do
+ requires :key, type: String, desc: 'Name for the metric.'
+ requires :value, type: Float, desc: 'Value of the metric.'
+ requires :timestamp, type: Integer, desc: 'Unix timestamp in milliseconds when metric was recorded'
+ optional :step, type: Integer, desc: 'Step at which the metric was recorded'
+ end
+ optional :params, type: Array, default: [] do
+ requires :key, type: String, desc: 'Name for the metric.'
+ requires :value, type: String, desc: 'Value of the metric.'
+ end
+ end
+ post 'log-batch', urgency: :low do
+ candidate_repository.add_metrics(candidate, params[:metrics])
+ candidate_repository.add_params(candidate, params[:params])
+
+ {}
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 77c479c529a..8ce875cdc03 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -73,7 +73,7 @@ module API
params do
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
requires :body, type: String, desc: 'The content of a note'
- optional :confidential, type: Boolean, desc: '[Deprecated in 15.3] Renamed to internal'
+ optional :confidential, type: Boolean, desc: '[Deprecated in 15.5] Renamed to internal'
optional :internal, type: Boolean, desc: 'Internal note flag, default is false'
optional :created_at, type: String, desc: 'The creation date of the note'
optional :merge_request_diff_head_sha, type: String, desc: 'The SHA of the head commit'
diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb
index 34d3a5150da..9cf61967ba4 100644
--- a/lib/api/pages_domains.rb
+++ b/lib/api/pages_domains.rb
@@ -106,7 +106,9 @@ module API
pages_domain_params = declared(params, include_parent_namespaces: false)
- pages_domain = user_project.pages_domains.create(pages_domain_params)
+ pages_domain = ::PagesDomains::CreateService
+ .new(user_project, current_user, pages_domain_params)
+ .execute
if pages_domain.persisted?
present pages_domain, with: Entities::PagesDomain
@@ -136,7 +138,9 @@ module API
pages_domain_params.delete(:user_provided_key)
end
- if pages_domain.update(pages_domain_params)
+ service = ::PagesDomains::UpdateService.new(user_project, current_user, pages_domain_params)
+
+ if service.execute(pages_domain)
present pages_domain, with: Entities::PagesDomain
else
render_validation_error!(pages_domain)
@@ -150,7 +154,9 @@ module API
delete ":id/pages/domains/:domain", requirements: PAGES_DOMAINS_ENDPOINT_REQUIREMENTS do
authorize! :update_pages, user_project
- pages_domain.destroy
+ ::PagesDomains::DeleteService
+ .new(user_project, current_user)
+ .execute(pages_domain)
no_content!
end
diff --git a/lib/api/personal_access_tokens.rb b/lib/api/personal_access_tokens.rb
index 1c00569bba2..a2903faa4ad 100644
--- a/lib/api/personal_access_tokens.rb
+++ b/lib/api/personal_access_tokens.rb
@@ -11,7 +11,15 @@ module API
success Entities::PersonalAccessToken
end
params do
- optional :user_id, type: Integer, desc: 'User ID'
+ optional :user_id, type: Integer, desc: 'Filter PATs by User ID'
+ optional :revoked, type: Boolean, desc: 'Filter PATs where revoked state matches parameter'
+ optional :state, type: String, desc: 'Filter PATs which are either active or not',
+ values: %w[active inactive]
+ optional :created_before, type: DateTime, desc: 'Filter PATs which were created before given datetime'
+ optional :created_after, type: DateTime, desc: 'Filter PATs which were created after given datetime'
+ optional :last_used_before, type: DateTime, desc: 'Filter PATs which were used before given datetime'
+ optional :last_used_after, type: DateTime, desc: 'Filter PATs which were used after given datetime'
+ optional :search, type: String, desc: 'Filters PATs by its name'
use :pagination
end
diff --git a/lib/api/personal_access_tokens/self_revocation.rb b/lib/api/personal_access_tokens/self_information.rb
index 22e07f4cc7b..89850614f94 100644
--- a/lib/api/personal_access_tokens/self_revocation.rb
+++ b/lib/api/personal_access_tokens/self_information.rb
@@ -2,21 +2,25 @@
module API
class PersonalAccessTokens
- class SelfRevocation < ::API::Base
+ class SelfInformation < ::API::Base
include APIGuard
feature_category :authentication_and_authorization
helpers ::API::Helpers::PersonalAccessTokensHelpers
- # As any token regardless of `scope` should be able to revoke itself
- # all availabe scopes are allowed for this API class.
+ # As any token regardless of `scope` should be able to view/revoke itself
+ # all available scopes are allowed for this API class.
# Please be aware of the permissive scope when adding new endpoints to this class.
allow_access_with_scope(Gitlab::Auth.all_available_scopes)
before { authenticate! }
resource :personal_access_tokens do
+ get 'self' do
+ present access_token, with: Entities::PersonalAccessToken
+ end
+
delete 'self' do
revoke_token(access_token)
end
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
index d610b5e4f95..29fdfe45566 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -46,7 +46,8 @@ module API
optional :description, type: String, desc: 'Override the project description'
optional :upload, type: Hash do
optional :url, type: String, desc: 'The URL to upload the project'
- optional :http_method, type: String, default: 'PUT', desc: 'HTTP method to upload the exported project'
+ optional :http_method, type: String, default: 'PUT', values: %w[PUT POST],
+ desc: 'HTTP method to upload the exported project'
end
end
post ':id/export' do
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index 7a66044c5b6..0da8c1ecedd 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -55,7 +55,7 @@ module API
params do
requires :path, type: String, desc: 'The new project path and name'
- requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The project export file to be imported'
+ requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The project export file to be imported', documentation: { type: 'file' }
optional :name, type: String, desc: 'The name of the project to be imported. Defaults to the path of the project if not provided.'
optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace."
optional :overwrite, type: Boolean, default: false, desc: 'If there is a project in the same namespace and with the same name overwrite it'
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 8c58cc585d8..bb97f4fa7ce 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -375,7 +375,7 @@ module API
optional :name, type: String, desc: 'The name that will be assigned to the fork'
optional :description, type: String, desc: 'The description that will be assigned to the fork'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the fork'
- optional :mr_default_target_self, Boolean, desc: 'Merge requests of this forked project targets itself by default'
+ optional :mr_default_target_self, type: Boolean, desc: 'Merge requests of this forked project targets itself by default'
end
post ':id/fork', feature_category: :source_code_management do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20759')
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
index ae583ca968a..1f27fcce879 100644
--- a/lib/api/pypi_packages.rb
+++ b/lib/api/pypi_packages.rb
@@ -56,7 +56,12 @@ module API
packages = Packages::Pypi::PackagesFinder.new(current_user, group_or_project, { package_name: params[:package_name] }).execute
empty_packages = packages.empty?
- redirect_registry_request(empty_packages, :pypi, package_name: params[:package_name]) do
+ redirect_registry_request(
+ forward_to_registry: empty_packages,
+ package_type: :pypi,
+ target: group_or_project,
+ package_name: params[:package_name]
+ ) do
not_found!('Package') if empty_packages
presenter = ::Packages::Pypi::SimplePackageVersionsPresenter.new(packages, group_or_project)
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 44bb4228786..ff17696ed3e 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -63,7 +63,7 @@ module API
@results = search_service(additional_params).search_objects(preload_method)
end
- set_global_search_log_information
+ set_global_search_log_information(additional_params)
Gitlab::Metrics::GlobalSearchSlis.record_apdex(
elapsed: @search_duration_s,
@@ -105,7 +105,7 @@ module API
# EE, without having to modify this file directly.
end
- def search_type
+ def search_type(additional_params = {})
'basic'
end
@@ -113,10 +113,10 @@ module API
params[:scope]
end
- def set_global_search_log_information
+ def set_global_search_log_information(additional_params)
Gitlab::Instrumentation::GlobalSearchApi.set_information(
- type: search_type,
- level: search_service.level,
+ type: search_type(additional_params),
+ level: search_service(additional_params).level,
scope: search_scope,
search_duration_s: @search_duration_s
)
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index f393f862f55..8c8b6c0a1ba 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -132,7 +132,7 @@ module API
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
end
optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
- optional :repository_storages_weighted, type: Hash, coerce_with: Validations::Types::HashOfIntegerValues.coerce, desc: 'Storage paths for new projects with a weighted value ranging from 0 to 100'
+ optional :repository_storages_weighted, type: Hash, coerce_with: Validations::Types::HashOfIntegerValues.coerce, desc: 'Storage paths for new projects with a weighted value ranging from 0 to 100', documentation: { type: 'Object', additional_properties: Integer }
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to set up Two-factor authentication'
given require_two_factor_authentication: ->(val) { val } do
requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 4e70ebddf94..5f8e6c806cb 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -6,6 +6,7 @@ module API
include PaginationParams
feature_category :snippets
+ urgency :low
resource :snippets do
helpers Helpers::SnippetsHelpers
@@ -51,7 +52,7 @@ module API
use :pagination
end
- get 'public', urgency: :low do
+ get 'public' do
authenticate!
filter_params = declared_params(include_missing: false).merge(only_personal: true)
@@ -192,7 +193,7 @@ module API
params do
use :raw_file_params
end
- get ":id/files/:ref/:file_path/raw", urgency: :low, requirements: { file_path: API::NO_SLASH_URL_PART_REGEX } do
+ get ":id/files/:ref/:file_path/raw", requirements: { file_path: API::NO_SLASH_URL_PART_REGEX } do
snippet = snippets.find_by_id(params.delete(:id))
not_found!('Snippet') unless snippet&.repo_exists?
diff --git a/lib/api/support/git_access_actor.rb b/lib/api/support/git_access_actor.rb
index f450630afdd..16861a146ae 100644
--- a/lib/api/support/git_access_actor.rb
+++ b/lib/api/support/git_access_actor.rb
@@ -57,3 +57,5 @@ module API
end
end
end
+
+API::Support::GitAccessActor.prepend_mod_with('API::Support::GitAccessActor')
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 85a299c5673..a80ef514943 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -7,15 +7,18 @@ module API
GLOBAL_TEMPLATE_TYPES = {
gitignores: {
gitlab_version: 8.8,
- feature_category: :source_code_management
+ feature_category: :source_code_management,
+ file_type: '.gitignore'
},
gitlab_ci_ymls: {
gitlab_version: 8.9,
- feature_category: :pipeline_authoring
+ feature_category: :pipeline_authoring,
+ file_type: 'GitLab CI/CD YAML'
},
dockerfiles: {
gitlab_version: 8.15,
- feature_category: :source_code_management
+ feature_category: :source_code_management,
+ file_type: 'Dockerfile'
}
}.freeze
@@ -26,7 +29,7 @@ module API
end
end
- desc 'Get the list of the available license template' do
+ desc 'Get all license templates' do
detail 'This feature was introduced in GitLab 8.7.'
success ::API::Entities::License
end
@@ -43,12 +46,14 @@ module API
present paginate(::Kaminari.paginate_array(templates)), with: ::API::Entities::License
end
- desc 'Get the text for a specific license' do
+ desc 'Get a single license template' do
detail 'This feature was introduced in GitLab 8.7.'
success ::API::Entities::License
end
params do
- requires :name, type: String, desc: 'The name of the template'
+ requires :name, type: String, desc: 'The name of the license template'
+ optional :project, type: String, desc: 'The copyrighted project name'
+ optional :fullname, type: String, desc: 'The full-name of the copyright holder'
end
get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ }, feature_category: :source_code_management do
template = TemplateFinder.build(:licenses, nil, name: params[:name]).execute
@@ -65,8 +70,9 @@ module API
GLOBAL_TEMPLATE_TYPES.each do |template_type, properties|
gitlab_version = properties[:gitlab_version]
+ file_type = properties[:file_type]
- desc 'Get the list of the available template' do
+ desc "Get all #{file_type} templates" do
detail "This feature was introduced in GitLab #{gitlab_version}."
success Entities::TemplatesList
end
@@ -78,12 +84,12 @@ module API
present paginate(templates), with: Entities::TemplatesList
end
- desc 'Get the text for a specific template present in local filesystem' do
+ desc "Get a single #{file_type} template" do
detail "This feature was introduced in GitLab #{gitlab_version}."
success Entities::Template
end
params do
- requires :name, type: String, desc: 'The name of the template'
+ requires :name, type: String, desc: "The name of the #{file_type} template"
end
get "templates/#{template_type}/:name", requirements: { name: /[\w\.-]+/ }, feature_category: properties[:feature_category] do
finder = TemplateFinder.build(template_type, nil, name: declared(params)[:name])
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index f1779df7cc6..57745ee8802 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -15,17 +15,17 @@ module API
}.freeze
params do
- requires :id, type: String, desc: 'The ID of a project'
+ requires :id, type: String, desc: 'The ID or URL-encoded path of the project owned by the authenticated user'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
ISSUABLE_TYPES.each do |type, finder|
type_id_str = "#{type.singularize}_iid".to_sym
- desc 'Create a todo on an issuable' do
+ desc 'Create a to-do item on an issuable' do
success Entities::Todo
end
params do
- requires type_id_str, type: Integer, desc: 'The IID of an issuable'
+ requires type_id_str, type: Integer, desc: 'The internal ID of an issuable'
end
post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder)
@@ -44,12 +44,12 @@ module API
resource :todos do
helpers do
params :todo_filters do
- optional :action, String, values: Todo::ACTION_NAMES.values.map(&:to_s)
- optional :author_id, Integer
- optional :state, String, values: Todo.state_machine.states.map(&:name).map(&:to_s)
- optional :type, String, values: TodosFinder.todo_types
- optional :project_id, Integer
- optional :group_id, Integer
+ optional :action, type: String, values: Todo::ACTION_NAMES.values.map(&:to_s), desc: 'The action to be filtered'
+ optional :author_id, type: Integer, desc: 'The ID of an author'
+ optional :project_id, type: Integer, desc: 'The ID of a project'
+ optional :group_id, type: Integer, desc: 'The ID of a group'
+ optional :state, type: String, values: Todo.state_machine.states.map(&:name).map(&:to_s), desc: 'The state of the to-do item'
+ optional :type, type: String, values: TodosFinder.todo_types.map(&:to_s), desc: 'The type of to-do item'
end
def find_todos
@@ -81,7 +81,7 @@ module API
end
end
- desc 'Get a todo list' do
+ desc 'Get a list of to-do items' do
success Entities::Todo
end
params do
@@ -96,11 +96,11 @@ module API
present todos, options
end
- desc 'Mark a todo as done' do
+ desc 'Mark a to-do item as done' do
success Entities::Todo
end
params do
- requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
+ requires :id, type: Integer, desc: 'The ID of to-do item'
end
post ':id/mark_as_done' do
todo = current_user.todos.find(params[:id])
@@ -110,7 +110,7 @@ module API
present todo, with: Entities::Todo, current_user: current_user
end
- desc 'Mark all todos as done'
+ desc 'Mark all to-do items as done'
post '/mark_as_done' do
todos = find_todos
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 1d1c633824e..7f44e46f1ca 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -50,11 +50,13 @@ module API
optional :provider, type: String, desc: 'The external provider'
optional :bio, type: String, desc: 'The biography of the user'
optional :location, type: String, desc: 'The location of the user'
+ optional :pronouns, type: String, desc: 'The pronouns of the user'
optional :public_email, type: String, desc: 'The public email of the user'
+ optional :commit_email, type: String, desc: 'The commit email, _private for private commit email'
optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator'
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
- optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for user'
+ optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for user', documentation: { type: 'file' }
optional :theme_id, type: Integer, desc: 'The GitLab theme for the user'
optional :color_scheme_id, type: Integer, desc: 'The color scheme for the file viewer'
optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
@@ -187,7 +189,10 @@ module API
user = find_user(params[:id])
not_found!('User') unless user
- if current_user.follow(user)
+ followee = current_user.follow(user)
+ if followee&.errors&.any?
+ render_api_error!(followee.errors.full_messages.join(', '), 400)
+ elsif followee&.persisted?
present user, with: Entities::UserBasic
else
not_modified!
@@ -885,7 +890,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the impersonation token'
optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the impersonation token'
- optional :scopes, type: Array, desc: 'The array of scopes of the impersonation token'
+ optional :scopes, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The array of scopes of the impersonation token'
end
post feature_category: :authentication_and_authorization do
impersonation_token = finder.build(declared_params(include_missing: false))
diff --git a/lib/api/version.rb b/lib/api/version.rb
deleted file mode 100644
index bdce88ab827..00000000000
--- a/lib/api/version.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class Version < ::API::Base
- helpers ::API::Helpers::GraphqlHelpers
- include APIGuard
-
- allow_access_with_scope :read_user, if: -> (request) { request.get? || request.head? }
-
- before { authenticate! }
-
- feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
-
- METADATA_QUERY = <<~EOF
- {
- metadata {
- version
- revision
- }
- }
- EOF
-
- desc 'Get the version information of the GitLab instance.' do
- detail 'This feature was introduced in GitLab 8.13.'
- end
- get '/version' do
- run_graphql!(
- query: METADATA_QUERY,
- context: { current_user: current_user },
- transform: ->(result) { result.dig('data', 'metadata') }
- )
- end
- end
-end
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 082be1f7e11..bb8ad5c4285 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -133,7 +133,7 @@ module API
success Entities::WikiAttachment
end
params do
- requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
+ requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded', documentation: { type: 'file' }
optional :branch, type: String, desc: 'The name of the branch'
end
post ":id/wikis/attachments" do