Merge branch '54626-able-to-download-a-single-archive-file-with-api-by-ref-name' into 'master'
Add endpoint to download single artifact by ref Closes #54626 See merge request gitlab-org/gitlab-ce!23538
This commit is contained in:
commit
c79c8739b6
6 changed files with 215 additions and 6 deletions
|
@ -655,6 +655,11 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def latest_successful_build_for(job_name, ref = default_branch)
|
||||
builds = latest_successful_builds_for(ref)
|
||||
builds.find_by!(name: job_name)
|
||||
end
|
||||
|
||||
def merge_base_commit(first_commit_id, second_commit_id)
|
||||
sha = repository.merge_base(first_commit_id, second_commit_id)
|
||||
commit_by(oid: sha) if sha
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add new endpoint to download single artifact file for a ref
|
||||
merge_request: 23538
|
||||
author:
|
||||
type: added
|
|
@ -404,7 +404,7 @@ Example response:
|
|||
|
||||
[ce-5347]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5347
|
||||
|
||||
## Download a single artifact file
|
||||
## Download a single artifact file by job ID
|
||||
|
||||
> Introduced in GitLab 10.0
|
||||
|
||||
|
@ -438,6 +438,41 @@ Example response:
|
|||
| 400 | Invalid path provided |
|
||||
| 404 | Build not found or no file/artifacts |
|
||||
|
||||
## Download a single artifact file from specific tag or branch
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23538) in GitLab 11.5.
|
||||
|
||||
Download a single artifact file from a specific tag or branch from within the
|
||||
job's artifacts archive. The file is extracted from the archive and streamed to
|
||||
the client.
|
||||
|
||||
```
|
||||
GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
|
||||
| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
|
||||
| `job` | string | yes | The name of the job. |
|
||||
|
||||
Example request:
|
||||
|
||||
```sh
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/raw/some/release/file.pdf?job=pdf"
|
||||
```
|
||||
|
||||
Possible response status codes:
|
||||
|
||||
| Status | Description |
|
||||
|-----------|--------------------------------------|
|
||||
| 200 | Sends a single artifact file |
|
||||
| 400 | Invalid path provided |
|
||||
| 404 | Build not found or no file/artifacts |
|
||||
|
||||
## Get a trace file
|
||||
|
||||
Get a trace of a specific job of a project
|
||||
|
|
|
@ -35,6 +35,29 @@ module API
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
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
|
||||
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(params[:job], params[:ref_name])
|
||||
|
||||
path = Gitlab::Ci::Build::Artifacts::Path
|
||||
.new(params[:artifact_path])
|
||||
|
||||
bad_request! unless path.valid?
|
||||
|
||||
send_artifacts_entry(build, path)
|
||||
end
|
||||
|
||||
desc 'Download the artifacts archive from a job' do
|
||||
detail 'This feature was introduced in GitLab 8.5'
|
||||
end
|
||||
|
@ -65,6 +88,7 @@ module API
|
|||
|
||||
path = Gitlab::Ci::Build::Artifacts::Path
|
||||
.new(params[:artifact_path])
|
||||
|
||||
bad_request! unless path.valid?
|
||||
|
||||
send_artifacts_entry(build, path)
|
||||
|
|
|
@ -1897,7 +1897,7 @@ describe Project do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#latest_successful_builds_for' do
|
||||
describe '#latest_successful_builds_for and #latest_successful_build_for' do
|
||||
def create_pipeline(status = 'success')
|
||||
create(:ci_pipeline, project: project,
|
||||
sha: project.commit.sha,
|
||||
|
@ -1919,14 +1919,16 @@ describe Project do
|
|||
it 'gives the latest builds from latest pipeline' do
|
||||
pipeline1 = create_pipeline
|
||||
pipeline2 = create_pipeline
|
||||
build1_p2 = create_build(pipeline2, 'test')
|
||||
create_build(pipeline1, 'test')
|
||||
create_build(pipeline1, 'test2')
|
||||
build1_p2 = create_build(pipeline2, 'test')
|
||||
build2_p2 = create_build(pipeline2, 'test2')
|
||||
|
||||
latest_builds = project.latest_successful_builds_for
|
||||
single_build = project.latest_successful_build_for(build1_p2.name)
|
||||
|
||||
expect(latest_builds).to contain_exactly(build2_p2, build1_p2)
|
||||
expect(single_build).to eq(build1_p2)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1936,16 +1938,22 @@ describe Project do
|
|||
context 'standalone pipeline' do
|
||||
it 'returns builds for ref for default_branch' do
|
||||
builds = project.latest_successful_builds_for
|
||||
single_build = project.latest_successful_build_for(build.name)
|
||||
|
||||
expect(builds).to contain_exactly(build)
|
||||
expect(single_build).to eq(build)
|
||||
end
|
||||
|
||||
it 'returns empty relation if the build cannot be found' do
|
||||
it 'returns empty relation if the build cannot be found for #latest_successful_builds_for' do
|
||||
builds = project.latest_successful_builds_for('TAIL')
|
||||
|
||||
expect(builds).to be_kind_of(ActiveRecord::Relation)
|
||||
expect(builds).to be_empty
|
||||
end
|
||||
|
||||
it 'returns exception if the build cannot be found for #latest_successful_build_for' do
|
||||
expect { project.latest_successful_build_for(build.name, 'TAIL') }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with some pending pipeline' do
|
||||
|
@ -1954,9 +1962,11 @@ describe Project do
|
|||
end
|
||||
|
||||
it 'gives the latest build from latest pipeline' do
|
||||
latest_build = project.latest_successful_builds_for
|
||||
latest_builds = project.latest_successful_builds_for
|
||||
last_single_build = project.latest_successful_build_for(build.name)
|
||||
|
||||
expect(latest_build).to contain_exactly(build)
|
||||
expect(latest_builds).to contain_exactly(build)
|
||||
expect(last_single_build).to eq(build)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -586,6 +586,136 @@ describe API::Jobs do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name' do
|
||||
context 'when job has artifacts' do
|
||||
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
|
||||
let(:artifact) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' }
|
||||
let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
|
||||
let(:public_builds) { true }
|
||||
|
||||
before do
|
||||
stub_artifacts_object_storage
|
||||
job.success
|
||||
|
||||
project.update(visibility_level: visibility_level,
|
||||
public_builds: public_builds)
|
||||
|
||||
get_artifact_file(artifact)
|
||||
end
|
||||
|
||||
context 'when user is anonymous' do
|
||||
let(:api_user) { nil }
|
||||
|
||||
context 'when project is public' do
|
||||
let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
|
||||
let(:public_builds) { true }
|
||||
|
||||
it 'allows to access artifacts' do
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response.headers.to_h)
|
||||
.to include('Content-Type' => 'application/json',
|
||||
'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is public with builds access disabled' do
|
||||
let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
|
||||
let(:public_builds) { false }
|
||||
|
||||
it 'rejects access to artifacts' do
|
||||
expect(response).to have_gitlab_http_status(403)
|
||||
expect(json_response).to have_key('message')
|
||||
expect(response.headers.to_h)
|
||||
.not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is private' do
|
||||
let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
|
||||
let(:public_builds) { true }
|
||||
|
||||
it 'rejects access and hides existence of artifacts' do
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
expect(json_response).to have_key('message')
|
||||
expect(response.headers.to_h)
|
||||
.not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
|
||||
let(:public_builds) { true }
|
||||
|
||||
it 'returns a specific artifact file for a valid path' do
|
||||
expect(Gitlab::Workhorse)
|
||||
.to receive(:send_artifacts_entry)
|
||||
.and_call_original
|
||||
|
||||
get_artifact_file(artifact)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response.headers.to_h)
|
||||
.to include('Content-Type' => 'application/json',
|
||||
'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with branch name containing slash' do
|
||||
before do
|
||||
pipeline.reload
|
||||
pipeline.update(ref: 'improve/awesome',
|
||||
sha: project.commit('improve/awesome').sha)
|
||||
end
|
||||
|
||||
it 'returns a specific artifact file for a valid path' do
|
||||
get_artifact_file(artifact, 'improve/awesome')
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response.headers.to_h)
|
||||
.to include('Content-Type' => 'application/json',
|
||||
'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'non-existing job' do
|
||||
shared_examples 'not found' do
|
||||
it { expect(response).to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'has no such ref' do
|
||||
before do
|
||||
get_artifact_file('some/artifact', 'wrong-ref')
|
||||
end
|
||||
|
||||
it_behaves_like 'not found'
|
||||
end
|
||||
|
||||
context 'has no such job' do
|
||||
before do
|
||||
get_artifact_file('some/artifact', pipeline.ref, 'wrong-job-name')
|
||||
end
|
||||
|
||||
it_behaves_like 'not found'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job does not have artifacts' do
|
||||
let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
|
||||
|
||||
it 'does not return job artifact file' do
|
||||
get_artifact_file('some/artifact')
|
||||
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
def get_artifact_file(artifact_path, ref = pipeline.ref, job_name = job.name)
|
||||
get api("/projects/#{project.id}/jobs/artifacts/#{ref}/raw/#{artifact_path}", api_user), job: job_name
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/jobs/:job_id/trace' do
|
||||
before do
|
||||
get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
|
||||
|
|
Loading…
Reference in a new issue