Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
commitb76ae638462ab0f673e5915986070518dd3f9ad3 (patch)
treebdab0533383b52873be0ec0eb4d3c66598ff8b91 /lib/api
parent434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff)
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/api.rb9
-rw-r--r--lib/api/appearance.rb2
-rw-r--r--lib/api/badges.rb2
-rw-r--r--lib/api/bulk_imports.rb53
-rw-r--r--lib/api/ci/helpers/runner.rb123
-rw-r--r--lib/api/ci/job_artifacts.rb143
-rw-r--r--lib/api/ci/jobs.rb206
-rw-r--r--lib/api/ci/pipelines.rb27
-rw-r--r--lib/api/ci/runner.rb22
-rw-r--r--lib/api/ci/triggers.rb148
-rw-r--r--lib/api/ci/variables.rb126
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/concerns/packages/debian_distribution_endpoints.rb4
-rw-r--r--lib/api/concerns/packages/debian_package_endpoints.rb165
-rw-r--r--lib/api/debian_group_packages.rb47
-rw-r--r--lib/api/debian_project_packages.rb50
-rw-r--r--lib/api/entities/ci/job_request/dependency.rb2
-rw-r--r--lib/api/entities/ci/pipeline_basic.rb2
-rw-r--r--lib/api/entities/error_tracking.rb1
-rw-r--r--lib/api/entities/issue_basic.rb2
-rw-r--r--lib/api/entities/project.rb1
-rw-r--r--lib/api/entities/project_with_access.rb6
-rw-r--r--lib/api/environments.rb6
-rw-r--r--lib/api/error_tracking.rb5
-rw-r--r--lib/api/error_tracking_collector.rb26
-rw-r--r--lib/api/group_debian_distributions.rb35
-rw-r--r--lib/api/group_variables.rb2
-rw-r--r--lib/api/groups.rb10
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/helpers/groups_helpers.rb2
-rw-r--r--lib/api/helpers/members_helpers.rb8
-rw-r--r--lib/api/helpers/packages/dependency_proxy_helpers.rb23
-rw-r--r--lib/api/helpers/packages/npm.rb14
-rw-r--r--lib/api/helpers/projects_helpers.rb11
-rw-r--r--lib/api/helpers/runner.rb121
-rw-r--r--lib/api/internal/base.rb4
-rw-r--r--lib/api/invitations.rb11
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/job_artifacts.rb141
-rw-r--r--lib/api/jobs.rb204
-rw-r--r--lib/api/members.rb8
-rw-r--r--lib/api/merge_requests.rb1
-rw-r--r--lib/api/namespaces.rb5
-rw-r--r--lib/api/project_debian_distributions.rb2
-rw-r--r--lib/api/project_templates.rb2
-rw-r--r--lib/api/projects.rb48
-rw-r--r--lib/api/pypi_packages.rb43
-rw-r--r--lib/api/repositories.rb12
-rw-r--r--lib/api/rubygem_packages.rb2
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/tags.rb2
-rw-r--r--lib/api/templates.rb19
-rw-r--r--lib/api/time_tracking_endpoints.rb1
-rw-r--r--lib/api/triggers.rb146
-rw-r--r--lib/api/user_counts.rb6
-rw-r--r--lib/api/v3/github.rb2
-rw-r--r--lib/api/variables.rb124
57 files changed, 1217 insertions, 982 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index f9e89191a36..40f1b2fa9d3 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -153,10 +153,14 @@ module API
mount ::API::Branches
mount ::API::BroadcastMessages
mount ::API::BulkImports
+ mount ::API::Ci::JobArtifacts
+ mount ::API::Ci::Jobs
mount ::API::Ci::Pipelines
mount ::API::Ci::PipelineSchedules
mount ::API::Ci::Runner
mount ::API::Ci::Runners
+ mount ::API::Ci::Triggers
+ mount ::API::Ci::Variables
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::ContainerRegistryEvent
@@ -184,14 +188,13 @@ module API
mount ::API::GroupMilestones
mount ::API::Groups
mount ::API::GroupContainerRepositories
+ mount ::API::GroupDebianDistributions
mount ::API::GroupVariables
mount ::API::ImportBitbucketServer
mount ::API::ImportGithub
mount ::API::IssueLinks
mount ::API::Invitations
mount ::API::Issues
- mount ::API::JobArtifacts
- mount ::API::Jobs
mount ::API::Keys
mount ::API::Labels
mount ::API::Lint
@@ -268,14 +271,12 @@ module API
mount ::API::Tags
mount ::API::Templates
mount ::API::Todos
- mount ::API::Triggers
mount ::API::Unleash
mount ::API::UsageData
mount ::API::UsageDataQueries
mount ::API::UsageDataNonSqlMetrics
mount ::API::UserCounts
mount ::API::Users
- mount ::API::Variables
mount ::API::Version
mount ::API::Wikis
end
diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb
index fe498bf611b..1eaa4167a7d 100644
--- a/lib/api/appearance.rb
+++ b/lib/api/appearance.rb
@@ -48,3 +48,5 @@ module API
end
end
end
+
+API::Appearance.prepend_mod
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
index 04f155be4e1..d7c850c2f40 100644
--- a/lib/api/badges.rb
+++ b/lib/api/badges.rb
@@ -8,7 +8,7 @@ module API
helpers ::API::Helpers::BadgesHelpers
- feature_category :continuous_integration
+ feature_category :projects
helpers do
def find_source_if_admin(source_type)
diff --git a/lib/api/bulk_imports.rb b/lib/api/bulk_imports.rb
index 189851cee65..0705a8285c1 100644
--- a/lib/api/bulk_imports.rb
+++ b/lib/api/bulk_imports.rb
@@ -8,7 +8,10 @@ module API
helpers do
def bulk_imports
- @bulk_imports ||= ::BulkImports::ImportsFinder.new(user: current_user, status: params[:status]).execute
+ @bulk_imports ||= ::BulkImports::ImportsFinder.new(
+ user: current_user,
+ status: params[:status]
+ ).execute
end
def bulk_import
@@ -16,7 +19,11 @@ module API
end
def bulk_import_entities
- @bulk_import_entities ||= ::BulkImports::EntitiesFinder.new(user: current_user, bulk_import: bulk_import, status: params[:status]).execute
+ @bulk_import_entities ||= ::BulkImports::EntitiesFinder.new(
+ user: current_user,
+ bulk_import: bulk_import,
+ status: params[:status]
+ ).execute
end
def bulk_import_entity
@@ -27,13 +34,44 @@ module API
before { authenticate! }
resource :bulk_imports do
+ desc 'Start a new GitLab Migration' do
+ detail 'This feature was introduced in GitLab 14.2.'
+ end
+ params do
+ requires :configuration, type: Hash, desc: 'The source GitLab instance configuration' do
+ requires :url, type: String, desc: 'Source GitLab instance URL'
+ requires :access_token, type: String, desc: 'Access token to the source GitLab instance'
+ end
+ requires :entities, type: Array, desc: 'List of entities to import' do
+ requires :source_type, type: String, desc: 'Source entity type (only `group_entity` is supported)',
+ values: %w[group_entity]
+ requires :source_full_path, type: String, desc: 'Source full path of the entity to import'
+ requires :destination_name, type: String, desc: 'Destination name for the entity'
+ requires :destination_namespace, type: String, desc: 'Destination namespace for the entity'
+ end
+ end
+ post do
+ response = BulkImportService.new(
+ current_user,
+ params[:entities],
+ url: params[:configuration][:url],
+ access_token: params[:configuration][:access_token]
+ ).execute
+
+ if response.success?
+ present response.payload, with: Entities::BulkImport
+ else
+ render_api_error!(response.message, response.http_status)
+ end
+ end
+
desc 'List all GitLab Migrations' do
detail 'This feature was introduced in GitLab 14.1.'
end
params do
use :pagination
optional :status, type: String, values: BulkImport.all_human_statuses,
- desc: 'Return GitLab Migrations with specified status'
+ desc: 'Return GitLab Migrations with specified status'
end
get do
present paginate(bulk_imports), with: Entities::BulkImport
@@ -45,10 +83,13 @@ module API
params do
use :pagination
optional :status, type: String, values: ::BulkImports::Entity.all_human_statuses,
- desc: "Return all GitLab Migrations' entities with specified status"
+ desc: "Return all GitLab Migrations' entities with specified status"
end
get :entities do
- entities = ::BulkImports::EntitiesFinder.new(user: current_user, status: params[:status]).execute
+ entities = ::BulkImports::EntitiesFinder.new(
+ user: current_user,
+ status: params[:status]
+ ).execute
present paginate(entities), with: Entities::BulkImports::Entity
end
@@ -69,7 +110,7 @@ module API
params do
requires :import_id, type: Integer, desc: "The ID of user's GitLab Migration"
optional :status, type: String, values: ::BulkImports::Entity.all_human_statuses,
- desc: 'Return import entities with specified status'
+ desc: 'Return import entities with specified status'
use :pagination
end
get ':import_id/entities' do
diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb
new file mode 100644
index 00000000000..b9662b822fb
--- /dev/null
+++ b/lib/api/ci/helpers/runner.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ module Helpers
+ module Runner
+ include Gitlab::Utils::StrongMemoize
+
+ prepend_mod_with('API::Ci::Helpers::Runner') # rubocop: disable Cop/InjectEnterpriseEditionModule
+
+ JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'
+ JOB_TOKEN_PARAM = :token
+
+ def runner_registration_token_valid?
+ ActiveSupport::SecurityUtils.secure_compare(params[:token], Gitlab::CurrentSettings.runners_registration_token)
+ end
+
+ def runner_registrar_valid?(type)
+ Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
+ end
+
+ def authenticate_runner!
+ forbidden! unless current_runner
+
+ current_runner
+ .heartbeat(get_runner_details_from_request)
+ end
+
+ def get_runner_details_from_request
+ return get_runner_ip unless params['info'].present?
+
+ attributes_for_keys(%w(name version revision platform architecture), params['info'])
+ .merge(get_runner_config_from_request)
+ .merge(get_runner_ip)
+ end
+
+ def get_runner_ip
+ { ip_address: ip_address }
+ end
+
+ def current_runner
+ token = params[:token]
+
+ if token
+ ::Gitlab::Database::LoadBalancing::RackMiddleware
+ .stick_or_unstick(env, :runner, token)
+ end
+
+ strong_memoize(:current_runner) do
+ ::Ci::Runner.find_by_token(token.to_s)
+ end
+ end
+
+ # HTTP status codes to terminate the job on GitLab Runner:
+ # - 403
+ def authenticate_job!(require_running: true)
+ job = current_job
+
+ # 404 is not returned here because we want to terminate the job if it's
+ # running. A 404 can be returned from anywhere in the networking stack which is why
+ # we are explicit about a 403, we should improve this in
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/327703
+ forbidden! unless job
+
+ forbidden! unless job_token_valid?(job)
+
+ forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete?
+ forbidden!('Job has been erased!') if job.erased?
+
+ if require_running
+ job_forbidden!(job, 'Job is not running') unless job.running?
+ end
+
+ job.runner&.heartbeat(get_runner_ip)
+
+ job
+ end
+
+ def current_job
+ id = params[:id]
+
+ if id
+ ::Gitlab::Database::LoadBalancing::RackMiddleware
+ .stick_or_unstick(env, :build, id)
+ end
+
+ strong_memoize(:current_job) do
+ ::Ci::Build.find_by_id(id)
+ end
+ end
+
+ def job_token_valid?(job)
+ token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
+ token && job.valid_token?(token)
+ end
+
+ def job_forbidden!(job, reason)
+ header 'Job-Status', job.status
+ forbidden!(reason)
+ end
+
+ def set_application_context
+ return unless current_job
+
+ Gitlab::ApplicationContext.push(
+ user: -> { current_job.user },
+ project: -> { current_job.project }
+ )
+ end
+
+ def track_ci_minutes_usage!(_build, _runner)
+ # noop: overridden in EE
+ end
+
+ private
+
+ def get_runner_config_from_request
+ { config: attributes_for_keys(%w(gpus), params.dig('info', 'config')) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb
new file mode 100644
index 00000000000..6431436b50d
--- /dev/null
+++ b/lib/api/ci/job_artifacts.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ class JobArtifacts < ::API::Base
+ before { authenticate_non_get! }
+
+ feature_category :build_artifacts
+
+ # EE::API::Ci::JobArtifacts would override the following helpers
+ helpers do
+ def authorize_download_artifacts!
+ authorize_read_builds!
+ end
+ end
+
+ prepend_mod_with('API::Ci::JobArtifacts') # rubocop: disable Cop/InjectEnterpriseEditionModule
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Download the artifacts archive 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
+ route_setting :authentication, job_token_allowed: true
+ get ':id/jobs/artifacts/:ref_name/download',
+ requirements: { ref_name: /.+/ } do
+ authorize_download_artifacts!
+
+ latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
+ authorize_read_job_artifacts!(latest_build)
+
+ present_carrierwave_file!(latest_build.artifacts_file)
+ end
+
+ desc 'Download a specific file from artifacts archive from a ref' do
+ detail 'This feature was introduced in GitLab 11.5'
+ end
+ params do
+ requires :ref_name, type: String, desc: 'The ref from repository'
+ requires :job, type: String, desc: 'The name for the job'
+ requires :artifact_path, type: String, desc: 'Artifact path'
+ end
+ route_setting :authentication, job_token_allowed: true
+ get ':id/jobs/artifacts/:ref_name/raw/*artifact_path',
+ format: false,
+ requirements: { ref_name: /.+/ } do
+ authorize_download_artifacts!
+
+ build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
+ authorize_read_job_artifacts!(build)
+
+ path = Gitlab::Ci::Build::Artifacts::Path
+ .new(params[:artifact_path])
+
+ bad_request! unless path.valid?
+
+ send_artifacts_entry(build.artifacts_file, path)
+ end
+
+ desc 'Download the artifacts archive 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
+ route_setting :authentication, job_token_allowed: true
+ get ':id/jobs/:job_id/artifacts' do
+ authorize_download_artifacts!
+
+ build = find_build!(params[:job_id])
+ authorize_read_job_artifacts!(build)
+
+ present_carrierwave_file!(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
+ route_setting :authentication, job_token_allowed: true
+ get ':id/jobs/:job_id/artifacts/*artifact_path', format: false do
+ authorize_download_artifacts!
+
+ build = find_build!(params[:job_id])
+ authorize_read_job_artifacts!(build)
+
+ not_found! unless build.available_artifacts?
+
+ path = Gitlab::Ci::Build::Artifacts::Path
+ .new(params[:artifact_path])
+
+ bad_request! unless path.valid?
+
+ send_artifacts_entry(build.artifacts_file, path)
+ end
+
+ desc 'Keep the artifacts to prevent them from being deleted' do
+ success ::API::Entities::Ci::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)
+ break not_found!(build) unless build.artifacts?
+
+ build.keep_artifacts!
+
+ status 200
+ present build, with: ::API::Entities::Ci::Job
+ end
+
+ desc 'Delete the artifacts files from a job' do
+ detail 'This feature was introduced in GitLab 11.9'
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a job'
+ end
+ delete ':id/jobs/:job_id/artifacts' do
+ authorize_destroy_artifacts!
+ build = find_build!(params[:job_id])
+ authorize!(:destroy_artifacts, build)
+
+ build.erase_erasable_artifacts!
+
+ status :no_content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb
new file mode 100644
index 00000000000..eea1637c32a
--- /dev/null
+++ b/lib/api/ci/jobs.rb
@@ -0,0 +1,206 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ class Jobs < ::API::Base
+ include PaginationParams
+ before { authenticate! }
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ 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 ::Hash
+ scope.values
+ when ::Array
+ scope
+ else
+ ['unknown']
+ end
+ }
+ end
+ end
+
+ desc 'Get a projects jobs' do
+ success Entities::Ci::Job
+ end
+ params do
+ use :optional_scope
+ use :pagination
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ get ':id/jobs', feature_category: :continuous_integration do
+ authorize_read_builds!
+
+ builds = user_project.builds.order('id DESC')
+ builds = filter_builds(builds, params[:scope])
+
+ builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, :tags, pipeline: :project)
+ present paginate(builds), with: Entities::Ci::Job
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ desc 'Get a specific job of a project' do
+ success Entities::Ci::Job
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a job'
+ end
+ get ':id/jobs/:job_id', feature_category: :continuous_integration do
+ authorize_read_builds!
+
+ build = find_build!(params[:job_id])
+
+ present build, with: Entities::Ci::Job
+ end
+
+ # TODO: We should use `present_disk_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.
+ desc 'Get a trace of a specific job of a project'
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a job'
+ end
+ get ':id/jobs/:job_id/trace', feature_category: :continuous_integration do
+ authorize_read_builds!
+
+ build = find_build!(params[:job_id])
+
+ authorize_read_build_trace!(build) if build
+
+ header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
+ content_type 'text/plain'
+ env['api.format'] = :binary
+
+ # The trace can be nil bu body method expects a string as an argument.
+ trace = build.trace.raw || ''
+ body trace
+ end
+
+ desc 'Cancel a specific job of a project' do
+ success Entities::Ci::Job
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a job'
+ end
+ post ':id/jobs/:job_id/cancel', feature_category: :continuous_integration do
+ authorize_update_builds!
+
+ build = find_build!(params[:job_id])
+ authorize!(:update_build, build)
+
+ build.cancel
+
+ present build, with: Entities::Ci::Job
+ end
+
+ desc 'Retry a specific build of a project' do
+ success Entities::Ci::Job
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/jobs/:job_id/retry', feature_category: :continuous_integration do
+ authorize_update_builds!
+
+ build = find_build!(params[:job_id])
+ authorize!(:update_build, build)
+ break forbidden!('Job is not retryable') unless build.retryable?
+
+ build = ::Ci::Build.retry(build, current_user)
+
+ present build, with: Entities::Ci::Job
+ end
+
+ desc 'Erase job (remove artifacts and the trace)' do
+ success Entities::Ci::Job
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/jobs/:job_id/erase', feature_category: :continuous_integration do
+ authorize_update_builds!
+
+ build = find_build!(params[:job_id])
+ authorize!(:erase_build, build)
+ break forbidden!('Job is not erasable!') unless build.erasable?
+
+ build.erase(erased_by: current_user)
+ present build, with: Entities::Ci::Job
+ end
+
+ desc 'Trigger an actionable job (manual, delayed, etc)' do
+ success Entities::Ci::JobBasic
+ detail 'This feature was added in GitLab 8.11'
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a Job'
+ end
+
+ post ":id/jobs/:job_id/play", feature_category: :continuous_integration do
+ authorize_read_builds!
+
+ job = find_job!(params[:job_id])
+
+ authorize!(:play_job, job)
+
+ bad_request!("Unplayable Job") unless job.playable?
+
+ job.play(current_user)
+
+ status 200
+
+ if job.is_a?(::Ci::Build)
+ present job, with: Entities::Ci::Job
+ else
+ present job, with: Entities::Ci::Bridge
+ end
+ end
+ end
+
+ resource :job do
+ desc 'Get current project using job token' do
+ success Entities::Ci::Job
+ end
+ route_setting :authentication, job_token_allowed: true
+ get '', feature_category: :continuous_integration do
+ validate_current_authenticated_job
+
+ present current_authenticated_job, with: Entities::Ci::Job
+ end
+ end
+
+ helpers do
+ # 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: available_statuses && scope)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def validate_current_authenticated_job
+ # current_authenticated_job will be nil if user is using
+ # a valid authentication (like PRIVATE-TOKEN) that is not CI_JOB_TOKEN
+ not_found!('Job') unless current_authenticated_job
+ end
+ end
+ end
+ end
+end
+
+API::Ci::Jobs.prepend_mod_with('API::Ci::Jobs')
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
index 339c0e779f9..4d6d38f2dce 100644
--- a/lib/api/ci/pipelines.rb
+++ b/lib/api/ci/pipelines.rb
@@ -44,7 +44,7 @@ module API
optional :ref, type: String, desc: 'The ref of pipelines'
optional :sha, type: String, desc: 'The sha of pipelines'
optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations'
- optional :name, type: String, desc: 'The name of the user who triggered pipelines'
+ optional :name, type: String, desc: '(deprecated) The name of the user who triggered pipelines'
optional :username, type: String, desc: 'The username of the user who triggered pipelines'
optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
@@ -52,13 +52,14 @@ module API
desc: 'Order pipelines'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Sort pipelines'
+ optional :source, type: String, values: ::Ci::Pipeline.sources.keys
end
get ':id/pipelines' do
authorize! :read_pipeline, user_project
authorize! :read_build, user_project
pipelines = ::Ci::PipelinesFinder.new(user_project, current_user, params).execute
- present paginate(pipelines), with: Entities::Ci::PipelineBasic
+ present paginate(pipelines), with: Entities::Ci::PipelineBasic, project: user_project
end
desc 'Create a new pipeline' do
@@ -78,12 +79,11 @@ module API
.merge(variables_attributes: params[:variables])
.except(:variables)
- new_pipeline = ::Ci::CreatePipelineService.new(user_project,
- current_user,
- pipeline_params)
- .execute(:api, ignore_skip_ci: true, save_on_errors: false)
+ response = ::Ci::CreatePipelineService.new(user_project, current_user, pipeline_params)
+ .execute(:api, ignore_skip_ci: true, save_on_errors: false)
+ new_pipeline = response.payload
- if new_pipeline.persisted?
+ if response.success?
present new_pipeline, with: Entities::Ci::Pipeline
else
render_validation_error!(new_pipeline)
@@ -188,6 +188,19 @@ module API
present pipeline.test_reports, with: TestReportEntity, details: true
end
+ desc 'Gets the test report summary for a given pipeline' do
+ detail 'This feature was introduced in GitLab 14.2'
+ success TestReportSummaryEntity
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ get ':id/pipelines/:pipeline_id/test_report_summary' do
+ authorize! :read_build, pipeline
+
+ present pipeline.test_report_summary, with: TestReportSummaryEntity
+ end
+
desc 'Deletes a pipeline' do
detail 'This feature was introduced in GitLab 11.6'
http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']]
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index 0bac6fe2054..aabcf34952c 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -3,12 +3,10 @@
module API
module Ci
class Runner < ::API::Base
- helpers ::API::Helpers::Runner
+ helpers ::API::Ci::Helpers::Runner
content_type :txt, 'text/plain'
- feature_category :runner
-
resource :runners do
desc 'Registers a new Runner' do
success Entities::Ci::RunnerRegistrationDetails
@@ -26,7 +24,7 @@ module API
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: %q(List of Runner's tags)
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
end
- post '/' do
+ post '/', feature_category: :runner do
attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
.merge(get_runner_details_from_request)
@@ -59,7 +57,7 @@ module API
params do
requires :token, type: String, desc: %q(Runner's authentication token)
end
- delete '/' do
+ delete '/', feature_category: :runner do
authenticate_runner!
destroy_conditionally!(current_runner)
@@ -71,7 +69,7 @@ module API
params do
requires :token, type: String, desc: %q(Runner's authentication token)
end
- post '/verify' do
+ post '/verify', feature_category: :runner do
authenticate_runner!
status 200
body "200"
@@ -123,7 +121,7 @@ module API
formatter :build_json, ->(object, _) { object }
parser :build_json, ::Grape::Parser::Json
- post '/request' do
+ post '/request', feature_category: :continuous_integration do
authenticate_runner!
unless current_runner.active?
@@ -177,7 +175,7 @@ module API
end
optional :exit_code, type: Integer, desc: %q(Job's exit code)
end
- put '/:id' do
+ put '/:id', feature_category: :continuous_integration do
job = authenticate_job!
Gitlab::Metrics.add_event(:update_build)
@@ -204,7 +202,7 @@ module API
requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
end
- patch '/:id/trace' do
+ patch '/:id/trace', feature_category: :continuous_integration do
job = authenticate_job!
error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
@@ -249,7 +247,7 @@ module API
optional :artifact_type, type: String, desc: %q(The type of artifact),
default: 'archive', values: ::Ci::JobArtifact.file_types.keys
end
- post '/:id/artifacts/authorize' do
+ post '/:id/artifacts/authorize', feature_category: :build_artifacts do
not_allowed! unless Gitlab.config.artifacts.enabled
require_gitlab_workhorse!
@@ -285,7 +283,7 @@ module API
default: 'zip', values: ::Ci::JobArtifact.file_formats.keys
optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware))
end
- post '/:id/artifacts' do
+ post '/:id/artifacts', feature_category: :build_artifacts do
not_allowed! unless Gitlab.config.artifacts.enabled
require_gitlab_workhorse!
@@ -314,7 +312,7 @@ module API
optional :token, type: String, desc: %q(Job's authentication token)
optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
end
- get '/:id/artifacts' do
+ get '/:id/artifacts', feature_category: :build_artifacts do
job = authenticate_job!(require_running: false)
present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
diff --git a/lib/api/ci/triggers.rb b/lib/api/ci/triggers.rb
new file mode 100644
index 00000000000..6a2b16e1568
--- /dev/null
+++ b/lib/api/ci/triggers.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ class Triggers < ::API::Base
+ include PaginationParams
+
+ HTTP_GITLAB_EVENT_HEADER = "HTTP_#{WebHookService::GITLAB_EVENT_HEADER}".underscore.upcase
+
+ feature_category :continuous_integration
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Trigger a GitLab project pipeline' do
+ success Entities::Ci::Pipeline
+ end
+ params do
+ requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false
+ requires :token, type: String, desc: 'The unique token of trigger or job token'
+ optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
+ end
+ post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20758')
+
+ forbidden! if gitlab_pipeline_hook_request?
+
+ # validate variables
+ 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
+
+ project = find_project(params[:id])
+ not_found! unless project
+
+ result = ::Ci::PipelineTriggerService.new(project, nil, params).execute
+ not_found! unless result
+
+ if result.error?
+ render_api_error!(result[:message], result[:http_status])
+ else
+ present result[:pipeline], with: Entities::Ci::Pipeline
+ end
+ end
+
+ desc 'Get triggers list' do
+ success Entities::Trigger
+ end
+ params do
+ use :pagination
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ get ':id/triggers' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ triggers = user_project.triggers.includes(:trigger_requests)
+
+ present paginate(triggers), with: Entities::Trigger, current_user: current_user
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ desc 'Get specific trigger of a project' do
+ success Entities::Trigger
+ end
+ params do
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ end
+ get ':id/triggers/:trigger_id' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
+ break not_found!('Trigger') unless trigger
+
+ present trigger, with: Entities::Trigger, current_user: current_user
+ end
+
+ desc 'Create a trigger' do
+ success Entities::Trigger
+ end
+ params do
+ requires :description, type: String, desc: 'The trigger description'
+ end
+ post ':id/triggers' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.create(
+ declared_params(include_missing: false).merge(owner: current_user))
+
+ if trigger.valid?
+ present trigger, with: Entities::Trigger, current_user: current_user
+ else
+ render_validation_error!(trigger)
+ end
+ end
+
+ desc 'Update a trigger' do
+ success Entities::Trigger
+ end
+ params do
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ optional :description, type: String, desc: 'The trigger description'
+ end
+ put ':id/triggers/:trigger_id' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
+ break not_found!('Trigger') unless trigger
+
+ authorize! :admin_trigger, trigger
+
+ if trigger.update(declared_params(include_missing: false))
+ present trigger, with: Entities::Trigger, current_user: current_user
+ else
+ render_validation_error!(trigger)
+ end
+ end
+
+ desc 'Delete a trigger' do
+ success Entities::Trigger
+ end
+ params do
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ end
+ delete ':id/triggers/:trigger_id' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
+ break not_found!('Trigger') unless trigger
+
+ destroy_conditionally!(trigger)
+ end
+ end
+
+ helpers do
+ def gitlab_pipeline_hook_request?
+ request.get_header(HTTP_GITLAB_EVENT_HEADER) == WebHookService.hook_to_event(:pipeline_hooks)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/ci/variables.rb b/lib/api/ci/variables.rb
new file mode 100644
index 00000000000..9c04d5e9923
--- /dev/null
+++ b/lib/api/ci/variables.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+module API
+ module Ci
+ class Variables < ::API::Base
+ include PaginationParams
+
+ before { authenticate! }
+ before { authorize! :admin_build, user_project }
+
+ feature_category :pipeline_authoring
+
+ helpers ::API::Helpers::VariablesHelpers
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get project variables' do
+ success Entities::Ci::Variable
+ end
+ params do
+ use :pagination
+ end
+ get ':id/variables' do
+ variables = user_project.variables
+ present paginate(variables), with: Entities::Ci::Variable
+ end
+
+ desc 'Get a specific variable from a project' do
+ success Entities::Ci::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ get ':id/variables/:key' do
+ variable = find_variable(user_project, params)
+ not_found!('Variable') unless variable
+
+ present variable, with: Entities::Ci::Variable
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ desc 'Create a new variable in a project' do
+ success Entities::Ci::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ requires :value, type: String, desc: 'The value of the variable'
+ optional :protected, type: Boolean, desc: 'Whether the variable is protected'
+ optional :masked, type: Boolean, desc: 'Whether the variable is masked'
+ optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+ optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
+ end
+ post ':id/variables' do
+ variable = ::Ci::ChangeVariableService.new(
+ container: user_project,
+ current_user: current_user,
+ params: { action: :create, variable_params: declared_params(include_missing: false) }
+ ).execute
+
+ if variable.valid?
+ present variable, with: Entities::Ci::Variable
+ else
+ render_validation_error!(variable)
+ end
+ end
+
+ desc 'Update an existing variable from a project' do
+ success Entities::Ci::Variable
+ end
+ params do
+ optional :key, type: String, desc: 'The key of the variable'
+ optional :value, type: String, desc: 'The value of the variable'
+ optional :protected, type: Boolean, desc: 'Whether the variable is protected'
+ optional :masked, type: Boolean, desc: 'Whether the variable is masked'
+ optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+ optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
+ optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production'
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ put ':id/variables/:key' do
+ variable = find_variable(user_project, params)
+ not_found!('Variable') unless variable
+
+ variable = ::Ci::ChangeVariableService.new(
+ container: user_project,
+ current_user: current_user,
+ params: { action: :update, variable: variable, variable_params: declared_params(include_missing: false).except(:key, :filter) }
+ ).execute
+
+ if variable.valid?
+ present variable, with: Entities::Ci::Variable
+ else
+ render_validation_error!(variable)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ desc 'Delete an existing variable from a project' do
+ success Entities::Ci::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production'
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ delete ':id/variables/:key' do
+ variable = find_variable(user_project, params)
+ not_found!('Variable') unless variable
+
+ ::Ci::ChangeVariableService.new(
+ container: user_project,
+ current_user: current_user,
+ params: { action: :destroy, variable: variable }
+ ).execute
+
+ no_content!
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 541a37b0abe..5d8985455ad 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -47,7 +47,7 @@ module API
path = params[:path]
before = params[:until]
after = params[:since]
- ref = params[:ref_name].presence || user_project.try(:default_branch) || 'master' unless params[:all]
+ ref = params[:ref_name].presence || user_project.default_branch unless params[:all]
offset = (params[:page] - 1) * params[:per_page]
all = params[:all]
with_stats = params[:with_stats]
diff --git a/lib/api/concerns/packages/debian_distribution_endpoints.rb b/lib/api/concerns/packages/debian_distribution_endpoints.rb
index 4670c3e3521..798e583b87a 100644
--- a/lib/api/concerns/packages/debian_distribution_endpoints.rb
+++ b/lib/api/concerns/packages/debian_distribution_endpoints.rb
@@ -80,6 +80,8 @@ module API
use :optional_distribution_params
end
get '/' do
+ authorize_read_package!(project_or_group)
+
distribution_params = declared_params(include_missing: false)
distributions = ::Packages::Debian::DistributionsFinder.new(project_or_group, distribution_params).execute
@@ -96,6 +98,8 @@ module API
requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename'
end
get '/:codename' do
+ authorize_read_package!(project_or_group)
+
distribution = ::Packages::Debian::DistributionsFinder.new(project_or_group, codename: params[:codename]).execute.last!
present distribution, with: ::API::Entities::Packages::Debian::Distribution
diff --git a/lib/api/concerns/packages/debian_package_endpoints.rb b/lib/api/concerns/packages/debian_package_endpoints.rb
index 7740ba6bfa6..0acc015f366 100644
--- a/lib/api/concerns/packages/debian_package_endpoints.rb
+++ b/lib/api/concerns/packages/debian_package_endpoints.rb
@@ -6,8 +6,6 @@ module API
module DebianPackageEndpoints
extend ActiveSupport::Concern
- LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze
- PACKAGE_REGEX = API::NO_SLASH_URL_PART_REGEX
DISTRIBUTION_REQUIREMENTS = {
distribution: ::Packages::Debian::DISTRIBUTION_REGEX
}.freeze
@@ -15,14 +13,6 @@ module API
component: ::Packages::Debian::COMPONENT_REGEX,
architecture: ::Packages::Debian::ARCHITECTURE_REGEX
}.freeze
- COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS = {
- component: ::Packages::Debian::COMPONENT_REGEX,
- letter: LETTER_REGEX,
- source_package: PACKAGE_REGEX
- }.freeze
- FILE_NAME_REQUIREMENTS = {
- file_name: API::NO_SLASH_URL_PART_REGEX
- }.freeze
included do
feature_category :package_registry
@@ -31,109 +21,106 @@ module API
helpers ::API::Helpers::Packages::BasicAuthHelpers
include ::API::Helpers::Authentication
- namespace 'packages/debian' do
- authenticate_with do |accept|
- accept.token_types(:personal_access_token, :deploy_token, :job_token)
- .sent_through(:http_basic_auth)
+ helpers do
+ params :shared_package_file_params do
+ requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex
+ requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)'
+ requires :package_name, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex
+ requires :package_version, type: String, desc: 'The Debian Source Package Version', regexp: Gitlab::Regex.debian_version_regex
+ requires :file_name, type: String, desc: 'The Debian File Name'
end
- helpers do
- def present_release_file
- distribution = ::Packages::Debian::DistributionsFinder.new(project_or_group, codename_or_suite: params[:distribution]).execute.last!
-
- present_carrierwave_file!(distribution.file)
- end
+ def distribution_from!(container)
+ ::Packages::Debian::DistributionsFinder.new(container, codename_or_suite: params[:distribution]).execute.last!
end
- format :txt
- content_type :txt, 'text/plain'
+ def present_package_file!
+ not_found! unless params[:package_name].start_with?(params[:letter])
- params do
- requires :distribution, type: String, desc: 'The Debian Codename', regexp: Gitlab::Regex.debian_distribution_regex
+ package_file = distribution_from!(user_project).package_files.with_file_name(params[:file_name]).last!
+
+ present_carrierwave_file!(package_file.file)
end
+ end
- namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do
- # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release.gpg
- desc 'The Release file signature' do
- detail 'This feature was introduced in GitLab 13.5'
- end
+ authenticate_with do |accept|
+ accept.token_types(:personal_access_token, :deploy_token, :job_token)
+ .sent_through(:http_basic_auth)
+ end
- route_setting :authentication, authenticate_non_public: true
- get 'Release.gpg' do
- not_found!
- end
+ rescue_from ArgumentError do |e|
+ render_api_error!(e.message, 400)
+ end
- # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release
- desc 'The unsigned Release file' do
- detail 'This feature was introduced in GitLab 13.5'
- end
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
- route_setting :authentication, authenticate_non_public: true
- get 'Release' do
- present_release_file
- end
+ format :txt
+ content_type :txt, 'text/plain'
- # GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease
- desc 'The signed Release file' do
- detail 'This feature was introduced in GitLab 13.5'
- end
+ params do
+ requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex
+ end
- route_setting :authentication, authenticate_non_public: true
- get 'InRelease' do
- # Signature to be added in 7.3 of https://gitlab.com/groups/gitlab-org/-/epics/6057#note_582697034
- present_release_file
- end
+ namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do
+ # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release.gpg
+ desc 'The Release file signature' do
+ detail 'This feature was introduced in GitLab 13.5'
+ end
- params do
- requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex
- requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex
- end
+ route_setting :authentication, authenticate_non_public: true
+ get 'Release.gpg' do
+ distribution_from!(project_or_group).file_signature
+ end
- namespace ':component/binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do
- # GET {projects|groups}/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages
- desc 'The binary files index' do
- detail 'This feature was introduced in GitLab 13.5'
- end
-
- route_setting :authentication, authenticate_non_public: true
- get 'Packages' do
- relation = "::Packages::Debian::#{project_or_group.class.name}ComponentFile".constantize
-
- component_file = relation
- .preload_distribution
- .with_container(project_or_group)
- .with_codename_or_suite(params[:distribution])
- .with_component_name(params[:component])
- .with_file_type(:packages)
- .with_architecture_name(params[:architecture])
- .with_compression_type(nil)
- .order_created_asc
- .last!
-
- present_carrierwave_file!(component_file.file)
- end
- end
+ # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release
+ desc 'The unsigned Release file' do
+ detail 'This feature was introduced in GitLab 13.5'
+ end
+
+ route_setting :authentication, authenticate_non_public: true
+ get 'Release' do
+ present_carrierwave_file!(distribution_from!(project_or_group).file)
+ end
+
+ # GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease
+ desc 'The signed Release file' do
+ detail 'This feature was introduced in GitLab 13.5'
+ end
+
+ route_setting :authentication, authenticate_non_public: true
+ get 'InRelease' do
+ present_carrierwave_file!(distribution_from!(project_or_group).signed_file)
end
params do
requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex
- requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)'
- requires :source_package, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex
+ requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex
end
- namespace 'pool/:component/:letter/:source_package', requirements: COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS do
- # GET {projects|groups}/:id/packages/debian/pool/:component/:letter/:source_package/:file_name
- params do
- requires :file_name, type: String, desc: 'The Debian File Name'
- end
- desc 'The package' do
+ namespace ':component/binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do
+ # GET {projects|groups}/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages
+ desc 'The binary files index' do
detail 'This feature was introduced in GitLab 13.5'
end
route_setting :authentication, authenticate_non_public: true
- get ':file_name', requirements: FILE_NAME_REQUIREMENTS do
- # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286
- 'TODO File'
+ get 'Packages' do
+ relation = "::Packages::Debian::#{project_or_group.class.name}ComponentFile".constantize
+
+ component_file = relation
+ .preload_distribution
+ .with_container(project_or_group)
+ .with_codename_or_suite(params[:distribution])
+ .with_component_name(params[:component])
+ .with_file_type(:packages)
+ .with_architecture_name(params[:architecture])
+ .with_compression_type(nil)
+ .order_created_asc
+ .last!
+
+ present_carrierwave_file!(component_file.file)
end
end
end
diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb
index 191ed42a5b8..29f5047230a 100644
--- a/lib/api/debian_group_packages.rb
+++ b/lib/api/debian_group_packages.rb
@@ -2,35 +2,50 @@
module API
class DebianGroupPackages < ::API::Base
- params do
- requires :id, type: String, desc: 'The ID of a group'
- end
+ PACKAGE_FILE_REQUIREMENTS = ::API::DebianProjectPackages::PACKAGE_FILE_REQUIREMENTS.merge(
+ project_id: %r{[0-9]+}.freeze
+ ).freeze
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- rescue_from ArgumentError do |e|
- render_api_error!(e.message, 400)
- end
+ helpers do
+ def user_project
+ @project ||= find_project!(params[:project_id])
+ end
- rescue_from ActiveRecord::RecordInvalid do |e|
- render_api_error!(e.message, 400)
+ def project_or_group
+ user_group
+ end
end
- before do
+ after_validation do
require_packages_enabled!
- not_found! unless ::Feature.enabled?(:debian_packages, user_group)
+ not_found! unless ::Feature.enabled?(:debian_group_packages, user_group)
authorize_read_package!(user_group)
end
- namespace ':id/-' do
- helpers do
- def project_or_group
- user_group
- end
- end
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ namespace ':id/-/packages/debian' do
include ::API::Concerns::Packages::DebianPackageEndpoints
+
+ # GET groups/:id/packages/debian/pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name
+ params do
+ requires :project_id, type: Integer, desc: 'The Project Id'
+ use :shared_package_file_params
+ end
+
+ desc 'The package' do
+ detail 'This feature was introduced in GitLab 14.2'
+ end
+
+ route_setting :authentication, authenticate_non_public: true
+ get 'pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do
+ present_package_file!
+ end
end
end
end
diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb
index 70ddf9dea37..497ce2f4356 100644
--- a/lib/api/debian_project_packages.rb
+++ b/lib/api/debian_project_packages.rb
@@ -2,17 +2,23 @@
module API
class DebianProjectPackages < ::API::Base
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
+ PACKAGE_FILE_REQUIREMENTS = {
+ id: API::NO_SLASH_URL_PART_REGEX,
+ distribution: ::Packages::Debian::DISTRIBUTION_REGEX,
+ letter: ::Packages::Debian::LETTER_REGEX,
+ package_name: API::NO_SLASH_URL_PART_REGEX,
+ package_version: API::NO_SLASH_URL_PART_REGEX,
+ file_name: API::NO_SLASH_URL_PART_REGEX
+ }.freeze
+ FILE_NAME_REQUIREMENTS = {
+ file_name: API::NO_SLASH_URL_PART_REGEX
+ }.freeze
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- rescue_from ArgumentError do |e|
- render_api_error!(e.message, 400)
- end
-
- rescue_from ActiveRecord::RecordInvalid do |e|
- render_api_error!(e.message, 400)
+ helpers do
+ def project_or_group
+ user_project
+ end
end
after_validation do
@@ -23,20 +29,32 @@ module API
authorize_read_package!
end
- namespace ':id' do
- helpers do
- def project_or_group
- user_project
- end
- end
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ namespace ':id/packages/debian' do
include ::API::Concerns::Packages::DebianPackageEndpoints
+ # GET projects/:id/packages/debian/pool/:distribution/:letter/:package_name/:package_version/:file_name
+ params do
+ use :shared_package_file_params
+ end
+
+ desc 'The package' do
+ detail 'This feature was introduced in GitLab 14.2'
+ end
+
+ route_setting :authentication, authenticate_non_public: true
+ get 'pool/:distribution/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do
+ present_package_file!
+ end
+
params do
requires :file_name, type: String, desc: 'The file name'
end
- namespace 'packages/debian/:file_name', requirements: FILE_NAME_REQUIREMENTS do
+ namespace ':file_name', requirements: FILE_NAME_REQUIREMENTS do
format :txt
content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
diff --git a/lib/api/entities/ci/job_request/dependency.rb b/lib/api/entities/ci/job_request/dependency.rb
index 2c6ed417714..2672a4a245b 100644
--- a/lib/api/entities/ci/job_request/dependency.rb
+++ b/lib/api/entities/ci/job_request/dependency.rb
@@ -6,7 +6,7 @@ module API
module JobRequest
class Dependency < Grape::Entity
expose :id, :name, :token
- expose :artifacts_file, using: Entities::Ci::JobArtifactFile, if: ->(job, _) { job.artifacts? }
+ expose :artifacts_file, using: Entities::Ci::JobArtifactFile, if: ->(job, _) { job.available_artifacts? }
end
end
end
diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb
index f4f2356c812..8086062dc9b 100644
--- a/lib/api/entities/ci/pipeline_basic.rb
+++ b/lib/api/entities/ci/pipeline_basic.rb
@@ -7,6 +7,8 @@ module API
expose :id, :project_id, :sha, :ref, :status
expose :created_at, :updated_at
+ expose :source, if: ->(pipeline, options) { ::Feature.enabled?(:pipeline_source_filter, options[:project], default_enabled: :yaml) }
+
expose :web_url do |pipeline, _options|
Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline)
end
diff --git a/lib/api/entities/error_tracking.rb b/lib/api/entities/error_tracking.rb
index c762c274486..a38e00ca295 100644
--- a/lib/api/entities/error_tracking.rb
+++ b/lib/api/entities/error_tracking.rb
@@ -8,6 +8,7 @@ module API
expose :project_name
expose :sentry_external_url
expose :api_url
+ expose :integrated
end
end
end
diff --git a/lib/api/entities/issue_basic.rb b/lib/api/entities/issue_basic.rb
index 6c332870228..ab248523028 100644
--- a/lib/api/entities/issue_basic.rb
+++ b/lib/api/entities/issue_basic.rb
@@ -23,7 +23,7 @@ module API
expose :issue_type,
as: :type,
format_with: :upcase,
- documentation: { type: "String", desc: "One of #{::Issue.issue_types.keys.map(&:upcase)}" }
+ documentation: { type: "String", desc: "One of #{::WorkItem::Type.base_types.keys.map(&:upcase)}" }
expose :assignee, using: ::API::Entities::UserBasic do |issue|
issue.assignees.first
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index f5f565e5b07..890b42ed8c8 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -71,6 +71,7 @@ module API
expose(:pages_access_level) { |project, options| project.project_feature.string_access_level(:pages) }
expose(:operations_access_level) { |project, options| project.project_feature.string_access_level(:operations) }
expose(:analytics_access_level) { |project, options| project.project_feature.string_access_level(:analytics) }
+ expose(:container_registry_access_level) { |project, options| project.project_feature.string_access_level(:container_registry) }
expose :emails_disabled
expose :shared_runners_enabled
diff --git a/lib/api/entities/project_with_access.rb b/lib/api/entities/project_with_access.rb
index c53a712a879..ac89cb52e43 100644
--- a/lib/api/entities/project_with_access.rb
+++ b/lib/api/entities/project_with_access.rb
@@ -26,8 +26,10 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {})
relation = super(projects_relation, options)
- project_ids = relation.select('projects.id')
- namespace_ids = relation.select(:namespace_id)
+ # use reselect to override the existing select and
+ # prevent an error `subquery has too many columns`
+ project_ids = relation.reselect('projects.id')
+ namespace_ids = relation.reselect(:namespace_id)
options[:project_members] = options[:current_user]
.project_members
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 57e548183b0..e50da4264b5 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -77,7 +77,7 @@ module API
desc "Delete multiple stopped review apps" do
detail "Remove multiple stopped review environments older than a specific age"
- success Entities::Environment
+ success Entities::EnvironmentBasic
end
params do
optional :before, type: Time, desc: "The timestamp before which environments can be deleted. Defaults to 30 days ago.", default: -> { 30.days.ago }
@@ -90,8 +90,8 @@ module API
result = ::Environments::ScheduleToDeleteReviewAppsService.new(user_project, current_user, params).execute
response = {
- scheduled_entries: Entities::Environment.represent(result.scheduled_entries),
- unprocessable_entries: Entities::Environment.represent(result.unprocessable_entries)
+ scheduled_entries: Entities::EnvironmentBasic.represent(result.scheduled_entries),
+ unprocessable_entries: Entities::EnvironmentBasic.represent(result.unprocessable_entries)
}
if result.success?
diff --git a/lib/api/error_tracking.rb b/lib/api/error_tracking.rb
index 0e44c8b1081..3abf2831bd3 100644
--- a/lib/api/error_tracking.rb
+++ b/lib/api/error_tracking.rb
@@ -32,6 +32,7 @@ module API
end
params do
requires :active, type: Boolean, desc: 'Specifying whether to enable or disable error tracking settings', allow_blank: false
+ optional :integrated, type: Boolean, desc: 'Specifying whether to enable or disable integrated error tracking'
end
patch ':id/error_tracking/settings/' do
@@ -45,6 +46,10 @@ module API
error_tracking_setting_attributes: { enabled: params[:active] }
}
+ unless params[:integrated].nil?
+ update_params[:error_tracking_setting_attributes][:integrated] = params[:integrated]
+ end
+
result = ::Projects::Operations::UpdateService.new(user_project, current_user, update_params).execute
if result[:status] == :success
diff --git a/lib/api/error_tracking_collector.rb b/lib/api/error_tracking_collector.rb
index 08ff8d2e4d1..13e8e476808 100644
--- a/lib/api/error_tracking_collector.rb
+++ b/lib/api/error_tracking_collector.rb
@@ -13,6 +13,7 @@ module API
before do
not_found!('Project') unless project
not_found! unless feature_enabled?
+ not_found! unless active_client_key?
end
helpers do
@@ -21,8 +22,24 @@ module API
end
def feature_enabled?
- ::Feature.enabled?(:integrated_error_tracking, project) &&
- project.error_tracking_setting&.enabled?
+ project.error_tracking_setting&.enabled? &&
+ project.error_tracking_setting&.integrated_client?
+ end
+
+ def find_client_key(public_key)
+ return unless public_key.present?
+
+ project.error_tracking_client_keys.active.find_by_public_key(public_key)
+ end
+
+ def active_client_key?
+ begin
+ public_key = ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key]
+ rescue StandardError
+ bad_request!('Failed to parse sentry request')
+ end
+
+ find_client_key(public_key)
end
end
@@ -46,7 +63,7 @@ module API
begin
parsed_request = ::ErrorTracking::Collector::SentryRequestParser.parse(request)
rescue StandardError
- render_api_error!('Failed to parse sentry request', 400)
+ bad_request!('Failed to parse sentry request')
end
type = parsed_request[:request_type]
@@ -67,6 +84,9 @@ module API
.execute
end
+ # Collector should never return any information back.
+ # Because DSN and public key are designed for public use,
+ # it is safe only for submission of new events.
no_content!
end
end
diff --git a/lib/api/group_debian_distributions.rb b/lib/api/group_debian_distributions.rb
new file mode 100644
index 00000000000..01a8774bd97
--- /dev/null
+++ b/lib/api/group_debian_distributions.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module API
+ class GroupDebianDistributions < ::API::Base
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ rescue_from ArgumentError do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ rescue_from ActiveRecord::RecordInvalid do |e|
+ render_api_error!(e.message, 400)
+ end
+
+ after_validation do
+ require_packages_enabled!
+
+ not_found! unless ::Feature.enabled?(:debian_group_packages, user_group)
+ end
+
+ namespace ':id/-' do
+ helpers do
+ def project_or_group
+ user_group
+ end
+ end
+
+ include ::API::Concerns::Packages::DebianDistributionEndpoints
+ end
+ end
+ end
+end
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index 8d52a0a5b4e..13daf05fc78 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -8,7 +8,7 @@ module API
before { authorize! :admin_group, user_group }
feature_category :continuous_integration
- helpers Helpers::VariablesHelpers
+ helpers ::API::Helpers::VariablesHelpers
params do
requires :id, type: String, desc: 'The ID of a group'
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9b6b28733ff..0896357cc73 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -35,7 +35,8 @@ module API
:all_available,
:custom_attributes,
:owned, :min_access_level,
- :include_parent_descendants
+ :include_parent_descendants,
+ :search
)
find_params[:parent] = if params[:top_level_only]
@@ -48,7 +49,6 @@ module API
find_params.fetch(:all_available, current_user&.can_read_all_resources?)
groups = GroupsFinder.new(current_user, find_params).execute
- groups = groups.search(params[:search], include_parents: true) if params[:search].present?
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
order_groups(groups)
@@ -128,10 +128,6 @@ module API
groups.reorder(group_without_similarity_options) # rubocop: disable CodeReuse/ActiveRecord
end
- def order_by_similarity?
- params[:order_by] == 'similarity' && params[:search].present?
- end
-
def group_without_similarity_options
order_options = { params[:order_by] => params[:sort] }
order_options['name'] = order_options.delete('similarity') if order_options.has_key?('similarity')
@@ -141,7 +137,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def handle_similarity_order(group, projects)
- if params[:search].present? && Feature.enabled?(:similarity_search, group, default_enabled: true)
+ if params[:search].present?
projects.sorted_by_similarity_desc(params[:search])
else
order_options = { name: :asc }
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3398d5da7f5..9c347148fd0 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -577,6 +577,10 @@ module API
Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}")
end
+ def order_by_similarity?(allow_unauthorized: true)
+ params[:order_by] == 'similarity' && params[:search].present? && (allow_unauthorized || current_user.present?)
+ end
+
protected
def project_finder_params_visibility_ce
diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb
index e38213532ba..72bdb32d38c 100644
--- a/lib/api/helpers/groups_helpers.rb
+++ b/lib/api/helpers/groups_helpers.rb
@@ -23,7 +23,7 @@ module API
optional :mentions_disabled, type: Boolean, desc: 'Disable a group from getting mentioned'
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 :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to master'
+ optional :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to default branch'
optional :shared_runners_setting, type: String, values: ::Namespace::SHARED_RUNNERS_SETTINGS, desc: 'Enable/disable shared runners for the group and its subgroups and projects'
end
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index bd0c2501220..e72bbb931f0 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -54,6 +54,14 @@ module API
source.add_user(user, params[:access_level], current_user: current_user, expires_at: params[:expires_at])
end
+ def track_areas_of_focus(member, areas_of_focus)
+ return unless areas_of_focus
+
+ areas_of_focus.each do |area_of_focus|
+ Gitlab::Tracking.event(::Members::CreateService.name, 'area_of_focus', label: area_of_focus, property: member.id.to_s)
+ end
+ end
+
def present_members(members)
present members, with: Entities::Member, current_user: current_user, show_seat_info: params[:show_seat_info]
end
diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb
index 989c4e1761b..b8ae1dddd7e 100644
--- a/lib/api/helpers/packages/dependency_proxy_helpers.rb
+++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb
@@ -5,11 +5,17 @@ module API
module Packages
module DependencyProxyHelpers
REGISTRY_BASE_URLS = {
- npm: 'https://registry.npmjs.org/'
+ npm: 'https://registry.npmjs.org/',
+ pypi: 'https://pypi.org/simple/'
+ }.freeze
+
+ APPLICATION_SETTING_NAMES = {
+ npm: 'npm_package_requests_forwarding',
+ pypi: 'pypi_package_requests_forwarding'
}.freeze
def redirect_registry_request(forward_to_registry, package_type, options)
- if forward_to_registry && redirect_registry_request_available?
+ if forward_to_registry && redirect_registry_request_available?(package_type)
::Gitlab::Tracking.event(self.options[:for].name, "#{package_type}_request_forward")
redirect(registry_url(package_type, options))
else
@@ -25,11 +31,20 @@ module API
case package_type
when :npm
"#{base_url}#{options[:package_name]}"
+ when :pypi
+ "#{base_url}#{options[:package_name]}/"
end
end
- def redirect_registry_request_available?
- ::Gitlab::CurrentSettings.current_application_settings.npm_package_requests_forwarding
+ def redirect_registry_request_available?(package_type)
+ application_setting_name = APPLICATION_SETTING_NAMES[package_type]
+
+ raise ArgumentError, "Can't find application setting for package_type #{package_type}" unless application_setting_name
+
+ ::Gitlab::CurrentSettings
+ .current_application_settings
+ .attributes
+ .fetch(application_setting_name, false)
end
end
end
diff --git a/lib/api/helpers/packages/npm.rb b/lib/api/helpers/packages/npm.rb
index 2d556f889bf..ce5db52fdbc 100644
--- a/lib/api/helpers/packages/npm.rb
+++ b/lib/api/helpers/packages/npm.rb
@@ -49,28 +49,20 @@ module API
when :project
params[:id]
when :instance
- namespace_path = namespace_path_from_package_name
+ package_name = params[:package_name]
+ namespace_path = ::Packages::Npm.scope_of(package_name)
next unless namespace_path
namespace = Namespace.top_most
.by_path(namespace_path)
next unless namespace
- finder = ::Packages::Npm::PackageFinder.new(params[:package_name], namespace: namespace)
+ finder = ::Packages::Npm::PackageFinder.new(package_name, namespace: namespace)
finder.last&.project_id
end
end
end
-
- # from "@scope/package-name" return "scope" or nil
- def namespace_path_from_package_name
- package_name = params[:package_name]
- return unless package_name.starts_with?('@')
- return unless package_name.include?('/')
-
- package_name.match(Gitlab::Regex.npm_package_name_regex)&.captures&.first
- end
end
end
end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 272452bd8db..becd25595a6 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -35,13 +35,14 @@ module API
optional :pages_access_level, type: String, values: %w(disabled private enabled public), desc: 'Pages access level. One of `disabled`, `private`, `enabled` or `public`'
optional :operations_access_level, type: String, values: %w(disabled private enabled), desc: 'Operations access level. One of `disabled`, `private` or `enabled`'
optional :analytics_access_level, type: String, values: %w(disabled private enabled), desc: 'Analytics access level. One of `disabled`, `private` or `enabled`'
+ optional :container_registry_access_level, type: String, values: %w(disabled private enabled), desc: 'Controls visibility of the container registry. One of `disabled`, `private` or `enabled`. `private` will make the container registry accessible only to project members (reporter role and above). `enabled` will make the container registry accessible to everyone who has access to the project. `disabled` will disable the container registry'
optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge'
- optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
+ optional :container_registry_enabled, type: Boolean, desc: 'Deprecated: Use :container_registry_access_level instead. Flag indication if the container registry is enabled for that project'
optional :container_expiration_policy_attributes, type: Hash do
use :optional_container_expiration_policy_params
end
@@ -124,7 +125,7 @@ module API
:ci_config_path,
:ci_default_git_depth,
:ci_forward_deployment_enabled,
- :container_registry_enabled,
+ :container_registry_access_level,
:container_expiration_policy_attributes,
:default_branch,
:description,
@@ -132,7 +133,10 @@ module API
:forking_access_level,
:issues_access_level,
:lfs_enabled,
+ :merge_pipelines_enabled,
:merge_requests_access_level,
+ :merge_requests_template,
+ :merge_trains_enabled,
:merge_method,
:name,
:only_allow_merge_if_all_discussions_are_resolved,
@@ -166,7 +170,8 @@ module API
:jobs_enabled,
:merge_requests_enabled,
:wiki_enabled,
- :snippets_enabled
+ :snippets_enabled,
+ :container_registry_enabled
]
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
deleted file mode 100644
index a022d1a56ac..00000000000
--- a/lib/api/helpers/runner.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Helpers
- module Runner
- include Gitlab::Utils::StrongMemoize
-
- prepend_mod_with('API::Helpers::Runner') # rubocop: disable Cop/InjectEnterpriseEditionModule
-
- JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'
- JOB_TOKEN_PARAM = :token
-
- def runner_registration_token_valid?
- ActiveSupport::SecurityUtils.secure_compare(params[:token], Gitlab::CurrentSettings.runners_registration_token)
- end
-
- def runner_registrar_valid?(type)
- Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
- end
-
- def authenticate_runner!
- forbidden! unless current_runner
-
- current_runner
- .heartbeat(get_runner_details_from_request)
- end
-
- def get_runner_details_from_request
- return get_runner_ip unless params['info'].present?
-
- attributes_for_keys(%w(name version revision platform architecture), params['info'])
- .merge(get_runner_config_from_request)
- .merge(get_runner_ip)
- end
-
- def get_runner_ip
- { ip_address: ip_address }
- end
-
- def current_runner
- token = params[:token]
-
- if token
- ::Gitlab::Database::LoadBalancing::RackMiddleware
- .stick_or_unstick(env, :runner, token)
- end
-
- strong_memoize(:current_runner) do
- ::Ci::Runner.find_by_token(token.to_s)
- end
- end
-
- # HTTP status codes to terminate the job on GitLab Runner:
- # - 403
- def authenticate_job!(require_running: true)
- job = current_job
-
- # 404 is not returned here because we want to terminate the job if it's
- # running. A 404 can be returned from anywhere in the networking stack which is why
- # we are explicit about a 403, we should improve this in
- # https://gitlab.com/gitlab-org/gitlab/-/issues/327703
- forbidden! unless job
-
- forbidden! unless job_token_valid?(job)
-
- forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete?
- forbidden!('Job has been erased!') if job.erased?
-
- if require_running
- job_forbidden!(job, 'Job is not running') unless job.running?
- end
-
- job.runner&.heartbeat(get_runner_ip)
-
- job
- end
-
- def current_job
- id = params[:id]
-
- if id
- ::Gitlab::Database::LoadBalancing::RackMiddleware
- .stick_or_unstick(env, :build, id)
- end
-
- strong_memoize(:current_job) do
- ::Ci::Build.find_by_id(id)
- end
- end
-
- def job_token_valid?(job)
- token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
- token && job.valid_token?(token)
- end
-
- def job_forbidden!(job, reason)
- header 'Job-Status', job.status
- forbidden!(reason)
- end
-
- def set_application_context
- return unless current_job
-
- Gitlab::ApplicationContext.push(
- user: -> { current_job.user },
- project: -> { current_job.project }
- )
- end
-
- def track_ci_minutes_usage!(_build, _runner)
- # noop: overridden in EE
- end
-
- private
-
- def get_runner_config_from_request
- { config: attributes_for_keys(%w(gpus), params.dig('info', 'config')) }
- end
- end
- end
-end
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index a06b052847d..d740c626557 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -165,9 +165,9 @@ module API
# Check whether an SSH key is known to GitLab
#
get '/authorized_keys', feature_category: :source_code_management do
- fingerprint = Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
+ fingerprint = Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint_sha256
- key = Key.find_by_fingerprint(fingerprint)
+ key = Key.find_by_fingerprint_sha256(fingerprint)
not_found!('Key') if key.nil?
present key, with: Entities::SSHKey
end
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index 46d8c0c958d..1f437ad5bd3 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -24,6 +24,7 @@ module API
requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api'
+ optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
end
post ":id/invitations" do
params[:source] = find_source(source_type, params[:id])
@@ -54,11 +55,11 @@ module API
success Entities::Member
end
params do
- requires :email, type: String, desc: 'The email address of the invitation.'
- optional :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level).'
- optional :expires_at, type: DateTime, desc: 'Date string in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`).'
+ requires :email, type: String, desc: 'The email address of the invitation'
+ optional :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
+ optional :expires_at, type: DateTime, desc: 'Date string in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`)'
end
- put ":id/invitations/:email", requirements: { email: /[^\/]+/ } do
+ put ":id/invitations/:email", requirements: { email: %r{[^/]+} } do
source = find_source(source_type, params.delete(:id))
invite_email = params[:email]
authorize_admin_source!(source_type, source)
@@ -87,7 +88,7 @@ module API
params do
requires :email, type: String, desc: 'The email address of the invitation'
end
- delete ":id/invitations/:email", requirements: { email: /[^\/]+/ } do
+ delete ":id/invitations/:email", requirements: { email: %r{[^/]+} } do
source = find_source(source_type, params[:id])
invite_email = params[:email]
authorize_admin_source!(source_type, source)
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 54013d0e7b4..a6565f913e3 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -74,7 +74,7 @@ module API
desc: 'Return issues sorted in `asc` or `desc` order.'
optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '',
desc: 'Return issues that have no due date (`0`), or whose due date is this week, this month, between two weeks ago and next month, or which are overdue. Accepts: `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`, `0`'
- optional :issue_type, type: String, values: Issue.issue_types.keys, desc: "The type of the issue. Accepts: #{Issue.issue_types.keys.join(', ')}"
+ optional :issue_type, type: String, values: WorkItem::Type.base_types.keys, desc: "The type of the issue. Accepts: #{WorkItem::Type.base_types.keys.join(', ')}"
use :issues_stats_params
use :pagination
@@ -91,7 +91,7 @@ module API
optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked"
- optional :issue_type, type: String, values: Issue.issue_types.keys, desc: "The type of the issue. Accepts: #{Issue.issue_types.keys.join(', ')}"
+ optional :issue_type, type: String, values: WorkItem::Type.base_types.keys, desc: "The type of the issue. Accepts: #{WorkItem::Type.base_types.keys.join(', ')}"
use :optional_issue_params_ee
end
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
deleted file mode 100644
index beda4433e4f..00000000000
--- a/lib/api/job_artifacts.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class JobArtifacts < ::API::Base
- before { authenticate_non_get! }
-
- feature_category :build_artifacts
-
- # EE::API::JobArtifacts would override the following helpers
- helpers do
- def authorize_download_artifacts!
- authorize_read_builds!
- end
- end
-
- prepend_mod_with('API::JobArtifacts') # rubocop: disable Cop/InjectEnterpriseEditionModule
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Download the artifacts archive 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
- route_setting :authentication, job_token_allowed: true
- get ':id/jobs/artifacts/:ref_name/download',
- requirements: { ref_name: /.+/ } do
- authorize_download_artifacts!
-
- latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
- authorize_read_job_artifacts!(latest_build)
-
- present_carrierwave_file!(latest_build.artifacts_file)
- end
-
- desc 'Download a specific file from artifacts archive from a ref' do
- detail 'This feature was introduced in GitLab 11.5'
- end
- params do
- requires :ref_name, type: String, desc: 'The ref from repository'
- requires :job, type: String, desc: 'The name for the job'
- requires :artifact_path, type: String, desc: 'Artifact path'
- end
- route_setting :authentication, job_token_allowed: true
- get ':id/jobs/artifacts/:ref_name/raw/*artifact_path',
- format: false,
- requirements: { ref_name: /.+/ } do
- authorize_download_artifacts!
-
- build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
- authorize_read_job_artifacts!(build)
-
- path = Gitlab::Ci::Build::Artifacts::Path
- .new(params[:artifact_path])
-
- bad_request! unless path.valid?
-
- send_artifacts_entry(build.artifacts_file, path)
- end
-
- desc 'Download the artifacts archive 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
- route_setting :authentication, job_token_allowed: true
- get ':id/jobs/:job_id/artifacts' do
- authorize_download_artifacts!
-
- build = find_build!(params[:job_id])
- authorize_read_job_artifacts!(build)
-
- present_carrierwave_file!(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
- route_setting :authentication, job_token_allowed: true
- get ':id/jobs/:job_id/artifacts/*artifact_path', format: false do
- authorize_download_artifacts!
-
- build = find_build!(params[:job_id])
- authorize_read_job_artifacts!(build)
-
- not_found! unless build.available_artifacts?
-
- path = Gitlab::Ci::Build::Artifacts::Path
- .new(params[:artifact_path])
-
- bad_request! unless path.valid?
-
- send_artifacts_entry(build.artifacts_file, path)
- end
-
- desc 'Keep the artifacts to prevent them from being deleted' do
- success ::API::Entities::Ci::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)
- break not_found!(build) unless build.artifacts?
-
- build.keep_artifacts!
-
- status 200
- present build, with: ::API::Entities::Ci::Job
- end
-
- desc 'Delete the artifacts files from a job' do
- detail 'This feature was introduced in GitLab 11.9'
- end
- params do
- requires :job_id, type: Integer, desc: 'The ID of a job'
- end
- delete ':id/jobs/:job_id/artifacts' do
- authorize_destroy_artifacts!
- build = find_build!(params[:job_id])
- authorize!(:destroy_artifacts, build)
-
- build.erase_erasable_artifacts!
-
- status :no_content
- end
- end
- end
-end
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
deleted file mode 100644
index 723a5b0fa3a..00000000000
--- a/lib/api/jobs.rb
+++ /dev/null
@@ -1,204 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class Jobs < ::API::Base
- include PaginationParams
- before { authenticate! }
-
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- params do
- requires :id, type: String, desc: 'The ID of a project'
- 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 ::Hash
- scope.values
- when ::Array
- scope
- else
- ['unknown']
- end
- }
- end
- end
-
- desc 'Get a projects jobs' do
- success Entities::Ci::Job
- end
- params do
- use :optional_scope
- use :pagination
- end
- # rubocop: disable CodeReuse/ActiveRecord
- get ':id/jobs', feature_category: :continuous_integration do
- authorize_read_builds!
-
- builds = user_project.builds.order('id DESC')
- builds = filter_builds(builds, params[:scope])
-
- builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, :tags, pipeline: :project)
- present paginate(builds), with: Entities::Ci::Job
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Get a specific job of a project' do
- success Entities::Ci::Job
- end
- params do
- requires :job_id, type: Integer, desc: 'The ID of a job'
- end
- get ':id/jobs/:job_id', feature_category: :continuous_integration do
- authorize_read_builds!
-
- build = find_build!(params[:job_id])
-
- present build, with: Entities::Ci::Job
- end
-
- # TODO: We should use `present_disk_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.
- desc 'Get a trace of a specific job of a project'
- params do
- requires :job_id, type: Integer, desc: 'The ID of a job'
- end
- get ':id/jobs/:job_id/trace', feature_category: :continuous_integration do
- authorize_read_builds!
-
- build = find_build!(params[:job_id])
-
- authorize_read_build_trace!(build) if build
-
- header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
- content_type 'text/plain'
- env['api.format'] = :binary
-
- # The trace can be nil bu body method expects a string as an argument.
- trace = build.trace.raw || ''
- body trace
- end
-
- desc 'Cancel a specific job of a project' do
- success Entities::Ci::Job
- end
- params do
- requires :job_id, type: Integer, desc: 'The ID of a job'
- end
- post ':id/jobs/:job_id/cancel', feature_category: :continuous_integration do
- authorize_update_builds!
-
- build = find_build!(params[:job_id])
- authorize!(:update_build, build)
-
- build.cancel
-
- present build, with: Entities::Ci::Job
- end
-
- desc 'Retry a specific build of a project' do
- success Entities::Ci::Job
- end
- params do
- requires :job_id, type: Integer, desc: 'The ID of a build'
- end
- post ':id/jobs/:job_id/retry', feature_category: :continuous_integration do
- authorize_update_builds!
-
- build = find_build!(params[:job_id])
- authorize!(:update_build, build)
- break forbidden!('Job is not retryable') unless build.retryable?
-
- build = ::Ci::Build.retry(build, current_user)
-
- present build, with: Entities::Ci::Job
- end
-
- desc 'Erase job (remove artifacts and the trace)' do
- success Entities::Ci::Job
- end
- params do
- requires :job_id, type: Integer, desc: 'The ID of a build'
- end
- post ':id/jobs/:job_id/erase', feature_category: :continuous_integration do
- authorize_update_builds!
-
- build = find_build!(params[:job_id])
- authorize!(:erase_build, build)
- break forbidden!('Job is not erasable!') unless build.erasable?
-
- build.erase(erased_by: current_user)
- present build, with: Entities::Ci::Job
- end
-
- desc 'Trigger an actionable job (manual, delayed, etc)' do
- success Entities::Ci::JobBasic
- detail 'This feature was added in GitLab 8.11'
- end
- params do
- requires :job_id, type: Integer, desc: 'The ID of a Job'
- end
-
- post ":id/jobs/:job_id/play", feature_category: :continuous_integration do
- authorize_read_builds!
-
- job = find_job!(params[:job_id])
-
- authorize!(:play_job, job)
-
- bad_request!("Unplayable Job") unless job.playable?
-
- job.play(current_user)
-
- status 200
-
- if job.is_a?(::Ci::Build)
- present job, with: Entities::Ci::Job
- else
- present job, with: Entities::Ci::Bridge
- end
- end
- end
-
- resource :job do
- desc 'Get current project using job token' do
- success Entities::Ci::Job
- end
- route_setting :authentication, job_token_allowed: true
- get '', feature_category: :continuous_integration do
- validate_current_authenticated_job
-
- present current_authenticated_job, with: Entities::Ci::Job
- end
- end
-
- helpers do
- # 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: available_statuses && scope)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def validate_current_authenticated_job
- # current_authenticated_job will be nil if user is using
- # a valid authentication (like PRIVATE-TOKEN) that is not CI_JOB_TOKEN
- not_found!('Job') unless current_authenticated_job
- end
- end
- end
-end
-
-API::Jobs.prepend_mod_with('API::Jobs')
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 70e13e8d4ae..7130635281a 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -94,6 +94,7 @@ module API
requires :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api'
+ optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
end
# rubocop: disable CodeReuse/ActiveRecord
post ":id/members" do
@@ -119,7 +120,12 @@ module API
not_allowed! # This currently can only be reached in EE
elsif member.valid? && member.persisted?
present_members(member)
- Gitlab::Tracking.event(::Members::CreateService.name, 'create_member', label: params[:invite_source], property: 'existing_user', user: current_user)
+ Gitlab::Tracking.event(::Members::CreateService.name,
+ 'create_member',
+ label: params[:invite_source],
+ property: 'existing_user',
+ user: current_user)
+ track_areas_of_focus(member, params[:areas_of_focus])
else
render_validation_error!(member)
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index a9617482557..7ab57982907 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -404,6 +404,7 @@ module API
pipeline = ::MergeRequests::CreatePipelineService
.new(project: user_project, current_user: current_user, params: { allow_duplicate: true })
.execute(find_merge_request_with_access(params[:merge_request_iid]))
+ .payload
if pipeline.nil?
not_allowed!
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index 9d41c2f148f..c2d839571a6 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -27,12 +27,15 @@ module API
end
params do
optional :search, type: String, desc: "Search query for namespaces"
+ optional :owned_only, type: Boolean, desc: "Owned namespaces only"
use :pagination
use :optional_list_params_ee
end
get do
- namespaces = current_user.admin ? Namespace.all : current_user.namespaces
+ owned_only = params[:owned_only] == true
+
+ namespaces = current_user.admin ? Namespace.all : current_user.namespaces(owned_only: owned_only)
namespaces = namespaces.include_route
diff --git a/lib/api/project_debian_distributions.rb b/lib/api/project_debian_distributions.rb
index 58edf51f4f7..f057251fb6b 100644
--- a/lib/api/project_debian_distributions.rb
+++ b/lib/api/project_debian_distributions.rb
@@ -19,8 +19,6 @@ module API
require_packages_enabled!
not_found! unless ::Feature.enabled?(:debian_packages, user_project)
-
- authorize_read_package!
end
namespace ':id' do
diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb
index acf9bfece65..fe0e837c596 100644
--- a/lib/api/project_templates.rb
+++ b/lib/api/project_templates.rb
@@ -12,7 +12,7 @@ module API
before { authenticate_non_get! }
- feature_category :templates
+ feature_category :source_code_management
params do
requires :id, type: String, desc: 'The ID of a project'
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 3b1d239398f..28bcb382ecf 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -45,6 +45,20 @@ module API
end
end
+ def support_order_by_similarity!(attrs)
+ return unless params[:order_by] == 'similarity'
+
+ if order_by_similarity?(allow_unauthorized: false)
+ # Limit to projects the current user is a member of.
+ # Do not include all public projects because it
+ # could cause long running queries
+ attrs[:non_public] = true
+ attrs[:sort] = params['order_by']
+ else
+ params[:order_by] = route.params['order_by'][:default]
+ end
+ end
+
def delete_project(user_project)
destroy_conditionally!(user_project) do
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
@@ -93,8 +107,8 @@ module API
params :sort_params do
optional :order_by, type: String,
- values: %w[id name path created_at updated_at last_activity_at] + Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS,
- default: 'created_at', desc: "Return projects ordered by field. #{Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS.join(', ')} are only available to admins."
+ values: %w[id name path created_at updated_at last_activity_at similarity] + Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS,
+ default: 'created_at', desc: "Return projects ordered by field. #{Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS.join(', ')} are only available to admins. Similarity is available when searching and is limited to projects the user has access to."
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return projects sorted in ascending and descending order'
end
@@ -131,16 +145,17 @@ module API
end
def load_projects
- params = project_finder_params
- verify_project_filters!(params)
+ project_params = project_finder_params
+ support_order_by_similarity!(project_params)
+ verify_project_filters!(project_params)
- ProjectsFinder.new(current_user: current_user, params: params).execute
+ ProjectsFinder.new(current_user: current_user, params: project_params).execute
end
def present_projects(projects, options = {})
verify_statistics_order_by_projects!
- projects = reorder_projects(projects)
+ projects = reorder_projects(projects) unless order_by_similarity?(allow_unauthorized: false)
projects = apply_filters(projects)
records, options = paginate_with_strategies(projects, options[:request_scope]) do |projects|
@@ -572,6 +587,27 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ desc 'Import members from another project' do
+ detail 'This feature was introduced in GitLab 14.2'
+ end
+ params do
+ requires :project_id, type: Integer, desc: 'The ID of the source project to import the members from.'
+ end
+ post ":id/import_project_members/:project_id", feature_category: :experimentation_expansion do
+ authorize! :admin_project, user_project
+
+ source_project = Project.find_by_id(params[:project_id])
+ not_found!('Project') unless source_project && can?(current_user, :read_project, source_project)
+
+ result = ::Members::ImportProjectTeamService.new(current_user, params).execute
+
+ if result
+ { status: result, message: 'Successfully imported' }
+ else
+ render_api_error!('Import failed', :unprocessable_entity)
+ end
+ end
+
desc 'Workhorse authorize the file upload' do
detail 'This feature was introduced in GitLab 13.11'
end
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
index 7c5f8bb4d99..706c0702fce 100644
--- a/lib/api/pypi_packages.rb
+++ b/lib/api/pypi_packages.rb
@@ -10,6 +10,7 @@ module API
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
+ helpers ::API::Helpers::Packages::DependencyProxyHelpers
include ::API::Helpers::Packages::BasicAuthHelpers::Constants
feature_category :package_registry
@@ -40,7 +41,7 @@ module API
end
params do
- requires :id, type: Integer, desc: 'The ID of a group'
+ requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
after_validation do
@@ -82,21 +83,26 @@ module API
track_package_event('list_package', :pypi)
- packages = Packages::Pypi::PackagesFinder.new(current_user, group, { package_name: params[:package_name] }).execute!
- presenter = ::Packages::Pypi::PackagePresenter.new(packages, group)
+ packages = Packages::Pypi::PackagesFinder.new(current_user, group, { package_name: params[:package_name] }).execute
+ empty_packages = packages.empty?
- # Adjusts grape output format
- # to be HTML
- content_type "text/html; charset=utf-8"
- env['api.format'] = :binary
+ redirect_registry_request(empty_packages, :pypi, package_name: params[:package_name]) do
+ not_found!('Package') if empty_packages
+ presenter = ::Packages::Pypi::PackagePresenter.new(packages, group)
- body presenter.body
+ # Adjusts grape output format
+ # to be HTML
+ content_type "text/html; charset=utf-8"
+ env['api.format'] = :binary
+
+ body presenter.body
+ end
end
end
end
params do
- requires :id, type: Integer, desc: 'The ID of a project'
+ requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
@@ -142,15 +148,20 @@ module API
track_package_event('list_package', :pypi, project: authorized_user_project, namespace: authorized_user_project.namespace)
- packages = Packages::Pypi::PackagesFinder.new(current_user, authorized_user_project, { package_name: params[:package_name] }).execute!
- presenter = ::Packages::Pypi::PackagePresenter.new(packages, authorized_user_project)
+ packages = Packages::Pypi::PackagesFinder.new(current_user, authorized_user_project, { package_name: params[:package_name] }).execute
+ empty_packages = packages.empty?
+
+ redirect_registry_request(empty_packages, :pypi, package_name: params[:package_name]) do
+ not_found!('Package') if empty_packages
+ presenter = ::Packages::Pypi::PackagePresenter.new(packages, authorized_user_project)
- # Adjusts grape output format
- # to be HTML
- content_type "text/html; charset=utf-8"
- env['api.format'] = :binary
+ # Adjusts grape output format
+ # to be HTML
+ content_type "text/html; charset=utf-8"
+ env['api.format'] = :binary
- body presenter.body
+ body presenter.body
+ end
end
desc 'The PyPi Package upload endpoint' do
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index f274406e225..20320d1b7ae 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -29,14 +29,13 @@ module API
not_found!
end
- def assign_blob_vars!
+ def assign_blob_vars!(limit:)
authorize! :download_code, user_project
@repo = user_project.repository
begin
- @blob = Gitlab::Git::Blob.raw(@repo, params[:sha])
- @blob.load_all_data!(@repo)
+ @blob = Gitlab::Git::Blob.raw(@repo, params[:sha], limit: limit)
rescue StandardError
not_found! 'Blob'
end
@@ -55,7 +54,7 @@ module API
use :pagination
end
get ':id/repository/tree' do
- ref = params[:ref] || user_project.try(:default_branch) || 'master'
+ ref = params[:ref] || user_project.default_branch
path = params[:path] || nil
commit = user_project.commit(ref)
@@ -71,7 +70,8 @@ module API
requires :sha, type: String, desc: 'The commit hash'
end
get ':id/repository/blobs/:sha/raw' do
- assign_blob_vars!
+ # Load metadata enough to ask Workhorse to load the whole blob
+ assign_blob_vars!(limit: 0)
no_cache_headers
@@ -83,7 +83,7 @@ module API
requires :sha, type: String, desc: 'The commit hash'
end
get ':id/repository/blobs/:sha' do
- assign_blob_vars!
+ assign_blob_vars!(limit: -1)
{
size: @blob.size,
diff --git a/lib/api/rubygem_packages.rb b/lib/api/rubygem_packages.rb
index d7f9c584c67..9ef6ec03a41 100644
--- a/lib/api/rubygem_packages.rb
+++ b/lib/api/rubygem_packages.rb
@@ -101,7 +101,7 @@ module API
package_file = nil
- ActiveRecord::Base.transaction do
+ ApplicationRecord.transaction do
package = ::Packages::CreateTemporaryPackageService.new(
user_project, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:rubygems, name: ::Packages::Rubygems::TEMPORARY_PACKAGE_NAME)
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 952bf09b1b1..aac195f0668 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -48,7 +48,7 @@ module API
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
optional :default_ci_config_path, type: String, desc: 'The instance default CI/CD configuration file and path for new projects'
optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
- optional :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to master'
+ optional :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to default branch'
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 6c8e2c69a6d..395aacced78 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -59,8 +59,6 @@ module API
optional :message, type: String, desc: 'Specifying a message creates an annotated tag'
end
post ':id/repository/tags', :release_orchestration do
- deprecate_release_notes unless params[:release_description].blank?
-
authorize_admin_tag
result = ::Tags::CreateService.new(user_project, current_user)
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index b7fb35eac03..a595129fd6a 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -4,17 +4,18 @@ module API
class Templates < ::API::Base
include PaginationParams
- feature_category :templates
-
GLOBAL_TEMPLATE_TYPES = {
gitignores: {
- gitlab_version: 8.8
+ gitlab_version: 8.8,
+ feature_category: :source_code_management
},
gitlab_ci_ymls: {
- gitlab_version: 8.9
+ gitlab_version: 8.9,
+ feature_category: :continuous_integration
},
dockerfiles: {
- gitlab_version: 8.15
+ gitlab_version: 8.15,
+ feature_category: :source_code_management
}
}.freeze
@@ -33,7 +34,7 @@ module API
optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
use :pagination
end
- get "templates/licenses" do
+ get "templates/licenses", feature_category: :source_code_management do
popular = declared(params)[:popular]
popular = to_boolean(popular) if popular.present?
@@ -49,7 +50,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the template'
end
- get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do
+ get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ }, feature_category: :source_code_management do
template = TemplateFinder.build(:licenses, nil, name: params[:name]).execute
not_found!('License') unless template.present?
@@ -72,7 +73,7 @@ module API
params do
use :pagination
end
- get "templates/#{template_type}" do
+ get "templates/#{template_type}", feature_category: properties[:feature_category] do
templates = ::Kaminari.paginate_array(TemplateFinder.build(template_type, nil).execute)
present paginate(templates), with: Entities::TemplatesList
end
@@ -84,7 +85,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the template'
end
- get "templates/#{template_type}/:name", requirements: { name: /[\w\.-]+/ } do
+ get "templates/#{template_type}/:name", requirements: { name: /[\w\.-]+/ }, feature_category: properties[:feature_category] do
finder = TemplateFinder.build(template_type, nil, name: declared(params)[:name])
new_template = finder.execute
diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb
index 969122d7906..b8323304957 100644
--- a/lib/api/time_tracking_endpoints.rb
+++ b/lib/api/time_tracking_endpoints.rb
@@ -88,6 +88,7 @@ module API
update_params = {
spend_time: {
duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)),
+ summary: params.delete(:summary),
user_id: current_user.id
}
}
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
deleted file mode 100644
index a359083a9d2..00000000000
--- a/lib/api/triggers.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class Triggers < ::API::Base
- include PaginationParams
-
- HTTP_GITLAB_EVENT_HEADER = "HTTP_#{WebHookService::GITLAB_EVENT_HEADER}".underscore.upcase
-
- feature_category :continuous_integration
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Trigger a GitLab project pipeline' do
- success Entities::Ci::Pipeline
- end
- params do
- requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false
- requires :token, type: String, desc: 'The unique token of trigger or job token'
- optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
- end
- post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
- Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20758')
-
- forbidden! if gitlab_pipeline_hook_request?
-
- # validate variables
- 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
-
- project = find_project(params[:id])
- not_found! unless project
-
- result = ::Ci::PipelineTriggerService.new(project, nil, params).execute
- not_found! unless result
-
- if result.error?
- render_api_error!(result[:message], result[:http_status])
- else
- present result[:pipeline], with: Entities::Ci::Pipeline
- end
- end
-
- desc 'Get triggers list' do
- success Entities::Trigger
- end
- params do
- use :pagination
- end
- # rubocop: disable CodeReuse/ActiveRecord
- get ':id/triggers' do
- authenticate!
- authorize! :admin_build, user_project
-
- triggers = user_project.triggers.includes(:trigger_requests)
-
- present paginate(triggers), with: Entities::Trigger, current_user: current_user
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Get specific trigger of a project' do
- success Entities::Trigger
- end
- params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
- end
- get ':id/triggers/:trigger_id' do
- authenticate!
- authorize! :admin_build, user_project
-
- trigger = user_project.triggers.find(params.delete(:trigger_id))
- break not_found!('Trigger') unless trigger
-
- present trigger, with: Entities::Trigger, current_user: current_user
- end
-
- desc 'Create a trigger' do
- success Entities::Trigger
- end
- params do
- requires :description, type: String, desc: 'The trigger description'
- end
- post ':id/triggers' do
- authenticate!
- authorize! :admin_build, user_project
-
- trigger = user_project.triggers.create(
- declared_params(include_missing: false).merge(owner: current_user))
-
- if trigger.valid?
- present trigger, with: Entities::Trigger, current_user: current_user
- else
- render_validation_error!(trigger)
- end
- end
-
- desc 'Update a trigger' do
- success Entities::Trigger
- end
- params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
- optional :description, type: String, desc: 'The trigger description'
- end
- put ':id/triggers/:trigger_id' do
- authenticate!
- authorize! :admin_build, user_project
-
- trigger = user_project.triggers.find(params.delete(:trigger_id))
- break not_found!('Trigger') unless trigger
-
- authorize! :admin_trigger, trigger
-
- if trigger.update(declared_params(include_missing: false))
- present trigger, with: Entities::Trigger, current_user: current_user
- else
- render_validation_error!(trigger)
- end
- end
-
- desc 'Delete a trigger' do
- success Entities::Trigger
- end
- params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
- end
- delete ':id/triggers/:trigger_id' do
- authenticate!
- authorize! :admin_build, user_project
-
- trigger = user_project.triggers.find(params.delete(:trigger_id))
- break not_found!('Trigger') unless trigger
-
- destroy_conditionally!(trigger)
- end
- end
-
- helpers do
- def gitlab_pipeline_hook_request?
- request.get_header(HTTP_GITLAB_EVENT_HEADER) == WebHookService.hook_to_event(:pipeline_hooks)
- end
- end
- end
-end
diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb
index 31c923a219a..634dd0f2179 100644
--- a/lib/api/user_counts.rb
+++ b/lib/api/user_counts.rb
@@ -6,15 +6,17 @@ module API
resource :user_counts do
desc 'Return the user specific counts' do
- detail 'Open MR Count'
+ detail 'Assigned open issues, assigned MRs and pending todos count'
end
get do
unauthorized! unless current_user
{
merge_requests: current_user.assigned_open_merge_requests_count, # @deprecated
+ assigned_issues: current_user.assigned_open_issues_count,
assigned_merge_requests: current_user.assigned_open_merge_requests_count,
- review_requested_merge_requests: current_user.review_requested_open_merge_requests_count
+ review_requested_merge_requests: current_user.review_requested_open_merge_requests_count,
+ todos: current_user.todos_pending_count
}
end
end
diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb
index 29e4a79110f..310054c298a 100644
--- a/lib/api/v3/github.rb
+++ b/lib/api/v3/github.rb
@@ -214,6 +214,8 @@ module API
update_project_feature_usage_for(user_project)
+ next [] unless user_project.repo_exists?
+
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
present paginate(branches), with: ::API::Github::Entities::Branch, project: user_project
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
deleted file mode 100644
index 75df0e050a6..00000000000
--- a/lib/api/variables.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class Variables < ::API::Base
- include PaginationParams
-
- before { authenticate! }
- before { authorize! :admin_build, user_project }
-
- feature_category :pipeline_authoring
-
- helpers Helpers::VariablesHelpers
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
-
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Get project variables' do
- success Entities::Ci::Variable
- end
- params do
- use :pagination
- end
- get ':id/variables' do
- variables = user_project.variables
- present paginate(variables), with: Entities::Ci::Variable
- end
-
- desc 'Get a specific variable from a project' do
- success Entities::Ci::Variable
- end
- params do
- requires :key, type: String, desc: 'The key of the variable'
- end
- # rubocop: disable CodeReuse/ActiveRecord
- get ':id/variables/:key' do
- variable = find_variable(user_project, params)
- not_found!('Variable') unless variable
-
- present variable, with: Entities::Ci::Variable
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Create a new variable in a project' do
- success Entities::Ci::Variable
- end
- params do
- requires :key, type: String, desc: 'The key of the variable'
- requires :value, type: String, desc: 'The value of the variable'
- optional :protected, type: Boolean, desc: 'Whether the variable is protected'
- optional :masked, type: Boolean, desc: 'Whether the variable is masked'
- optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
- optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
- end
- post ':id/variables' do
- variable = ::Ci::ChangeVariableService.new(
- container: user_project,
- current_user: current_user,
- params: { action: :create, variable_params: declared_params(include_missing: false) }
- ).execute
-
- if variable.valid?
- present variable, with: Entities::Ci::Variable
- else
- render_validation_error!(variable)
- end
- end
-
- desc 'Update an existing variable from a project' do
- success Entities::Ci::Variable
- end
- params do
- optional :key, type: String, desc: 'The key of the variable'
- optional :value, type: String, desc: 'The value of the variable'
- optional :protected, type: Boolean, desc: 'Whether the variable is protected'
- optional :masked, type: Boolean, desc: 'Whether the variable is masked'
- optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
- optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
- optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production'
- end
- # rubocop: disable CodeReuse/ActiveRecord
- put ':id/variables/:key' do
- variable = find_variable(user_project, params)
- not_found!('Variable') unless variable
-
- variable = ::Ci::ChangeVariableService.new(
- container: user_project,
- current_user: current_user,
- params: { action: :update, variable: variable, variable_params: declared_params(include_missing: false).except(:key, :filter) }
- ).execute
-
- if variable.valid?
- present variable, with: Entities::Ci::Variable
- else
- render_validation_error!(variable)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Delete an existing variable from a project' do
- success Entities::Ci::Variable
- end
- params do
- requires :key, type: String, desc: 'The key of the variable'
- optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production'
- end
- # rubocop: disable CodeReuse/ActiveRecord
- delete ':id/variables/:key' do
- variable = find_variable(user_project, params)
- not_found!('Variable') unless variable
-
- ::Ci::ChangeVariableService.new(
- container: user_project,
- current_user: current_user,
- params: { action: :destroy, variable: variable }
- ).execute
-
- no_content!
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
- end
-end