summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzegorz@gitlab.com>2018-11-16 14:02:54 +0000
committerGrzegorz Bizon <grzegorz@gitlab.com>2018-11-16 14:02:54 +0000
commit9c233fa972192940acdc1090c3d9a9ec6e6f5de7 (patch)
tree9222b4d40a3f96b57b881bbe9bdad71a757622ff
parent4e24ccc436e1678c79d71225a3cb9eb3c11ed17a (diff)
parentde605ad1437e8e0beb5da76c900623dd541e1f72 (diff)
downloadgitlab-ce-9c233fa972192940acdc1090c3d9a9ec6e6f5de7.tar.gz
Merge branch '41875-allow-pipelines-to-be-deleted-by-project-owners' into 'master'
Resolve "Allow pipelines to be deleted by project owners" Closes #41875 See merge request gitlab-org/gitlab-ce!22988
-rw-r--r--app/policies/ci/pipeline_policy.rb4
-rw-r--r--app/services/ci/destroy_pipeline_service.rb13
-rw-r--r--changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml5
-rw-r--r--doc/api/pipelines.md17
-rw-r--r--lib/api/pipelines.rb15
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb18
-rw-r--r--spec/requests/api/pipelines_spec.rb61
-rw-r--r--spec/services/ci/destroy_pipeline_service_spec.rb60
8 files changed, 193 insertions, 0 deletions
diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb
index f9623587957..e42d78f47c5 100644
--- a/app/policies/ci/pipeline_policy.rb
+++ b/app/policies/ci/pipeline_policy.rb
@@ -16,6 +16,10 @@ module Ci
enable :update_pipeline
end
+ rule { can?(:owner_access) }.policy do
+ enable :destroy_pipeline
+ end
+
def ref_protected?(user, project, tag, ref)
access = ::Gitlab::UserAccess.new(user, project: project)
diff --git a/app/services/ci/destroy_pipeline_service.rb b/app/services/ci/destroy_pipeline_service.rb
new file mode 100644
index 00000000000..13f892aabb8
--- /dev/null
+++ b/app/services/ci/destroy_pipeline_service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Ci
+ class DestroyPipelineService < BaseService
+ def execute(pipeline)
+ raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_pipeline, pipeline)
+
+ AuditEventService.new(current_user, pipeline).security_event
+
+ pipeline.destroy!
+ end
+ end
+end
diff --git a/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml b/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml
new file mode 100644
index 00000000000..0662ff6f523
--- /dev/null
+++ b/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml
@@ -0,0 +1,5 @@
+---
+title: Allow deleting a Pipeline via the API.
+merge_request: 22988
+author:
+type: added
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 574be52801c..7b4c9a8fbb3 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -235,5 +235,22 @@ Response:
}
```
+## Delete a pipeline
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22988) in GitLab 11.6.
+
+```
+DELETE /projects/:id/pipelines/:pipeline_id
+```
+
+| 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: 9koXpg98eAheJpvBs5tK" --request "DELETE" "https://gitlab.example.com/api/v4/projects/1/pipelines/46"
+```
+
[ce-5837]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5837
[ce-7209]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7209
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 1cfb982c04b..cba1e3a6684 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -81,6 +81,21 @@ module API
present pipeline, with: Entities::Pipeline
end
+ desc 'Deletes a pipeline' do
+ detail 'This feature was introduced in GitLab 11.6'
+ http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']]
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ delete ':id/pipelines/:pipeline_id' do
+ authorize! :destroy_pipeline, pipeline
+
+ destroy_conditionally!(pipeline) do
+ ::Ci::DestroyPipelineService.new(user_project, current_user).execute(pipeline)
+ end
+ end
+
desc 'Retry builds in the pipeline' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Pipeline
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
index bd32faf06ef..8022f61e67d 100644
--- a/spec/policies/ci/pipeline_policy_spec.rb
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -74,5 +74,23 @@ describe Ci::PipelinePolicy, :models do
expect(policy).to be_allowed :update_pipeline
end
end
+
+ describe 'destroy_pipeline' 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 :destroy_pipeline
+ end
+ end
+
+ context 'when user is not owner' do
+ it 'is disabled' do
+ expect(policy).not_to be_allowed :destroy_pipeline
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index f0e1992bccd..638cc9767d4 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -438,6 +438,67 @@ describe API::Pipelines do
end
end
+ describe 'DELETE /projects/:id/pipelines/:pipeline_id' do
+ context 'authorized user' do
+ let(:owner) { project.owner }
+
+ it 'destroys the pipeline' do
+ delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner)
+
+ expect(response).to have_gitlab_http_status(204)
+ expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'returns 404 when it does not exist' do
+ delete api("/projects/#{project.id}/pipelines/123456", owner)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq '404 Not found'
+ end
+
+ it 'logs an audit event' do
+ expect { delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) }.to change { SecurityEvent.count }.by(1)
+ end
+
+ context 'when the pipeline has jobs' do
+ let!(:build) { create(:ci_build, project: project, pipeline: pipeline) }
+
+ it 'destroys associated jobs' do
+ delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner)
+
+ expect(response).to have_gitlab_http_status(204)
+ expect { build.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ context 'when user is not member' do
+ it 'should return a 404' do
+ delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ end
+ end
+
+ context 'when user is developer' do
+ let(:developer) { create(:user) }
+
+ before do
+ project.add_developer(developer)
+ end
+
+ it 'should return a 403' do
+ delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", developer)
+
+ expect(response).to have_gitlab_http_status(403)
+ expect(json_response['message']).to eq '403 Forbidden'
+ end
+ end
+ end
+ end
+
describe 'POST /projects/:id/pipelines/:pipeline_id/retry' do
context 'authorized user' do
let!(:pipeline) do
diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb
new file mode 100644
index 00000000000..097daf67feb
--- /dev/null
+++ b/spec/services/ci/destroy_pipeline_service_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Ci::DestroyPipelineService do
+ let(:project) { create(:project) }
+ let!(:pipeline) { create(:ci_pipeline, project: project) }
+
+ subject { described_class.new(project, user).execute(pipeline) }
+
+ context 'user is owner' do
+ let(:user) { project.owner }
+
+ it 'destroys the pipeline' do
+ subject
+
+ expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'logs an audit event' do
+ expect { subject }.to change { SecurityEvent.count }.by(1)
+ end
+
+ context 'when the pipeline has jobs' do
+ let!(:build) { create(:ci_build, project: project, pipeline: pipeline) }
+
+ it 'destroys associated jobs' do
+ subject
+
+ expect { build.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'destroys associated stages' do
+ stages = pipeline.stages
+
+ subject
+
+ expect(stages).to all(raise_error(ActiveRecord::RecordNotFound))
+ end
+
+ context 'when job has artifacts' do
+ let!(:artifact) { create(:ci_job_artifact, :archive, job: build) }
+
+ it 'destroys associated artifacts' do
+ subject
+
+ expect { artifact.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+ end
+
+ context 'user is not owner' do
+ let(:user) { create(:user) }
+
+ it 'raises an exception' do
+ expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
+ end
+ end
+end