From 3fb4a62f6f40ebf04c9604420ea10e78c6774a6b Mon Sep 17 00:00:00 2001 From: Akash Srivastava Date: Sat, 24 Oct 2020 21:57:49 +0530 Subject: [PATCH 1/2] Added missing methods and fixed existing ones with file download in job artifacts API --- lib/gitlab/client/jobs.rb | 70 +++++++++++++++-- spec/fixtures/job_artifacts.zip | Bin 0 -> 180 bytes spec/gitlab/client/jobs_spec.rb | 133 ++++++++++++++++++++++++++++++-- 3 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 spec/fixtures/job_artifacts.zip diff --git a/lib/gitlab/client/jobs.rb b/lib/gitlab/client/jobs.rb index 39cfad40f..4c61336d3 100644 --- a/lib/gitlab/client/jobs.rb +++ b/lib/gitlab/client/jobs.rb @@ -70,16 +70,70 @@ def job_artifacts(project_id, job_id) # Gitlab.job_artifacts_download(1, "master", "release") # Gitlab.job_artifacts_download("project", "master", "release") # - # @param [Integer, String] id, The ID or name of a project. - # @param [String] ref, Ref Name - # @param [String] job, jobname - # @return [Array] + # @param [Integer, String] project_id The ID or name of a project. + # @param [String] ref Ref Name + # @param [String] job jobname + # @return [Gitlab::FileResponse] def job_artifacts_download(project_id, ref_name, job_name) - get("/projects/#{url_encode project_id}/jobs/artifacts/#{ref_name}/download", query: { job: job_name }, - format: nil, - headers: { Accept: 'text/plain' }, - parser: ::Gitlab::Request::Parser) + get("/projects/#{url_encode project_id}/jobs/artifacts/#{ref_name}/download", + query: { job: job_name }, + format: nil, + headers: { Accept: 'application/octet-stream' }, + parser: proc { |body, _| + if body.encoding == Encoding::ASCII_8BIT # binary response + ::Gitlab::FileResponse.new StringIO.new(body, 'rb+') + else # error with json response + ::Gitlab::Request.parse(body) + end + }) + end + + # Download a single artifact file by job ID + # + # @example + # Gitlab.download_job_artifact_file(1, 5, "some/release/file.pdf") + # + # @param [Integer, String] project_id(required) The ID or name of a project. + # @param [String] job_id(required) The unique job identifier. + # @param [String] artifact_path(required) Path to a file inside the artifacts archive. + # @return [Gitlab::FileResponse] + def download_job_artifact_file(project_id, job_id, artifact_path) + get("/projects/#{url_encode project_id}/jobs/#{job_id}/artifacts/#{artifact_path}", + format: nil, + headers: { Accept: 'application/octet-stream' }, + parser: proc { |body, _| + if body.encoding == Encoding::ASCII_8BIT # binary response + ::Gitlab::FileResponse.new StringIO.new(body, 'rb+') + else # error with json response + ::Gitlab::Request.parse(body) + end + }) + end + + # Download a single artifact file from specific tag or branch + # + # @example + # Gitlab.download_branch_artifact_file(1, "master", "some/release/file.pdf", 'pdf') + # + # @param [Integer, String] project_id(required) The ID or name of a project. + # @param [String] ref_name(required) Branch or tag name in repository. HEAD or SHA references are not supported. + # @param [String] artifact_path(required) Path to a file inside the artifacts archive. + # @param [String] job(required) The name of the job. + # @return [Gitlab::FileResponse] + def download_branch_artifact_file(project_id, ref_name, artifact_path, job) + get("/projects/#{url_encode project_id}/jobs/artifacts/#{ref_name}/raw/#{artifact_path}", + query: { job: job }, + format: nil, + headers: { Accept: 'application/octet-stream' }, + parser: proc { |body, _| + if body.encoding == Encoding::ASCII_8BIT # binary response + ::Gitlab::FileResponse.new StringIO.new(body, 'rb+') + else # error with json response + ::Gitlab::Request.parse(body) + end + }) end + alias download_tag_artifact_file download_branch_artifact_file # Get Job Trace # diff --git a/spec/fixtures/job_artifacts.zip b/spec/fixtures/job_artifacts.zip new file mode 100644 index 0000000000000000000000000000000000000000..6851e346addae5f29b68c11494da8f9240e1a33d GIT binary patch literal 180 zcmWIWW@h1H0D-5RhdjF3>Q{3B*&xi#Aj6Pak(gVMld4xzQ4$)$$-vCV Gitlab.private_token }) + .to_return(body: fixture.read, headers: { 'Content-Disposition' => 'attachment; filename=job_artifacts.zip' }) + @job_artifacts = Gitlab.job_artifacts_download(3, 'master', 'test') + end + + it 'gets the correct resource' do + expect(a_get('/projects/3/jobs/artifacts/master/download') + .with(query: { job: 'test' })).to have_been_made + end + + it 'returns a FileResponse' do + expect(@job_artifacts).to be_a Gitlab::FileResponse + end + + it 'returns a file with filename' do + expect(@job_artifacts.filename).to eq 'job_artifacts.zip' + end end - it 'gets the correct resource' do - expect(a_get('/projects/1/jobs/artifacts/master/download?job=Release%20Build')).to have_been_made + context 'when bad request' do + it 'throws an exception' do + stub_get('/projects/3/jobs/artifacts/master/download', 'error_project_not_found', 404) + .with(query: { job: 'test' }) + expect { Gitlab.job_artifacts_download(3, 'master', 'test') }.to raise_error(Gitlab::Error::NotFound, "Server responded with code 404, message: 404 Project Not Found. Request URI: #{Gitlab.endpoint}/projects/3/jobs/artifacts/master/download") + end + end + end + + describe '.download_job_artifact_file' do + context 'when successful request' do + before do + fixture = load_fixture('raw_file.txt') + fixture.set_encoding(Encoding::ASCII_8BIT) + stub_request(:get, "#{Gitlab.endpoint}/projects/3/jobs/5/artifacts/raw_file.txt") + .with(headers: { 'PRIVATE-TOKEN' => Gitlab.private_token }) + .to_return(body: fixture.read, headers: { 'Content-Disposition' => 'attachment; filename=raw_file.txt' }) + @job_artifact_file = Gitlab.download_job_artifact_file(3, 5, 'raw_file.txt') + end + + it 'gets the correct resource' do + expect(a_get('/projects/3/jobs/5/artifacts/raw_file.txt')).to have_been_made + end + + it 'returns a FileResponse' do + expect(@job_artifact_file).to be_a Gitlab::FileResponse + end + + it 'returns a file with filename' do + expect(@job_artifact_file.filename).to eq 'raw_file.txt' + end + end + + context 'when bad request' do + it 'throws an exception' do + stub_get('/projects/3/jobs/5/artifacts/raw_file.txt', 'error_project_not_found', 404) + expect { Gitlab.download_job_artifact_file(3, 5, 'raw_file.txt') }.to raise_error(Gitlab::Error::NotFound, "Server responded with code 404, message: 404 Project Not Found. Request URI: #{Gitlab.endpoint}/projects/3/jobs/5/artifacts/raw_file.txt") + end + end + end + + describe '.download_branch_artifact_file' do + context 'when successful request' do + before do + fixture = load_fixture('raw_file.txt') + fixture.set_encoding(Encoding::ASCII_8BIT) + stub_request(:get, "#{Gitlab.endpoint}/projects/1/jobs/artifacts/master/raw/raw_file.txt") + .with(query: { job: 'txt' }, headers: { 'PRIVATE-TOKEN' => Gitlab.private_token }) + .to_return(body: fixture.read, headers: { 'Content-Disposition' => 'attachment; filename=raw_file.txt' }) + @branch_artifact_file = Gitlab.download_branch_artifact_file(1, 'master', 'raw_file.txt', 'txt') + end + + it 'gets the correct resource' do + expect(a_get('/projects/1/jobs/artifacts/master/raw/raw_file.txt') + .with(query: { job: 'txt' })).to have_been_made + end + + it 'returns a FileResponse' do + expect(@branch_artifact_file).to be_a Gitlab::FileResponse + end + + it 'returns a file with filename' do + expect(@branch_artifact_file.filename).to eq 'raw_file.txt' + end + end + + context 'when bad request' do + it 'throws an exception' do + stub_get('/projects/1/jobs/artifacts/master/raw/raw_file.txt', 'error_project_not_found', 404) + .with(query: { job: 'txt' }) + expect { Gitlab.download_branch_artifact_file(1, 'master', 'raw_file.txt', 'txt') }.to raise_error(Gitlab::Error::NotFound, "Server responded with code 404, message: 404 Project Not Found. Request URI: #{Gitlab.endpoint}/projects/1/jobs/artifacts/master/raw/raw_file.txt") + end + end + end + + describe '.download_tag_artifact_file' do + context 'when successful request' do + before do + fixture = load_fixture('raw_file.txt') + fixture.set_encoding(Encoding::ASCII_8BIT) + stub_request(:get, "#{Gitlab.endpoint}/projects/1/jobs/artifacts/release/raw/raw_file.txt") + .with(query: { job: 'txt' }, headers: { 'PRIVATE-TOKEN' => Gitlab.private_token }) + .to_return(body: fixture.read, headers: { 'Content-Disposition' => 'attachment; filename=raw_file.txt' }) + @branch_artifact_file = Gitlab.download_tag_artifact_file(1, 'release', 'raw_file.txt', 'txt') + end + + it 'gets the correct resource' do + expect(a_get('/projects/1/jobs/artifacts/release/raw/raw_file.txt') + .with(query: { job: 'txt' })).to have_been_made + end + + it 'returns a FileResponse' do + expect(@branch_artifact_file).to be_a Gitlab::FileResponse + end + + it 'returns a file with filename' do + expect(@branch_artifact_file.filename).to eq 'raw_file.txt' + end + end + + context 'when bad request' do + it 'throws an exception' do + stub_get('/projects/1/jobs/artifacts/release/raw/raw_file.txt', 'error_project_not_found', 404) + .with(query: { job: 'txt' }) + expect { Gitlab.download_tag_artifact_file(1, 'release', 'raw_file.txt', 'txt') }.to raise_error(Gitlab::Error::NotFound, "Server responded with code 404, message: 404 Project Not Found. Request URI: #{Gitlab.endpoint}/projects/1/jobs/artifacts/release/raw/raw_file.txt") + end end end From ffd69552c214aa4d8778ab357e70cae2bcb75027 Mon Sep 17 00:00:00 2001 From: Akash Srivastava Date: Sat, 24 Oct 2020 22:03:51 +0530 Subject: [PATCH 2/2] Rubocop fixes --- lib/gitlab/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/client.rb b/lib/gitlab/client.rb index ef75d0513..a4848a503 100644 --- a/lib/gitlab/client.rb +++ b/lib/gitlab/client.rb @@ -82,7 +82,7 @@ def inspect # # @return [String] def url_encode(url) - url.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| sprintf('%%%02X', m.unpack1('C')) } # rubocop:disable Style/FormatString, Style/FormatStringToken + url.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| sprintf('%%%02X', m.unpack1('C')) } # rubocop:disable Style/FormatString end private