summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 13:18:24 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 13:18:24 +0000
commit0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch)
tree4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /lib/api
parent744144d28e3e7fddc117924fef88de5d9674fe4c (diff)
downloadgitlab-ce-0653e08efd039a5905f3fa4f6e9cef9f5d2f799c.tar.gz
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/admin/sidekiq.rb4
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/ci/pipelines.rb27
-rw-r--r--lib/api/ci/runners.rb50
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/dependency_proxy.rb4
-rw-r--r--lib/api/entities/application_setting.rb8
-rw-r--r--lib/api/entities/basic_project_details.rb10
-rw-r--r--lib/api/entities/blob.rb2
-rw-r--r--lib/api/entities/ci/pipeline_basic.rb4
-rw-r--r--lib/api/entities/ci/reset_registration_token_result.rb11
-rw-r--r--lib/api/entities/clusters/agent_authorization.rb13
-rw-r--r--lib/api/entities/commit_note.rb2
-rw-r--r--lib/api/entities/compare.rb4
-rw-r--r--lib/api/entities/error_tracking.rb7
-rw-r--r--lib/api/entities/global_notification_setting.rb2
-rw-r--r--lib/api/entities/project.rb19
-rw-r--r--lib/api/entities/user.rb10
-rw-r--r--lib/api/entities/user_public.rb2
-rw-r--r--lib/api/environments.rb3
-rw-r--r--lib/api/error_tracking.rb28
-rw-r--r--lib/api/error_tracking_client_keys.rb50
-rw-r--r--lib/api/error_tracking_collector.rb50
-rw-r--r--lib/api/feature_flags.rb4
-rw-r--r--lib/api/files.rb15
-rw-r--r--lib/api/generic_packages.rb4
-rw-r--r--lib/api/group_variables.rb2
-rw-r--r--lib/api/groups.rb16
-rw-r--r--lib/api/helm_packages.rb11
-rw-r--r--lib/api/helpers/issues_helpers.rb18
-rw-r--r--lib/api/helpers/members_helpers.rb44
-rw-r--r--lib/api/helpers/packages/npm.rb6
-rw-r--r--lib/api/helpers/pagination_strategies.rb36
-rw-r--r--lib/api/helpers/settings_helpers.rb16
-rw-r--r--lib/api/internal/kubernetes.rb17
-rw-r--r--lib/api/issues.rb11
-rw-r--r--lib/api/lint.rb15
-rw-r--r--lib/api/members.rb32
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/npm_project_packages.rb3
-rw-r--r--lib/api/projects.rb46
-rw-r--r--lib/api/projects_relation_builder.rb25
-rw-r--r--lib/api/repositories.rb18
-rw-r--r--lib/api/settings.rb11
-rw-r--r--lib/api/templates.rb2
-rw-r--r--lib/api/users.rb48
46 files changed, 548 insertions, 167 deletions
diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb
index d91d4a0d4d5..05eb7f8222b 100644
--- a/lib/api/admin/sidekiq.rb
+++ b/lib/api/admin/sidekiq.rb
@@ -12,11 +12,11 @@ module API
namespace 'queues' do
desc 'Drop jobs matching the given metadata from the Sidekiq queue'
params do
- Gitlab::ApplicationContext::KNOWN_KEYS.each do |key|
+ Gitlab::SidekiqQueue::ALLOWED_KEYS.each do |key|
optional key, type: String, allow_blank: false
end
- at_least_one_of(*Gitlab::ApplicationContext::KNOWN_KEYS)
+ at_least_one_of(*Gitlab::SidekiqQueue::ALLOWED_KEYS)
end
delete ':queue_name' do
result =
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 40f1b2fa9d3..d0d96858f61 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -171,6 +171,7 @@ module API
mount ::API::Deployments
mount ::API::Environments
mount ::API::ErrorTracking
+ mount ::API::ErrorTrackingClientKeys
mount ::API::ErrorTrackingCollector
mount ::API::Events
mount ::API::FeatureFlags
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
index 4d6d38f2dce..03b59e7e6ad 100644
--- a/lib/api/ci/pipelines.rb
+++ b/lib/api/ci/pipelines.rb
@@ -7,8 +7,6 @@ module API
before { authenticate_non_get! }
- feature_category :continuous_integration
-
params do
requires :id, type: String, desc: 'The project ID'
end
@@ -44,7 +42,6 @@ module API
optional :ref, type: String, desc: 'The ref of pipelines'
optional :sha, type: String, desc: 'The sha of pipelines'
optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations'
- optional :name, type: String, desc: '(deprecated) The name of the user who triggered pipelines'
optional :username, type: String, desc: 'The username of the user who triggered pipelines'
optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
@@ -54,7 +51,7 @@ module API
desc: 'Sort pipelines'
optional :source, type: String, values: ::Ci::Pipeline.sources.keys
end
- get ':id/pipelines' do
+ get ':id/pipelines', feature_category: :continuous_integration do
authorize! :read_pipeline, user_project
authorize! :read_build, user_project
@@ -70,7 +67,7 @@ module API
requires :ref, type: String, desc: 'Reference'
optional :variables, Array, desc: 'Array of variables available in the pipeline'
end
- post ':id/pipeline' do
+ post ':id/pipeline', feature_category: :continuous_integration do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20711')
authorize! :create_pipeline, user_project
@@ -97,7 +94,7 @@ module API
params do
optional :ref, type: String, desc: 'branch ref of pipeline'
end
- get ':id/pipelines/latest' do
+ get ':id/pipelines/latest', feature_category: :continuous_integration do
authorize! :read_pipeline, latest_pipeline
present latest_pipeline, with: Entities::Ci::Pipeline
@@ -110,7 +107,7 @@ module API
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
- get ':id/pipelines/:pipeline_id' do
+ get ':id/pipelines/:pipeline_id', feature_category: :continuous_integration do
authorize! :read_pipeline, pipeline
present pipeline, with: Entities::Ci::Pipeline
@@ -126,7 +123,7 @@ module API
use :pagination
end
- get ':id/pipelines/:pipeline_id/jobs' do
+ get ':id/pipelines/:pipeline_id/jobs', feature_category: :continuous_integration do
authorize!(:read_pipeline, user_project)
pipeline = user_project.all_pipelines.find(params[:pipeline_id])
@@ -149,7 +146,7 @@ module API
use :pagination
end
- get ':id/pipelines/:pipeline_id/bridges' do
+ get ':id/pipelines/:pipeline_id/bridges', feature_category: :pipeline_authoring do
authorize!(:read_build, user_project)
pipeline = user_project.all_pipelines.find(params[:pipeline_id])
@@ -169,7 +166,7 @@ module API
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
- get ':id/pipelines/:pipeline_id/variables' do
+ get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring do
authorize! :read_pipeline_variable, pipeline
present pipeline.variables, with: Entities::Ci::Variable
@@ -182,7 +179,7 @@ module API
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
- get ':id/pipelines/:pipeline_id/test_report' do
+ get ':id/pipelines/:pipeline_id/test_report', feature_category: :code_testing do
authorize! :read_build, pipeline
present pipeline.test_reports, with: TestReportEntity, details: true
@@ -195,7 +192,7 @@ module API
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
- get ':id/pipelines/:pipeline_id/test_report_summary' do
+ get ':id/pipelines/:pipeline_id/test_report_summary', feature_category: :code_testing do
authorize! :read_build, pipeline
present pipeline.test_report_summary, with: TestReportSummaryEntity
@@ -208,7 +205,7 @@ module API
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
- delete ':id/pipelines/:pipeline_id' do
+ delete ':id/pipelines/:pipeline_id', feature_category: :continuous_integration do
authorize! :destroy_pipeline, pipeline
destroy_conditionally!(pipeline) do
@@ -223,7 +220,7 @@ module API
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
- post ':id/pipelines/:pipeline_id/retry' do
+ post ':id/pipelines/:pipeline_id/retry', feature_category: :continuous_integration do
authorize! :update_pipeline, pipeline
pipeline.retry_failed(current_user)
@@ -238,7 +235,7 @@ module API
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
- post ':id/pipelines/:pipeline_id/cancel' do
+ post ':id/pipelines/:pipeline_id/cancel', feature_category: :continuous_integration do
authorize! :update_pipeline, pipeline
pipeline.cancel_running
diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb
index 7f755b1a4d4..93a40925c21 100644
--- a/lib/api/ci/runners.rb
+++ b/lib/api/ci/runners.rb
@@ -222,6 +222,56 @@ module API
end
end
+ resource :runners do
+ before { authenticate_non_get! }
+
+ desc 'Resets runner registration token' do
+ success Entities::Ci::ResetRegistrationTokenResult
+ end
+ post 'reset_registration_token' do
+ authorize! :update_runners_registration_token
+
+ ApplicationSetting.current.reset_runners_registration_token!
+ present ApplicationSetting.current_without_cache.runners_registration_token, with: Entities::Ci::ResetRegistrationTokenResult
+ 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 { authenticate_non_get! }
+
+ desc 'Resets runner registration token' do
+ success Entities::Ci::ResetRegistrationTokenResult
+ end
+ post ':id/runners/reset_registration_token' do
+ project = find_project! user_project.id
+ authorize! :update_runners_registration_token, project
+
+ project.reset_runners_token!
+ present project.runners_token, with: Entities::Ci::ResetRegistrationTokenResult
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before { authenticate_non_get! }
+
+ desc 'Resets runner registration token' do
+ success Entities::Ci::ResetRegistrationTokenResult
+ end
+ post ':id/runners/reset_registration_token' do
+ group = find_group! user_group.id
+ authorize! :update_runners_registration_token, group
+
+ group.reset_runners_token!
+ present group.runners_token, with: Entities::Ci::ResetRegistrationTokenResult
+ end
+ end
+
helpers do
def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES)
return runners unless scope.present?
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 5d8985455ad..10dc51556b9 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -336,7 +336,7 @@ module API
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
lines.each do |line|
- next unless line.new_pos == params[:line] && line.type == params[:line_type]
+ next unless line.line == params[:line] && line.type == params[:line_type]
break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos)
end
diff --git a/lib/api/dependency_proxy.rb b/lib/api/dependency_proxy.rb
index 3379bb2f029..185b8d5a15d 100644
--- a/lib/api/dependency_proxy.rb
+++ b/lib/api/dependency_proxy.rb
@@ -15,7 +15,7 @@ module API
end
end
- before do
+ after_validation do
authorize! :admin_group, user_group
end
@@ -35,6 +35,8 @@ module API
# rubocop:disable CodeReuse/Worker
PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id)
# rubocop:enable CodeReuse/Worker
+
+ status :accepted
end
end
end
diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb
index f23fce40468..465c5f4112b 100644
--- a/lib/api/entities/application_setting.rb
+++ b/lib/api/entities/application_setting.rb
@@ -27,6 +27,14 @@ module API
expose(*::ApplicationSettingsHelper.external_authorization_service_attributes)
+ # Also expose these columns under their new attribute names.
+ #
+ # TODO: Once we rename the columns, we have to swap this around and keep supporting the old names until v5.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/340031
+ expose :throttle_unauthenticated_enabled, as: :throttle_unauthenticated_web_enabled
+ expose :throttle_unauthenticated_period_in_seconds, as: :throttle_unauthenticated_web_period_in_seconds
+ expose :throttle_unauthenticated_requests_per_period, as: :throttle_unauthenticated_web_requests_per_period
+
# support legacy names, can be removed in v5
expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
expose :password_authentication_enabled_for_web, as: :signin_enabled
diff --git a/lib/api/entities/basic_project_details.rb b/lib/api/entities/basic_project_details.rb
index 0b231906ccd..5c33af86b84 100644
--- a/lib/api/entities/basic_project_details.rb
+++ b/lib/api/entities/basic_project_details.rb
@@ -43,12 +43,20 @@ module API
# N+1 is solved then by using `subject.topics.map(&:name)`
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555
projects_relation.preload(:project_feature, :route)
- .preload(:import_state, :topics)
+ .preload(:import_state, :topics, :topics_acts_as_taggable)
.preload(:auto_devops)
.preload(namespace: [:route, :owner])
end
# rubocop: enable CodeReuse/ActiveRecord
+ def self.execute_batch_counting(projects_relation)
+ # Call the count methods on every project, so the BatchLoader would load them all at
+ # once when the entities are rendered
+ projects_relation.each(&:forks_count)
+
+ super
+ end
+
private
alias_method :project, :object
diff --git a/lib/api/entities/blob.rb b/lib/api/entities/blob.rb
index b14ef127b68..12700d99865 100644
--- a/lib/api/entities/blob.rb
+++ b/lib/api/entities/blob.rb
@@ -10,7 +10,7 @@ module API
# in the future we can only return the filename here without the leading
# directory path.
# https://gitlab.com/gitlab-org/gitlab/issues/34521
- expose :filename, &:path
+ expose :path, as: :filename
expose :id
expose :ref
expose :startline
diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb
index 8086062dc9b..4d56176bdb3 100644
--- a/lib/api/entities/ci/pipeline_basic.rb
+++ b/lib/api/entities/ci/pipeline_basic.rb
@@ -4,11 +4,9 @@ module API
module Entities
module Ci
class PipelineBasic < Grape::Entity
- expose :id, :project_id, :sha, :ref, :status
+ expose :id, :project_id, :sha, :ref, :status, :source
expose :created_at, :updated_at
- expose :source, if: ->(pipeline, options) { ::Feature.enabled?(:pipeline_source_filter, options[:project], default_enabled: :yaml) }
-
expose :web_url do |pipeline, _options|
Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline)
end
diff --git a/lib/api/entities/ci/reset_registration_token_result.rb b/lib/api/entities/ci/reset_registration_token_result.rb
new file mode 100644
index 00000000000..23426432f68
--- /dev/null
+++ b/lib/api/entities/ci/reset_registration_token_result.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ class ResetRegistrationTokenResult < Grape::Entity
+ expose(:token) {|object| object}
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/clusters/agent_authorization.rb b/lib/api/entities/clusters/agent_authorization.rb
new file mode 100644
index 00000000000..6c533fff105
--- /dev/null
+++ b/lib/api/entities/clusters/agent_authorization.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Clusters
+ class AgentAuthorization < Grape::Entity
+ expose :agent_id, as: :id
+ expose :project, with: Entities::ProjectIdentity, as: :config_project
+ expose :config, as: :configuration
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/commit_note.rb b/lib/api/entities/commit_note.rb
index d08b6fc8225..fe91712b48d 100644
--- a/lib/api/entities/commit_note.rb
+++ b/lib/api/entities/commit_note.rb
@@ -5,7 +5,7 @@ module API
class CommitNote < Grape::Entity
expose :note
expose(:path) { |note| note.diff_file.try(:file_path) if note.diff_note? }
- expose(:line) { |note| note.diff_line.try(:new_line) if note.diff_note? }
+ expose(:line) { |note| note.diff_line.try(:line) if note.diff_note? }
expose(:line_type) { |note| note.diff_line.try(:type) if note.diff_note? }
expose :author, using: Entities::UserBasic
expose :created_at
diff --git a/lib/api/entities/compare.rb b/lib/api/entities/compare.rb
index fe2f03db2af..75a36d9bb01 100644
--- a/lib/api/entities/compare.rb
+++ b/lib/api/entities/compare.rb
@@ -20,6 +20,10 @@ module API
end
expose :same, as: :compare_same_ref
+
+ expose :web_url do |compare, _|
+ Gitlab::UrlBuilder.build(compare)
+ end
end
end
end
diff --git a/lib/api/entities/error_tracking.rb b/lib/api/entities/error_tracking.rb
index a38e00ca295..b55cba05ea0 100644
--- a/lib/api/entities/error_tracking.rb
+++ b/lib/api/entities/error_tracking.rb
@@ -10,6 +10,13 @@ module API
expose :api_url
expose :integrated
end
+
+ class ClientKey < Grape::Entity
+ expose :id
+ expose :active
+ expose :public_key
+ expose :sentry_dsn
+ end
end
end
end
diff --git a/lib/api/entities/global_notification_setting.rb b/lib/api/entities/global_notification_setting.rb
index f3ca64347f0..f35efad5d01 100644
--- a/lib/api/entities/global_notification_setting.rb
+++ b/lib/api/entities/global_notification_setting.rb
@@ -4,7 +4,7 @@ module API
module Entities
class GlobalNotificationSetting < Entities::NotificationSetting
expose :notification_email do |notification_setting, options|
- notification_setting.user.notification_email
+ notification_setting.user.notification_email_or_default
end
end
end
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 890b42ed8c8..b0e53ac3794 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -126,6 +126,10 @@ module API
expose :keep_latest_artifacts_available?, as: :keep_latest_artifact
# rubocop: disable CodeReuse/ActiveRecord
+ def self.preload_resource(project)
+ ActiveRecord::Associations::Preloader.new.preload(project, project_group_links: { group: :route })
+ end
+
def self.preload_relation(projects_relation, options = {})
# Preloading topics, should be done with using only `:topics`,
# as `:topics` are defined as: `has_many :topics, through: :taggings`
@@ -140,12 +144,21 @@ module API
.preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,
- forked_from_project: [:route, :topics, :group, :project_feature, namespace: [:route, :owner]])
+ forked_from_project: [:route, :topics, :topics_acts_as_taggable, :group, :project_feature, namespace: [:route, :owner]])
end
# rubocop: enable CodeReuse/ActiveRecord
- def self.forks_counting_projects(projects_relation)
- projects_relation + projects_relation.map(&:forked_from_project).compact
+ def self.execute_batch_counting(projects_relation)
+ # Call the count methods on every project, so the BatchLoader would load them all at
+ # once when the entities are rendered
+ projects_relation.each(&:open_issues_count)
+ projects_relation.map(&:forked_from_project).compact.each(&:forks_count)
+
+ super
+ end
+
+ def self.repositories_for_preload(projects_relation)
+ super + projects_relation.map(&:forked_from_project).compact.map(&:repository)
end
end
end
diff --git a/lib/api/entities/user.rb b/lib/api/entities/user.rb
index 973e80dd5ef..5c46233a639 100644
--- a/lib/api/entities/user.rb
+++ b/lib/api/entities/user.rb
@@ -4,8 +4,10 @@ module API
module Entities
class User < UserBasic
include UsersHelper
+ include ActionView::Helpers::SanitizeHelper
+
expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) }
- expose :bio, :bio_html, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :pronouns
+ expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :pronouns
expose :bot?, as: :bot
expose :work_information do |user|
work_information(user)
@@ -16,6 +18,12 @@ module API
expose :following, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user|
user.followees.size
end
+
+ # This is only for multi version compatibility reasons, as we removed user.bio_html
+ # to be removed in 14.4
+ expose :bio_html do |user|
+ strip_tags(user.bio)
+ end
end
end
end
diff --git a/lib/api/entities/user_public.rb b/lib/api/entities/user_public.rb
index 78f088d3c1a..5d0e464abe1 100644
--- a/lib/api/entities/user_public.rb
+++ b/lib/api/entities/user_public.rb
@@ -14,7 +14,7 @@ module API
expose :two_factor_enabled?, as: :two_factor_enabled
expose :external
expose :private_profile
- expose :commit_email
+ expose :commit_email_or_default, as: :commit_email
end
end
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index e50da4264b5..c032b80e39b 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -58,7 +58,8 @@ module API
end
params do
requires :environment_id, type: Integer, desc: 'The environment ID'
- optional :name, type: String, desc: 'The new environment name'
+ # 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" }
end
diff --git a/lib/api/error_tracking.rb b/lib/api/error_tracking.rb
index 3abf2831bd3..369efe3bf8c 100644
--- a/lib/api/error_tracking.rb
+++ b/lib/api/error_tracking.rb
@@ -6,24 +6,30 @@ module API
feature_category :error_tracking
+ helpers do
+ def project_setting
+ @project_setting ||= user_project.error_tracking_setting
+ 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 do
+ authorize! :admin_operations, user_project
+
+ not_found!('Error Tracking Setting') unless project_setting
+ end
+
desc 'Get error tracking settings for the project' do
detail 'This feature was introduced in GitLab 12.7.'
success Entities::ErrorTracking::ProjectSetting
end
get ':id/error_tracking/settings' do
- authorize! :admin_operations, user_project
-
- setting = user_project.error_tracking_setting
-
- not_found!('Error Tracking Setting') unless setting
-
- present setting, with: Entities::ErrorTracking::ProjectSetting
+ present project_setting, with: Entities::ErrorTracking::ProjectSetting
end
desc 'Enable or disable error tracking settings for the project' do
@@ -36,12 +42,6 @@ module API
end
patch ':id/error_tracking/settings/' do
- authorize! :admin_operations, user_project
-
- setting = user_project.error_tracking_setting
-
- not_found!('Error Tracking Setting') unless setting
-
update_params = {
error_tracking_setting_attributes: { enabled: params[:active] }
}
@@ -53,7 +53,7 @@ module API
result = ::Projects::Operations::UpdateService.new(user_project, current_user, update_params).execute
if result[:status] == :success
- present setting, with: Entities::ErrorTracking::ProjectSetting
+ present project_setting, with: Entities::ErrorTracking::ProjectSetting
else
result
end
diff --git a/lib/api/error_tracking_client_keys.rb b/lib/api/error_tracking_client_keys.rb
new file mode 100644
index 00000000000..eaa84b7186c
--- /dev/null
+++ b/lib/api/error_tracking_client_keys.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module API
+ class ErrorTrackingClientKeys < ::API::Base
+ before { authenticate! }
+
+ feature_category :error_tracking
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ segment ':id/error_tracking' do
+ before do
+ authorize! :admin_operations, user_project
+ end
+
+ desc 'List all client keys' do
+ detail 'This feature was introduced in GitLab 14.3.'
+ success Entities::ErrorTracking::ClientKey
+ end
+ get '/client_keys' do
+ collection = user_project.error_tracking_client_keys
+
+ present paginate(collection), with: Entities::ErrorTracking::ClientKey
+ end
+
+ desc 'Create a client key' do
+ detail 'This feature was introduced in GitLab 14.3.'
+ success Entities::ErrorTracking::ClientKey
+ end
+ post '/client_keys' do
+ key = user_project.error_tracking_client_keys.create!
+
+ present key, with: Entities::ErrorTracking::ClientKey
+ end
+
+ desc 'Delete a client key' do
+ detail 'This feature was introduced in GitLab 14.3.'
+ success Entities::ErrorTracking::ClientKey
+ end
+ delete '/client_keys/:key_id' do
+ key = user_project.error_tracking_client_keys.find(params[:key_id])
+ key.destroy!
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/error_tracking_collector.rb b/lib/api/error_tracking_collector.rb
index 13e8e476808..b1e0f6a858a 100644
--- a/lib/api/error_tracking_collector.rb
+++ b/lib/api/error_tracking_collector.rb
@@ -8,6 +8,8 @@ module API
feature_category :error_tracking
content_type :envelope, 'application/x-sentry-envelope'
+ content_type :json, 'application/json'
+ content_type :txt, 'text/plain'
default_format :envelope
before do
@@ -33,17 +35,24 @@ module API
end
def active_client_key?
+ public_key = extract_public_key
+
+ find_client_key(public_key)
+ end
+
+ def extract_public_key
+ # Some SDK send public_key as a param. In this case we don't need to parse headers.
+ return params[:sentry_key] if params[:sentry_key].present?
+
begin
- public_key = ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key]
+ ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key]
rescue StandardError
bad_request!('Failed to parse sentry request')
end
-
- find_client_key(public_key)
end
end
- desc 'Submit error tracking event to the project' do
+ desc 'Submit error tracking event to the project as envelope' do
detail 'This feature was introduced in GitLab 14.1.'
end
params do
@@ -89,5 +98,38 @@ module API
# it is safe only for submission of new events.
no_content!
end
+
+ desc 'Submit error tracking event to the project' do
+ detail 'This feature was introduced in GitLab 14.1.'
+ end
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ post 'error_tracking/collector/api/:id/store' do
+ # There is a reason why we have such uncommon path.
+ # We depend on a client side error tracking software which
+ # modifies URL for its own reasons.
+ #
+ # When we give user a URL like this
+ # HOST/api/v4/error_tracking/collector/123
+ #
+ # Then error tracking software will convert it like this:
+ # HOST/api/v4/error_tracking/collector/api/123/store/
+
+ begin
+ parsed_body = Gitlab::Json.parse(request.body.read)
+ rescue StandardError
+ bad_request!('Failed to parse sentry request')
+ end
+
+ ::ErrorTracking::CollectErrorService
+ .new(project, nil, event: parsed_body)
+ .execute
+
+ # Collector should never return any information back.
+ # Because DSN and public key are designed for public use,
+ # it is safe only for submission of new events.
+ no_content!
+ end
end
end
diff --git a/lib/api/feature_flags.rb b/lib/api/feature_flags.rb
index fb5858bc10b..c1f958ac007 100644
--- a/lib/api/feature_flags.rb
+++ b/lib/api/feature_flags.rb
@@ -118,7 +118,7 @@ module API
put do
authorize_update_feature_flag!
exclude_legacy_flags_check!
- render_api_error!('PUT operations are not supported for legacy feature flags', :unprocessable_entity) if feature_flag.legacy_flag?
+ render_api_error!('PUT operations are not supported for legacy feature flags', :unprocessable_entity) unless feature_flag.new_version_flag?
attrs = declared_params(include_missing: false)
@@ -207,7 +207,7 @@ module API
end
def exclude_legacy_flags_check!
- if feature_flag.legacy_flag?
+ unless feature_flag.new_version_flag?
not_found!
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index f3de7fbe96b..9d2b7cce837 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -35,10 +35,9 @@ module API
not_found!('Commit') unless @commit
@repo = user_project.repository
- @blob = @repo.blob_at(@commit.sha, params[:file_path])
+ @blob = @repo.blob_at(@commit.sha, params[:file_path], limit: Gitlab::Git::Blob::LFS_POINTER_MAX_SIZE)
not_found!('File') unless @blob
- @blob.load_all_data!
end
def commit_response(attrs)
@@ -48,13 +47,21 @@ module API
}
end
+ def content_sha
+ Rails.cache.fetch("blob_content_sha256:#{user_project.full_path}:#{@blob.id}") do
+ @blob.load_all_data!
+
+ Digest::SHA256.hexdigest(@blob.data)
+ end
+ end
+
def blob_data
{
file_name: @blob.name,
file_path: @blob.path,
size: @blob.size,
encoding: "base64",
- content_sha256: Digest::SHA256.hexdigest(@blob.data),
+ content_sha256: content_sha,
ref: params[:ref],
blob_id: @blob.id,
commit_id: @commit.id,
@@ -154,6 +161,8 @@ module API
get ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
+ @blob.load_all_data!
+
data = blob_data
set_http_headers(data)
diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb
index a57d6bbcd2a..5e184d35255 100644
--- a/lib/api/generic_packages.rb
+++ b/lib/api/generic_packages.rb
@@ -62,7 +62,7 @@ module API
authorize_upload!(project)
bad_request!('File is too large') if max_file_size_exceeded?
- ::Gitlab::Tracking.event(self.options[:for].name, 'push_package', user: current_user, project: project, namespace: project.namespace)
+ track_package_event('push_package', :generic, project: project, user: current_user, namespace: project.namespace)
create_package_file_params = declared_params.merge(build: current_authenticated_job)
::Packages::Generic::CreatePackageFileService
@@ -96,7 +96,7 @@ module API
package = ::Packages::Generic::PackageFinder.new(project).execute!(params[:package_name], params[:package_version])
package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute!
- ::Gitlab::Tracking.event(self.options[:for].name, 'pull_package', user: current_user, project: project, namespace: project.namespace)
+ track_package_event('pull_package', :generic, project: project, user: current_user, namespace: project.namespace)
present_carrierwave_file!(package_file.file)
end
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index 13daf05fc78..e726f9b61cc 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -6,7 +6,7 @@ module API
before { authenticate! }
before { authorize! :admin_group, user_group }
- feature_category :continuous_integration
+ feature_category :pipeline_authoring
helpers ::API::Helpers::VariablesHelpers
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 0896357cc73..a1123b6291b 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -108,6 +108,20 @@ module API
present paginate(groups), options
end
+ def present_groups_with_pagination_strategies(params, groups)
+ return present_groups(params, groups) if current_user.present? || Feature.disabled?(:keyset_pagination_for_groups_api)
+
+ options = {
+ with: Entities::Group,
+ current_user: nil,
+ statistics: false
+ }
+
+ groups, options = with_custom_attributes(groups, options)
+
+ present paginate_with_strategies(groups), options
+ end
+
def delete_group(group)
destroy_conditionally!(group) do |group|
::Groups::DestroyService.new(group, current_user).async_execute
@@ -168,7 +182,7 @@ module API
end
get do
groups = find_groups(declared_params(include_missing: false), params[:id])
- present_groups params, groups
+ present_groups_with_pagination_strategies params, groups
end
desc 'Create a group. Available only for users who can create groups.' do
diff --git a/lib/api/helm_packages.rb b/lib/api/helm_packages.rb
index 4280744d8b4..8a7e84c9f87 100644
--- a/lib/api/helm_packages.rb
+++ b/lib/api/helm_packages.rb
@@ -44,15 +44,10 @@ module API
get ":channel/index.yaml" do
authorize_read_package!(authorized_user_project)
- package_files = Packages::Helm::PackageFilesFinder.new(
- authorized_user_project,
- params[:channel],
- order_by: 'created_at',
- sort: 'desc'
- ).execute
+ packages = Packages::Helm::PackagesFinder.new(authorized_user_project, params[:channel]).execute
env['api.format'] = :yaml
- present ::Packages::Helm::IndexPresenter.new(authorized_user_project, params[:id], package_files),
+ present ::Packages::Helm::IndexPresenter.new(params[:id], params[:channel], packages),
with: ::API::Entities::Helm::Index
end
@@ -66,7 +61,7 @@ module API
get ":channel/charts/:file_name.tgz", requirements: FILE_NAME_REQUIREMENTS do
authorize_read_package!(authorized_user_project)
- package_file = Packages::Helm::PackageFilesFinder.new(authorized_user_project, params[:channel], file_name: "#{params[:file_name]}.tgz").execute.last!
+ package_file = Packages::Helm::PackageFilesFinder.new(authorized_user_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)
diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb
index b1954f8ece9..185a10a250c 100644
--- a/lib/api/helpers/issues_helpers.rb
+++ b/lib/api/helpers/issues_helpers.rb
@@ -34,7 +34,17 @@ module API
end
def self.sort_options
- %w[created_at updated_at priority due_date relative_position label_priority milestone_due popularity]
+ %w[
+ created_at
+ due_date
+ label_priority
+ milestone_due
+ popularity
+ priority
+ relative_position
+ title
+ updated_at
+ ]
end
def issue_finder(args = {})
@@ -43,9 +53,11 @@ module API
args.delete(:id)
args[:not] ||= {}
args[:milestone_title] ||= args.delete(:milestone)
- args[:not][:milestone_title] ||= args[:not]&.delete(:milestone)
+ args[:milestone_wildcard_id] ||= args.delete(:milestone_id)
+ args[:not][:milestone_title] ||= args[:not].delete(:milestone)
+ args[:not][:milestone_wildcard_id] ||= args[:not].delete(:milestone_id)
args[:label_name] ||= args.delete(:labels)
- args[:not][:label_name] ||= args[:not]&.delete(:labels)
+ args[:not][:label_name] ||= args[:not].delete(:labels)
args[:scope] = args[:scope].underscore if args[:scope]
args[:sort] = "#{args[:order_by]}_#{args[:sort]}"
args[:issue_types] ||= args.delete(:issue_type)
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index e72bbb931f0..1e89f9f97a2 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -50,24 +50,48 @@ module API
GroupMembersFinder.new(group).execute
end
- def create_member(current_user, user, source, params)
- source.add_user(user, params[:access_level], current_user: current_user, expires_at: params[:expires_at])
+ def present_members(members)
+ present members, with: Entities::Member, current_user: current_user, show_seat_info: params[:show_seat_info]
end
- def track_areas_of_focus(member, areas_of_focus)
- return unless areas_of_focus
+ def present_member_invitations(invitations)
+ present invitations, with: Entities::Invitation, current_user: current_user
+ end
+
+ def add_single_member_by_user_id(create_service_params)
+ source = create_service_params[:source]
+ user_id = create_service_params[:user_ids]
+ user = User.find_by(id: user_id) # rubocop: disable CodeReuse/ActiveRecord
+
+ if user
+ conflict!('Member already exists') if member_already_exists?(source, user_id)
+
+ instance = ::Members::CreateService.new(current_user, create_service_params)
+ instance.execute
+
+ not_allowed! if instance.membership_locked # This currently can only be reached in EE if group membership is locked
- areas_of_focus.each do |area_of_focus|
- Gitlab::Tracking.event(::Members::CreateService.name, 'area_of_focus', label: area_of_focus, property: member.id.to_s)
+ member = instance.single_member
+ render_validation_error!(member) if member.invalid?
+
+ present_members(member)
+ else
+ not_found!('User')
end
end
- def present_members(members)
- present members, with: Entities::Member, current_user: current_user, show_seat_info: params[:show_seat_info]
+ def add_multiple_members?(user_id)
+ user_id.include?(',')
end
- def present_member_invitations(invitations)
- present invitations, with: Entities::Invitation, current_user: current_user
+ def add_single_member?(user_id)
+ user_id.present?
+ end
+
+ private
+
+ def member_already_exists?(source, user_id)
+ source.members.exists?(user_id: user_id) # rubocop: disable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/api/helpers/packages/npm.rb b/lib/api/helpers/packages/npm.rb
index ce5db52fdbc..34e126c73fc 100644
--- a/lib/api/helpers/packages/npm.rb
+++ b/lib/api/helpers/packages/npm.rb
@@ -57,7 +57,11 @@ module API
.by_path(namespace_path)
next unless namespace
- finder = ::Packages::Npm::PackageFinder.new(package_name, namespace: namespace)
+ finder = ::Packages::Npm::PackageFinder.new(
+ package_name,
+ namespace: namespace,
+ last_of_each_version: false
+ )
finder.last&.project_id
end
diff --git a/lib/api/helpers/pagination_strategies.rb b/lib/api/helpers/pagination_strategies.rb
index 61cff37e4ab..8c2186768ea 100644
--- a/lib/api/helpers/pagination_strategies.rb
+++ b/lib/api/helpers/pagination_strategies.rb
@@ -3,10 +3,16 @@
module API
module Helpers
module PaginationStrategies
- def paginate_with_strategies(relation, request_scope)
+ def paginate_with_strategies(relation, request_scope = nil)
paginator = paginator(relation, request_scope)
- yield(paginator.paginate(relation)).tap do |records, _|
+ result = if block_given?
+ yield(paginator.paginate(relation))
+ else
+ paginator.paginate(relation)
+ end
+
+ result.tap do |records, _|
paginator.finalize(records)
end
end
@@ -20,17 +26,31 @@ module API
private
def keyset_paginator(relation)
- request_context = Gitlab::Pagination::Keyset::RequestContext.new(self)
- unless Gitlab::Pagination::Keyset.available?(request_context, relation)
+ if cursor_based_keyset_pagination_supported?(relation)
+ request_context_class = Gitlab::Pagination::Keyset::CursorBasedRequestContext
+ paginator_class = Gitlab::Pagination::Keyset::CursorPager
+ availability_checker = Gitlab::Pagination::CursorBasedKeyset
+ else
+ request_context_class = Gitlab::Pagination::Keyset::RequestContext
+ paginator_class = Gitlab::Pagination::Keyset::Pager
+ availability_checker = Gitlab::Pagination::Keyset
+ end
+
+ request_context = request_context_class.new(self)
+
+ unless availability_checker.available?(request_context, relation)
return error!('Keyset pagination is not yet available for this type of request', 405)
end
- Gitlab::Pagination::Keyset::Pager.new(request_context)
+ paginator_class.new(request_context)
end
def offset_paginator(relation, request_scope)
offset_limit = limit_for_scope(request_scope)
- if Gitlab::Pagination::Keyset.available_for_type?(relation) && offset_limit_exceeded?(offset_limit)
+ if (Gitlab::Pagination::Keyset.available_for_type?(relation) ||
+ cursor_based_keyset_pagination_supported?(relation)) &&
+ offset_limit_exceeded?(offset_limit)
+
return error!("Offset pagination has a maximum allowed offset of #{offset_limit} " \
"for requests that return objects of type #{relation.klass}. " \
"Remaining records can be retrieved using keyset pagination.", 405)
@@ -39,6 +59,10 @@ module API
Gitlab::Pagination::OffsetPagination.new(self)
end
+ def cursor_based_keyset_pagination_supported?(relation)
+ Gitlab::Pagination::CursorBasedKeyset.available_for_type?(relation)
+ end
+
def keyset_pagination_enabled?
params[:pagination] == 'keyset'
end
diff --git a/lib/api/helpers/settings_helpers.rb b/lib/api/helpers/settings_helpers.rb
index a3ea1057bc8..82de4917f0b 100644
--- a/lib/api/helpers/settings_helpers.rb
+++ b/lib/api/helpers/settings_helpers.rb
@@ -10,10 +10,18 @@ module API
end
def self.optional_attributes
- [*::ApplicationSettingsHelper.visible_attributes,
- *::ApplicationSettingsHelper.external_authorization_service_attributes,
- *::ApplicationSettingsHelper.deprecated_attributes,
- :performance_bar_allowed_group_id].freeze
+ [
+ *::ApplicationSettingsHelper.visible_attributes,
+ *::ApplicationSettingsHelper.external_authorization_service_attributes,
+ *::ApplicationSettingsHelper.deprecated_attributes,
+ :performance_bar_allowed_group_id,
+ # TODO: Once we rename these columns, we can remove them here and add the old
+ # names to `ApplicationSettingsHelper.deprecated_attributes` instead.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/340031
+ :throttle_unauthenticated_web_enabled,
+ :throttle_unauthenticated_web_period_in_seconds,
+ :throttle_unauthenticated_web_requests_per_period
+ ].freeze
end
end
end
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index 7af5c2ad2ee..d1ad3c1feb1 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -100,6 +100,23 @@ module API
end
end
+ namespace 'kubernetes/agent_configuration' do
+ desc 'POST agent configuration' do
+ detail 'Store configuration for an agent'
+ end
+ params do
+ requires :agent_id, type: Integer, desc: 'ID of the configured Agent'
+ requires :agent_config, type: JSON, desc: 'Configuration for the Agent'
+ end
+ post '/' do
+ agent = Clusters::Agent.find(params[:agent_id])
+
+ Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute
+
+ no_content!
+ end
+ end
+
namespace 'kubernetes/usage_metrics' do
desc 'POST usage metrics' do
detail 'Updates usage metrics for agent'
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index a6565f913e3..39ce6e0b062 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -14,6 +14,10 @@ module API
params :negatable_issue_filter_params do
optional :labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
+ optional :milestone_id, types: String, values: %w[Any None Upcoming Started],
+ desc: 'Return issues assigned to milestones without the specified timebox value ("Any", "None", "Upcoming" or "Started")'
+ mutually_exclusive :milestone_id, :milestone
+
optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of issues'
optional :author_id, type: Integer, desc: 'Return issues which are not authored by the user with the given ID'
@@ -32,9 +36,14 @@ module API
params :issues_stats_params do
optional :labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
+ # 'milestone_id' only accepts wildcard values 'Any', 'None', 'Upcoming', 'Started'
+ # the param has '_id' in the name to keep consistency (ex. assignee_id accepts id and wildcard values).
+ optional :milestone_id, types: String, values: %w[Any None Upcoming Started],
+ desc: 'Return issues assigned to milestones with the specified timebox value ("Any", "None", "Upcoming" or "Started")'
optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of issues'
optional :search, type: String, desc: 'Search issues for text present in the title, description, or any combination of these'
optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma'
+ mutually_exclusive :milestone_id, :milestone
optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
optional :author_username, type: String, desc: 'Return issues which are authored by the user with the given username'
@@ -69,7 +78,7 @@ module API
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
optional :order_by, type: String, values: Helpers::IssuesHelpers.sort_options, default: 'created_at',
- desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
+ desc: 'Return issues ordered by `created_at`, `due_date`, `label_priority`, `milestone_due`, `popularity`, `priority`, `relative_position`, `title`, or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '',
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index 945cdf3edb2..fa871b4bc57 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -13,18 +13,13 @@ module API
post '/lint' do
unauthorized! if (Gitlab::CurrentSettings.signup_disabled? || Gitlab::CurrentSettings.signup_limited?) && current_user.nil?
- result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute
+ result = Gitlab::Ci::Lint.new(project: nil, current_user: current_user)
+ .validate(params[:content], dry_run: false)
status 200
-
- response = if result.errors.empty?
- { status: 'valid', errors: [], warnings: result.warnings }
- else
- { status: 'invalid', errors: result.errors, warnings: result.warnings }
- end
-
- response.tap do |response|
- response[:merged_yaml] = result.merged_yaml if params[:include_merged_yaml]
+ Entities::Ci::Lint::Result.represent(result, current_user: current_user).serializable_hash.tap do |presented_result|
+ presented_result[:status] = presented_result[:valid] ? 'valid' : 'invalid'
+ presented_result.delete(:merged_yaml) unless params[:include_merged_yaml]
end
end
end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 7130635281a..332520ccd26 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -96,42 +96,22 @@ module API
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api'
optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
end
- # rubocop: disable CodeReuse/ActiveRecord
+
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)
- if params[:user_id].to_s.include?(',')
- create_service_params = params.except(:user_id).merge({ user_ids: params[:user_id], source: source })
+ user_id = params[:user_id].to_s
+ create_service_params = params.except(:user_id).merge({ user_ids: user_id, source: source })
+ if add_multiple_members?(user_id)
::Members::CreateService.new(current_user, create_service_params).execute
- elsif params[:user_id].present?
- member = source.members.find_by(user_id: params[:user_id])
- conflict!('Member already exists') if member
-
- user = User.find_by_id(params[:user_id])
- not_found!('User') unless user
-
- member = create_member(current_user, user, source, params)
-
- if !member
- not_allowed! # This currently can only be reached in EE
- elsif member.valid? && member.persisted?
- present_members(member)
- Gitlab::Tracking.event(::Members::CreateService.name,
- 'create_member',
- label: params[:invite_source],
- property: 'existing_user',
- user: current_user)
- track_areas_of_focus(member, params[:areas_of_focus])
- else
- render_validation_error!(member)
- end
+ elsif add_single_member?(user_id)
+ add_single_member_by_user_id(create_service_params)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Updates a member of a group or project.' do
success Entities::Member
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 7ab57982907..34af9eab511 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -202,7 +202,7 @@ module API
options[:project] = user_project
if Feature.enabled?(:api_caching_merge_requests, user_project, type: :development, default_enabled: :yaml)
- present_cached merge_requests, expires_in: 10.minutes, **options
+ present_cached merge_requests, expires_in: 2.days, **options
else
present merge_requests, options
end
diff --git a/lib/api/npm_project_packages.rb b/lib/api/npm_project_packages.rb
index 7ff4439ce04..dbfc0a61577 100644
--- a/lib/api/npm_project_packages.rb
+++ b/lib/api/npm_project_packages.rb
@@ -48,14 +48,13 @@ module API
put ':package_name', requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!(project)
- track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, user: current_user, namespace: project.namespace)
-
created_package = ::Packages::Npm::CreatePackageService
.new(project, current_user, params.merge(build: current_authenticated_job)).execute
if created_package[:status] == :error
render_api_error!(created_package[:message], created_package[:http_status])
else
+ track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, user: current_user, namespace: project.namespace)
created_package
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 28bcb382ecf..a92d904be84 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -152,6 +152,12 @@ module API
ProjectsFinder.new(current_user: current_user, params: project_params).execute
end
+ def present_project(project, options = {})
+ options[:with].preload_resource(project) if options[:with].respond_to?(:preload_resource)
+
+ present project, options
+ end
+
def present_projects(projects, options = {})
verify_statistics_order_by_projects!
@@ -264,9 +270,9 @@ module API
project = ::Projects::CreateService.new(current_user, attrs).execute
if project.saved?
- present project, with: Entities::Project,
- user_can_admin_project: can?(current_user, :admin_project, project),
- current_user: current_user
+ present_project project, with: Entities::Project,
+ user_can_admin_project: can?(current_user, :admin_project, project),
+ current_user: current_user
else
if project.errors[:limit_reached].present?
error!(project.errors[:limit_reached], 403)
@@ -301,9 +307,9 @@ module API
project = ::Projects::CreateService.new(user, attrs).execute
if project.saved?
- present project, with: Entities::Project,
- user_can_admin_project: can?(current_user, :admin_project, project),
- current_user: current_user
+ present_project project, with: Entities::Project,
+ user_can_admin_project: can?(current_user, :admin_project, project),
+ current_user: current_user
else
render_validation_error!(project)
end
@@ -336,7 +342,7 @@ module API
project, options = with_custom_attributes(user_project, options)
- present project, options
+ present_project project, options
end
desc 'Fork new project for the current user or provided namespace.' do
@@ -376,9 +382,11 @@ module API
if forked_project.errors.any?
conflict!(forked_project.errors.messages)
else
- present forked_project, with: Entities::Project,
- user_can_admin_project: can?(current_user, :admin_project, forked_project),
- current_user: current_user
+ present_project forked_project, {
+ with: Entities::Project,
+ user_can_admin_project: can?(current_user, :admin_project, forked_project),
+ current_user: current_user
+ }
end
end
@@ -427,9 +435,9 @@ module API
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
if result[:status] == :success
- present user_project, with: Entities::Project,
- user_can_admin_project: can?(current_user, :admin_project, user_project),
- current_user: current_user
+ present_project user_project, with: Entities::Project,
+ user_can_admin_project: can?(current_user, :admin_project, user_project),
+ current_user: current_user
else
render_validation_error!(user_project)
end
@@ -443,7 +451,7 @@ module API
::Projects::UpdateService.new(user_project, current_user, archived: true).execute
- present user_project, with: Entities::Project, current_user: current_user
+ present_project user_project, with: Entities::Project, current_user: current_user
end
desc 'Unarchive a project' do
@@ -454,7 +462,7 @@ module API
::Projects::UpdateService.new(user_project, current_user, archived: false).execute
- present user_project, with: Entities::Project, current_user: current_user
+ present_project user_project, with: Entities::Project, current_user: current_user
end
desc 'Star a project' do
@@ -467,7 +475,7 @@ module API
current_user.toggle_star(user_project)
user_project.reset
- present user_project, with: Entities::Project, current_user: current_user
+ present_project user_project, with: Entities::Project, current_user: current_user
end
end
@@ -479,7 +487,7 @@ module API
current_user.toggle_star(user_project)
user_project.reset
- present user_project, with: Entities::Project, current_user: current_user
+ present_project user_project, with: Entities::Project, current_user: current_user
else
not_modified!
end
@@ -528,7 +536,7 @@ module API
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
if result
- present user_project.reset, with: Entities::Project, current_user: current_user
+ present_project user_project.reset, with: Entities::Project, current_user: current_user
else
render_api_error!("Project already forked", 409) if user_project.forked?
end
@@ -698,7 +706,7 @@ module API
result = ::Projects::TransferService.new(user_project, current_user).execute(namespace)
if result
- present user_project, with: Entities::Project, current_user: current_user
+ present_project user_project, with: Entities::Project, current_user: current_user
else
render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400)
end
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
index 6dfd82d109f..db46602cd90 100644
--- a/lib/api/projects_relation_builder.rb
+++ b/lib/api/projects_relation_builder.rb
@@ -7,28 +7,35 @@ module API
class_methods do
def prepare_relation(projects_relation, options = {})
projects_relation = preload_relation(projects_relation, options)
+
execute_batch_counting(projects_relation)
- # Call the forks count method on every project, so the BatchLoader would load them all at
- # once when the entities are rendered
- projects_relation.each(&:forks_count)
+
+ preload_repository_cache(projects_relation)
projects_relation
end
+ # This is overridden by the specific Entity class to
+ # preload assocations that it needs
def preload_relation(projects_relation, options = {})
projects_relation
end
- def forks_counting_projects(projects_relation)
- projects_relation
+ # This is overridden by the specific Entity class to
+ # batch load certain counts
+ def execute_batch_counting(projects_relation)
end
- def batch_open_issues_counting(projects_relation)
- ::Projects::BatchOpenIssuesCountService.new(projects_relation).refresh_cache
+ def preload_repository_cache(projects_relation)
+ repositories = repositories_for_preload(projects_relation)
+
+ Gitlab::RepositoryCache::Preloader.new(repositories).preload( # rubocop:disable CodeReuse/ActiveRecord
+ %i[exists? root_ref has_visible_content? avatar readme_path]
+ )
end
- def execute_batch_counting(projects_relation)
- batch_open_issues_counting(projects_relation)
+ def repositories_for_preload(projects_relation)
+ projects_relation.map(&:repository)
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 20320d1b7ae..3c9255e3117 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -51,18 +51,22 @@ module API
optional :ref, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
optional :path, type: String, desc: 'The path of the tree'
optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+
use :pagination
+ optional :pagination, type: String, values: %w(legacy keyset), default: 'legacy', desc: 'Specify the pagination method'
+
+ given pagination: -> (value) { value == 'keyset' } do
+ optional :page_token, type: String, desc: 'Record from which to start the keyset pagination'
+ end
end
get ':id/repository/tree' do
- ref = params[:ref] || user_project.default_branch
- path = params[:path] || nil
+ tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false))
+
+ not_found!("Tree") unless tree_finder.commit_exists?
- commit = user_project.commit(ref)
- not_found!('Tree') unless commit
+ tree = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(tree_finder)
- tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
- entries = ::Kaminari.paginate_array(tree.sorted_entries)
- present paginate(entries), with: Entities::TreeObject
+ present tree, with: Entities::TreeObject
end
desc 'Get raw blob contents from the repository'
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index aac195f0668..36f816ae638 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -176,6 +176,7 @@ module API
optional :require_admin_approval_after_user_signup, type: Boolean, desc: 'Require explicit admin approval for new signups'
optional :whats_new_variant, type: String, values: ApplicationSetting.whats_new_variants.keys, desc: "What's new variant, possible values: `all_tiers`, `current_tier`, and `disabled`."
optional :floc_enabled, type: Grape::API::Boolean, desc: 'Enable FloC (Federated Learning of Cohorts)'
+ optional :user_deactivation_emails_enabled, type: Boolean, desc: 'Send emails to users upon account deactivation'
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
@@ -225,6 +226,16 @@ module API
attrs[:asset_proxy_allowlist] = attrs.delete(:asset_proxy_whitelist)
end
+ # Also accept these attributes under their new names.
+ #
+ # TODO: Once we rename the columns, we have to swap this around and keep supporting the old names until v5.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/340031
+ %w[enabled period_in_seconds requests_per_period].each do |suffix|
+ old_name = :"throttle_unauthenticated_#{suffix}"
+ new_name = :"throttle_unauthenticated_web_#{suffix}"
+ attrs[old_name] = attrs.delete(new_name) if attrs.has_key?(new_name)
+ end
+
# since 13.0 it's not possible to disable hashed storage - support can be removed in 14.0
attrs.delete(:hashed_storage_enabled) if attrs.has_key?(:hashed_storage_enabled)
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index a595129fd6a..85a299c5673 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -11,7 +11,7 @@ module API
},
gitlab_ci_ymls: {
gitlab_version: 8.9,
- feature_category: :continuous_integration
+ feature_category: :pipeline_authoring
},
dockerfiles: {
gitlab_version: 8.15,
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 2608fb87e22..e3271b8b9b2 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -615,6 +615,22 @@ module API
end
end
+ desc 'Reject a pending user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ post ':id/reject', feature_category: :authentication_and_authorization do
+ user = find_user_by_id(params)
+
+ result = ::Users::RejectService.new(current_user).execute(user)
+
+ if result[:success]
+ present user
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
+
# rubocop: enable CodeReuse/ActiveRecord
desc 'Deactivate an active user. Available only for admins.'
params do
@@ -687,6 +703,38 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ desc 'Ban a user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ post ':id/ban', feature_category: :authentication_and_authorization do
+ authenticated_as_admin!
+ user = find_user_by_id(params)
+
+ result = ::Users::BanService.new(current_user).execute(user)
+ if result[:status] == :success
+ true
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
+
+ desc 'Unban a user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ post ':id/unban', feature_category: :authentication_and_authorization do
+ authenticated_as_admin!
+ user = find_user_by_id(params)
+
+ result = ::Users::UnbanService.new(current_user).execute(user)
+ if result[:status] == :success
+ true
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
+
desc 'Get memberships' do
success Entities::Membership
end