Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a task to clean up workflow's closed projects
If something goes wrong during the run of a workflow and the error is not correctly caught (yet), the workflow_run can stay in 'running' status forever and the temporary project created by the workflow is not removed as expected. Apart from fixing the errors, we need to ensure we clean up those projects. This rake task is introduced for that purpose. As the artifacts were never created, we rely on the workflow_run's payload information to get part of the project name (repository name and the PR number).
- Loading branch information
Showing
2 changed files
with
109 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
namespace :workflows do | ||
desc 'Remove projects that were not closed as expected and set workflow run status to running' | ||
task cleanup_non_closed_projects: :environment do | ||
workflow_runs = WorkflowRun.where(status: 'running') | ||
.select do |workflow_run| | ||
workflow_run.hook_event.in?(['pull_request', 'Merge Request Hook']) && | ||
workflow_run.hook_action.in?(['closed', 'close', 'merge']) | ||
end | ||
|
||
puts "There are #{workflow_runs.count} workflow runs affected" | ||
|
||
workflow_runs.each do |workflow_run| | ||
projects = Project.where('name LIKE ?', "%#{target_project_name_postfix(workflow_run)}") | ||
|
||
# If there is more than one project, we don't know which of them is the one related to the current | ||
# workflow run (as we only can get the postfix, we don't have the full project name). | ||
next if projects.count > 1 | ||
|
||
# If there is no project to remove (previously removed), the workflow run should change the status anyway. | ||
User.get_default_admin.run_as { projects.first.destroy } if projects.count == 1 | ||
workflow_run.update(status: 'success') | ||
rescue StandardError => e | ||
Airbrake.notify("Failed to remove project created by the workflow: #{e}") | ||
next | ||
end | ||
end | ||
end | ||
|
||
# If the name of the project created by the workflow is "home:Iggy:iggy:hello_world:PR-68", its postfix | ||
# is "iggy:hello_world:PR-68". This is the only information we can extract from the workflow_run. | ||
def target_project_name_postfix(workflow_run) | ||
":#{workflow_run.repository_name.tr('/', ':')}:PR-#{workflow_run.event_source_name}" if workflow_run.repository_name && workflow_run.event_source_name | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
require 'rails_helper' | ||
|
||
# rubocop:disable RSpec/DescribeClass | ||
RSpec.describe 'workflows' do | ||
# rubocop:enable RSpec/DescribeClass | ||
include_context 'rake' | ||
|
||
let!(:admin_user) { create(:admin_user, login: 'Admin') } | ||
let(:gitlab_request_headers) do | ||
<<~END_OF_HEADERS | ||
HTTP_X_GITLAB_EVENT: Merge Request Hook | ||
END_OF_HEADERS | ||
end | ||
let(:github_request_payload_opened) do | ||
<<~END_OF_REQUEST | ||
{ | ||
"action": "opened", | ||
"pull_request": { | ||
"number": 1 | ||
}, | ||
"repository": { | ||
"full_name": "iggy/hello_world" | ||
} | ||
} | ||
END_OF_REQUEST | ||
end | ||
let(:github_request_payload_closed) do | ||
<<~END_OF_REQUEST | ||
{ | ||
"action": "closed", | ||
"pull_request": { | ||
"number": 2 | ||
}, | ||
"repository": { | ||
"full_name": "iggy/hello_world" | ||
} | ||
} | ||
END_OF_REQUEST | ||
end | ||
let(:gitlab_request_payload_merge) do | ||
<<~END_OF_REQUEST | ||
{ | ||
"object_kind": "merge_request", | ||
"event_type": "merge_request", | ||
"object_attributes": { | ||
"iid": 3, | ||
"action": "merge" | ||
}, | ||
"repository": { | ||
"name": "iggy/test" | ||
} | ||
} | ||
END_OF_REQUEST | ||
end | ||
|
||
let!(:project_iggy_hello_world_pr1) { create(:project, name: 'home:Iggy:iggy:hello_world:PR-1') } | ||
let!(:project_iggy_hello_world_pr2) { create(:project, name: 'home:Iggy:iggy:hello_world:PR-2') } | ||
let!(:project_iggy_test_pr3) { create(:project, name: 'home:Iggy:iggy:test:PR-3') } | ||
|
||
let!(:workflow_run_running_pr_opened) { create(:running_workflow_run, request_payload: github_request_payload_opened) } | ||
let!(:workflow_run_succeeded_pr_opened) { create(:succeeded_workflow_run, request_payload: github_request_payload_opened) } | ||
let!(:workflow_run_failed_pr_opened) { create(:failed_workflow_run, request_payload: github_request_payload_opened) } | ||
let!(:workflow_run_running_pr_closed) { create(:running_workflow_run, request_payload: github_request_payload_closed) } | ||
let!(:another_workflow_run_running_pr_closed) { create(:running_workflow_run, request_payload: github_request_payload_closed) } | ||
let!(:workflow_run_running_pr_merge) { create(:running_workflow_run, request_headers: gitlab_request_headers, request_payload: gitlab_request_payload_merge) } | ||
|
||
describe 'cleanup_non_closed_projects' do | ||
let(:task) { 'workflows:cleanup_non_closed_projects' } | ||
|
||
it { expect { rake_task.invoke }.to change(WorkflowRun.where(status: 'running'), :count).from(4).to(1) } | ||
|
||
# The workflow runs defined above will create two target projects that should be deleted | ||
# because the corresponding PR or MR are closed/merged. | ||
it { expect { rake_task.invoke }.to change(Project, :count).from(3).to(1) } | ||
end | ||
end |