summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/access_requests.rb2
-rw-r--r--lib/api/admin/ci/variables.rb2
-rw-r--r--lib/api/admin/instance_clusters.rb2
-rw-r--r--lib/api/admin/sidekiq.rb2
-rw-r--r--lib/api/api.rb29
-rw-r--r--lib/api/appearance.rb2
-rw-r--r--lib/api/applications.rb2
-rw-r--r--lib/api/avatar.rb2
-rw-r--r--lib/api/award_emoji.rb14
-rw-r--r--lib/api/badges.rb2
-rw-r--r--lib/api/base.rb25
-rw-r--r--lib/api/boards.rb39
-rw-r--r--lib/api/boards_responses.rb35
-rw-r--r--lib/api/branches.rb2
-rw-r--r--lib/api/broadcast_messages.rb2
-rw-r--r--lib/api/ci/pipeline_schedules.rb2
-rw-r--r--lib/api/ci/pipelines.rb42
-rw-r--r--lib/api/ci/runner.rb31
-rw-r--r--lib/api/ci/runners.rb2
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/composer_packages.rb2
-rw-r--r--lib/api/conan_package_endpoints.rb2
-rw-r--r--lib/api/concerns/packages/npm_endpoints.rb134
-rw-r--r--lib/api/container_registry_event.rb2
-rw-r--r--lib/api/container_repositories.rb41
-rw-r--r--lib/api/debian_package_endpoints.rb2
-rw-r--r--lib/api/dependency_proxy.rb41
-rw-r--r--lib/api/deploy_keys.rb2
-rw-r--r--lib/api/deploy_tokens.rb2
-rw-r--r--lib/api/deployments.rb2
-rw-r--r--lib/api/discussions.rb20
-rw-r--r--lib/api/entities/board.rb1
-rw-r--r--lib/api/entities/commit_signature.rb19
-rw-r--r--lib/api/entities/container_registry.rb9
-rw-r--r--lib/api/entities/invitation.rb15
-rw-r--r--lib/api/entities/merge_request_changes.rb22
-rw-r--r--lib/api/entities/package.rb3
-rw-r--r--lib/api/entities/package_file.rb1
-rw-r--r--lib/api/entities/package_version.rb2
-rw-r--r--lib/api/entities/project_hook.rb2
-rw-r--r--lib/api/entities/release.rb2
-rw-r--r--lib/api/entities/releases/link.rb6
-rw-r--r--lib/api/entities/user_status.rb1
-rw-r--r--lib/api/environments.rb2
-rw-r--r--lib/api/error_tracking.rb2
-rw-r--r--lib/api/events.rb2
-rw-r--r--lib/api/feature_flag_scopes.rb2
-rw-r--r--lib/api/feature_flags.rb2
-rw-r--r--lib/api/feature_flags_user_lists.rb7
-rw-r--r--lib/api/features.rb12
-rw-r--r--lib/api/files.rb2
-rw-r--r--lib/api/freeze_periods.rb2
-rw-r--r--lib/api/generic_packages.rb2
-rwxr-xr-xlib/api/go_proxy.rb4
-rw-r--r--lib/api/group_boards.rb2
-rw-r--r--lib/api/group_clusters.rb2
-rw-r--r--lib/api/group_container_repositories.rb2
-rw-r--r--lib/api/group_export.rb2
-rw-r--r--lib/api/group_import.rb2
-rw-r--r--lib/api/group_labels.rb16
-rw-r--r--lib/api/group_milestones.rb2
-rw-r--r--lib/api/group_packages.rb2
-rw-r--r--lib/api/group_variables.rb2
-rw-r--r--lib/api/groups.rb4
-rw-r--r--lib/api/helpers.rb15
-rw-r--r--lib/api/helpers/discussions_helpers.rb9
-rw-r--r--lib/api/helpers/issues_helpers.rb3
-rw-r--r--lib/api/helpers/label_helpers.rb12
-rw-r--r--lib/api/helpers/members_helpers.rb17
-rw-r--r--lib/api/helpers/notes_helpers.rb10
-rw-r--r--lib/api/helpers/packages/npm.rb62
-rw-r--r--lib/api/helpers/resource_label_events_helpers.rb7
-rw-r--r--lib/api/import_bitbucket_server.rb2
-rw-r--r--lib/api/import_github.rb7
-rw-r--r--lib/api/internal/base.rb40
-rw-r--r--lib/api/internal/kubernetes.rb2
-rw-r--r--lib/api/internal/lfs.rb2
-rw-r--r--lib/api/internal/pages.rb2
-rw-r--r--lib/api/invitations.rb54
-rw-r--r--lib/api/issue_links.rb2
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/job_artifacts.rb2
-rw-r--r--lib/api/jobs.rb2
-rw-r--r--lib/api/keys.rb2
-rw-r--r--lib/api/labels.rb8
-rw-r--r--lib/api/lint.rb26
-rw-r--r--lib/api/markdown.rb2
-rw-r--r--lib/api/maven_packages.rb4
-rw-r--r--lib/api/members.rb6
-rw-r--r--lib/api/merge_request_approvals.rb2
-rw-r--r--lib/api/merge_request_diffs.rb2
-rw-r--r--lib/api/merge_requests.rb8
-rw-r--r--lib/api/metrics/dashboard/annotations.rb2
-rw-r--r--lib/api/metrics/user_starred_dashboards.rb2
-rw-r--r--lib/api/namespaces.rb2
-rw-r--r--lib/api/notes.rb10
-rw-r--r--lib/api/notification_settings.rb2
-rw-r--r--lib/api/npm_instance_packages.rb16
-rw-r--r--lib/api/npm_packages.rb173
-rw-r--r--lib/api/npm_project_packages.rb66
-rw-r--r--lib/api/nuget_packages.rb2
-rw-r--r--lib/api/package_files.rb2
-rw-r--r--lib/api/pages.rb2
-rw-r--r--lib/api/pages_domains.rb2
-rw-r--r--lib/api/personal_access_tokens.rb61
-rw-r--r--lib/api/project_clusters.rb2
-rw-r--r--lib/api/project_container_repositories.rb2
-rw-r--r--lib/api/project_events.rb2
-rw-r--r--lib/api/project_export.rb2
-rw-r--r--lib/api/project_hooks.rb3
-rw-r--r--lib/api/project_import.rb2
-rw-r--r--lib/api/project_milestones.rb2
-rw-r--r--lib/api/project_packages.rb2
-rw-r--r--lib/api/project_repository_storage_moves.rb2
-rw-r--r--lib/api/project_snapshots.rb2
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/project_statistics.rb2
-rw-r--r--lib/api/project_templates.rb2
-rw-r--r--lib/api/projects.rb50
-rw-r--r--lib/api/protected_branches.rb2
-rw-r--r--lib/api/protected_tags.rb2
-rw-r--r--lib/api/pypi_packages.rb2
-rw-r--r--lib/api/release/links.rb2
-rw-r--r--lib/api/releases.rb4
-rw-r--r--lib/api/remote_mirrors.rb2
-rw-r--r--lib/api/repositories.rb4
-rw-r--r--lib/api/resource_label_events.rb6
-rw-r--r--lib/api/resource_milestone_events.rb9
-rw-r--r--lib/api/resource_state_events.rb9
-rw-r--r--lib/api/search.rb7
-rw-r--r--lib/api/services.rb2
-rw-r--r--lib/api/settings.rb9
-rw-r--r--lib/api/sidekiq_metrics.rb2
-rw-r--r--lib/api/snippets.rb2
-rw-r--r--lib/api/statistics.rb2
-rw-r--r--lib/api/submodules.rb2
-rw-r--r--lib/api/subscriptions.rb16
-rw-r--r--lib/api/suggestions.rb2
-rw-r--r--lib/api/system_hooks.rb2
-rw-r--r--lib/api/tags.rb12
-rw-r--r--lib/api/templates.rb2
-rw-r--r--lib/api/terraform/state.rb4
-rw-r--r--lib/api/terraform/state_version.rb2
-rw-r--r--lib/api/todos.rb2
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/unleash.rb2
-rw-r--r--lib/api/usage_data.rb2
-rw-r--r--lib/api/user_counts.rb2
-rw-r--r--lib/api/users.rb131
-rw-r--r--lib/api/v3/github.rb2
-rw-r--r--lib/api/validations/validators/email_or_email_list.rb21
-rw-r--r--lib/api/variables.rb2
-rw-r--r--lib/api/version.rb2
-rw-r--r--lib/api/wikis.rb2
155 files changed, 1248 insertions, 414 deletions
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index 7e3d70a210a..e6ce62a1c6e 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -8,6 +8,8 @@ module API
helpers ::API::Helpers::MembersHelpers
+ feature_category :authentication_and_authorization
+
%w[group project].each do |source_type|
params do
requires :id, type: String, desc: "The #{source_type} ID"
diff --git a/lib/api/admin/ci/variables.rb b/lib/api/admin/ci/variables.rb
index 44c389d6f94..654d3a48162 100644
--- a/lib/api/admin/ci/variables.rb
+++ b/lib/api/admin/ci/variables.rb
@@ -8,6 +8,8 @@ module API
before { authenticated_as_admin! }
+ feature_category :continuous_integration
+
namespace 'admin' do
namespace 'ci' do
namespace 'variables' do
diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb
index ce1bdd65eff..679e231b283 100644
--- a/lib/api/admin/instance_clusters.rb
+++ b/lib/api/admin/instance_clusters.rb
@@ -5,6 +5,8 @@ module API
class InstanceClusters < ::API::Base
include PaginationParams
+ feature_category :kubernetes_management
+
before do
authenticated_as_admin!
end
diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb
index c2e9de5fb4e..7e561783685 100644
--- a/lib/api/admin/sidekiq.rb
+++ b/lib/api/admin/sidekiq.rb
@@ -5,6 +5,8 @@ module API
class Sidekiq < ::API::Base
before { authenticated_as_admin! }
+ feature_category :not_owned
+
namespace 'admin' do
namespace 'sidekiq' do
namespace 'queues' do
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 84b4d5a5835..ea149f25584 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -25,7 +25,8 @@ module API
Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new,
Gitlab::GrapeLogging::Loggers::PerfLogger.new,
Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new,
- Gitlab::GrapeLogging::Loggers::ContextLogger.new
+ Gitlab::GrapeLogging::Loggers::ContextLogger.new,
+ Gitlab::GrapeLogging::Loggers::ContentLogger.new
]
allow_access_with_scope :api
@@ -48,11 +49,17 @@ module API
before do
coerce_nil_params_to_array!
+ api_endpoint = env['api.endpoint']
+ feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s
+
+ header[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
+
Gitlab::ApplicationContext.push(
user: -> { @current_user },
project: -> { @project },
namespace: -> { @group },
- caller_id: route.origin
+ caller_id: route.origin,
+ feature_category: feature_category
)
end
@@ -115,7 +122,14 @@ module API
format :json
formatter :json, Gitlab::Json::GrapeFormatter
- content_type :txt, "text/plain"
+
+ # There is a small chance some users depend on the old behavior.
+ # We this change under a feature flag to see if affects GitLab.com users.
+ if Gitlab::Database.cached_table_exists?('features') && Feature.enabled?(:api_json_content_type)
+ content_type :json, 'application/json'
+ else
+ content_type :txt, 'text/plain'
+ end
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers
helpers ::API::Helpers
@@ -147,6 +161,8 @@ module API
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::ContainerRegistryEvent
+ mount ::API::ContainerRepositories
+ mount ::API::DependencyProxy
mount ::API::DeployKeys
mount ::API::DeployTokens
mount ::API::Deployments
@@ -171,6 +187,7 @@ module API
mount ::API::ImportBitbucketServer
mount ::API::ImportGithub
mount ::API::IssueLinks
+ mount ::API::Invitations
mount ::API::Issues
mount ::API::JobArtifacts
mount ::API::Jobs
@@ -202,7 +219,8 @@ module API
mount ::API::DebianGroupPackages
mount ::API::DebianProjectPackages
mount ::API::MavenPackages
- mount ::API::NpmPackages
+ mount ::API::NpmProjectPackages
+ mount ::API::NpmInstancePackages
mount ::API::GenericPackages
mount ::API::GoProxy
mount ::API::Pages
@@ -222,6 +240,7 @@ module API
mount ::API::ProjectTemplates
mount ::API::Terraform::State
mount ::API::Terraform::StateVersion
+ mount ::API::PersonalAccessTokens
mount ::API::ProtectedBranches
mount ::API::ProtectedTags
mount ::API::Releases
@@ -266,7 +285,7 @@ module API
end
end
- route :any, '*path' do
+ route :any, '*path', feature_category: :not_owned do
error!('404 Not Found', 404)
end
end
diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb
index 00b495bbc1e..fe498bf611b 100644
--- a/lib/api/appearance.rb
+++ b/lib/api/appearance.rb
@@ -4,6 +4,8 @@ module API
class Appearance < ::API::Base
before { authenticated_as_admin! }
+ feature_category :navigation
+
helpers do
def current_appearance
@current_appearance ||= (::Appearance.current || ::Appearance.new)
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
index 2afe8763d9d..8b14e16b495 100644
--- a/lib/api/applications.rb
+++ b/lib/api/applications.rb
@@ -5,6 +5,8 @@ module API
class Applications < ::API::Base
before { authenticated_as_admin! }
+ feature_category :authentication_and_authorization
+
resource :applications do
helpers do
def validate_redirect_uri(value)
diff --git a/lib/api/avatar.rb b/lib/api/avatar.rb
index 5a9b9940fcf..a42d89ddf83 100644
--- a/lib/api/avatar.rb
+++ b/lib/api/avatar.rb
@@ -2,6 +2,8 @@
module API
class Avatar < ::API::Base
+ feature_category :users
+
resource :avatar do
desc 'Return avatar url for a user' do
success Entities::Avatar
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 6d40ae8f5ff..8ea4f32d3eb 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -6,9 +6,9 @@ module API
before { authenticate! }
AWARDABLES = [
- { type: 'issue', find_by: :iid },
- { type: 'merge_request', find_by: :iid },
- { type: 'snippet', find_by: :id }
+ { type: 'issue', find_by: :iid, feature_category: :issue_tracking },
+ { type: 'merge_request', find_by: :iid, feature_category: :code_review },
+ { type: 'snippet', find_by: :id, feature_category: :snippets }
].freeze
params do
@@ -34,7 +34,7 @@ module API
params do
use :pagination
end
- get endpoint do
+ get endpoint, feature_category: awardable_params[:feature_category] do
if can_read_awardable?
awards = awardable.award_emoji
present paginate(awards), with: Entities::AwardEmoji
@@ -50,7 +50,7 @@ module API
params do
requires :award_id, type: Integer, desc: 'The ID of the award'
end
- get "#{endpoint}/:award_id" do
+ get "#{endpoint}/:award_id", feature_category: awardable_params[:feature_category] do
if can_read_awardable?
present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji
else
@@ -65,7 +65,7 @@ module API
params do
requires :name, type: String, desc: 'The name of a award_emoji (without colons)'
end
- post endpoint do
+ post endpoint, feature_category: awardable_params[:feature_category] do
not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable?
service = AwardEmojis::AddService.new(awardable, params[:name], current_user).execute
@@ -84,7 +84,7 @@ module API
params do
requires :award_id, type: Integer, desc: 'The ID of an award emoji'
end
- delete "#{endpoint}/:award_id" do
+ delete "#{endpoint}/:award_id", feature_category: awardable_params[:feature_category] do
award = awardable.award_emoji.find(params[:award_id])
unauthorized! unless award.user == current_user || current_user.admin?
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
index fc00594c9ec..04f155be4e1 100644
--- a/lib/api/badges.rb
+++ b/lib/api/badges.rb
@@ -8,6 +8,8 @@ module API
helpers ::API::Helpers::BadgesHelpers
+ feature_category :continuous_integration
+
helpers do
def find_source_if_admin(source_type)
source = find_source(source_type, params[:id])
diff --git a/lib/api/base.rb b/lib/api/base.rb
index e174cef3bad..33e47c18fcd 100644
--- a/lib/api/base.rb
+++ b/lib/api/base.rb
@@ -2,5 +2,30 @@
module API
class Base < Grape::API::Instance # rubocop:disable API/Base
+ include ::Gitlab::WithFeatureCategory
+
+ class << self
+ def feature_category_for_app(app)
+ feature_category_for_action(path_for_app(app))
+ end
+
+ def path_for_app(app)
+ normalize_path(app.namespace, app.options[:path].first)
+ end
+
+ def route(methods, paths = ['/'], route_options = {}, &block)
+ if category = route_options.delete(:feature_category)
+ feature_category(category, Array(paths).map { |path| normalize_path(namespace, path) })
+ end
+
+ super
+ end
+
+ private
+
+ def normalize_path(namespace, path)
+ [namespace.presence, path.to_s.chomp('/').presence].compact.join('/')
+ end
+ end
end
end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index d2d1628aff4..f4b23c507f4 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -9,6 +9,8 @@ module API
before { authenticate! }
+ feature_category :boards
+
helpers do
def board_parent
user_project
@@ -40,6 +42,43 @@ module API
authorize!(:read_board, user_project)
present board, with: Entities::Board
end
+
+ desc 'Create a project board' do
+ detail 'This feature was introduced in 10.4'
+ success Entities::Board
+ end
+ params do
+ requires :name, type: String, desc: 'The board name'
+ end
+ post '/' do
+ authorize!(:admin_board, board_parent)
+
+ create_board
+ end
+
+ desc 'Update a project board' do
+ detail 'This feature was introduced in 11.0'
+ success Entities::Board
+ end
+ params do
+ use :update_params
+ end
+ put '/:board_id' do
+ authorize!(:admin_board, board_parent)
+
+ update_board
+ end
+
+ desc 'Delete a project board' do
+ detail 'This feature was introduced in 10.4'
+ success Entities::Board
+ end
+
+ delete '/:board_id' do
+ authorize!(:admin_board, board_parent)
+
+ delete_board
+ end
end
params do
diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb
index 6a86c02bf4a..2ae82f78e01 100644
--- a/lib/api/boards_responses.rb
+++ b/lib/api/boards_responses.rb
@@ -10,6 +10,35 @@ module API
board_parent.boards.find(params[:board_id])
end
+ def create_board
+ forbidden! unless board_parent.multiple_issue_boards_available?
+
+ response =
+ ::Boards::CreateService.new(board_parent, current_user, { name: params[:name] }).execute
+
+ present response.payload, with: Entities::Board
+ end
+
+ def update_board
+ service = ::Boards::UpdateService.new(board_parent, current_user, declared_params(include_missing: false))
+ service.execute(board)
+
+ if board.valid?
+ present board, with: Entities::Board
+ else
+ bad_request!("Failed to save board #{board.errors.messages}")
+ end
+ end
+
+ def delete_board
+ forbidden! unless board_parent.multiple_issue_boards_available?
+
+ destroy_conditionally!(board) do |board|
+ service = ::Boards::DestroyService.new(board_parent, current_user)
+ service.execute(board)
+ end
+ end
+
def board_lists
board.destroyable_lists
end
@@ -62,6 +91,12 @@ module API
params :list_creation_params do
requires :label_id, type: Integer, desc: 'The ID of an existing label'
end
+
+ params :update_params do
+ # Configurable issue boards are not available in CE/EE Core.
+ # https://docs.gitlab.com/ee/user/project/issue_board.html#configurable-issue-boards
+ optional :name, type: String, desc: 'The board name'
+ end
end
end
end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 37cce6eafba..6842e93a4de 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -10,6 +10,8 @@ module API
after_validation { content_type "application/json" }
+ feature_category :source_code_management
+
before do
require_repository_enabled!
authorize! :download_code, user_project
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
index 8ce7694bbfd..0762c276aad 100644
--- a/lib/api/broadcast_messages.rb
+++ b/lib/api/broadcast_messages.rb
@@ -4,6 +4,8 @@ module API
class BroadcastMessages < ::API::Base
include PaginationParams
+ feature_category :navigation
+
resource :broadcast_messages do
helpers do
def find_message
diff --git a/lib/api/ci/pipeline_schedules.rb b/lib/api/ci/pipeline_schedules.rb
index b669acf668c..8a9ba2cbe0f 100644
--- a/lib/api/ci/pipeline_schedules.rb
+++ b/lib/api/ci/pipeline_schedules.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :continuous_integration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
index 61e03ed1a95..1b36e27f6c9 100644
--- a/lib/api/ci/pipelines.rb
+++ b/lib/api/ci/pipelines.rb
@@ -7,6 +7,8 @@ module API
before { authenticate_non_get! }
+ feature_category :continuous_integration
+
params do
requires :id, type: String, desc: 'The project ID'
end
@@ -128,15 +130,9 @@ module API
pipeline = user_project.all_pipelines.find(params[:pipeline_id])
- if Feature.enabled?(:ci_jobs_finder_refactor, default_enabled: true)
- builds = ::Ci::JobsFinder
- .new(current_user: current_user, pipeline: pipeline, params: params)
- .execute
- else
- authorize!(:read_build, pipeline)
- builds = pipeline.builds
- builds = filter_builds(builds, params[:scope])
- end
+ builds = ::Ci::JobsFinder
+ .new(current_user: current_user, pipeline: pipeline, params: params)
+ .execute
builds = builds.with_preloads
@@ -157,16 +153,9 @@ module API
pipeline = user_project.all_pipelines.find(params[:pipeline_id])
- if Feature.enabled?(:ci_jobs_finder_refactor, default_enabled: true)
- bridges = ::Ci::JobsFinder
- .new(current_user: current_user, pipeline: pipeline, params: params, type: ::Ci::Bridge)
- .execute
- else
- authorize!(:read_pipeline, pipeline)
- bridges = pipeline.bridges
- bridges = filter_builds(bridges, params[:scope])
- end
-
+ bridges = ::Ci::JobsFinder
+ .new(current_user: current_user, pipeline: pipeline, params: params, type: ::Ci::Bridge)
+ .execute
bridges = bridges.with_preloads
present paginate(bridges), with: Entities::Ci::Bridge
@@ -246,21 +235,6 @@ module API
end
helpers do
- # NOTE: This method should be removed once the ci_jobs_finder_refactor FF is
- # removed. https://gitlab.com/gitlab-org/gitlab/-/issues/245183
- # rubocop: disable CodeReuse/ActiveRecord
- def filter_builds(builds, scope)
- return builds if scope.nil? || scope.empty?
-
- available_statuses = ::CommitStatus::AVAILABLE_STATUSES
-
- unknown = scope - available_statuses
- render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?
-
- builds.where(status: scope)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def pipeline
strong_memoize(:pipeline) do
user_project.all_pipelines.find(params[:pipeline_id])
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index ef679147c9f..85232b4ae1b 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -5,6 +5,10 @@ module API
class Runner < ::API::Base
helpers ::API::Helpers::Runner
+ content_type :txt, 'text/plain'
+
+ feature_category :continuous_integration
+
resource :runners do
desc 'Registers a new Runner' do
success Entities::RunnerRegistrationDetails
@@ -203,27 +207,18 @@ module API
error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
content_range = request.headers['Content-Range']
- content_range = content_range.split('-')
-
- # TODO:
- # it seems that `Content-Range` as formatted by runner is wrong,
- # the `byte_end` should point to final byte, but it points byte+1
- # that means that we have to calculate end of body,
- # as we cannot use `content_length[1]`
- # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275
-
- body_data = request.body.read
- body_start = content_range[0].to_i
- body_end = body_start + body_data.bytesize
-
- stream_size = job.trace.append(body_data, body_start)
- unless stream_size == body_end
- break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" })
+
+ result = ::Ci::AppendBuildTraceService
+ .new(job, content_range: content_range)
+ .execute(request.body.read)
+
+ if result.status == 416
+ break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{result.stream_size}" })
end
- status 202
+ status result.status
header 'Job-Status', job.status
- header 'Range', "0-#{stream_size}"
+ header 'Range', "0-#{result.stream_size}"
header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s
end
diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb
index d37f10fe631..44ffc941cfa 100644
--- a/lib/api/ci/runners.rb
+++ b/lib/api/ci/runners.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :continuous_integration
+
resource :runners do
desc 'Get runners available for user' do
success Entities::Runner
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index af103b8c1f8..26af921432c 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -4,6 +4,8 @@ require 'mime/types'
module API
class CommitStatuses < ::API::Base
+ feature_category :continuous_integration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 582ccd41847..a24848082a9 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -6,6 +6,8 @@ module API
class Commits < ::API::Base
include PaginationParams
+ feature_category :source_code_management
+
before do
require_repository_enabled!
authorize! :download_code, user_project
diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb
index 1becbd668a3..0ac5cc45ccf 100644
--- a/lib/api/composer_packages.rb
+++ b/lib/api/composer_packages.rb
@@ -9,6 +9,8 @@ module API
include ::API::Helpers::Packages::BasicAuthHelpers::Constants
include ::Gitlab::Utils::StrongMemoize
+ feature_category :package_registry
+
content_type :json, 'application/json'
default_format :json
diff --git a/lib/api/conan_package_endpoints.rb b/lib/api/conan_package_endpoints.rb
index 9b6867a328b..188a42f26f8 100644
--- a/lib/api/conan_package_endpoints.rb
+++ b/lib/api/conan_package_endpoints.rb
@@ -29,6 +29,8 @@ module API
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
included do
+ feature_category :package_registry
+
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::Conan::ApiHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb
new file mode 100644
index 00000000000..a91db93b182
--- /dev/null
+++ b/lib/api/concerns/packages/npm_endpoints.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+# NPM Package Manager Client API
+#
+# These API endpoints are not consumed directly by users, so there is no documentation for the
+# individual endpoints. They are called by the NPM package manager client when users run commands
+# like `npm install` or `npm publish`. The usage of the GitLab NPM registry is documented here:
+# https://docs.gitlab.com/ee/user/packages/npm_registry/
+#
+# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
+#
+# Caution: This Concern has to be included at the end of the API class
+# The last route of this Concern has a globbing wildcard that will match all urls.
+# As such, routes declared after the last route of this Concern will not match any url.
+module API
+ module Concerns
+ module Packages
+ module NpmEndpoints
+ extend ActiveSupport::Concern
+
+ included do
+ helpers ::API::Helpers::Packages::DependencyProxyHelpers
+
+ before do
+ require_packages_enabled!
+ authenticate_non_get!
+ end
+
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ end
+ namespace '-/package/*package_name' do
+ desc 'Get all tags for a given an NPM package' do
+ detail 'This feature was introduced in GitLab 12.7'
+ success ::API::Entities::NpmPackageTag
+ end
+ get 'dist-tags', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
+ package_name = params[:package_name]
+
+ bad_request!('Package Name') if package_name.blank?
+
+ authorize_read_package!(project)
+
+ packages = ::Packages::Npm::PackageFinder.new(project, package_name)
+ .execute
+
+ not_found! if packages.empty?
+
+ present ::Packages::Npm::PackagePresenter.new(package_name, packages),
+ with: ::API::Entities::NpmPackageTag
+ end
+
+ params do
+ requires :tag, type: String, desc: "Package dist-tag"
+ end
+ namespace 'dist-tags/:tag', requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
+ desc 'Create or Update the given tag for the given NPM package and version' do
+ detail 'This feature was introduced in GitLab 12.7'
+ end
+ put format: false do
+ package_name = params[:package_name]
+ version = env['api.request.body']
+ tag = params[:tag]
+
+ bad_request!('Package Name') if package_name.blank?
+ bad_request!('Version') if version.blank?
+ bad_request!('Tag') if tag.blank?
+
+ authorize_create_package!(project)
+
+ package = ::Packages::Npm::PackageFinder
+ .new(project, package_name)
+ .find_by_version(version)
+ not_found!('Package') unless package
+
+ ::Packages::Npm::CreateTagService.new(package, tag).execute
+
+ no_content!
+ end
+
+ desc 'Deletes the given tag' do
+ detail 'This feature was introduced in GitLab 12.7'
+ end
+ delete format: false do
+ package_name = params[:package_name]
+ tag = params[:tag]
+
+ bad_request!('Package Name') if package_name.blank?
+ bad_request!('Tag') if tag.blank?
+
+ authorize_destroy_package!(project)
+
+ package_tag = ::Packages::TagsFinder
+ .new(project, package_name, package_type: :npm)
+ .find_by_name(tag)
+
+ not_found!('Package tag') unless package_tag
+
+ ::Packages::RemoveTagService.new(package_tag).execute
+
+ no_content!
+ end
+ end
+ end
+
+ desc 'NPM registry metadata endpoint' do
+ detail 'This feature was introduced in GitLab 11.8'
+ end
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ get '*package_name', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
+ package_name = params[:package_name]
+
+ packages = ::Packages::Npm::PackageFinder.new(project_or_nil, package_name)
+ .execute
+
+ redirect_request = project_or_nil.blank? || packages.empty?
+
+ redirect_registry_request(redirect_request, :npm, package_name: package_name) do
+ authorize_read_package!(project)
+
+ not_found!('Packages') if packages.empty?
+
+ present ::Packages::Npm::PackagePresenter.new(package_name, packages),
+ with: ::API::Entities::NpmPackage
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/container_registry_event.rb b/lib/api/container_registry_event.rb
index 6c4b80b612a..9bad31f6661 100644
--- a/lib/api/container_registry_event.rb
+++ b/lib/api/container_registry_event.rb
@@ -4,6 +4,8 @@ module API
class ContainerRegistryEvent < ::API::Base
DOCKER_DISTRIBUTION_EVENTS_V1_JSON = 'application/vnd.docker.distribution.events.v1+json'
+ feature_category :package_registry
+
before { authenticate_registry_notification! }
resource :container_registry_event do
diff --git a/lib/api/container_repositories.rb b/lib/api/container_repositories.rb
new file mode 100644
index 00000000000..c84527f26e7
--- /dev/null
+++ b/lib/api/container_repositories.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module API
+ class ContainerRepositories < ::API::Base
+ include Gitlab::Utils::StrongMemoize
+ helpers ::API::Helpers::PackagesHelpers
+
+ before { authenticate! }
+
+ feature_category :container_registry
+
+ namespace 'registry' do
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :repositories, requirements: { id: /[0-9]*/ } do
+ desc 'Get a container repository' do
+ detail 'This feature was introduced in GitLab 13.6.'
+ success Entities::ContainerRegistry::Repository
+ end
+ params do
+ optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included'
+ optional :tags_count, type: Boolean, default: false, desc: 'Determines if the tags count should be included'
+ end
+ get ':id' do
+ authorize!(:read_container_image, repository)
+
+ present repository, with: Entities::ContainerRegistry::Repository, tags: params[:tags], tags_count: params[:tags_count], user: current_user
+ end
+ end
+ end
+
+ helpers do
+ def repository
+ strong_memoize(:repository) do
+ ContainerRepository.find(params[:id])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/debian_package_endpoints.rb b/lib/api/debian_package_endpoints.rb
index 168b3ca7a4f..c95c75b7e5c 100644
--- a/lib/api/debian_package_endpoints.rb
+++ b/lib/api/debian_package_endpoints.rb
@@ -26,6 +26,8 @@ module API
}.freeze
included do
+ feature_category :package_registry
+
helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
diff --git a/lib/api/dependency_proxy.rb b/lib/api/dependency_proxy.rb
new file mode 100644
index 00000000000..3379bb2f029
--- /dev/null
+++ b/lib/api/dependency_proxy.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module API
+ class DependencyProxy < ::API::Base
+ helpers ::API::Helpers::PackagesHelpers
+
+ feature_category :dependency_proxy
+
+ helpers do
+ def obtain_new_purge_cache_lease
+ Gitlab::ExclusiveLease
+ .new("dependency_proxy:delete_group_blobs:#{user_group.id}",
+ timeout: 1.hour)
+ .try_obtain
+ end
+ end
+
+ before do
+ authorize! :admin_group, user_group
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Deletes all dependency_proxy_blobs for a group' do
+ detail 'This feature was introduced in GitLab 12.10'
+ end
+ delete ':id/dependency_proxy/cache' do
+ not_found! unless user_group.dependency_proxy_feature_available?
+
+ message = 'This request has already been made. It may take some time to purge the cache. You can run this at most once an hour for a given group'
+ render_api_error!(message, 409) unless obtain_new_purge_cache_lease
+
+ # rubocop:disable CodeReuse/Worker
+ PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id)
+ # rubocop:enable CodeReuse/Worker
+ end
+ end
+ end
+end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 314f5b6ee1d..0a541620c3a 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :continuous_delivery
+
helpers do
def add_deploy_keys_project(project, attrs = {})
project.deploy_keys_projects.create(attrs)
diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb
index 1c156b8b3bb..5fab590eb4e 100644
--- a/lib/api/deploy_tokens.rb
+++ b/lib/api/deploy_tokens.rb
@@ -4,6 +4,8 @@ module API
class DeployTokens < ::API::Base
include PaginationParams
+ feature_category :continuous_delivery
+
helpers do
def scope_params
scopes = params.delete(:scopes)
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index ff06bdbae16..5346fcf03c9 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :continuous_delivery
+
params do
requires :id, type: String, desc: 'The project ID'
end
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 3d2608c8c5a..4c4ec200060 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -8,7 +8,7 @@ module API
before { authenticate! }
- Helpers::DiscussionsHelpers.noteable_types.each do |noteable_type|
+ Helpers::DiscussionsHelpers.feature_category_per_noteable_type.each do |noteable_type, feature_category|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
noteables_path = noteable_type == Commit ? "repository/#{noteables_str}" : noteables_str
@@ -25,7 +25,7 @@ module API
use :pagination
end
- get ":id/#{noteables_path}/:noteable_id/discussions" do
+ get ":id/#{noteables_path}/:noteable_id/discussions", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
discussion_ids = paginate(noteable.discussion_ids_relation)
@@ -41,7 +41,7 @@ module API
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable'
end
- get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id" do
+ get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
@@ -91,7 +91,7 @@ module API
end
end
end
- post ":id/#{noteables_path}/:noteable_id/discussions" do
+ post ":id/#{noteables_path}/:noteable_id/discussions", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
type = params[:position] ? 'DiffNote' : 'DiscussionNote'
id_key = noteable.is_a?(Commit) ? :commit_id : :noteable_id
@@ -121,7 +121,7 @@ module API
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable'
end
- get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes" do
+ get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
@@ -141,7 +141,7 @@ module API
requires :body, type: String, desc: 'The content of a note'
optional :created_at, type: String, desc: 'The creation date of the note'
end
- post ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes" do
+ post ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
first_note = notes.first
@@ -175,7 +175,7 @@ module API
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :note_id, type: Integer, desc: 'The ID of a note'
end
- get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
+ get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
get_note(noteable, params[:note_id])
@@ -192,7 +192,7 @@ module API
optional :resolved, type: Boolean, desc: 'Mark note resolved/unresolved'
exactly_one_of :body, :resolved
end
- put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
+ put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
if params[:resolved].nil?
@@ -210,7 +210,7 @@ module API
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :note_id, type: Integer, desc: 'The ID of a note'
end
- delete ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
+ delete ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
delete_note(noteable, params[:note_id])
@@ -225,7 +225,7 @@ module API
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :resolved, type: Boolean, desc: 'Mark discussion resolved/unresolved'
end
- put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id" do
+ put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
resolve_discussion(noteable, params[:discussion_id], params[:resolved])
diff --git a/lib/api/entities/board.rb b/lib/api/entities/board.rb
index 5bb1cde0fa9..b7a50408313 100644
--- a/lib/api/entities/board.rb
+++ b/lib/api/entities/board.rb
@@ -4,6 +4,7 @@ module API
module Entities
class Board < Grape::Entity
expose :id
+ expose :name
expose :project, using: Entities::BasicProjectDetails
expose :lists, using: Entities::List do |board|
diff --git a/lib/api/entities/commit_signature.rb b/lib/api/entities/commit_signature.rb
index b5232273521..505ce462edf 100644
--- a/lib/api/entities/commit_signature.rb
+++ b/lib/api/entities/commit_signature.rb
@@ -4,13 +4,28 @@ module API
module Entities
class CommitSignature < Grape::Entity
expose :signature_type
+
expose :signature, merge: true do |commit, options|
- if commit.signature.is_a?(GpgSignature)
- ::API::Entities::GpgCommitSignature.represent commit.signature, options
+ if commit.signature.is_a?(GpgSignature) || commit.raw_commit_from_rugged?
+ ::API::Entities::GpgCommitSignature.represent commit_signature(commit), options
elsif commit.signature.is_a?(X509CommitSignature)
::API::Entities::X509Signature.represent commit.signature, options
end
end
+
+ expose :commit_source do |commit, _|
+ commit.raw_commit_from_rugged? ? "rugged" : "gitaly"
+ end
+
+ private
+
+ def commit_signature(commit)
+ if commit.raw_commit_from_rugged?
+ commit.gpg_commit.signature
+ else
+ commit.signature
+ end
+ end
end
end
end
diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb
index c430b73580b..c9c2c5156cc 100644
--- a/lib/api/entities/container_registry.rb
+++ b/lib/api/entities/container_registry.rb
@@ -10,6 +10,8 @@ module API
end
class Repository < Grape::Entity
+ include ::API::Helpers::RelatedResourcesHelpers
+
expose :id
expose :name
expose :path
@@ -19,6 +21,13 @@ module API
expose :expiration_policy_started_at, as: :cleanup_policy_started_at
expose :tags_count, if: -> (_, options) { options[:tags_count] }
expose :tags, using: Tag, if: -> (_, options) { options[:tags] }
+ expose :delete_api_path, if: ->(object, options) { Ability.allowed?(options[:user], :admin_container_image, object) }
+
+ private
+
+ def delete_api_path
+ expose_url api_v4_projects_registry_repositories_path(repository_id: object.id, id: object.project_id)
+ end
end
class TagDetails < Tag
diff --git a/lib/api/entities/invitation.rb b/lib/api/entities/invitation.rb
new file mode 100644
index 00000000000..342f4804cf3
--- /dev/null
+++ b/lib/api/entities/invitation.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Invitation < Grape::Entity
+ expose :access_level
+ expose :requested_at
+ expose :expires_at
+ expose :invite_email
+ expose :invite_token
+ expose :user_name, if: -> (member, _) { member.user.present? }
+ expose :created_by_name
+ end
+ end
+end
diff --git a/lib/api/entities/merge_request_changes.rb b/lib/api/entities/merge_request_changes.rb
index a835d119736..488f33dfb93 100644
--- a/lib/api/entities/merge_request_changes.rb
+++ b/lib/api/entities/merge_request_changes.rb
@@ -4,7 +4,27 @@ module API
module Entities
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::Diff do |compare, _|
- compare.raw_diffs(limits: false).to_a
+ Array(diff_collection(compare))
+ end
+
+ expose :overflow?, as: :overflow
+
+ private
+
+ def overflow?
+ expose_raw_diffs? ? false : diff_collection(object).overflow?
+ end
+
+ def diff_collection(compare)
+ @diffs ||= if expose_raw_diffs?
+ compare.raw_diffs(limits: false)
+ else
+ compare.diffs.diffs
+ end
+ end
+
+ def expose_raw_diffs?
+ options[:access_raw_diffs] || ::Feature.enabled?(:mrc_api_use_raw_diffs_from_gitaly, options[:project])
end
end
end
diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb
index b54f0e04a9d..e7153f9bebb 100644
--- a/lib/api/entities/package.rb
+++ b/lib/api/entities/package.rb
@@ -38,7 +38,8 @@ module API
expose :project_path, if: ->(obj, opts) { opts[:group] && Ability.allowed?(opts[:user], :read_project, obj.project) }
expose :tags
- expose :pipeline, if: ->(package) { package.build_info }, using: Package::Pipeline
+ expose :pipeline, if: ->(package) { package.original_build_info }, using: Package::Pipeline
+ expose :pipelines, if: ->(package) { package.pipelines.present? }, using: Package::Pipeline
expose :versions, using: ::API::Entities::PackageVersion, unless: ->(_, opts) { opts[:collection] }
diff --git a/lib/api/entities/package_file.rb b/lib/api/entities/package_file.rb
index 8be4e5a4316..2cc2f62a948 100644
--- a/lib/api/entities/package_file.rb
+++ b/lib/api/entities/package_file.rb
@@ -6,6 +6,7 @@ module API
expose :id, :package_id, :created_at
expose :file_name, :size
expose :file_md5, :file_sha1
+ expose :pipelines, if: ->(package_file) { package_file.pipelines.present? }, using: Package::Pipeline
end
end
end
diff --git a/lib/api/entities/package_version.rb b/lib/api/entities/package_version.rb
index 5f3e86c3229..82522d3f423 100644
--- a/lib/api/entities/package_version.rb
+++ b/lib/api/entities/package_version.rb
@@ -8,7 +8,7 @@ module API
expose :created_at
expose :tags
- expose :pipeline, if: ->(package) { package.build_info }, using: Package::Pipeline
+ expose :pipeline, if: ->(package) { package.original_build_info }, using: Package::Pipeline
end
end
end
diff --git a/lib/api/entities/project_hook.rb b/lib/api/entities/project_hook.rb
index 751f9500252..6c71e5d317c 100644
--- a/lib/api/entities/project_hook.rb
+++ b/lib/api/entities/project_hook.rb
@@ -5,7 +5,7 @@ module API
class ProjectHook < Hook
expose :project_id, :issues_events, :confidential_issues_events
expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events, :deployment_events
- expose :job_events
+ expose :job_events, :releases_events
expose :push_events_branch_filter
end
end
diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb
index afe14cf33cf..44a46c5861e 100644
--- a/lib/api/entities/release.rb
+++ b/lib/api/entities/release.rb
@@ -30,8 +30,6 @@ module API
expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_download_code? }
expose :_links do
expose :self_url, as: :self, expose_nil: false
- expose :merge_requests_url, expose_nil: false
- expose :issues_url, expose_nil: false
expose :edit_url, expose_nil: false
end
diff --git a/lib/api/entities/releases/link.rb b/lib/api/entities/releases/link.rb
index 654df2e2caf..c1d83a8924f 100644
--- a/lib/api/entities/releases/link.rb
+++ b/lib/api/entities/releases/link.rb
@@ -14,10 +14,8 @@ module API
def direct_asset_url
return object.url unless object.filepath
- release = object.release
- project = release.project
-
- Gitlab::Routing.url_helpers.project_release_url(project, release) << object.filepath
+ release = object.release.present
+ release.download_url(object.filepath)
end
end
end
diff --git a/lib/api/entities/user_status.rb b/lib/api/entities/user_status.rb
index 9bc4cbf240f..1d5cc27e5ef 100644
--- a/lib/api/entities/user_status.rb
+++ b/lib/api/entities/user_status.rb
@@ -5,6 +5,7 @@ module API
class UserStatus < Grape::Entity
expose :emoji
expose :message
+ expose :availability
expose :message_html do |entity|
MarkupHelper.markdown_field(entity, :message)
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 0e780d4ef36..5dd2fa22690 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :continuous_delivery
+
params do
requires :id, type: String, desc: 'The project ID'
end
diff --git a/lib/api/error_tracking.rb b/lib/api/error_tracking.rb
index 03f83477954..0e44c8b1081 100644
--- a/lib/api/error_tracking.rb
+++ b/lib/api/error_tracking.rb
@@ -4,6 +4,8 @@ module API
class ErrorTracking < ::API::Base
before { authenticate! }
+ feature_category :error_tracking
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/events.rb b/lib/api/events.rb
index 43efacf9c0b..233c62b5389 100644
--- a/lib/api/events.rb
+++ b/lib/api/events.rb
@@ -8,6 +8,8 @@ module API
allow_access_with_scope :read_user, if: -> (request) { request.get? }
+ feature_category :users
+
resource :events do
desc "List currently authenticated user's events" do
detail 'This feature was introduced in GitLab 9.3.'
diff --git a/lib/api/feature_flag_scopes.rb b/lib/api/feature_flag_scopes.rb
index d77e243aa88..3f3bf4d9f42 100644
--- a/lib/api/feature_flag_scopes.rb
+++ b/lib/api/feature_flag_scopes.rb
@@ -7,6 +7,8 @@ module API
ENVIRONMENT_SCOPE_ENDPOINT_REQUIREMENTS = FeatureFlags::FEATURE_FLAG_ENDPOINT_REQUIREMENTS
.merge(environment_scope: API::NO_SLASH_URL_PART_REGEX)
+ feature_category :feature_flags
+
before do
authorize_read_feature_flags!
end
diff --git a/lib/api/feature_flags.rb b/lib/api/feature_flags.rb
index 613c3fb0f5b..67168ba9be6 100644
--- a/lib/api/feature_flags.rb
+++ b/lib/api/feature_flags.rb
@@ -7,6 +7,8 @@ module API
FEATURE_FLAG_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
.merge(name: API::NO_SLASH_URL_PART_REGEX)
+ feature_category :feature_flags
+
before do
authorize_read_feature_flags!
end
diff --git a/lib/api/feature_flags_user_lists.rb b/lib/api/feature_flags_user_lists.rb
index e5218cfd7f1..086bcbcdc89 100644
--- a/lib/api/feature_flags_user_lists.rb
+++ b/lib/api/feature_flags_user_lists.rb
@@ -8,6 +8,8 @@ module API
message.is_a?(String) ? { message: message }.to_json : message.to_json
}
+ feature_category :feature_flags
+
before do
authorize_admin_feature_flags_user_lists!
end
@@ -22,10 +24,13 @@ module API
success ::API::Entities::FeatureFlag::UserList
end
params do
+ optional :search, type: String, desc: 'Returns the list of user lists matching the search critiera'
+
use :pagination
end
get do
- present paginate(user_project.operations_feature_flags_user_lists),
+ user_lists = ::FeatureFlagsUserListsFinder.new(user_project, current_user, params).execute
+ present paginate(user_lists),
with: ::API::Entities::FeatureFlag::UserList
end
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 5d2e545abd6..2c2e3e3d0c9 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -4,6 +4,8 @@ module API
class Features < ::API::Base
before { authenticated_as_admin! }
+ feature_category :feature_flags
+
helpers do
def gate_value(params)
case params[:value]
@@ -61,6 +63,8 @@ module API
mutually_exclusive :key, :project
end
post ':name' do
+ validate_feature_flag_name!(params[:name])
+
feature = Feature.get(params[:name]) # rubocop:disable Gitlab/AvoidFeatureGet
targets = gate_targets(params)
value = gate_value(params)
@@ -97,5 +101,13 @@ module API
no_content!
end
end
+
+ helpers do
+ def validate_feature_flag_name!(name)
+ # no-op
+ end
+ end
end
end
+
+API::Features.prepend_if_ee('EE::API::Features')
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 6833fc429e2..cb73bde73f5 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -9,6 +9,8 @@ module API
# Prevents returning plain/text responses for files with .txt extension
after_validation { content_type "application/json" }
+ feature_category :source_code_management
+
helpers ::API::Helpers::HeadersHelpers
helpers do
diff --git a/lib/api/freeze_periods.rb b/lib/api/freeze_periods.rb
index a83e36165a2..d001ced8581 100644
--- a/lib/api/freeze_periods.rb
+++ b/lib/api/freeze_periods.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :continuous_delivery
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb
index a0c33ab65b9..3e1dd044c8d 100644
--- a/lib/api/generic_packages.rb
+++ b/lib/api/generic_packages.rb
@@ -7,6 +7,8 @@ module API
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
+ feature_category :package_registry
+
before do
require_packages_enabled!
authenticate!
diff --git a/lib/api/go_proxy.rb b/lib/api/go_proxy.rb
index 30f0cfb4dfd..8fb4c561c40 100755
--- a/lib/api/go_proxy.rb
+++ b/lib/api/go_proxy.rb
@@ -4,11 +4,15 @@ module API
helpers Gitlab::Golang
helpers ::API::Helpers::PackagesHelpers
+ feature_category :package_registry
+
# basic semver, except case encoded (A => !a)
MODULE_VERSION_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.!a-z0-9]+))?(?:\+([-.!a-z0-9]+))?/.freeze
MODULE_VERSION_REQUIREMENTS = { module_version: MODULE_VERSION_REGEX }.freeze
+ content_type :txt, 'text/plain'
+
before { require_packages_enabled! }
helpers do
diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb
index d4574b22d99..ac5a1a2ce94 100644
--- a/lib/api/group_boards.rb
+++ b/lib/api/group_boards.rb
@@ -7,6 +7,8 @@ module API
prepend_if_ee('EE::API::BoardsResponses') # rubocop: disable Cop/InjectEnterpriseEditionModule
+ feature_category :boards
+
before do
authenticate!
end
diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb
index 75429cf7a5c..a435b050042 100644
--- a/lib/api/group_clusters.rb
+++ b/lib/api/group_clusters.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :kubernetes_management
+
params do
requires :id, type: String, desc: 'The ID of the group'
end
diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb
index 1bb26b3931c..4fede0ad583 100644
--- a/lib/api/group_container_repositories.rb
+++ b/lib/api/group_container_repositories.rb
@@ -8,6 +8,8 @@ module API
before { authorize_read_group_container_images! }
+ feature_category :package_registry
+
REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
tag_name: API::NO_SLASH_URL_PART_REGEX)
diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb
index 6ebaa8de185..29ffbea687a 100644
--- a/lib/api/group_export.rb
+++ b/lib/api/group_export.rb
@@ -10,6 +10,8 @@ module API
authorize! :admin_group, user_group
end
+ feature_category :importers
+
params do
requires :id, type: String, desc: 'The ID of a group'
end
diff --git a/lib/api/group_import.rb b/lib/api/group_import.rb
index e703a217fd5..4a752732652 100644
--- a/lib/api/group_import.rb
+++ b/lib/api/group_import.rb
@@ -2,6 +2,8 @@
module API
class GroupImport < ::API::Base
+ feature_category :importers
+
helpers Helpers::FileUploadHelpers
helpers do
diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb
index 8443ddf10ce..bf3ac8800b7 100644
--- a/lib/api/group_labels.rb
+++ b/lib/api/group_labels.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :issue_tracking
+
params do
requires :id, type: String, desc: 'The ID of a group'
end
@@ -20,10 +22,16 @@ module API
desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
+ optional :include_descendant_groups, type: Boolean, default: false,
+ desc: 'Include descendant groups. This feature was added in GitLab 13.6'
+ optional :only_group_labels, type: Boolean, default: true,
+ desc: 'Toggle to include only group labels or also project labels. This feature was added in GitLab 13.6'
+ optional :search, type: String,
+ desc: 'Keyword to filter labels by. This feature was added in GitLab 13.6'
use :pagination
end
get ':id/labels' do
- get_labels(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups])
+ get_labels(user_group, Entities::GroupLabel, declared_params)
end
desc 'Get a single label' do
@@ -33,9 +41,13 @@ module API
params do
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
+ optional :include_descendant_groups, type: Boolean, default: false,
+ desc: 'Include descendant groups. This feature was added in GitLab 13.6'
+ optional :only_group_labels, type: Boolean, default: true,
+ desc: 'Toggle to include only group labels or also project labels. This feature was added in GitLab 13.6'
end
get ':id/labels/:name' do
- get_label(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups])
+ get_label(user_group, Entities::GroupLabel, declared_params)
end
desc 'Create a new label' do
diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb
index aef9877b84c..dfffd3b1209 100644
--- a/lib/api/group_milestones.rb
+++ b/lib/api/group_milestones.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :issue_tracking
+
params do
requires :id, type: String, desc: 'The ID of a group'
end
diff --git a/lib/api/group_packages.rb b/lib/api/group_packages.rb
index 5b6290df0dd..31b28c3990f 100644
--- a/lib/api/group_packages.rb
+++ b/lib/api/group_packages.rb
@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_group)
end
+ feature_category :package_registry
+
helpers ::API::Helpers::PackagesHelpers
params do
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index ee110d67fa5..0c40db02eb5 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_group }
+ feature_category :continuous_integration
+
params do
requires :id, type: String, desc: 'The ID of a group'
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index bf3d6c3c7e0..a8b1cdab021 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -7,6 +7,8 @@ module API
before { authenticate_non_get! }
+ feature_category :subgroups
+
helpers Helpers::GroupsHelpers
helpers do
@@ -46,7 +48,7 @@ module API
find_params.fetch(:all_available, current_user&.can_read_all_resources?)
groups = GroupsFinder.new(current_user, find_params).execute
- groups = groups.search(params[:search]) if params[:search].present?
+ groups = groups.search(params[:search], include_parents: true) if params[:search].present?
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
order_options = { params[:order_by] => params[:sort] }
order_options["id"] ||= "asc"
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index c8aee1f3479..147d8407142 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -89,16 +89,15 @@ module API
@project ||= find_project!(params[:id])
end
- def available_labels_for(label_parent, include_ancestor_groups: true)
- search_params = { include_ancestor_groups: include_ancestor_groups }
-
+ def available_labels_for(label_parent, params = { include_ancestor_groups: true, only_group_labels: true })
if label_parent.is_a?(Project)
- search_params[:project_id] = label_parent.id
+ params.delete(:only_group_labels)
+ params[:project_id] = label_parent.id
else
- search_params.merge!(group_id: label_parent.id, only_group_labels: true)
+ params[:group_id] = label_parent.id
end
- LabelsFinder.new(current_user, search_params).execute
+ LabelsFinder.new(current_user, params).execute
end
def find_user(id)
@@ -388,8 +387,8 @@ module API
render_api_error!('401 Unauthorized', 401)
end
- def not_allowed!
- render_api_error!('405 Method Not Allowed', 405)
+ def not_allowed!(message = nil)
+ render_api_error!(message || '405 Method Not Allowed', :method_not_allowed)
end
def not_acceptable!
diff --git a/lib/api/helpers/discussions_helpers.rb b/lib/api/helpers/discussions_helpers.rb
index 799d5582b38..3c0db1d0ea9 100644
--- a/lib/api/helpers/discussions_helpers.rb
+++ b/lib/api/helpers/discussions_helpers.rb
@@ -3,10 +3,15 @@
module API
module Helpers
module DiscussionsHelpers
- def self.noteable_types
+ def self.feature_category_per_noteable_type
# This is a method instead of a constant, allowing EE to more easily
# extend it.
- [Issue, Snippet, MergeRequest, Commit]
+ {
+ Issue => :issue_tracking,
+ Snippet => :snippets,
+ MergeRequest => :code_review,
+ Commit => :code_review
+ }
end
end
end
diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb
index 638b31cc7ba..b303f1f845d 100644
--- a/lib/api/helpers/issues_helpers.rb
+++ b/lib/api/helpers/issues_helpers.rb
@@ -5,6 +5,9 @@ module API
module IssuesHelpers
extend Grape::API::Helpers
+ params :negatable_issue_filter_params_ee do
+ end
+
params :optional_issue_params_ee do
end
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
index 2fb2d9b79cf..4018f2dec21 100644
--- a/lib/api/helpers/label_helpers.rb
+++ b/lib/api/helpers/label_helpers.rb
@@ -28,23 +28,23 @@ module API
at_least_one_of :new_name, :color, :description
end
- def find_label(parent, id_or_title, include_ancestor_groups: true)
- labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)
+ def find_label(parent, id_or_title, params = { include_ancestor_groups: true })
+ labels = available_labels_for(parent, params)
label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title)
label || not_found!('Label')
end
- def get_labels(parent, entity, include_ancestor_groups: true)
- present paginate(available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)),
+ def get_labels(parent, entity, params = {})
+ present paginate(available_labels_for(parent, params)),
with: entity,
current_user: current_user,
parent: parent,
with_counts: params[:with_counts]
end
- def get_label(parent, entity, include_ancestor_groups: true)
- label = find_label(parent, params_id_or_title, include_ancestor_groups: include_ancestor_groups)
+ def get_label(parent, entity, params = {})
+ label = find_label(parent, params_id_or_title, params)
present label, with: entity, current_user: current_user, parent: parent
end
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index 5cc435e6801..431001c227d 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -20,12 +20,23 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def retrieve_members(source, params:, deep: false)
- members = deep ? find_all_members(source) : source.members.where.not(user_id: nil)
+ members = deep ? find_all_members(source) : source_members(source).where.not(user_id: nil)
members = members.includes(:user)
members = members.references(:user).merge(User.search(params[:query])) if params[:query].present?
members = members.where(user_id: params[:user_ids]) if params[:user_ids].present?
members
end
+
+ def retrieve_member_invitations(source, query = nil)
+ members = source_members(source).where.not(invite_token: nil)
+ members = members.includes(:user)
+ members = members.where(invite_email: query) if query.present?
+ members
+ end
+
+ def source_members(source)
+ source.members
+ end
# rubocop: enable CodeReuse/ActiveRecord
def find_all_members(source)
@@ -48,6 +59,10 @@ module API
def present_members(members)
present members, with: Entities::Member, current_user: current_user, show_seat_info: params[:show_seat_info]
end
+
+ def present_member_invitations(invitations)
+ present invitations, with: Entities::Invitation, current_user: current_user
+ end
end
end
end
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index f61bcfe963e..6798c4d284b 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -5,10 +5,12 @@ module API
module NotesHelpers
include ::RendersNotes
- def self.noteable_types
- # This is a method instead of a constant, allowing EE to more easily
- # extend it.
- [Issue, MergeRequest, Snippet]
+ def self.feature_category_per_noteable_type
+ {
+ Issue => :issue_tracking,
+ MergeRequest => :code_review,
+ Snippet => :snippets
+ }
end
def update_note(noteable, note_id)
diff --git a/lib/api/helpers/packages/npm.rb b/lib/api/helpers/packages/npm.rb
new file mode 100644
index 00000000000..c1f6a001201
--- /dev/null
+++ b/lib/api/helpers/packages/npm.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module Packages
+ module Npm
+ include Gitlab::Utils::StrongMemoize
+ include ::API::Helpers::PackagesHelpers
+
+ NPM_ENDPOINT_REQUIREMENTS = {
+ package_name: API::NO_SLASH_URL_PART_REGEX
+ }.freeze
+
+ def endpoint_scope
+ params[:id].present? ? :project : :instance
+ end
+
+ def project
+ strong_memoize(:project) do
+ case endpoint_scope
+ when :project
+ user_project
+ when :instance
+ # Simulate the same behavior as #user_project by re-using #find_project!
+ # but take care if the project_id is nil as #find_project! is not designed
+ # to handle it.
+ project_id = project_id_or_nil
+
+ not_found!('Project') unless project_id
+
+ find_project!(project_id)
+ end
+ end
+ end
+
+ def project_or_nil
+ # mainly used by the metadata endpoint where we need to get a project
+ # and return nil if not found (no errors should be raised)
+ strong_memoize(:project_or_nil) do
+ next unless project_id_or_nil
+
+ find_project(project_id_or_nil)
+ end
+ end
+
+ def project_id_or_nil
+ strong_memoize(:project_id_or_nil) do
+ case endpoint_scope
+ when :project
+ params[:id]
+ when :instance
+ ::Packages::Package.npm
+ .with_name(params[:package_name])
+ .first
+ &.project_id
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/resource_label_events_helpers.rb b/lib/api/helpers/resource_label_events_helpers.rb
index 423bd4e704b..ad2733baffc 100644
--- a/lib/api/helpers/resource_label_events_helpers.rb
+++ b/lib/api/helpers/resource_label_events_helpers.rb
@@ -3,10 +3,13 @@
module API
module Helpers
module ResourceLabelEventsHelpers
- def self.eventable_types
+ def self.feature_category_per_eventable_type
# This is a method instead of a constant, allowing EE to more easily
# extend it.
- [Issue, MergeRequest]
+ {
+ Issue => :issue_tracking,
+ MergeRequest => :code_review
+ }
end
end
end
diff --git a/lib/api/import_bitbucket_server.rb b/lib/api/import_bitbucket_server.rb
index a0238c24f3b..ecd78c6e6db 100644
--- a/lib/api/import_bitbucket_server.rb
+++ b/lib/api/import_bitbucket_server.rb
@@ -2,6 +2,8 @@
module API
class ImportBitbucketServer < ::API::Base
+ feature_category :importers
+
helpers do
def client
@client ||= BitbucketServer::Client.new(credentials)
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index 61fce7a2c1b..c91a7700f58 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -2,6 +2,8 @@
module API
class ImportGithub < ::API::Base
+ feature_category :importers
+
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
before do
@@ -11,7 +13,7 @@ module API
helpers do
def client
@client ||= if Feature.enabled?(:remove_legacy_github_client)
- Gitlab::GithubImport::Client.new(params[:personal_access_token])
+ Gitlab::GithubImport::Client.new(params[:personal_access_token], host: params[:github_hostname])
else
Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options)
end
@@ -22,7 +24,7 @@ module API
end
def client_options
- {}
+ { host: params[:github_hostname] }
end
def provider
@@ -43,6 +45,7 @@ module API
requires :repo_id, type: Integer, desc: 'GitHub repository ID'
optional :new_name, type: String, desc: 'New repo name'
requires :target_namespace, type: String, desc: 'Namespace to import repo into'
+ optional :github_hostname, type: String, desc: 'Custom GitHub enterprise hostname'
end
post 'import/github' do
result = Import::GithubService.new(client, current_user, params).execute(access_params, provider)
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index 6d8f13c36e6..61ef1d5bde0 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -7,10 +7,16 @@ module API
before { authenticate_by_gitlab_shell_token! }
before do
+ api_endpoint = env['api.endpoint']
+ feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s
+
+ header[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
+
Gitlab::ApplicationContext.push(
user: -> { actor&.user },
project: -> { project },
- caller_id: route.origin
+ caller_id: route.origin,
+ feature_category: feature_category
)
end
@@ -28,10 +34,10 @@ module API
{ status: success, message: message }.merge(extra_options).compact
end
- def lfs_authentication_url(project)
+ def lfs_authentication_url(container)
# This is a separate method so that EE can alter its behaviour more
# easily.
- project.http_url_to_repo
+ container.lfs_http_url_to_repo
end
def check_allowed(params)
@@ -122,13 +128,15 @@ module API
# changes - changes as "oldrev newrev ref", see Gitlab::ChangesList
# check_ip - optional, only in EE version, may limit access to
# group resources based on its IP restrictions
- post "/allowed" do
+ post "/allowed", feature_category: :source_code_management do
# It was moved to a separate method so that EE can alter its behaviour more
# easily.
check_allowed(params)
end
- post "/lfs_authenticate" do
+ post "/lfs_authenticate", feature_category: :source_code_management do
+ not_found! unless container&.lfs_enabled?
+
status 200
unless actor.key_or_user
@@ -139,14 +147,14 @@ module API
Gitlab::LfsToken
.new(actor.key_or_user)
- .authentication_payload(lfs_authentication_url(project))
+ .authentication_payload(lfs_authentication_url(container))
end
#
# Get a ssh key using the fingerprint
#
# rubocop: disable CodeReuse/ActiveRecord
- get '/authorized_keys' do
+ get '/authorized_keys', feature_category: :source_code_management do
fingerprint = params.fetch(:fingerprint) do
Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
end
@@ -159,11 +167,11 @@ module API
#
# Discover user by ssh key, user id or username
#
- get '/discover' do
+ get '/discover', feature_category: :authentication_and_authorization do
present actor.user, with: Entities::UserSafe
end
- get '/check' do
+ get '/check', feature_category: :not_owned do
{
api_version: API.version,
gitlab_version: Gitlab::VERSION,
@@ -172,7 +180,7 @@ module API
}
end
- post '/two_factor_recovery_codes' do
+ post '/two_factor_recovery_codes', feature_category: :authentication_and_authorization do
status 200
actor.update_last_used_at!
@@ -201,7 +209,7 @@ module API
{ success: true, recovery_codes: codes }
end
- post '/personal_access_token' do
+ post '/personal_access_token', feature_category: :authentication_and_authorization do
status 200
actor.update_last_used_at!
@@ -239,7 +247,7 @@ module API
end
result = ::PersonalAccessTokens::CreateService.new(
- user, name: params[:name], scopes: params[:scopes], expires_at: expires_at
+ current_user: user, target_user: user, params: { name: params[:name], scopes: params[:scopes], expires_at: expires_at }
).execute
unless result.status == :success
@@ -251,7 +259,7 @@ module API
{ success: true, token: access_token.token, scopes: access_token.scopes, expires_at: access_token.expires_at }
end
- post '/pre_receive' do
+ post '/pre_receive', feature_category: :source_code_management do
status 200
reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase
@@ -259,7 +267,7 @@ module API
{ reference_counter_increased: reference_counter_increased }
end
- post '/post_receive' do
+ post '/post_receive', feature_category: :source_code_management do
status 200
response = PostReceiveService.new(actor.user, repository, project, params).execute
@@ -267,7 +275,7 @@ module API
present response, with: Entities::InternalPostReceive::Response
end
- post '/two_factor_config' do
+ post '/two_factor_config', feature_category: :authentication_and_authorization do
status 200
break { success: false } unless Feature.enabled?(:two_factor_for_cli)
@@ -289,7 +297,7 @@ module API
end
end
- post '/two_factor_otp_check' do
+ post '/two_factor_otp_check', feature_category: :authentication_and_authorization do
status 200
break { success: false } unless Feature.enabled?(:two_factor_for_cli)
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index 90e224b2ccb..d4690709de4 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -4,6 +4,8 @@ module API
# Kubernetes Internal API
module Internal
class Kubernetes < ::API::Base
+ feature_category :kubernetes_management
+
before do
check_feature_enabled
authenticate_gitlab_kas_request!
diff --git a/lib/api/internal/lfs.rb b/lib/api/internal/lfs.rb
index 630f0ec77a8..66baa4f1034 100644
--- a/lib/api/internal/lfs.rb
+++ b/lib/api/internal/lfs.rb
@@ -7,6 +7,8 @@ module API
before { authenticate_by_gitlab_shell_token! }
+ feature_category :source_code_management
+
helpers do
def find_lfs_object(lfs_oid)
LfsObject.find_by_oid(lfs_oid)
diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb
index 51136144c19..690f52d89f3 100644
--- a/lib/api/internal/pages.rb
+++ b/lib/api/internal/pages.rb
@@ -4,6 +4,8 @@ module API
# Pages Internal API
module Internal
class Pages < ::API::Base
+ feature_category :pages
+
before do
authenticate_gitlab_pages_request!
end
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
new file mode 100644
index 00000000000..be8147908e9
--- /dev/null
+++ b/lib/api/invitations.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module API
+ class Invitations < ::API::Base
+ include PaginationParams
+
+ feature_category :users
+
+ before { authenticate! }
+
+ helpers ::API::Helpers::MembersHelpers
+
+ %w[group project].each do |source_type|
+ params do
+ requires :id, type: String, desc: "The #{source_type} ID"
+ end
+ resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Invite non-members by email address to a group or project.' do
+ detail 'This feature was introduced in GitLab 13.6'
+ 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 :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
+ end
+ post ":id/invitations" do
+ source = find_source(source_type, params[:id])
+
+ authorize_admin_source!(source_type, source)
+
+ ::Members::InviteService.new(current_user, params).execute(source)
+ end
+
+ desc 'Get a list of group or project invitations viewable by the authenticated user' do
+ detail 'This feature was introduced in GitLab 13.6'
+ success Entities::Invitation
+ end
+ params do
+ optional :query, type: String, desc: 'A query string to search for members'
+ use :pagination
+ end
+ get ":id/invitations" do
+ source = find_source(source_type, params[:id])
+ query = params[:query]
+
+ invitations = paginate(retrieve_member_invitations(source, query))
+
+ present_member_invitations invitations
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb
index db4979c9052..e938dbbae87 100644
--- a/lib/api/issue_links.rb
+++ b/lib/api/issue_links.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :issue_tracking
+
params do
requires :id, type: String, desc: 'The ID of a project'
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 143f9e40736..6a6ee7a4e1c 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -8,6 +8,8 @@ module API
before { authenticate_non_get! }
+ feature_category :issue_tracking
+
helpers do
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'
@@ -26,6 +28,8 @@ module API
coerce_with: Validations::Validators::CheckAssigneesCount.coerce,
desc: 'Return issues which are assigned to the user with the given username'
mutually_exclusive :assignee_id, :assignee_username
+
+ use :negatable_issue_filter_params_ee
end
params :issues_stats_params do
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index 536b361b308..1faa28d6f07 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -4,6 +4,8 @@ module API
class JobArtifacts < ::API::Base
before { authenticate_non_get! }
+ feature_category :continuous_integration
+
# EE::API::JobArtifacts would override the following helpers
helpers do
def authorize_download_artifacts!
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index bdb23b4a9be..51659c2e8a1 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :continuous_integration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/keys.rb b/lib/api/keys.rb
index 2e4568029b5..fb1bedd5e92 100644
--- a/lib/api/keys.rb
+++ b/lib/api/keys.rb
@@ -5,6 +5,8 @@ module API
class Keys < ::API::Base
before { authenticate! }
+ feature_category :authentication_and_authorization
+
resource :keys do
desc 'Get single ssh key by id. Only available to admin users' do
success Entities::SSHKeyWithUser
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 0cc9f33bd07..a8fc277989e 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :issue_tracking
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -19,10 +21,12 @@ module API
desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
+ optional :search, type: String,
+ desc: 'Keyword to filter labels by. This feature was added in GitLab 13.6'
use :pagination
end
get ':id/labels' do
- get_labels(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups])
+ get_labels(user_project, Entities::ProjectLabel, declared_params)
end
desc 'Get a single label' do
@@ -34,7 +38,7 @@ module API
desc: 'Include ancestor groups'
end
get ':id/labels/:name' do
- get_label(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups])
+ get_label(user_project, Entities::ProjectLabel, declared_params)
end
desc 'Create a new label' do
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index bfd152f70b1..58181adaa93 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -2,6 +2,8 @@
module API
class Lint < ::API::Base
+ feature_category :pipeline_authoring
+
namespace :ci do
desc 'Validation of .gitlab-ci.yml content'
params do
@@ -15,9 +17,9 @@ module API
status 200
response = if error.blank?
- { status: 'valid', errors: [] }
+ { status: 'valid', errors: [], warnings: result.warnings }
else
- { status: 'invalid', errors: [error] }
+ { status: 'invalid', errors: [error], warnings: result.warnings }
end
response.tap do |response|
@@ -44,5 +46,25 @@ module API
present result, with: Entities::Ci::Lint::Result, current_user: current_user
end
end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Validation of .gitlab-ci.yml content' do
+ detail 'This feature was introduced in GitLab 13.6.'
+ end
+ params do
+ requires :content, type: String, desc: 'Content of .gitlab-ci.yml'
+ optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.'
+ end
+ post ':id/ci/lint' do
+ authorize! :download_code, user_project
+
+ result = Gitlab::Ci::Lint
+ .new(project: user_project, current_user: current_user)
+ .validate(params[:content], dry_run: params[:dry_run])
+
+ status 200
+ present result, with: Entities::Ci::Lint::Result, current_user: current_user
+ end
+ end
end
end
diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb
index 97549abd273..de612ff8321 100644
--- a/lib/api/markdown.rb
+++ b/lib/api/markdown.rb
@@ -2,6 +2,8 @@
module API
class Markdown < ::API::Base
+ feature_category :not_owned
+
params do
requires :text, type: String, desc: "The markdown text to render"
optional :gfm, type: Boolean, desc: "Render text using GitLab Flavored Markdown"
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
index a3e2fa84c32..7b4e52d18e8 100644
--- a/lib/api/maven_packages.rb
+++ b/lib/api/maven_packages.rb
@@ -5,6 +5,8 @@ module API
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
+ feature_category :package_registry
+
content_type :md5, 'text/plain'
content_type :sha1, 'text/plain'
content_type :binary, 'application/octet-stream'
@@ -244,7 +246,7 @@ module API
file_md5: params['file.md5']
}
- ::Packages::CreatePackageFileService.new(package, file_params).execute
+ ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job)).execute
end
end
end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index c28b3b1cc7c..803de51651a 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :authentication_and_authorization
+
helpers ::API::Helpers::MembersHelpers
%w[group project].each do |source_type|
@@ -134,7 +136,7 @@ module API
source = find_source(source_type, params.delete(:id))
authorize_admin_source!(source_type, source)
- member = source.members.find_by!(user_id: params[:user_id])
+ member = source_members(source).find_by!(user_id: params[:user_id])
updated_member =
::Members::UpdateService
.new(current_user, declared_params(include_missing: false))
@@ -157,7 +159,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
delete ":id/members/:user_id" do
source = find_source(source_type, params[:id])
- member = source.members.find_by!(user_id: params[:user_id])
+ member = source_members(source).find_by!(user_id: params[:user_id])
destroy_conditionally!(member) do
::Members::DestroyService.new(current_user).execute(member, unassign_issuables: params[:unassign_issuables])
diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb
index 14d6e3995ea..27ef0b9c7cd 100644
--- a/lib/api/merge_request_approvals.rb
+++ b/lib/api/merge_request_approvals.rb
@@ -4,6 +4,8 @@ module API
class MergeRequestApprovals < ::API::Base
before { authenticate_non_get! }
+ feature_category :code_review
+
helpers do
params :ee_approval_params do
end
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 22023888bbd..0ffb38438eb 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :code_review
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index b24dd870c8b..d17e451093b 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -8,6 +8,8 @@ module API
before { authenticate_non_get! }
+ feature_category :code_review
+
helpers Helpers::MergeRequestsHelpers
# EE::API::MergeRequests would override the following helpers
@@ -350,7 +352,11 @@ module API
get ':id/merge_requests/:merge_request_iid/changes' do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
- present merge_request, with: Entities::MergeRequestChanges, current_user: current_user, project: user_project
+ present merge_request,
+ with: Entities::MergeRequestChanges,
+ current_user: current_user,
+ project: user_project,
+ access_raw_diffs: params.fetch(:access_raw_diffs, false)
end
desc 'Get the merge request pipelines' do
diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb
index b6bc0af2202..0989340b3ea 100644
--- a/lib/api/metrics/dashboard/annotations.rb
+++ b/lib/api/metrics/dashboard/annotations.rb
@@ -4,6 +4,8 @@ module API
module Metrics
module Dashboard
class Annotations < ::API::Base
+ feature_category :metrics
+
desc 'Create a new monitoring dashboard annotation' do
success Entities::Metrics::Dashboard::Annotation
end
diff --git a/lib/api/metrics/user_starred_dashboards.rb b/lib/api/metrics/user_starred_dashboards.rb
index cb6e7099247..909f7f0405d 100644
--- a/lib/api/metrics/user_starred_dashboards.rb
+++ b/lib/api/metrics/user_starred_dashboards.rb
@@ -3,6 +3,8 @@
module API
module Metrics
class UserStarredDashboards < ::API::Base
+ feature_category :metrics
+
resource :projects do
desc 'Marks selected metrics dashboard as starred' do
success Entities::Metrics::UserStarredDashboard
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index f98a1f6dd1d..25a901c18b6 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :subgroups
+
helpers do
params :optional_list_params_ee do
# EE::API::Namespaces would override this helper
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 0db537ca616..d249431b2f8 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -7,7 +7,7 @@ module API
before { authenticate! }
- Helpers::NotesHelpers.noteable_types.each do |noteable_type|
+ Helpers::NotesHelpers.feature_category_per_noteable_type.each do |noteable_type, feature_category|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
@@ -29,7 +29,7 @@ module API
use :pagination
end
# rubocop: disable CodeReuse/ActiveRecord
- get ":id/#{noteables_str}/:noteable_id/notes" do
+ get ":id/#{noteables_str}/:noteable_id/notes", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
# We exclude notes that are cross-references and that cannot be viewed
@@ -57,7 +57,7 @@ module API
requires :note_id, type: Integer, desc: 'The ID of a note'
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
end
- get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
+ get ":id/#{noteables_str}/:noteable_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
get_note(noteable, params[:note_id])
end
@@ -71,7 +71,7 @@ module API
optional :confidential, type: Boolean, desc: 'Confidentiality note flag, default is false'
optional :created_at, type: String, desc: 'The creation date of the note'
end
- post ":id/#{noteables_str}/:noteable_id/notes" do
+ post ":id/#{noteables_str}/:noteable_id/notes", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
opts = {
@@ -104,7 +104,7 @@ module API
optional :body, type: String, allow_blank: false, desc: 'The content of a note'
optional :confidential, type: Boolean, desc: 'Confidentiality note flag'
end
- put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
+ put ":id/#{noteables_str}/:noteable_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
update_note(noteable, params[:note_id])
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index bad3f5ead7a..7d28394e034 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -5,6 +5,8 @@ module API
class NotificationSettings < ::API::Base
before { authenticate! }
+ feature_category :users
+
helpers ::API::Helpers::MembersHelpers
resource :notification_settings do
diff --git a/lib/api/npm_instance_packages.rb b/lib/api/npm_instance_packages.rb
new file mode 100644
index 00000000000..12fc008e00f
--- /dev/null
+++ b/lib/api/npm_instance_packages.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+module API
+ class NpmInstancePackages < ::API::Base
+ helpers ::API::Helpers::Packages::Npm
+
+ feature_category :package_registry
+
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ namespace 'packages/npm' do
+ include ::API::Concerns::Packages::NpmEndpoints
+ end
+ end
+end
diff --git a/lib/api/npm_packages.rb b/lib/api/npm_packages.rb
deleted file mode 100644
index 1443b28c1ee..00000000000
--- a/lib/api/npm_packages.rb
+++ /dev/null
@@ -1,173 +0,0 @@
-# frozen_string_literal: true
-module API
- class NpmPackages < ::API::Base
- helpers ::API::Helpers::PackagesHelpers
- helpers ::API::Helpers::Packages::DependencyProxyHelpers
-
- NPM_ENDPOINT_REQUIREMENTS = {
- package_name: API::NO_SLASH_URL_PART_REGEX
- }.freeze
-
- rescue_from ActiveRecord::RecordInvalid do |e|
- render_api_error!(e.message, 400)
- end
-
- before do
- require_packages_enabled!
- authenticate_non_get!
- end
-
- helpers do
- def project_by_package_name
- strong_memoize(:project_by_package_name) do
- ::Packages::Package.npm.with_name(params[:package_name]).first&.project
- end
- end
- end
-
- desc 'Get all tags for a given an NPM package' do
- detail 'This feature was introduced in GitLab 12.7'
- success ::API::Entities::NpmPackageTag
- end
- params do
- requires :package_name, type: String, desc: 'Package name'
- end
- get 'packages/npm/-/package/*package_name/dist-tags', format: false, requirements: NPM_ENDPOINT_REQUIREMENTS do
- package_name = params[:package_name]
-
- bad_request!('Package Name') if package_name.blank?
-
- authorize_read_package!(project_by_package_name)
-
- packages = ::Packages::Npm::PackageFinder.new(project_by_package_name, package_name)
- .execute
-
- present ::Packages::Npm::PackagePresenter.new(package_name, packages),
- with: ::API::Entities::NpmPackageTag
- end
-
- params do
- requires :package_name, type: String, desc: 'Package name'
- requires :tag, type: String, desc: "Package dist-tag"
- end
- namespace 'packages/npm/-/package/*package_name/dist-tags/:tag', requirements: NPM_ENDPOINT_REQUIREMENTS do
- desc 'Create or Update the given tag for the given NPM package and version' do
- detail 'This feature was introduced in GitLab 12.7'
- end
- put format: false do
- package_name = params[:package_name]
- version = env['api.request.body']
- tag = params[:tag]
-
- bad_request!('Package Name') if package_name.blank?
- bad_request!('Version') if version.blank?
- bad_request!('Tag') if tag.blank?
-
- authorize_create_package!(project_by_package_name)
-
- package = ::Packages::Npm::PackageFinder
- .new(project_by_package_name, package_name)
- .find_by_version(version)
- not_found!('Package') unless package
-
- ::Packages::Npm::CreateTagService.new(package, tag).execute
-
- no_content!
- end
-
- desc 'Deletes the given tag' do
- detail 'This feature was introduced in GitLab 12.7'
- end
- delete format: false do
- package_name = params[:package_name]
- tag = params[:tag]
-
- bad_request!('Package Name') if package_name.blank?
- bad_request!('Tag') if tag.blank?
-
- authorize_destroy_package!(project_by_package_name)
-
- package_tag = ::Packages::TagsFinder
- .new(project_by_package_name, package_name, package_type: :npm)
- .find_by_name(tag)
-
- not_found!('Package tag') unless package_tag
-
- ::Packages::RemoveTagService.new(package_tag).execute
-
- no_content!
- end
- end
-
- desc 'NPM registry endpoint at instance level' do
- detail 'This feature was introduced in GitLab 11.8'
- end
- params do
- requires :package_name, type: String, desc: 'Package name'
- end
- route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
- get 'packages/npm/*package_name', format: false, requirements: NPM_ENDPOINT_REQUIREMENTS do
- package_name = params[:package_name]
-
- redirect_registry_request(project_by_package_name.blank?, :npm, package_name: package_name) do
- authorize_read_package!(project_by_package_name)
-
- packages = ::Packages::Npm::PackageFinder
- .new(project_by_package_name, package_name).execute
-
- present ::Packages::Npm::PackagePresenter.new(package_name, packages),
- with: ::API::Entities::NpmPackage
- end
- end
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Download the NPM tarball' do
- detail 'This feature was introduced in GitLab 11.8'
- end
- params do
- requires :package_name, type: String, desc: 'Package name'
- requires :file_name, type: String, desc: 'Package file name'
- end
- route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
- get ':id/packages/npm/*package_name/-/*file_name', format: false do
- authorize_read_package!(user_project)
-
- package = user_project.packages.npm
- .by_name_and_file_name(params[:package_name], params[:file_name])
-
- package_file = ::Packages::PackageFileFinder
- .new(package, params[:file_name]).execute!
-
- track_package_event('pull_package', package)
-
- present_carrierwave_file!(package_file.file)
- end
-
- desc 'Create NPM package' do
- detail 'This feature was introduced in GitLab 11.8'
- end
- params do
- requires :package_name, type: String, desc: 'Package name'
- requires :versions, type: Hash, desc: 'Package version info'
- end
- route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
- put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
- authorize_create_package!(user_project)
-
- track_package_event('push_package', :npm)
-
- created_package = ::Packages::Npm::CreatePackageService
- .new(user_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
- created_package
- end
- end
- end
- end
-end
diff --git a/lib/api/npm_project_packages.rb b/lib/api/npm_project_packages.rb
new file mode 100644
index 00000000000..887084dc9ae
--- /dev/null
+++ b/lib/api/npm_project_packages.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+module API
+ class NpmProjectPackages < ::API::Base
+ helpers ::API::Helpers::Packages::Npm
+
+ feature_category :package_registry
+
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ namespace 'projects/:id/packages/npm' do
+ desc 'Download the NPM tarball' do
+ detail 'This feature was introduced in GitLab 11.8'
+ end
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ requires :file_name, type: String, desc: 'Package file name'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ get '*package_name/-/*file_name', format: false do
+ authorize_read_package!(project)
+
+ package = project.packages.npm
+ .by_name_and_file_name(params[:package_name], params[:file_name])
+
+ not_found!('Package') unless package
+
+ package_file = ::Packages::PackageFileFinder
+ .new(package, params[:file_name]).execute!
+
+ track_package_event('pull_package', package, category: 'API::NpmPackages')
+
+ present_carrierwave_file!(package_file.file)
+ end
+
+ desc 'Create NPM package' do
+ detail 'This feature was introduced in GitLab 11.8'
+ end
+ params do
+ requires :package_name, type: String, desc: 'Package name'
+ requires :versions, type: Hash, desc: 'Package version info'
+ end
+ route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
+ 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')
+
+ 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
+ created_package
+ end
+ end
+
+ include ::API::Concerns::Packages::NpmEndpoints
+ end
+ end
+end
diff --git a/lib/api/nuget_packages.rb b/lib/api/nuget_packages.rb
index 0f2c956a9df..65a85f3c930 100644
--- a/lib/api/nuget_packages.rb
+++ b/lib/api/nuget_packages.rb
@@ -10,6 +10,8 @@ module API
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
+ feature_category :package_registry
+
POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze
NON_NEGATIVE_INTEGER_REGEX = %r{\A0|[1-9]\d*\z}.freeze
diff --git a/lib/api/package_files.rb b/lib/api/package_files.rb
index c1fc9a6e4d8..4a33f3e8af2 100644
--- a/lib/api/package_files.rb
+++ b/lib/api/package_files.rb
@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_project)
end
+ feature_category :package_registry
+
helpers ::API::Helpers::PackagesHelpers
params do
diff --git a/lib/api/pages.rb b/lib/api/pages.rb
index 813307c498f..5f695f3853d 100644
--- a/lib/api/pages.rb
+++ b/lib/api/pages.rb
@@ -2,6 +2,8 @@
module API
class Pages < ::API::Base
+ feature_category :pages
+
before do
require_pages_config_enabled!
authenticated_with_can_read_all_resources!
diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb
index 00c51298c45..2e7f8475509 100644
--- a/lib/api/pages_domains.rb
+++ b/lib/api/pages_domains.rb
@@ -4,6 +4,8 @@ module API
class PagesDomains < ::API::Base
include PaginationParams
+ feature_category :pages
+
PAGES_DOMAINS_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(domain: API::NO_SLASH_URL_PART_REGEX)
before do
diff --git a/lib/api/personal_access_tokens.rb b/lib/api/personal_access_tokens.rb
new file mode 100644
index 00000000000..2c60938b75a
--- /dev/null
+++ b/lib/api/personal_access_tokens.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module API
+ class PersonalAccessTokens < ::API::Base
+ include ::API::PaginationParams
+
+ feature_category :authentication_and_authorization
+
+ desc 'Get all Personal Access Tokens' do
+ detail 'This feature was added in GitLab 13.3'
+ success Entities::PersonalAccessToken
+ end
+ params do
+ optional :user_id, type: Integer, desc: 'User ID'
+
+ use :pagination
+ end
+
+ before do
+ authenticate!
+ restrict_non_admins! unless current_user.admin?
+ end
+
+ helpers do
+ def finder_params(current_user)
+ current_user.admin? ? { user: user(params[:user_id]) } : { user: current_user }
+ end
+
+ def user(user_id)
+ UserFinder.new(user_id).find_by_id
+ end
+
+ def restrict_non_admins!
+ return if params[:user_id].blank?
+
+ unauthorized! unless Ability.allowed?(current_user, :read_user_personal_access_tokens, user(params[:user_id]))
+ end
+
+ def find_token(id)
+ PersonalAccessToken.find(id) || not_found!
+ end
+ end
+
+ resources :personal_access_tokens do
+ get do
+ tokens = PersonalAccessTokensFinder.new(finder_params(current_user), current_user).execute
+
+ present paginate(tokens), with: Entities::PersonalAccessToken
+ end
+
+ delete ':id' do
+ service = ::PersonalAccessTokens::RevokeService.new(
+ current_user,
+ token: find_token(params[:id])
+ ).execute
+
+ service.success? ? no_content! : bad_request!(nil)
+ end
+ end
+ end
+end
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
index 46ccb4ba1a0..cfb0c5fd705 100644
--- a/lib/api/project_clusters.rb
+++ b/lib/api/project_clusters.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :kubernetes_management
+
params do
requires :id, type: String, desc: 'The ID of the project'
end
diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb
index d565531d372..3125de88de5 100644
--- a/lib/api/project_container_repositories.rb
+++ b/lib/api/project_container_repositories.rb
@@ -10,6 +10,8 @@ module API
before { authorize_read_container_images! }
+ feature_category :package_registry
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/project_events.rb b/lib/api/project_events.rb
index 3765473bc0e..69b47f9420d 100644
--- a/lib/api/project_events.rb
+++ b/lib/api/project_events.rb
@@ -6,6 +6,8 @@ module API
include APIGuard
helpers ::API::Helpers::EventsHelpers
+ feature_category :users
+
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 184f89200ab..76b3dea723a 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -4,6 +4,8 @@ module API
class ProjectExport < ::API::Base
helpers Helpers::RateLimiter
+ feature_category :importers
+
before do
not_found! unless Gitlab::CurrentSettings.project_export_enabled?
authorize_admin_project
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index bc2d8c816a8..431ba199131 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
before { authorize_admin_project }
+ feature_category :integrations
+
helpers do
params :project_hook_properties do
requires :url, type: String, desc: "The URL to send the request to"
@@ -21,6 +23,7 @@ module API
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
optional :deployment_events, type: Boolean, desc: "Trigger hook on deployment events"
+ optional :releases_events, type: Boolean, desc: "Trigger hook on release events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
optional :push_events_branch_filter, type: String, desc: "Trigger hook on specified branch only"
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index 5c4e1d73ee1..15b06cea385 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -8,6 +8,8 @@ module API
helpers Helpers::FileUploadHelpers
helpers Helpers::RateLimiter
+ feature_category :importers
+
helpers do
def import_params
declared_params(include_missing: false)
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index a81118f44bd..8675de33923 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
+ feature_category :issue_tracking
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb
index b8d97b1243a..56e94333433 100644
--- a/lib/api/project_packages.rb
+++ b/lib/api/project_packages.rb
@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_project)
end
+ feature_category :package_registry
+
helpers ::API::Helpers::PackagesHelpers
params do
diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb
index 38eb74663d3..fe6de3ea385 100644
--- a/lib/api/project_repository_storage_moves.rb
+++ b/lib/api/project_repository_storage_moves.rb
@@ -6,6 +6,8 @@ module API
before { authenticated_as_admin! }
+ feature_category :gitaly
+
resource :project_repository_storage_moves do
desc 'Get a list of all project repository storage moves' do
detail 'This feature was introduced in GitLab 13.0.'
diff --git a/lib/api/project_snapshots.rb b/lib/api/project_snapshots.rb
index e19afb6e8e4..d33d2976b1c 100644
--- a/lib/api/project_snapshots.rb
+++ b/lib/api/project_snapshots.rb
@@ -6,6 +6,8 @@ module API
before { authorize_read_git_snapshot! }
+ feature_category :source_code_management
+
resource :projects do
desc 'Download a (possibly inconsistent) snapshot of a repository' do
detail 'This feature was introduced in GitLab 10.7'
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index b4de260fe49..899984fe0ba 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -6,6 +6,8 @@ module API
before { check_snippets_enabled }
+ feature_category :snippets
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/project_statistics.rb b/lib/api/project_statistics.rb
index 1ead969fc81..3db8d20ebac 100644
--- a/lib/api/project_statistics.rb
+++ b/lib/api/project_statistics.rb
@@ -2,6 +2,8 @@
module API
class ProjectStatistics < ::API::Base
+ feature_category :source_code_management
+
before do
authenticate!
authorize! :daily_statistics, user_project
diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb
index 7d851de0237..af5d96969ef 100644
--- a/lib/api/project_templates.rb
+++ b/lib/api/project_templates.rb
@@ -12,6 +12,8 @@ module API
before { authenticate_non_get! }
+ feature_category :templates
+
params do
requires :id, type: String, desc: 'The ID of a project'
requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses|metrics_dashboard_ymls|issues|merge_requests) of the template'
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index ecee76ae60c..2012c348cd1 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -11,6 +11,8 @@ module API
before { authenticate_non_get! }
+ feature_category :projects, ['/projects/:id/custom_attributes', '/projects/:id/custom_attributes/:key']
+
helpers do
# EE::API::Projects would override this method
def apply_filters(projects)
@@ -150,7 +152,7 @@ module API
use :statistics_params
use :with_custom_attributes
end
- get ":user_id/projects" do
+ get ":user_id/projects", feature_category: :projects do
user = find_user(params[:user_id])
not_found!('User') unless user
@@ -167,7 +169,7 @@ module API
use :collection_params
use :statistics_params
end
- get ":user_id/starred_projects" do
+ get ":user_id/starred_projects", feature_category: :projects do
user = find_user(params[:user_id])
not_found!('User') unless user
@@ -187,7 +189,7 @@ module API
use :statistics_params
use :with_custom_attributes
end
- get do
+ get feature_category: :projects do
present_projects load_projects
end
@@ -234,7 +236,7 @@ module API
use :create_params
end
# rubocop: disable CodeReuse/ActiveRecord
- post "user/:user_id" do
+ post "user/:user_id", feature_category: :projects do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/21139')
authenticated_as_admin!
user = User.find_by(id: params.delete(:user_id))
@@ -270,7 +272,7 @@ module API
optional :license, type: Boolean, default: false,
desc: 'Include project license data'
end
- get ":id" do
+ get ":id", feature_category: :projects do
options = {
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
current_user: current_user,
@@ -294,7 +296,7 @@ module API
optional :path, type: String, desc: 'The path that will be assigned to the fork'
optional :name, type: String, desc: 'The name that will be assigned to the fork'
end
- post ':id/fork' do
+ post ':id/fork', feature_category: :source_code_management do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284')
not_found! unless can?(current_user, :fork_project, user_project)
@@ -332,14 +334,14 @@ module API
use :collection_params
use :with_custom_attributes
end
- get ':id/forks' do
+ get ':id/forks', feature_category: :source_code_management do
forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute
present_projects forks, request_scope: user_project
end
desc 'Check pages access of this project'
- get ':id/pages_access' do
+ get ':id/pages_access', feature_category: :pages do
authorize! :read_pages_content, user_project unless user_project.public_pages?
status 200
end
@@ -357,7 +359,7 @@ module API
at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of)
end
- put ':id' do
+ put ':id', feature_category: :projects do
authorize_admin_project
attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present?
@@ -381,7 +383,7 @@ module API
desc 'Archive a project' do
success Entities::Project
end
- post ':id/archive' do
+ post ':id/archive', feature_category: :projects do
authorize!(:archive_project, user_project)
::Projects::UpdateService.new(user_project, current_user, archived: true).execute
@@ -392,7 +394,7 @@ module API
desc 'Unarchive a project' do
success Entities::Project
end
- post ':id/unarchive' do
+ post ':id/unarchive', feature_category: :projects do
authorize!(:archive_project, user_project)
::Projects::UpdateService.new(user_project, current_user, archived: false).execute
@@ -403,7 +405,7 @@ module API
desc 'Star a project' do
success Entities::Project
end
- post ':id/star' do
+ post ':id/star', feature_category: :projects do
if current_user.starred?(user_project)
not_modified!
else
@@ -417,7 +419,7 @@ module API
desc 'Unstar a project' do
success Entities::Project
end
- post ':id/unstar' do
+ post ':id/unstar', feature_category: :projects do
if current_user.starred?(user_project)
current_user.toggle_star(user_project)
user_project.reset
@@ -435,21 +437,21 @@ module API
optional :search, type: String, desc: 'Return list of users matching the search criteria'
use :pagination
end
- get ':id/starrers' do
+ get ':id/starrers', feature_category: :projects do
starrers = UsersStarProjectsFinder.new(user_project, params, current_user: current_user).execute
present paginate(starrers), with: Entities::UserStarsProject
end
desc 'Get languages in project repository'
- get ':id/languages' do
+ get ':id/languages', feature_category: :source_code_management do
::Projects::RepositoryLanguagesService
.new(user_project, current_user)
.execute.map { |lang| [lang.name, lang.share] }.to_h
end
desc 'Delete a project'
- delete ":id" do
+ delete ":id", feature_category: :projects do
authorize! :remove_project, user_project
delete_project(user_project)
@@ -459,7 +461,7 @@ module API
params do
requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from'
end
- post ":id/fork/:forked_from_id" do
+ post ":id/fork/:forked_from_id", feature_category: :source_code_management do
authorize! :admin_project, user_project
fork_from_project = find_project!(params[:forked_from_id])
@@ -478,7 +480,7 @@ module API
end
desc 'Remove a forked_from relationship'
- delete ":id/fork" do
+ delete ":id/fork", feature_category: :source_code_management do
authorize! :remove_fork_project, user_project
result = destroy_conditionally!(user_project) do
@@ -496,7 +498,7 @@ module API
requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level'
optional :expires_at, type: Date, desc: 'Share expiration date'
end
- post ":id/share" do
+ post ":id/share", feature_category: :authentication_and_authorization do
authorize! :admin_project, user_project
group = Group.find_by_id(params[:group_id])
@@ -518,7 +520,7 @@ module API
requires :group_id, type: Integer, desc: 'The ID of the group'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete ":id/share/:group_id" do
+ delete ":id/share/:group_id", feature_category: :authentication_and_authorization do
authorize! :admin_project, user_project
link = user_project.project_group_links.find_by(group_id: params[:group_id])
@@ -535,7 +537,7 @@ module API
# TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
requires :file, type: File, desc: 'The file to be uploaded' # rubocop:disable Scalability/FileUploads
end
- post ":id/uploads" do
+ post ":id/uploads", feature_category: :not_owned do
upload = UploadService.new(user_project, params[:file]).execute
present upload, with: Entities::ProjectUpload
@@ -549,7 +551,7 @@ module API
optional :skip_users, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Filter out users with the specified IDs'
use :pagination
end
- get ':id/users' do
+ get ':id/users', feature_category: :authentication_and_authorization do
users = DeclarativePolicy.subject_scope { user_project.team.users }
users = users.search(params[:search]) if params[:search].present?
users = users.where_not_in(params[:skip_users]) if params[:skip_users].present?
@@ -560,7 +562,7 @@ module API
desc 'Start the housekeeping task for a project' do
detail 'This feature was introduced in GitLab 9.0.'
end
- post ':id/housekeeping' do
+ post ':id/housekeeping', feature_category: :source_code_management do
authorize_admin_project
begin
@@ -574,7 +576,7 @@ module API
params do
requires :namespace, type: String, desc: 'The ID or path of the new namespace'
end
- put ":id/transfer" do
+ put ":id/transfer", feature_category: :projects do
authorize! :change_namespace, user_project
namespace = find_namespace!(params[:namespace])
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index a448682d8bd..17574739a7c 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -8,6 +8,8 @@ module API
before { authorize_admin_project }
+ feature_category :source_code_management
+
helpers Helpers::ProtectedBranchesHelpers
params do
diff --git a/lib/api/protected_tags.rb b/lib/api/protected_tags.rb
index dd3e407ffc9..b9385df1f8d 100644
--- a/lib/api/protected_tags.rb
+++ b/lib/api/protected_tags.rb
@@ -8,6 +8,8 @@ module API
before { authorize_admin_project }
+ feature_category :source_code_management
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
index 5622bc6e42d..7104fb8d999 100644
--- a/lib/api/pypi_packages.rb
+++ b/lib/api/pypi_packages.rb
@@ -12,6 +12,8 @@ module API
helpers ::API::Helpers::Packages::BasicAuthHelpers
include ::API::Helpers::Packages::BasicAuthHelpers::Constants
+ feature_category :package_registry
+
default_format :json
rescue_from ArgumentError do |e|
diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb
index 23de9f9fc9f..d3a185a51c8 100644
--- a/lib/api/release/links.rb
+++ b/lib/api/release/links.rb
@@ -10,6 +10,8 @@ module API
before { authorize! :read_release, user_project }
+ feature_category :release_orchestration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 3bd6ea77403..c20e618efd1 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -9,6 +9,8 @@ module API
before { authorize_read_releases! }
+ feature_category :release_orchestration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -89,7 +91,7 @@ module API
optional :name, type: String, desc: 'The name of the release'
optional :description, type: String, desc: 'Release notes with markdown support'
optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.'
- optional :milestones, type: Array, desc: 'The titles of the related milestones'
+ optional :milestones, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The titles of the related milestones'
end
put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do
authorize_update_release!
diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb
index f63ea04a529..83096772d32 100644
--- a/lib/api/remote_mirrors.rb
+++ b/lib/api/remote_mirrors.rb
@@ -4,6 +4,8 @@ module API
class RemoteMirrors < ::API::Base
include PaginationParams
+ feature_category :source_code_management
+
before do
unauthorized! unless can?(current_user, :admin_remote_mirror, user_project)
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 38ac1f22a48..8af8ffc3b63 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -6,10 +6,14 @@ module API
class Repositories < ::API::Base
include PaginationParams
+ content_type :txt, 'text/plain'
+
helpers ::API::Helpers::HeadersHelpers
before { authorize! :download_code, user_project }
+ feature_category :source_code_management
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb
index d3a219f0810..33589f6c393 100644
--- a/lib/api/resource_label_events.rb
+++ b/lib/api/resource_label_events.rb
@@ -7,7 +7,7 @@ module API
before { authenticate! }
- Helpers::ResourceLabelEventsHelpers.eventable_types.each do |eventable_type|
+ Helpers::ResourceLabelEventsHelpers.feature_category_per_eventable_type.each do |eventable_type, feature_category|
parent_type = eventable_type.parent_class.to_s.underscore
eventables_str = eventable_type.to_s.underscore.pluralize
@@ -24,7 +24,7 @@ module API
use :pagination
end
- get ":id/#{eventables_str}/:eventable_id/resource_label_events" do
+ get ":id/#{eventables_str}/:eventable_id/resource_label_events", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id])
events = eventable.resource_label_events.inc_relations
@@ -40,7 +40,7 @@ module API
requires :event_id, type: String, desc: 'The ID of a resource label event'
requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable'
end
- get ":id/#{eventables_str}/:eventable_id/resource_label_events/:event_id" do
+ get ":id/#{eventables_str}/:eventable_id/resource_label_events/:event_id", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id])
event = eventable.resource_label_events.find(params[:event_id])
diff --git a/lib/api/resource_milestone_events.rb b/lib/api/resource_milestone_events.rb
index 21411f68dd5..aeedd7ad109 100644
--- a/lib/api/resource_milestone_events.rb
+++ b/lib/api/resource_milestone_events.rb
@@ -7,7 +7,10 @@ module API
before { authenticate! }
- [Issue, MergeRequest].each do |eventable_type|
+ {
+ Issue => :issue_tracking,
+ MergeRequest => :code_review
+ }.each do |eventable_type, feature_category|
parent_type = eventable_type.parent_class.to_s.underscore
eventables_str = eventable_type.to_s.underscore.pluralize
@@ -23,7 +26,7 @@ module API
use :pagination
end
- get ":id/#{eventables_str}/:eventable_id/resource_milestone_events" do
+ get ":id/#{eventables_str}/:eventable_id/resource_milestone_events", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id])
events = ResourceMilestoneEventFinder.new(current_user, eventable).execute
@@ -38,7 +41,7 @@ module API
requires :event_id, type: String, desc: 'The ID of a resource milestone event'
requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable'
end
- get ":id/#{eventables_str}/:eventable_id/resource_milestone_events/:event_id" do
+ get ":id/#{eventables_str}/:eventable_id/resource_milestone_events/:event_id", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id])
event = eventable.resource_milestone_events.find(params[:event_id])
diff --git a/lib/api/resource_state_events.rb b/lib/api/resource_state_events.rb
index 9bfda39be90..3460aa2c00e 100644
--- a/lib/api/resource_state_events.rb
+++ b/lib/api/resource_state_events.rb
@@ -7,7 +7,10 @@ module API
before { authenticate! }
- [Issue, MergeRequest].each do |eventable_class|
+ {
+ Issue => :issue_tracking,
+ MergeRequest => :code_review
+ }.each do |eventable_class, feature_category|
eventable_name = eventable_class.to_s.underscore
params do
@@ -22,7 +25,7 @@ module API
use :pagination
end
- get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events" do
+ get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events", feature_category: feature_category do
eventable = find_noteable(eventable_class, params[:eventable_iid])
events = ResourceStateEventFinder.new(current_user, eventable).execute
@@ -37,7 +40,7 @@ module API
requires :eventable_iid, types: Integer, desc: "The IID of the #{eventable_name}"
requires :event_id, type: Integer, desc: 'The ID of a resource state event'
end
- get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events/:event_id" do
+ get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events/:event_id", feature_category: feature_category do
eventable = find_noteable(eventable_class, params[:eventable_iid])
event = ResourceStateEventFinder.new(current_user, eventable).find(params[:event_id])
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 85f0a8e2e60..f0ffe6ba443 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :global_search
+
helpers do
SCOPE_ENTITY = {
merge_requests: Entities::MergeRequestBasic,
@@ -35,8 +37,11 @@ module API
state: params[:state],
confidential: params[:confidential],
snippets: snippets?,
+ basic_search: params[:basic_search],
page: params[:page],
- per_page: params[:per_page]
+ per_page: params[:per_page],
+ order_by: params[:order_by],
+ sort: params[:sort]
}.merge(additional_params)
results = SearchService.new(current_user, search_params).search_objects(preload_method)
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 5f3d14010a8..cfcae13e518 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module API
class Services < ::API::Base
+ feature_category :integrations
+
services = Helpers::ServicesHelpers.services
service_classes = Helpers::ServicesHelpers.service_classes
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index dc917d9c529..b95856d99d1 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -4,6 +4,8 @@ module API
class Settings < ::API::Base
before { authenticated_as_admin! }
+ feature_category :not_owned
+
helpers Helpers::SettingsHelpers
helpers do
@@ -51,9 +53,9 @@ module API
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility'
optional :disabled_oauth_sign_in_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Disable certain OAuth sign-in sources'
- optional :domain_blacklist_enabled, type: Boolean, desc: 'Enable domain blacklist for sign ups'
- optional :domain_blacklist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
- optional :domain_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
+ optional :domain_denylist_enabled, type: Boolean, desc: 'Enable domain denylist for sign ups'
+ optional :domain_denylist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
+ optional :domain_allowlist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
optional :eks_integration_enabled, type: Boolean, desc: 'Enable integration with Amazon EKS'
given eks_integration_enabled: -> (val) { val } do
requires :eks_account_id, type: String, desc: 'Amazon account ID for EKS integration'
@@ -157,6 +159,7 @@ module API
optional :issues_create_limit, type: Integer, desc: "Maximum number of issue creation requests allowed per minute per user. Set to 0 for unlimited requests per minute."
optional :raw_blob_request_limit, type: Integer, desc: "Maximum number of requests per minute for each raw path. Set to 0 for unlimited requests per minute."
optional :wiki_page_max_content_bytes, type: Integer, desc: "Maximum wiki page content size in bytes"
+ optional :require_admin_approval_after_user_signup, type: Boolean, desc: 'Require explicit admin approval for new signups'
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb
index b025dbfab37..680363d036e 100644
--- a/lib/api/sidekiq_metrics.rb
+++ b/lib/api/sidekiq_metrics.rb
@@ -6,6 +6,8 @@ module API
class SidekiqMetrics < ::API::Base
before { authenticated_as_admin! }
+ feature_category :not_owned
+
helpers do
def queue_metrics
Sidekiq::Queue.all.each_with_object({}) do |queue, hash|
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 2e67b9649bc..52b597fb788 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -5,6 +5,8 @@ module API
class Snippets < ::API::Base
include PaginationParams
+ feature_category :snippets
+
resource :snippets do
helpers Helpers::SnippetsHelpers
helpers do
diff --git a/lib/api/statistics.rb b/lib/api/statistics.rb
index fa7176491ba..1814e1a6782 100644
--- a/lib/api/statistics.rb
+++ b/lib/api/statistics.rb
@@ -4,6 +4,8 @@ module API
class Statistics < ::API::Base
before { authenticated_as_admin! }
+ feature_category :instance_statistics
+
COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue,
MergeRequest, Note, Snippet, Key, Milestone].freeze
diff --git a/lib/api/submodules.rb b/lib/api/submodules.rb
index e2ceb49c119..5c71a18c6d0 100644
--- a/lib/api/submodules.rb
+++ b/lib/api/submodules.rb
@@ -4,6 +4,8 @@ module API
class Submodules < ::API::Base
before { authenticate! }
+ feature_category :source_code_management
+
helpers do
def commit_params(attrs)
{
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index 35a28da4736..914bab52929 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -11,25 +11,29 @@ module API
type: 'merge_requests',
entity: Entities::MergeRequest,
source: Project,
- finder: ->(id) { find_merge_request_with_access(id, :update_merge_request) }
+ finder: ->(id) { find_merge_request_with_access(id, :update_merge_request) },
+ feature_category: :code_review
},
{
type: 'issues',
entity: Entities::Issue,
source: Project,
- finder: ->(id) { find_project_issue(id) }
+ finder: ->(id) { find_project_issue(id) },
+ feature_category: :issue_tracking
},
{
type: 'labels',
entity: Entities::ProjectLabel,
source: Project,
- finder: ->(id) { find_label(user_project, id) }
+ finder: ->(id) { find_label(user_project, id) },
+ feature_category: :issue_tracking
},
{
type: 'labels',
entity: Entities::GroupLabel,
source: Group,
- finder: ->(id) { find_label(user_group, id) }
+ finder: ->(id) { find_label(user_group, id) },
+ feature_category: :issue_tracking
}
]
@@ -44,7 +48,7 @@ module API
desc 'Subscribe to a resource' do
success subscribable[:entity]
end
- post ":id/#{subscribable[:type]}/:subscribable_id/subscribe" do
+ post ":id/#{subscribable[:type]}/:subscribable_id/subscribe", subscribable.slice(:feature_category) do
parent = parent_resource(source_type)
resource = instance_exec(params[:subscribable_id], &subscribable[:finder])
@@ -59,7 +63,7 @@ module API
desc 'Unsubscribe from a resource' do
success subscribable[:entity]
end
- post ":id/#{subscribable[:type]}/:subscribable_id/unsubscribe" do
+ post ":id/#{subscribable[:type]}/:subscribable_id/unsubscribe", subscribable.slice(:feature_category) do
parent = parent_resource(source_type)
resource = instance_exec(params[:subscribable_id], &subscribable[:finder])
diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb
index f23d279c3f4..a024d6de874 100644
--- a/lib/api/suggestions.rb
+++ b/lib/api/suggestions.rb
@@ -4,6 +4,8 @@ module API
class Suggestions < ::API::Base
before { authenticate! }
+ feature_category :code_review
+
resource :suggestions do
desc 'Apply suggestion patch in the Merge Request it was created' do
success Entities::Suggestion
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 2820d305d0f..42e16d47a0b 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -4,6 +4,8 @@ module API
class SystemHooks < ::API::Base
include PaginationParams
+ feature_category :integrations
+
before do
authenticate!
authenticated_as_admin!
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index b969394ec47..7636c45bdac 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -23,7 +23,7 @@ module API
optional :search, type: String, desc: 'Return list of tags matching the search criteria'
use :pagination
end
- get ':id/repository/tags' do
+ get ':id/repository/tags', feature_category: :source_code_management do
tags = ::TagsFinder.new(user_project.repository,
sort: "#{params[:order_by]}_#{params[:sort]}",
search: params[:search]).execute
@@ -37,7 +37,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
- get ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
+ get ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :source_code_management do
tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag
@@ -54,7 +54,7 @@ module API
optional :message, type: String, desc: 'Specifying a message creates an annotated tag'
optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)'
end
- post ':id/repository/tags' do
+ post ':id/repository/tags', :release_orchestration do
authorize_admin_tag
result = ::Tags::CreateService.new(user_project, current_user)
@@ -86,7 +86,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
- delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
+ delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :source_code_management do
authorize_admin_tag
tag = user_project.repository.find_tag(params[:tag_name])
@@ -112,7 +112,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
requires :description, type: String, desc: 'Release notes with markdown support'
end
- post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do
+ post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
authorize_create_release!
##
@@ -144,7 +144,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
requires :description, type: String, desc: 'Release notes with markdown support'
end
- put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do
+ put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
authorize_update_release!
result = ::Releases::UpdateService
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 0b427bbf5b9..b7fb35eac03 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -4,6 +4,8 @@ module API
class Templates < ::API::Base
include PaginationParams
+ feature_category :templates
+
GLOBAL_TEMPLATE_TYPES = {
gitignores: {
gitlab_version: 8.8
diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb
index 3dbde4639ca..c664c0a4590 100644
--- a/lib/api/terraform/state.rb
+++ b/lib/api/terraform/state.rb
@@ -7,6 +7,8 @@ module API
class State < ::API::Base
include ::Gitlab::Utils::StrongMemoize
+ feature_category :infrastructure_as_code
+
default_format :json
before do
@@ -51,7 +53,7 @@ module API
no_content! if data.empty?
remote_state_handler.handle_with_lock do |state|
- state.update_file!(CarrierWaveStringFile.new(data), version: params[:serial])
+ state.update_file!(CarrierWaveStringFile.new(data), version: params[:serial], build: current_authenticated_job)
end
body false
diff --git a/lib/api/terraform/state_version.rb b/lib/api/terraform/state_version.rb
index b4a0efd7a2b..d3680323b9f 100644
--- a/lib/api/terraform/state_version.rb
+++ b/lib/api/terraform/state_version.rb
@@ -5,6 +5,8 @@ module API
class StateVersion < ::API::Base
default_format :json
+ feature_category :infrastructure_as_code
+
before do
authenticate!
authorize! :read_terraform_state, user_project
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index ce07d13cc9a..03850ba1c4e 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :issue_tracking
+
ISSUABLE_TYPES = {
'merge_requests' => ->(iid) { find_merge_request_with_access(iid) },
'issues' => ->(iid) { find_project_issue(iid) }
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 960d004a04c..aebbc95cbea 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -6,6 +6,8 @@ module API
HTTP_GITLAB_EVENT_HEADER = "HTTP_#{WebHookService::GITLAB_EVENT_HEADER}".underscore.upcase
+ feature_category :continuous_integration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/unleash.rb b/lib/api/unleash.rb
index 907422118f1..3148c56339a 100644
--- a/lib/api/unleash.rb
+++ b/lib/api/unleash.rb
@@ -4,6 +4,8 @@ module API
class Unleash < ::API::Base
include PaginationParams
+ feature_category :feature_flags
+
namespace :feature_flags do
resource :unleash, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb
index fa5bfc1cbe9..7b038ec74bb 100644
--- a/lib/api/usage_data.rb
+++ b/lib/api/usage_data.rb
@@ -4,6 +4,8 @@ module API
class UsageData < ::API::Base
before { authenticate! }
+ feature_category :collection
+
namespace 'usage_data' do
before do
not_found! unless Feature.enabled?(:usage_data_api, default_enabled: true)
diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb
index 6d9db53fec8..3071f08e1de 100644
--- a/lib/api/user_counts.rb
+++ b/lib/api/user_counts.rb
@@ -2,6 +2,8 @@
module API
class UserCounts < ::API::Base
+ feature_category :navigation
+
resource :user_counts do
desc 'Return the user specific counts' do
detail 'Open MR Count'
diff --git a/lib/api/users.rb b/lib/api/users.rb
index e7c1d644324..501ed629c7e 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -8,6 +8,8 @@ module API
allow_access_with_scope :read_user, if: -> (request) { request.get? }
+ feature_category :users, ['/users/:id/custom_attributes', '/users/:id/custom_attributes/:key']
+
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
include CustomAttributesEndpoints
@@ -63,9 +65,9 @@ module API
params :sort_params do
optional :order_by, type: String, values: %w[id name username created_at updated_at],
- default: 'id', desc: 'Return users ordered by a field'
+ default: 'id', desc: 'Return users ordered by a field'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
- desc: 'Return users sorted in ascending and descending order'
+ desc: 'Return users sorted in ascending and descending order'
end
end
@@ -93,7 +95,7 @@ module API
use :optional_index_params_ee
end
# rubocop: disable CodeReuse/ActiveRecord
- get do
+ get feature_category: :users do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
unless current_user&.admin?
@@ -134,7 +136,7 @@ module API
use :with_custom_attributes
end
# rubocop: disable CodeReuse/ActiveRecord
- get ":id" do
+ get ":id", feature_category: :users do
user = User.find_by(id: params[:id])
not_found!('User') unless user && can?(current_user, :read_user, user)
@@ -149,7 +151,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 do
+ get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :users do
user = find_user(params[:user_id])
not_found!('User') unless user && can?(current_user, :read_user, user)
@@ -170,7 +172,7 @@ module API
optional :force_random_password, type: Boolean, desc: 'Flag indicating a random password will be set'
use :optional_attributes
end
- post do
+ post feature_category: :users do
authenticated_as_admin!
params = declared_params(include_missing: false)
@@ -204,7 +206,7 @@ module API
use :optional_attributes
end
# rubocop: disable CodeReuse/ActiveRecord
- put ":id" do
+ put ":id", feature_category: :users do
authenticated_as_admin!
user = User.find_by(id: params.delete(:id))
@@ -245,7 +247,7 @@ module API
requires :provider, type: String, desc: 'The external provider'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete ":id/identities/:provider" do
+ delete ":id/identities/:provider", feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params[:id])
@@ -268,7 +270,7 @@ module API
optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'
end
# rubocop: disable CodeReuse/ActiveRecord
- post ":id/keys" do
+ post ":id/keys", feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params.delete(:id))
@@ -291,7 +293,7 @@ module API
requires :user_id, type: String, desc: 'The ID or username of the user'
use :pagination
end
- get ':user_id/keys', requirements: API::USER_REQUIREMENTS do
+ get ':user_id/keys', requirements: API::USER_REQUIREMENTS, feature_category: :authentication_and_authorization do
user = find_user(params[:user_id])
not_found!('User') unless user && can?(current_user, :read_user, user)
@@ -307,7 +309,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete ':id/keys/:key_id' do
+ delete ':id/keys/:key_id', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params[:id])
@@ -332,7 +334,7 @@ module API
requires :key, type: String, desc: 'The new GPG key'
end
# rubocop: disable CodeReuse/ActiveRecord
- post ':id/gpg_keys' do
+ post ':id/gpg_keys', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params.delete(:id))
@@ -357,7 +359,7 @@ module API
use :pagination
end
# rubocop: disable CodeReuse/ActiveRecord
- get ':id/gpg_keys' do
+ get ':id/gpg_keys', feature_category: :authentication_and_authorization do
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -374,7 +376,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
# rubocop: disable CodeReuse/ActiveRecord
- get ':id/gpg_keys/:key_id' do
+ get ':id/gpg_keys/:key_id', feature_category: :authentication_and_authorization do
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -393,7 +395,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete ':id/gpg_keys/:key_id' do
+ delete ':id/gpg_keys/:key_id', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params[:id])
@@ -417,7 +419,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
# rubocop: disable CodeReuse/ActiveRecord
- post ':id/gpg_keys/:key_id/revoke' do
+ post ':id/gpg_keys/:key_id/revoke', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params[:id])
@@ -440,7 +442,7 @@ module API
optional :skip_confirmation, type: Boolean, desc: 'Skip confirmation of email and assume it is verified'
end
# rubocop: disable CodeReuse/ActiveRecord
- post ":id/emails" do
+ post ":id/emails", feature_category: :users do
authenticated_as_admin!
user = User.find_by(id: params.delete(:id))
@@ -464,7 +466,7 @@ module API
use :pagination
end
# rubocop: disable CodeReuse/ActiveRecord
- get ':id/emails' do
+ get ':id/emails', feature_category: :users do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -481,7 +483,7 @@ module API
requires :email_id, type: Integer, desc: 'The ID of the email'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete ':id/emails/:email_id' do
+ delete ':id/emails/:email_id', feature_category: :users do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -503,7 +505,7 @@ module API
optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions"
end
# rubocop: disable CodeReuse/ActiveRecord
- delete ":id" do
+ delete ":id", feature_category: :users do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/20757')
authenticated_as_admin!
@@ -523,7 +525,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user'
end
# rubocop: disable CodeReuse/ActiveRecord
- post ':id/activate' do
+ post ':id/activate', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params[:id])
@@ -538,7 +540,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user'
end
# rubocop: disable CodeReuse/ActiveRecord
- post ':id/deactivate' do
+ post ':id/deactivate', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -564,7 +566,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user'
end
# rubocop: disable CodeReuse/ActiveRecord
- post ':id/block' do
+ post ':id/block', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -589,7 +591,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user'
end
# rubocop: disable CodeReuse/ActiveRecord
- post ':id/unblock' do
+ post ':id/unblock', feature_category: :authentication_and_authorization do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -612,7 +614,7 @@ module API
optional :type, type: String, values: %w[Project Namespace]
use :pagination
end
- get ":user_id/memberships" do
+ get ":user_id/memberships", feature_category: :users do
authenticated_as_admin!
user = find_user_by_id(params)
@@ -656,7 +658,9 @@ module API
use :pagination
optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) impersonation_tokens'
end
- get { present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken }
+ get feature_category :authentication_and_authorization do
+ present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken
+ end
desc 'Create a impersonation token. Available only for admins.' do
detail 'This feature was introduced in GitLab 9.0'
@@ -667,7 +671,7 @@ module API
optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the impersonation token'
optional :scopes, type: Array, desc: 'The array of scopes of the impersonation token'
end
- post do
+ post feature_category: :authentication_and_authorization do
impersonation_token = finder.build(declared_params(include_missing: false))
if impersonation_token.save
@@ -684,7 +688,7 @@ module API
params do
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end
- get ':impersonation_token_id' do
+ get ':impersonation_token_id', feature_category: :authentication_and_authorization do
present find_impersonation_token, with: Entities::ImpersonationToken
end
@@ -694,7 +698,7 @@ module API
params do
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end
- delete ':impersonation_token_id' do
+ delete ':impersonation_token_id', feature_category: :authentication_and_authorization do
token = find_impersonation_token
destroy_conditionally!(token) do
@@ -702,6 +706,40 @@ module API
end
end
end
+
+ resource :personal_access_tokens do
+ helpers do
+ def target_user
+ find_user_by_id(params)
+ end
+ end
+
+ before { authenticated_as_admin! }
+
+ desc 'Create a personal access token. Available only for admins.' do
+ detail 'This feature was introduced in GitLab 13.6'
+ success Entities::PersonalAccessTokenWithToken
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the personal access token'
+ requires :scopes, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: ::Gitlab::Auth.all_available_scopes.map(&:to_s),
+ desc: 'The array of scopes of the personal access token'
+ optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the personal access token'
+ end
+ post feature_category: :authentication_and_authorization do
+ not_found! unless Feature.enabled?(:pat_creation_api_for_admin)
+
+ response = ::PersonalAccessTokens::CreateService.new(
+ current_user: current_user, target_user: target_user, params: declared_params(include_missing: false)
+ ).execute
+
+ if response.success?
+ present response.payload[:personal_access_token], with: Entities::PersonalAccessTokenWithToken
+ else
+ render_api_error!(response.message, response.http_status || :unprocessable_entity)
+ end
+ end
+ end
end
end
@@ -716,7 +754,7 @@ module API
desc 'Get the currently authenticated user' do
success Entities::UserPublic
end
- get do
+ get feature_category: :users do
entity =
if current_user.admin?
Entities::UserWithAdmin
@@ -734,7 +772,7 @@ module API
params do
use :pagination
end
- get "keys" do
+ get "keys", feature_category: :authentication_and_authorization do
keys = current_user.keys.preload_users
present paginate(keys), with: Entities::SSHKey
@@ -747,7 +785,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end
# rubocop: disable CodeReuse/ActiveRecord
- get "keys/:key_id" do
+ get "keys/:key_id", feature_category: :authentication_and_authorization do
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
@@ -763,7 +801,7 @@ module API
requires :title, type: String, desc: 'The title of the new SSH key'
optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'
end
- post "keys" do
+ post "keys", feature_category: :authentication_and_authorization do
key = ::Keys::CreateService.new(current_user, declared_params(include_missing: false)).execute
if key.persisted?
@@ -780,7 +818,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete "keys/:key_id" do
+ delete "keys/:key_id", feature_category: :authentication_and_authorization do
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
@@ -798,7 +836,7 @@ module API
params do
use :pagination
end
- get 'gpg_keys' do
+ get 'gpg_keys', feature_category: :authentication_and_authorization do
present paginate(current_user.gpg_keys), with: Entities::GpgKey
end
@@ -810,7 +848,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
# rubocop: disable CodeReuse/ActiveRecord
- get 'gpg_keys/:key_id' do
+ get 'gpg_keys/:key_id', feature_category: :authentication_and_authorization do
key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
@@ -825,7 +863,7 @@ module API
params do
requires :key, type: String, desc: 'The new GPG key'
end
- post 'gpg_keys' do
+ post 'gpg_keys', feature_category: :authentication_and_authorization do
key = ::GpgKeys::CreateService.new(current_user, declared_params(include_missing: false)).execute
if key.persisted?
@@ -842,7 +880,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
# rubocop: disable CodeReuse/ActiveRecord
- post 'gpg_keys/:key_id/revoke' do
+ post 'gpg_keys/:key_id/revoke', feature_category: :authentication_and_authorization do
key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
@@ -858,7 +896,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete 'gpg_keys/:key_id' do
+ delete 'gpg_keys/:key_id', feature_category: :authentication_and_authorization do
key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
@@ -875,7 +913,7 @@ module API
params do
use :pagination
end
- get "emails" do
+ get "emails", feature_category: :users do
present paginate(current_user.emails), with: Entities::Email
end
@@ -886,7 +924,7 @@ module API
requires :email_id, type: Integer, desc: 'The ID of the email'
end
# rubocop: disable CodeReuse/ActiveRecord
- get "emails/:email_id" do
+ get "emails/:email_id", feature_category: :users do
email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email
@@ -900,7 +938,7 @@ module API
params do
requires :email, type: String, desc: 'The new email'
end
- post "emails" do
+ post "emails", feature_category: :users do
email = Emails::CreateService.new(current_user, declared_params.merge(user: current_user)).execute
if email.errors.blank?
@@ -915,7 +953,7 @@ module API
requires :email_id, type: Integer, desc: 'The ID of the email'
end
# rubocop: disable CodeReuse/ActiveRecord
- delete "emails/:email_id" do
+ delete "emails/:email_id", feature_category: :users do
email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email
@@ -931,7 +969,7 @@ module API
use :pagination
end
# rubocop: disable CodeReuse/ActiveRecord
- get "activities" do
+ get "activities", feature_category: :users do
authenticated_as_admin!
activities = User
@@ -948,8 +986,9 @@ module API
params do
optional :emoji, type: String, desc: "The emoji to set on the status"
optional :message, type: String, desc: "The status message to set"
+ optional :availability, type: String, desc: "The availability of user to set"
end
- put "status" do
+ put "status", feature_category: :users do
forbidden! unless can?(current_user, :update_user_status, current_user)
if ::Users::SetStatusService.new(current_user, declared_params).execute
@@ -962,7 +1001,7 @@ module API
desc 'get the status of the current user' do
success Entities::UserStatus
end
- get 'status' do
+ get 'status', feature_category: :users do
present current_user.status || {}, with: Entities::UserStatus
end
end
diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb
index aed88e6091c..327335aec2d 100644
--- a/lib/api/v3/github.rb
+++ b/lib/api/v3/github.rb
@@ -22,6 +22,8 @@ module API
include PaginationParams
+ feature_category :integrations
+
before do
authorize_jira_user_agent!(request)
authenticate!
diff --git a/lib/api/validations/validators/email_or_email_list.rb b/lib/api/validations/validators/email_or_email_list.rb
new file mode 100644
index 00000000000..b7f2a0cd443
--- /dev/null
+++ b/lib/api/validations/validators/email_or_email_list.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module API
+ module Validations
+ module Validators
+ class EmailOrEmailList < Grape::Validations::Base
+ def validate_param!(attr_name, params)
+ value = params[attr_name]
+
+ return unless value
+
+ return if value.split(',').map { |v| ValidateEmail.valid?(v) }.all?
+
+ raise Grape::Exceptions::Validation,
+ params: [@scope.full_name(attr_name)],
+ message: "contains an invalid email address"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index f5de3d844e6..94fa98b7a14 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_project }
+ feature_category :continuous_integration
+
helpers do
def filter_variable_parameters(params)
# This method exists so that EE can more easily filter out certain
diff --git a/lib/api/version.rb b/lib/api/version.rb
index 841b55f8d6c..f8072658cc6 100644
--- a/lib/api/version.rb
+++ b/lib/api/version.rb
@@ -9,6 +9,8 @@ module API
before { authenticate! }
+ feature_category :not_owned
+
METADATA_QUERY = <<~EOF
{
metadata {
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 21f457046f1..3fa42be47a9 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -4,6 +4,8 @@ module API
class Wikis < ::API::Base
helpers ::API::Helpers::WikisHelpers
+ feature_category :wiki
+
helpers do
attr_reader :container