summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/admin/ci/variables.rb137
-rw-r--r--lib/api/api.rb16
-rw-r--r--lib/api/api_guard.rb9
-rw-r--r--lib/api/appearance.rb3
-rw-r--r--lib/api/branches.rb2
-rw-r--r--lib/api/deploy_tokens.rb6
-rw-r--r--lib/api/entities/appearance.rb1
-rw-r--r--lib/api/entities/branch.rb6
-rw-r--r--lib/api/entities/design_management/design.rb16
-rw-r--r--lib/api/entities/freeze_period.rb11
-rw-r--r--lib/api/entities/job_request/artifacts.rb1
-rw-r--r--lib/api/entities/merge_request_basic.rb6
-rw-r--r--lib/api/entities/metrics/user_starred_dashboard.rb11
-rw-r--r--lib/api/entities/personal_snippet.rb3
-rw-r--r--lib/api/entities/project_repository_storage_move.rb14
-rw-r--r--lib/api/entities/release.rb2
-rw-r--r--lib/api/entities/remote_mirror.rb4
-rw-r--r--lib/api/entities/runner_details.rb9
-rw-r--r--lib/api/entities/snippet.rb10
-rw-r--r--lib/api/entities/todo.rb13
-rw-r--r--lib/api/entities/user_basic.rb2
-rw-r--r--lib/api/entities/user_path.rb2
-rw-r--r--lib/api/features.rb22
-rw-r--r--lib/api/freeze_periods.rb107
-rw-r--r--lib/api/groups.rb61
-rw-r--r--lib/api/helpers.rb18
-rw-r--r--lib/api/helpers/internal_helpers.rb2
-rw-r--r--lib/api/helpers/merge_requests_helpers.rb1
-rw-r--r--lib/api/helpers/pagination_strategies.rb34
-rw-r--r--lib/api/helpers/projects_helpers.rb1
-rw-r--r--lib/api/helpers/search_helpers.rb2
-rw-r--r--lib/api/helpers/services_helpers.rb9
-rw-r--r--lib/api/helpers/snippets_helpers.rb17
-rw-r--r--lib/api/internal/base.rb19
-rw-r--r--lib/api/issues.rb2
-rw-r--r--lib/api/job_artifacts.rb4
-rw-r--r--lib/api/members.rb2
-rw-r--r--lib/api/merge_requests.rb7
-rw-r--r--lib/api/metrics/dashboard/annotations.rb45
-rw-r--r--lib/api/metrics/user_starred_dashboards.rb46
-rw-r--r--lib/api/pipelines.rb15
-rw-r--r--lib/api/project_repository_storage_moves.rb34
-rw-r--r--lib/api/project_snippets.rb34
-rw-r--r--lib/api/project_templates.rb12
-rw-r--r--lib/api/projects.rb4
-rw-r--r--lib/api/remote_mirrors.rb2
-rw-r--r--lib/api/search.rb3
-rw-r--r--lib/api/settings.rb16
-rw-r--r--lib/api/snippets.rb31
-rw-r--r--lib/api/wikis.rb14
50 files changed, 705 insertions, 143 deletions
diff --git a/lib/api/admin/ci/variables.rb b/lib/api/admin/ci/variables.rb
new file mode 100644
index 00000000000..df731148bac
--- /dev/null
+++ b/lib/api/admin/ci/variables.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+module API
+ module Admin
+ module Ci
+ class Variables < Grape::API
+ include PaginationParams
+
+ before { authenticated_as_admin! }
+
+ namespace 'admin' do
+ namespace 'ci' do
+ namespace 'variables' do
+ desc 'Get instance-level variables' do
+ success Entities::Variable
+ end
+ params do
+ use :pagination
+ end
+ get '/' do
+ variables = ::Ci::InstanceVariable.all
+
+ present paginate(variables), with: Entities::Variable
+ end
+
+ desc 'Get a specific variable from a group' do
+ success Entities::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ end
+ get ':key' do
+ key = params[:key]
+ variable = ::Ci::InstanceVariable.find_by_key(key)
+
+ break not_found!('InstanceVariable') unless variable
+
+ present variable, with: Entities::Variable
+ end
+
+ desc 'Create a new instance-level variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :key,
+ type: String,
+ desc: 'The key of the variable'
+
+ requires :value,
+ type: String,
+ desc: 'The value of the variable'
+
+ optional :protected,
+ type: String,
+ desc: 'Whether the variable is protected'
+
+ optional :masked,
+ type: String,
+ desc: 'Whether the variable is masked'
+
+ optional :variable_type,
+ type: String,
+ values: ::Ci::InstanceVariable.variable_types.keys,
+ desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+ end
+ post '/' do
+ variable_params = declared_params(include_missing: false)
+
+ variable = ::Ci::InstanceVariable.new(variable_params)
+
+ if variable.save
+ present variable, with: Entities::Variable
+ else
+ render_validation_error!(variable)
+ end
+ end
+
+ desc 'Update an existing instance-variable' do
+ success Entities::Variable
+ end
+ params do
+ optional :key,
+ type: String,
+ desc: 'The key of the variable'
+
+ optional :value,
+ type: String,
+ desc: 'The value of the variable'
+
+ optional :protected,
+ type: String,
+ desc: 'Whether the variable is protected'
+
+ optional :masked,
+ type: String,
+ desc: 'Whether the variable is masked'
+
+ optional :variable_type,
+ type: String,
+ values: ::Ci::InstanceVariable.variable_types.keys,
+ desc: 'The type of variable, must be one of env_var or file'
+ end
+ put ':key' do
+ variable = ::Ci::InstanceVariable.find_by_key(params[:key])
+
+ break not_found!('InstanceVariable') unless variable
+
+ variable_params = declared_params(include_missing: false).except(:key)
+
+ if variable.update(variable_params)
+ present variable, with: Entities::Variable
+ else
+ render_validation_error!(variable)
+ end
+ end
+
+ desc 'Delete an existing instance-level variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ end
+ delete ':key' do
+ variable = ::Ci::InstanceVariable.find_by_key(params[:key])
+ not_found!('InstanceVariable') unless variable
+
+ variable.destroy
+
+ no_content!
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index de9a3120d90..b8135539cda 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -24,7 +24,8 @@ module API
Gitlab::GrapeLogging::Loggers::ExceptionLogger.new,
Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new,
Gitlab::GrapeLogging::Loggers::PerfLogger.new,
- Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new
+ Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new,
+ Gitlab::GrapeLogging::Loggers::ContextLogger.new
]
allow_access_with_scope :api
@@ -97,6 +98,15 @@ module API
handle_api_exception(exception)
end
+ # This is a specific exception raised by `rack-timeout` gem when Puma
+ # requests surpass its timeout. Given it inherits from Exception, we
+ # should rescue it separately. For more info, see:
+ # - https://github.com/sharpstone/rack-timeout/blob/master/doc/exceptions.md
+ # - https://github.com/ruby-grape/grape#exception-handling
+ rescue_from Rack::Timeout::RequestTimeoutException do |exception|
+ handle_api_exception(exception)
+ end
+
format :json
content_type :txt, "text/plain"
@@ -111,6 +121,7 @@ module API
# Keep in alphabetical order
mount ::API::AccessRequests
+ mount ::API::Admin::Ci::Variables
mount ::API::Admin::Sidekiq
mount ::API::Appearance
mount ::API::Applications
@@ -131,6 +142,7 @@ module API
mount ::API::Events
mount ::API::Features
mount ::API::Files
+ mount ::API::FreezePeriods
mount ::API::GroupBoards
mount ::API::GroupClusters
mount ::API::GroupExport
@@ -153,6 +165,7 @@ module API
mount ::API::MergeRequestDiffs
mount ::API::MergeRequests
mount ::API::Metrics::Dashboard::Annotations
+ mount ::API::Metrics::UserStarredDashboards
mount ::API::Namespaces
mount ::API::Notes
mount ::API::Discussions
@@ -169,6 +182,7 @@ module API
mount ::API::ProjectImport
mount ::API::ProjectHooks
mount ::API::ProjectMilestones
+ mount ::API::ProjectRepositoryStorageMoves
mount ::API::Projects
mount ::API::ProjectSnapshots
mount ::API::ProjectSnippets
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 9dd2de5c7ba..c6557fce541 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -65,7 +65,8 @@ module API
end
def find_user_from_sources
- find_user_from_access_token ||
+ deploy_token_from_request ||
+ find_user_from_access_token ||
find_user_from_job_token ||
find_user_from_warden
end
@@ -90,12 +91,16 @@ module API
end
def api_access_allowed?(user)
- Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
+ user_allowed_or_deploy_token?(user) && user.can?(:access_api)
end
def api_access_denied_message(user)
Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message
end
+
+ def user_allowed_or_deploy_token?(user)
+ Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken)
+ end
end
class_methods do
diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb
index a775102e87d..71a35bb4493 100644
--- a/lib/api/appearance.rb
+++ b/lib/api/appearance.rb
@@ -27,7 +27,8 @@ module API
optional :logo, type: File, desc: 'Instance image used on the sign in / sign up page' # rubocop:disable Scalability/FileUploads
optional :header_logo, type: File, desc: 'Instance image used for the main navigation bar' # rubocop:disable Scalability/FileUploads
optional :favicon, type: File, desc: 'Instance favicon in .ico/.png format' # rubocop:disable Scalability/FileUploads
- optional :new_project_guidelines, type: String, desc: 'Markmarkdown text shown on the new project page'
+ optional :new_project_guidelines, type: String, desc: 'Markdown text shown on the new project page'
+ optional :profile_image_guidelines, type: String, desc: 'Markdown text shown on the profile page below Public Avatar'
optional :header_message, type: String, desc: 'Message within the system header bar'
optional :footer_message, type: String, desc: 'Message within the system footer bar'
optional :message_background_color, type: String, desc: 'Background color for the system header / footer bar'
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 999bf1627c1..081e8ffe4f0 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -8,6 +8,8 @@ module API
BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
+ after_validation { content_type "application/json" }
+
before do
require_repository_enabled!
authorize! :download_code, user_project
diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb
index f3a08ae970a..0fbbd96cf02 100644
--- a/lib/api/deploy_tokens.rb
+++ b/lib/api/deploy_tokens.rb
@@ -11,6 +11,8 @@ module API
result_hash = Hashie::Mash.new
result_hash[:read_registry] = scopes.include?('read_registry')
result_hash[:write_registry] = scopes.include?('write_registry')
+ result_hash[:read_package_registry] = scopes.include?('read_package_registry')
+ result_hash[:write_package_registry] = scopes.include?('write_package_registry')
result_hash[:read_repository] = scopes.include?('read_repository')
result_hash
end
@@ -55,7 +57,7 @@ module API
params do
requires :name, type: String, desc: "New deploy token's name"
requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s),
- desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", or "write_registry".'
+ desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".'
optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.'
optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`'
end
@@ -118,7 +120,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the deploy token'
requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s),
- desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", or "write_registry".'
+ desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".'
optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.'
optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`'
end
diff --git a/lib/api/entities/appearance.rb b/lib/api/entities/appearance.rb
index c3cffc8d05c..a09faf55f48 100644
--- a/lib/api/entities/appearance.rb
+++ b/lib/api/entities/appearance.rb
@@ -19,6 +19,7 @@ module API
end
expose :new_project_guidelines
+ expose :profile_image_guidelines
expose :header_message
expose :footer_message
expose :message_background_color
diff --git a/lib/api/entities/branch.rb b/lib/api/entities/branch.rb
index 1d5017ac702..f9d06082ad6 100644
--- a/lib/api/entities/branch.rb
+++ b/lib/api/entities/branch.rb
@@ -3,6 +3,8 @@
module API
module Entities
class Branch < Grape::Entity
+ include Gitlab::Routing
+
expose :name
expose :commit, using: Entities::Commit do |repo_branch, options|
@@ -36,6 +38,10 @@ module API
expose :default do |repo_branch, options|
options[:project].default_branch == repo_branch.name
end
+
+ expose :web_url do |repo_branch|
+ project_tree_url(options[:project], repo_branch.name)
+ end
end
end
end
diff --git a/lib/api/entities/design_management/design.rb b/lib/api/entities/design_management/design.rb
new file mode 100644
index 00000000000..183fe06d8f1
--- /dev/null
+++ b/lib/api/entities/design_management/design.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module DesignManagement
+ class Design < Grape::Entity
+ expose :id
+ expose :project_id
+ expose :filename
+ expose :image_url do |design|
+ ::Gitlab::UrlBuilder.build(design)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/freeze_period.rb b/lib/api/entities/freeze_period.rb
new file mode 100644
index 00000000000..9b5f08925db
--- /dev/null
+++ b/lib/api/entities/freeze_period.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class FreezePeriod < Grape::Entity
+ expose :id
+ expose :freeze_start, :freeze_end, :cron_timezone
+ expose :created_at, :updated_at
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/artifacts.rb b/lib/api/entities/job_request/artifacts.rb
index c6871fdd875..0d27f5a9189 100644
--- a/lib/api/entities/job_request/artifacts.rb
+++ b/lib/api/entities/job_request/artifacts.rb
@@ -7,6 +7,7 @@ module API
expose :name
expose :untracked
expose :paths
+ expose :exclude, expose_nil: false
expose :when
expose :expire_in
expose :artifact_type
diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb
index 4610220e4f6..1a89a83a619 100644
--- a/lib/api/entities/merge_request_basic.rb
+++ b/lib/api/entities/merge_request_basic.rb
@@ -50,8 +50,10 @@ module API
# use `MergeRequest#mergeable?` instead (boolean).
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/42344 for more
# information.
- expose :merge_status do |merge_request|
- merge_request.check_mergeability(async: true)
+ #
+ # For list endpoints, we skip the recheck by default, since it's expensive
+ expose :merge_status do |merge_request, options|
+ merge_request.check_mergeability(async: true) unless options[:skip_merge_status_recheck]
merge_request.public_merge_status
end
expose :diff_head_sha, as: :sha
diff --git a/lib/api/entities/metrics/user_starred_dashboard.rb b/lib/api/entities/metrics/user_starred_dashboard.rb
new file mode 100644
index 00000000000..d774160e3ea
--- /dev/null
+++ b/lib/api/entities/metrics/user_starred_dashboard.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Metrics
+ class UserStarredDashboard < Grape::Entity
+ expose :id, :dashboard_path, :user_id, :project_id
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/personal_snippet.rb b/lib/api/entities/personal_snippet.rb
index eb0266e61e6..03ab6c0809c 100644
--- a/lib/api/entities/personal_snippet.rb
+++ b/lib/api/entities/personal_snippet.rb
@@ -3,9 +3,6 @@
module API
module Entities
class PersonalSnippet < Snippet
- expose :raw_url do |snippet|
- Gitlab::UrlBuilder.build(snippet, raw: true)
- end
end
end
end
diff --git a/lib/api/entities/project_repository_storage_move.rb b/lib/api/entities/project_repository_storage_move.rb
new file mode 100644
index 00000000000..25643651a14
--- /dev/null
+++ b/lib/api/entities/project_repository_storage_move.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class ProjectRepositoryStorageMove < Grape::Entity
+ expose :id
+ expose :created_at
+ expose :human_state_name, as: :state
+ expose :source_storage_name
+ expose :destination_storage_name
+ expose :project, using: Entities::ProjectIdentity
+ end
+ end
+end
diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb
index edcd9bc6505..99fa496d368 100644
--- a/lib/api/entities/release.rb
+++ b/lib/api/entities/release.rb
@@ -21,7 +21,6 @@ module API
expose :milestones, using: Entities::MilestoneWithStats, if: -> (release, _) { release.milestones.present? && can_read_milestone? }
expose :commit_path, expose_nil: false
expose :tag_path, expose_nil: false
- expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? }
expose :assets do
expose :assets_count, as: :count do |release, _|
@@ -32,7 +31,6 @@ module API
expose :links, using: Entities::Releases::Link do |release, options|
release.links.sorted
end
- expose :evidence_file_path, expose_nil: false, if: ->(_, _) { can_download_code? }
end
expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_download_code? }
expose :_links do
diff --git a/lib/api/entities/remote_mirror.rb b/lib/api/entities/remote_mirror.rb
index 18d51726bab..87daef9a05c 100644
--- a/lib/api/entities/remote_mirror.rb
+++ b/lib/api/entities/remote_mirror.rb
@@ -12,9 +12,7 @@ module API
expose :last_successful_update_at
expose :last_error
expose :only_protected_branches
- expose :keep_divergent_refs, if: -> (mirror, _options) do
- ::Feature.enabled?(:keep_divergent_refs, mirror.project)
- end
+ expose :keep_divergent_refs
end
end
end
diff --git a/lib/api/entities/runner_details.rb b/lib/api/entities/runner_details.rb
index 2bb143253fe..1dd8543d595 100644
--- a/lib/api/entities/runner_details.rb
+++ b/lib/api/entities/runner_details.rb
@@ -11,9 +11,12 @@ module API
expose :version, :revision, :platform, :architecture
expose :contacted_at
- # @deprecated in 12.10 https://gitlab.com/gitlab-org/gitlab/-/issues/214320
- # will be removed by 13.0 https://gitlab.com/gitlab-org/gitlab/-/issues/214322
- expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.instance_type? }
+ # Will be removed: https://gitlab.com/gitlab-org/gitlab/-/issues/217105
+ expose(:token, if: ->(runner, options) do
+ return false if ::Feature.enabled?(:hide_token_from_runners_api, default_enabled: true)
+
+ options[:current_user].admin? || !runner.instance_type?
+ end)
# rubocop: disable CodeReuse/ActiveRecord
expose :projects, with: Entities::BasicProjectDetails do |runner, options|
diff --git a/lib/api/entities/snippet.rb b/lib/api/entities/snippet.rb
index 8a13b4746a9..19c89603cbc 100644
--- a/lib/api/entities/snippet.rb
+++ b/lib/api/entities/snippet.rb
@@ -3,14 +3,20 @@
module API
module Entities
class Snippet < Grape::Entity
- expose :id, :title, :file_name, :description, :visibility
+ expose :id, :title, :description, :visibility
expose :author, using: Entities::UserBasic
expose :updated_at, :created_at
expose :project_id
expose :web_url do |snippet|
Gitlab::UrlBuilder.build(snippet)
end
- expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.versioned_enabled_for?(options[:current_user]) }
+ expose :raw_url do |snippet|
+ Gitlab::UrlBuilder.build(snippet, raw: true)
+ end
+ expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.repository_exists? }
+ expose :file_name do |snippet|
+ snippet.file_name_on_repo || snippet.file_name
+ end
end
end
end
diff --git a/lib/api/entities/todo.rb b/lib/api/entities/todo.rb
index abfdde89bf1..0acbb4cb704 100644
--- a/lib/api/entities/todo.rb
+++ b/lib/api/entities/todo.rb
@@ -22,6 +22,7 @@ module API
expose :body
expose :state
expose :created_at
+ expose :updated_at
def todo_target_class(target_type)
# false as second argument prevents looking up in module hierarchy
@@ -30,6 +31,8 @@ module API
end
def todo_target_url(todo)
+ return design_todo_target_url(todo) if todo.for_design?
+
target_type = todo.target_type.underscore
target_url = "#{todo.resource_parent.class.to_s.underscore}_#{target_type}_url"
@@ -41,6 +44,16 @@ module API
def todo_target_anchor(todo)
"note_#{todo.note_id}" if todo.note_id?
end
+
+ def design_todo_target_url(todo)
+ design = todo.target
+ path_options = {
+ anchor: todo_target_anchor(todo),
+ vueroute: design.filename
+ }
+
+ ::Gitlab::Routing.url_helpers.designs_project_issue_url(design.project, design.issue, path_options)
+ end
end
end
end
diff --git a/lib/api/entities/user_basic.rb b/lib/api/entities/user_basic.rb
index e063aa42855..80f3ee7b502 100644
--- a/lib/api/entities/user_basic.rb
+++ b/lib/api/entities/user_basic.rb
@@ -18,3 +18,5 @@ module API
end
end
end
+
+API::Entities::UserBasic.prepend_if_ee('EE::API::Entities::UserBasic')
diff --git a/lib/api/entities/user_path.rb b/lib/api/entities/user_path.rb
index 7d922b39654..3f007659813 100644
--- a/lib/api/entities/user_path.rb
+++ b/lib/api/entities/user_path.rb
@@ -12,3 +12,5 @@ module API
end
end
end
+
+API::Entities::UserPath.prepend_if_ee('EE::API::Entities::UserPath')
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 69b751e9bdb..f507919b055 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -16,6 +16,15 @@ module API
end
end
+ def gate_key(params)
+ case params[:key]
+ when 'percentage_of_actors'
+ :percentage_of_actors
+ else
+ :percentage_of_time
+ end
+ end
+
def gate_targets(params)
Feature::Target.new(params).targets
end
@@ -40,15 +49,22 @@ module API
end
params do
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
+ optional :key, type: String, desc: '`percentage_of_actors` or the default `percentage_of_time`'
optional :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username'
optional :group, type: String, desc: "A GitLab group's path, such as 'gitlab-org'"
optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce'
+
+ mutually_exclusive :key, :feature_group
+ mutually_exclusive :key, :user
+ mutually_exclusive :key, :group
+ mutually_exclusive :key, :project
end
post ':name' do
feature = Feature.get(params[:name])
targets = gate_targets(params)
value = gate_value(params)
+ key = gate_key(params)
case value
when true
@@ -64,7 +80,11 @@ module API
feature.disable
end
else
- feature.enable_percentage_of_time(value)
+ if key == :percentage_of_actors
+ feature.enable_percentage_of_actors(value)
+ else
+ feature.enable_percentage_of_time(value)
+ end
end
present feature, with: Entities::Feature, current_user: current_user
diff --git a/lib/api/freeze_periods.rb b/lib/api/freeze_periods.rb
new file mode 100644
index 00000000000..9c7e5a5832d
--- /dev/null
+++ b/lib/api/freeze_periods.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+module API
+ class FreezePeriods < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get project freeze periods' do
+ detail 'This feature was introduced in GitLab 13.0.'
+ success Entities::FreezePeriod
+ end
+ params do
+ use :pagination
+ end
+
+ get ":id/freeze_periods" do
+ authorize! :read_freeze_period, user_project
+
+ freeze_periods = ::FreezePeriodsFinder.new(user_project, current_user).execute
+
+ present paginate(freeze_periods), with: Entities::FreezePeriod, current_user: current_user
+ end
+
+ desc 'Get a single freeze period' do
+ detail 'This feature was introduced in GitLab 13.0.'
+ success Entities::FreezePeriod
+ end
+ params do
+ requires :freeze_period_id, type: Integer, desc: 'The ID of a project freeze period'
+ end
+ get ":id/freeze_periods/:freeze_period_id" do
+ authorize! :read_freeze_period, user_project
+
+ present freeze_period, with: Entities::FreezePeriod, current_user: current_user
+ end
+
+ desc 'Create a new freeze period' do
+ detail 'This feature was introduced in GitLab 13.0.'
+ success Entities::FreezePeriod
+ end
+ params do
+ requires :freeze_start, type: String, desc: 'Freeze Period start'
+ requires :freeze_end, type: String, desc: 'Freeze Period end'
+ optional :cron_timezone, type: String, desc: 'Timezone'
+ end
+ post ':id/freeze_periods' do
+ authorize! :create_freeze_period, user_project
+
+ freeze_period_params = declared(params, include_parent_namespaces: false)
+
+ freeze_period = user_project.freeze_periods.create(freeze_period_params)
+
+ if freeze_period.persisted?
+ present freeze_period, with: Entities::FreezePeriod
+ else
+ render_validation_error!(freeze_period)
+ end
+ end
+
+ desc 'Update a freeze period' do
+ detail 'This feature was introduced in GitLab 13.0.'
+ success Entities::FreezePeriod
+ end
+ params do
+ optional :freeze_start, type: String, desc: 'Freeze Period start'
+ optional :freeze_end, type: String, desc: 'Freeze Period end'
+ optional :cron_timezone, type: String, desc: 'Freeze Period Timezone'
+ end
+ put ':id/freeze_periods/:freeze_period_id' do
+ authorize! :update_freeze_period, user_project
+
+ freeze_period_params = declared(params, include_parent_namespaces: false, include_missing: false)
+
+ if freeze_period.update(freeze_period_params)
+ present freeze_period, with: Entities::FreezePeriod
+ else
+ render_validation_error!(freeze_period)
+ end
+ end
+
+ desc 'Delete a freeze period' do
+ detail 'This feature was introduced in GitLab 13.0.'
+ success Entities::FreezePeriod
+ end
+ params do
+ requires :freeze_period_id, type: Integer, desc: 'Freeze Period ID'
+ end
+ delete ':id/freeze_periods/:freeze_period_id' do
+ authorize! :destroy_freeze_period, user_project
+
+ destroy_conditionally!(freeze_period)
+ end
+ end
+
+ helpers do
+ def freeze_period
+ @freeze_period ||= user_project.freeze_periods.find(params[:freeze_period_id])
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index d375c35e8c0..353c8b4b242 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -60,18 +60,14 @@ module API
.execute
end
- def find_group_projects(params)
+ def find_group_projects(params, finder_options)
group = find_group!(params[:id])
- options = {
- only_owned: !params[:with_shared],
- include_subgroups: params[:include_subgroups]
- }
projects = GroupProjectsFinder.new(
group: group,
current_user: current_user,
params: project_finder_params,
- options: options
+ options: finder_options
).execute
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
@@ -80,11 +76,22 @@ module API
paginate(projects)
end
+ def present_projects(params, projects)
+ options = {
+ with: params[:simple] ? Entities::BasicProjectDetails : Entities::Project,
+ current_user: current_user
+ }
+
+ projects, options = with_custom_attributes(projects, options)
+
+ present options[:with].prepare_relation(projects), options
+ end
+
def present_groups(params, groups)
options = {
with: Entities::Group,
current_user: current_user,
- statistics: params[:statistics] && current_user.admin?
+ statistics: params[:statistics] && current_user&.admin?
}
groups = groups.with_statistics if options[:statistics]
@@ -226,16 +233,42 @@ module API
use :optional_projects_params
end
get ":id/projects" do
- projects = find_group_projects(params)
-
- options = {
- with: params[:simple] ? Entities::BasicProjectDetails : Entities::Project,
- current_user: current_user
+ finder_options = {
+ only_owned: !params[:with_shared],
+ include_subgroups: params[:include_subgroups]
}
- projects, options = with_custom_attributes(projects, options)
+ projects = find_group_projects(params, finder_options)
- present options[:with].prepare_relation(projects), options
+ present_projects(params, projects)
+ end
+
+ desc 'Get a list of shared projects in this group' do
+ success Entities::Project
+ end
+ params do
+ optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
+ desc: 'Limit by visibility'
+ optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
+ optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
+ default: 'created_at', desc: 'Return projects ordered by field'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return projects sorted in ascending and descending order'
+ optional :simple, type: Boolean, default: false,
+ desc: 'Return only the ID, URL, name, and path of each project'
+ optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
+ optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
+ optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
+ optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user on projects'
+
+ use :pagination
+ use :with_custom_attributes
+ end
+ get ":id/projects/shared" do
+ projects = find_group_projects(params, { only_shared: true })
+
+ present_projects(params, projects)
end
desc 'Get a list of subgroups in this group.' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 42b82aac1c4..c6f6dc255d4 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -11,6 +11,7 @@ module API
SUDO_PARAM = :sudo
API_USER_ENV = 'gitlab.api.user'
API_EXCEPTION_ENV = 'gitlab.api.exception'
+ API_RESPONSE_STATUS_CODE = 'gitlab.api.response_status_code'
def declared_params(options = {})
options = { include_parent_namespaces: false }.merge(options)
@@ -178,6 +179,14 @@ module API
end
end
+ def find_tag!(tag_name)
+ if Gitlab::GitRefValidator.validate(tag_name)
+ user_project.repository.find_tag(tag_name) || not_found!('Tag')
+ else
+ render_api_error!('The tag refname is invalid', 400)
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def find_project_issue(iid, project_id = nil)
project = project_id ? find_project!(project_id) : user_project
@@ -416,6 +425,11 @@ module API
end
def render_api_error!(message, status)
+ # grape-logging doesn't pass the status code, so this is a
+ # workaround for getting that information in the loggers:
+ # https://github.com/aserafin/grape_logging/issues/71
+ env[API_RESPONSE_STATUS_CODE] = Rack::Utils.status_code(status)
+
error!({ 'message' => message }, status, header)
end
@@ -595,8 +609,8 @@ module API
header(*Gitlab::Workhorse.send_git_archive(repository, **kwargs))
end
- def send_artifacts_entry(build, entry)
- header(*Gitlab::Workhorse.send_artifacts_entry(build, entry))
+ def send_artifacts_entry(file, entry)
+ header(*Gitlab::Workhorse.send_artifacts_entry(file, entry))
end
# The Grape Error Middleware only has access to `env` but not `params` nor
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 31272c537a3..b05e82a541d 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -51,7 +51,7 @@ module API
def parse_env
return {} if params[:env].blank?
- JSON.parse(params[:env])
+ Gitlab::Json.parse(params[:env])
rescue JSON::ParserError
{}
end
diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb
index 73711a7e0ba..9dab2a88f0b 100644
--- a/lib/api/helpers/merge_requests_helpers.rb
+++ b/lib/api/helpers/merge_requests_helpers.rb
@@ -27,6 +27,7 @@ module API
coerce_with: Validations::Types::LabelsList.coerce,
desc: 'Comma-separated list of label names'
optional :with_labels_details, type: Boolean, desc: 'Return titles of labels and other details', default: false
+ optional :with_merge_status_recheck, type: Boolean, desc: 'Request that stale merge statuses be rechecked asynchronously', default: false
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time'
diff --git a/lib/api/helpers/pagination_strategies.rb b/lib/api/helpers/pagination_strategies.rb
index 6bebb4bfeac..823891d6fe7 100644
--- a/lib/api/helpers/pagination_strategies.rb
+++ b/lib/api/helpers/pagination_strategies.rb
@@ -3,19 +3,24 @@
module API
module Helpers
module PaginationStrategies
- def paginate_with_strategies(relation)
- paginator = paginator(relation)
+ def paginate_with_strategies(relation, request_scope)
+ paginator = paginator(relation, request_scope)
yield(paginator.paginate(relation)).tap do |records, _|
paginator.finalize(records)
end
end
- def paginator(relation)
- return Gitlab::Pagination::OffsetPagination.new(self) unless keyset_pagination_enabled?
+ def paginator(relation, request_scope = nil)
+ return keyset_paginator(relation) if keyset_pagination_enabled?
- request_context = Gitlab::Pagination::Keyset::RequestContext.new(self)
+ offset_paginator(relation, request_scope)
+ end
+
+ private
+ def keyset_paginator(relation)
+ request_context = Gitlab::Pagination::Keyset::RequestContext.new(self)
unless Gitlab::Pagination::Keyset.available?(request_context, relation)
return error!('Keyset pagination is not yet available for this type of request', 405)
end
@@ -23,11 +28,28 @@ module API
Gitlab::Pagination::Keyset::Pager.new(request_context)
end
- private
+ def offset_paginator(relation, request_scope)
+ offset_limit = limit_for_scope(request_scope)
+ if Gitlab::Pagination::Keyset.available_for_type?(relation) && offset_limit_exceeded?(offset_limit)
+ return error!("Offset pagination has a maximum allowed offset of #{offset_limit} " \
+ "for requests that return objects of type #{relation.klass}. " \
+ "Remaining records can be retrieved using keyset pagination.", 405)
+ end
+
+ Gitlab::Pagination::OffsetPagination.new(self)
+ end
def keyset_pagination_enabled?
params[:pagination] == 'keyset'
end
+
+ def limit_for_scope(scope)
+ (scope || Plan.default).actual_limits.offset_pagination_limit
+ end
+
+ def offset_limit_exceeded?(offset_limit)
+ offset_limit.positive? && params[:page] * params[:per_page] > offset_limit
+ end
end
end
end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 14c83114f32..5afdb34da97 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -31,6 +31,7 @@ module API
optional :pages_access_level, type: String, values: %w(disabled private enabled public), desc: 'Pages access level. One of `disabled`, `private`, `enabled` or `public`'
optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
+ optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge'
diff --git a/lib/api/helpers/search_helpers.rb b/lib/api/helpers/search_helpers.rb
index de8cbe62106..936684ea1f8 100644
--- a/lib/api/helpers/search_helpers.rb
+++ b/lib/api/helpers/search_helpers.rb
@@ -5,7 +5,7 @@ module API
module SearchHelpers
def self.global_search_scopes
# This is a separate method so that EE can redefine it.
- %w(projects issues merge_requests milestones snippet_titles snippet_blobs users)
+ %w(projects issues merge_requests milestones snippet_titles users)
end
def self.group_search_scopes
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index 4c44aca2de4..02e60ff5db5 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -724,6 +724,15 @@ module API
desc: 'The Unify Circuit webhook. e.g. https://circuit.com/rest/v2/webhooks/incoming/…'
},
chat_notification_events
+ ].flatten,
+ 'webex-teams' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The Webex Teams webhook. e.g. https://api.ciscospark.com/v1/webhooks/incoming/…'
+ },
+ chat_notification_events
].flatten
}
end
diff --git a/lib/api/helpers/snippets_helpers.rb b/lib/api/helpers/snippets_helpers.rb
new file mode 100644
index 00000000000..20aeca6a9d3
--- /dev/null
+++ b/lib/api/helpers/snippets_helpers.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module SnippetsHelpers
+ def content_for(snippet)
+ if snippet.empty_repo?
+ snippet.content
+ else
+ blob = snippet.blobs.first
+ blob.load_all_data!
+ blob.data
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index 0d50a310b37..79c407b9581 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -30,10 +30,6 @@ module API
project.http_url_to_repo
end
- def ee_post_receive_response_hook(response)
- # Hook for EE to add messages
- end
-
def check_allowed(params)
# This is a separate method so that EE can alter its behaviour more
# easily.
@@ -73,13 +69,14 @@ module API
}
# Custom option for git-receive-pack command
+ if Feature.enabled?(:gitaly_upload_pack_filter, project, default_enabled: true)
+ payload[:git_config_options] << "uploadpack.allowFilter=true" << "uploadpack.allowAnySHA1InWant=true"
+ end
+
receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
+
if receive_max_input_size > 0
payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
-
- if Feature.enabled?(:gitaly_upload_pack_filter, project)
- payload[:git_config_options] << "uploadpack.allowFilter=true" << "uploadpack.allowAnySHA1InWant=true"
- end
end
response_with_status(**payload)
@@ -116,10 +113,6 @@ module API
# check_ip - optional, only in EE version, may limit access to
# group resources based on its IP restrictions
post "/allowed" do
- if repo_type.snippet? && params[:protocol] != 'web' && Feature.disabled?(:version_snippets, actor.user)
- break response_with_status(code: 401, success: false, message: 'Snippet git access is disabled.')
- end
-
# It was moved to a separate method so that EE can alter its behaviour more
# easily.
check_allowed(params)
@@ -216,8 +209,6 @@ module API
response = PostReceiveService.new(actor.user, repository, project, params).execute
- ee_post_receive_response_hook(response)
-
present response, with: Entities::InternalPostReceive::Response
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index f27afd0055f..be50c3e0381 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -95,6 +95,8 @@ module API
use :issues_params
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me',
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
+ optional :non_archived, type: Boolean, default: true,
+ desc: 'Return issues from non archived projects'
end
get do
authenticate! unless params[:scope] == 'all'
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index 920938ad453..6a82256cc96 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -54,7 +54,7 @@ module API
bad_request! unless path.valid?
- send_artifacts_entry(build, path)
+ send_artifacts_entry(build.artifacts_file, path)
end
desc 'Download the artifacts archive from a job' do
@@ -90,7 +90,7 @@ module API
bad_request! unless path.valid?
- send_artifacts_entry(build, path)
+ send_artifacts_entry(build.artifacts_file, path)
end
desc 'Keep the artifacts to prevent them from being deleted' do
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 2e49b4be45c..37d4ca29b68 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -160,3 +160,5 @@ module API
end
end
end
+
+API::Members.prepend_if_ee('EE::API::Members')
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index d45786cdd3d..ff4ad85115b 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -26,6 +26,8 @@ module API
assignee_ids
description
labels
+ add_labels
+ remove_labels
milestone_id
remove_source_branch
state_event
@@ -91,6 +93,9 @@ module API
options[:with] = Entities::MergeRequestSimple
else
options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest', current_user)
+ if Feature.enabled?(:mr_list_api_skip_merge_status_recheck, default_enabled: true)
+ options[:skip_merge_status_recheck] = !declared_params[:with_merge_status_recheck]
+ end
end
options
@@ -180,6 +185,8 @@ module API
optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
+ optional :add_labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
+ optional :remove_labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
optional :allow_collaboration, type: Boolean, desc: 'Allow commits from members who can merge to the target branch'
optional :allow_maintainer_to_push, type: Boolean, as: :allow_collaboration, desc: '[deprecated] See allow_collaboration'
diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb
index 691abac863a..c8ec4d29657 100644
--- a/lib/api/metrics/dashboard/annotations.rb
+++ b/lib/api/metrics/dashboard/annotations.rb
@@ -8,30 +8,37 @@ module API
success Entities::Metrics::Dashboard::Annotation
end
- params do
- requires :starting_at, type: DateTime,
- desc: 'Date time indicating starting moment to which the annotation relates.'
- optional :ending_at, type: DateTime,
- desc: 'Date time indicating ending moment to which the annotation relates.'
- requires :dashboard_path, type: String,
- desc: 'The path to a file defining the dashboard on which the annotation should be added'
- requires :description, type: String, desc: 'The description of the annotation'
- end
+ ANNOTATIONS_SOURCES = [
+ { class: ::Environment, resource: :environments, create_service_param_key: :environment },
+ { class: Clusters::Cluster, resource: :clusters, create_service_param_key: :cluster }
+ ].freeze
+
+ ANNOTATIONS_SOURCES.each do |annotations_source|
+ resource annotations_source[:resource] do
+ params do
+ requires :starting_at, type: DateTime,
+ desc: 'Date time indicating starting moment to which the annotation relates.'
+ optional :ending_at, type: DateTime,
+ desc: 'Date time indicating ending moment to which the annotation relates.'
+ requires :dashboard_path, type: String, coerce_with: -> (val) { CGI.unescape(val) },
+ desc: 'The path to a file defining the dashboard on which the annotation should be added'
+ requires :description, type: String, desc: 'The description of the annotation'
+ end
- resource :environments do
- post ':id/metrics_dashboard/annotations' do
- environment = ::Environment.find(params[:id])
+ post ':id/metrics_dashboard/annotations' do
+ annotations_source_object = annotations_source[:class].find(params[:id])
- not_found! unless Feature.enabled?(:metrics_dashboard_annotations, environment.project)
+ forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, annotations_source_object)
- forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, environment)
+ create_service_params = declared(params).merge(annotations_source[:create_service_param_key] => annotations_source_object)
- result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, declared(params).merge(environment: environment)).execute
+ result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, create_service_params).execute
- if result[:status] == :success
- present result[:annotation], with: Entities::Metrics::Dashboard::Annotation
- else
- error!(result, 400)
+ if result[:status] == :success
+ present result[:annotation], with: Entities::Metrics::Dashboard::Annotation
+ else
+ error!(result, 400)
+ end
end
end
end
diff --git a/lib/api/metrics/user_starred_dashboards.rb b/lib/api/metrics/user_starred_dashboards.rb
new file mode 100644
index 00000000000..85fc0f33ed8
--- /dev/null
+++ b/lib/api/metrics/user_starred_dashboards.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module API
+ module Metrics
+ class UserStarredDashboards < Grape::API
+ resource :projects do
+ desc 'Marks selected metrics dashboard as starred' do
+ success Entities::Metrics::UserStarredDashboard
+ end
+
+ params do
+ requires :dashboard_path, type: String, allow_blank: false, coerce_with: ->(val) { CGI.unescape(val) },
+ desc: 'Url encoded path to a file defining the dashboard to which the star should be added'
+ end
+
+ post ':id/metrics/user_starred_dashboards' do
+ result = ::Metrics::UsersStarredDashboards::CreateService.new(current_user, user_project, params[:dashboard_path]).execute
+
+ if result.success?
+ present result.payload, with: Entities::Metrics::UserStarredDashboard
+ else
+ error!({ errors: result.message }, 400)
+ end
+ end
+
+ desc 'Remove star from selected metrics dashboard'
+
+ params do
+ optional :dashboard_path, type: String, allow_blank: false, coerce_with: ->(val) { CGI.unescape(val) },
+ desc: 'Url encoded path to a file defining the dashboard from which the star should be removed'
+ end
+
+ delete ':id/metrics/user_starred_dashboards' do
+ result = ::Metrics::UsersStarredDashboards::DeleteService.new(current_user, user_project, params[:dashboard_path]).execute
+
+ if result.success?
+ status :ok
+ result.payload
+ else
+ status :bad_request
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 06f8920b37c..c09bca26a41 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -108,6 +108,21 @@ module API
present pipeline.variables, with: Entities::Variable
end
+ desc 'Gets the test report for a given pipeline' do
+ detail 'This feature was introduced in GitLab 13.0. Disabled by default behind feature flag `junit_pipeline_view`'
+ success TestReportEntity
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ get ':id/pipelines/:pipeline_id/test_report' do
+ not_found! unless Feature.enabled?(:junit_pipeline_view, user_project)
+
+ authorize! :read_build, pipeline
+
+ present pipeline.test_reports, with: TestReportEntity
+ end
+
desc 'Deletes a pipeline' do
detail 'This feature was introduced in GitLab 11.6'
http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']]
diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb
new file mode 100644
index 00000000000..1a63e984fbf
--- /dev/null
+++ b/lib/api/project_repository_storage_moves.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module API
+ class ProjectRepositoryStorageMoves < Grape::API
+ include PaginationParams
+
+ before { authenticated_as_admin! }
+
+ 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.'
+ success Entities::ProjectRepositoryStorageMove
+ end
+ params do
+ use :pagination
+ end
+ get do
+ storage_moves = ProjectRepositoryStorageMove.with_projects.order_created_at_desc
+
+ present paginate(storage_moves), with: Entities::ProjectRepositoryStorageMove, current_user: current_user
+ end
+
+ desc 'Get a project repository storage move' do
+ detail 'This feature was introduced in GitLab 13.0.'
+ success Entities::ProjectRepositoryStorageMove
+ end
+ get ':id' do
+ storage_move = ProjectRepositoryStorageMove.find(params[:id])
+
+ present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user
+ end
+ end
+ end
+end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index e8234a9285c..68f4a0dcb65 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -11,6 +11,7 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ helpers Helpers::SnippetsHelpers
helpers do
def check_snippets_enabled
forbidden! unless user_project.feature_available?(:snippets, current_user)
@@ -54,30 +55,27 @@ module API
success Entities::ProjectSnippet
end
params do
- requires :title, type: String, desc: 'The title of the snippet'
+ requires :title, type: String, allow_blank: false, desc: 'The title of the snippet'
requires :file_name, type: String, desc: 'The file name of the snippet'
- optional :code, type: String, allow_blank: false, desc: 'The content of the snippet (deprecated in favor of "content")'
- optional :content, type: String, allow_blank: false, desc: 'The content of the snippet'
+ requires :content, type: String, allow_blank: false, desc: 'The content of the snippet'
optional :description, type: String, desc: 'The description of a snippet'
requires :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
- mutually_exclusive :code, :content
end
post ":id/snippets" do
authorize! :create_snippet, user_project
snippet_params = declared_params(include_missing: false).merge(request: request, api: true)
- snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
service_response = ::Snippets::CreateService.new(user_project, current_user, snippet_params).execute
snippet = service_response.payload[:snippet]
- render_spam_error! if snippet.spam?
-
- if snippet.persisted?
+ if service_response.success?
present snippet, with: Entities::ProjectSnippet
else
- render_validation_error!(snippet)
+ render_spam_error! if snippet.spam?
+
+ render_api_error!({ error: service_response.message }, service_response.http_status)
end
end
@@ -86,16 +84,14 @@ module API
end
params do
requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
- optional :title, type: String, desc: 'The title of the snippet'
+ optional :title, type: String, allow_blank: false, desc: 'The title of the snippet'
optional :file_name, type: String, desc: 'The file name of the snippet'
- optional :code, type: String, allow_blank: false, desc: 'The content of the snippet (deprecated in favor of "content")'
optional :content, type: String, allow_blank: false, desc: 'The content of the snippet'
optional :description, type: String, desc: 'The description of a snippet'
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
- at_least_one_of :title, :file_name, :code, :content, :visibility_level
- mutually_exclusive :code, :content
+ at_least_one_of :title, :file_name, :content, :visibility_level
end
# rubocop: disable CodeReuse/ActiveRecord
put ":id/snippets/:snippet_id" do
@@ -107,17 +103,15 @@ module API
snippet_params = declared_params(include_missing: false)
.merge(request: request, api: true)
- snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
-
service_response = ::Snippets::UpdateService.new(user_project, current_user, snippet_params).execute(snippet)
snippet = service_response.payload[:snippet]
- render_spam_error! if snippet.spam?
-
- if snippet.valid?
+ if service_response.success?
present snippet, with: Entities::ProjectSnippet
else
- render_validation_error!(snippet)
+ render_spam_error! if snippet.spam?
+
+ render_api_error!({ error: service_response.message }, service_response.http_status)
end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -155,7 +149,7 @@ module API
env['api.format'] = :txt
content_type 'text/plain'
- present snippet.content
+ present content_for(snippet)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb
index 119902a189c..cfcc7f5212d 100644
--- a/lib/api/project_templates.rb
+++ b/lib/api/project_templates.rb
@@ -5,6 +5,10 @@ module API
include PaginationParams
TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses].freeze
+ # The regex is needed to ensure a period (e.g. agpl-3.0)
+ # isn't confused with a format type. We also need to allow encoded
+ # values (e.g. C%2B%2B for C++), so allow % and + as well.
+ TEMPLATE_NAMES_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(name: /[\w%.+-]+/)
before { authenticate_non_get! }
@@ -12,7 +16,7 @@ module API
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) of the template'
end
- resource :projects do
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get a list of templates available to this project' do
detail 'This endpoint was introduced in GitLab 11.4'
end
@@ -36,10 +40,8 @@ module API
optional :project, type: String, desc: 'The project name to use when expanding placeholders in the template. Only affects licenses'
optional :fullname, type: String, desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses'
end
- # The regex is needed to ensure a period (e.g. agpl-3.0)
- # isn't confused with a format type. We also need to allow encoded
- # values (e.g. C%2B%2B for C++), so allow % and + as well.
- get ':id/templates/:type/:name', requirements: { name: /[\w%.+-]+/ } do
+
+ get ':id/templates/:type/:name', requirements: TEMPLATE_NAMES_ENDPOINT_REQUIREMENTS do
template = TemplateFinder
.build(params[:type], user_project, name: params[:name])
.execute
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index ee0731a331f..732453cf1c4 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -95,7 +95,7 @@ module API
projects = reorder_projects(projects)
projects = apply_filters(projects)
- records, options = paginate_with_strategies(projects) do |projects|
+ records, options = paginate_with_strategies(projects, options[:request_scope]) do |projects|
projects, options = with_custom_attributes(projects, options)
options = options.reverse_merge(
@@ -313,7 +313,7 @@ module API
get ':id/forks' do
forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute
- present_projects forks
+ present_projects forks, request_scope: user_project
end
desc 'Check pages access of this project'
diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb
index 7e484eb8885..0808541d3c7 100644
--- a/lib/api/remote_mirrors.rb
+++ b/lib/api/remote_mirrors.rb
@@ -34,7 +34,6 @@ module API
end
post ':id/remote_mirrors' do
create_params = declared_params(include_missing: false)
- create_params.delete(:keep_divergent_refs) unless ::Feature.enabled?(:keep_divergent_refs, user_project)
new_mirror = user_project.remote_mirrors.create(create_params)
@@ -59,7 +58,6 @@ module API
mirror_params = declared_params(include_missing: false)
mirror_params[:id] = mirror_params.delete(:mirror_id)
- mirror_params.delete(:keep_divergent_refs) unless ::Feature.enabled?(:keep_divergent_refs, user_project)
update_params = { remote_mirrors_attributes: mirror_params }
diff --git a/lib/api/search.rb b/lib/api/search.rb
index ed52a4fc8f2..3d2d4527e30 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -17,7 +17,6 @@ module API
blobs: Entities::Blob,
wiki_blobs: Entities::Blob,
snippet_titles: Entities::Snippet,
- snippet_blobs: Entities::Snippet,
users: Entities::UserBasic
}.freeze
@@ -36,7 +35,7 @@ module API
end
def snippets?
- %w(snippet_blobs snippet_titles).include?(params[:scope]).to_s
+ %w(snippet_titles).include?(params[:scope]).to_s
end
def entity
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 5362b3060c1..e3a8f0671ef 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -84,16 +84,7 @@ module API
optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts"
optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
- optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics'
- given metrics_enabled: ->(val) { val } do
- requires :metrics_host, type: String, desc: 'The InfluxDB host'
- requires :metrics_method_call_threshold, type: Integer, desc: 'A method call is only tracked when it takes longer to complete than the given amount of milliseconds.'
- requires :metrics_packet_size, type: Integer, desc: 'The amount of points to store in a single UDP packet'
- requires :metrics_pool_size, type: Integer, desc: 'The amount of InfluxDB connections to open'
- requires :metrics_port, type: Integer, desc: 'The UDP port to use for connecting to InfluxDB'
- requires :metrics_sample_interval, type: Integer, desc: 'The sampling interval in seconds'
- requires :metrics_timeout, type: Integer, desc: 'The amount of seconds after which an InfluxDB connection will time out'
- end
+ optional :metrics_method_call_threshold, type: Integer, desc: 'A method call is only tracked when it takes longer to complete than the given amount of milliseconds.'
optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
optional :password_authentication_enabled_for_web, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
mutually_exclusive :password_authentication_enabled_for_web, :password_authentication_enabled, :signin_enabled
@@ -153,6 +144,8 @@ module API
optional :snowplow_cookie_domain, type: String, desc: 'The Snowplow cookie domain'
optional :snowplow_app_id, type: String, desc: 'The Snowplow site name / application id'
end
+ 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."
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
@@ -192,6 +185,9 @@ module API
attrs[:allow_local_requests_from_web_hooks_and_services] = attrs.delete(:allow_local_requests_from_hooks_and_services)
end
+ # since 13.0 it's not possible to disable hashed storage - support can be removed in 14.0
+ attrs.delete(:hashed_storage_enabled) if attrs.has_key?(:hashed_storage_enabled)
+
attrs = filter_attributes_using_license(attrs)
if ApplicationSettings::UpdateService.new(current_settings, current_user, attrs).execute
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 0aaab9a812f..be58b832f97 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -8,6 +8,7 @@ module API
before { authenticate! }
resource :snippets do
+ helpers Helpers::SnippetsHelpers
helpers do
def snippets_for_current_user
SnippetsFinder.new(current_user, author: current_user).execute
@@ -24,13 +25,13 @@ module API
desc 'Get a snippets list for authenticated user' do
detail 'This feature was introduced in GitLab 8.15.'
- success Entities::PersonalSnippet
+ success Entities::Snippet
end
params do
use :pagination
end
get do
- present paginate(snippets_for_current_user), with: Entities::PersonalSnippet
+ present paginate(snippets_for_current_user), with: Entities::Snippet
end
desc 'List all public personal snippets current_user has access to' do
@@ -64,9 +65,9 @@ module API
success Entities::PersonalSnippet
end
params do
- requires :title, type: String, desc: 'The title of a snippet'
+ requires :title, type: String, allow_blank: false, desc: 'The title of a snippet'
requires :file_name, type: String, desc: 'The name of a snippet file'
- requires :content, type: String, desc: 'The content of a snippet'
+ requires :content, type: String, allow_blank: false, desc: 'The content of a snippet'
optional :description, type: String, desc: 'The description of a snippet'
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
@@ -80,12 +81,12 @@ module API
service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute
snippet = service_response.payload[:snippet]
- render_spam_error! if snippet.spam?
-
- if snippet.persisted?
+ if service_response.success?
present snippet, with: Entities::PersonalSnippet
else
- render_validation_error!(snippet)
+ render_spam_error! if snippet.spam?
+
+ render_api_error!({ error: service_response.message }, service_response.http_status)
end
end
@@ -95,9 +96,9 @@ module API
end
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
- optional :title, type: String, desc: 'The title of a snippet'
+ optional :title, type: String, allow_blank: false, desc: 'The title of a snippet'
optional :file_name, type: String, desc: 'The name of a snippet file'
- optional :content, type: String, desc: 'The content of a snippet'
+ optional :content, type: String, allow_blank: false, desc: 'The content of a snippet'
optional :description, type: String, desc: 'The description of a snippet'
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
@@ -114,12 +115,12 @@ module API
service_response = ::Snippets::UpdateService.new(nil, current_user, attrs).execute(snippet)
snippet = service_response.payload[:snippet]
- render_spam_error! if snippet.spam?
-
- if snippet.persisted?
+ if service_response.success?
present snippet, with: Entities::PersonalSnippet
else
- render_validation_error!(snippet)
+ render_spam_error! if snippet.spam?
+
+ render_api_error!({ error: service_response.message }, service_response.http_status)
end
end
@@ -159,7 +160,7 @@ module API
env['api.format'] = :txt
content_type 'text/plain'
header['Content-Disposition'] = 'attachment'
- present snippet.content
+ present content_for(snippet)
end
desc 'Get the user agent details for a snippet' do
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index a2146406690..884e3019a2d 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -70,7 +70,7 @@ module API
post ':id/wikis' do
authorize! :create_wiki, user_project
- page = WikiPages::CreateService.new(user_project, current_user, params).execute
+ page = WikiPages::CreateService.new(container: user_project, current_user: current_user, params: params).execute
if page.valid?
present page, with: Entities::WikiPage
@@ -91,7 +91,7 @@ module API
put ':id/wikis/:slug' do
authorize! :create_wiki, user_project
- page = WikiPages::UpdateService.new(user_project, current_user, params).execute(wiki_page)
+ page = WikiPages::UpdateService.new(container: user_project, current_user: current_user, params: params).execute(wiki_page)
if page.valid?
present page, with: Entities::WikiPage
@@ -107,7 +107,7 @@ module API
delete ':id/wikis/:slug' do
authorize! :admin_wiki, user_project
- WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page)
+ WikiPages::DestroyService.new(container: user_project, current_user: current_user).execute(wiki_page)
no_content!
end
@@ -123,9 +123,11 @@ module API
post ":id/wikis/attachments" do
authorize! :create_wiki, user_project
- result = ::Wikis::CreateAttachmentService.new(user_project,
- current_user,
- commit_params(declared_params(include_missing: false))).execute
+ result = ::Wikis::CreateAttachmentService.new(
+ container: user_project,
+ current_user: current_user,
+ params: commit_params(declared_params(include_missing: false))
+ ).execute
if result[:status] == :success
status(201)