summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/api.rb16
-rw-r--r--lib/api/applications.rb16
-rw-r--r--lib/api/ci/pipelines.rb93
-rw-r--r--lib/api/ci/runner.rb20
-rw-r--r--lib/api/commit_statuses.rb6
-rw-r--r--lib/api/commits.rb5
-rw-r--r--lib/api/composer_packages.rb2
-rw-r--r--lib/api/conan_instance_packages.rb10
-rw-r--r--lib/api/conan_package_endpoints.rb (renamed from lib/api/conan_packages.rb)38
-rw-r--r--lib/api/conan_project_packages.rb16
-rw-r--r--lib/api/entities/issue_link.rb11
-rw-r--r--lib/api/entities/milestone.rb1
-rw-r--r--lib/api/entities/package.rb2
-rw-r--r--lib/api/entities/related_issue.rb10
-rw-r--r--lib/api/generic_packages.rb34
-rw-r--r--lib/api/github/entities.rb217
-rw-r--r--lib/api/helpers.rb20
-rw-r--r--lib/api/helpers/internal_helpers.rb2
-rw-r--r--lib/api/helpers/packages/conan/api_helpers.rb98
-rw-r--r--lib/api/helpers/packages_helpers.rb4
-rw-r--r--lib/api/helpers/packages_manager_clients_helpers.rb22
-rw-r--r--lib/api/helpers/search_helpers.rb4
-rw-r--r--lib/api/helpers/services_helpers.rb27
-rw-r--r--lib/api/helpers/snippets_helpers.rb46
-rw-r--r--lib/api/internal/base.rb12
-rw-r--r--lib/api/internal/kubernetes.rb32
-rw-r--r--lib/api/issue_links.rb82
-rw-r--r--lib/api/issues.rb13
-rw-r--r--lib/api/jobs.rb48
-rw-r--r--lib/api/maven_packages.rb11
-rw-r--r--lib/api/merge_requests.rb10
-rw-r--r--lib/api/npm_packages.rb4
-rw-r--r--lib/api/nuget_packages.rb13
-rw-r--r--lib/api/project_snippets.rb19
-rw-r--r--lib/api/pypi_packages.rb21
-rw-r--r--lib/api/search.rb4
-rw-r--r--lib/api/settings.rb7
-rw-r--r--lib/api/sidekiq_metrics.rb2
-rw-r--r--lib/api/snippets.rb22
-rw-r--r--lib/api/terraform/state.rb7
-rw-r--r--lib/api/todos.rb13
-rw-r--r--lib/api/usage_data.rb30
-rw-r--r--lib/api/users.rb12
-rw-r--r--lib/api/v3/github.rb232
-rw-r--r--lib/api/variables.rb1
-rw-r--r--lib/api/wikis.rb13
46 files changed, 1114 insertions, 214 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 2be6792af5f..b37751e1b47 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -167,6 +167,7 @@ module API
mount ::API::GroupVariables
mount ::API::ImportBitbucketServer
mount ::API::ImportGithub
+ mount ::API::IssueLinks
mount ::API::Issues
mount ::API::JobArtifacts
mount ::API::Jobs
@@ -193,9 +194,11 @@ module API
mount ::API::NugetPackages
mount ::API::PypiPackages
mount ::API::ComposerPackages
- mount ::API::ConanPackages
+ mount ::API::ConanProjectPackages
+ mount ::API::ConanInstancePackages
mount ::API::MavenPackages
mount ::API::NpmPackages
+ mount ::API::GenericPackages
mount ::API::GoProxy
mount ::API::Pages
mount ::API::PagesDomains
@@ -233,6 +236,7 @@ module API
mount ::API::Templates
mount ::API::Todos
mount ::API::Triggers
+ mount ::API::UsageData
mount ::API::UserCounts
mount ::API::Users
mount ::API::Variables
@@ -244,6 +248,16 @@ module API
mount ::API::Internal::Pages
mount ::API::Internal::Kubernetes
+ version 'v3', using: :path do
+ # Although the following endpoints are kept behind V3 namespace,
+ # they're not deprecated neither should be removed when V3 get
+ # removed. They're needed as a layer to integrate with Jira
+ # Development Panel.
+ namespace '/', requirements: ::API::V3::Github::ENDPOINT_REQUIREMENTS do
+ mount ::API::V3::Github
+ end
+ end
+
route :any, '*path' do
error!('404 Not Found', 404)
end
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
index 4e8d68c8d09..4f2c3ee79ef 100644
--- a/lib/api/applications.rb
+++ b/lib/api/applications.rb
@@ -6,6 +6,15 @@ module API
before { authenticated_as_admin! }
resource :applications do
+ helpers do
+ def validate_redirect_uri(value)
+ uri = ::URI.parse(value)
+ !uri.is_a?(URI::HTTP) || uri.host
+ rescue URI::InvalidURIError
+ false
+ end
+ end
+
desc 'Create a new application' do
detail 'This feature was introduced in GitLab 10.5'
success Entities::ApplicationWithSecret
@@ -19,6 +28,13 @@ module API
desc: 'Application will be used where the client secret is confidential'
end
post do
+ # Validate that host in uri is specified
+ # Please remove it when https://github.com/doorkeeper-gem/doorkeeper/pull/1440 is merged
+ # and the doorkeeper gem version is bumped
+ unless validate_redirect_uri(declared_params[:redirect_uri])
+ render_api_error!({ redirect_uri: ["must be an absolute URI."] }, :bad_request)
+ end
+
application = Doorkeeper::Application.new(declared_params)
if application.save
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
index a010e0dd761..045f81074a7 100644
--- a/lib/api/ci/pipelines.rb
+++ b/lib/api/ci/pipelines.rb
@@ -15,6 +15,24 @@ module API
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Ci::PipelineBasic
end
+
+ helpers do
+ params :optional_scope do
+ optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
+ values: ::CommitStatus::AVAILABLE_STATUSES,
+ coerce_with: ->(scope) {
+ case scope
+ when String
+ [scope]
+ when ::Array
+ scope
+ else
+ ['unknown']
+ end
+ }
+ end
+ end
+
params do
use :pagination
optional :scope, type: String, values: %w[running pending finished branches tags],
@@ -96,6 +114,64 @@ module API
present pipeline, with: Entities::Ci::Pipeline
end
+ desc 'Get pipeline jobs' do
+ success Entities::Ci::Job
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ use :optional_scope
+ use :pagination
+ end
+
+ get ':id/pipelines/:pipeline_id/jobs' do
+ authorize!(:read_pipeline, user_project)
+
+ pipeline = user_project.all_pipelines.find(params[:pipeline_id])
+
+ if Feature.enabled?(:ci_jobs_finder_refactor)
+ builds = ::Ci::JobsFinder
+ .new(current_user: current_user, pipeline: pipeline, params: params)
+ .execute
+ else
+ authorize!(:read_build, pipeline)
+ builds = pipeline.builds
+ builds = filter_builds(builds, params[:scope])
+ end
+
+ builds = builds.with_preloads
+
+ present paginate(builds), with: Entities::Ci::Job
+ end
+
+ desc 'Get pipeline bridge jobs' do
+ success Entities::Ci::Bridge
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ use :optional_scope
+ use :pagination
+ end
+
+ get ':id/pipelines/:pipeline_id/bridges' do
+ authorize!(:read_build, user_project)
+
+ pipeline = user_project.all_pipelines.find(params[:pipeline_id])
+
+ if Feature.enabled?(:ci_jobs_finder_refactor)
+ bridges = ::Ci::JobsFinder
+ .new(current_user: current_user, pipeline: pipeline, params: params, type: ::Ci::Bridge)
+ .execute
+ else
+ authorize!(:read_pipeline, pipeline)
+ bridges = pipeline.bridges
+ bridges = filter_builds(bridges, params[:scope])
+ end
+
+ bridges = bridges.with_preloads
+
+ present paginate(bridges), with: Entities::Ci::Bridge
+ end
+
desc 'Gets the variables for a given pipeline' do
detail 'This feature was introduced in GitLab 11.11'
success Entities::Ci::Variable
@@ -170,6 +246,21 @@ module API
end
helpers do
+ # NOTE: This method should be removed once the ci_jobs_finder_refactor FF is
+ # removed. https://gitlab.com/gitlab-org/gitlab/-/issues/245183
+ # rubocop: disable CodeReuse/ActiveRecord
+ def filter_builds(builds, scope)
+ return builds if scope.nil? || scope.empty?
+
+ available_statuses = ::CommitStatus::AVAILABLE_STATUSES
+
+ unknown = scope - available_statuses
+ render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?
+
+ builds.where(status: scope)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def pipeline
strong_memoize(:pipeline) do
user_project.all_pipelines.find(params[:pipeline_id])
@@ -178,7 +269,7 @@ module API
def latest_pipeline
strong_memoize(:latest_pipeline) do
- user_project.latest_pipeline_for_ref(params[:ref])
+ user_project.latest_pipeline(params[:ref])
end
end
end
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index 31be1bb7e3e..08903dce3dc 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -159,29 +159,29 @@ module API
end
desc 'Updates a job' do
- http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
+ http_codes [[200, 'Job was updated'],
+ [202, 'Update accepted'],
+ [400, 'Unknown parameters'],
+ [403, 'Forbidden']]
end
params do
requires :token, type: String, desc: %q(Runners's authentication token)
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 :checksum, type: String, desc: %q(Job's trace CRC32 checksum)
optional :failure_reason, type: String, desc: %q(Job's failure_reason)
end
put '/:id' do
job = authenticate_job!
- job.trace.set(params[:trace]) if params[:trace]
-
Gitlab::Metrics.add_event(:update_build)
- case params[:state].to_s
- when 'running'
- job.touch if job.needs_touch?
- when 'success'
- job.success!
- when 'failed'
- job.drop!(params[:failure_reason] || :unknown_failure)
+ service = ::Ci::UpdateBuildStateService
+ .new(job, declared_params(include_missing: false))
+
+ service.execute.then do |result|
+ status result.status
end
end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 140351c9e5c..9f5a6e87505 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -117,8 +117,10 @@ module API
render_api_error!('invalid state', 400)
end
- MergeRequest.where(source_project: user_project, source_branch: ref)
- .update_all(head_pipeline_id: pipeline.id) if pipeline.latest?
+ if pipeline.latest?
+ MergeRequest.where(source_project: user_project, source_branch: ref)
+ .update_all(head_pipeline_id: pipeline.id)
+ end
present status, with: Entities::CommitStatus
rescue StateMachines::InvalidTransition => e
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 3c7ed2a25a0..20877fb5c5f 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -136,7 +136,10 @@ module API
if result[:status] == :success
commit_detail = user_project.repository.commit(result[:result])
- Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count if find_user_from_warden
+ if find_user_from_warden
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count
+ Gitlab::UsageDataCounters::EditorUniqueCounter.track_web_ide_edit_action(author: current_user)
+ end
present commit_detail, with: Entities::CommitDetail, stats: params[:stats]
else
diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb
index 05887e58425..31d097c4bea 100644
--- a/lib/api/composer_packages.rb
+++ b/lib/api/composer_packages.rb
@@ -123,7 +123,7 @@ module API
bad_request!
end
- track_event('push_package')
+ package_event('push_package')
::Packages::Composer::CreatePackageService
.new(authorized_user_project, current_user, declared_params)
diff --git a/lib/api/conan_instance_packages.rb b/lib/api/conan_instance_packages.rb
new file mode 100644
index 00000000000..209748d79fa
--- /dev/null
+++ b/lib/api/conan_instance_packages.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# Conan Instance-Level Package Manager Client API
+module API
+ class ConanInstancePackages < Grape::API::Instance
+ namespace 'packages/conan/v1' do
+ include ConanPackageEndpoints
+ end
+ end
+end
diff --git a/lib/api/conan_packages.rb b/lib/api/conan_package_endpoints.rb
index 7f2afea9931..445447cfcd2 100644
--- a/lib/api/conan_packages.rb
+++ b/lib/api/conan_package_endpoints.rb
@@ -9,8 +9,8 @@
#
# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
module API
- class ConanPackages < Grape::API::Instance
- helpers ::API::Helpers::PackagesManagerClientsHelpers
+ module ConanPackageEndpoints
+ extend ActiveSupport::Concern
PACKAGE_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX,
@@ -28,15 +28,19 @@ module API
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
- before do
- require_packages_enabled!
+ included do
+ helpers ::API::Helpers::PackagesManagerClientsHelpers
+ helpers ::API::Helpers::Packages::Conan::ApiHelpers
+ helpers ::API::Helpers::RelatedResourcesHelpers
- # Personal access token will be extracted from Bearer or Basic authorization
- # in the overridden find_personal_access_token or find_user_from_job_token helpers
- authenticate!
- end
+ before do
+ require_packages_enabled!
+
+ # Personal access token will be extracted from Bearer or Basic authorization
+ # in the overridden find_personal_access_token or find_user_from_job_token helpers
+ authenticate!
+ end
- namespace 'packages/conan/v1' do
desc 'Ping the Conan API' do
detail 'This feature was introduced in GitLab 12.2'
end
@@ -115,7 +119,7 @@ module API
authorize!(:read_package, project)
presenter = ::Packages::Conan::PackagePresenter.new(
- recipe,
+ package,
current_user,
project,
conan_package_reference: params[:conan_package_reference]
@@ -133,7 +137,7 @@ module API
get do
authorize!(:read_package, project)
- presenter = ::Packages::Conan::PackagePresenter.new(recipe, current_user, project)
+ presenter = ::Packages::Conan::PackagePresenter.new(package, current_user, project)
present presenter, with: ::API::Entities::ConanPackage::ConanRecipeSnapshot
end
@@ -242,7 +246,7 @@ module API
delete do
authorize!(:destroy_package, project)
- track_event('delete_package')
+ package_event('delete_package', category: 'API::ConanPackages')
package.destroy
end
@@ -295,7 +299,7 @@ module API
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize' do
- authorize_workhorse!(subject: project)
+ authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end
end
@@ -322,7 +326,7 @@ module API
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize' do
- authorize_workhorse!(subject: project)
+ authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end
desc 'Upload package files' do
@@ -341,11 +345,5 @@ module API
end
end
end
-
- helpers do
- include Gitlab::Utils::StrongMemoize
- include ::API::Helpers::RelatedResourcesHelpers
- include ::API::Helpers::Packages::Conan::ApiHelpers
- end
end
end
diff --git a/lib/api/conan_project_packages.rb b/lib/api/conan_project_packages.rb
new file mode 100644
index 00000000000..c51992231a7
--- /dev/null
+++ b/lib/api/conan_project_packages.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# Conan Project-Level Package Manager Client API
+module API
+ class ConanProjectPackages < Grape::API::Instance
+ params do
+ requires :id, type: Integer, desc: 'The ID of a project', regexp: %r{\A[1-9]\d*\z}
+ end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ namespace ':id/packages/conan/v1' do
+ include ConanPackageEndpoints
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/issue_link.rb b/lib/api/entities/issue_link.rb
new file mode 100644
index 00000000000..8e24b046325
--- /dev/null
+++ b/lib/api/entities/issue_link.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class IssueLink < Grape::Entity
+ expose :source, as: :source_issue, using: ::API::Entities::IssueBasic
+ expose :target, as: :target_issue, using: ::API::Entities::IssueBasic
+ expose :link_type
+ end
+ end
+end
diff --git a/lib/api/entities/milestone.rb b/lib/api/entities/milestone.rb
index 5a0c222d691..b191210a234 100644
--- a/lib/api/entities/milestone.rb
+++ b/lib/api/entities/milestone.rb
@@ -10,6 +10,7 @@ module API
expose :state, :created_at, :updated_at
expose :due_date
expose :start_date
+ expose :expired?, as: :expired
expose :web_url do |milestone, _options|
Gitlab::UrlBuilder.build(milestone)
diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb
index 670965b225c..d903f50befa 100644
--- a/lib/api/entities/package.rb
+++ b/lib/api/entities/package.rb
@@ -28,7 +28,7 @@ module API
expose :pipeline, if: ->(package) { package.build_info }, using: Package::Pipeline
- expose :versions, using: ::API::Entities::PackageVersion
+ expose :versions, using: ::API::Entities::PackageVersion, unless: ->(_, opts) { opts[:collection] }
private
diff --git a/lib/api/entities/related_issue.rb b/lib/api/entities/related_issue.rb
new file mode 100644
index 00000000000..491c606bd49
--- /dev/null
+++ b/lib/api/entities/related_issue.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class RelatedIssue < ::API::Entities::Issue
+ expose :issue_link_id
+ expose :issue_link_type, as: :link_type
+ end
+ end
+end
diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb
new file mode 100644
index 00000000000..98b8a40c7c9
--- /dev/null
+++ b/lib/api/generic_packages.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module API
+ class GenericPackages < Grape::API::Instance
+ before do
+ require_packages_enabled!
+ authenticate!
+
+ require_generic_packages_available!
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ route_setting :authentication, job_token_allowed: true
+
+ namespace ':id/packages/generic' do
+ get 'ping' do
+ :pong
+ end
+ end
+ end
+
+ helpers do
+ include ::API::Helpers::PackagesHelpers
+
+ def require_generic_packages_available!
+ not_found! unless Feature.enabled?(:generic_packages, user_project)
+ end
+ end
+ end
+end
diff --git a/lib/api/github/entities.rb b/lib/api/github/entities.rb
new file mode 100644
index 00000000000..c28a0b8eb7e
--- /dev/null
+++ b/lib/api/github/entities.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: true
+
+# Simplified version of Github API entities.
+# It's mainly used to mimic Github API and integrate with Jira Development Panel.
+#
+module API
+ module Github
+ module Entities
+ class Repository < Grape::Entity
+ expose :id
+ expose :owner do |project, options|
+ root_namespace = options[:root_namespace] || project.root_namespace
+
+ { login: root_namespace.path }
+ end
+ expose :name do |project, options|
+ ::Gitlab::Jira::Dvcs.encode_project_name(project)
+ end
+ end
+
+ class BranchCommit < Grape::Entity
+ expose :id, as: :sha
+ expose :type do |_|
+ 'commit'
+ end
+ end
+
+ class RepoCommit < Grape::Entity
+ expose :id, as: :sha
+ expose :author do |commit|
+ {
+ login: commit.author&.username,
+ email: commit.author_email
+ }
+ end
+ expose :committer do |commit|
+ {
+ login: commit.author&.username,
+ email: commit.committer_email
+ }
+ end
+ expose :commit do |commit|
+ {
+ author: {
+ name: commit.author_name,
+ email: commit.author_email,
+ date: commit.authored_date.iso8601,
+ type: 'User'
+ },
+ committer: {
+ name: commit.committer_name,
+ email: commit.committer_email,
+ date: commit.committed_date.iso8601,
+ type: 'User'
+ },
+ message: commit.safe_message
+ }
+ end
+ expose :parents do |commit|
+ commit.parent_ids.map { |id| { sha: id } }
+ end
+ expose :files do |commit|
+ commit.diffs.diff_files.flat_map do |diff|
+ additions = diff.added_lines
+ deletions = diff.removed_lines
+
+ if diff.new_file?
+ {
+ status: 'added',
+ filename: diff.new_path,
+ additions: additions,
+ changes: additions
+ }
+ elsif diff.deleted_file?
+ {
+ status: 'removed',
+ filename: diff.old_path,
+ deletions: deletions,
+ changes: deletions
+ }
+ elsif diff.renamed_file?
+ [
+ {
+ status: 'removed',
+ filename: diff.old_path,
+ deletions: deletions,
+ changes: deletions
+ },
+ {
+ status: 'added',
+ filename: diff.new_path,
+ additions: additions,
+ changes: additions
+ }
+ ]
+ else
+ {
+ status: 'modified',
+ filename: diff.new_path,
+ additions: additions,
+ deletions: deletions,
+ changes: (additions + deletions)
+ }
+ end
+ end
+ end
+ end
+
+ class Branch < Grape::Entity
+ expose :name
+
+ expose :commit, using: BranchCommit do |repo_branch, options|
+ options[:project].repository.commit(repo_branch.dereferenced_target)
+ end
+ end
+
+ class User < Grape::Entity
+ expose :id
+ expose :username, as: :login
+ expose :user_url, as: :url
+ expose :user_url, as: :html_url
+ expose :avatar_url
+
+ private
+
+ def user_url
+ Gitlab::Routing.url_helpers.user_url(object)
+ end
+ end
+
+ class NoteableComment < Grape::Entity
+ expose :id
+ expose :author, as: :user, using: User
+ expose :note, as: :body
+ expose :created_at
+ end
+
+ class PullRequest < Grape::Entity
+ expose :title
+ expose :assignee, using: User do |merge_request|
+ merge_request.assignee
+ end
+ expose :author, as: :user, using: User
+ expose :created_at
+ expose :description, as: :body
+ # Since Jira service requests `/repos/-/jira/pulls` (without project
+ # scope), we need to make it work with ID instead IID.
+ expose :id, as: :number
+ # GitHub doesn't have a "merged" or "closed" state. It's just "open" or
+ # "closed".
+ expose :state do |merge_request|
+ case merge_request.state
+ when 'opened', 'locked'
+ 'open'
+ when 'merged'
+ 'closed'
+ else
+ merge_request.state
+ end
+ end
+ expose :merged?, as: :merged
+ expose :merged_at do |merge_request|
+ merge_request.metrics&.merged_at
+ end
+ expose :closed_at do |merge_request|
+ merge_request.metrics&.latest_closed_at
+ end
+ expose :updated_at
+ expose :html_url do |merge_request|
+ Gitlab::UrlBuilder.build(merge_request)
+ end
+ expose :head do
+ expose :source_branch, as: :label
+ expose :source_branch, as: :ref
+ expose :source_project, as: :repo, using: Repository
+ end
+ expose :base do
+ expose :target_branch, as: :label
+ expose :target_branch, as: :ref
+ expose :target_project, as: :repo, using: Repository
+ end
+ end
+
+ class PullRequestPayload < Grape::Entity
+ expose :action do |merge_request|
+ case merge_request.state
+ when 'merged', 'closed'
+ 'closed'
+ else
+ 'opened'
+ end
+ end
+
+ expose :id
+ expose :pull_request, using: PullRequest do |merge_request|
+ merge_request
+ end
+ end
+
+ class PullRequestEvent < Grape::Entity
+ expose :id do |merge_request|
+ updated_at = merge_request.updated_at.to_i
+ "#{merge_request.id}-#{updated_at}"
+ end
+ expose :type do |_merge_request|
+ 'PullRequestEvent'
+ end
+ expose :updated_at, as: :created_at
+ expose :payload, using: PullRequestPayload do |merge_request|
+ # The merge request data is used by PullRequestPayload and PullRequest, so we just provide it
+ # here. Otherwise Grape::Entity would try to access a field "payload" on Merge Request.
+ merge_request
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 599d5bd0baf..1912a06682e 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -532,11 +532,29 @@ module API
::Gitlab::Tracking.event(category, action.to_s, **args)
rescue => error
- Rails.logger.warn( # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.warn(
"Tracking event failed for action: #{action}, category: #{category}, message: #{error.message}"
)
end
+ # @param event_name [String] the event name
+ # @param values [Array|String] the values counted
+ def increment_unique_values(event_name, values)
+ return unless values.present?
+
+ feature_name = "usage_data_#{event_name}"
+ return unless Feature.enabled?(feature_name)
+ return unless Gitlab::CurrentSettings.usage_ping_enabled?
+
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event(values, event_name)
+ rescue => error
+ Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}")
+ end
+
+ def with_api_params(&block)
+ yield({ api: true, request: request })
+ end
+
protected
def project_finder_params_visibility_ce
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index b7ce1eba3f9..69b53ea6c2f 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -67,7 +67,7 @@ module API
result == 'PONG'
rescue => e
- Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}")
false
end
diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb
index 1161d1386bb..dcbf933a4e1 100644
--- a/lib/api/helpers/packages/conan/api_helpers.rb
+++ b/lib/api/helpers/packages/conan/api_helpers.rb
@@ -5,11 +5,13 @@ module API
module Packages
module Conan
module ApiHelpers
+ include Gitlab::Utils::StrongMemoize
+
def present_download_urls(entity)
authorize!(:read_package, project)
presenter = ::Packages::Conan::PackagePresenter.new(
- recipe,
+ package,
current_user,
project,
conan_package_reference: params[:conan_package_reference]
@@ -31,7 +33,7 @@ module API
def recipe_upload_urls
{ upload_urls: Hash[
file_names.select(&method(:recipe_file?)).map do |file_name|
- [file_name, recipe_file_upload_url(file_name)]
+ [file_name, build_recipe_file_upload_url(file_name)]
end
] }
end
@@ -39,7 +41,7 @@ module API
def package_upload_urls
{ upload_urls: Hash[
file_names.select(&method(:package_file?)).map do |file_name|
- [file_name, package_file_upload_url(file_name)]
+ [file_name, build_package_file_upload_url(file_name)]
end
] }
end
@@ -52,32 +54,58 @@ module API
file_name.in?(::Packages::Conan::FileMetadatum::PACKAGE_FILES)
end
- def package_file_upload_url(file_name)
- expose_url(
- api_v4_packages_conan_v1_files_package_path(
- package_name: params[:package_name],
- package_version: params[:package_version],
- package_username: params[:package_username],
- package_channel: params[:package_channel],
- recipe_revision: '0',
- conan_package_reference: params[:conan_package_reference],
- package_revision: '0',
- file_name: file_name
- )
+ def build_package_file_upload_url(file_name)
+ options = url_options(file_name).merge(
+ conan_package_reference: params[:conan_package_reference],
+ package_revision: ::Packages::Conan::FileMetadatum::DEFAULT_PACKAGE_REVISION
)
+
+ package_file_url(options)
+ end
+
+ def build_recipe_file_upload_url(file_name)
+ recipe_file_url(url_options(file_name))
end
- def recipe_file_upload_url(file_name)
- expose_url(
- api_v4_packages_conan_v1_files_export_path(
- package_name: params[:package_name],
- package_version: params[:package_version],
- package_username: params[:package_username],
- package_channel: params[:package_channel],
- recipe_revision: '0',
- file_name: file_name
+ def url_options(file_name)
+ {
+ package_name: params[:package_name],
+ package_version: params[:package_version],
+ package_username: params[:package_username],
+ package_channel: params[:package_channel],
+ file_name: file_name,
+ recipe_revision: ::Packages::Conan::FileMetadatum::DEFAULT_RECIPE_REVISION
+ }
+ end
+
+ def package_file_url(options)
+ case package_scope
+ when :project
+ expose_url(
+ api_v4_projects_packages_conan_v1_files_package_path(
+ options.merge(id: project.id)
+ )
)
- )
+ when :instance
+ expose_url(
+ api_v4_packages_conan_v1_files_package_path(options)
+ )
+ end
+ end
+
+ def recipe_file_url(options)
+ case package_scope
+ when :project
+ expose_url(
+ api_v4_projects_packages_conan_v1_files_export_path(
+ options.merge(id: project.id)
+ )
+ )
+ when :instance
+ expose_url(
+ api_v4_packages_conan_v1_files_export_path(options)
+ )
+ end
end
def recipe
@@ -86,16 +114,23 @@ module API
def project
strong_memoize(:project) do
- full_path = ::Packages::Conan::Metadatum.full_path_from(package_username: params[:package_username])
- Project.find_by_full_path(full_path)
+ case package_scope
+ when :project
+ find_project!(params[:id])
+ when :instance
+ full_path = ::Packages::Conan::Metadatum.full_path_from(package_username: params[:package_username])
+ find_project!(full_path)
+ end
end
end
def package
strong_memoize(:package) do
project.packages
+ .conan
.with_name(params[:package_name])
.with_version(params[:package_version])
+ .with_conan_username(params[:package_username])
.with_conan_channel(params[:package_channel])
.order_created
.last
@@ -123,7 +158,7 @@ module API
conan_package_reference: params[:conan_package_reference]
).execute!
- track_event('pull_package') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY
+ package_event('pull_package', category: 'API::ConanPackages') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY
present_carrierwave_file!(package_file.file)
end
@@ -134,7 +169,7 @@ module API
def track_push_package_event
if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate
- track_event('push_package')
+ package_event('push_package', category: 'API::ConanPackages')
end
end
@@ -155,6 +190,7 @@ module API
def upload_package_file(file_type)
authorize_upload!(project)
+ bad_request!('File is too large') if project.actual_limits.exceeded?(:conan_max_file_size, params['file.size'].to_i)
current_package = find_or_create_package
@@ -234,6 +270,10 @@ module API
token
end
+
+ def package_scope
+ params[:id].present? ? :project : :instance
+ end
end
end
end
diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb
index c6037d52de9..403f5ea3851 100644
--- a/lib/api/helpers/packages_helpers.rb
+++ b/lib/api/helpers/packages_helpers.rb
@@ -47,6 +47,10 @@ module API
authorize_create_package!(subject)
require_gitlab_workhorse!
end
+
+ def package_event(event_name, **args)
+ track_event(event_name, **args)
+ end
end
end
end
diff --git a/lib/api/helpers/packages_manager_clients_helpers.rb b/lib/api/helpers/packages_manager_clients_helpers.rb
index 955d21cb44f..e7662b03577 100644
--- a/lib/api/helpers/packages_manager_clients_helpers.rb
+++ b/lib/api/helpers/packages_manager_clients_helpers.rb
@@ -6,18 +6,8 @@ module API
extend Grape::API::Helpers
include ::API::Helpers::PackagesHelpers
- params :workhorse_upload_params do
- optional 'file.path', type: String, desc: 'Path to locally stored body (generated by Workhorse)'
- optional 'file.name', type: String, desc: 'Real filename as send in Content-Disposition (generated by Workhorse)'
- optional 'file.type', type: String, desc: 'Real content type as send in Content-Type (generated by Workhorse)'
- optional 'file.size', type: Integer, desc: 'Real size of file (generated by Workhorse)'
- optional 'file.md5', type: String, desc: 'MD5 checksum of the file (generated by Workhorse)'
- optional 'file.sha1', type: String, desc: 'SHA1 checksum of the file (generated by Workhorse)'
- optional 'file.sha256', type: String, desc: 'SHA256 checksum of the file (generated by Workhorse)'
- end
-
def find_job_from_http_basic_auth
- return unless headers
+ return unless request.headers
token = decode_token
@@ -27,7 +17,7 @@ module API
end
def find_deploy_token_from_http_basic_auth
- return unless headers
+ return unless request.headers
token = decode_token
@@ -36,16 +26,10 @@ module API
DeployToken.active.find_by_token(token)
end
- def uploaded_package_file(param_name = :file)
- uploaded_file = UploadedFile.from_params(params, param_name, ::Packages::PackageFileUploader.workhorse_local_upload_path)
- bad_request!('Missing package file!') unless uploaded_file
- uploaded_file
- end
-
private
def decode_token
- encoded_credentials = headers['Authorization'].to_s.split('Basic ', 2).second
+ encoded_credentials = request.headers['Authorization'].to_s.split('Basic ', 2).second
Base64.decode64(encoded_credentials || '').split(':', 2).second
end
end
diff --git a/lib/api/helpers/search_helpers.rb b/lib/api/helpers/search_helpers.rb
index 936684ea1f8..cb5f92fa62a 100644
--- a/lib/api/helpers/search_helpers.rb
+++ b/lib/api/helpers/search_helpers.rb
@@ -17,6 +17,10 @@ module API
# This is a separate method so that EE can redefine it.
%w(issues merge_requests milestones notes wiki_blobs commits blobs users)
end
+
+ def self.search_states
+ %w(all opened closed merged)
+ end
end
end
end
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index ff938358439..4bceda51900 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -631,12 +631,26 @@ module API
name: :issues_url,
type: String,
desc: 'The issues URL'
+ }
+ ],
+ 'ewm' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'New Issue URL'
},
{
- required: false,
- name: :description,
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'Project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
type: String,
- desc: 'The description of the tracker'
+ desc: 'Issues URL'
}
],
'youtrack' => [
@@ -651,12 +665,6 @@ module API
name: :issues_url,
type: String,
desc: 'The issues URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'The description of the tracker'
}
],
'slack' => [
@@ -747,6 +755,7 @@ module API
::DiscordService,
::DroneCiService,
::EmailsOnPushService,
+ ::EwmService,
::ExternalWikiService,
::FlowdockService,
::HangoutsChatService,
diff --git a/lib/api/helpers/snippets_helpers.rb b/lib/api/helpers/snippets_helpers.rb
index 79367da8d1f..9224381735f 100644
--- a/lib/api/helpers/snippets_helpers.rb
+++ b/lib/api/helpers/snippets_helpers.rb
@@ -27,6 +27,24 @@ module API
exactly_one_of :files, :content
end
+ params :update_file_params do |options|
+ optional :files, type: Array, desc: 'An array of files to update' do
+ requires :action, type: String,
+ values: SnippetInputAction::ACTIONS.map(&:to_s),
+ desc: "The type of action to perform on the file, must be one of: #{SnippetInputAction::ACTIONS.join(", ")}"
+ optional :content, type: String, desc: 'The content of a snippet'
+ optional :file_path, file_path: true, type: String, desc: 'The file path of a snippet file'
+ optional :previous_path, file_path: true, type: String, desc: 'The previous path of a snippet file'
+ end
+
+ mutually_exclusive :files, :content
+ mutually_exclusive :files, :file_name
+ end
+
+ params :minimum_update_params do
+ at_least_one_of :content, :description, :files, :file_name, :title, :visibility
+ end
+
def content_for(snippet)
if snippet.empty_repo?
env['api.format'] = :txt
@@ -53,10 +71,30 @@ module API
end
end
- def process_file_args(args)
- args[:snippet_actions] = args.delete(:files)&.map do |file|
- file[:action] = :create
- file.symbolize_keys
+ def process_create_params(args)
+ with_api_params do |api_params|
+ args[:snippet_actions] = args.delete(:files)&.map do |file|
+ file[:action] = :create
+ file.symbolize_keys
+ end
+
+ args.merge(api_params)
+ end
+ end
+
+ def process_update_params(args)
+ with_api_params do |api_params|
+ args[:snippet_actions] = args.delete(:files)&.map(&:symbolize_keys)
+
+ args.merge(api_params)
+ end
+ end
+
+ def validate_params_for_multiple_files(snippet)
+ return unless params[:content] || params[:file_name]
+
+ if Feature.enabled?(:snippet_multiple_files, current_user) && snippet.multiple_files?
+ render_api_error!({ error: _('To update Snippets with multiple files, you must use the `files` parameter') }, 400)
end
end
end
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index 17599c72243..ff687a57888 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -241,14 +241,16 @@ module API
break { success: false, message: "Invalid token expiry date: '#{params[:expires_at]}'" }
end
- access_token = nil
+ result = ::PersonalAccessTokens::CreateService.new(
+ user, name: params[:name], scopes: params[:scopes], expires_at: expires_at
+ ).execute
- ::Users::UpdateService.new(current_user, user: user).execute! do |user|
- access_token = user.personal_access_tokens.create!(
- name: params[:name], scopes: params[:scopes], expires_at: expires_at
- )
+ unless result.status == :success
+ break { success: false, message: "Failed to create token: #{result.message}" }
end
+ access_token = result.payload[:personal_access_token]
+
{ success: true, token: access_token.token, scopes: access_token.scopes, expires_at: access_token.expires_at }
end
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index 7f64fd7efe3..6d5dfd086e7 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -4,7 +4,16 @@ module API
# Kubernetes Internal API
module Internal
class Kubernetes < Grape::API::Instance
+ before do
+ check_feature_enabled
+ authenticate_gitlab_kas_request!
+ end
+
helpers do
+ def authenticate_gitlab_kas_request!
+ unauthorized! unless Gitlab::Kas.verify_api_request(headers)
+ end
+
def agent_token
@agent_token ||= cluster_agent_token_from_authorization_token
end
@@ -36,7 +45,7 @@ module API
end
def check_feature_enabled
- not_found! unless Feature.enabled?(:kubernetes_agent_internal_api)
+ not_found! unless Feature.enabled?(:kubernetes_agent_internal_api, default_enabled: true, type: :ops)
end
def check_agent_token
@@ -47,7 +56,6 @@ module API
namespace 'internal' do
namespace 'kubernetes' do
before do
- check_feature_enabled
check_agent_token
end
@@ -89,6 +97,26 @@ module API
}
end
end
+
+ namespace 'kubernetes/usage_metrics' do
+ desc 'POST usage metrics' do
+ detail 'Updates usage metrics for agent'
+ end
+ params do
+ requires :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by'
+ end
+ post '/' do
+ gitops_sync_count = params[:gitops_sync_count]
+
+ if gitops_sync_count < 0
+ bad_request!('gitops_sync_count must be greater than or equal to zero')
+ else
+ Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_gitops_sync(gitops_sync_count)
+
+ no_content!
+ end
+ end
+ end
end
end
end
diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb
new file mode 100644
index 00000000000..6cc5b344f47
--- /dev/null
+++ b/lib/api/issue_links.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module API
+ class IssueLinks < Grape::API::Instance
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
+ desc 'Get related issues' do
+ success Entities::RelatedIssue
+ end
+ get ':id/issues/:issue_iid/links' do
+ source_issue = find_project_issue(params[:issue_iid])
+ related_issues = source_issue.related_issues(current_user)
+
+ present related_issues,
+ with: Entities::RelatedIssue,
+ current_user: current_user,
+ project: user_project
+ end
+
+ desc 'Relate issues' do
+ success Entities::IssueLink
+ end
+ params do
+ requires :target_project_id, type: String, desc: 'The ID of the target project'
+ requires :target_issue_iid, type: Integer, desc: 'The IID of the target issue'
+ optional :link_type, type: String, values: IssueLink.link_types.keys,
+ desc: 'The type of the relation'
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ post ':id/issues/:issue_iid/links' do
+ source_issue = find_project_issue(params[:issue_iid])
+ target_issue = find_project_issue(declared_params[:target_issue_iid],
+ declared_params[:target_project_id])
+
+ create_params = { target_issuable: target_issue, link_type: declared_params[:link_type] }
+
+ result = ::IssueLinks::CreateService
+ .new(source_issue, current_user, create_params)
+ .execute
+
+ if result[:status] == :success
+ issue_link = IssueLink.find_by!(source: source_issue, target: target_issue)
+
+ present issue_link, with: Entities::IssueLink
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ desc 'Remove issues relation' do
+ success Entities::IssueLink
+ end
+ params do
+ requires :issue_link_id, type: Integer, desc: 'The ID of an issue link'
+ end
+ delete ':id/issues/:issue_iid/links/:issue_link_id' do
+ issue_link = IssueLink.find(declared_params[:issue_link_id])
+
+ find_project_issue(params[:issue_iid])
+ find_project_issue(issue_link.target.iid.to_s, issue_link.target.project_id.to_s)
+
+ result = ::IssueLinks::DestroyService
+ .new(issue_link, current_user)
+ .execute
+
+ if result[:status] == :success
+ present issue_link, with: Entities::IssueLink
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 1694a967f26..0e5b0fae6e2 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -114,6 +114,19 @@ module API
present issues, options
end
+
+ desc "Get specified issue (admin only)" do
+ success Entities::Issue
+ end
+ params do
+ requires :id, type: String, desc: 'The ID of the Issue'
+ end
+ get ":id" do
+ authenticated_as_admin!
+ issue = Issue.find(params['id'])
+
+ present issue, with: Entities::Issue, current_user: current_user, project: issue.project
+ end
end
params do
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 084c146abe7..ad46d948f3b 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -48,54 +48,6 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
- desc 'Get pipeline jobs' do
- success Entities::Ci::Job
- end
- params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
- use :optional_scope
- use :pagination
- end
- # rubocop: disable CodeReuse/ActiveRecord
- get ':id/pipelines/:pipeline_id/jobs' do
- authorize!(:read_pipeline, user_project)
- pipeline = user_project.all_pipelines.find(params[:pipeline_id])
- authorize!(:read_build, pipeline)
-
- builds = pipeline.builds
- builds = filter_builds(builds, params[:scope])
- builds = builds.preload(:job_artifacts_archive, :job_artifacts, project: [:namespace])
-
- present paginate(builds), with: Entities::Ci::Job
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Get pipeline bridge jobs' do
- success ::API::Entities::Ci::Bridge
- end
- params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
- use :optional_scope
- use :pagination
- end
- # rubocop: disable CodeReuse/ActiveRecord
- get ':id/pipelines/:pipeline_id/bridges' do
- authorize!(:read_build, user_project)
- pipeline = user_project.ci_pipelines.find(params[:pipeline_id])
- authorize!(:read_pipeline, pipeline)
-
- bridges = pipeline.bridges
- bridges = filter_builds(bridges, params[:scope])
- bridges = bridges.preload(
- :metadata,
- downstream_pipeline: [project: [:route, { namespace: :route }]],
- project: [:namespace]
- )
-
- present paginate(bridges), with: ::API::Entities::Ci::Bridge
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
desc 'Get a specific job of a project' do
success Entities::Ci::Job
end
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
index 32a45c59cfa..e6d9a9a7c20 100644
--- a/lib/api/maven_packages.rb
+++ b/lib/api/maven_packages.rb
@@ -107,7 +107,7 @@ module API
when 'sha1'
package_file.file_sha1
else
- track_event('pull_package') if jar_file?(format)
+ package_event('pull_package') if jar_file?(format)
present_carrierwave_file_with_head_support!(package_file.file)
end
end
@@ -145,7 +145,7 @@ module API
when 'sha1'
package_file.file_sha1
else
- track_event('pull_package') if jar_file?(format)
+ package_event('pull_package') if jar_file?(format)
present_carrierwave_file_with_head_support!(package_file.file)
end
@@ -181,7 +181,7 @@ module API
when 'sha1'
package_file.file_sha1
else
- track_event('pull_package') if jar_file?(format)
+ package_event('pull_package') if jar_file?(format)
present_carrierwave_file_with_head_support!(package_file.file)
end
@@ -200,7 +200,7 @@ module API
status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
- ::Packages::PackageFileUploader.workhorse_authorize(has_length: true)
+ ::Packages::PackageFileUploader.workhorse_authorize(has_length: true, maximum_size: user_project.actual_limits.maven_max_file_size)
end
desc 'Upload the maven package file' do
@@ -214,6 +214,7 @@ module API
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
authorize_upload!
+ bad_request!('File is too large') if user_project.actual_limits.exceeded?(:maven_max_file_size, params[:file].size)
file_name, format = extract_format(params[:file_name])
@@ -232,7 +233,7 @@ module API
when 'md5'
nil
else
- track_event('push_package') if jar_file?(format)
+ package_event('push_package') if jar_file?(format)
file_params = {
file: params[:file],
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 6f25df720c4..4bd72b267a9 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -29,11 +29,13 @@ module API
remove_labels
milestone_id
remove_source_branch
- state_event
+ allow_collaboration
+ allow_maintainer_to_push
+ squash
target_branch
title
+ state_event
discussion_locked
- squash
]
end
@@ -154,13 +156,13 @@ module API
helpers do
params :optional_params do
- optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
optional :assignee_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The array of user IDs to assign issue'
- optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
+ optional :description, type: String, desc: 'The description of the merge request'
optional :labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
optional :add_labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
optional :remove_labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names'
+ optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
optional :allow_collaboration, type: Boolean, desc: 'Allow commits from members who can merge to the target branch'
optional :allow_maintainer_to_push, type: Boolean, as: :allow_collaboration, desc: '[deprecated] See allow_collaboration'
diff --git a/lib/api/npm_packages.rb b/lib/api/npm_packages.rb
index 21ca57b7985..fca405b76b7 100644
--- a/lib/api/npm_packages.rb
+++ b/lib/api/npm_packages.rb
@@ -141,7 +141,7 @@ module API
package_file = ::Packages::PackageFileFinder
.new(package, params[:file_name]).execute!
- track_event('pull_package')
+ package_event('pull_package')
present_carrierwave_file!(package_file.file)
end
@@ -157,7 +157,7 @@ module API
put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!(user_project)
- track_event('push_package')
+ package_event('push_package')
created_package = ::Packages::Npm::CreatePackageService
.new(user_project, current_user, params.merge(build: current_authenticated_job)).execute
diff --git a/lib/api/nuget_packages.rb b/lib/api/nuget_packages.rb
index 56c4de2071d..f84a3acbe6d 100644
--- a/lib/api/nuget_packages.rb
+++ b/lib/api/nuget_packages.rb
@@ -92,6 +92,7 @@ module API
put do
authorize_upload!(authorized_user_project)
+ bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
file_params = params.merge(
file: params[:package],
@@ -104,7 +105,7 @@ module API
package_file = ::Packages::CreatePackageFileService.new(package, file_params)
.execute
- track_event('push_package')
+ package_event('push_package')
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
@@ -118,7 +119,11 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
put 'authorize' do
- authorize_workhorse!(subject: authorized_user_project, has_length: false)
+ authorize_workhorse!(
+ subject: authorized_user_project,
+ has_length: false,
+ maximum_size: authorized_user_project.actual_limits.nuget_max_file_size
+ )
end
params do
@@ -193,7 +198,7 @@ module API
not_found!('Package') unless package_file
- track_event('pull_package')
+ package_event('pull_package')
# nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false
present_carrierwave_file!(package_file.file, supports_direct_download: false)
@@ -228,7 +233,7 @@ module API
.new(authorized_user_project, params[:q], search_options)
.execute
- track_event('search_package')
+ package_event('search_package')
present ::Packages::Nuget::SearchResultsPresenter.new(search),
with: ::API::Entities::Nuget::SearchResults
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index fba4c60504f..f6e87fece89 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -64,12 +64,8 @@ module API
end
post ":id/snippets" do
authorize! :create_snippet, user_project
- snippet_params = declared_params(include_missing: false).tap do |create_args|
- create_args[:request] = request
- create_args[:api] = true
- process_file_args(create_args)
- end
+ snippet_params = process_create_params(declared_params(include_missing: false))
service_response = ::Snippets::CreateService.new(user_project, current_user, snippet_params).execute
snippet = service_response.payload[:snippet]
@@ -88,14 +84,16 @@ module API
end
params do
requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
- optional :title, type: String, allow_blank: false, desc: 'The title of the snippet'
- optional :file_name, type: String, desc: 'The file name of the snippet'
optional :content, type: String, allow_blank: false, desc: 'The content of the snippet'
optional :description, type: String, desc: 'The description of a snippet'
+ optional :file_name, type: String, desc: 'The file name of the snippet'
+ optional :title, type: String, allow_blank: false, desc: 'The title of the snippet'
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
- at_least_one_of :title, :file_name, :content, :visibility_level
+
+ use :update_file_params
+ use :minimum_update_params
end
# rubocop: disable CodeReuse/ActiveRecord
put ":id/snippets/:snippet_id" do
@@ -104,8 +102,9 @@ module API
authorize! :update_snippet, snippet
- snippet_params = declared_params(include_missing: false)
- .merge(request: request, api: true)
+ validate_params_for_multiple_files(snippet)
+
+ snippet_params = process_update_params(declared_params(include_missing: false))
service_response = ::Snippets::UpdateService.new(user_project, current_user, snippet_params).execute(snippet)
snippet = service_response.payload[:snippet]
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
index 739928a61ed..c07db68f8a8 100644
--- a/lib/api/pypi_packages.rb
+++ b/lib/api/pypi_packages.rb
@@ -64,7 +64,7 @@ module API
requires :sha256, type: String, desc: 'The PyPi package sha256 check sum'
end
- route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get 'files/:sha256/*file_identifier' do
project = unauthorized_user_project!
@@ -72,7 +72,7 @@ module API
package = packages_finder(project).by_file_name_and_sha256(filename, params[:sha256])
package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute
- track_event('pull_package')
+ package_event('pull_package')
present_carrierwave_file!(package_file.file, supports_direct_download: true)
end
@@ -87,11 +87,11 @@ module API
# An Api entry point but returns an HTML file instead of JSON.
# PyPi simple API returns the package descriptor as a simple HTML file.
- route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get 'simple/*package_name', format: :txt do
authorize_read_package!(authorized_user_project)
- track_event('list_package')
+ package_event('list_package')
packages = find_package_versions
presenter = ::Packages::Pypi::PackagePresenter.new(packages, authorized_user_project)
@@ -117,11 +117,12 @@ module API
optional :sha256_digest, type: String
end
- route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
post do
authorize_upload!(authorized_user_project)
+ bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:pypi_max_file_size, params[:content].size)
- track_event('push_package')
+ package_event('push_package')
::Packages::Pypi::CreatePackageService
.new(authorized_user_project, current_user, declared_params)
@@ -134,9 +135,13 @@ module API
forbidden!
end
- route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
post 'authorize' do
- authorize_workhorse!(subject: authorized_user_project, has_length: false)
+ authorize_workhorse!(
+ subject: authorized_user_project,
+ has_length: false,
+ maximum_size: authorized_user_project.actual_limits.pypi_max_file_size
+ )
end
end
end
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 53095e0b81a..b9c6a823f4f 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -32,6 +32,7 @@ module API
search_params = {
scope: params[:scope],
search: params[:search],
+ state: params[:state],
snippets: snippets?,
page: params[:page],
per_page: params[:per_page]
@@ -79,6 +80,7 @@ module API
type: String,
desc: 'The scope of the search',
values: Helpers::SearchHelpers.global_search_scopes
+ optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
use :pagination
end
get do
@@ -100,6 +102,7 @@ module API
type: String,
desc: 'The scope of the search',
values: Helpers::SearchHelpers.group_search_scopes
+ optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
use :pagination
end
get ':id/(-/)search' do
@@ -122,6 +125,7 @@ module API
desc: 'The scope of the search',
values: Helpers::SearchHelpers.project_search_scopes
optional :ref, type: String, desc: 'The name of a repository branch or tag. If not given, the default branch is used'
+ optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
use :pagination
end
get ':id/(-/)search' do
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index f2e0aaecfb9..6e5534d0c9a 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -61,6 +61,10 @@ module API
end
optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.'
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 :gitpod_enabled, type: Boolean, desc: 'Enable Gitpod'
+ given gitpod_enabled: ->(val) { val } do
+ requires :gitpod_url, type: String, desc: 'The configured Gitpod instance URL'
+ end
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.'
@@ -140,11 +144,9 @@ module API
end
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
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'
- optional :snowplow_iglu_registry_url, type: String, desc: 'The Snowplow base Iglu Schema Registry URL to use for custom context and self describing events'
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'
@@ -152,6 +154,7 @@ module API
end
optional :issues_create_limit, type: Integer, desc: "Maximum number of issue creation requests allowed per minute per user. Set to 0 for unlimited requests per minute."
optional :raw_blob_request_limit, type: Integer, desc: "Maximum number of requests per minute for each raw path. Set to 0 for unlimited requests per minute."
+ optional :wiki_page_max_content_bytes, type: Integer, desc: "Maximum wiki page content size in bytes"
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb
index de1373144e3..77f2b1e871e 100644
--- a/lib/api/sidekiq_metrics.rb
+++ b/lib/api/sidekiq_metrics.rb
@@ -17,7 +17,7 @@ module API
end
def process_metrics
- Sidekiq::ProcessSet.new.map do |process|
+ Sidekiq::ProcessSet.new(false).map do |process|
{
hostname: process['hostname'],
pid: process['pid'],
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 1a3283aed98..c6ef35875fc 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -76,12 +76,7 @@ module API
post do
authorize! :create_snippet
- attrs = declared_params(include_missing: false).tap do |create_args|
- create_args[:request] = request
- create_args[:api] = true
-
- process_file_args(create_args)
- end
+ attrs = process_create_params(declared_params(include_missing: false))
service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute
snippet = service_response.payload[:snippet]
@@ -99,16 +94,19 @@ module API
detail 'This feature was introduced in GitLab 8.15.'
success Entities::PersonalSnippet
end
+
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
- optional :title, type: String, allow_blank: false, desc: 'The title of a snippet'
- optional :file_name, type: String, desc: 'The name of a snippet file'
optional :content, type: String, allow_blank: false, desc: 'The content of a snippet'
optional :description, type: String, desc: 'The description of a snippet'
+ optional :file_name, type: String, desc: 'The name of a snippet file'
+ optional :title, type: String, allow_blank: false, desc: 'The title of a snippet'
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
- at_least_one_of :title, :file_name, :content, :visibility
+
+ use :update_file_params
+ use :minimum_update_params
end
put ':id' do
snippet = snippets_for_current_user.find_by_id(params.delete(:id))
@@ -116,8 +114,12 @@ module API
authorize! :update_snippet, snippet
- attrs = declared_params(include_missing: false).merge(request: request, api: true)
+ validate_params_for_multiple_files(snippet)
+
+ attrs = process_update_params(declared_params(include_missing: false))
+
service_response = ::Snippets::UpdateService.new(nil, current_user, attrs).execute(snippet)
+
snippet = service_response.payload[:snippet]
if service_response.success?
diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb
index f6e966defce..7063a3d08b5 100644
--- a/lib/api/terraform/state.rb
+++ b/lib/api/terraform/state.rb
@@ -35,10 +35,10 @@ module API
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get do
remote_state_handler.find_with_lock do |state|
- no_content! unless state.file.exists?
+ no_content! unless state.latest_file && state.latest_file.exists?
env['api.format'] = :binary # this bypasses json serialization
- body state.file.read
+ body state.latest_file.read
status :ok
end
end
@@ -52,8 +52,7 @@ module API
no_content! if data.empty?
remote_state_handler.handle_with_lock do |state|
- state.file = CarrierWaveStringFile.new(data)
- state.save!
+ state.update_file!(CarrierWaveStringFile.new(data), version: params[:serial])
status :ok
end
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 4a73e3e0e94..5eae92a251e 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -39,8 +39,17 @@ module API
resource :todos do
helpers do
+ params :todo_filters do
+ optional :action, String, values: Todo::ACTION_NAMES.values.map(&:to_s)
+ optional :author_id, Integer
+ optional :state, String, values: Todo.state_machine.states.map(&:name).map(&:to_s)
+ optional :type, String, values: TodosFinder.todo_types
+ optional :project_id, Integer
+ optional :group_id, Integer
+ end
+
def find_todos
- TodosFinder.new(current_user, params).execute
+ TodosFinder.new(current_user, declared_params(include_missing: false)).execute
end
def issuable_and_awardable?(type)
@@ -72,7 +81,7 @@ module API
success Entities::Todo
end
params do
- use :pagination
+ use :pagination, :todo_filters
end
get do
todos = paginate(find_todos.with_entity_associations)
diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb
new file mode 100644
index 00000000000..a1512197ee1
--- /dev/null
+++ b/lib/api/usage_data.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module API
+ class UsageData < Grape::API::Instance
+ before { authenticate! }
+
+ namespace 'usage_data' do
+ before do
+ not_found! unless Feature.enabled?(:usage_data_api)
+ forbidden!('Invalid CSRF token is provided') unless verified_request?
+ end
+
+ desc 'Track usage data events' do
+ detail 'This feature was introduced in GitLab 13.4.'
+ end
+
+ params do
+ requires :event, type: String, desc: 'The event name that should be tracked'
+ end
+
+ post 'increment_unique_users' do
+ event_name = params[:event]
+
+ increment_unique_values(event_name, current_user.id)
+
+ status :ok
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 335624963aa..73bb43b88fc 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -84,6 +84,7 @@ module API
optional :created_after, type: DateTime, desc: 'Return users created after the specified time'
optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
optional :without_projects, type: Boolean, default: false, desc: 'Filters only users without projects'
+ optional :exclude_internal, as: :non_internal, type: Boolean, default: false, desc: 'Filters only non internal users'
all_or_none_of :extern_uid, :provider
use :sort_params
@@ -115,6 +116,7 @@ module API
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin
+ users = users.preload(:identities, :webauthn_registrations) if entity == Entities::UserWithAdmin
users, options = with_custom_attributes(users, { with: entity, current_user: current_user })
users = users.preload(:user_detail)
@@ -217,9 +219,15 @@ module API
.where.not(id: user.id).exists?
user_params = declared_params(include_missing: false)
+ admin_making_changes_for_another_user = (current_user != user)
- user_params[:password_expires_at] = Time.current if user_params[:password].present?
- result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute
+ if user_params[:password].present?
+ user_params[:password_expires_at] = Time.current if admin_making_changes_for_another_user
+ end
+
+ result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute do |user|
+ user.send_only_admin_changed_your_password_notification! if admin_making_changes_for_another_user
+ end
if result[:status] == :success
present user, with: Entities::UserWithAdmin, current_user: current_user
diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb
new file mode 100644
index 00000000000..593f90460ac
--- /dev/null
+++ b/lib/api/v3/github.rb
@@ -0,0 +1,232 @@
+# frozen_string_literal: true
+
+# These endpoints partially mimic Github API behavior in order to successfully
+# integrate with Jira Development Panel.
+# Endpoints returning an empty list were temporarily added to avoid 404's
+# during Jira's DVCS integration.
+#
+module API
+ module V3
+ class Github < Grape::API::Instance
+ NO_SLASH_URL_PART_REGEX = %r{[^/]+}.freeze
+ ENDPOINT_REQUIREMENTS = {
+ namespace: NO_SLASH_URL_PART_REGEX,
+ project: NO_SLASH_URL_PART_REGEX,
+ username: NO_SLASH_URL_PART_REGEX
+ }.freeze
+
+ # Used to differentiate Jira Cloud requests from Jira Server requests
+ # Jira Cloud user agent format: Jira DVCS Connector Vertigo/version
+ # Jira Server user agent format: Jira DVCS Connector/version
+ JIRA_DVCS_CLOUD_USER_AGENT = 'Jira DVCS Connector Vertigo'.freeze
+
+ include PaginationParams
+
+ before do
+ authorize_jira_user_agent!(request)
+ authenticate!
+ end
+
+ helpers do
+ params :project_full_path do
+ requires :namespace, type: String
+ requires :project, type: String
+ end
+
+ def authorize_jira_user_agent!(request)
+ not_found! unless Gitlab::Jira::Middleware.jira_dvcs_connector?(request.env)
+ end
+
+ def update_project_feature_usage_for(project)
+ # Prevent errors on GitLab Geo not allowing
+ # UPDATE statements to happen in GET requests.
+ return if Gitlab::Database.read_only?
+
+ project.log_jira_dvcs_integration_usage(cloud: jira_cloud?)
+ end
+
+ def jira_cloud?
+ request.env['HTTP_USER_AGENT'].include?(JIRA_DVCS_CLOUD_USER_AGENT)
+ end
+
+ def find_project_with_access(params)
+ project = find_project!(
+ ::Gitlab::Jira::Dvcs.restore_full_path(params.slice(:namespace, :project).symbolize_keys)
+ )
+ not_found! unless can?(current_user, :download_code, project)
+ project
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def find_merge_requests
+ merge_requests = authorized_merge_requests.reorder(updated_at: :desc)
+ paginate(merge_requests)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def find_merge_request_with_access(id, access_level = :read_merge_request)
+ merge_request = authorized_merge_requests.find_by(id: id)
+ not_found! unless can?(current_user, access_level, merge_request)
+ merge_request
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def authorized_merge_requests
+ MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?).execute
+ end
+
+ def authorized_merge_requests_for_project(project)
+ MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?, project_id: project.id).execute
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def find_notes(noteable)
+ # They're not presented on Jira Dev Panel ATM. A comments count with a
+ # redirect link is presented.
+ notes = paginate(noteable.notes.user.reorder(nil))
+ notes.select { |n| n.readable_by?(current_user) }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+
+ resource :orgs do
+ get ':namespace/repos' do
+ present []
+ end
+ end
+
+ resource :user do
+ get :repos do
+ present []
+ end
+ end
+
+ resource :users do
+ params do
+ use :pagination
+ end
+
+ get ':namespace/repos' do
+ namespace = Namespace.find_by_full_path(params[:namespace])
+ not_found!('Namespace') unless namespace
+
+ projects = current_user.can_read_all_resources? ? Project.all : current_user.authorized_projects
+ projects = projects.in_namespace(namespace.self_and_descendants)
+
+ projects_cte = Project.wrap_with_cte(projects)
+ .eager_load_namespace_and_owner
+ .with_route
+
+ present paginate(projects_cte),
+ with: ::API::Github::Entities::Repository,
+ root_namespace: namespace.root_ancestor
+ end
+
+ get ':username' do
+ forbidden! unless can?(current_user, :read_users_list)
+ user = UsersFinder.new(current_user, { username: params[:username] }).execute.first
+ not_found! unless user
+ present user, with: ::API::Github::Entities::User
+ end
+ end
+
+ # Jira dev panel integration weirdly requests for "/-/jira/pulls" instead
+ # "/api/v3/repos/<namespace>/<project>/pulls". This forces us into
+ # returning _all_ Merge Requests from authorized projects (user is a member),
+ # instead just the authorized MRs from a project.
+ # Jira handles the filtering, presenting just MRs mentioning the Jira
+ # issue ID on the MR title / description.
+ resource :repos do
+ # Keeping for backwards compatibility with old Jira integration instructions
+ # so that users that do not change it will not suddenly have a broken integration
+ get '/-/jira/pulls' do
+ present find_merge_requests, with: ::API::Github::Entities::PullRequest
+ end
+
+ get '/-/jira/events' do
+ present []
+ end
+
+ params do
+ use :project_full_path
+ end
+ get ':namespace/:project/pulls' do
+ user_project = find_project_with_access(params)
+
+ merge_requests = authorized_merge_requests_for_project(user_project)
+
+ present paginate(merge_requests), with: ::API::Github::Entities::PullRequest
+ end
+
+ params do
+ use :project_full_path
+ end
+ get ':namespace/:project/pulls/:id' do
+ merge_request = find_merge_request_with_access(params[:id])
+
+ present merge_request, with: ::API::Github::Entities::PullRequest
+ end
+
+ # In Github, each Merge Request is automatically also an issue.
+ # Therefore we return its comments here.
+ # It'll present _just_ the comments counting with a link to GitLab on
+ # Jira dev panel, not the actual note content.
+ get ':namespace/:project/issues/:id/comments' do
+ merge_request = find_merge_request_with_access(params[:id])
+
+ present find_notes(merge_request), with: ::API::Github::Entities::NoteableComment
+ end
+
+ # This refer to "review" comments but Jira dev panel doesn't seem to
+ # present it accordingly.
+ get ':namespace/:project/pulls/:id/comments' do
+ present []
+ end
+
+ # Commits are not presented within "Pull Requests" modal on Jira dev
+ # panel.
+ get ':namespace/:project/pulls/:id/commits' do
+ present []
+ end
+
+ # Self-hosted Jira (tested on 7.11.1) requests this endpoint right
+ # after fetching branches.
+ get ':namespace/:project/events' do
+ user_project = find_project_with_access(params)
+
+ merge_requests = authorized_merge_requests_for_project(user_project)
+
+ present paginate(merge_requests), with: ::API::Github::Entities::PullRequestEvent
+ end
+
+ params do
+ use :project_full_path
+ use :pagination
+ end
+ get ':namespace/:project/branches' do
+ user_project = find_project_with_access(params)
+
+ update_project_feature_usage_for(user_project)
+
+ branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
+
+ present paginate(branches), with: ::API::Github::Entities::Branch, project: user_project
+ end
+
+ params do
+ use :project_full_path
+ end
+ get ':namespace/:project/commits/:sha' do
+ user_project = find_project_with_access(params)
+
+ commit = user_project.commit(params[:sha])
+
+ not_found! 'Commit' unless commit
+
+ present commit, with: ::API::Github::Entities::RepoCommit
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index cea0cb3a19c..0b3ec10f1b4 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -17,7 +17,6 @@ module API
def find_variable(params)
variables = ::Ci::VariablesFinder.new(user_project, params).execute.to_a
- return variables.first unless ::Gitlab::Ci::Features.variables_api_filter_environment_scope?
return variables.first unless variables.many? # rubocop: disable CodeReuse/ActiveRecord
conflict!("There are multiple variables with provided parameters. Please use 'filter[environment_scope]'")
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 95afa36113c..4eba12157bd 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -83,11 +83,12 @@ module API
put ':id/wikis/:slug' do
authorize! :create_wiki, container
- page = WikiPages::UpdateService
+ response = WikiPages::UpdateService
.new(container: container, current_user: current_user, params: params)
.execute(wiki_page)
+ page = response.payload[:page]
- if page.valid?
+ if response.success?
present page, with: Entities::WikiPage
else
render_validation_error!(page)
@@ -101,11 +102,15 @@ module API
delete ':id/wikis/:slug' do
authorize! :admin_wiki, container
- WikiPages::DestroyService
+ response = WikiPages::DestroyService
.new(container: container, current_user: current_user)
.execute(wiki_page)
- no_content!
+ if response.success?
+ no_content!
+ else
+ render_api_error!(reponse.message)
+ end
end
desc 'Upload an attachment to the wiki repository' do