Skip to content

Commit

Permalink
Add a task to clean up workflow's closed projects
Browse files Browse the repository at this point in the history
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
saraycp committed Mar 29, 2022
1 parent f366d5b commit 5a977eb
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 0 deletions.
33 changes: 33 additions & 0 deletions src/api/lib/tasks/workflows.rake
@@ -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
76 changes: 76 additions & 0 deletions src/api/spec/lib/tasks/workflows_spec.rb
@@ -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

0 comments on commit 5a977eb

Please sign in to comment.