Commit b477eb98 authored by Stan Hu's avatar Stan Hu

Merge branch '273602-move-api-related-code-under-ci-namespace' into 'master'

Move API CI related classes under Ci namespace

See merge request gitlab-org/gitlab!66263
parents 6b8a8a50 631d18cd
...@@ -673,17 +673,19 @@ class definition to make it easy and clear: ...@@ -673,17 +673,19 @@ class definition to make it easy and clear:
```ruby ```ruby
module API module API
class JobArtifacts < Grape::API::Instance module Ci
# EE::API::JobArtifacts would override the following helpers class JobArtifacts < Grape::API::Instance
helpers do # EE::API::Ci::JobArtifacts would override the following helpers
def authorize_download_artifacts! helpers do
authorize_read_builds! def authorize_download_artifacts!
authorize_read_builds!
end
end end
end 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: 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: ...@@ -691,14 +693,16 @@ And then we can follow regular object-oriented practices to override it:
```ruby ```ruby
module EE module EE
module API module API
module JobArtifacts module Ci
extend ActiveSupport::Concern module JobArtifacts
extend ActiveSupport::Concern
prepended do prepended do
helpers do helpers do
def authorize_download_artifacts! def authorize_download_artifacts!
super super
check_cross_project_pipelines_feature! check_cross_project_pipelines_feature!
end
end end
end end
end end
......
# frozen_string_literal: true
module EE
module API
module Ci
module Helpers
module Runner
extend ::Gitlab::Utils::Override
override :track_ci_minutes_usage!
def track_ci_minutes_usage!(build, runner)
::Ci::Minutes::TrackLiveConsumptionService.new(build).execute
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module API
module Ci
module JobArtifacts
extend ActiveSupport::Concern
prepended do
helpers do
def authorize_download_artifacts!
super
check_cross_project_pipelines_feature!
end
def check_cross_project_pipelines_feature!
if job_token_authentication? && !user_project.licensed_feature_available?(:cross_project_pipelines)
not_found!('Project')
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module API
module Ci
module Jobs
extend ActiveSupport::Concern
prepended do
resource :job do
desc 'Get current agents' do
detail 'Retrieves a list of agents for the given job token'
end
route_setting :authentication, job_token_allowed: true
get '/allowed_agents', feature_category: :kubernetes_management do
validate_current_authenticated_job
status 200
pipeline = current_authenticated_job.pipeline
project = current_authenticated_job.project
allowed_agents = ::Clusters::DeployableAgentsFinder.new(project).execute
{
allowed_agents: ::API::Entities::Clusters::Agent.represent(allowed_agents),
job: ::API::Entities::Ci::JobRequest::JobInfo.represent(current_authenticated_job),
pipeline: ::API::Entities::Ci::PipelineBasic.represent(pipeline),
project: ::API::Entities::ProjectIdentity.represent(project),
user: ::API::Entities::UserBasic.represent(current_user)
}
end
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module API
module Helpers
module Runner
extend ::Gitlab::Utils::Override
override :track_ci_minutes_usage!
def track_ci_minutes_usage!(build, runner)
::Ci::Minutes::TrackLiveConsumptionService.new(build).execute
end
end
end
end
end
# frozen_string_literal: true
module EE
module API
module JobArtifacts
extend ActiveSupport::Concern
prepended do
helpers do
def authorize_download_artifacts!
super
check_cross_project_pipelines_feature!
end
def check_cross_project_pipelines_feature!
if job_token_authentication? && !user_project.feature_available?(:cross_project_pipelines)
not_found!('Project')
end
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module API
module Jobs
extend ActiveSupport::Concern
prepended do
resource :job do
desc 'Get current agents' do
detail 'Retrieves a list of agents for the given job token'
end
route_setting :authentication, job_token_allowed: true
get '/allowed_agents', feature_category: :kubernetes_management do
validate_current_authenticated_job
status 200
pipeline = current_authenticated_job.pipeline
project = current_authenticated_job.project
allowed_agents = ::Clusters::DeployableAgentsFinder.new(project).execute
{
allowed_agents: ::API::Entities::Clusters::Agent.represent(allowed_agents),
job: ::API::Entities::Ci::JobRequest::JobInfo.represent(current_authenticated_job),
pipeline: ::API::Entities::Ci::PipelineBasic.represent(pipeline),
project: ::API::Entities::ProjectIdentity.represent(project),
user: ::API::Entities::UserBasic.represent(current_user)
}
end
end
end
end
end
end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Jobs do RSpec.describe API::Ci::Jobs do
let_it_be(:project) do let_it_be(:project) do
create(:project, :repository, public_builds: false) create(:project, :repository, public_builds: false)
end end
...@@ -29,7 +29,7 @@ RSpec.describe API::Jobs do ...@@ -29,7 +29,7 @@ RSpec.describe API::Jobs do
let_it_be(:agent) { create(:cluster_agent, project: project) } let_it_be(:agent) { create(:cluster_agent, project: project) }
let(:api_user) { developer } let(:api_user) { developer }
let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token } } let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token } }
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user, status: job_status) } let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user, status: job_status) }
let(:job_status) { 'running' } let(:job_status) { 'running' }
let(:params) { {} } let(:params) { {} }
...@@ -92,7 +92,7 @@ RSpec.describe API::Jobs do ...@@ -92,7 +92,7 @@ RSpec.describe API::Jobs do
end end
context 'when token is invalid' do context 'when token is invalid' do
let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => 'bad_token' } } let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => 'bad_token' } }
it 'returns unauthorized' do it 'returns unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
......
...@@ -9,7 +9,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do ...@@ -9,7 +9,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let_it_be(:runner) { create(:ci_runner, :instance) } let_it_be(:runner) { create(:ci_runner, :instance) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
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' } }
before do before do
allow(Gitlab).to receive(:com?).and_return(true) allow(Gitlab).to receive(:com?).and_return(true)
......
...@@ -9,7 +9,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do ...@@ -9,7 +9,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let_it_be(:runner) { create(:ci_runner, :instance) } let_it_be(:runner) { create(:ci_runner, :instance) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
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' } }
before do before do
allow(Gitlab).to receive(:com?).and_return(true) allow(Gitlab).to receive(:com?).and_return(true)
...@@ -74,7 +74,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do ...@@ -74,7 +74,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end end
def patch_the_trace(content = ' appended') def patch_the_trace(content = ' appended')
headers = { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } headers = { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' }
job.trace.read do |stream| job.trace.read do |stream|
offset = stream.size offset = stream.size
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Triggers do RSpec.describe API::Ci::Triggers do
include AfterNextHelpers include AfterNextHelpers
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Variables do RSpec.describe API::Ci::Variables do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -43,7 +43,7 @@ RSpec.describe API::Internal::AppSec::Dast::SiteValidations do ...@@ -43,7 +43,7 @@ RSpec.describe API::Internal::AppSec::Dast::SiteValidations do
end end
context 'when a job token header is set' do context 'when a job token header is set' do
let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token } } let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token } }
context 'when user does not have access to the site validation' do context 'when user does not have access to the site validation' do
let(:job) { create(:ci_build, :running, user: create(:user)) } let(:job) { create(:ci_build, :running, user: create(:user)) }
......
...@@ -153,10 +153,14 @@ module API ...@@ -153,10 +153,14 @@ module API
mount ::API::Branches mount ::API::Branches
mount ::API::BroadcastMessages mount ::API::BroadcastMessages
mount ::API::BulkImports mount ::API::BulkImports
mount ::API::Ci::JobArtifacts
mount ::API::Ci::Jobs
mount ::API::Ci::Pipelines mount ::API::Ci::Pipelines
mount ::API::Ci::PipelineSchedules mount ::API::Ci::PipelineSchedules
mount ::API::Ci::Runner mount ::API::Ci::Runner
mount ::API::Ci::Runners mount ::API::Ci::Runners
mount ::API::Ci::Triggers
mount ::API::Ci::Variables
mount ::API::Commits mount ::API::Commits
mount ::API::CommitStatuses mount ::API::CommitStatuses
mount ::API::ContainerRegistryEvent mount ::API::ContainerRegistryEvent
...@@ -190,8 +194,6 @@ module API ...@@ -190,8 +194,6 @@ module API
mount ::API::IssueLinks mount ::API::IssueLinks
mount ::API::Invitations mount ::API::Invitations
mount ::API::Issues mount ::API::Issues
mount ::API::JobArtifacts
mount ::API::Jobs
mount ::API::Keys mount ::API::Keys
mount ::API::Labels mount ::API::Labels
mount ::API::Lint mount ::API::Lint
...@@ -268,14 +270,12 @@ module API ...@@ -268,14 +270,12 @@ module API
mount ::API::Tags mount ::API::Tags
mount ::API::Templates mount ::API::Templates
mount ::API::Todos mount ::API::Todos
mount ::API::Triggers
mount ::API::Unleash mount ::API::Unleash
mount ::API::UsageData mount ::API::UsageData
mount ::API::UsageDataQueries mount ::API::UsageDataQueries
mount ::API::UsageDataNonSqlMetrics mount ::API::UsageDataNonSqlMetrics
mount ::API::UserCounts mount ::API::UserCounts
mount ::API::Users mount ::API::Users
mount ::API::Variables
mount ::API::Version mount ::API::Version
mount ::API::Wikis mount ::API::Wikis
end end
......
# 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
# 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
# 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')
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module API module API
module Ci module Ci
class Runner < ::API::Base class Runner < ::API::Base
helpers ::API::Helpers::Runner helpers ::API::Ci::Helpers::Runner
content_type :txt, 'text/plain' content_type :txt, 'text/plain'
......
# 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
# 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
...@@ -8,7 +8,7 @@ module API ...@@ -8,7 +8,7 @@ module API
before { authorize! :admin_group, user_group } before { authorize! :admin_group, user_group }
feature_category :continuous_integration feature_category :continuous_integration
helpers Helpers::VariablesHelpers helpers ::API::Helpers::VariablesHelpers
params do params do
requires :id, type: String, desc: 'The ID of a group' requires :id, type: String, desc: 'The ID of a group'
......
# 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
# 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
# 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')
# 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
# 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
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Helpers::Runner do RSpec.describe API::Ci::Helpers::Runner do
let(:ip_address) { '1.2.3.4' } let(:ip_address) { '1.2.3.4' }
let(:runner_class) do let(:runner_class) do
Class.new do Class.new do
include API::Helpers include API::Helpers
include API::Helpers::Runner include API::Ci::Helpers::Runner
attr_accessor :params attr_accessor :params
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Helpers::Runner do RSpec.describe API::Ci::Helpers::Runner do
let(:helper) { Class.new { include API::Helpers::Runner }.new } let(:helper) { Class.new { include API::Ci::Helpers::Runner }.new }
before do before do
allow(helper).to receive(:env).and_return({}) allow(helper).to receive(:env).and_return({})
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Jobs do RSpec.describe API::Ci::Jobs do
include HttpBasicAuthHelpers include HttpBasicAuthHelpers
include DependencyProxyHelpers include DependencyProxyHelpers
...@@ -114,7 +114,7 @@ RSpec.describe API::Jobs do ...@@ -114,7 +114,7 @@ RSpec.describe API::Jobs do
context 'with job token authentication header' do context 'with job token authentication header' do
include_context 'with auth headers' 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 end
it_behaves_like 'returns common job data' do it_behaves_like 'returns common job data' do
...@@ -150,7 +150,7 @@ RSpec.describe API::Jobs do ...@@ -150,7 +150,7 @@ RSpec.describe API::Jobs do
context 'with non running job' do context 'with non running job' do
include_context 'with auth headers' 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 end
it_behaves_like 'returns unauthorized' it_behaves_like 'returns unauthorized'
...@@ -523,15 +523,13 @@ RSpec.describe API::Jobs do ...@@ -523,15 +523,13 @@ RSpec.describe API::Jobs do
context 'when artifacts are stored remotely' do context 'when artifacts are stored remotely' do
let(:proxy_download) { false } let(:proxy_download) { false }
let(:job) { create(:ci_build, pipeline: pipeline) }
let(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
before do before do
stub_artifacts_object_storage(proxy_download: proxy_download) 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 job.reload
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user) get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
...@@ -708,11 +706,7 @@ RSpec.describe API::Jobs do ...@@ -708,11 +706,7 @@ RSpec.describe API::Jobs do
context 'with branch name containing slash' do context 'with branch name containing slash' do
before do before do
pipeline.reload pipeline.reload
pipeline.update!(ref: 'improve/awesome', pipeline.update!(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
sha: project.commit('improve/awesome').sha)
end
before do
get_for_ref('improve/awesome') get_for_ref('improve/awesome')
end end
......
...@@ -32,7 +32,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do ...@@ -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(: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(: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) { { '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_upload) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload('spec/fixtures/dk.png', '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 ...@@ -398,7 +398,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
context 'when using runners token' do context 'when using runners token' do
it 'responds with forbidden' 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) expect(response).to have_gitlab_http_status(:forbidden)
end end
......
...@@ -33,7 +33,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks do ...@@ -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) project: project, user: user, runner_id: runner.id, pipeline: pipeline)
end 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(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
let(:update_interval) { 10.seconds.to_i } let(:update_interval) { 10.seconds.to_i }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Triggers do RSpec.describe API::Ci::Triggers do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) } let_it_be(:user2) { create(:user) }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Variables do RSpec.describe API::Ci::Variables do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) } let!(:project) { create(:project, creator_id: user.id) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment