Commit 7e46db0f authored by Tomasz Maczukin's avatar Tomasz Maczukin

Add job patch trace API

parent d5f7e542
module API module API
module Helpers module Helpers
module Runner module Runner
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'
JOB_TOKEN_PARAM = :token
UPDATE_RUNNER_EVERY = 10 * 60 UPDATE_RUNNER_EVERY = 10 * 60
def runner_registration_token_valid? def runner_registration_token_valid?
...@@ -55,6 +57,17 @@ module API ...@@ -55,6 +57,17 @@ module API
forbidden!('Project has been deleted!') unless job.project forbidden!('Project has been deleted!') unless job.project
forbidden!('Job has been erased!') if job.erased? forbidden!('Job has been erased!') if job.erased?
end end
def authenticate_job!(job)
validate_job!(job) do
forbidden! unless job_token_valid?(job)
end
end
def job_token_valid?(job)
token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
token && job.valid_token?(token)
end
end end
end end
end end
...@@ -93,7 +93,7 @@ module API ...@@ -93,7 +93,7 @@ module API
http_codes [[200, 'Job was updated'], [403, 'Forbidden']] http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
end end
params do params do
requires :token, type: String, desc: %q(Job's authentication token) requires :token, type: String, desc: %q(Runners's authentication token)
requires :id, type: Fixnum, desc: %q(Job's ID) requires :id, type: Fixnum, desc: %q(Job's ID)
optional :trace, type: String, desc: %q(Job's full trace) optional :trace, type: String, desc: %q(Job's full trace)
optional :state, type: String, desc: %q(Job's status: success, failed) optional :state, type: String, desc: %q(Job's status: success, failed)
...@@ -114,6 +114,36 @@ module API ...@@ -114,6 +114,36 @@ module API
job.drop job.drop
end end
end end
desc 'Appends a patch to the job.trace' do
http_codes [[202, 'Trace was patched'],
[400, 'Missing Content-Range header'],
[403, 'Forbidden'],
[416, 'Range not satisfiable']]
end
params do
requires :id, type: Fixnum, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token)
end
patch '/:id/trace' do
job = Ci::Build.find_by_id(params[:id])
authenticate_job!(job)
error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range')
content_range = request.headers['Content-Range']
content_range = content_range.split('-')
current_length = job.trace_length
unless current_length == content_range[0].to_i
return error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{current_length}" })
end
job.append_trace(request.body.read, content_range[0].to_i)
status 202
header 'Job-Status', job.status
header 'Range', "0-#{job.trace_length}"
end
end end
end end
end end
...@@ -480,5 +480,139 @@ describe API::Runner do ...@@ -480,5 +480,139 @@ describe API::Runner do
put api("/jobs/#{job.id}"), new_params put api("/jobs/#{job.id}"), new_params
end end
end end
describe 'PATCH /api/v4/jobs/:id/trace' do
let(:job) { create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) }
let(:headers) { { API::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 }
before { initial_patch_the_trace }
context 'when request is valid' do
it 'gets correct response' do
expect(response.status).to eq 202
expect(job.reload.trace).to eq 'BUILD TRACE appended'
expect(response.header).to have_key 'Range'
expect(response.header).to have_key 'Job-Status'
end
context 'when job has been updated recently' do
it { expect{ patch_the_trace }.not_to change { job.updated_at }}
it "changes the job's trace" do
patch_the_trace
expect(job.reload.trace).to eq 'BUILD TRACE appended appended'
end
context 'when Runner makes a force-patch' do
it { expect{ force_patch_the_trace }.not_to change { job.updated_at }}
it "doesn't change the build.trace" do
force_patch_the_trace
expect(job.reload.trace).to eq 'BUILD TRACE appended'
end
end
end
context 'when job was not updated recently' do
let(:update_interval) { 15.minutes.to_i }
it { expect { patch_the_trace }.to change { job.updated_at } }
it 'changes the job.trace' do
patch_the_trace
expect(job.reload.trace).to eq 'BUILD TRACE appended appended'
end
context 'when Runner makes a force-patch' do
it { expect { force_patch_the_trace }.to change { job.updated_at } }
it "doesn't change the job.trace" do
force_patch_the_trace
expect(job.reload.trace).to eq 'BUILD TRACE appended'
end
end
end
context 'when project for the build has been deleted' do
let(:job) do
create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) do |job|
job.project.update(pending_delete: true)
end
end
it 'responds with forbidden' do
expect(response.status).to eq(403)
end
end
end
context 'when Runner makes a force-patch' do
before do
force_patch_the_trace
end
it 'gets correct response' do
expect(response.status).to eq 202
expect(job.reload.trace).to eq 'BUILD TRACE appended'
expect(response.header).to have_key 'Range'
expect(response.header).to have_key 'Job-Status'
end
end
context 'when content-range start is too big' do
let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) }
it 'gets 416 error response with range headers' do
expect(response.status).to eq 416
expect(response.header).to have_key 'Range'
expect(response.header['Range']).to eq '0-11'
end
end
context 'when content-range start is too small' do
let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) }
it 'gets 416 error response with range headers' do
expect(response.status).to eq 416
expect(response.header).to have_key 'Range'
expect(response.header['Range']).to eq '0-11'
end
end
context 'when Content-Range header is missing' do
let(:headers_with_range) { headers }
it { expect(response.status).to eq 400 }
end
context 'when job has been errased' do
let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
it { expect(response.status).to eq 403 }
end
def patch_the_trace(content = ' appended', request_headers = nil)
unless request_headers
offset = job.trace_length
limit = offset + content.length - 1
request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" })
end
Timecop.travel(job.updated_at + update_interval) do
patch api("/jobs/#{job.id}/trace"), content, request_headers
job.reload
end
end
def initial_patch_the_trace
patch_the_trace(' appended', headers_with_range)
end
def force_patch_the_trace
2.times { patch_the_trace('') }
end
end
end end
end end
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