Commit 9c233fa9 authored by Grzegorz Bizon's avatar Grzegorz Bizon

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
parents 4e24ccc4 de605ad1
...@@ -16,6 +16,10 @@ module Ci ...@@ -16,6 +16,10 @@ module Ci
enable :update_pipeline enable :update_pipeline
end end
rule { can?(:owner_access) }.policy do
enable :destroy_pipeline
end
def ref_protected?(user, project, tag, ref) def ref_protected?(user, project, tag, ref)
access = ::Gitlab::UserAccess.new(user, project: project) access = ::Gitlab::UserAccess.new(user, project: project)
......
# 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
---
title: Allow deleting a Pipeline via the API.
merge_request: 22988
author:
type: added
...@@ -235,5 +235,22 @@ Response: ...@@ -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-5837]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5837
[ce-7209]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7209 [ce-7209]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7209
...@@ -81,6 +81,21 @@ module API ...@@ -81,6 +81,21 @@ module API
present pipeline, with: Entities::Pipeline present pipeline, with: Entities::Pipeline
end 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 desc 'Retry builds in the pipeline' do
detail 'This feature was introduced in GitLab 8.11.' detail 'This feature was introduced in GitLab 8.11.'
success Entities::Pipeline success Entities::Pipeline
......
...@@ -74,5 +74,23 @@ describe Ci::PipelinePolicy, :models do ...@@ -74,5 +74,23 @@ describe Ci::PipelinePolicy, :models do
expect(policy).to be_allowed :update_pipeline expect(policy).to be_allowed :update_pipeline
end end
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
end end
...@@ -438,6 +438,67 @@ describe API::Pipelines do ...@@ -438,6 +438,67 @@ describe API::Pipelines do
end end
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 describe 'POST /projects/:id/pipelines/:pipeline_id/retry' do
context 'authorized user' do context 'authorized user' do
let!(:pipeline) do let!(:pipeline) do
......
# 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
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