diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 01d96754518..b81a3cf8362 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -750,6 +750,10 @@ module Ci self.sha == sha || self.source_sha == sha end + def triggered_by?(current_user) + user == current_user + end + private def ci_yaml_from_repo diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb index 2c90b8a73cd..662c29a0973 100644 --- a/app/policies/ci/pipeline_policy.rb +++ b/app/policies/ci/pipeline_policy.rb @@ -14,6 +14,10 @@ module Ci @subject.external? end + condition(:triggerer_of_pipeline) do + @subject.triggered_by?(@user) + end + # Disallow users without permissions from accessing internal pipelines rule { ~can?(:read_build) & ~external_pipeline }.policy do prevent :read_pipeline @@ -29,6 +33,14 @@ module Ci enable :destroy_pipeline end + rule { can?(:admin_pipeline) }.policy do + enable :read_pipeline_variable + end + + rule { can?(:update_pipeline) & triggerer_of_pipeline }.policy do + enable :read_pipeline_variable + end + def ref_protected?(user, project, tag, ref) access = ::Gitlab::UserAccess.new(user, project: project) diff --git a/changelogs/unreleased/expose-pipeline-variables-via-api.yml b/changelogs/unreleased/expose-pipeline-variables-via-api.yml new file mode 100644 index 00000000000..f37bf0c5cd8 --- /dev/null +++ b/changelogs/unreleased/expose-pipeline-variables-via-api.yml @@ -0,0 +1,5 @@ +--- +title: Expose pipeline variables via API +merge_request: 26501 +author: Agustin Henze +type: added diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 43bbf463c8d..1a4310ef328 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -93,6 +93,36 @@ Example of response } ``` +### Get variables of a pipeline + +``` +GET /projects/:id/pipelines/:pipeline_id/variables +``` + +| 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 | +| `pipeline_id` | integer | yes | The ID of a pipeline | + +``` +curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects/1/pipelines/46/variables" +``` + +Example of response + +```json +[ + { + "key": "RUN_NIGHTLY_BUILD", + "value": "true" + }, + { + "key": "foo", + "value": "bar" + } +] +``` + ## Create a new pipeline > [Introduced][ce-7209] in GitLab 8.14 diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index ac8fe98e55e..f29a18e94cf 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -81,6 +81,19 @@ module API present pipeline, with: Entities::Pipeline end + desc 'Gets the variables for a given pipeline' do + detail 'This feature was introduced in GitLab 11.11' + success Entities::Variable + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + get ':id/pipelines/:pipeline_id/variables' do + authorize! :read_pipeline_variable, pipeline + + present pipeline.variables, with: Entities::Variable + end + desc 'Deletes a pipeline' do detail 'This feature was introduced in GitLab 11.6' http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']] diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb index 844d96017de..126d44d1860 100644 --- a/spec/policies/ci/pipeline_policy_spec.rb +++ b/spec/policies/ci/pipeline_policy_spec.rb @@ -100,5 +100,51 @@ describe Ci::PipelinePolicy, :models do end end end + + describe 'read_pipeline_variable' do + let(:project) { create(:project, :public) } + + context 'when user has owner access' do + let(:user) { project.owner } + + it 'is enabled' do + expect(policy).to be_allowed :read_pipeline_variable + end + end + + context 'when user is developer and the creator of the pipeline' do + let(:pipeline) { create(:ci_empty_pipeline, project: project, user: user) } + + before do + project.add_developer(user) + create(:protected_branch, :developers_can_merge, + name: pipeline.ref, project: project) + end + + it 'is enabled' do + expect(policy).to be_allowed :read_pipeline_variable + end + end + + context 'when user is developer and it is not the creator of the pipeline' do + let(:pipeline) { create(:ci_empty_pipeline, project: project, user: project.owner) } + + before do + project.add_developer(user) + create(:protected_branch, :developers_can_merge, + name: pipeline.ref, project: project) + end + + it 'is disabled' do + expect(policy).to be_disallowed :read_pipeline_variable + end + end + + context 'when user is not owner nor developer' do + it 'is disabled' do + expect(policy).not_to be_allowed :read_pipeline_variable + end + end + end end end diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 9fed07cae82..0d46463312b 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -445,6 +445,72 @@ describe API::Pipelines do end end + describe 'GET /projects/:id/pipelines/:pipeline_id/variables' do + subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", api_user) } + + let(:api_user) { user } + + context 'user is a mantainer' do + it 'returns pipeline variables empty' do + subject + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_empty + end + + context 'with variables' do + let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') } + + it 'returns pipeline variables' do + subject + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to contain_exactly({ "key" => "foo", "value" => "bar" }) + end + end + end + + context 'user is a developer' do + let(:pipeline_owner_user) { create(:user) } + let(:pipeline) { create(:ci_empty_pipeline, project: project, user: pipeline_owner_user) } + + before do + project.add_developer(api_user) + end + + context 'pipeline created by the developer user' do + let(:api_user) { pipeline_owner_user } + let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') } + + it 'returns pipeline variables' do + subject + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to contain_exactly({ "key" => "foo", "value" => "bar" }) + end + end + + context 'pipeline created is not created by the developer user' do + let(:api_user) { create(:user) } + + it 'should not return pipeline variables' do + subject + + expect(response).to have_gitlab_http_status(403) + end + end + end + + context 'user is not a project member' do + it 'should not return pipeline variables' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", non_member) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq '404 Project Not Found' + end + end + end + describe 'DELETE /projects/:id/pipelines/:pipeline_id' do context 'authorized user' do let(:owner) { project.owner }