summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
authorKamil Trzciński <ayufan@ayufan.eu>2018-02-28 20:06:12 +0100
committerKamil Trzciński <ayufan@ayufan.eu>2018-02-28 20:06:12 +0100
commit5a69b51bc870f5b42ee3406ba77de02f44ef8d32 (patch)
treec2a6e5b2c171826236b5d0f5e1ed8d02bd1554d2 /lib/api
parentb1f8d8a1739ff48412c8205f0007a2af8399d097 (diff)
parentb39d0c318921bae2e3a11df9ee6828291dad9864 (diff)
downloadgitlab-ce-5a69b51bc870f5b42ee3406ba77de02f44ef8d32.tar.gz
Merge commit 'b39d0c318921bae2e3a11df9ee6828291dad9864' into object-storage-ee-to-ce-backport
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/access_requests.rb10
-rw-r--r--lib/api/api.rb13
-rw-r--r--lib/api/api_guard.rb2
-rw-r--r--lib/api/award_emoji.rb5
-rw-r--r--lib/api/boards.rb12
-rw-r--r--lib/api/branches.rb40
-rw-r--r--lib/api/broadcast_messages.rb5
-rw-r--r--lib/api/commit_statuses.rb10
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/deploy_keys.rb5
-rw-r--r--lib/api/deployments.rb2
-rw-r--r--lib/api/entities.rb95
-rw-r--r--lib/api/environments.rb5
-rw-r--r--lib/api/events.rb2
-rw-r--r--lib/api/files.rb17
-rw-r--r--lib/api/group_milestones.rb2
-rw-r--r--lib/api/group_variables.rb5
-rw-r--r--lib/api/groups.rb34
-rw-r--r--lib/api/helpers.rb59
-rw-r--r--lib/api/helpers/internal_helpers.rb13
-rw-r--r--lib/api/helpers/members_helpers.rb4
-rw-r--r--lib/api/helpers/pagination.rb17
-rw-r--r--lib/api/helpers/runner.rb2
-rw-r--r--lib/api/internal.rb28
-rw-r--r--lib/api/issues.rb37
-rw-r--r--lib/api/job_artifacts.rb80
-rw-r--r--lib/api/jobs.rb84
-rw-r--r--lib/api/labels.rb5
-rw-r--r--lib/api/members.rb10
-rw-r--r--lib/api/merge_request_diffs.rb4
-rw-r--r--lib/api/merge_requests.rb9
-rw-r--r--lib/api/milestone_responses.rb2
-rw-r--r--lib/api/notes.rb10
-rw-r--r--lib/api/notification_settings.rb2
-rw-r--r--lib/api/pipeline_schedules.rb86
-rw-r--r--lib/api/pipelines.rb2
-rw-r--r--lib/api/project_hooks.rb5
-rw-r--r--lib/api/project_milestones.rb2
-rw-r--r--lib/api/project_snippets.rb6
-rw-r--r--lib/api/projects.rb20
-rw-r--r--lib/api/protected_branches.rb6
-rw-r--r--lib/api/repositories.rb2
-rw-r--r--lib/api/runner.rb12
-rw-r--r--lib/api/runners.rb15
-rw-r--r--lib/api/services.rb16
-rw-r--r--lib/api/settings.rb8
-rw-r--r--lib/api/snippets.rb3
-rw-r--r--lib/api/subscriptions.rb2
-rw-r--r--lib/api/system_hooks.rb3
-rw-r--r--lib/api/tags.rb15
-rw-r--r--lib/api/templates.rb6
-rw-r--r--lib/api/todos.rb2
-rw-r--r--lib/api/triggers.rb5
-rw-r--r--lib/api/users.rb187
-rw-r--r--lib/api/v3/builds.rb2
-rw-r--r--lib/api/v3/entities.rb17
-rw-r--r--lib/api/v3/merge_request_diffs.rb2
-rw-r--r--lib/api/v3/milestones.rb1
-rw-r--r--lib/api/v3/notes.rb6
-rw-r--r--lib/api/v3/projects.rb11
-rw-r--r--lib/api/v3/templates.rb6
-rw-r--r--lib/api/v3/triggers.rb32
-rw-r--r--lib/api/variables.rb3
-rw-r--r--lib/api/wikis.rb89
64 files changed, 888 insertions, 316 deletions
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index cdacf9839e5..374b611f55e 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
- resource source_type.pluralize, requirements: { id: %r{[^/]+} } do
+ resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc "Gets a list of access requests for a #{source_type}." do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::AccessRequester
@@ -67,10 +67,12 @@ module API
end
delete ":id/access_requests/:user_id" do
source = find_source(source_type, params[:id])
+ member = source.requesters.find_by!(user_id: params[:user_id])
- status 204
- ::Members::DestroyService.new(source, current_user, params)
- .execute(:requesters)
+ destroy_conditionally!(member) do
+ ::Members::DestroyService.new(source, current_user, params)
+ .execute(:requesters)
+ end
end
end
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 94df543853b..ee4e1688e12 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -2,6 +2,17 @@ module API
class API < Grape::API
include APIGuard
+ LOG_FILENAME = Rails.root.join("log", "api_json.log")
+
+ use GrapeLogging::Middleware::RequestLogger,
+ logger: Logger.new(LOG_FILENAME),
+ formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new,
+ include: [
+ GrapeLogging::Loggers::Response.new,
+ GrapeLogging::Loggers::FilterParameters.new,
+ GrapeLogging::Loggers::ClientEnv.new
+ ]
+
allow_access_with_scope :api
prefix :api
@@ -108,6 +119,7 @@ module API
mount ::API::Internal
mount ::API::Issues
mount ::API::Jobs
+ mount ::API::JobArtifacts
mount ::API::Keys
mount ::API::Labels
mount ::API::Lint
@@ -143,6 +155,7 @@ module API
mount ::API::Variables
mount ::API::GroupVariables
mount ::API::Version
+ mount ::API::Wikis
route :any, '*path' do
error!('404 Not Found', 404)
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 0d2d71e336a..c4c0fdda665 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -122,7 +122,7 @@ module API
error_classes = [MissingTokenError, TokenNotFoundError,
ExpiredError, RevokedError, InsufficientScopeError]
- base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
+ base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend
end
def oauth2_bearer_token_error_handler
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 5a028fc9d0b..c3d93996816 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -12,7 +12,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
AWARDABLES.each do |awardable_params|
awardable_string = awardable_params[:type].pluralize
awardable_id_string = "#{awardable_params[:type]}_#{awardable_params[:find_by]}"
@@ -88,8 +88,7 @@ module API
unauthorized! unless award.user == current_user || current_user.admin?
- status 204
- award.destroy
+ destroy_conditionally!(award)
end
end
end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index 5a2d7a681e3..366b0dc9a6f 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get all project boards' do
detail 'This feature was introduced in 8.13'
success Entities::Board
@@ -122,13 +122,13 @@ module API
end
delete "/lists/:list_id" do
authorize!(:admin_list, user_project)
-
list = board_lists.find(params[:list_id])
- service = ::Boards::Lists::DestroyService.new(user_project, current_user)
-
- unless service.execute(list)
- render_api_error!({ error: 'List could not be deleted!' }, 400)
+ destroy_conditionally!(list) do |list|
+ service = ::Boards::Lists::DestroyService.new(user_project, current_user)
+ unless service.execute(list)
+ render_api_error!({ error: 'List could not be deleted!' }, 400)
+ end
end
end
end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index d3dbf941298..642c1140fcc 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -24,17 +24,22 @@ module API
present paginate(branches), with: Entities::RepoBranch, project: user_project
end
- desc 'Get a single branch' do
- success Entities::RepoBranch
- end
- params do
- requires :branch, type: String, desc: 'The name of the branch'
- end
- get ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
- branch = user_project.repository.find_branch(params[:branch])
- not_found!("Branch") unless branch
+ resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
+ desc 'Get a single branch' do
+ success Entities::RepoBranch
+ end
+ params do
+ requires :branch, type: String, desc: 'The name of the branch'
+ end
+ head do
+ user_project.repository.branch_exists?(params[:branch]) ? status(204) : status(404)
+ end
+ get do
+ branch = user_project.repository.find_branch(params[:branch])
+ not_found!('Branch') unless branch
- present branch, with: Entities::RepoBranch, project: user_project
+ present branch, with: Entities::RepoBranch, project: user_project
+ end
end
# Note: This API will be deprecated in favor of the protected branches API.
@@ -125,11 +130,18 @@ module API
delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_push_project
- result = DeleteBranchService.new(user_project, current_user)
- .execute(params[:branch])
+ branch = user_project.repository.find_branch(params[:branch])
+ not_found!('Branch') unless branch
+
+ commit = user_project.repository.commit(branch.dereferenced_target)
+
+ destroy_conditionally!(commit, last_updated: commit.authored_date) do
+ result = DeleteBranchService.new(user_project, current_user)
+ .execute(params[:branch])
- if result[:status] != :success
- render_api_error!(result[:message], result[:return_code])
+ if result[:status] != :success
+ render_api_error!(result[:message], result[:return_code])
+ end
end
end
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
index 9980aec4752..d7138b2f2fe 100644
--- a/lib/api/broadcast_messages.rb
+++ b/lib/api/broadcast_messages.rb
@@ -20,7 +20,7 @@ module API
use :pagination
end
get do
- messages = BroadcastMessage.all
+ messages = BroadcastMessage.all.order_id_desc
present paginate(messages), with: Entities::BroadcastMessage
end
@@ -91,8 +91,7 @@ module API
delete ':id' do
message = find_message
- status 204
- message.destroy
+ destroy_conditionally!(message)
end
end
end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 485b680cd5f..829eef18795 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -5,7 +5,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
include PaginationParams
before { authenticate! }
@@ -74,7 +74,8 @@ module API
source: :external,
sha: commit.sha,
ref: ref,
- user: current_user)
+ user: current_user,
+ protected: @project.protected_for?(ref))
end
status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
@@ -82,7 +83,8 @@ module API
pipeline: pipeline,
name: name,
ref: ref,
- user: current_user
+ user: current_user,
+ protected: @project.protected_for?(ref)
)
optional_attributes =
@@ -101,7 +103,7 @@ module API
when 'success'
status.success!
when 'failed'
- status.drop!
+ status.drop!(:api_failure)
when 'canceled'
status.cancel!
else
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index ea78737288a..4b8d248f5f7 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -104,7 +104,7 @@ module API
not_found! 'Commit' unless commit
- commit.raw_diffs.to_a
+ present commit.raw_diffs.to_a, with: Entities::RepoDiff
end
desc "Get a commit's comments" do
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 42e7c1486b0..281269b1190 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -17,7 +17,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of the project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
before { authorize_admin_project }
desc "Get a specific project's deploy keys" do
@@ -125,8 +125,7 @@ module API
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
not_found!('Deploy Key') unless key
- status 204
- key.destroy
+ destroy_conditionally!(key)
end
end
end
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index 46b936897f6..1efee9a1324 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -8,7 +8,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get all deployments of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Deployment
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 6ba4005dd0b..52c49e5caa9 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1,11 +1,21 @@
module API
module Entities
+ class WikiPageBasic < Grape::Entity
+ expose :format
+ expose :slug
+ expose :title
+ end
+
+ class WikiPage < WikiPageBasic
+ expose :content
+ end
+
class UserSafe < Grape::Entity
- expose :name, :username
+ expose :id, :name, :username
end
class UserBasic < UserSafe
- expose :id, :state
+ expose :state
expose :avatar_url do |user, options|
user.avatar_url(only_path: false)
end
@@ -35,7 +45,7 @@ module API
expose :confirmed_at
expose :last_activity_on
expose :email
- expose :color_scheme_id, :projects_limit, :current_sign_in_at
+ expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
@@ -83,7 +93,7 @@ module API
expose :created_at, :last_activity_at
end
- class Project < BasicProjectDetails
+ class Project < BasicProjectDetails
include ::API::Helpers::RelatedResourcesHelpers
expose :_links do
@@ -119,6 +129,7 @@ module API
expose :archived?, as: :archived
expose :visibility
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
+ expose :resolve_outdated_diff_discussions
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
@@ -132,7 +143,7 @@ module API
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
expose :namespace, using: 'API::Entities::Namespace'
- expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
+ expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :avatar_url do |user, options|
@@ -290,10 +301,11 @@ module API
end
class RepoDiff < Grape::Entity
- expose :old_path, :new_path, :a_mode, :b_mode, :diff
+ expose :old_path, :new_path, :a_mode, :b_mode
expose :new_file?, as: :new_file
expose :renamed_file?, as: :renamed_file
expose :deleted_file?, as: :deleted_file
+ expose :json_safe_diff, as: :diff
end
class ProtectedRefAccess < Grape::Entity
@@ -320,7 +332,10 @@ module API
end
class IssueBasic < ProjectEntity
- expose :label_names, as: :labels
+ expose :labels do |issue, options|
+ # Avoids an N+1 query since labels are preloaded
+ issue.labels.map(&:title).sort
+ end
expose :milestone, using: Entities::Milestone
expose :assignees, :author, using: Entities::UserBasic
@@ -329,13 +344,32 @@ module API
end
expose :user_notes_count
- expose :upvotes, :downvotes
+ expose :upvotes do |issue, options|
+ if options[:issuable_metadata]
+ # Avoids an N+1 query when metadata is included
+ options[:issuable_metadata][issue.id].upvotes
+ else
+ issue.upvotes
+ end
+ end
+ expose :downvotes do |issue, options|
+ if options[:issuable_metadata]
+ # Avoids an N+1 query when metadata is included
+ options[:issuable_metadata][issue.id].downvotes
+ else
+ issue.downvotes
+ end
+ end
expose :due_date
expose :confidential
expose :web_url do |issue, options|
Gitlab::UrlBuilder.build(issue)
end
+
+ expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
+ issue
+ end
end
class Issue < IssueBasic
@@ -365,10 +399,22 @@ module API
end
class IssuableTimeStats < Grape::Entity
+ format_with(:time_tracking_formatter) do |time_spent|
+ Gitlab::TimeTrackingFormatter.output(time_spent)
+ end
+
expose :time_estimate
expose :total_time_spent
expose :human_time_estimate
- expose :human_total_time_spent
+
+ with_options(format_with: :time_tracking_formatter) do
+ expose :total_time_spent, as: :human_total_time_spent
+ end
+
+ def total_time_spent
+ # Avoids an N+1 query since timelogs are preloaded
+ object.timelogs.map(&:time_spent).sum
+ end
end
class ExternalIssue < Grape::Entity
@@ -418,6 +464,10 @@ module API
expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request)
end
+
+ expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request|
+ merge_request
+ end
end
class MergeRequest < MergeRequestBasic
@@ -453,6 +503,10 @@ module API
expose :user, using: Entities::UserPublic
end
+ class GPGKey < Grape::Entity
+ expose :id, :key, :created_at
+ end
+
class Note < Grape::Entity
# Only Issue and MergeRequest have iid
NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze
@@ -497,14 +551,24 @@ module API
expose :author, using: Entities::UserBasic
end
+ class PushEventPayload < Grape::Entity
+ expose :commit_count, :action, :ref_type, :commit_from, :commit_to
+ expose :ref, :commit_title
+ end
+
class Event < Grape::Entity
- expose :title, :project_id, :action_name
+ expose :project_id, :action_name
expose :target_id, :target_iid, :target_type, :author_id
- expose :data, :target_title
+ expose :target_title
expose :created_at
expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author }
+ expose :push_event_payload,
+ as: :push_data,
+ using: PushEventPayload,
+ if: -> (event, _) { event.push? }
+
expose :author_username do |event, options|
event.author&.username
end
@@ -531,8 +595,9 @@ module API
target_url = "namespace_project_#{target_type}_url"
target_anchor = "note_#{todo.note_id}" if todo.note_id?
- Gitlab::Routing.url_helpers.public_send(target_url,
- todo.project.namespace, todo.project, todo.target, anchor: target_anchor)
+ Gitlab::Routing
+ .url_helpers
+ .public_send(target_url, todo.project.namespace, todo.project, todo.target, anchor: target_anchor) # rubocop:disable GitlabSecurity/PublicSend
end
expose :body
@@ -726,6 +791,7 @@ module API
expose :tag_list
expose :run_untagged
expose :locked
+ expose :access_level
expose :version, :revision, :platform, :architecture
expose :contacted_at
expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.is_shared? }
@@ -769,7 +835,7 @@ module API
class Variable < Grape::Entity
expose :key, :value
- expose :protected?, as: :protected
+ expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
end
class Pipeline < PipelineBasic
@@ -790,6 +856,7 @@ module API
class PipelineScheduleDetails < PipelineSchedule
expose :last_pipeline, using: Entities::PipelineBasic
+ expose :variables, using: Entities::Variable
end
class EnvironmentBasic < Grape::Entity
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index c774a5c6685..5c63ec028d9 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get all environments of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Environment
@@ -79,8 +79,7 @@ module API
environment = user_project.environments.find(params[:environment_id])
- status 204
- environment.destroy
+ destroy_conditionally!(environment)
end
desc 'Stops an existing environment' do
diff --git a/lib/api/events.rb b/lib/api/events.rb
index dabdf579119..b0713ff1d54 100644
--- a/lib/api/events.rb
+++ b/lib/api/events.rb
@@ -67,7 +67,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc "List a Project's visible events" do
success Entities::Event
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 450334fee84..1598d3c00b8 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,5 +1,10 @@
module API
class Files < Grape::API
+ FILE_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX)
+
+ # Prevents returning plain/text responses for files with .txt extension
+ after_validation { content_type "application/json" }
+
helpers do
def commit_params(attrs)
{
@@ -55,13 +60,13 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: FILE_ENDPOINT_REQUIREMENTS do
desc 'Get raw file contents 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 commit'
end
- get ":id/repository/files/:file_path/raw" do
+ get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
send_git_blob @repo, @blob
@@ -72,7 +77,7 @@ module API
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'
end
- get ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do
+ get ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
{
@@ -92,7 +97,7 @@ module API
params do
use :extended_file_params
end
- post ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do
+ post ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project
file_params = declared_params(include_missing: false)
@@ -110,7 +115,7 @@ module API
params do
use :extended_file_params
end
- put ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do
+ put ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project
file_params = declared_params(include_missing: false)
@@ -134,7 +139,7 @@ module API
params do
use :simple_file_params
end
- delete ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do
+ delete ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project
file_params = declared_params(include_missing: false)
diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb
index b85eb59dc0a..93fa0b95857 100644
--- a/lib/api/group_milestones.rb
+++ b/lib/api/group_milestones.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups, requirements: { id: %r{[^/]+} } do
+ resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a list of group milestones' do
success Entities::Milestone
end
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index f64da4ab77b..92800ce6450 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -9,7 +9,7 @@ module API
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups, requirements: { id: %r{[^/]+} } do
+ resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get group-level variables' do
success Entities::Variable
end
@@ -88,8 +88,7 @@ module API
variable = user_group.variables.find_by(key: params[:key])
not_found!('GroupVariable') unless variable
- status 204
- variable.destroy
+ destroy_conditionally!(variable)
end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 49c3b2278c7..e817dcbbc4b 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -2,12 +2,16 @@ module API
class Groups < Grape::API
include PaginationParams
- before { authenticate! }
+ before { authenticate_non_get! }
helpers do
params :optional_params_ce do
optional :description, type: String, desc: 'The description of the group'
- optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the group'
+ optional :visibility, type: String,
+ values: Gitlab::VisibilityLevel.string_values,
+ default: Gitlab::VisibilityLevel.string_level(
+ Gitlab::CurrentSettings.current_application_settings.default_group_visibility),
+ desc: 'The visibility of the group'
optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
@@ -47,16 +51,8 @@ module API
use :pagination
end
get do
- groups = if params[:owned]
- current_user.owned_groups
- elsif current_user.admin
- Group.all
- elsif params[:all_available]
- GroupsFinder.new(current_user).execute
- else
- current_user.groups
- end
-
+ find_params = { all_available: params[:all_available], owned: params[:owned] }
+ groups = GroupsFinder.new(current_user, find_params).execute
groups = groups.search(params[:search]) if params[:search].present?
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
@@ -78,7 +74,12 @@ module API
use :optional_params
end
post do
- authorize! :create_group
+ parent_group = find_group!(params[:parent_id]) if params[:parent_id].present?
+ if parent_group
+ authorize! :create_subgroup, parent_group
+ else
+ authorize! :create_group
+ end
group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
@@ -93,7 +94,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups, requirements: { id: %r{[^/]+} } do
+ resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Update a group. Available only for users who can administrate groups.' do
success Entities::Group
end
@@ -126,8 +127,9 @@ module API
group = find_group!(params[:id])
authorize! :admin_group, group
- status 204
- ::Groups::DestroyService.new(group, current_user).execute
+ destroy_conditionally!(group) do |group|
+ ::Groups::DestroyService.new(group, current_user).execute
+ end
end
desc 'Get a list of projects in this group.' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 4056d06bcc7..6a0be74623e 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -11,6 +11,27 @@ module API
declared(params, options).to_h.symbolize_keys
end
+ def check_unmodified_since!(last_modified)
+ if_unmodified_since = Time.parse(headers['If-Unmodified-Since']) rescue nil
+
+ if if_unmodified_since && last_modified && last_modified > if_unmodified_since
+ render_api_error!('412 Precondition Failed', 412)
+ end
+ end
+
+ def destroy_conditionally!(resource, last_updated: nil)
+ last_updated ||= resource.updated_at
+
+ check_unmodified_since!(last_updated)
+
+ status 204
+ if block_given?
+ yield resource
+ else
+ resource.destroy
+ end
+ end
+
def current_user
return @current_user if defined?(@current_user)
@@ -35,6 +56,12 @@ module API
@project ||= find_project!(params[:id])
end
+ def wiki_page
+ page = user_project.wiki.find_page(params[:slug])
+
+ page || not_found!('Wiki Page')
+ end
+
def available_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute
end
@@ -66,7 +93,7 @@ module API
end
def find_group(id)
- if id =~ /^\d+$/
+ if id.to_s =~ /^\d+$/
Group.find_by(id: id)
else
Group.find_by_full_path(id)
@@ -107,6 +134,10 @@ module API
merge_request
end
+ def find_build!(id)
+ user_project.builds.find(id.to_i)
+ end
+
def authenticate!
unauthorized! unless current_user && can?(initial_current_user, :access_api)
end
@@ -139,6 +170,14 @@ module API
authorize! :admin_project, user_project
end
+ def authorize_read_builds!
+ authorize! :read_build, user_project
+ end
+
+ def authorize_update_builds!
+ authorize! :update_build, user_project
+ end
+
def require_gitlab_workhorse!
unless env['HTTP_GITLAB_WORKHORSE'].present?
forbidden!('Request should be executed via GitLab Workhorse')
@@ -189,7 +228,7 @@ module API
def bad_request!(attribute)
message = ["400 (Bad request)"]
- message << "\"" + attribute.to_s + "\" not given"
+ message << "\"" + attribute.to_s + "\" not given" if attribute
render_api_error!(message.join(' '), 400)
end
@@ -257,7 +296,15 @@ module API
message << " " << trace.join("\n ")
API.logger.add Logger::FATAL, message
- rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
+
+ response_message =
+ if Rails.env.test?
+ message
+ else
+ '500 Internal Server Error'
+ end
+
+ rack_response({ 'message' => response_message }.to_json, 500)
end
# project helpers
@@ -282,7 +329,7 @@ module API
def uploaded_file(field, uploads_path)
if params[field]
- bad_request!("#{field} is not a file") unless params[field].respond_to?(:filename)
+ bad_request!("#{field} is not a file") unless params[field][:filename]
return params[field]
end
@@ -403,6 +450,10 @@ module API
header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
end
+ def send_artifacts_entry(build, entry)
+ header(*Gitlab::Workhorse.send_artifacts_entry(build, entry))
+ end
+
# The Grape Error Middleware only has access to env but no params. We workaround this by
# defining a method that returns the right value.
def define_params_for_grape_middleware
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index ecb79317093..4c0db4d42b1 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -42,6 +42,19 @@ module API
::Users::ActivityService.new(actor, 'Git SSH').execute if commands.include?(params[:action])
end
+ def merge_request_urls
+ ::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
+ end
+
+ def redis_ping
+ result = Gitlab::Redis::SharedState.with { |redis| redis.ping }
+
+ result == 'PONG'
+ rescue => e
+ Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}")
+ false
+ end
+
private
def set_project
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index d9cae1501f8..a50ea0b52aa 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -1,8 +1,10 @@
+# rubocop:disable GitlabSecurity/PublicSend
+
module API
module Helpers
module MembersHelpers
def find_source(source_type, id)
- public_send("find_#{source_type}!", id)
+ public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend
end
def authorize_admin_source!(source_type, source)
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 0764b58fb4c..95108292aac 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -11,7 +11,7 @@ module API
def add_pagination_headers(paginated_data)
header 'X-Total', paginated_data.total_count.to_s
- header 'X-Total-Pages', paginated_data.total_pages.to_s
+ header 'X-Total-Pages', total_pages(paginated_data).to_s
header 'X-Per-Page', paginated_data.limit_value.to_s
header 'X-Page', paginated_data.current_page.to_s
header 'X-Next-Page', paginated_data.next_page.to_s
@@ -26,20 +26,25 @@ module API
links = []
- request_params[:page] = paginated_data.current_page - 1
- links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page?
+ request_params[:page] = paginated_data.prev_page
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") if request_params[:page]
- request_params[:page] = paginated_data.current_page + 1
- links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page?
+ request_params[:page] = paginated_data.next_page
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="next") if request_params[:page]
request_params[:page] = 1
links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
- request_params[:page] = paginated_data.total_pages
+ request_params[:page] = total_pages(paginated_data)
links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
links.join(', ')
end
+
+ def total_pages(paginated_data)
+ # Ensure there is in total at least 1 page
+ [paginated_data.total_pages, 1].max
+ end
end
end
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index f8645e364ce..282af32ca94 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -1,6 +1,8 @@
module API
module Helpers
module Runner
+ include Gitlab::CurrentSettings
+
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
JOB_TOKEN_PARAM = :token
UPDATE_RUNNER_EVERY = 10 * 60
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 8b007869dc3..c0fef56378f 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -68,7 +68,7 @@ module API
end
get "/merge_request_urls" do
- ::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
+ merge_request_urls
end
#
@@ -88,7 +88,8 @@ module API
{
api_version: API.version,
gitlab_version: Gitlab::VERSION,
- gitlab_rev: Gitlab::REVISION
+ gitlab_rev: Gitlab::REVISION,
+ redis: redis_ping
}
end
@@ -142,6 +143,14 @@ module API
{ success: true, recovery_codes: codes }
end
+ post '/pre_receive' do
+ status 200
+
+ reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase
+
+ { reference_counter_increased: reference_counter_increased }
+ end
+
post "/notify_post_receive" do
status 200
@@ -155,6 +164,21 @@ module API
# render_api_error!(e, 500)
# end
end
+
+ post '/post_receive' do
+ status 200
+
+ PostReceive.perform_async(params[:gl_repository], params[:identifier],
+ params[:changes])
+ broadcast_message = BroadcastMessage.current&.last&.message
+ reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
+
+ {
+ merge_request_urls: merge_request_urls,
+ broadcast_message: broadcast_message,
+ reference_counter_decreased: reference_counter_decreased
+ }
+ end
end
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 4cec1145f3a..1729df2aad0 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -4,6 +4,8 @@ module API
before { authenticate! }
+ helpers ::Gitlab::IssuableMetadata
+
helpers do
def find_issues(args = {})
args = params.merge(args)
@@ -13,6 +15,7 @@ module API
args[:label_name] = args.delete(:labels)
issues = IssuesFinder.new(current_user, args).execute
+ .preload(:assignees, :labels, :notes, :timelogs)
issues.reorder(args[:order_by] => args[:sort])
end
@@ -33,6 +36,7 @@ module API
optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`'
+ optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
use :pagination
end
@@ -65,14 +69,20 @@ module API
get do
issues = find_issues
- present paginate(issues), with: Entities::IssueBasic, current_user: current_user
+ options = {
+ with: Entities::IssueBasic,
+ current_user: current_user,
+ issuable_metadata: issuable_meta_data(issues, 'Issue')
+ }
+
+ present paginate(issues), options
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups, requirements: { id: %r{[^/]+} } do
+ resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a list of group issues' do
success Entities::IssueBasic
end
@@ -86,14 +96,20 @@ module API
issues = find_issues(group_id: group.id)
- present paginate(issues), with: Entities::IssueBasic, current_user: current_user
+ options = {
+ with: Entities::IssueBasic,
+ current_user: current_user,
+ issuable_metadata: issuable_meta_data(issues, 'Issue')
+ }
+
+ present paginate(issues), options
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
@@ -109,7 +125,14 @@ module API
issues = find_issues(project_id: project.id)
- present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
+ options = {
+ with: Entities::IssueBasic,
+ current_user: current_user,
+ project: user_project,
+ issuable_metadata: issuable_meta_data(issues, 'Issue')
+ }
+
+ present paginate(issues), options
end
desc 'Get a single project issue' do
@@ -230,8 +253,8 @@ module API
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
- status 204
- issue.destroy
+
+ destroy_conditionally!(issue)
end
desc 'List merge requests closing issue' do
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
new file mode 100644
index 00000000000..2a8fa7659bf
--- /dev/null
+++ b/lib/api/job_artifacts.rb
@@ -0,0 +1,80 @@
+module API
+ class JobArtifacts < Grape::API
+ before { authenticate_non_get! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+ desc 'Download the artifacts file from a job' do
+ detail 'This feature was introduced in GitLab 8.10'
+ end
+ params do
+ requires :ref_name, type: String, desc: 'The ref from repository'
+ requires :job, type: String, desc: 'The name for the job'
+ end
+ get ':id/jobs/artifacts/:ref_name/download',
+ requirements: { ref_name: /.+/ } do
+ authorize_read_builds!
+
+ builds = user_project.latest_successful_builds_for(params[:ref_name])
+ latest_build = builds.find_by!(name: params[:job])
+
+ present_artifacts!(latest_build.artifacts_file)
+ end
+
+ desc 'Download the artifacts file from a job' do
+ detail 'This feature was introduced in GitLab 8.5'
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a job'
+ end
+ get ':id/jobs/:job_id/artifacts' do
+ authorize_read_builds!
+
+ build = find_build!(params[:job_id])
+
+ present_artifacts!(build.artifacts_file)
+ end
+
+ desc 'Download a specific file from artifacts archive' do
+ detail 'This feature was introduced in GitLab 10.0'
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a job'
+ requires :artifact_path, type: String, desc: 'Artifact path'
+ end
+ get ':id/jobs/:job_id/artifacts/*artifact_path', format: false do
+ authorize_read_builds!
+
+ build = find_build!(params[:job_id])
+ not_found! unless build.artifacts?
+
+ path = Gitlab::Ci::Build::Artifacts::Path
+ .new(params[:artifact_path])
+ bad_request! unless path.valid?
+
+ send_artifacts_entry(build, path)
+ end
+
+ desc 'Keep the artifacts to prevent them from being deleted' do
+ success Entities::Job
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a job'
+ end
+ post ':id/jobs/:job_id/artifacts/keep' do
+ authorize_update_builds!
+
+ build = find_build!(params[:job_id])
+ authorize!(:update_build, build)
+ return not_found!(build) unless build.artifacts?
+
+ build.keep_artifacts!
+
+ status 200
+ present build, with: Entities::Job
+ end
+ end
+ end
+end
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 8a67de10bca..3c1c412ba42 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
helpers do
params :optional_scope do
optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
@@ -16,9 +16,9 @@ module API
case scope
when String
[scope]
- when Hashie::Mash
+ when ::Hash
scope.values
- when Hashie::Array
+ when ::Array
scope
else
['unknown']
@@ -66,42 +66,11 @@ module API
get ':id/jobs/:job_id' do
authorize_read_builds!
- build = get_build!(params[:job_id])
+ build = find_build!(params[:job_id])
present build, with: Entities::Job
end
- desc 'Download the artifacts file from a job' do
- detail 'This feature was introduced in GitLab 8.5'
- end
- params do
- requires :job_id, type: Integer, desc: 'The ID of a job'
- end
- get ':id/jobs/:job_id/artifacts' do
- authorize_read_builds!
-
- build = get_build!(params[:job_id])
-
- present_artifacts!(build.artifacts_file)
- end
-
- desc 'Download the artifacts file from a job' do
- detail 'This feature was introduced in GitLab 8.10'
- end
- params do
- requires :ref_name, type: String, desc: 'The ref from repository'
- requires :job, type: String, desc: 'The name for the job'
- end
- get ':id/jobs/artifacts/:ref_name/download',
- requirements: { ref_name: /.+/ } do
- authorize_read_builds!
-
- builds = user_project.latest_successful_builds_for(params[:ref_name])
- latest_build = builds.find_by!(name: params[:job])
-
- present_artifacts!(latest_build.artifacts_file)
- end
-
# TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace
# is saved in the DB instead of file). But before that, we need to consider how to replace the value of
# `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
@@ -112,7 +81,7 @@ module API
get ':id/jobs/:job_id/trace' do
authorize_read_builds!
- build = get_build!(params[:job_id])
+ build = find_build!(params[:job_id])
header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
content_type 'text/plain'
@@ -131,7 +100,7 @@ module API
post ':id/jobs/:job_id/cancel' do
authorize_update_builds!
- build = get_build!(params[:job_id])
+ build = find_build!(params[:job_id])
authorize!(:update_build, build)
build.cancel
@@ -148,7 +117,7 @@ module API
post ':id/jobs/:job_id/retry' do
authorize_update_builds!
- build = get_build!(params[:job_id])
+ build = find_build!(params[:job_id])
authorize!(:update_build, build)
return forbidden!('Job is not retryable') unless build.retryable?
@@ -166,7 +135,7 @@ module API
post ':id/jobs/:job_id/erase' do
authorize_update_builds!
- build = get_build!(params[:job_id])
+ build = find_build!(params[:job_id])
authorize!(:update_build, build)
return forbidden!('Job is not erasable!') unless build.erasable?
@@ -174,25 +143,6 @@ module API
present build, with: Entities::Job
end
- desc 'Keep the artifacts to prevent them from being deleted' do
- success Entities::Job
- end
- params do
- requires :job_id, type: Integer, desc: 'The ID of a job'
- end
- post ':id/jobs/:job_id/artifacts/keep' do
- authorize_update_builds!
-
- build = get_build!(params[:job_id])
- authorize!(:update_build, build)
- return not_found!(build) unless build.artifacts?
-
- build.keep_artifacts!
-
- status 200
- present build, with: Entities::Job
- end
-
desc 'Trigger a manual job' do
success Entities::Job
detail 'This feature was added in GitLab 8.11'
@@ -203,7 +153,7 @@ module API
post ":id/jobs/:job_id/play" do
authorize_read_builds!
- build = get_build!(params[:job_id])
+ build = find_build!(params[:job_id])
authorize!(:update_build, build)
bad_request!("Unplayable Job") unless build.playable?
@@ -216,14 +166,6 @@ module API
end
helpers do
- def find_build(id)
- user_project.builds.find_by(id: id.to_i)
- end
-
- def get_build!(id)
- find_build(id) || not_found!
- end
-
def filter_builds(builds, scope)
return builds if scope.nil? || scope.empty?
@@ -234,14 +176,6 @@ module API
builds.where(status: available_statuses && scope)
end
-
- def authorize_read_builds!
- authorize! :read_build, user_project
- end
-
- def authorize_update_builds!
- authorize! :update_build, user_project
- end
end
end
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 4520c98d951..e41a1720ac1 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get all labels of the project' do
success Entities::Label
end
@@ -56,8 +56,7 @@ module API
label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
- status 204
- label.destroy
+ destroy_conditionally!(label)
end
desc 'Update an existing label. At least one optional parameter is required.' do
diff --git a/lib/api/members.rb b/lib/api/members.rb
index bb970b7cd54..22e4bdead41 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
- resource source_type.pluralize, requirements: { id: %r{[^/]+} } do
+ resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Gets a list of group or project members viewable by the authenticated user.' do
success Entities::Member
end
@@ -93,11 +93,11 @@ module API
end
delete ":id/members/:user_id" do
source = find_source(source_type, params[:id])
- # Ensure that memeber exists
- source.members.find_by!(user_id: params[:user_id])
+ member = source.members.find_by!(user_id: params[:user_id])
- status 204
- ::Members::DestroyService.new(source, current_user, declared_params).execute
+ destroy_conditionally!(member) do
+ ::Members::DestroyService.new(source, current_user, declared_params).execute
+ end
end
end
end
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 4b79eac2b8b..95ef8f42954 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -8,7 +8,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a list of merge request diff versions' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::MergeRequestDiff
@@ -21,7 +21,7 @@ module API
get ":id/merge_requests/:merge_request_iid/versions" do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
- present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff
+ present paginate(merge_request.merge_request_diffs.order_id_desc), with: Entities::MergeRequestDiff
end
desc 'Get a single merge request diff version' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 8810d4e441d..56d72d511da 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -21,7 +21,7 @@ module API
return merge_requests if args[:view] == 'simple'
merge_requests
- .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels)
+ .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels, :timelogs)
end
params :merge_requests_params do
@@ -40,6 +40,7 @@ module API
optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID'
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`'
+ optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
use :pagination
end
end
@@ -72,7 +73,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
include TimeTrackingEndpoints
helpers do
@@ -164,8 +165,8 @@ module API
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request)
- status 204
- merge_request.destroy
+
+ destroy_conditionally!(merge_request)
end
params do
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
index ef09d9505d2..c570eace862 100644
--- a/lib/api/milestone_responses.rb
+++ b/lib/api/milestone_responses.rb
@@ -28,7 +28,7 @@ module API
end
def list_milestones_for(parent)
- milestones = parent.milestones
+ milestones = parent.milestones.order_id_desc
milestones = Milestone.filter_by_state(milestones, params[:state])
milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
milestones = filter_by_search(milestones, params[:search]) if params[:search]
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 65ff89edf65..d6e7203adaf 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
NOTEABLE_TYPES.each do |noteable_type|
noteables_str = noteable_type.to_s.underscore.pluralize
@@ -129,17 +129,19 @@ module API
end
delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
note = user_project.notes.find(params[:note_id])
+
authorize! :admin_note, note
- status 204
- ::Notes::DestroyService.new(user_project, current_user).execute(note)
+ destroy_conditionally!(note) do |note|
+ ::Notes::DestroyService.new(user_project, current_user).execute(note)
+ end
end
end
end
helpers do
def find_project_noteable(noteables_str, noteable_id)
- public_send("find_project_#{noteables_str.singularize}", noteable_id)
+ public_send("find_project_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
end
def noteable_read_ability_name(noteable)
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index 5d113c94b22..bcc0833aa5c 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -54,7 +54,7 @@ module API
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
- resource source_type.pluralize, requirements: { id: %r{[^/]+} } do
+ resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc "Get #{source_type} level notification level settings, defaults to Global" do
detail 'This feature was introduced in GitLab 8.12'
success Entities::NotificationSetting
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index dbeaf9e17ef..37f32411296 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get all pipeline schedules' do
success Entities::PipelineSchedule
end
@@ -31,10 +31,6 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
get ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
-
present pipeline_schedule, with: Entities::PipelineScheduleDetails
end
@@ -74,9 +70,6 @@ module API
optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
end
put ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.update(declared_params(include_missing: false))
@@ -93,9 +86,6 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.own!(current_user)
@@ -112,22 +102,84 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
delete ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
+ authorize! :admin_pipeline_schedule, pipeline_schedule
+
+ destroy_conditionally!(pipeline_schedule)
+ end
+
+ desc 'Create a new pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ requires :value, type: String, desc: 'The value of the variable'
+ end
+ post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
- not_found!('PipelineSchedule') unless pipeline_schedule
+ variable_params = declared_params(include_missing: false)
+ variable = pipeline_schedule.variables.create(variable_params)
+ if variable.persisted?
+ present variable, with: Entities::Variable
+ else
+ render_validation_error!(variable)
+ end
+ end
+
+ desc 'Edit a pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ optional :value, type: String, desc: 'The value of the variable'
+ end
+ put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ if pipeline_schedule_variable.update(declared_params(include_missing: false))
+ present pipeline_schedule_variable, with: Entities::Variable
+ else
+ render_validation_error!(pipeline_schedule_variable)
+ end
+ end
+
+ desc 'Delete a pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ end
+ delete ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
authorize! :admin_pipeline_schedule, pipeline_schedule
status :accepted
- present pipeline_schedule.destroy, with: Entities::PipelineScheduleDetails
+ present pipeline_schedule_variable.destroy, with: Entities::Variable
end
end
helpers do
def pipeline_schedule
@pipeline_schedule ||=
- user_project.pipeline_schedules
- .preload(:owner, :last_pipeline)
- .find_by(id: params.delete(:pipeline_schedule_id))
+ user_project
+ .pipeline_schedules
+ .preload(:owner, :last_pipeline)
+ .find_by(id: params.delete(:pipeline_schedule_id)).tap do |pipeline_schedule|
+ unless can?(current_user, :read_pipeline_schedule, pipeline_schedule)
+ not_found!('Pipeline Schedule')
+ end
+ end
+ end
+
+ def pipeline_schedule_variable
+ @pipeline_schedule_variable ||=
+ pipeline_schedule.variables.find_by(key: params[:key]).tap do |pipeline_schedule_variable|
+ unless pipeline_schedule_variable
+ not_found!('Pipeline Schedule Variable')
+ end
+ end
end
end
end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index e505cae3992..74b3376a1f3 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get all Pipelines of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::PipelineBasic
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 649dd891f56..86066e2b58f 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -24,7 +24,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get project hooks' do
success Entities::ProjectHook
end
@@ -96,8 +96,7 @@ module API
delete ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id))
- status 204
- hook.destroy
+ destroy_conditionally!(hook)
end
end
end
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index 451998c726a..0cb209a02d0 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a list of project milestones' do
success Entities::Milestone
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index f3d905b0068..2ccda1c1aa1 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
helpers do
def handle_project_member_errors(errors)
if errors[:project_access].any?
@@ -116,8 +116,8 @@ module API
not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet
- status 204
- snippet.destroy
+
+ destroy_conditionally!(snippet)
end
desc 'Get a raw project snippet'
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 89dda88d3f5..7dc19788462 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,7 +1,6 @@
require_dependency 'declarative_policy'
module API
- # Projects API
class Projects < Grape::API
include PaginationParams
@@ -17,6 +16,7 @@ module API
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 :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'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
@@ -96,7 +96,7 @@ module API
end
end
- resource :users, requirements: { user_id: %r{[^/]+} } do
+ resource :users, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a user projects' do
success Entities::BasicProjectDetails
end
@@ -184,7 +184,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a single project' do
success Entities::ProjectWithAccess
end
@@ -237,6 +237,7 @@ module API
at_least_one_of_ce =
[
:jobs_enabled,
+ :resolve_outdated_diff_discussions,
:container_registry_enabled,
:default_branch,
:description,
@@ -334,7 +335,10 @@ module API
desc 'Remove a project'
delete ":id" do
authorize! :remove_project, user_project
- ::Projects::DestroyService.new(user_project, current_user, {}).async_execute
+
+ destroy_conditionally!(user_project) do
+ ::Projects::DestroyService.new(user_project, current_user, {}).async_execute
+ end
accepted!
end
@@ -351,6 +355,8 @@ module API
if user_project.forked_from_project.nil?
user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+
+ ::Projects::ForksCountService.new(forked_from_project).refresh_cache
else
render_api_error!("Project already forked", 409)
end
@@ -361,8 +367,7 @@ module API
authorize! :remove_fork_project, user_project
if user_project.forked?
- status 204
- user_project.forked_project_link.destroy
+ destroy_conditionally!(user_project.forked_project_link)
else
not_modified!
end
@@ -406,8 +411,7 @@ module API
link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link
- status 204
- link.destroy
+ destroy_conditionally!(link)
end
desc 'Upload a file'
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index d742f2e18d0..15fcb9e8e27 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -61,7 +61,7 @@ module API
service_args = [user_project, current_user, protected_branch_params]
protected_branch = ::ProtectedBranches::CreateService.new(*service_args).execute
-
+
if protected_branch.persisted?
present protected_branch, with: Entities::ProtectedBranch, project: user_project
else
@@ -76,9 +76,7 @@ module API
delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
protected_branch = user_project.protected_branches.find_by!(name: params[:name])
- protected_branch.destroy
-
- status 204
+ destroy_conditionally!(protected_branch)
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 14d2bff9cb5..2255fb1b70d 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
helpers do
def handle_project_member_errors(errors)
if errors[:project_access].any?
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 88fc62d33df..a3987c560dd 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -45,8 +45,10 @@ module API
end
delete '/' do
authenticate_runner!
- status 204
- Ci::Runner.find_by_token(params[:token]).destroy
+
+ runner = Ci::Runner.find_by_token(params[:token])
+
+ destroy_conditionally!(runner)
end
desc 'Validates authentication credentials' do
@@ -78,7 +80,7 @@ module API
no_content! unless current_runner.active?
update_runner_info
- if current_runner.is_runner_queue_value_latest?(params[:last_update])
+ if current_runner.runner_queue_value_latest?(params[:last_update])
header 'X-GitLab-Last-Update', params[:last_update]
Gitlab::Metrics.add_event(:build_not_found_cached)
return no_content!
@@ -112,6 +114,8 @@ module API
requires :id, type: Integer, desc: %q(Job's ID)
optional :trace, type: String, desc: %q(Job's full trace)
optional :state, type: String, desc: %q(Job's status: success, failed)
+ optional :failure_reason, type: String, values: CommitStatus.failure_reasons.keys,
+ desc: %q(Job's failure_reason)
end
put '/:id' do
job = authenticate_job!
@@ -125,7 +129,7 @@ module API
when 'success'
job.success
when 'failed'
- job.drop
+ job.drop(params[:failure_reason] || :unknown_failure)
end
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 5bf5a18e42f..d3559ef71be 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -55,7 +55,9 @@ module API
optional :tag_list, type: Array[String], desc: 'The list of tags for a runner'
optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
- at_least_one_of :description, :active, :tag_list, :run_untagged, :locked
+ optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
+ desc: 'The access_level of the runner'
+ at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level
end
put ':id' do
runner = get_runner(params.delete(:id))
@@ -77,17 +79,17 @@ module API
end
delete ':id' do
runner = get_runner(params[:id])
+
authenticate_delete_runner!(runner)
- status 204
- runner.destroy!
+ destroy_conditionally!(runner)
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
before { authorize_admin_project }
desc 'Get runners available for project' do
@@ -135,8 +137,7 @@ module API
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
- status 204
- runner_project.destroy
+ destroy_conditionally!(runner_project)
end
end
@@ -153,7 +154,7 @@ module API
render_api_error!('Scope contains invalid value', 400)
end
- runners.send(scope)
+ runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
end
def get_runner(id)
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 843c05ae32e..2cbd0517dc3 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -601,7 +601,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
before { authenticate! }
before { authorize_admin_project }
@@ -656,12 +656,14 @@ module API
delete ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
- attrs = service_attributes(service).inject({}) do |hash, key|
- hash.merge!(key => nil)
- end
+ destroy_conditionally!(service) do
+ attrs = service_attributes(service).inject({}) do |hash, key|
+ hash.merge!(key => nil)
+ end
- unless service.update_attributes(attrs.merge(active: false))
- render_api_error!('400 Bad Request', 400)
+ unless service.update_attributes(attrs.merge(active: false))
+ render_api_error!('400 Bad Request', 400)
+ end
end
end
@@ -689,7 +691,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc "Trigger a slash command for #{service_slug}" do
detail 'Added in GitLab 8.13'
end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index d55a61fa638..851b226e9e5 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -29,6 +29,7 @@ module API
desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources'
optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.'
+ optional :project_export_enabled, type: Boolean, desc: 'Enable project export'
optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
@@ -121,6 +122,13 @@ module API
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
+ ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
+ optional :"#{type}_key_restriction",
+ type: Integer,
+ values: KeyRestrictionValidator.supported_key_restrictions(type),
+ desc: "Restrictions on the complexity of uploaded #{type.upcase} keys. A value of #{ApplicationSetting::FORBIDDEN_KEY_VALUE} disables all #{type.upcase} keys."
+ end
+
optional(*::ApplicationSettingsHelper.visible_attributes)
at_least_one_of(*::ApplicationSettingsHelper.visible_attributes)
end
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 35ece56c65c..00eb7c60f16 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -123,8 +123,7 @@ module API
authorize! :destroy_personal_snippet, snippet
- status 204
- snippet.destroy
+ destroy_conditionally!(snippet)
end
desc 'Get a raw snippet' do
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index 91567909998..b3e1e23031a 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -12,7 +12,7 @@ module API
requires :id, type: String, desc: 'The ID of a project'
requires :subscribable_id, type: String, desc: 'The ID of a resource'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
subscribable_types.each do |type, finder|
type_singularized = type.singularize
entity_class = Entities.const_get(type_singularized.camelcase)
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index c0179037440..6b6a03e3300 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -66,8 +66,7 @@ module API
hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook
- status 204
- hook.destroy
+ destroy_conditionally!(hook)
end
end
end
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 1333747cced..912415e3a7f 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -65,11 +65,18 @@ module API
delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
authorize_push_project
- result = ::Tags::DestroyService.new(user_project, current_user)
- .execute(params[:tag_name])
+ tag = user_project.repository.find_tag(params[:tag_name])
+ not_found!('Tag') unless tag
+
+ commit = user_project.repository.commit(tag.dereferenced_target)
+
+ destroy_conditionally!(commit, last_updated: commit.authored_date) do
+ result = ::Tags::DestroyService.new(user_project, current_user)
+ .execute(params[:tag_name])
- if result[:status] != :success
- render_api_error!(result[:message], result[:return_code])
+ if result[:status] != :success
+ render_api_error!(result[:message], result[:return_code])
+ end
end
end
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 0fc13b35d5b..f70bc0622b7 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -57,7 +57,7 @@ module API
end
get "templates/licenses" do
options = {
- featured: declared(params).popular.present? ? true : nil
+ featured: declared(params)[:popular].present? ? true : nil
}
licences = ::Kaminari.paginate_array(Licensee::License.all(options))
present paginate(licences), with: Entities::RepoLicense
@@ -71,7 +71,7 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do
- not_found!('License') unless Licensee::License.find(declared(params).name)
+ not_found!('License') unless Licensee::License.find(declared(params)[:name])
template = parsed_license_template
@@ -102,7 +102,7 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get "templates/#{template_type}/:name" do
- new_template = klass.find(declared(params).name)
+ new_template = klass.find(declared(params)[:name])
render_response(template_type, new_template)
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 55191169dd4..ffccfebe752 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -12,7 +12,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
ISSUABLE_TYPES.each do |type, finder|
type_id_str = "#{type.singularize}_iid".to_sym
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index edfdb63d183..dd6801664b1 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -5,7 +5,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Trigger a GitLab project pipeline' do
success Entities::Pipeline
end
@@ -140,8 +140,7 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
- status 204
- trigger.destroy
+ destroy_conditionally!(trigger)
end
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index a590f2692a2..1825c90a23b 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -79,22 +79,17 @@ module API
end
desc 'Get a single user' do
- success Entities::UserBasic
+ success Entities::User
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
end
get ":id" do
user = User.find_by(id: params[:id])
- not_found!('User') unless user
+ not_found!('User') unless user && can?(current_user, :read_user, user)
- if current_user && current_user.admin?
- present user, with: Entities::UserPublic
- elsif can?(current_user, :read_user, user)
- present user, with: Entities::User
- else
- render_api_error!("User not found.", 404)
- end
+ opts = current_user&.admin? ? { with: Entities::UserWithAdmin } : {}
+ present user, opts
end
desc 'Create a user. Available only for admins.' do
@@ -235,10 +230,89 @@ module API
key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
+ destroy_conditionally!(key)
+ end
+
+ desc 'Add a GPG key to a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key, type: String, desc: 'The new GPG key'
+ end
+ post ':id/gpg_keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params.delete(:id))
+ not_found!('User') unless user
+
+ key = user.gpg_keys.new(declared_params(include_missing: false))
+
+ if key.save
+ present key, with: Entities::GPGKey
+ else
+ render_validation_error!(key)
+ end
+ end
+
+ desc 'Get the GPG keys of a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/gpg_keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present paginate(user.gpg_keys), with: Entities::GPGKey
+ end
+
+ desc 'Delete an existing GPG key from a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ delete ':id/gpg_keys/:key_id' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ key = user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
status 204
key.destroy
end
+ desc 'Revokes an existing GPG key from a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ post ':id/gpg_keys/:key_id/revoke' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ key = user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ key.revoke
+ status :accepted
+ end
+
desc 'Add an email address to a specified user. Available only for admins.' do
success Entities::Email
end
@@ -292,7 +366,11 @@ module API
email = user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email
- Emails::DestroyService.new(user, email: email.email).execute
+ destroy_conditionally!(email) do |email|
+ Emails::DestroyService.new(current_user, email: email.email).execute
+ end
+
+ user.update_secondary_emails!
end
desc 'Delete a user. Available only for admins.' do
@@ -304,11 +382,13 @@ module API
end
delete ":id" do
authenticated_as_admin!
+
user = User.find_by(id: params[:id])
not_found!('User') unless user
- status 204
- user.delete_async(deleted_by: current_user, params: params)
+ destroy_conditionally!(user) do
+ user.delete_async(deleted_by: current_user, params: params)
+ end
end
desc 'Block a user. Available only for admins.'
@@ -408,8 +488,11 @@ module API
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end
delete ':impersonation_token_id' do
- status 204
- find_impersonation_token.revoke!
+ token = find_impersonation_token
+
+ destroy_conditionally!(token) do
+ token.revoke!
+ end
end
end
end
@@ -486,6 +569,75 @@ module API
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
+ destroy_conditionally!(key)
+ end
+
+ desc "Get the currently authenticated user's GPG keys" do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ use :pagination
+ end
+ get 'gpg_keys' do
+ present paginate(current_user.gpg_keys), with: Entities::GPGKey
+ end
+
+ desc 'Get a single GPG key owned by currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ get 'gpg_keys/:key_id' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ present key, with: Entities::GPGKey
+ end
+
+ desc 'Add a new GPG key to the currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :key, type: String, desc: 'The new GPG key'
+ end
+ post 'gpg_keys' do
+ key = current_user.gpg_keys.new(declared_params)
+
+ if key.save
+ present key, with: Entities::GPGKey
+ else
+ render_validation_error!(key)
+ end
+ end
+
+ desc 'Revoke a GPG key owned by currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ post 'gpg_keys/:key_id/revoke' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ key.revoke
+ status :accepted
+ end
+
+ desc 'Delete a GPG key from the currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the SSH key'
+ end
+ delete 'gpg_keys/:key_id' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
status 204
key.destroy
end
@@ -538,8 +690,11 @@ module API
email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email
- status 204
- Emails::DestroyService.new(current_user, email: email.email).execute
+ destroy_conditionally!(email) do |email|
+ Emails::DestroyService.new(current_user, email: email.email).execute
+ end
+
+ current_user.update_secondary_emails!
end
desc 'Get a list of user activities'
diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb
index 93ad9eb26b8..c189d486f50 100644
--- a/lib/api/v3/builds.rb
+++ b/lib/api/v3/builds.rb
@@ -16,7 +16,7 @@ module API
coerce_with: ->(scope) {
if scope.is_a?(String)
[scope]
- elsif scope.is_a?(Hashie::Mash)
+ elsif scope.is_a?(::Hash)
scope.values
else
['unknown']
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 773f667abe0..c928ce5265b 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -25,14 +25,24 @@ module API
expose(:downvote?) { |note| false }
end
+ class PushEventPayload < Grape::Entity
+ expose :commit_count, :action, :ref_type, :commit_from, :commit_to
+ expose :ref, :commit_title
+ end
+
class Event < Grape::Entity
- expose :title, :project_id, :action_name
+ expose :project_id, :action_name
expose :target_id, :target_type, :author_id
- expose :data, :target_title
+ expose :target_title
expose :created_at
expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
expose :author, using: ::API::Entities::UserBasic, if: ->(event, options) { event.author }
+ expose :push_event_payload,
+ as: :push_data,
+ using: PushEventPayload,
+ if: -> (event, _) { event.push? }
+
expose :author_username do |event, options|
event.author&.username
end
@@ -54,6 +64,7 @@ module API
expose :owner, using: ::API::Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace
expose :path, :path_with_namespace
+ expose :resolve_outdated_diff_discussions
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
@@ -68,7 +79,7 @@ module API
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
expose :namespace, using: 'API::Entities::Namespace'
- expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
+ expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :avatar_url do |user, options|
user.avatar_url(only_path: false)
end
diff --git a/lib/api/v3/merge_request_diffs.rb b/lib/api/v3/merge_request_diffs.rb
index 35f462e907b..22866fc2845 100644
--- a/lib/api/v3/merge_request_diffs.rb
+++ b/lib/api/v3/merge_request_diffs.rb
@@ -20,7 +20,7 @@ module API
get ":id/merge_requests/:merge_request_id/versions" do
merge_request = find_merge_request_with_access(params[:merge_request_id])
- present merge_request.merge_request_diffs, with: ::API::Entities::MergeRequestDiff
+ present merge_request.merge_request_diffs.order_id_desc, with: ::API::Entities::MergeRequestDiff
end
desc 'Get a single merge request diff version' do
diff --git a/lib/api/v3/milestones.rb b/lib/api/v3/milestones.rb
index 4c7061d4939..9be4cf9d22a 100644
--- a/lib/api/v3/milestones.rb
+++ b/lib/api/v3/milestones.rb
@@ -34,6 +34,7 @@ module API
milestones = user_project.milestones
milestones = filter_milestones_state(milestones, params[:state])
milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present?
+ milestones = milestones.order_id_desc
present paginate(milestones), with: ::API::Entities::Milestone
end
diff --git a/lib/api/v3/notes.rb b/lib/api/v3/notes.rb
index 23fe95e42e4..d49772b92f2 100644
--- a/lib/api/v3/notes.rb
+++ b/lib/api/v3/notes.rb
@@ -22,7 +22,7 @@ module API
use :pagination
end
get ":id/#{noteables_str}/:noteable_id/notes" do
- noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+ noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
if can?(current_user, noteable_read_ability_name(noteable), noteable)
# We exclude notes that are cross-references and that cannot be viewed
@@ -50,7 +50,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 = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+ noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
note = noteable.notes.find(params[:note_id])
can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
@@ -76,7 +76,7 @@ module API
noteable_id: params[:noteable_id]
}
- noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+ noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
if can?(current_user, noteable_read_ability_name(noteable), noteable)
if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index eb090453b48..7c260b8d910 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -18,6 +18,7 @@ module API
optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are 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'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.'
@@ -119,7 +120,7 @@ module API
get do
authenticate!
- present_projects current_user.authorized_projects,
+ present_projects current_user.authorized_projects.order_id_desc,
with: ::API::V3::Entities::ProjectWithAccess
end
@@ -296,9 +297,9 @@ module API
use :optional_params
at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
:wiki_enabled, :builds_enabled, :snippets_enabled,
- :shared_runners_enabled, :container_registry_enabled,
- :lfs_enabled, :public, :visibility_level, :public_builds,
- :request_access_enabled, :only_allow_merge_if_build_succeeds,
+ :shared_runners_enabled, :resolve_outdated_diff_discussions,
+ :container_registry_enabled, :lfs_enabled, :public, :visibility_level,
+ :public_builds, :request_access_enabled, :only_allow_merge_if_build_succeeds,
:only_allow_merge_if_all_discussions_are_resolved, :path,
:default_branch
end
@@ -388,6 +389,8 @@ module API
if user_project.forked_from_project.nil?
user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+
+ ::Projects::ForksCountService.new(forked_from_project).refresh_cache
else
render_api_error!("Project already forked", 409)
end
diff --git a/lib/api/v3/templates.rb b/lib/api/v3/templates.rb
index 4c577a8d2b7..2a2fb59045c 100644
--- a/lib/api/v3/templates.rb
+++ b/lib/api/v3/templates.rb
@@ -59,7 +59,7 @@ module API
end
get route do
options = {
- featured: declared(params).popular.present? ? true : nil
+ featured: declared(params)[:popular].present? ? true : nil
}
present Licensee::License.all(options), with: ::API::Entities::RepoLicense
end
@@ -76,7 +76,7 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get route, requirements: { name: /[\w\.-]+/ } do
- not_found!('License') unless Licensee::License.find(declared(params).name)
+ not_found!('License') unless Licensee::License.find(declared(params)[:name])
template = parsed_license_template
@@ -111,7 +111,7 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get route do
- new_template = klass.find(declared(params).name)
+ new_template = klass.find(declared(params)[:name])
render_response(template_type, new_template)
end
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index e9d4c35307b..534911fde5c 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -16,25 +16,31 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do
- project = find_project(params[:id])
- trigger = Ci::Trigger.find_by_token(params[:token].to_s)
- not_found! unless project && trigger
- unauthorized! unless trigger.project == project
-
# validate variables
- variables = params[:variables].to_h
- unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ params[:variables] = params[:variables].to_h
+ unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
- # create request and trigger builds
- result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables)
- pipeline = result.pipeline
+ project = find_project(params[:id])
+ not_found! unless project
+
+ result = Ci::PipelineTriggerService.new(project, nil, params).execute
+ not_found! unless result
- if pipeline.persisted?
- present result.trigger_request, with: ::API::V3::Entities::TriggerRequest
+ if result[:http_status]
+ render_api_error!(result[:message], result[:http_status])
else
- render_validation_error!(pipeline)
+ pipeline = result[:pipeline]
+
+ # We switched to Ci::PipelineVariable from Ci::TriggerRequest.variables.
+ # Ci::TriggerRequest doesn't save variables anymore.
+ # Here is copying Ci::PipelineVariable to Ci::TriggerRequest.variables for presenting the variables.
+ # The same endpoint in v4 API pressents Pipeline instead of TriggerRequest, so it doesn't need such a process.
+ trigger_request = pipeline.trigger_requests.last
+ trigger_request.variables = params[:variables]
+
+ present trigger_request, with: ::API::V3::Entities::TriggerRequest
end
end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 7c0fdd3d1be..d08876ae1b9 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -9,7 +9,7 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get project variables' do
success Entities::Variable
end
@@ -88,6 +88,7 @@ module API
variable = user_project.variables.find_by(key: params[:key])
not_found!('Variable') unless variable
+ # Variables don't have any timestamp. Therfore, destroy unconditionally.
status 204
variable.destroy
end
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
new file mode 100644
index 00000000000..b3fc4e876ad
--- /dev/null
+++ b/lib/api/wikis.rb
@@ -0,0 +1,89 @@
+module API
+ class Wikis < Grape::API
+ helpers do
+ params :wiki_page_params do
+ requires :content, type: String, desc: 'Content of a wiki page'
+ requires :title, type: String, desc: 'Title of a wiki page'
+ optional :format,
+ type: String,
+ values: ProjectWiki::MARKUPS.values.map(&:to_s),
+ default: 'markdown',
+ desc: 'Format of a wiki page. Available formats are markdown, rdoc, and asciidoc'
+ end
+ end
+
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+ desc 'Get a list of wiki pages' do
+ success Entities::WikiPageBasic
+ end
+ params do
+ optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
+ end
+ get ':id/wikis' do
+ authorize! :read_wiki, user_project
+
+ entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
+ present user_project.wiki.pages, with: entity
+ end
+
+ desc 'Get a wiki page' do
+ success Entities::WikiPage
+ end
+ params do
+ requires :slug, type: String, desc: 'The slug of a wiki page'
+ end
+ get ':id/wikis/:slug' do
+ authorize! :read_wiki, user_project
+
+ present wiki_page, with: Entities::WikiPage
+ end
+
+ desc 'Create a wiki page' do
+ success Entities::WikiPage
+ end
+ params do
+ use :wiki_page_params
+ end
+ post ':id/wikis' do
+ authorize! :create_wiki, user_project
+
+ page = WikiPages::CreateService.new(user_project, current_user, params).execute
+
+ if page.valid?
+ present page, with: Entities::WikiPage
+ else
+ render_validation_error!(page)
+ end
+ end
+
+ desc 'Update a wiki page' do
+ success Entities::WikiPage
+ end
+ params do
+ use :wiki_page_params
+ end
+ put ':id/wikis/:slug' do
+ authorize! :create_wiki, user_project
+
+ page = WikiPages::UpdateService.new(user_project, current_user, params).execute(wiki_page)
+
+ if page.valid?
+ present page, with: Entities::WikiPage
+ else
+ render_validation_error!(page)
+ end
+ end
+
+ desc 'Delete a wiki page'
+ params do
+ requires :slug, type: String, desc: 'The slug of a wiki page'
+ end
+ delete ':id/wikis/:slug' do
+ authorize! :admin_wiki, user_project
+
+ status 204
+ WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page)
+ end
+ end
+ end
+end