Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-20 21:09:52 +00:00
parent 1bf106b172
commit 900c5cc840
27 changed files with 794 additions and 784 deletions

View File

@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
VERSION = '0.30.0'
VERSION = '0.31.0'
self.table_name = 'clusters_applications_runners'

View File

@ -11,8 +11,7 @@ This is the API documentation for [Helm](../../user/packages/helm_repository/ind
WARNING:
This API is used by the Helm-related package clients such as [Helm](https://helm.sh/)
and [`helm-push`](https://github.com/chartmuseum/helm-push/#readme),
and is generally not meant for manual consumption. This API is under development and is not ready
for production use due to limited functionality.
and is generally not meant for manual consumption.
For instructions on how to upload and install Helm packages from the GitLab
Package Registry, see the [Helm registry documentation](../../user/packages/helm_repository/index.md).

View File

@ -673,17 +673,19 @@ class definition to make it easy and clear:
```ruby
module API
class JobArtifacts < Grape::API::Instance
# EE::API::JobArtifacts would override the following helpers
helpers do
def authorize_download_artifacts!
authorize_read_builds!
module Ci
class JobArtifacts < Grape::API::Instance
# EE::API::Ci::JobArtifacts would override the following helpers
helpers do
def authorize_download_artifacts!
authorize_read_builds!
end
end
end
end
end
API::JobArtifacts.prepend_mod_with('API::JobArtifacts')
API::Ci::JobArtifacts.prepend_mod_with('API::Ci::JobArtifacts')
```
And then we can follow regular object-oriented practices to override it:
@ -691,14 +693,16 @@ And then we can follow regular object-oriented practices to override it:
```ruby
module EE
module API
module JobArtifacts
extend ActiveSupport::Concern
module Ci
module JobArtifacts
extend ActiveSupport::Concern
prepended do
helpers do
def authorize_download_artifacts!
super
check_cross_project_pipelines_feature!
prepended do
helpers do
def authorize_download_artifacts!
super
check_cross_project_pipelines_feature!
end
end
end
end

View File

@ -15,6 +15,8 @@ GitLab allows [Owners](../user/permissions.md) to set a project's visibility as:
These visibility levels affect who can see the project in the public access directory (`/public`
for your GitLab instance). For example, <https://gitlab.com/public>.
You can control the visibility of individual features with
[project feature settings](../user/permissions.md#project-features).
## Public projects

View File

@ -8,10 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18997) in GitLab 14.1.
WARNING:
The Helm package registry for GitLab is under development and isn't ready for production use due to
limited functionality.
Publish Helm packages in your project's Package Registry. Then install the
packages whenever you need to use them as a dependency.

View File

@ -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
@ -190,8 +194,6 @@ module API
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 +270,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

View File

@ -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

143
lib/api/ci/job_artifacts.rb Normal file
View File

@ -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

206
lib/api/ci/jobs.rb Normal file
View File

@ -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')

View File

@ -3,7 +3,7 @@
module API
module Ci
class Runner < ::API::Base
helpers ::API::Helpers::Runner
helpers ::API::Ci::Helpers::Runner
content_type :txt, 'text/plain'

148
lib/api/ci/triggers.rb Normal file
View File

@ -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

126
lib/api/ci/variables.rb Normal file
View File

@ -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

View File

@ -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'

View File

@ -132,7 +132,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,

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -2,12 +2,12 @@
require 'spec_helper'
RSpec.describe API::Helpers::Runner do
RSpec.describe API::Ci::Helpers::Runner do
let(:ip_address) { '1.2.3.4' }
let(:runner_class) do
Class.new do
include API::Helpers
include API::Helpers::Runner
include API::Ci::Helpers::Runner
attr_accessor :params

View File

@ -2,8 +2,8 @@
require 'spec_helper'
RSpec.describe API::Helpers::Runner do
let(:helper) { Class.new { include API::Helpers::Runner }.new }
RSpec.describe API::Ci::Helpers::Runner do
let(:helper) { Class.new { include API::Ci::Helpers::Runner }.new }
before do
allow(helper).to receive(:env).and_return({})

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe API::Jobs do
RSpec.describe API::Ci::Jobs do
include HttpBasicAuthHelpers
include DependencyProxyHelpers
@ -114,7 +114,7 @@ RSpec.describe API::Jobs do
context 'with job token authentication header' do
include_context 'with auth headers' do
let(:header) { { API::Helpers::Runner::JOB_TOKEN_HEADER => running_job.token } }
let(:header) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => running_job.token } }
end
it_behaves_like 'returns common job data' do
@ -150,7 +150,7 @@ RSpec.describe API::Jobs do
context 'with non running job' do
include_context 'with auth headers' do
let(:header) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token } }
let(:header) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token } }
end
it_behaves_like 'returns unauthorized'
@ -523,15 +523,13 @@ RSpec.describe API::Jobs do
context 'when artifacts are stored remotely' do
let(:proxy_download) { false }
let(:job) { create(:ci_build, pipeline: pipeline) }
let(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
before do
stub_artifacts_object_storage(proxy_download: proxy_download)
end
let(:job) { create(:ci_build, pipeline: pipeline) }
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
before do
artifact
job.reload
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
@ -708,11 +706,7 @@ RSpec.describe API::Jobs do
context 'with branch name containing slash' do
before do
pipeline.reload
pipeline.update!(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
end
before do
pipeline.update!(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
get_for_ref('improve/awesome')
end

View File

@ -32,7 +32,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :pending, user: user, project: project, pipeline: pipeline, runner_id: runner.id) }
let(:jwt) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt } }
let(:headers_with_token) { headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.token) }
let(:headers_with_token) { headers.merge(API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token) }
let(:file_upload) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload('spec/fixtures/dk.png', 'image/gif') }
@ -398,7 +398,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
context 'when using runners token' do
it 'responds with forbidden' do
upload_artifacts(file_upload, headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.project.runners_token))
upload_artifacts(file_upload, headers.merge(API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.project.runners_token))
expect(response).to have_gitlab_http_status(:forbidden)
end

View File

@ -33,7 +33,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks do
project: project, user: user, runner_id: runner.id, pipeline: pipeline)
end
let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } }
let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } }
let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
let(:update_interval) { 10.seconds.to_i }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe API::Triggers do
RSpec.describe API::Ci::Triggers do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe API::Variables do
RSpec.describe API::Ci::Variables do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }

View File

@ -89,6 +89,8 @@ ci_cd_settings:
- group_runners_enabled
- merge_pipelines_enabled
- merge_trains_enabled
- merge_pipelines_enabled
- merge_trains_enabled
- auto_rollback_enabled
remapped_attributes:
default_git_depth: ci_default_git_depth