Commit 3a87f0d6 authored by Allison Browne's avatar Allison Browne Committed by Bob Van Landuyt

Add status page unpublish logic

Add status page unpublish logic for images
parent ee9f0224
...@@ -53,10 +53,12 @@ module StatusPage ...@@ -53,10 +53,12 @@ module StatusPage
success(object_key: key) success(object_key: key)
end end
def delete(key) def delete_object(key)
storage_client.delete_object(key) storage_client.delete_object(key)
end
success(object_key: key) def recursive_delete(prefix)
storage_client.recursive_delete(prefix)
end end
def limit_exceeded?(json) def limit_exceeded?(json)
......
...@@ -13,12 +13,21 @@ module StatusPage ...@@ -13,12 +13,21 @@ module StatusPage
private private
def process(issue) def process(issue)
key = object_key(issue) # Delete the incident prior to deleting images to avoid broken links
json_key = json_object_key(issue)
delete_object(json_key)
delete(key) upload_keys_prefix = uploads_path(issue)
recursive_delete(upload_keys_prefix)
success(object_key: json_key)
end
def uploads_path(issue)
StatusPage::Storage.uploads_path(issue.iid)
end end
def object_key(issue) def json_object_key(issue)
StatusPage::Storage.details_path(issue.iid) StatusPage::Storage.details_path(issue.iid)
end end
end end
......
...@@ -45,7 +45,7 @@ module StatusPage ...@@ -45,7 +45,7 @@ module StatusPage
objects = response.contents.map { |obj| { key: obj.key } } objects = response.contents.map { |obj| { key: obj.key } }
# Batch delete in sets determined by default max_key argument that can be passed to list_objects_v2 # Batch delete in sets determined by default max_key argument that can be passed to list_objects_v2
client.delete_objects({ bucket: bucket_name, delete: { objects: objects } }) client.delete_objects({ bucket: bucket_name, delete: { objects: objects } }) unless objects.empty?
end end
end end
......
...@@ -96,6 +96,16 @@ describe StatusPage::Storage::S3Client, :aws_s3 do ...@@ -96,6 +96,16 @@ describe StatusPage::Storage::S3Client, :aws_s3 do
end end
end end
context 'when list_object returns no objects' do
include_context 'no objects list_objects_v2 result'
it 'does not attempt to delete' do
expect(aws_client).not_to receive(:delete_objects).with(delete_objects_data(key_list_no_objects))
result
end
end
context 'when failed' do context 'when failed' do
let(:aws_error) { 'SomeError' } let(:aws_error) { 'SomeError' }
...@@ -129,6 +139,15 @@ describe StatusPage::Storage::S3Client, :aws_s3 do ...@@ -129,6 +139,15 @@ describe StatusPage::Storage::S3Client, :aws_s3 do
end end
end end
context 'when list_object returns no objects' do
include_context 'no objects list_objects_v2 result'
it 'returns an empty set' do
expect(result).to be_an_instance_of(Set)
expect(result.empty?).to be(true)
end
end
context 'when failed' do context 'when failed' do
let(:aws_error) { 'SomeError' } let(:aws_error) { 'SomeError' }
......
...@@ -45,20 +45,20 @@ describe StatusPage::PublishService do ...@@ -45,20 +45,20 @@ describe StatusPage::PublishService do
describe 'unpublish details' do describe 'unpublish details' do
let_it_be(:issue) { create(:issue, :confidential, project: project) } let_it_be(:issue) { create(:issue, :confidential, project: project) }
context 'when deletion succeeds' do context 'when unpublish succeeds' do
it 'deletes incident details and upload list' do it 'unpublishes incident details and uploads incident list' do
expect_to_delete_details(issue) expect_to_unpublish(error?: false)
expect_to_upload_list expect_to_upload_list
expect(result).to be_success expect(result).to be_success
end end
end end
context 'when deletion fails' do context 'when unpublish service responses with error' do
it 'propagates the exception' do it 'returns the response' do
expect_to_delete_details(issue, status: 403) response = expect_to_unpublish(error?: true)
expect { result }.to raise_error(StatusPage::Storage::Error) expect(result).to be(response)
end end
end end
end end
...@@ -95,6 +95,15 @@ describe StatusPage::PublishService do ...@@ -95,6 +95,15 @@ describe StatusPage::PublishService do
private private
def expect_to_unpublish(**response_kwargs)
service_response = double(**response_kwargs)
expect_next_instance_of(StatusPage::UnpublishDetailsService) do |service|
expect(service).to receive(:execute).and_return(service_response)
end
service_response
end
def expect_to_upload_details(issue, **kwargs) def expect_to_upload_details(issue, **kwargs)
stub_aws_request(:put, StatusPage::Storage.details_path(issue.iid), **kwargs) stub_aws_request(:put, StatusPage::Storage.details_path(issue.iid), **kwargs)
end end
......
...@@ -7,6 +7,7 @@ describe StatusPage::UnpublishDetailsService do ...@@ -7,6 +7,7 @@ describe StatusPage::UnpublishDetailsService do
let(:issue) { instance_double(Issue, iid: incident_id) } let(:issue) { instance_double(Issue, iid: incident_id) }
let(:incident_id) { 1 } let(:incident_id) { 1 }
let(:key) { StatusPage::Storage.details_path(incident_id) } let(:key) { StatusPage::Storage.details_path(incident_id) }
let(:image_uploads_path) { StatusPage::Storage.uploads_path(issue.iid) }
let(:service) { described_class.new(project: project) } let(:service) { described_class.new(project: project) }
...@@ -31,15 +32,23 @@ describe StatusPage::UnpublishDetailsService do ...@@ -31,15 +32,23 @@ describe StatusPage::UnpublishDetailsService do
context 'when deletion succeeds' do context 'when deletion succeeds' do
before do before do
allow(storage_client).to receive(:delete_object).with(key) allow(storage_client).to receive(:delete_object).with(key)
allow(storage_client).to receive(:recursive_delete).with(image_uploads_path)
end end
it 'removes details from CDN' do it 'removes files from the CDN (incident first)' do
expect(storage_client).to receive(:delete_object).ordered
expect(storage_client).to receive(:recursive_delete).with(image_uploads_path).ordered
result
end
it 'returns service success' do
expect(result).to be_success expect(result).to be_success
expect(result.payload).to eq(object_key: key) expect(result.payload).to eq(object_key: key)
end end
end end
context 'when upload fails due to exception' do context 'when delete fails due to exception' do
let(:bucket) { 'bucket_name' } let(:bucket) { 'bucket_name' }
let(:error) { StandardError.new } let(:error) { StandardError.new }
...@@ -47,13 +56,28 @@ describe StatusPage::UnpublishDetailsService do ...@@ -47,13 +56,28 @@ describe StatusPage::UnpublishDetailsService do
StatusPage::Storage::Error.new(bucket: bucket, error: error) StatusPage::Storage::Error.new(bucket: bucket, error: error)
end end
before do context 'when json delete fails' do
allow(storage_client).to receive(:delete_object).with(key) before do
.and_raise(exception) allow(storage_client).to receive(:delete_object).with(key)
.and_raise(exception)
allow(storage_client).to receive(:recursive_delete)
end
it 'propagates the exception' do
expect { result }.to raise_error(exception)
end
end end
it 'propagates the exception' do context 'when image delete fails' do
expect { result }.to raise_error(exception) before do
allow(storage_client).to receive(:delete_object)
allow(storage_client).to receive(:recursive_delete).with(image_uploads_path)
.and_raise(exception)
end
it 'propagates the exception' do
expect { result }.to raise_error(exception)
end
end end
end end
......
...@@ -36,3 +36,14 @@ RSpec.shared_context 'oversized list_objects_v2 result' do ...@@ -36,3 +36,14 @@ RSpec.shared_context 'oversized list_objects_v2 result' do
(0...desired_size).to_a.map { |_| SecureRandom.hex } (0...desired_size).to_a.map { |_| SecureRandom.hex }
end end
end end
RSpec.shared_context 'no objects list_objects_v2 result' do
let(:key_list_no_objects) { [] }
before do
stub_responses(
:list_objects_v2,
list_objects_data(key_list: key_list_no_objects, next_continuation_token: nil, is_truncated: false)
)
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