summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/api.rb15
-rw-r--r--lib/api/boards.rb2
-rw-r--r--lib/api/boards_responses.rb2
-rw-r--r--lib/api/branches.rb5
-rw-r--r--lib/api/commit_statuses.rb4
-rw-r--r--lib/api/commits.rb23
-rw-r--r--lib/api/discussions.rb44
-rw-r--r--lib/api/entities.rb111
-rw-r--r--lib/api/entities/container_registry.rb10
-rw-r--r--lib/api/environments.rb7
-rw-r--r--lib/api/files.rb25
-rw-r--r--lib/api/group_boards.rb2
-rw-r--r--lib/api/group_clusters.rb140
-rw-r--r--lib/api/group_container_repositories.rb39
-rw-r--r--lib/api/group_labels.rb2
-rw-r--r--lib/api/groups.rb5
-rw-r--r--lib/api/helpers.rb17
-rw-r--r--lib/api/helpers/graphql_helpers.rb2
-rw-r--r--lib/api/helpers/internal_helpers.rb2
-rw-r--r--lib/api/helpers/label_helpers.rb6
-rw-r--r--lib/api/helpers/notes_helpers.rb25
-rw-r--r--lib/api/helpers/pagination.rb4
-rw-r--r--lib/api/helpers/projects_helpers.rb49
-rw-r--r--lib/api/helpers/runner.rb5
-rw-r--r--lib/api/helpers/services_helpers.rb42
-rw-r--r--lib/api/helpers/variables_helpers.rb13
-rw-r--r--lib/api/import_github.rb2
-rw-r--r--lib/api/issues.rb8
-rw-r--r--lib/api/job_artifacts.rb4
-rw-r--r--lib/api/labels.rb2
-rw-r--r--lib/api/merge_requests.rb29
-rw-r--r--lib/api/notes.rb12
-rw-r--r--lib/api/pages_domains.rb15
-rw-r--r--lib/api/project_clusters.rb2
-rw-r--r--lib/api/project_container_repositories.rb (renamed from lib/api/container_registry.rb)37
-rw-r--r--lib/api/project_import.rb1
-rw-r--r--lib/api/projects.rb35
-rw-r--r--lib/api/releases.rb2
-rw-r--r--lib/api/resource_label_events.rb4
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/api/settings.rb24
-rw-r--r--lib/api/tags.rb4
-rw-r--r--lib/api/todos.rb17
-rw-r--r--lib/api/triggers.rb21
-rw-r--r--lib/api/user_counts.rb18
-rw-r--r--lib/api/users.rb26
-rw-r--r--lib/api/validations/types/labels_list.rb2
-rw-r--r--lib/api/variables.rb8
48 files changed, 601 insertions, 275 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 20f8c637274..219ed45eff6 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -10,14 +10,15 @@ module API
NAMESPACE_OR_PROJECT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze
COMMIT_ENDPOINT_REQUIREMENTS = NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(sha: NO_SLASH_URL_PART_REGEX).freeze
USER_REQUIREMENTS = { user_id: NO_SLASH_URL_PART_REGEX }.freeze
+ LOG_FILTERS = ::Rails.application.config.filter_parameters + [/^output$/]
insert_before Grape::Middleware::Error,
GrapeLogging::Middleware::RequestLogger,
logger: Logger.new(LOG_FILENAME),
formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new,
include: [
- GrapeLogging::Loggers::FilterParameters.new,
- GrapeLogging::Loggers::ClientEnv.new,
+ GrapeLogging::Loggers::FilterParameters.new(LOG_FILTERS),
+ Gitlab::GrapeLogging::Loggers::ClientEnvLogger.new,
Gitlab::GrapeLogging::Loggers::RouteLogger.new,
Gitlab::GrapeLogging::Loggers::UserLogger.new,
Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new,
@@ -52,7 +53,10 @@ module API
rack_response({ 'message' => '404 Not found' }.to_json, 404)
end
- rescue_from ::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError do
+ rescue_from(
+ ::ActiveRecord::StaleObjectError,
+ ::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ ) do
rack_response({ 'message' => '409 Conflict: Resource lock' }.to_json, 409)
end
@@ -100,7 +104,6 @@ module API
mount ::API::BroadcastMessages
mount ::API::Commits
mount ::API::CommitStatuses
- mount ::API::ContainerRegistry
mount ::API::DeployKeys
mount ::API::Deployments
mount ::API::Environments
@@ -108,9 +111,11 @@ module API
mount ::API::Features
mount ::API::Files
mount ::API::GroupBoards
+ mount ::API::GroupClusters
mount ::API::GroupLabels
mount ::API::GroupMilestones
mount ::API::Groups
+ mount ::API::GroupContainerRepositories
mount ::API::GroupVariables
mount ::API::ImportGithub
mount ::API::Internal
@@ -133,6 +138,7 @@ module API
mount ::API::Pipelines
mount ::API::PipelineSchedules
mount ::API::ProjectClusters
+ mount ::API::ProjectContainerRepositories
mount ::API::ProjectEvents
mount ::API::ProjectExport
mount ::API::ProjectImport
@@ -163,6 +169,7 @@ module API
mount ::API::Templates
mount ::API::Todos
mount ::API::Triggers
+ mount ::API::UserCounts
mount ::API::Users
mount ::API::Variables
mount ::API::Version
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index b7c77730afb..4e31f74f18a 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -27,7 +27,7 @@ module API
end
get '/' do
authorize!(:read_board, user_project)
- present paginate(board_parent.boards), with: Entities::Board
+ present paginate(board_parent.boards.with_associations), with: Entities::Board
end
desc 'Find a project board' do
diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb
index 86d9b24802f..68497a08fb8 100644
--- a/lib/api/boards_responses.rb
+++ b/lib/api/boards_responses.rb
@@ -11,7 +11,7 @@ module API
end
def board_lists
- board.lists.destroyable
+ board.destroyable_lists
end
def create_list
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 65d7f68bbf9..c3821630b6b 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -8,7 +8,10 @@ module API
BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
- before { authorize! :download_code, user_project }
+ before do
+ require_repository_enabled!
+ authorize! :download_code, user_project
+ end
helpers do
params :filter_params do
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 08b4f8db8b0..d58a5e214ed 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -52,6 +52,7 @@ module API
optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
optional :coverage, type: Float, desc: 'The total code coverage'
+ optional :pipeline_id, type: Integer, desc: 'An existing pipeline ID, when multiple pipelines on the same commit SHA have been triggered'
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/statuses/:sha' do
@@ -73,7 +74,8 @@ module API
name = params[:name] || params[:context] || 'default'
- pipeline = @project.pipeline_for(ref, commit.sha)
+ pipeline = @project.pipeline_for(ref, commit.sha, params[:pipeline_id])
+
unless pipeline
pipeline = @project.ci_pipelines.create!(
source: :external,
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 80913f4ca07..a2f3e87ebd2 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -6,7 +6,10 @@ module API
class Commits < Grape::API
include PaginationParams
- before { authorize! :download_code, user_project }
+ before do
+ require_repository_enabled!
+ authorize! :download_code, user_project
+ end
helpers do
def user_access
@@ -40,7 +43,7 @@ module API
path = params[:path]
before = params[:until]
after = params[:since]
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master' unless params[:all]
+ ref = params[:ref_name].presence || user_project.try(:default_branch) || 'master' unless params[:all]
offset = (params[:page] - 1) * params[:per_page]
all = params[:all]
with_stats = params[:with_stats]
@@ -73,7 +76,7 @@ module API
detail 'This feature was introduced in GitLab 8.13'
end
params do
- requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.', allow_blank: false
+ requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.', allow_blank: false
requires :commit_message, type: String, desc: 'Commit message'
requires :actions, type: Array, desc: 'Actions to perform in commit' do
requires :action, type: String, desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze
@@ -95,12 +98,16 @@ module API
requires :execute_filemode, type: Boolean, desc: 'When `true/false` enables/disables the execute flag on the file.'
end
end
- optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
- optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the commit from'
+
+ optional :start_branch, type: String, desc: 'Name of the branch to start the new branch from'
+ optional :start_sha, type: String, desc: 'SHA of the commit to start the new branch from'
+ mutually_exclusive :start_branch, :start_sha
+
+ optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the new branch from'
optional :author_email, type: String, desc: 'Author email for commit'
optional :author_name, type: String, desc: 'Author name for commit'
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
- optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch`'
+ optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch` or `start_sha`'
end
post ':id/repository/commits' do
if params[:start_project]
@@ -115,7 +122,7 @@ module API
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
- attrs[:start_branch] ||= attrs[:branch_name]
+ attrs[:start_branch] ||= attrs[:branch_name] unless attrs[:start_sha]
attrs[:start_project] = start_project if start_project
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
@@ -123,7 +130,7 @@ module API
if result[:status] == :success
commit_detail = user_project.repository.commit(result[:result])
- Gitlab::WebIdeCommitsCounter.increment if find_user_from_warden
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count if find_user_from_warden
present commit_detail, with: Entities::CommitDetail, stats: params[:stats]
else
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 693172b7d08..6c1acc3963f 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -4,6 +4,7 @@ module API
class Discussions < Grape::API
include PaginationParams
helpers ::API::Helpers::NotesHelpers
+ helpers ::RendersNotes
before { authenticate! }
@@ -23,21 +24,15 @@ module API
requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable'
use :pagination
end
- # rubocop: disable CodeReuse/ActiveRecord
- get ":id/#{noteables_path}/:noteable_id/discussions" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
- notes = noteable.notes
- .inc_relations_for_view
- .includes(:noteable)
- .fresh
+ get ":id/#{noteables_path}/:noteable_id/discussions" do
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
- notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes = readable_discussion_notes(noteable)
discussions = Kaminari.paginate_array(Discussion.build_collection(notes, noteable))
present paginate(discussions), with: Entities::Discussion
end
- # rubocop: enable CodeReuse/ActiveRecord
desc "Get a single #{noteable_type.to_s.downcase} discussion" do
success Entities::Discussion
@@ -47,7 +42,7 @@ module API
requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable'
end
get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
if notes.empty?
@@ -82,7 +77,7 @@ module API
end
end
post ":id/#{noteables_path}/:noteable_id/discussions" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
type = params[:position] ? 'DiffNote' : 'DiscussionNote'
id_key = noteable.is_a?(Commit) ? :commit_id : :noteable_id
@@ -112,7 +107,7 @@ module API
requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable'
end
get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
if notes.empty?
@@ -132,7 +127,7 @@ module API
optional :created_at, type: String, desc: 'The creation date of the note'
end
post ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
first_note = notes.first
@@ -166,7 +161,7 @@ module API
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
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
get_note(noteable, params[:note_id])
end
@@ -183,7 +178,7 @@ module API
exactly_one_of :body, :resolved
end
put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
if params[:resolved].nil?
update_note(noteable, params[:note_id])
@@ -201,7 +196,7 @@ module API
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
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
delete_note(noteable, params[:note_id])
end
@@ -216,7 +211,7 @@ module API
requires :resolved, type: Boolean, desc: 'Mark discussion resolved/unresolved'
end
put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
resolve_discussion(noteable, params[:discussion_id], params[:resolved])
end
@@ -226,13 +221,24 @@ module API
helpers do
# rubocop: disable CodeReuse/ActiveRecord
- def readable_discussion_notes(noteable, discussion_id)
+ def readable_discussion_notes(noteable, discussion_id = nil)
notes = noteable.notes
- .where(discussion_id: discussion_id)
+ notes = notes.where(discussion_id: discussion_id) if discussion_id
+ notes = notes
.inc_relations_for_view
.includes(:noteable)
.fresh
+ # Without RendersActions#prepare_notes_for_rendering,
+ # Note#cross_reference_not_visible_for? will attempt to render
+ # Markdown references mentioned in the note to see whether they
+ # should be redacted. For notes that reference a commit, this
+ # would also incur a Gitaly call to verify the commit exists.
+ #
+ # With prepare_notes_for_rendering, we can avoid Gitaly calls
+ # because notes are redacted if they point to projects that
+ # cannot be accessed by the user.
+ notes = prepare_notes_for_rendering(notes)
notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 8840accf675..5e66b4e76a5 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -2,6 +2,19 @@
module API
module Entities
+ class BlameRangeCommit < Grape::Entity
+ expose :id
+ expose :parent_ids
+ expose :message
+ expose :authored_date, :author_name, :author_email
+ expose :committed_date, :committer_name, :committer_email
+ end
+
+ class BlameRange < Grape::Entity
+ expose :commit, using: BlameRangeCommit
+ expose :lines
+ end
+
class WikiPageBasic < Grape::Entity
expose :format
expose :slug
@@ -64,6 +77,11 @@ module API
expose :last_activity_on, as: :last_activity_at # Back-compat
end
+ class UserStarsProject < Grape::Entity
+ expose :starred_since
+ expose :user, using: Entities::UserBasic
+ end
+
class Identity < Grape::Entity
expose :provider, :extern_uid
end
@@ -201,6 +219,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
projects_relation.preload(:project_feature, :route)
.preload(:import_state, :tags)
+ .preload(:auto_devops)
.preload(namespace: [:route, :owner])
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -247,12 +266,20 @@ module API
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
+ # TODO: remove in API v5, replaced by *_access_level
expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
+ expose(:issues_access_level) { |project, options| project.project_feature.string_access_level(:issues) }
+ expose(:repository_access_level) { |project, options| project.project_feature.string_access_level(:repository) }
+ expose(:merge_requests_access_level) { |project, options| project.project_feature.string_access_level(:merge_requests) }
+ expose(:wiki_access_level) { |project, options| project.project_feature.string_access_level(:wiki) }
+ expose(:builds_access_level) { |project, options| project.project_feature.string_access_level(:builds) }
+ expose(:snippets_access_level) { |project, options| project.project_feature.string_access_level(:snippets) }
+
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
@@ -267,6 +294,12 @@ module API
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :ci_default_git_depth
expose :public_builds, as: :public_jobs
+ expose :build_git_strategy, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options|
+ project.build_allow_git_fetch ? 'fetch' : 'clone'
+ end
+ expose :build_timeout
+ expose :auto_cancel_pending_pipelines
+ expose :build_coverage_regex
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links, options)
@@ -279,7 +312,10 @@ module API
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
- expose :external_authorization_classification_label
+ expose :auto_devops_enabled?, as: :auto_devops_enabled
+ expose :auto_devops_deploy_strategy do |project, options|
+ project.auto_devops.nil? ? 'continuous' : project.auto_devops.deploy_strategy
+ end
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {})
@@ -289,6 +325,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
super(projects_relation).preload(:group)
.preload(:ci_cd_settings)
+ .preload(:auto_devops)
.preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,
@@ -347,10 +384,7 @@ module API
end
expose :request_access_enabled
expose :full_name, :full_path
-
- if ::Group.supports_nested_objects?
- expose :parent_id
- end
+ expose :parent_id
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
@@ -491,16 +525,16 @@ module API
end
end
- class ProjectEntity < Grape::Entity
+ class IssuableEntity < Grape::Entity
expose :id, :iid
expose(:project_id) { |entity| entity&.project.try(:id) }
expose :title, :description
expose :state, :created_at, :updated_at
# Avoids an N+1 query when metadata is included
- def issuable_metadata(subject, options, method)
+ def issuable_metadata(subject, options, method, args = nil)
cached_subject = options.dig(:issuable_metadata, subject.id)
- (cached_subject || subject).public_send(method) # rubocop: disable GitlabSecurity/PublicSend
+ (cached_subject || subject).public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend
end
end
@@ -544,7 +578,7 @@ module API
end
end
- class IssueBasic < ProjectEntity
+ class IssueBasic < IssuableEntity
expose :closed_at
expose :closed_by, using: Entities::UserBasic
@@ -564,7 +598,7 @@ module API
end
expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) }
- expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count) }
+ expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count, options[:current_user]) }
expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) }
expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) }
expose :due_date
@@ -611,7 +645,10 @@ module API
end
end
- expose :subscribed do |issue, options|
+ # Calculating the value of subscribed field triggers Markdown
+ # processing. We can't do that for multiple issues / merge
+ # requests in a single API request.
+ expose :subscribed, if: -> (_, options) { options.fetch(:include_subscribed, true) } do |issue, options|
issue.subscribed?(options[:current_user], options[:project] || issue.project)
end
end
@@ -650,14 +687,14 @@ module API
end
end
- class MergeRequestSimple < ProjectEntity
+ class MergeRequestSimple < IssuableEntity
expose :title
expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request)
end
end
- class MergeRequestBasic < ProjectEntity
+ class MergeRequestBasic < IssuableEntity
expose :merged_by, using: Entities::UserBasic do |merge_request, _options|
merge_request.metrics&.merged_by
end
@@ -703,7 +740,7 @@ module API
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more
# information.
expose :merge_status do |merge_request|
- merge_request.check_if_can_be_merged
+ merge_request.check_mergeability
merge_request.merge_status
end
expose :diff_head_sha, as: :sha
@@ -757,7 +794,9 @@ module API
merge_request.metrics&.pipeline
end
- expose :head_pipeline, using: 'API::Entities::Pipeline'
+ expose :head_pipeline, using: 'API::Entities::Pipeline', if: -> (_, options) do
+ Ability.allowed?(options[:current_user], :read_pipeline, options[:project])
+ end
expose :diff_refs, using: Entities::DiffRefs
@@ -997,7 +1036,7 @@ module API
class ProjectService < Grape::Entity
expose :id, :title, :created_at, :updated_at, :active
- expose :push_events, :issues_events, :confidential_issues_events
+ expose :commit_events, :push_events, :issues_events, :confidential_issues_events
expose :merge_requests_events, :tag_push_events, :note_events
expose :confidential_note_events, :pipeline_events, :wiki_page_events
expose :job_events
@@ -1031,15 +1070,8 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {})
relation = super(projects_relation, options)
-
- # MySQL doesn't support LIMIT inside an IN subquery
- if Gitlab::Database.mysql?
- project_ids = relation.pluck('projects.id')
- namespace_ids = relation.pluck(:namespace_id)
- else
- project_ids = relation.select('projects.id')
- namespace_ids = relation.select(:namespace_id)
- end
+ project_ids = relation.select('projects.id')
+ namespace_ids = relation.select(:namespace_id)
options[:project_members] = options[:current_user]
.project_members
@@ -1061,16 +1093,18 @@ module API
end
class Label < LabelBasic
- expose :open_issues_count do |label, options|
- label.open_issues_count(options[:current_user])
- end
+ with_options if: lambda { |_, options| options[:with_counts] } do
+ expose :open_issues_count do |label, options|
+ label.open_issues_count(options[:current_user])
+ end
- expose :closed_issues_count do |label, options|
- label.closed_issues_count(options[:current_user])
- end
+ expose :closed_issues_count do |label, options|
+ label.closed_issues_count(options[:current_user])
+ end
- expose :open_merge_requests_count do |label, options|
- label.open_merge_requests_count(options[:current_user])
+ expose :open_merge_requests_count do |label, options|
+ label.open_merge_requests_count(options[:current_user])
+ end
end
expose :subscribed do |label, options|
@@ -1101,7 +1135,7 @@ module API
expose :project, using: Entities::BasicProjectDetails
expose :lists, using: Entities::List do |board|
- board.lists.destroyable
+ board.destroyable_lists
end
end
@@ -1138,6 +1172,7 @@ module API
attributes = ::ApplicationSettingsHelper.visible_attributes
attributes.delete(:performance_bar_allowed_group_path)
attributes.delete(:performance_bar_enabled)
+ attributes.delete(:allow_local_requests_from_hooks_and_services)
attributes
end
@@ -1156,6 +1191,7 @@ module API
# support legacy names, can be removed in v5
expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
expose :password_authentication_enabled_for_web, as: :signin_enabled
+ expose :allow_local_requests_from_web_hooks_and_services, as: :allow_local_requests_from_hooks_and_services
end
# deprecated old Release representation
@@ -1186,8 +1222,10 @@ module API
MarkupHelper.markdown_field(entity, :description)
end
expose :created_at
+ expose :released_at
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? }
+ expose :upcoming_release?, as: :upcoming_release
expose :assets do
expose :assets_count, as: :count do |release, _|
@@ -1313,6 +1351,7 @@ module API
expose :variable_type, :key, :value
expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) }
+ expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) }
end
class Pipeline < PipelineBasic
@@ -1662,5 +1701,9 @@ module API
class ClusterProject < Cluster
expose :project, using: Entities::BasicProjectDetails
end
+
+ class ClusterGroup < Cluster
+ expose :group, using: Entities::BasicGroupDetails
+ end
end
end
diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb
index 00833ca7480..6250f35c7cb 100644
--- a/lib/api/entities/container_registry.rb
+++ b/lib/api/entities/container_registry.rb
@@ -3,18 +3,20 @@
module API
module Entities
module ContainerRegistry
- class Repository < Grape::Entity
- expose :id
+ class Tag < Grape::Entity
expose :name
expose :path
expose :location
- expose :created_at
end
- class Tag < Grape::Entity
+ class Repository < Grape::Entity
+ expose :id
expose :name
expose :path
+ expose :project_id
expose :location
+ expose :created_at
+ expose :tags, using: Tag, if: -> (_, options) { options[:tags] }
end
class TagDetails < Tag
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 6cd43923559..ec58b3b7bb9 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -18,11 +18,16 @@ module API
end
params do
use :pagination
+ optional :name, type: String, desc: 'Returns the environment with this name'
+ optional :search, type: String, desc: 'Returns list of environments matching the search criteria'
+ mutually_exclusive :name, :search, message: 'cannot be used together'
end
get ':id/environments' do
authorize! :read_environment, user_project
- present paginate(user_project.environments), with: Entities::Environment, current_user: current_user
+ environments = ::EnvironmentsFinder.new(user_project, current_user, params).find
+
+ present paginate(environments), with: Entities::Environment, current_user: current_user
end
desc 'Creates a new environment' do
diff --git a/lib/api/files.rb b/lib/api/files.rb
index ca59d330e1c..0b438fb5bbc 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -83,6 +83,31 @@ module API
resource :projects, requirements: FILE_ENDPOINT_REQUIREMENTS do
allow_access_with_scope :read_repository, if: -> (request) { request.get? || request.head? }
+ desc 'Get blame file metadata from repository'
+ params do
+ requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
+ end
+ head ":id/repository/files/:file_path/blame", requirements: FILE_ENDPOINT_REQUIREMENTS do
+ assign_file_vars!
+
+ set_http_headers(blob_data)
+ end
+
+ desc 'Get blame file from the repository'
+ params do
+ requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
+ end
+ get ":id/repository/files/:file_path/blame", requirements: FILE_ENDPOINT_REQUIREMENTS do
+ assign_file_vars!
+
+ set_http_headers(blob_data)
+
+ blame_ranges = Gitlab::Blame.new(@blob, @commit).groups(highlight: false)
+ present blame_ranges, with: Entities::BlameRange
+ end
+
desc 'Get raw file metadata from repository'
params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb
index 9a20ee8c8b9..feb2254963e 100644
--- a/lib/api/group_boards.rb
+++ b/lib/api/group_boards.rb
@@ -37,7 +37,7 @@ module API
use :pagination
end
get '/' do
- present paginate(board_parent.boards), with: Entities::Board
+ present paginate(board_parent.boards.with_associations), with: Entities::Board
end
end
diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb
new file mode 100644
index 00000000000..db0f8081140
--- /dev/null
+++ b/lib/api/group_clusters.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+module API
+ class GroupClusters < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ # EE::API::GroupClusters will
+ # override these methods
+ helpers do
+ params :create_params_ee do
+ end
+
+ params :update_params_ee do
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of the group'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all clusters from the group' do
+ success Entities::Cluster
+ end
+ params do
+ use :pagination
+ end
+ get ':id/clusters' do
+ authorize! :read_cluster, user_group
+
+ present paginate(clusters_for_current_user), with: Entities::Cluster
+ end
+
+ desc 'Get specific cluster for the group' do
+ success Entities::ClusterGroup
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The cluster ID'
+ end
+ get ':id/clusters/:cluster_id' do
+ authorize! :read_cluster, cluster
+
+ present cluster, with: Entities::ClusterGroup
+ end
+
+ desc 'Adds an existing cluster' do
+ success Entities::ClusterGroup
+ end
+ params do
+ requires :name, type: String, desc: 'Cluster name'
+ optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true'
+ optional :domain, type: String, desc: 'Cluster base domain'
+ optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true'
+ requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
+ requires :api_url, type: String, allow_blank: false, desc: 'URL to access the Kubernetes API'
+ requires :token, type: String, desc: 'Token to authenticate against Kubernetes'
+ optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
+ optional :namespace, type: String, desc: 'Unique namespace related to Group'
+ optional :authorization_type, type: String, values: Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
+ end
+ use :create_params_ee
+ end
+ post ':id/clusters/user' do
+ authorize! :add_cluster, user_group
+
+ user_cluster = ::Clusters::CreateService
+ .new(current_user, create_cluster_user_params)
+ .execute
+
+ if user_cluster.persisted?
+ present user_cluster, with: Entities::ClusterGroup
+ else
+ render_validation_error!(user_cluster)
+ end
+ end
+
+ desc 'Update an existing cluster' do
+ success Entities::ClusterGroup
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The cluster ID'
+ optional :name, type: String, desc: 'Cluster name'
+ optional :domain, type: String, desc: 'Cluster base domain'
+ optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
+ optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
+ optional :token, type: String, desc: 'Token to authenticate against Kubernetes'
+ optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
+ optional :namespace, type: String, desc: 'Unique namespace related to Group'
+ end
+ use :update_params_ee
+ end
+ put ':id/clusters/:cluster_id' do
+ authorize! :update_cluster, cluster
+
+ update_service = Clusters::UpdateService.new(current_user, update_cluster_params)
+
+ if update_service.execute(cluster)
+ present cluster, with: Entities::ClusterGroup
+ else
+ render_validation_error!(cluster)
+ end
+ end
+
+ desc 'Remove a cluster' do
+ success Entities::ClusterGroup
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The Cluster ID'
+ end
+ delete ':id/clusters/:cluster_id' do
+ authorize! :admin_cluster, cluster
+
+ destroy_conditionally!(cluster)
+ end
+ end
+
+ helpers do
+ def clusters_for_current_user
+ @clusters_for_current_user ||= ClustersFinder.new(user_group, current_user, :all).execute
+ end
+
+ def cluster
+ @cluster ||= clusters_for_current_user.find(params[:cluster_id])
+ end
+
+ def create_cluster_user_params
+ declared_params.merge({
+ provider_type: :user,
+ platform_type: :kubernetes,
+ clusterable: user_group
+ })
+ end
+
+ def update_cluster_params
+ declared_params(include_missing: false).without(:cluster_id)
+ end
+ end
+ end
+end
diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb
new file mode 100644
index 00000000000..fd24662cc9a
--- /dev/null
+++ b/lib/api/group_container_repositories.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module API
+ class GroupContainerRepositories < Grape::API
+ include PaginationParams
+
+ before { authorize_read_group_container_images! }
+
+ REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
+ tag_name: API::NO_SLASH_URL_PART_REGEX)
+
+ params do
+ requires :id, type: String, desc: "Group's ID or path"
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get a list of all repositories within a group' do
+ detail 'This feature was introduced in GitLab 12.2.'
+ success Entities::ContainerRegistry::Repository
+ end
+ params do
+ use :pagination
+ optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included'
+ end
+ get ':id/registry/repositories' do
+ repositories = ContainerRepositoriesFinder.new(
+ id: user_group.id, container_type: :group
+ ).execute
+
+ present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags]
+ end
+ end
+
+ helpers do
+ def authorize_read_group_container_images!
+ authorize! :read_container_image, user_group
+ end
+ end
+ end
+end
diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb
index 0dbc5f45a68..79a44941c81 100644
--- a/lib/api/group_labels.rb
+++ b/lib/api/group_labels.rb
@@ -16,6 +16,8 @@ module API
success Entities::GroupLabel
end
params do
+ optional :with_counts, type: Boolean, default: false,
+ desc: 'Include issue and merge request counts'
use :pagination
end
get ':id/labels' do
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index ec1020c7c78..f545f33c06b 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -114,10 +114,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group'
-
- if ::Group.supports_nested_objects?
- optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
- end
+ optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
use :optional_params
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 00bcf6b055b..1aa6dc44bf7 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -183,11 +183,6 @@ module API
user_project.commit_by(oid: id)
end
- def find_project_snippet(id)
- finder_params = { project: user_project }
- SnippetsFinder.new(current_user, finder_params).find(id)
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def find_merge_request_with_access(iid, access_level = :read_merge_request)
merge_request = user_project.merge_requests.find_by!(iid: iid)
@@ -235,6 +230,10 @@ module API
authorize! :push_code, user_project
end
+ def authorize_admin_tag
+ authorize! :admin_tag, user_project
+ end
+
def authorize_admin_project
authorize! :admin_project, user_project
end
@@ -251,6 +250,10 @@ module API
authorize! :update_build, user_project
end
+ def require_repository_enabled!(subject = :global)
+ not_found!("Repository") unless user_project.feature_available?(:repository, current_user)
+ end
+
def require_gitlab_workhorse!
unless env['HTTP_GITLAB_WORKHORSE'].present?
forbidden!('Request should be executed via GitLab Workhorse')
@@ -541,5 +544,9 @@ module API
params[:archived]
end
+
+ def ip_address
+ env["action_dispatch.remote_ip"].to_s || request.ip
+ end
end
end
diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb
index 94010ab1bc2..bd60470fbd6 100644
--- a/lib/api/helpers/graphql_helpers.rb
+++ b/lib/api/helpers/graphql_helpers.rb
@@ -7,8 +7,6 @@ module API
# should be in app/graphql/ or lib/gitlab/graphql/
module GraphqlHelpers
def conditionally_graphql!(fallback:, query:, context: {}, transform: nil)
- return fallback.call unless Feature.enabled?(:graphql)
-
result = GitlabSchema.execute(query, context: context)
if transform
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index c318f5b9127..9afe6c5b027 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -72,7 +72,7 @@ module API
result == 'PONG'
rescue => e
- Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}")
+ Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
false
end
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
index c11e7d614ab..896b0aba52b 100644
--- a/lib/api/helpers/label_helpers.rb
+++ b/lib/api/helpers/label_helpers.rb
@@ -19,7 +19,11 @@ module API
end
def get_labels(parent, entity)
- present paginate(available_labels_for(parent)), with: entity, current_user: current_user, parent: parent
+ present paginate(available_labels_for(parent)),
+ with: entity,
+ current_user: current_user,
+ parent: parent,
+ with_counts: params[:with_counts]
end
def create_label(parent, entity)
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index a068de4361c..6bf9057fad7 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -73,14 +73,29 @@ module API
"read_#{noteable.class.to_s.underscore}".to_sym
end
- def find_noteable(parent, noteables_str, noteable_id)
- noteable = public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
+ def find_noteable(parent_type, parent_id, noteable_type, noteable_id)
+ params = finder_params_by_noteable_type_and_id(noteable_type, noteable_id, parent_id)
- readable = can?(current_user, noteable_read_ability_name(noteable), noteable)
+ noteable = NotesFinder.new(current_user, params).target
+ noteable = nil unless can?(current_user, noteable_read_ability_name(noteable), noteable)
+ noteable || not_found!(noteable_type)
+ end
+
+ def finder_params_by_noteable_type_and_id(type, id, parent_id)
+ target_type = type.name.underscore
+ { target_type: target_type }.tap do |h|
+ if %w(issue merge_request).include?(target_type)
+ h[:target_iid] = id
+ else
+ h[:target_id] = id
+ end
- return not_found!(noteables_str) unless readable
+ add_parent_to_finder_params(h, type, parent_id)
+ end
+ end
- noteable
+ def add_parent_to_finder_params(finder_params, noteable_type, parent_id)
+ finder_params[:project] = user_project
end
def noteable_parent(noteable)
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 2a9b17ad22a..71bbc218f94 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -205,7 +205,9 @@ module API
limited_total_count = pagination_data.total_count_with_limit
if limited_total_count > Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT
- pagination_data.without_count
+ # The call to `total_count_with_limit` memoizes `@arel` because of a call to `references_eager_loaded_tables?`
+ # We need to call `reset` because `without_count` relies on `@arel` being unmemoized
+ pagination_data.reset.without_count
else
pagination_data
end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index f242f1fea0e..51b7cf05c8f 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -8,12 +8,26 @@ module API
params :optional_project_params_ce do
optional :description, type: String, desc: 'The description of the project'
+ optional :build_git_strategy, type: String, values: %w(fetch clone), desc: 'The Git strategy. Defaults to `fetch`'
+ optional :build_timeout, type: Integer, desc: 'Build timeout'
+ optional :auto_cancel_pending_pipelines, type: String, values: %w(disabled enabled), desc: 'Auto-cancel pending pipelines'
+ optional :build_coverage_regex, type: String, desc: 'Test coverage parsing'
optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
+
+ # TODO: remove in API v5, replaced by *_access_level
optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
+
+ optional :issues_access_level, type: String, values: %w(disabled private enabled), desc: 'Issues access level. One of `disabled`, `private` or `enabled`'
+ optional :repository_access_level, type: String, values: %w(disabled private enabled), desc: 'Repository access level. One of `disabled`, `private` or `enabled`'
+ optional :merge_requests_access_level, type: String, values: %w(disabled private enabled), desc: 'Merge requests access level. One of `disabled`, `private` or `enabled`'
+ optional :wiki_access_level, type: String, values: %w(disabled private enabled), desc: 'Wiki access level. One of `disabled`, `private` or `enabled`'
+ optional :builds_access_level, type: String, values: %w(disabled private enabled), desc: 'Builds access level. One of `disabled`, `private` or `enabled`'
+ optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`'
+
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 :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
@@ -28,8 +42,9 @@ module API
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
- optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning'
+ optional :auto_devops_enabled, type: Boolean, desc: 'Flag indication if Auto DevOps is enabled'
+ optional :auto_devops_deploy_strategy, type: String, values: %w(continuous manual timed_incremental), desc: 'Auto Deploy strategy'
end
params :optional_project_params_ee do
@@ -48,15 +63,21 @@ module API
def self.update_params_at_least_one_of
[
- :jobs_enabled,
- :resolve_outdated_diff_discussions,
+ :auto_devops_enabled,
+ :auto_devops_deploy_strategy,
+ :auto_cancel_pending_pipelines,
+ :build_coverage_regex,
+ :build_git_strategy,
+ :build_timeout,
+ :builds_access_level,
:ci_config_path,
+ :ci_default_git_depth,
:container_registry_enabled,
:default_branch,
:description,
- :issues_enabled,
+ :issues_access_level,
:lfs_enabled,
- :merge_requests_enabled,
+ :merge_requests_access_level,
:merge_method,
:name,
:only_allow_merge_if_all_discussions_are_resolved,
@@ -64,16 +85,28 @@ module API
:path,
:printing_merge_request_link_enabled,
:public_builds,
+ :repository_access_level,
:request_access_enabled,
+ :resolve_outdated_diff_discussions,
:shared_runners_enabled,
- :snippets_enabled,
+ :snippets_access_level,
:tag_list,
:visibility,
- :wiki_enabled,
+ :wiki_access_level,
:avatar,
- :external_authorization_classification_label
+
+ # TODO: remove in API v5, replaced by *_access_level
+ :issues_enabled,
+ :jobs_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :jobs_enabled,
+ :snippets_enabled
]
end
+
+ def filter_attributes_using_license!(attrs)
+ end
end
end
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index ff73a49d5e8..5b87eccf860 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -7,8 +7,7 @@ module API
JOB_TOKEN_PARAM = :token
def runner_registration_token_valid?
- ActiveSupport::SecurityUtils.variable_size_secure_compare(params[:token],
- Gitlab::CurrentSettings.runners_registration_token)
+ ActiveSupport::SecurityUtils.secure_compare(params[:token], Gitlab::CurrentSettings.runners_registration_token)
end
def authenticate_runner!
@@ -26,7 +25,7 @@ module API
end
def get_runner_ip
- { ip_address: env["action_dispatch.remote_ip"].to_s || request.ip }
+ { ip_address: ip_address }
end
def current_runner
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index 44c577204b8..422db5c7a50 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -462,57 +462,31 @@ module API
required: true,
name: :url,
type: String,
- desc: 'The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
+ desc: 'The base URL to the Jira instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
},
{
required: false,
name: :api_url,
type: String,
- desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
+ desc: 'The base URL to the Jira instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
},
{
required: true,
name: :username,
type: String,
- desc: 'The username of the user created to be used with GitLab/JIRA'
+ desc: 'The username of the user created to be used with GitLab/Jira'
},
{
required: true,
name: :password,
type: String,
- desc: 'The password of the user created to be used with GitLab/JIRA'
+ desc: 'The password of the user created to be used with GitLab/Jira'
},
{
required: false,
name: :jira_issue_transition_id,
type: String,
- desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
- }
- ],
- 'kubernetes' => [
- {
- required: true,
- name: :namespace,
- type: String,
- desc: 'The Kubernetes namespace to use'
- },
- {
- required: true,
- name: :api_url,
- type: String,
- desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
- },
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The service token to authenticate against the Kubernetes cluster with'
- },
- {
- required: false,
- name: :ca_pem,
- type: String,
- desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
+ desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the Jira workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
}
],
'mattermost-slash-commands' => [
@@ -683,8 +657,9 @@ module API
name: :webhook,
type: String,
desc: 'The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/…'
- }
- ],
+ },
+ chat_notification_flags
+ ].flatten,
'mattermost' => [
chat_notification_settings,
chat_notification_flags,
@@ -738,7 +713,6 @@ module API
::HipchatService,
::IrkerService,
::JiraService,
- ::KubernetesService,
::MattermostSlashCommandsService,
::SlackSlashCommandsService,
::PackagistService,
diff --git a/lib/api/helpers/variables_helpers.rb b/lib/api/helpers/variables_helpers.rb
deleted file mode 100644
index 78a92d0f5a6..00000000000
--- a/lib/api/helpers/variables_helpers.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Helpers
- module VariablesHelpers
- extend ActiveSupport::Concern
- extend Grape::API::Helpers
-
- params :optional_params_ee do
- end
- end
- end
-end
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index e7504051808..21d4928193e 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -28,7 +28,7 @@ module API
desc 'Import a GitHub project' do
detail 'This feature was introduced in GitLab 11.3.4.'
- success Entities::ProjectEntity
+ success ::ProjectEntity
end
params do
requires :personal_access_token, type: String, desc: 'GitHub personal access token'
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 039ebf92187..7819c2de515 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -96,7 +96,8 @@ module API
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
- issuable_metadata: issuable_meta_data(issues, 'Issue')
+ issuable_metadata: issuable_meta_data(issues, 'Issue', current_user),
+ include_subscribed: false
}
present issues, options
@@ -122,7 +123,8 @@ module API
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
- issuable_metadata: issuable_meta_data(issues, 'Issue')
+ issuable_metadata: issuable_meta_data(issues, 'Issue', current_user),
+ include_subscribed: false
}
present issues, options
@@ -161,7 +163,7 @@ module API
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
project: user_project,
- issuable_metadata: issuable_meta_data(issues, 'Issue')
+ issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
}
present issues, options
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index e7fed55170e..b35aa952f81 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -27,7 +27,7 @@ module API
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
- latest_build = user_project.latest_successful_build_for!(params[:job], params[:ref_name])
+ latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
present_carrierwave_file!(latest_build.artifacts_file)
end
@@ -45,7 +45,7 @@ module API
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
- build = user_project.latest_successful_build_for!(params[:job], params[:ref_name])
+ build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
path = Gitlab::Ci::Build::Artifacts::Path
.new(params[:artifact_path])
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index d729d3ee625..c183198d3c6 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -15,6 +15,8 @@ module API
success Entities::ProjectLabel
end
params do
+ optional :with_counts, type: Boolean, default: false,
+ desc: 'Include issue and merge request counts'
use :pagination
end
get ':id/labels' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 955624404f1..64ee82cd775 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -72,7 +72,7 @@ module API
if params[:view] == 'simple'
options[:with] = Entities::MergeRequestSimple
else
- options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest')
+ options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest', current_user)
end
options
@@ -397,28 +397,16 @@ module API
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end
- desc 'Merge a merge request to its default temporary merge ref path'
- params do
- optional :merge_commit_message, type: String, desc: 'Custom merge commit message'
- end
- put ':id/merge_requests/:merge_request_iid/merge_to_ref' do
+ desc 'Returns the up to date merge-ref HEAD commit'
+ get ':id/merge_requests/:merge_request_iid/merge_ref' do
merge_request = find_project_merge_request(params[:merge_request_iid])
- authorize! :admin_merge_request, user_project
-
- merge_params = {
- commit_message: params[:merge_commit_message]
- }
-
- result = ::MergeRequests::MergeToRefService
- .new(merge_request.target_project, current_user, merge_params)
- .execute(merge_request)
+ result = ::MergeRequests::MergeabilityCheckService.new(merge_request).execute(recheck: true)
- if result[:status] == :success
- present result.slice(:commit_id), 200
+ if result.success?
+ present :commit_id, result.payload.dig(:merge_ref_head, :commit_id)
else
- http_status = result[:http_status] || 400
- render_api_error!(result[:message], http_status)
+ render_api_error!(result.message, 400)
end
end
@@ -441,9 +429,10 @@ module API
authorize_push_to_merge_request!(merge_request)
- RebaseWorker.perform_async(merge_request.id, current_user.id)
+ merge_request.rebase_async(current_user.id)
status :accepted
+ present rebase_in_progress: merge_request.rebase_in_progress?
end
desc 'List issues that will be closed on merge' do
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 416cf39d3ec..9381f045144 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -15,8 +15,6 @@ module API
requires :id, type: String, desc: "The ID of a #{parent_type}"
end
resource parent_type.pluralize.to_sym, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- noteables_str = noteable_type.to_s.underscore.pluralize
-
desc "Get a list of #{noteable_type.to_s.downcase} notes" do
success Entities::Note
end
@@ -30,7 +28,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ":id/#{noteables_str}/:noteable_id/notes" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
# We exclude notes that are cross-references and that cannot be viewed
# by the current user. By doing this exclusion at this level and not
@@ -56,7 +54,7 @@ module API
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
end
get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
get_note(noteable, params[:note_id])
end
@@ -69,7 +67,7 @@ module API
optional :created_at, type: String, desc: 'The creation date of the note'
end
post ":id/#{noteables_str}/:noteable_id/notes" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
opts = {
note: params[:body],
@@ -96,7 +94,7 @@ module API
requires :body, type: String, desc: 'The content of a note'
end
put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
update_note(noteable, params[:note_id])
end
@@ -109,7 +107,7 @@ module API
requires :note_id, type: Integer, desc: 'The ID of a note'
end
delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
- noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
+ noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
delete_note(noteable, params[:note_id])
end
diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb
index 78442f465bd..4227a106a95 100644
--- a/lib/api/pages_domains.rb
+++ b/lib/api/pages_domains.rb
@@ -90,14 +90,15 @@ module API
end
params do
requires :domain, type: String, desc: 'The domain'
- optional :certificate, allow_blank: false, types: [File, String], desc: 'The certificate'
- optional :key, allow_blank: false, types: [File, String], desc: 'The key'
- all_or_none_of :certificate, :key
+ optional :certificate, allow_blank: false, types: [File, String], desc: 'The certificate', as: :user_provided_certificate
+ optional :key, allow_blank: false, types: [File, String], desc: 'The key', as: :user_provided_key
+ all_or_none_of :user_provided_certificate, :user_provided_key
end
post ":id/pages/domains" do
authorize! :update_pages, user_project
pages_domain_params = declared(params, include_parent_namespaces: false)
+
pages_domain = user_project.pages_domains.create(pages_domain_params)
if pages_domain.persisted?
@@ -110,8 +111,8 @@ module API
desc 'Updates a pages domain'
params do
requires :domain, type: String, desc: 'The domain'
- optional :certificate, allow_blank: false, types: [File, String], desc: 'The certificate'
- optional :key, allow_blank: false, types: [File, String], desc: 'The key'
+ optional :certificate, allow_blank: false, types: [File, String], desc: 'The certificate', as: :user_provided_certificate
+ optional :key, allow_blank: false, types: [File, String], desc: 'The key', as: :user_provided_key
end
put ":id/pages/domains/:domain", requirements: PAGES_DOMAINS_ENDPOINT_REQUIREMENTS do
authorize! :update_pages, user_project
@@ -119,8 +120,8 @@ module API
pages_domain_params = declared(params, include_parent_namespaces: false)
# Remove empty private key if certificate is not empty.
- if pages_domain_params[:certificate] && !pages_domain_params[:key]
- pages_domain_params.delete(:key)
+ if pages_domain_params[:user_provided_certificate] && !pages_domain_params[:user_provided_key]
+ pages_domain_params.delete(:user_provided_key)
end
if pages_domain.update(pages_domain_params)
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
index dcc8d94fb79..4f093e9be08 100644
--- a/lib/api/project_clusters.rb
+++ b/lib/api/project_clusters.rb
@@ -65,7 +65,7 @@ module API
use :create_params_ee
end
post ':id/clusters/user' do
- authorize! :add_cluster, user_project, 'Instance does not support multiple Kubernetes clusters'
+ authorize! :add_cluster, user_project
user_cluster = ::Clusters::CreateService
.new(current_user, create_cluster_user_params)
diff --git a/lib/api/container_registry.rb b/lib/api/project_container_repositories.rb
index e4493910196..6d53abcc500 100644
--- a/lib/api/container_registry.rb
+++ b/lib/api/project_container_repositories.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
module API
- class ContainerRegistry < Grape::API
+ class ProjectContainerRepositories < Grape::API
include PaginationParams
- REGISTRY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
+ REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
tag_name: API::NO_SLASH_URL_PART_REGEX)
before { error!('404 Not Found', 404) unless Feature.enabled?(:container_registry_api, user_project, default_enabled: true) }
@@ -20,11 +20,14 @@ module API
end
params do
use :pagination
+ optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included'
end
get ':id/registry/repositories' do
- repositories = user_project.container_repositories.ordered
+ repositories = ContainerRepositoriesFinder.new(
+ id: user_project.id, container_type: :project
+ ).execute
- present paginate(repositories), with: Entities::ContainerRegistry::Repository
+ present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags]
end
desc 'Delete repository' do
@@ -33,7 +36,7 @@ module API
params do
requires :repository_id, type: Integer, desc: 'The ID of the repository'
end
- delete ':id/registry/repositories/:repository_id', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ delete ':id/registry/repositories/:repository_id', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_admin_container_image!
DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id)
@@ -49,7 +52,7 @@ module API
requires :repository_id, type: Integer, desc: 'The ID of the repository'
use :pagination
end
- get ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ get ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_read_container_image!
tags = Kaminari.paginate_array(repository.tags)
@@ -65,9 +68,12 @@ module API
optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name'
optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month'
end
- delete ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ delete ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_admin_container_image!
+ message = 'This request has already been made. You can run this at most once an hour for a given container repository'
+ render_api_error!(message, 400) unless obtain_new_cleanup_container_lease
+
CleanupContainerRepositoryWorker.perform_async(current_user.id, repository.id,
declared_params.except(:repository_id)) # rubocop: disable CodeReuse/ActiveRecord
@@ -82,7 +88,7 @@ module API
requires :repository_id, type: Integer, desc: 'The ID of the repository'
requires :tag_name, type: String, desc: 'The name of the tag'
end
- get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_read_container_image!
validate_tag!
@@ -96,7 +102,7 @@ module API
requires :repository_id, type: Integer, desc: 'The ID of the repository'
requires :tag_name, type: String, desc: 'The name of the tag'
end
- delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_destroy_container_image!
validate_tag!
@@ -115,18 +121,21 @@ module API
authorize! :read_container_image, repository
end
- def authorize_update_container_image!
- authorize! :update_container_image, repository
- end
-
def authorize_destroy_container_image!
- authorize! :admin_container_image, repository
+ authorize! :destroy_container_image, repository
end
def authorize_admin_container_image!
authorize! :admin_container_image, repository
end
+ def obtain_new_cleanup_container_lease
+ Gitlab::ExclusiveLease
+ .new("container_repository:cleanup_tags:#{repository.id}",
+ timeout: 1.hour)
+ .try_obtain
+ end
+
def repository
@repository ||= user_project.container_repositories.find(params[:repository_id])
end
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index 71891e43dcc..bb1b037c08f 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -59,6 +59,7 @@ module API
}
override_params = import_params.delete(:override_params)
+ filter_attributes_using_license!(override_params) if override_params
project = ::Projects::GitlabProjectsImportService.new(
current_user, project_params, override_params
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 1e14c77b5be..996205d4b7b 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -115,6 +115,22 @@ module API
present_projects load_projects
end
+
+ desc 'Get projects starred by a user' do
+ success Entities::BasicProjectDetails
+ end
+ params do
+ requires :user_id, type: String, desc: 'The ID or username of the user'
+ use :collection_params
+ use :statistics_params
+ end
+ get ":user_id/starred_projects" do
+ user = find_user(params[:user_id])
+ not_found!('User') unless user
+
+ starred_projects = StarredProjectsFinder.new(user, params: project_finder_params, current_user: current_user).execute
+ present_projects starred_projects
+ end
end
resource :projects do
@@ -145,6 +161,7 @@ module API
post do
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
+ filter_attributes_using_license!(attrs)
project = ::Projects::CreateService.new(current_user, attrs).execute
if project.saved?
@@ -179,6 +196,7 @@ module API
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
+ filter_attributes_using_license!(attrs)
project = ::Projects::CreateService.new(user, attrs).execute
if project.saved?
@@ -292,7 +310,7 @@ module API
authorize! :change_visibility_level, user_project if attrs[:visibility].present?
attrs = translate_params_for_compatibility(attrs)
-
+ filter_attributes_using_license!(attrs)
verify_update_project_attrs!(user_project, attrs)
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
@@ -356,6 +374,19 @@ module API
end
end
+ desc 'Get the users who starred a project' do
+ success Entities::UserBasic
+ end
+ params do
+ optional :search, type: String, desc: 'Return list of users matching the search criteria'
+ use :pagination
+ end
+ get ':id/starrers' 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
::Projects::RepositoryLanguagesService
@@ -474,7 +505,7 @@ module API
authorize_admin_project
begin
- ::Projects::HousekeepingService.new(user_project).execute
+ ::Projects::HousekeepingService.new(user_project, :gc).execute
rescue ::Projects::HousekeepingService::LeaseTaken => error
conflict!(error.message)
end
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 6b17f4317db..7a3d804c30c 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -54,6 +54,7 @@ module API
requires :url, type: String
end
end
+ optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.'
end
post ':id/releases' do
authorize_create_release!
@@ -77,6 +78,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
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.'
end
put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
authorize_update_release!
diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb
index 448bef12cec..505a6c68c9c 100644
--- a/lib/api/resource_label_events.rb
+++ b/lib/api/resource_label_events.rb
@@ -26,7 +26,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
get ":id/#{eventables_str}/:eventable_id/resource_label_events" do
- eventable = find_noteable(parent_type, eventables_str, params[:eventable_id])
+ eventable = find_noteable(parent_type, params[:id], eventable_type, params[:eventable_id])
events = eventable.resource_label_events.includes(:label, :user)
present paginate(events), with: Entities::ResourceLabelEvent
@@ -42,7 +42,7 @@ module API
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
- eventable = find_noteable(parent_type, eventables_str, params[:eventable_id])
+ eventable = find_noteable(parent_type, params[:id], eventable_type, params[:eventable_id])
event = eventable.resource_label_events.find(params[:event_id])
present event, with: Entities::ResourceLabelEvent
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index f3fea463e7f..c2d371b6867 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -115,6 +115,8 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the runner'
optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES
+ optional :order_by, type: String, desc: 'Order by `id` or not', values: RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
+ optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get ':id/jobs' do
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 6767ef882cb..c36ee5af63f 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -36,10 +36,6 @@ module API
given akismet_enabled: ->(val) { val } do
requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com'
end
- optional :clientside_sentry_enabled, type: Boolean, desc: 'Sentry can also be used for reporting and logging clientside exceptions. https://sentry.io/for/javascript/'
- given clientside_sentry_enabled: ->(val) { val } do
- requires :clientside_sentry_dsn, type: String, desc: 'Clientside Sentry Data Source Name'
- end
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
@@ -59,9 +55,11 @@ module API
optional :gitaly_timeout_default, type: Integer, desc: 'Default Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
optional :gitaly_timeout_fast, type: Integer, desc: 'Gitaly fast operation timeout, in seconds. Set to 0 to disable timeouts.'
optional :gitaly_timeout_medium, type: Integer, desc: 'Medium Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :grafana_enabled, type: Boolean, desc: 'Enable Grafana'
+ optional :grafana_url, type: String, desc: 'Grafana URL'
optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
optional :help_page_hide_commercial_content, type: Boolean, desc: 'Hide marketing-related entries from help'
- optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page'
+ optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page and help dropdown'
optional :help_page_text, type: String, desc: 'Custom text displayed on the help page'
optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page'
optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)'
@@ -114,10 +112,6 @@ module API
end
optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up'
- optional :sentry_enabled, type: Boolean, desc: 'Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: https://getsentry.com'
- given sentry_enabled: ->(val) { val } do
- requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name'
- end
optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.'
optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects'
given shared_runners_enabled: ->(val) { val } do
@@ -130,6 +124,13 @@ module API
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated"
+ optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5
+ optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking'
+ given snowplow_enabled: ->(val) { val } do
+ requires :snowplow_collector_hostname, type: String, desc: 'The Snowplow collector hostname'
+ optional :snowplow_cookie_domain, type: String, desc: 'The Snowplow cookie domain'
+ optional :snowplow_site_id, type: String, desc: 'The Snowplow site name / application ic'
+ end
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
@@ -164,6 +165,11 @@ module API
attrs[:password_authentication_enabled_for_web] = attrs.delete(:password_authentication_enabled)
end
+ # support legacy names, can be removed in v5
+ if attrs.has_key?(:allow_local_requests_from_hooks_and_services)
+ attrs[:allow_local_requests_from_web_hooks_and_services] = attrs.delete(:allow_local_requests_from_hooks_and_services)
+ end
+
attrs = filter_attributes_using_license(attrs)
if ApplicationSettings::UpdateService.new(current_settings, current_user, attrs).execute
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index f5359fd316c..796b1450602 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -55,7 +55,7 @@ module API
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
- authorize_push_project
+ authorize_admin_tag
result = ::Tags::CreateService.new(user_project, current_user)
.execute(params[:tag_name], params[:ref], params[:message])
@@ -87,7 +87,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag'
end
delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
- authorize_push_project
+ authorize_admin_tag
tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index d2196f05173..404675bfaec 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -13,6 +13,13 @@ module API
'issues' => ->(iid) { find_project_issue(iid) }
}.freeze
+ helpers do
+ # EE::API::Todos would override this method
+ def find_todos
+ TodosFinder.new(current_user, params).execute
+ end
+ end
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -41,10 +48,6 @@ module API
resource :todos do
helpers do
- def find_todos
- TodosFinder.new(current_user, params).execute
- end
-
def issuable_and_awardable?(type)
obj_type = Object.const_get(type)
@@ -65,7 +68,7 @@ module API
next unless collection
targets = collection.map(&:target)
- options[type] = { issuable_metadata: issuable_meta_data(targets, type) }
+ options[type] = { issuable_metadata: issuable_meta_data(targets, type, current_user) }
end
end
end
@@ -77,7 +80,7 @@ module API
use :pagination
end
get do
- todos = paginate(find_todos.with_api_entity_associations)
+ todos = paginate(find_todos.with_entity_associations)
options = { with: Entities::Todo, current_user: current_user }
batch_load_issuable_metadata(todos, options)
@@ -107,3 +110,5 @@ module API
end
end
end
+
+API::Todos.prepend_if_ee('EE::API::Todos')
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 0e829c5699b..eeecc390256 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -112,27 +112,6 @@ module API
end
end
- desc 'Take ownership of trigger' do
- success Entities::Trigger
- end
- params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
- end
- post ':id/triggers/:trigger_id/take_ownership' do
- authenticate!
- authorize! :admin_build, user_project
-
- trigger = user_project.triggers.find(params.delete(:trigger_id))
- break not_found!('Trigger') unless trigger
-
- if trigger.update(owner: current_user)
- status :ok
- present trigger, with: Entities::Trigger, current_user: current_user
- else
- render_validation_error!(trigger)
- end
- end
-
desc 'Delete a trigger' do
success Entities::Trigger
end
diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb
new file mode 100644
index 00000000000..8df4b381bbf
--- /dev/null
+++ b/lib/api/user_counts.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module API
+ class UserCounts < Grape::API
+ resource :user_counts do
+ desc 'Return the user specific counts' do
+ detail 'Open MR Count'
+ end
+ get do
+ unauthorized! unless current_user
+
+ {
+ merge_requests: current_user.assigned_open_merge_requests_count
+ }
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 6afeebb6890..a4ac5b629b8 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -51,7 +51,7 @@ module API
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
optional :avatar, type: File, desc: 'Avatar image for user'
- optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
+ optional :private_profile, type: Boolean, default: false, desc: 'Flag indicating the user has a private profile'
all_or_none_of :extern_uid, :provider
use :optional_params_ee
@@ -148,7 +148,7 @@ module API
end
desc 'Create a user. Available only for admins.' do
- success Entities::UserPublic
+ success Entities::UserWithAdmin
end
params do
requires :email, type: String, desc: 'The email of the user'
@@ -158,6 +158,7 @@ module API
at_least_one_of :password, :reset_password
requires :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user'
+ optional :force_random_password, type: Boolean, desc: 'Flag indicating a random password will be set'
use :optional_attributes
end
post do
@@ -167,7 +168,7 @@ module API
user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true)
if user.persisted?
- present user, with: Entities::UserPublic, current_user: current_user
+ present user, with: Entities::UserWithAdmin, current_user: current_user
else
conflict!('Email has already been taken') if User
.by_any_email(user.email.downcase)
@@ -182,7 +183,7 @@ module API
end
desc 'Update a user. Available only for admins.' do
- success Entities::UserPublic
+ success Entities::UserWithAdmin
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
@@ -209,25 +210,12 @@ module API
.where.not(id: user.id).count > 0
user_params = declared_params(include_missing: false)
- identity_attrs = user_params.slice(:provider, :extern_uid)
-
- if identity_attrs.any?
- identity = user.identities.find_by(provider: identity_attrs[:provider])
-
- if identity
- identity.update(identity_attrs)
- else
- identity = user.identities.build(identity_attrs)
- identity.save
- end
- end
user_params[:password_expires_at] = Time.now if user_params[:password].present?
-
- result = ::Users::UpdateService.new(current_user, user_params.except(:extern_uid, :provider).merge(user: user)).execute
+ result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute
if result[:status] == :success
- present user, with: Entities::UserPublic, current_user: current_user
+ present user, with: Entities::UserWithAdmin, current_user: current_user
else
render_validation_error!(user)
end
diff --git a/lib/api/validations/types/labels_list.rb b/lib/api/validations/types/labels_list.rb
index 47cd83c29cf..60277b99106 100644
--- a/lib/api/validations/types/labels_list.rb
+++ b/lib/api/validations/types/labels_list.rb
@@ -10,7 +10,7 @@ module API
when String
value.split(',').map(&:strip)
when Array
- value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
+ value.flat_map { |v| v.to_s.split(',').map(&:strip) }
when LabelsList
value
else
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index af1d7936556..f022b9e665a 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -7,8 +7,6 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_project }
- helpers Helpers::VariablesHelpers
-
helpers do
def filter_variable_parameters(params)
# This method exists so that EE can more easily filter out certain
@@ -59,8 +57,7 @@ module API
optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
-
- use :optional_params_ee
+ optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
@@ -84,8 +81,7 @@ module API
optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
-
- use :optional_params_ee
+ optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do