Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/admin/instance_clusters.rb2
-rw-r--r--lib/api/admin/plan_limits.rb2
-rw-r--r--lib/api/admin/sidekiq.rb2
-rw-r--r--lib/api/alert_management_alerts.rb137
-rw-r--r--lib/api/api.rb8
-rw-r--r--lib/api/bulk_imports.rb10
-rw-r--r--lib/api/ci/helpers/runner.rb5
-rw-r--r--lib/api/ci/job_artifacts.rb12
-rw-r--r--lib/api/ci/jobs.rb11
-rw-r--r--lib/api/ci/pipelines.rb2
-rw-r--r--lib/api/ci/secure_files.rb5
-rw-r--r--lib/api/ci/variables.rb2
-rw-r--r--lib/api/clusters/agents.rb81
-rw-r--r--lib/api/composer_packages.rb13
-rw-r--r--lib/api/entities/application_setting.rb3
-rw-r--r--lib/api/entities/award_emoji.rb1
-rw-r--r--lib/api/entities/basic_release_details.rb16
-rw-r--r--lib/api/entities/ci/job_request/artifacts.rb2
-rw-r--r--lib/api/entities/clusters/agent.rb3
-rw-r--r--lib/api/entities/commit_with_link.rb8
-rw-r--r--lib/api/entities/issue.rb4
-rw-r--r--lib/api/entities/member.rb1
-rw-r--r--lib/api/entities/merge_request_changes.rb2
-rw-r--r--lib/api/entities/metric_image.rb9
-rw-r--r--lib/api/entities/project.rb5
-rw-r--r--lib/api/entities/release.rb8
-rw-r--r--lib/api/entities/user_with_admin.rb1
-rw-r--r--lib/api/entities/wiki_attachment.rb4
-rw-r--r--lib/api/environments.rb2
-rw-r--r--lib/api/files.rb7
-rw-r--r--lib/api/group_export.rb2
-rw-r--r--lib/api/groups.rb32
-rw-r--r--lib/api/helpers.rb8
-rw-r--r--lib/api/integrations.rb2
-rw-r--r--lib/api/internal/base.rb2
-rw-r--r--lib/api/internal/kubernetes.rb6
-rw-r--r--lib/api/invitations.rb14
-rw-r--r--lib/api/issue_links.rb10
-rw-r--r--lib/api/lint.rb2
-rw-r--r--lib/api/markdown.rb2
-rw-r--r--lib/api/members.rb3
-rw-r--r--lib/api/metrics/dashboard/annotations.rb2
-rw-r--r--lib/api/namespaces.rb8
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/notification_settings.rb2
-rw-r--r--lib/api/project_events.rb3
-rw-r--r--lib/api/project_export.rb2
-rw-r--r--lib/api/project_import.rb2
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/projects.rb17
-rw-r--r--lib/api/projects_relation_builder.rb20
-rw-r--r--lib/api/releases.rb58
-rw-r--r--lib/api/remote_mirrors.rb35
-rw-r--r--lib/api/resource_access_tokens.rb22
-rw-r--r--lib/api/settings.rb5
-rw-r--r--lib/api/sidekiq_metrics.rb2
-rw-r--r--lib/api/snippets.rb4
-rw-r--r--lib/api/users.rb18
-rw-r--r--lib/api/validations/validators/limit.rb2
-rw-r--r--lib/api/version.rb2
-rw-r--r--lib/api/wikis.rb6
61 files changed, 541 insertions, 124 deletions
diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb
index 4aebd9c0d40..d6c212a9886 100644
--- a/lib/api/admin/instance_clusters.rb
+++ b/lib/api/admin/instance_clusters.rb
@@ -112,7 +112,7 @@ module API
helpers do
def clusterable_instance
- Clusters::Instance.new
+ ::Clusters::Instance.new
end
def clusters_for_current_user
diff --git a/lib/api/admin/plan_limits.rb b/lib/api/admin/plan_limits.rb
index d595b5b2e09..99be30809d2 100644
--- a/lib/api/admin/plan_limits.rb
+++ b/lib/api/admin/plan_limits.rb
@@ -5,7 +5,7 @@ module API
class PlanLimits < ::API::Base
before { authenticated_as_admin! }
- feature_category :not_owned
+ feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
helpers do
def current_plan(name)
diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb
index 05eb7f8222b..9be432046a5 100644
--- a/lib/api/admin/sidekiq.rb
+++ b/lib/api/admin/sidekiq.rb
@@ -5,7 +5,7 @@ module API
class Sidekiq < ::API::Base
before { authenticated_as_admin! }
- feature_category :not_owned
+ feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
namespace 'admin' do
namespace 'sidekiq' do
diff --git a/lib/api/alert_management_alerts.rb b/lib/api/alert_management_alerts.rb
new file mode 100644
index 00000000000..88230c86247
--- /dev/null
+++ b/lib/api/alert_management_alerts.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+module API
+ class AlertManagementAlerts < ::API::Base
+ feature_category :incident_management
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :alert_iid, type: Integer, desc: 'The IID of the Alert'
+ end
+
+ resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ namespace ':id/alert_management_alerts/:alert_iid/metric_images' do
+ post 'authorize' do
+ authorize!(:upload_alert_management_metric_image, find_project_alert(request.params[:alert_iid]))
+
+ require_gitlab_workhorse!
+ ::Gitlab::Workhorse.verify_api_request!(request.headers)
+ status 200
+ content_type ::Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+
+ params = {
+ has_length: false,
+ maximum_size: ::AlertManagement::MetricImage::MAX_FILE_SIZE.to_i
+ }
+
+ ::MetricImageUploader.workhorse_authorize(**params)
+ end
+
+ desc 'Upload a metric image for an alert' do
+ success Entities::MetricImage
+ end
+ params do
+ requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded'
+ 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
+ post do
+ require_gitlab_workhorse!
+ bad_request!('File is too large') if max_file_size_exceeded?
+
+ alert = find_project_alert(params[:alert_iid])
+
+ authorize!(:upload_alert_management_metric_image, alert)
+
+ upload = ::AlertManagement::MetricImages::UploadService.new(
+ alert,
+ current_user,
+ params.slice(:file, :url, :url_text)
+ ).execute
+
+ if upload.success?
+ present upload.payload[:metric],
+ with: Entities::MetricImage,
+ current_user: current_user,
+ project: user_project
+ else
+ render_api_error!(upload.message, upload.http_status)
+ end
+ end
+
+ desc 'Metric Images for alert'
+ get do
+ alert = find_project_alert(params[:alert_iid])
+
+ if can?(current_user, :read_alert_management_metric_image, alert)
+ present alert.metric_images.order_created_at_asc, with: Entities::MetricImage
+ else
+ render_api_error!('Alert not found', 404)
+ end
+ end
+
+ desc 'Update a metric image for an alert' do
+ success Entities::MetricImage
+ end
+ params do
+ requires :metric_image_id, type: Integer, desc: 'The ID of metric image'
+ 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
+ put ':metric_image_id' do
+ alert = find_project_alert(params[:alert_iid])
+
+ authorize!(:update_alert_management_metric_image, alert)
+
+ render_api_error!('Feature not available', 403) unless alert.metric_images_available?
+
+ metric_image = alert.metric_images.find_by_id(params[:metric_image_id])
+
+ render_api_error!('Metric image not found', 404) unless metric_image
+
+ if metric_image.update(params.slice(:url, :url_text))
+ present metric_image, with: Entities::MetricImage, current_user: current_user, project: user_project
+ else
+ unprocessable_entity!('Metric image could not be updated')
+ end
+ end
+
+ desc 'Remove a metric image for an alert' do
+ success Entities::MetricImage
+ end
+ params do
+ requires :metric_image_id, type: Integer, desc: 'The ID of metric image'
+ end
+ delete ':metric_image_id' do
+ alert = find_project_alert(params[:alert_iid])
+
+ authorize!(:destroy_alert_management_metric_image, alert)
+
+ render_api_error!('Feature not available', 403) unless alert.metric_images_available?
+
+ metric_image = alert.metric_images.find_by_id(params[:metric_image_id])
+
+ render_api_error!('Metric image not found', 404) unless metric_image
+
+ if metric_image.destroy
+ no_content!
+ else
+ unprocessable_entity!('Metric image could not be deleted')
+ end
+ end
+ end
+ end
+
+ helpers do
+ def find_project_alert(iid, project_id = nil)
+ project = project_id ? find_project!(project_id) : user_project
+
+ ::AlertManagement::AlertsFinder.new(current_user, project, { iid: [iid] }).execute.first
+ end
+
+ def max_file_size_exceeded?
+ params[:file].size > ::AlertManagement::MetricImage::MAX_FILE_SIZE
+ end
+ end
+ end
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 5100ec9ec9d..4dca47efdf2 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -80,6 +80,10 @@ module API
Gitlab::UsageDataCounters::JetBrainsPluginActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
end
+ after do
+ Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
+ end
+
# The locale is set to the current user's locale when `current_user` is loaded
after { Gitlab::I18n.use_default_locale }
@@ -159,6 +163,7 @@ module API
mount ::API::Admin::InstanceClusters
mount ::API::Admin::PlanLimits
mount ::API::Admin::Sidekiq
+ mount ::API::AlertManagementAlerts
mount ::API::Appearance
mount ::API::Applications
mount ::API::Avatar
@@ -178,6 +183,7 @@ module API
mount ::API::Ci::SecureFiles
mount ::API::Ci::Triggers
mount ::API::Ci::Variables
+ mount ::API::Clusters::Agents
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::ContainerRegistryEvent
@@ -317,7 +323,7 @@ module API
end
end
- route :any, '*path', feature_category: :not_owned do
+ route :any, '*path', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
error!('404 Not Found', 404)
end
end
diff --git a/lib/api/bulk_imports.rb b/lib/api/bulk_imports.rb
index c732da17166..53967e0af5d 100644
--- a/lib/api/bulk_imports.rb
+++ b/lib/api/bulk_imports.rb
@@ -10,7 +10,7 @@ module API
def bulk_imports
@bulk_imports ||= ::BulkImports::ImportsFinder.new(
user: current_user,
- status: params[:status]
+ params: params
).execute
end
@@ -22,7 +22,7 @@ module API
@bulk_import_entities ||= ::BulkImports::EntitiesFinder.new(
user: current_user,
bulk_import: bulk_import,
- status: params[:status]
+ params: params
).execute
end
@@ -70,6 +70,8 @@ module API
end
params do
use :pagination
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return GitLab Migrations sorted in created by `asc` or `desc` order.'
optional :status, type: String, values: BulkImport.all_human_statuses,
desc: 'Return GitLab Migrations with specified status'
end
@@ -82,13 +84,15 @@ module API
end
params do
use :pagination
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return GitLab Migrations sorted in created by `asc` or `desc` order.'
optional :status, type: String, values: ::BulkImports::Entity.all_human_statuses,
desc: "Return all GitLab Migrations' entities with specified status"
end
get :entities do
entities = ::BulkImports::EntitiesFinder.new(
user: current_user,
- status: params[:status]
+ params: params
).execute
present paginate(entities), with: Entities::BulkImports::Entity
diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb
index 43ed35b99fd..173cfc9a59a 100644
--- a/lib/api/ci/helpers/runner.rb
+++ b/lib/api/ci/helpers/runner.rb
@@ -104,10 +104,7 @@ module API
def set_application_context
return unless current_job
- Gitlab::ApplicationContext.push(
- user: -> { current_job.user },
- project: -> { current_job.project }
- )
+ Gitlab::ApplicationContext.push(job: current_job)
end
def track_ci_minutes_usage!(_build, _runner)
diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb
index 9f59eea5013..0800993602b 100644
--- a/lib/api/ci/job_artifacts.rb
+++ b/lib/api/ci/job_artifacts.rb
@@ -28,7 +28,7 @@ module API
requires :job, type: String, desc: 'The name for the job'
end
route_setting :authentication, job_token_allowed: true
- get ':id/jobs/artifacts/:ref_name/download',
+ get ':id/jobs/artifacts/:ref_name/download', urgency: :low,
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
@@ -87,7 +87,7 @@ module API
requires :artifact_path, type: String, desc: 'Artifact path'
end
route_setting :authentication, job_token_allowed: true
- get ':id/jobs/:job_id/artifacts/*artifact_path', format: false do
+ get ':id/jobs/:job_id/artifacts/*artifact_path', urgency: :low, format: false do
authorize_download_artifacts!
build = find_build!(params[:job_id])
@@ -100,7 +100,11 @@ module API
bad_request! unless path.valid?
- send_artifacts_entry(build.artifacts_file, path)
+ # This endpoint is being used for Artifact Browser feature that renders the content via pages.
+ # Since Content-Type is controlled by Rails and Workhorse, if a wrong
+ # content-type is sent, it could cause a regression on pages rendering.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/357078 for more information.
+ legacy_send_artifacts_entry(build.artifacts_file, path)
end
desc 'Keep the artifacts to prevent them from being deleted' do
@@ -140,8 +144,6 @@ module API
desc 'Expire the artifacts files from a project'
delete ':id/artifacts' do
- not_found! unless Feature.enabled?(:bulk_expire_project_artifacts, default_enabled: :yaml)
-
authorize_destroy_artifacts!
::Ci::JobArtifacts::DeleteProjectArtifactsService.new(project: user_project).execute
diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb
index d9d0da2e4d1..86897eb61ae 100644
--- a/lib/api/ci/jobs.rb
+++ b/lib/api/ci/jobs.rb
@@ -114,11 +114,14 @@ module API
build = find_build!(params[:job_id])
authorize!(:update_build, build)
- break forbidden!('Job is not retryable') unless build.retryable?
- build = ::Ci::Build.retry(build, current_user)
+ response = ::Ci::RetryJobService.new(@project, current_user).execute(build)
- present build, with: Entities::Ci::Job
+ if response.success?
+ present response[:job], with: Entities::Ci::Job
+ else
+ forbidden!('Job is not retryable')
+ end
end
desc 'Erase job (remove artifacts and the trace)' do
@@ -194,7 +197,7 @@ module API
pipeline = current_authenticated_job.pipeline
project = current_authenticated_job.project
- agent_authorizations = Clusters::AgentAuthorizationsFinder.new(project).execute
+ agent_authorizations = ::Clusters::AgentAuthorizationsFinder.new(project).execute
project_groups = project.group&.self_and_ancestor_ids&.map { |id| { id: id } } || []
user_access_level = project.team.max_member_access(current_user.id)
roles_in_project = Gitlab::Access.sym_options_with_owner
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
index 2d7a437ca08..8d2c58dabdf 100644
--- a/lib/api/ci/pipelines.rb
+++ b/lib/api/ci/pipelines.rb
@@ -146,7 +146,7 @@ module API
use :pagination
end
- get ':id/pipelines/:pipeline_id/bridges', feature_category: :pipeline_authoring do
+ get ':id/pipelines/:pipeline_id/bridges', urgency: :low, feature_category: :pipeline_authoring do
authorize!(:read_build, user_project)
pipeline = user_project.all_pipelines.find(params[:pipeline_id])
diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb
index d5b21e2ef29..ee39bdfd90c 100644
--- a/lib/api/ci/secure_files.rb
+++ b/lib/api/ci/secure_files.rb
@@ -54,6 +54,7 @@ module API
resource do
before do
+ read_only_feature_flag_enabled?
authorize! :admin_secure_files, user_project
end
@@ -97,6 +98,10 @@ module API
def feature_flag_enabled?
service_unavailable! unless Feature.enabled?(:ci_secure_files, user_project, default_enabled: :yaml)
end
+
+ def read_only_feature_flag_enabled?
+ service_unavailable! if Feature.enabled?(:ci_secure_files_read_only, user_project, type: :ops, default_enabled: :yaml)
+ end
end
end
end
diff --git a/lib/api/ci/variables.rb b/lib/api/ci/variables.rb
index 9c04d5e9923..ec9951aba0d 100644
--- a/lib/api/ci/variables.rb
+++ b/lib/api/ci/variables.rb
@@ -23,7 +23,7 @@ module API
params do
use :pagination
end
- get ':id/variables' do
+ get ':id/variables', urgency: :low do
variables = user_project.variables
present paginate(variables), with: Entities::Ci::Variable
end
diff --git a/lib/api/clusters/agents.rb b/lib/api/clusters/agents.rb
new file mode 100644
index 00000000000..6c1bf21b952
--- /dev/null
+++ b/lib/api/clusters/agents.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module API
+ module Clusters
+ class Agents < ::API::Base
+ include PaginationParams
+
+ before { authenticate! }
+
+ feature_category :kubernetes_management
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'List agents' do
+ detail 'This feature was introduced in GitLab 14.10.'
+ success Entities::Clusters::Agent
+ end
+ params do
+ use :pagination
+ end
+ get ':id/cluster_agents' do
+ authorize! :read_cluster, user_project
+
+ agents = ::Clusters::AgentsFinder.new(user_project, current_user).execute
+
+ present paginate(agents), with: Entities::Clusters::Agent
+ end
+
+ desc 'Get single agent' do
+ detail 'This feature was introduced in GitLab 14.10.'
+ success Entities::Clusters::Agent
+ end
+ params do
+ requires :agent_id, type: Integer, desc: 'The ID of an agent'
+ end
+ get ':id/cluster_agents/:agent_id' do
+ authorize! :read_cluster, user_project
+
+ agent = user_project.cluster_agents.find(params[:agent_id])
+
+ present agent, with: Entities::Clusters::Agent
+ end
+
+ desc 'Add an agent to a project' do
+ detail 'This feature was introduced in GitLab 14.10.'
+ success Entities::Clusters::Agent
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the agent'
+ end
+ post ':id/cluster_agents' do
+ authorize! :create_cluster, user_project
+
+ params = declared_params(include_missing: false)
+
+ result = ::Clusters::Agents::CreateService.new(user_project, current_user).execute(name: params[:name])
+
+ bad_request!(result[:message]) if result[:status] == :error
+
+ present result[:cluster_agent], with: Entities::Clusters::Agent
+ end
+
+ desc 'Delete an agent' do
+ detail 'This feature was introduced in GitLab 14.10.'
+ end
+ params do
+ requires :agent_id, type: Integer, desc: 'The ID of an agent'
+ end
+ delete ':id/cluster_agents/:agent_id' do
+ authorize! :admin_cluster, user_project
+
+ agent = user_project.cluster_agents.find(params.delete(:agent_id))
+
+ destroy_conditionally!(agent)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb
index 0e6e04d2645..c311b34a697 100644
--- a/lib/api/composer_packages.rb
+++ b/lib/api/composer_packages.rb
@@ -113,10 +113,6 @@ module API
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- before do
- unauthorized_user_project!
- end
-
desc 'Composer packages endpoint for registering packages'
namespace ':id/packages/composer' do
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
@@ -150,8 +146,11 @@ module API
requires :sha, type: String, desc: 'Shasum of current json'
requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end
+ route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get 'archives/*package_name' do
- metadata = unauthorized_user_project
+ authorize_read_package!(authorized_user_project)
+
+ metadata = authorized_user_project
.packages
.composer
.with_name(params[:package_name])
@@ -161,9 +160,9 @@ module API
not_found! unless metadata
- track_package_event('pull_package', :composer, project: unauthorized_user_project, namespace: unauthorized_user_project.namespace)
+ track_package_event('pull_package', :composer, project: authorized_user_project, namespace: authorized_user_project.namespace)
- send_git_archive unauthorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true
+ send_git_archive authorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true
end
end
end
diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb
index 465c5f4112b..db51d4380d0 100644
--- a/lib/api/entities/application_setting.rb
+++ b/lib/api/entities/application_setting.rb
@@ -40,6 +40,9 @@ module API
expose :password_authentication_enabled_for_web, as: :signin_enabled
expose :allow_local_requests_from_web_hooks_and_services, as: :allow_local_requests_from_hooks_and_services
expose :asset_proxy_allowlist, as: :asset_proxy_whitelist
+
+ # This field is deprecated and always returns true
+ expose(:housekeeping_bitmaps_enabled) { |_settings, _options| true }
end
end
end
diff --git a/lib/api/entities/award_emoji.rb b/lib/api/entities/award_emoji.rb
index da9a183bf39..40dc38b1900 100644
--- a/lib/api/entities/award_emoji.rb
+++ b/lib/api/entities/award_emoji.rb
@@ -8,6 +8,7 @@ module API
expose :user, using: Entities::UserBasic
expose :created_at, :updated_at
expose :awardable_id, :awardable_type
+ expose :url
end
end
end
diff --git a/lib/api/entities/basic_release_details.rb b/lib/api/entities/basic_release_details.rb
new file mode 100644
index 00000000000..d13080f32f4
--- /dev/null
+++ b/lib/api/entities/basic_release_details.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class BasicReleaseDetails < Grape::Entity
+ include ::API::Helpers::Presentable
+
+ expose :name
+ expose :tag, as: :tag_name
+ expose :description
+ expose :created_at
+ expose :released_at
+ expose :upcoming_release?, as: :upcoming_release
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/artifacts.rb b/lib/api/entities/ci/job_request/artifacts.rb
index 4b09db40504..d1fb7d330b9 100644
--- a/lib/api/entities/ci/job_request/artifacts.rb
+++ b/lib/api/entities/ci/job_request/artifacts.rb
@@ -6,7 +6,7 @@ module API
module JobRequest
class Artifacts < Grape::Entity
expose :name
- expose :untracked
+ expose :untracked, expose_nil: false
expose :paths
expose :exclude, expose_nil: false
expose :when
diff --git a/lib/api/entities/clusters/agent.rb b/lib/api/entities/clusters/agent.rb
index 3b4538b81c2..140b680f5e8 100644
--- a/lib/api/entities/clusters/agent.rb
+++ b/lib/api/entities/clusters/agent.rb
@@ -5,7 +5,10 @@ module API
module Clusters
class Agent < Grape::Entity
expose :id
+ expose :name
expose :project, with: Entities::ProjectIdentity, as: :config_project
+ expose :created_at
+ expose :created_by_user_id
end
end
end
diff --git a/lib/api/entities/commit_with_link.rb b/lib/api/entities/commit_with_link.rb
index a135cc19480..23efaca34d5 100644
--- a/lib/api/entities/commit_with_link.rb
+++ b/lib/api/entities/commit_with_link.rb
@@ -29,7 +29,7 @@ module API
end
expose :signature_html, if: { type: :full } do |commit|
- render('projects/commit/_signature', signature: commit.signature) if commit.has_signature?
+ ::CommitPresenter.new(commit).signature_html
end
expose :prev_commit_id, if: { type: :full } do |commit|
@@ -50,12 +50,6 @@ module API
pipelines_project_commit_path(pipeline_project, commit.id, ref: pipeline_ref)
end
-
- def render(*args)
- return unless request.respond_to?(:render) && request.render.respond_to?(:call)
-
- request.render.call(*args)
- end
end
end
end
diff --git a/lib/api/entities/issue.rb b/lib/api/entities/issue.rb
index e2506cc596e..f87ef093cd8 100644
--- a/lib/api/entities/issue.rb
+++ b/lib/api/entities/issue.rb
@@ -35,6 +35,10 @@ module API
issue
end
+ expose :severity,
+ format_with: :upcase,
+ documentation: { type: "String", desc: "One of #{::IssuableSeverity.severities.keys.map(&:upcase)}" }
+
# Calculating the value of subscribed field triggers Markdown
# processing. We can't do that for multiple issues / merge
# requests in a single API request.
diff --git a/lib/api/entities/member.rb b/lib/api/entities/member.rb
index 87f03adba31..7ce1e73a043 100644
--- a/lib/api/entities/member.rb
+++ b/lib/api/entities/member.rb
@@ -6,6 +6,7 @@ module API
expose :user, merge: true, using: UserBasic
expose :access_level
expose :created_at
+ expose :created_by, with: UserBasic, expose_nil: false
expose :expires_at
end
end
diff --git a/lib/api/entities/merge_request_changes.rb b/lib/api/entities/merge_request_changes.rb
index 488f33dfb93..a1e8b5ae00a 100644
--- a/lib/api/entities/merge_request_changes.rb
+++ b/lib/api/entities/merge_request_changes.rb
@@ -24,7 +24,7 @@ module API
end
def expose_raw_diffs?
- options[:access_raw_diffs] || ::Feature.enabled?(:mrc_api_use_raw_diffs_from_gitaly, options[:project])
+ options[:access_raw_diffs]
end
end
end
diff --git a/lib/api/entities/metric_image.rb b/lib/api/entities/metric_image.rb
new file mode 100644
index 00000000000..fd5e3a62e40
--- /dev/null
+++ b/lib/api/entities/metric_image.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class MetricImage < Grape::Entity
+ expose :id, :created_at, :filename, :file_path, :url, :url_text
+ end
+ end
+end
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 8f9a8add938..60cc5167c41 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -85,8 +85,11 @@ module API
end
expose :mr_default_target_self, if: -> (project) { project.forked? }
+ expose :import_url, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } do |project|
+ project[:import_url]
+ end
+ expose :import_type, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) }
expose :import_status
-
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
project.import_state&.last_error
end
diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb
index 056b54674f1..2403c907f7f 100644
--- a/lib/api/entities/release.rb
+++ b/lib/api/entities/release.rb
@@ -2,20 +2,14 @@
module API
module Entities
- class Release < Grape::Entity
+ class Release < BasicReleaseDetails
include ::API::Helpers::Presentable
- expose :name
- expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? }
- expose :description
expose :description_html, if: -> (_, options) { options[:include_html_description] } do |entity|
MarkupHelper.markdown_field(entity, :description, current_user: options[:current_user])
end
- expose :created_at
- expose :released_at
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? }
- expose :upcoming_release?, as: :upcoming_release
expose :milestones,
using: Entities::MilestoneWithStats,
if: -> (release, _) { release.milestones.present? && can_read_milestone? } do |release, _|
diff --git a/lib/api/entities/user_with_admin.rb b/lib/api/entities/user_with_admin.rb
index e148a5c45b5..f9c1a646a4f 100644
--- a/lib/api/entities/user_with_admin.rb
+++ b/lib/api/entities/user_with_admin.rb
@@ -5,6 +5,7 @@ module API
class UserWithAdmin < UserPublic
expose :admin?, as: :is_admin
expose :note
+ expose :namespace_id
end
end
end
diff --git a/lib/api/entities/wiki_attachment.rb b/lib/api/entities/wiki_attachment.rb
index e622dea04dd..03a6cc8d644 100644
--- a/lib/api/entities/wiki_attachment.rb
+++ b/lib/api/entities/wiki_attachment.rb
@@ -16,11 +16,11 @@ module API
end
def filename
- object.file_name
+ object[:file_name]
end
def secure_url
- object.file_path
+ object[:file_path]
end
end
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index c032b80e39b..19b48c1e3cf 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -131,7 +131,7 @@ module API
environment = user_project.environments.find(params[:environment_id])
authorize! :stop_environment, environment
- environment.stop_with_action!(current_user)
+ environment.stop_with_actions!(current_user)
status 200
present environment, with: Entities::Environment, current_user: current_user
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 39b3904ec90..41a8e899614 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -24,7 +24,8 @@ module API
file_content_encoding: attrs[:encoding],
author_email: attrs[:author_email],
author_name: attrs[:author_name],
- last_commit_sha: attrs[:last_commit_id]
+ last_commit_sha: attrs[:last_commit_id],
+ execute_filemode: attrs[:execute_filemode]
}
end
@@ -65,7 +66,8 @@ module API
ref: params[:ref],
blob_id: @blob.id,
commit_id: @commit.id,
- last_commit_id: @repo.last_commit_id_for_path(@commit.sha, params[:file_path], literal_pathspec: true)
+ last_commit_id: @repo.last_commit_id_for_path(@commit.sha, params[:file_path], literal_pathspec: true),
+ execute_filemode: @blob.executable?
}
end
@@ -83,6 +85,7 @@ module API
requires :content, type: String, desc: 'File content'
optional :encoding, type: String, values: %w[base64], desc: 'File encoding'
optional :last_commit_id, type: String, desc: 'Last known commit id for this file'
+ optional :execute_filemode, type: Boolean, desc: 'Enable / Disable the executable flag on the file path'
end
end
diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb
index f0c0182a02f..5754eceda97 100644
--- a/lib/api/group_export.rb
+++ b/lib/api/group_export.rb
@@ -3,8 +3,6 @@
module API
class GroupExport < ::API::Base
before do
- not_found! unless Feature.enabled?(:group_import_export, user_group, default_enabled: true)
-
authorize! :admin_group, user_group
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 5fbf222be5d..0ed14476c61 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -7,10 +7,10 @@ module API
before { authenticate_non_get! }
- feature_category :subgroups
-
helpers Helpers::GroupsHelpers
+ feature_category :subgroups, ['/groups/:id/custom_attributes', '/groups/:id/custom_attributes/:key']
+
helpers do
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
@@ -181,7 +181,7 @@ module API
use :group_list_params
use :with_custom_attributes
end
- get do
+ get feature_category: :subgroups do
groups = find_groups(declared_params(include_missing: false), params[:id])
present_groups_with_pagination_strategies params, groups
end
@@ -196,7 +196,7 @@ module API
use :optional_params
end
- post do
+ post feature_category: :subgroups do
parent_group = find_group!(params[:parent_id]) if params[:parent_id].present?
if parent_group
authorize! :create_subgroup, parent_group
@@ -229,7 +229,7 @@ module API
use :optional_update_params
use :optional_update_params_ee
end
- put ':id' do
+ put ':id', feature_category: :subgroups do
group = find_group!(params[:id])
group.preload_shared_group_links
@@ -249,7 +249,8 @@ module API
use :with_custom_attributes
optional :with_projects, type: Boolean, default: true, desc: 'Omit project details'
end
- get ":id" do
+ # TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/357841
+ get ":id", feature_category: :subgroups, urgency: :low do
group = find_group!(params[:id])
group.preload_shared_group_links
@@ -265,7 +266,7 @@ module API
end
desc 'Remove a group.'
- delete ":id" do
+ delete ":id", feature_category: :subgroups do
group = find_group!(params[:id])
authorize! :admin_group, group
check_subscription! group
@@ -300,7 +301,8 @@ module API
use :with_custom_attributes
use :optional_projects_params
end
- get ":id/projects" do
+ # TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/211498
+ get ":id/projects", feature_category: :subgroups, urgency: :low do
finder_options = {
only_owned: !params[:with_shared],
include_subgroups: params[:include_subgroups],
@@ -334,7 +336,7 @@ module API
use :pagination
use :with_custom_attributes
end
- get ":id/projects/shared" do
+ get ":id/projects/shared", feature_category: :subgroups do
projects = find_group_projects(params, { only_shared: true })
present_projects(params, projects)
@@ -347,7 +349,7 @@ module API
use :group_list_params
use :with_custom_attributes
end
- get ":id/subgroups" do
+ get ":id/subgroups", feature_category: :subgroups, urgency: :low do
groups = find_groups(declared_params(include_missing: false), params[:id])
present_groups params, groups
end
@@ -359,7 +361,7 @@ module API
use :group_list_params
use :with_custom_attributes
end
- get ":id/descendant_groups" do
+ get ":id/descendant_groups", feature_category: :subgroups do
finder_params = declared_params(include_missing: false).merge(include_parent_descendants: true)
groups = find_groups(finder_params, params[:id])
present_groups params, groups
@@ -371,7 +373,7 @@ module API
params do
requires :project_id, type: String, desc: 'The ID or path of the project'
end
- post ":id/projects/:project_id", requirements: { project_id: /.+/ } do
+ post ":id/projects/:project_id", requirements: { project_id: /.+/ }, feature_category: :projects do
authenticated_as_admin!
group = find_group!(params[:id])
group.preload_shared_group_links
@@ -391,7 +393,7 @@ module API
desc: 'The ID of the target group to which the group needs to be transferred to.'\
'If not provided, the source group will be promoted to a root group.'
end
- post ':id/transfer' do
+ post ':id/transfer', feature_category: :subgroups do
group = find_group!(params[:id])
authorize! :admin_group, group
@@ -415,7 +417,7 @@ module API
requires :group_access, type: Integer, values: Gitlab::Access.all_values, desc: 'The group access level'
optional :expires_at, type: Date, desc: 'Share expiration date'
end
- post ":id/share" do
+ post ":id/share", feature_category: :subgroups do
shared_group = find_group!(params[:id])
shared_with_group = find_group!(params[:group_id])
@@ -438,7 +440,7 @@ module API
requires :group_id, type: Integer, desc: 'The ID of the shared group'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete ":id/share/:group_id" do
+ delete ":id/share/:group_id", feature_category: :subgroups do
shared_group = find_group!(params[:id])
link = shared_group.shared_with_group_links.find_by(shared_with_group_id: params[:group_id])
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index de9d42bdce7..e4a7f2213ae 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -705,8 +705,16 @@ module API
body ''
end
+ # Deprecated. Use `send_artifacts_entry` instead.
+ def legacy_send_artifacts_entry(file, entry)
+ header(*Gitlab::Workhorse.send_artifacts_entry(file, entry))
+
+ body ''
+ end
+
def send_artifacts_entry(file, entry)
header(*Gitlab::Workhorse.send_artifacts_entry(file, entry))
+ header(*Gitlab::Workhorse.detect_content_type)
body ''
end
diff --git a/lib/api/integrations.rb b/lib/api/integrations.rb
index ff1d88e35f0..71c55704ddf 100644
--- a/lib/api/integrations.rb
+++ b/lib/api/integrations.rb
@@ -6,7 +6,7 @@ module API
integrations = Helpers::IntegrationsHelpers.integrations
integration_classes = Helpers::IntegrationsHelpers.integration_classes
- if Rails.env.development?
+ if Gitlab.dev_or_test_env?
integrations['mock-ci'] = [
{
required: true,
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index 9c527f28d44..2ab5d482295 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -189,7 +189,7 @@ module API
present actor.user, with: Entities::UserSafe
end
- get '/check', feature_category: :not_owned do
+ get '/check', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
{
api_version: API.version,
gitlab_version: Gitlab::VERSION,
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index df887a83c4f..59bc917a602 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -54,7 +54,7 @@ module API
def check_agent_token
unauthorized! unless agent_token
- Clusters::AgentTokens::TrackUsageService.new(agent_token).execute
+ ::Clusters::AgentTokens::TrackUsageService.new(agent_token).execute
end
end
@@ -91,9 +91,9 @@ module API
requires :agent_config, type: JSON, desc: 'Configuration for the Agent'
end
post '/' do
- agent = Clusters::Agent.find(params[:agent_id])
+ agent = ::Clusters::Agent.find(params[:agent_id])
- Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute
+ ::Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute
no_content!
end
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index d78576b5d5b..75f63a5d98f 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -20,19 +20,25 @@ module API
success Entities::Invitation
end
params do
- requires :email, types: [String, Array[String]], email_or_email_list: true, desc: 'The email address to invite, or multiple emails separated by comma'
requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
+ optional :email, types: [String, Array[String]], email_or_email_list: true, desc: 'The email address to invite, or multiple emails separated by comma'
+ optional :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api'
optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do'
optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues'
end
post ":id/invitations" do
- params[:source] = find_source(source_type, params[:id])
+ ::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/354016')
- authorize_admin_source!(source_type, params[:source])
+ bad_request!('Must provide either email or user_id as a parameter') if params[:email].blank? && params[:user_id].blank?
- ::Members::InviteService.new(current_user, params).execute
+ source = find_source(source_type, params[:id])
+ authorize_admin_source!(source_type, source)
+
+ create_service_params = params.except(:user_id).merge({ user_ids: params[:user_id], source: source })
+
+ ::Members::InviteService.new(current_user, create_service_params).execute
end
desc 'Get a list of group or project invitations viewable by the authenticated user' do
diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb
index 98451afb12d..0e93a4adb65 100644
--- a/lib/api/issue_links.rb
+++ b/lib/api/issue_links.rb
@@ -67,14 +67,16 @@ module API
requires :issue_link_id, type: Integer, desc: 'The ID of an issue link'
end
delete ':id/issues/:issue_iid/links/:issue_link_id' do
- issue_link = IssueLink.find(declared_params[:issue_link_id])
+ issue = find_project_issue(params[:issue_iid])
+ issue_link = IssueLink
+ .for_source_or_target(issue)
+ .find(declared_params[:issue_link_id])
- find_project_issue(params[:issue_iid])
find_project_issue(issue_link.target.iid.to_s, issue_link.target.project_id.to_s)
result = ::IssueLinks::DestroyService
- .new(issue_link, current_user)
- .execute
+ .new(issue_link, current_user)
+ .execute
if result[:status] == :success
present issue_link, with: Entities::IssueLink
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index 6de78c81cac..f65ecf3b4a6 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -21,7 +21,7 @@ module API
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response'
end
- post '/lint' do
+ post '/lint', urgency: :low do
unauthorized! unless can_lint_ci?
result = Gitlab::Ci::Lint.new(project: nil, current_user: current_user)
diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb
index de612ff8321..c465087c4a2 100644
--- a/lib/api/markdown.rb
+++ b/lib/api/markdown.rb
@@ -2,7 +2,7 @@
module API
class Markdown < ::API::Base
- feature_category :not_owned
+ feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
params do
requires :text, type: String, desc: "The markdown text to render"
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 4798edc4ddf..01e859c94c4 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -7,6 +7,7 @@ module API
before { authenticate! }
feature_category :authentication_and_authorization
+ urgency :low
helpers ::API::Helpers::MembersHelpers
@@ -100,8 +101,6 @@ module API
end
post ":id/members" do
- ::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/333434')
-
source = find_source(source_type, params[:id])
authorize_admin_source!(source_type, source)
diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb
index 0989340b3ea..c6406bf61df 100644
--- a/lib/api/metrics/dashboard/annotations.rb
+++ b/lib/api/metrics/dashboard/annotations.rb
@@ -12,7 +12,7 @@ module API
ANNOTATIONS_SOURCES = [
{ class: ::Environment, resource: :environments, create_service_param_key: :environment },
- { class: Clusters::Cluster, resource: :clusters, create_service_param_key: :cluster }
+ { class: ::Clusters::Cluster, resource: :clusters, create_service_param_key: :cluster }
].freeze
ANNOTATIONS_SOURCES.each do |annotations_source|
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index d2468fb1c2e..1f3516e0667 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -6,8 +6,6 @@ module API
before { authenticate! }
- feature_category :subgroups
-
helpers do
params :optional_list_params_ee do
# EE::API::Namespaces would override this helper
@@ -32,7 +30,7 @@ module API
use :pagination
use :optional_list_params_ee
end
- get do
+ get feature_category: :subgroups do
owned_only = params[:owned_only] == true
namespaces = current_user.admin ? Namespace.all : current_user.namespaces(owned_only: owned_only)
@@ -54,7 +52,7 @@ module API
params do
requires :id, type: String, desc: "Namespace's ID or path"
end
- get ':id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ get ':id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups do
user_namespace = find_namespace!(params[:id])
present user_namespace, with: Entities::Namespace, current_user: current_user
@@ -67,7 +65,7 @@ module API
requires :namespace, type: String, desc: "Namespace's path"
optional :parent_id, type: Integer, desc: "The ID of the parent namespace. If no ID is specified, only top-level namespaces are considered."
end
- get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups do
namespace_path = params[:namespace]
exists = Namespace.without_project_namespaces.by_parent(params[:parent_id]).filter_by_path(namespace_path).exists?
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index b260f5289b3..c12b3bf5562 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -112,7 +112,7 @@ module API
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
requires :note_id, type: Integer, desc: 'The ID of a note'
optional :body, type: String, allow_blank: false, desc: 'The content of a note'
- optional :confidential, type: Boolean, desc: 'Confidentiality note flag'
+ optional :confidential, type: Boolean, desc: '[Deprecated in 14.10] No longer allowed to update confidentiality of notes'
end
put ":id/#{noteables_str}/:noteable_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index 7d28394e034..420eabb41db 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -5,7 +5,7 @@ module API
class NotificationSettings < ::API::Base
before { authenticate! }
- feature_category :users
+ feature_category :team_planning
helpers ::API::Helpers::MembersHelpers
diff --git a/lib/api/project_events.rb b/lib/api/project_events.rb
index 69b47f9420d..e8829216336 100644
--- a/lib/api/project_events.rb
+++ b/lib/api/project_events.rb
@@ -8,6 +8,9 @@ module API
feature_category :users
+ # TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/357839
+ urgency :low
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
index 843f72c0e1d..8b27d8d2163 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -25,7 +25,7 @@ module API
detail 'This feature was introduced in GitLab 10.6.'
end
get ':id/export/download' do
- check_rate_limit! :project_download_export, scope: [current_user, user_project]
+ check_rate_limit! :project_download_export, scope: [current_user, user_project.namespace]
if user_project.export_file_exists?
if user_project.export_archive_exists?
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index fae170d638b..bd8faefa803 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -135,8 +135,6 @@ module API
success Entities::ProjectImportStatus
end
post 'remote-import' do
- not_found! unless ::Feature.enabled?(:import_project_from_remote_file, default_enabled: :yaml)
-
check_rate_limit! :project_import, scope: [current_user, :project_import]
response = ::Import::GitlabProjects::CreateProjectService.new(
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index a80e45637dc..14792730eae 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -38,7 +38,7 @@ module API
params do
use :pagination
end
- get ":id/snippets" do
+ get ":id/snippets", urgency: :low do
authenticate!
present paginate(snippets_for_current_user), with: Entities::ProjectSnippet, current_user: current_user
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index d772079372c..9f7b3f9b088 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -20,6 +20,7 @@ module API
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
projects = projects.with_statistics if params[:statistics]
projects = projects.joins(:statistics) if params[:order_by].include?('project_statistics') # rubocop: disable CodeReuse/ActiveRecord
+ projects = projects.created_by(current_user).imported.with_import_state if params[:imported]
lang = params[:with_programming_language]
projects = projects.with_programming_language(lang) if lang
@@ -125,6 +126,7 @@ module API
optional :search_namespaces, type: Boolean, desc: "Include ancestor namespaces when matching search criteria"
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
+ optional :imported, type: Boolean, default: false, desc: 'Limit by imported by authenticated user'
optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
@@ -212,7 +214,7 @@ module API
use :statistics_params
use :with_custom_attributes
end
- get ":user_id/projects", feature_category: :projects do
+ get ":user_id/projects", feature_category: :projects, urgency: :default do
user = find_user(params[:user_id])
not_found!('User') unless user
@@ -249,7 +251,8 @@ module API
use :statistics_params
use :with_custom_attributes
end
- get feature_category: :projects do
+ # TODO: Set higher urgency https://gitlab.com/gitlab-org/gitlab/-/issues/211495
+ get feature_category: :projects, urgency: :low do
present_projects load_projects
end
@@ -338,7 +341,8 @@ module API
optional :license, type: Boolean, default: false,
desc: 'Include project license data'
end
- get ":id", feature_category: :projects do
+ # TODO: Set higher urgency https://gitlab.com/gitlab-org/gitlab/-/issues/357622
+ get ":id", feature_category: :projects, urgency: :default do
options = {
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
current_user: current_user,
@@ -609,7 +613,8 @@ module API
params do
requires :project_id, type: Integer, desc: 'The ID of the source project to import the members from.'
end
- post ":id/import_project_members/:project_id", feature_category: :experimentation_expansion do
+ post ":id/import_project_members/:project_id", feature_category: :projects do
+ ::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/355916')
authorize! :admin_project, user_project
source_project = Project.find_by_id(params[:project_id])
@@ -628,7 +633,7 @@ module API
desc 'Workhorse authorize the file upload' do
detail 'This feature was introduced in GitLab 13.11'
end
- post ':id/uploads/authorize', feature_category: :not_owned do
+ post ':id/uploads/authorize', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
require_gitlab_workhorse!
status 200
@@ -640,7 +645,7 @@ module API
params do
requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
end
- post ":id/uploads", feature_category: :not_owned do
+ post ":id/uploads", feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
log_if_upload_exceed_max_size(user_project, params[:file])
service = UploadService.new(user_project, params[:file])
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
index aabecb43653..35f555e16b5 100644
--- a/lib/api/projects_relation_builder.rb
+++ b/lib/api/projects_relation_builder.rb
@@ -14,6 +14,7 @@ module API
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user]
Preloaders::SingleHierarchyProjectGroupPlansPreloader.new(projects_relation).execute if options[:single_hierarchy]
+ preload_groups(projects_relation) if options[:with] == Entities::Project
projects_relation
end
@@ -40,6 +41,25 @@ module API
def repositories_for_preload(projects_relation)
projects_relation.map(&:repository)
end
+
+ # For all projects except those in a user namespace, the `namespace`
+ # and `group` are identical. Preload the group when it's not a user namespace.
+ def preload_groups(projects_relation)
+ return unless Feature.enabled?(:group_projects_api_preload_groups)
+
+ group_projects = projects_for_group_preload(projects_relation)
+ groups = group_projects.map(&:namespace)
+
+ Preloaders::GroupRootAncestorPreloader.new(groups).execute
+
+ group_projects.each do |project|
+ project.group = project.namespace
+ end
+ end
+
+ def projects_for_group_preload(projects_relation)
+ projects_relation.select { |project| project.namespace.type == Group.sti_name }
+ end
end
end
end
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 7b89a177fd9..9e085a91a7c 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -8,16 +8,48 @@ module API
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
RELEASE_CLI_USER_AGENT = 'GitLab-release-cli'
- before { authorize_read_releases! }
+ feature_category :release_orchestration
- after { track_release_event }
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before { authorize_read_group_releases! }
- feature_category :release_orchestration
+ desc 'Get a list of releases for projects in this group.' do
+ success Entities::Release
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the group to get releases for'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return projects sorted in ascending and descending order by released_at'
+ optional :simple, type: Boolean, default: false,
+ desc: 'Return only the ID, URL, name, and path of each project'
+
+ use :pagination
+ end
+ get ":id/releases" do
+ not_found! unless Feature.enabled?(:group_releases_finder_inoperator)
+
+ finder_options = {
+ sort: params[:sort]
+ }
+
+ strict_params = declared_params(include_missing: false)
+ releases = find_group_releases(finder_options)
+
+ present_group_releases(strict_params, releases)
+ end
+ end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before { authorize_read_releases! }
+
+ after { track_release_event }
+
desc 'Get a project releases' do
detail 'This feature was introduced in GitLab 11.7.'
named 'get_releases'
@@ -162,6 +194,10 @@ module API
end
helpers do
+ def authorize_read_group_releases!
+ authorize! :read_release, user_group
+ end
+
def authorize_create_release!
authorize! :create_release, user_project
end
@@ -220,6 +256,22 @@ module API
Gitlab::Tracking.event(options[:for].name, options[:route_options][:named],
project: user_project, user: current_user, **event_context)
end
+
+ def find_group_releases(finder_options)
+ ::Releases::GroupReleasesFinder
+ .new(user_group, current_user, finder_options)
+ .execute(preload: true)
+ end
+
+ def present_group_releases(params, releases)
+ options = {
+ with: params[:simple] ? Entities::BasicReleaseDetails : Entities::Release,
+ current_user: current_user
+ }
+
+ # GroupReleasesFinder has already ordered the data for us
+ present paginate(releases, skip_default_order: true), options
+ end
end
end
end
diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb
index 83096772d32..8de155312fb 100644
--- a/lib/api/remote_mirrors.rb
+++ b/lib/api/remote_mirrors.rb
@@ -25,6 +25,18 @@ module API
with: Entities::RemoteMirror
end
+ desc 'Get a single remote mirror' do
+ success Entities::RemoteMirror
+ end
+ params do
+ requires :mirror_id, type: String, desc: 'The ID of a remote mirror'
+ end
+ get ':id/remote_mirrors/:mirror_id' do
+ mirror = user_project.remote_mirrors.find(params[:mirror_id])
+
+ present mirror, with: Entities::RemoteMirror
+ end
+
desc 'Create remote mirror for a project' do
success Entities::RemoteMirror
end
@@ -73,6 +85,29 @@ module API
render_api_error!(result[:message], result[:http_status])
end
end
+
+ desc 'Delete a single remote mirror' do
+ detail 'This feature was introduced in GitLab 14.10'
+ end
+ params do
+ requires :mirror_id, type: String, desc: 'The ID of a remote mirror'
+ end
+ delete ':id/remote_mirrors/:mirror_id' do
+ mirror = user_project.remote_mirrors.find(params[:mirror_id])
+
+ destroy_conditionally!(mirror) do
+ mirror_params = declared_params(include_missing: false).merge(_destroy: 1)
+ mirror_params[:id] = mirror_params.delete(:mirror_id)
+ update_params = { remote_mirrors_attributes: mirror_params }
+
+ # Note: We are using the update service to be consistent with how the controller handles deletion
+ result = ::Projects::UpdateService.new(user_project, current_user, update_params).execute
+
+ if result[:status] != :success
+ render_api_error!(result[:message], 400)
+ end
+ end
+ end
end
end
end
diff --git a/lib/api/resource_access_tokens.rb b/lib/api/resource_access_tokens.rb
index e52f8fd9111..2ba109b7092 100644
--- a/lib/api/resource_access_tokens.rb
+++ b/lib/api/resource_access_tokens.rb
@@ -27,6 +27,28 @@ module API
present paginate(tokens), with: Entities::ResourceAccessToken, resource: resource
end
+ desc 'Get an access token for the specified resource by ID' do
+ detail 'This feature was introduced in GitLab 14.10.'
+ end
+ params do
+ requires :id, type: String, desc: "The #{source_type} ID"
+ requires :token_id, type: String, desc: "The ID of the token"
+ end
+ get ":id/access_tokens/:token_id" do
+ resource = find_source(source_type, params[:id])
+
+ next unauthorized! unless current_user.can?(:read_resource_access_tokens, resource)
+
+ token = find_token(resource, params[:token_id])
+
+ if token.nil?
+ next not_found!("Could not find #{source_type} access token with token_id: #{params[:token_id]}")
+ end
+
+ resource.members.load
+ present token, with: Entities::ResourceAccessToken, resource: resource
+ end
+
desc 'Revoke a resource access token' do
detail 'This feature was introduced in GitLab 13.9.'
end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index b256432fbf1..774ab472f2d 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -4,7 +4,7 @@ module API
class Settings < ::API::Base
before { authenticated_as_admin! }
- feature_category :not_owned
+ feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
helpers Helpers::SettingsHelpers
@@ -83,7 +83,6 @@ module API
optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page'
optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)'
given housekeeping_enabled: ->(val) { val } do
- requires :housekeeping_bitmaps_enabled, type: Boolean, desc: "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance."
requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run."
requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."
requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run."
@@ -182,7 +181,7 @@ module API
optional :group_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for group runners, in seconds'
optional :project_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for project runners, in seconds'
- ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
+ Gitlab::SSHPublicKey.supported_types.each do |type|
optional :"#{type}_key_restriction",
type: Integer,
values: KeyRestrictionValidator.supported_key_restrictions(type),
diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb
index 680363d036e..c30b9d7583a 100644
--- a/lib/api/sidekiq_metrics.rb
+++ b/lib/api/sidekiq_metrics.rb
@@ -6,7 +6,7 @@ module API
class SidekiqMetrics < ::API::Base
before { authenticated_as_admin! }
- feature_category :not_owned
+ feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
helpers do
def queue_metrics
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 9a3c68bc854..496532a15b2 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -184,7 +184,7 @@ module API
params do
use :raw_file_params
end
- get ":id/files/:ref/:file_path/raw", requirements: { file_path: API::NO_SLASH_URL_PART_REGEX } do
+ get ":id/files/:ref/:file_path/raw", urgency: :low, 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?
@@ -200,7 +200,7 @@ module API
get ":id/user_agent_detail" do
authenticated_as_admin!
- snippet = Snippet.find_by_id!(params[:id])
+ snippet = Snippet.find(params[:id])
break not_found!('UserAgentDetail') unless snippet.user_agent_detail
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 0f710e0a307..b26611cfe03 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -89,6 +89,7 @@ module API
optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
optional :without_projects, type: Boolean, default: false, desc: 'Filters only users without projects'
optional :exclude_internal, as: :non_internal, type: Boolean, default: false, desc: 'Filters only non internal users'
+ optional :without_project_bots, type: Boolean, default: false, desc: 'Filters users without project bots'
optional :admins, type: Boolean, default: false, desc: 'Filters only admin users'
all_or_none_of :extern_uid, :provider
@@ -98,7 +99,7 @@ module API
use :optional_index_params_ee
end
# rubocop: disable CodeReuse/ActiveRecord
- get feature_category: :users do
+ get feature_category: :users, urgency: :default do
authenticated_as_admin! if params[:extern_uid].present? && params[:provider].present?
unless current_user&.admin?
@@ -120,8 +121,11 @@ module API
users = reorder_users(users)
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
- users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin
- users = users.preload(:identities, :webauthn_registrations) if entity == Entities::UserWithAdmin
+
+ if entity == Entities::UserWithAdmin
+ users = users.preload(:identities, :u2f_registrations, :webauthn_registrations, :namespace)
+ end
+
users, options = with_custom_attributes(users, { with: entity, current_user: current_user })
users = users.preload(:user_detail)
@@ -139,7 +143,7 @@ module API
use :with_custom_attributes
end
# rubocop: disable CodeReuse/ActiveRecord
- get ":id", feature_category: :users do
+ get ":id", feature_category: :users, urgency: :medium do
forbidden!('Not authorized!') unless current_user
unless current_user.admin?
@@ -164,7 +168,7 @@ module API
params do
requires :user_id, type: String, desc: 'The ID or username of the user'
end
- get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :users do
+ get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :users, urgency: :high do
user = find_user(params[:user_id])
not_found!('User') unless user && can?(current_user, :read_user, user)
@@ -915,7 +919,7 @@ module API
desc 'Get the currently authenticated user' do
success Entities::UserPublic
end
- get feature_category: :users do
+ get feature_category: :users, urgency: :medium do
entity =
if current_user.admin?
Entities::UserWithAdmin
@@ -1090,7 +1094,7 @@ module API
requires :credit_card_mask_number, type: String, desc: 'The last 4 digits of credit card number'
requires :credit_card_type, type: String, desc: 'The credit card network name'
end
- put ":user_id/credit_card_validation", feature_category: :users do
+ put ":user_id/credit_card_validation", feature_category: :purchase do
authenticated_as_admin!
user = find_user(params[:user_id])
diff --git a/lib/api/validations/validators/limit.rb b/lib/api/validations/validators/limit.rb
index e8f894849a5..7e11f1d77cc 100644
--- a/lib/api/validations/validators/limit.rb
+++ b/lib/api/validations/validators/limit.rb
@@ -7,7 +7,7 @@ module API
def validate_param!(attr_name, params)
value = params[attr_name]
- return if value.size <= @option
+ return if value.nil? || value.size <= @option
raise Grape::Exceptions::Validation.new(
params: [@scope.full_name(attr_name)],
diff --git a/lib/api/version.rb b/lib/api/version.rb
index 86eb34ca589..bdce88ab827 100644
--- a/lib/api/version.rb
+++ b/lib/api/version.rb
@@ -9,7 +9,7 @@ module API
before { authenticate! }
- feature_category :not_owned
+ feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
METADATA_QUERY = <<~EOF
{
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index e90d88940a5..12dbf4792d6 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -12,7 +12,7 @@ module API
params :common_wiki_page_params do
optional :format,
type: String,
- values: Wiki::MARKUPS.values.map(&:to_s),
+ values: Wiki::VALID_USER_MARKUPS.keys.map(&:to_s),
default: 'markdown',
desc: 'Format of a wiki page. Available formats are markdown, rdoc, asciidoc and org'
end
@@ -48,7 +48,7 @@ module API
optional :version, type: String, desc: 'The version hash of a wiki page'
optional :render_html, type: Boolean, default: false, desc: 'Render content to HTML'
end
- get ':id/wikis/:slug' do
+ get ':id/wikis/:slug', urgency: :low do
authorize! :read_wiki, container
present wiki_page(params[:version]), with: Entities::WikiPage, render_html: params[:render_html]
@@ -136,7 +136,7 @@ module API
if result[:status] == :success
status(201)
- present OpenStruct.new(result[:result]), with: Entities::WikiAttachment
+ present result[:result], with: Entities::WikiAttachment
else
render_api_error!(result[:message], 400)
end