Skip to content
Snippets Groups Projects
Commit da1f1ef4 authored by Mayra Cabrera's avatar Mayra Cabrera :zero:
Browse files

Adjust exception handling for post-deploy prepare class

`PostDeployMigrations::Prepare` class raises an exception when on
pending post-migrations are available or when the production checks
fail. The exception is not useful:

* Release managers can't take any action from it, in most of the cases,
  they'll need to wait for a resolution.
* We don't raise exceptions on auto-deploy operations when production
  checks fail. We just notify release managers about it.

This commit updates the way the `Prepare` classes handles exception by:

* Sending a message when there are no pending post-migrations
* Catch the exception when there are no pending post-migrations and when
  production health checks fail
* It still sends a message when production checks fail.

The job that invokes the prepare class will still fail.

As part of gitlab-com/gl-infra/delivery#2441
parent 9988c71e
No related branches found
No related tags found
No related merge requests found
......@@ -17,7 +17,9 @@ module ReleaseTools
if pending_post_migrations.empty?
logger.warn('No pending post migrations to be executed, skipping execution')
raise NoPendingPostMigrationsError, 'no pending post migrations'
send_no_pending_post_migration_message
raise NoPendingPostMigrationsError, 'no pending post-migrations'
end
logger.info('Pending post migrations present, executing the post-deploy pipeline', post_migrations: pending_post_migrations)
......@@ -25,6 +27,10 @@ module ReleaseTools
check_production_status
post_note_to_release_issue(pending_post_migrations)
true
rescue NoPendingPostMigrationsError, UnsafeProductionError
false
end
private
......@@ -33,6 +39,10 @@ module ReleaseTools
PostDeployMigrations::Pending.new.execute
end
def send_no_pending_post_migration_message
initial_notification.no_pending_post_migrations_message
end
def post_note_to_release_issue(pending_post_migrations)
ReleaseNote.new(production_status, pending_post_migrations).execute
end
......@@ -41,9 +51,9 @@ module ReleaseTools
if production_status.fine?
logger.info('Production checks have succeeded, proceeding with post-deploy migrations')
else
logger.warn('Production checks have failed', production_checks: failed_checks)
logger.warn('Production checks have failed')
send_slack_message unless SharedStatus.dry_run?
initial_notification.production_status_message(production_status)
raise UnsafeProductionError, 'production checks have failed'
end
......@@ -53,36 +63,8 @@ module ReleaseTools
@production_status ||= ReleaseTools::Promotion::ProductionStatus.new(:canary_up, :active_deployments)
end
def send_slack_message
Retriable.retriable do
ReleaseTools::Slack::ChatopsNotification.fire_hook(
text: text_message,
channel: ReleaseTools::Slack::F_UPCOMING_RELEASE,
blocks: foreword_slack_blocks + production_status.to_slack_blocks
)
end
end
def failed_checks
production_status.failed_checks.collect(&:to_s)
end
def text_message
'Post-deployment migrations are blocked'
end
def foreword_slack_blocks
text = StringIO.new
text.puts(":red_circle: #{release_managers_mention} #{text_message}")
text.puts
blocks = ::Slack::BlockKit.blocks
blocks.section { |block| block.mrkdwn(text: text.string) }
blocks.as_json
end
def release_managers_mention
"<!subteam^#{ReleaseTools::Slack::RELEASE_MANAGERS}>"
def initial_notification
ReleaseTools::Slack::PostDeployPipelineInitialNotification.new
end
end
end
......
......@@ -12,6 +12,7 @@ require 'release_tools/slack/chatops_notification'
require 'release_tools/slack/merge_train_notification'
require 'release_tools/slack/message'
require 'release_tools/slack/post_deploy_migrations_notification'
require 'release_tools/slack/post_deploy_pipeline_initial_notification'
require 'release_tools/slack/qa_notification'
require 'release_tools/slack/search'
require 'release_tools/slack/tag_notification'
......
# frozen_string_literal: true
module ReleaseTools
module Slack
class PostDeployPipelineInitialNotification
def no_pending_post_migrations_message
return if SharedStatus.dry_run?
message =
'No pending post migrations available, skipping post-deploy pipeline execution'
foreword_message = ":ci_passed_with_warnings #{message}"
blocks = foreword_slack_blocks(foreword_message)
send_chatops_notification(message, blocks)
end
def production_status_message(production_status)
return if SharedStatus.dry_run?
message = 'Post-deployment pipeline is blocked'
foreword_message =
":red_circle: #{release_managers_mention} #{message}"
blocks =
foreword_slack_blocks(foreword_message) + production_status.to_slack_blocks
send_chatops_notification(message, blocks)
end
private
def no_pending_post_migrations
'No pending post migrations available, skipping post-deploy pipeline execution'
end
def foreword_slack_blocks(message)
text = StringIO.new
text.puts(message)
text.puts
blocks = ::Slack::BlockKit.blocks
blocks.section { |block| block.mrkdwn(text: text.string) }
blocks.as_json
end
def release_managers_mention
"<!subteam^#{ReleaseTools::Slack::RELEASE_MANAGERS}>"
end
def send_chatops_notification(text, blocks)
Retriable.retriable do
ReleaseTools::Slack::ChatopsNotification.fire_hook(
text: text,
channel: ReleaseTools::Slack::F_UPCOMING_RELEASE,
blocks: blocks
)
end
end
end
end
end
......@@ -12,7 +12,9 @@ namespace :post_deploy_migrations do
desc 'Performs production checks before executing the post-deployment migrations, and posts note to monthly release issue'
task :prepare do
ReleaseTools::AutoDeploy::PostDeployMigrations::Prepare.new.execute
prepare = ReleaseTools::AutoDeploy::PostDeployMigrations::Prepare.new
raise 'Skipping post-deploy pipeline execution' unless prepare.execute
end
desc 'Builds post-migrations artifacts'
......
......@@ -3,9 +3,6 @@
require 'spec_helper'
describe ReleaseTools::AutoDeploy::PostDeployMigrations::Prepare do
let(:healthy) { true }
let(:failed_checks) { [] }
let(:post_migrations) do
[
'20220510192117_foo.rb',
......@@ -17,12 +14,14 @@ describe ReleaseTools::AutoDeploy::PostDeployMigrations::Prepare do
double('ReleaseTools::AutoDeploy::PostDeployMigrations::ReleaseNote')
end
let(:notification) do
double('ReleaseTools::Slack::PostDeployPipelineInitialNotification')
end
let(:production_status) do
instance_spy(
ReleaseTools::Promotion::ProductionStatus,
fine?: healthy,
failed_checks: failed_checks,
to_slack_blocks: ['production status']
fine?: true
)
end
......@@ -47,88 +46,38 @@ describe ReleaseTools::AutoDeploy::PostDeployMigrations::Prepare do
allow(ReleaseTools::AutoDeploy::PostDeployMigrations::ReleaseNote)
.to receive(:new)
.and_return(release_note)
allow(ReleaseTools::Slack::PostDeployPipelineInitialNotification)
.to receive(:new)
.and_return(notification)
end
describe '#execute' do
context 'with pending post migrations' do
it 'verifies the production checks and posts a note to the release issue' do
it 'proceeds to execute the post-deploy pipeline' do
expect(production_status).to receive(:fine?)
expect(release_note).to receive(:execute)
prepare.execute
expect(prepare.execute).to be_truthy
end
context 'when production is unhealthy' do
shared_examples 'skips post-deploy pipeline execution' do
let(:healthy) { false }
let(:slack_message) do
[
{
type: 'section',
text: {
type: 'mrkdwn',
text: ":red_circle: <!subteam^S0127FU8PDE> Post-deployment migrations are blocked\n\n"
}
}
]
end
before do
allow(ReleaseTools::Slack::ChatopsNotification)
.to receive(:fire_hook)
end
it 'notifies release managers and raises exception' do
expect(ReleaseTools::Slack::ChatopsNotification)
.to receive(:fire_hook)
.with(
blocks: slack_message + ['production status'],
text: 'Post-deployment migrations are blocked',
channel: ReleaseTools::Slack::F_UPCOMING_RELEASE
)
expect do
without_dry_run { prepare.execute }
end.to raise_error('production checks have failed')
end
it 'skips posting a note on the release issue' do
expect(release_note).not_to receive(:execute)
expect do
prepare.execute
end.to raise_error('production checks have failed')
end
context 'with dry run' do
it 'does not post to Slack and raises error' do
expect(ReleaseTools::Slack::ChatopsNotification)
.not_to receive(:fire_hook)
expect do
prepare.execute
end.to raise_error('production checks have failed')
end
end
let(:production_status) do
instance_spy(
ReleaseTools::Promotion::ProductionStatus,
fine?: false,
to_slack_blocks: ['production status']
)
end
it_behaves_like 'skips post-deploy pipeline execution'
it 'skips post-deploy pipeline execution' do
expect(notification)
.to receive(:production_status_message)
.with(production_status)
context 'with active incidents' do
let(:failed_checks) do
[ReleaseTools::Promotion::Checks::ActiveIncidents]
end
expect(release_note).not_to receive(:execute)
it_behaves_like 'skips post-deploy pipeline execution'
end
context 'with active deployments' do
let(:failed_checks) do
[ReleaseTools::Promotion::Checks::ActiveDeployments]
end
it_behaves_like 'skips post-deploy pipeline execution'
expect(prepare.execute).to be_falsy
end
end
end
......@@ -136,10 +85,13 @@ describe ReleaseTools::AutoDeploy::PostDeployMigrations::Prepare do
context 'without pending post-migrations' do
let(:post_migrations) { [] }
it 'raises an error' do
expect do
prepare.execute
end.to raise_error('no pending post migrations')
it 'skips post-deploy pipeline execution' do
expect(notification)
.to receive(:no_pending_post_migrations_message)
expect(release_note).not_to receive(:execute)
expect(prepare.execute).to be_falsy
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
describe ReleaseTools::Slack::PostDeployPipelineInitialNotification do
subject(:initial_notification) { described_class.new }
before do
allow(ReleaseTools::Slack::ChatopsNotification)
.to receive(:fire_hook)
.and_return({})
end
describe '#no_pending_post_migration_message' do
it 'sends a chatops message' do
block_message =
":ci_passed_with_warnings No pending post migrations available, skipping post-deploy pipeline execution\n\n"
expect(ReleaseTools::Slack::ChatopsNotification)
.to receive(:fire_hook)
.with(
text: 'No pending post migrations available, skipping post-deploy pipeline execution',
channel: ReleaseTools::Slack::F_UPCOMING_RELEASE,
blocks: slack_mrkdwn_block(text: block_message)
)
without_dry_run do
initial_notification.no_pending_post_migrations_message
end
end
context 'with dry-run' do
it 'skips sending a message' do
expect(ReleaseTools::Slack::Message)
.not_to receive(:post)
initial_notification.no_pending_post_migrations_message
end
end
end
describe '#production_status_message' do
let(:production_status) do
instance_double(
ReleaseTools::Promotion::ProductionStatus,
{
to_slack_blocks: ['blocks']
}
)
end
it 'sends a chatops message' do
block_message =
":red_circle: <!subteam^#{ReleaseTools::Slack::RELEASE_MANAGERS}> Post-deployment pipeline is blocked\n\n"
expect(ReleaseTools::Slack::ChatopsNotification)
.to receive(:fire_hook)
.with(
text: 'Post-deployment pipeline is blocked',
channel: ReleaseTools::Slack::F_UPCOMING_RELEASE,
blocks: slack_mrkdwn_block(text: block_message) + ['blocks']
)
without_dry_run do
initial_notification.production_status_message(production_status)
end
end
context 'with a dry-run' do
it 'skips sending a message' do
expect(ReleaseTools::Slack::ChatopsNotification)
.not_to receive(:fire_hook)
initial_notification.production_status_message(production_status)
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment