From 02ce214ac521cf32a1d8e2d28540658e4b2e53e9 Mon Sep 17 00:00:00 2001 From: cornet Date: Wed, 2 Aug 2023 09:19:24 +0100 Subject: [PATCH 1/2] Complete refactor This is a complete refactor while maintaining the current behaviour. We move all the logic into a number of classes: `Deployment::Deployment` This is responsible for checking the status of a state machine execution. If it has failed for some reason then we find the fail event and then pass the event onto the correct class for further parsing. If no class exists then we return a generic error message. `Deployment::Events::FailStateEntered` This handles the case where we hit the Fail state. It then extracts the error attempts to determine if it was sidekiq that failed or not returning the appropriate message. `Deployment::Events::LambdaFunctionFailed` This handles the case where a lambda error causes the state machine execution to terminate but doesn't actually reach the fail state. Currently the only time this happens is during the Pre Flight Checks. This happens to be by accident since looking at the definition then we are supposed to go to the fail state but we are catching the error incorrectly. --- .rubocop.yml | 2 + Gemfile | 8 +- Gemfile.lock | 21 ++ app.rb | 65 ++---- lib/deployment.rb | 3 + lib/deployment/deployment.rb | 61 +++++ lib/deployment/events/fail_state_entered.rb | 36 +++ .../events/lambda_function_failed.rb | 42 ++++ spec/app_spec.rb | 117 ++++++++++ spec/deployment_spec.rb | 208 ++++++++++++++++++ spec/fail_state_entered_spec.rb | 61 +++++ spec/lambda_function_failed_spec.rb | 79 +++++++ spec/spec_helper.rb | 3 + 13 files changed, 656 insertions(+), 50 deletions(-) create mode 100644 .rubocop.yml create mode 100644 lib/deployment.rb create mode 100644 lib/deployment/deployment.rb create mode 100644 lib/deployment/events/fail_state_entered.rb create mode 100644 lib/deployment/events/lambda_function_failed.rb create mode 100644 spec/app_spec.rb create mode 100644 spec/deployment_spec.rb create mode 100644 spec/fail_state_entered_spec.rb create mode 100644 spec/lambda_function_failed_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..45a9174 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,2 @@ +Metrics/BlockLength: + Enabled: false diff --git a/Gemfile b/Gemfile index b5aabfc..e72c38f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,9 @@ source 'https://rubygems.org' do gem 'aws-sdk-states' -end \ No newline at end of file + + group :test do + gem 'nokogiri' + gem 'rspec' + gem 'rspec-mocks' + end +end diff --git a/Gemfile.lock b/Gemfile.lock index 7d84981..bc02bca 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,13 +16,34 @@ GEM aws-sigv4 (~> 1.1) aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) + diff-lcs (1.5.1) jmespath (1.6.2) + nokogiri (1.16.2-arm64-darwin) + racc (~> 1.4) + racc (1.7.3) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) PLATFORMS arm64-darwin-21 + arm64-darwin-23 DEPENDENCIES aws-sdk-states! + nokogiri! + rspec! + rspec-mocks! BUNDLED WITH 2.3.7 diff --git a/app.rb b/app.rb index 0ce4a5a..1605a5e 100644 --- a/app.rb +++ b/app.rb @@ -1,6 +1,12 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path('.', 'lib') + require 'aws-sdk-states' require 'json' +require 'deployment' + STDOUT.sync = true execution_arn = ENV['EXECUTION_ARN'] @@ -12,60 +18,21 @@ aws_client = Aws::States::Client.new(region: 'eu-west-1') -def deployment_status(aws_client, execution_arn) - aws_client.describe_execution({ execution_arn: execution_arn }).status -end - -def failure_reason(aws_client, execution_arn) - resp = aws_client.get_execution_history({ - execution_arn: execution_arn, - max_results: 2, - reverse_order: true - }) - - event_type = resp.events[1].type - if event_type == "LambdaFunctionFailed" - error_message = JSON.parse(resp.events[1].lambda_function_failed_event_details.cause)['errorMessage'] - if error_message.include? "Pre flight checks failed" - preflight_checks_output = error_message.lines[1] - forward_deploy_check_result = preflight_checks_output.match(/ForwardDeployCheck=>"(.*?)"/i).captures[0] - if forward_deploy_check_result == "FAILED" - deploy_fail_reason = "Forward deploy check FAILED. No need to panic! "\ - "This likely means your commit has already been deployed as part of a previous deploy. "\ - "To confirm you can check whether your SHA is a parent commit to the currently deployed SHA. "\ - "You can figure out the currently deployed SHA by following this guide https://www.notion.so/freeagent/Deployment-Runbooks-29796221387e40b7abbb217d7d33c4ac?pvs=4#3bfa2ab5d3ab4c33b7a46522027f94bb" - return deploy_fail_reason - end - end - deploy_fail_reason = error_message - elsif event_type == "FailStateEntered" - error_message = JSON.parse(JSON.parse(resp.events[1].state_entered_event_details.input)['Error']['Cause'])['errorMessage'] - if error_message.include? "ECS" and error_message.include? "IN_PROGRESS" - deploy_fail_reason = "Sidekiq workers failed to start or failed to stabilise." - else - deploy_fail_reason = "Failure message: #{error_message}. Please investigate further here if required https://eu-west-1.console.aws.amazon.com/states/home?region=eu-west-1#/executions/details/#{execution_arn}" - end - else - deploy_fail_reason = "Uncaught failure. Please investigate here https://eu-west-1.console.aws.amazon.com/states/home?region=eu-west-1#/executions/details/#{execution_arn}" - end - - return deploy_fail_reason -end +deploy = Deployment::Deployment.new(aws_client, execution_arn) -# One of: "RUNNING", "SUCCEEDED", "FAILED", "TIMED_OUT", "ABORTED" -if deployment_status(aws_client, execution_arn) == 'RUNNING' +if deploy.running? puts 'Deployment in progress...🔄' puts "Monitor at https://eu-west-1.console.aws.amazon.com/states/home?region=eu-west-1#/executions/details/#{execution_arn}" - sleep 15 until deployment_status(aws_client, execution_arn) != 'RUNNING' end -deploy_status = deployment_status(aws_client, execution_arn) -if %w[FAILED TIMED_OUT ABORTED].include?(deploy_status) - puts "Deployment Failure Status: #{deploy_status} ❌" +sleep 10 while deploy.running? + +if deploy.succeeded? + puts 'Deployment Successful 🎉' +else + puts "Deployment Failure Status: #{deploy.status} ❌" File.open(ENV['GITHUB_OUTPUT'], 'a') do |f| - f.puts "deployment_failed=true" - f.puts "deployment_failure_reason=#{failure_reason(aws_client, execution_arn)}" + f.puts 'deployment_failed=true' + f.puts "deployment_failure_reason=#{deploy.failure_reason}" end -elsif deploy_status == 'SUCCEEDED' - puts 'Deployment Successful 🎉' end diff --git a/lib/deployment.rb b/lib/deployment.rb new file mode 100644 index 0000000..7df44f4 --- /dev/null +++ b/lib/deployment.rb @@ -0,0 +1,3 @@ +require 'deployment/deployment' +require 'deployment/events/fail_state_entered' +require 'deployment/events/lambda_function_failed' diff --git a/lib/deployment/deployment.rb b/lib/deployment/deployment.rb new file mode 100644 index 0000000..59abd6e --- /dev/null +++ b/lib/deployment/deployment.rb @@ -0,0 +1,61 @@ +module Deployment + class Deployment + def initialize(aws_client, execution_arn) + @aws_client = aws_client + @execution_arn = execution_arn + end + + def status + @aws_client.describe_execution({ execution_arn: @execution_arn }).status + end + + def running? + status == 'RUNNING' + end + + def succeeded? + status == 'SUCCEEDED' + end + + def aborted? + status == 'ABORTED' + end + + def timed_out? + status == 'TIMED_OUT' + end + + def failed? + status == 'FAILED' + end + + def failure_reason + raise 'Deploy still runnning' if running? + raise 'Deploy succeeded' if succeeded? + + event_type = fail_event.type + + case event_type + when 'LambdaFunctionFailed' + Events::LambdaFunctionFailed.new(fail_event).error + when 'FailStateEntered' + Events::FailStateEntered.new(fail_event, @execution_arn).error + else + "Uncaught failure. Please investigate here https://eu-west-1.console.aws.amazon.com/states/home?region=eu-west-1#/executions/details/#{@execution_arn}" + end + end + + private + + def fail_event + # The penultimate event holds the failure reason + @aws_client.get_execution_history( + { + execution_arn: @execution_arn, + max_results: 2, + reverse_order: true + } + ).events.last + end + end +end diff --git a/lib/deployment/events/fail_state_entered.rb b/lib/deployment/events/fail_state_entered.rb new file mode 100644 index 0000000..c14c5d8 --- /dev/null +++ b/lib/deployment/events/fail_state_entered.rb @@ -0,0 +1,36 @@ +module Deployment + module Events + class FailStateEntered + def initialize(event, execution_arn) + @event = event + @execution_arn = execution_arn + end + + def error + if sidekiq_error? + 'Sidekiq workers failed to start or failed to stabilise.' + else + "Failure message: #{error_message}. Please investigate further here if required https://eu-west-1.console.aws.amazon.com/states/home?region=eu-west-1#/executions/details/#{@execution_arn}" + end + end + + private + + def error_message + @error_message ||= JSON.parse(JSON.parse(@event.state_entered_event_details.input)['Error']['Cause'])['errorMessage'] + end + + def in_progress? + error_message.include? 'IN_PROGRESS' + end + + def ecs_error? + error_message.include? 'ECS' + end + + def sidekiq_error? + ecs_error? && in_progress? + end + end + end +end diff --git a/lib/deployment/events/lambda_function_failed.rb b/lib/deployment/events/lambda_function_failed.rb new file mode 100644 index 0000000..5d7618b --- /dev/null +++ b/lib/deployment/events/lambda_function_failed.rb @@ -0,0 +1,42 @@ +module Deployment + module Events + class LambdaFunctionFailed + def initialize(event) + @event = event + end + + def error + if preflight_checks_failed? && forward_deploy_check_failed? + 'Forward deploy check FAILED. No need to panic! '\ + 'This likely means your commit has already been deployed as part of a previous deploy. '\ + 'To confirm you can check whether your SHA is a parent commit to the currently deployed SHA. '\ + 'You can figure out the currently deployed SHA by following this guide https://www.notion.so/freeagent/Deployment-Runbooks-29796221387e40b7abbb217d7d33c4ac?pvs=4#3bfa2ab5d3ab4c33b7a46522027f94bb' + else + error_message + end + end + + private + + def error_message + @error_message ||= JSON.parse(@event.lambda_function_failed_event_details.cause)['errorMessage'] + end + + def preflight_checks_output + error_message.lines[1] + end + + def preflight_checks_failed? + error_message.include? 'Pre flight checks failed' + end + + def forward_deploy_check_result + preflight_checks_output.match(/ForwardDeployCheck=>"(.*?)"/i).captures[0] + end + + def forward_deploy_check_failed? + forward_deploy_check_result == 'FAILED' + end + end + end +end diff --git a/spec/app_spec.rb b/spec/app_spec.rb new file mode 100644 index 0000000..9eb67e8 --- /dev/null +++ b/spec/app_spec.rb @@ -0,0 +1,117 @@ +require 'rspec' +require 'stringio' + +require 'aws-sdk-states' + +RSpec.describe 'app' do + let(:mock_client) { double('Aws::States::Client') } + let(:execution_arn) { 'arn:aws:states:eu-west-1:123456789012:execution:my-execution-flow' } + let(:test_github_log) { Tempfile.new('github_output') } + + before(:each) do + ENV['EXECUTION_ARN'] = execution_arn + ENV['GITHUB_OUTPUT'] = test_github_log.path + allow(Aws::States::Client).to receive(:new).and_return(mock_client) + allow(mock_client).to receive(:describe_execution).with(any_args).and_return(desc_exec_resp) + allow(mock_client).to receive(:get_execution_history).with(any_args).and_return(get_exec_history) + end + + context 'when deployment was successful' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'SUCCEEDED') } + let(:get_exec_history) {} + + it 'outputs "Deployment Successful 🎉"' do + $stdout = StringIO.new + + # Execute the script + load "#{__dir__}/../app.rb" + + # Assert the output + expect($stdout.string).to eq("Deployment Successful 🎉\n") + end + end + + context 'when forward deploy check failed' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'FAILED') } + let(:get_exec_history) do + Aws::States::Types::GetExecutionHistoryOutput.new( + events: [ + '', + Aws::States::Types::HistoryEvent.new( + type: 'LambdaFunctionFailed', + lambda_function_failed_event_details: Aws::States::Types::LambdaFunctionFailedEventDetails.new( + cause: ' + { + "errorMessage": "Pre flight checks failed:\n{\"Checks\"=>{:RequiredParameters=>\"PASSED\", :CommitCheck=>\"PASSED\", :ScheduleCheck=>\"PASSED\", :ForwardDeployCheck=>\"FAILED\"}, \"Status\"=>\"FAILED\"}", + "errorType": "Function", + "stackTrace": [ + "/var/task/pre_flight_checks.rb:145:in `handler" + ] + }' + ) + ) + ] + ) + end + + it 'outputs that it failed' do + $stdout = StringIO.new + + # Execute the script + load "#{__dir__}/../app.rb" + + # Assert the output + expect($stdout.string).to eq("Deployment Failure Status: FAILED ❌\n") + expect(File.readlines(test_github_log.path)).to eq( + [ + "deployment_failed=true\n", + 'deployment_failure_reason=' \ + 'Forward deploy check FAILED. No need to panic! '\ + 'This likely means your commit has already been deployed as part of a previous deploy. '\ + 'To confirm you can check whether your SHA is a parent commit to the currently deployed SHA. '\ + "You can figure out the currently deployed SHA by following this guide https://www.notion.so/freeagent/Deployment-Runbooks-29796221387e40b7abbb217d7d33c4ac?pvs=4#3bfa2ab5d3ab4c33b7a46522027f94bb\n" + ] + ) + end + end + + context 'when sidekiq failed to start' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'FAILED') } + let(:get_exec_history) do + Aws::States::Types::GetExecutionHistoryOutput.new( + events: [ + '', + Aws::States::Types::HistoryEvent.new( + type: 'FailStateEntered', + state_entered_event_details: Aws::States::Types::StateEnteredEventDetails.new( + input: ' + { + "Error": { + "Cause":"{\"errorMessage\":\"ECS deployment status: IN_PROGRESS\",\"errorType\":\"Function\",\"stackTrace\":[\"/var/task/ecs_deployment_handler.rb:49:in `handler\"]}", + "error":"Function","resource":"invoke","resourceType":"lambda}" + } + } + ' + ) + ) + ] + ) + end + + it 'outputs that it failed' do + $stdout = StringIO.new + + # Execute the script + load "#{__dir__}/../app.rb" + + # Assert the output + expect($stdout.string).to eq("Deployment Failure Status: FAILED ❌\n") + expect(File.readlines(test_github_log.path)).to eq( + [ + "deployment_failed=true\n", + "deployment_failure_reason=Sidekiq workers failed to start or failed to stabilise.\n" + ] + ) + end + end +end diff --git a/spec/deployment_spec.rb b/spec/deployment_spec.rb new file mode 100644 index 0000000..ceff5af --- /dev/null +++ b/spec/deployment_spec.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Deployment::Deployment do + subject(:deployment) { Deployment::Deployment.new(mock_client, execution_arn) } + + let(:mock_client) { double('Aws::States::Client') } + let(:execution_arn) { 'arn:aws:states:eu-west-1:123456789012:execution:my-execution-flow' } + let(:get_exec_history) { [] } + + before(:each) do + allow(Aws::States::Client).to receive(:new).and_return(mock_client) + allow(mock_client).to receive(:describe_execution).with(any_args).and_return(desc_exec_resp) + allow(mock_client).to receive(:get_execution_history).with(any_args).and_return(get_exec_history) + end + + context 'when deploy is running' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'RUNNING') } + + describe 'status' do + it 'returns RUNNING' do + expect(deployment.status).to eq('RUNNING') + end + end + + describe 'running?' do + it 'returns true' do + expect(deployment.running?).to be true + end + end + + describe 'failure_reason' do + it 'should raise an exception' do + expect { deployment.failure_reason }.to raise_error(RuntimeError) + end + end + end + + context 'when deploy succeeded' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'SUCCEEDED') } + + describe 'status' do + it 'returns SUCCEEDED' do + expect(deployment.status).to eq('SUCCEEDED') + end + end + + describe 'succeeded?' do + it 'returns true' do + expect(deployment.succeeded?).to be true + end + end + + describe 'running?' do + it 'returns false' do + expect(deployment.running?).to be false + end + end + + describe 'failure_reason' do + it 'should raise an exception' do + expect { deployment.failure_reason }.to raise_error(RuntimeError) + end + end + end + + context 'when deploy failed' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'FAILED') } + + describe 'status' do + it 'returns FAILED' do + expect(deployment.status).to eq('FAILED') + end + end + + describe 'failed?' do + it 'returns true' do + expect(deployment.failed?).to be true + end + end + + describe 'running?' do + it 'returns false' do + expect(deployment.running?).to be false + end + end + end + + context 'when deploy has been aborted' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'ABORTED') } + + describe 'status' do + it 'returns ABORTED' do + expect(deployment.status).to eq('ABORTED') + end + end + + describe 'aborted?' do + it 'returns true' do + expect(deployment.aborted?).to be true + end + end + + describe 'running?' do + it 'returns false' do + expect(deployment.running?).to be false + end + end + end + + context 'when deploy has timed out' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'TIMED_OUT') } + + describe 'status' do + it 'returns TIMED_OUT' do + expect(deployment.status).to eq('TIMED_OUT') + end + end + + describe 'timed_out?' do + it 'returns true' do + expect(deployment.timed_out?).to be true + end + end + + describe 'running?' do + it 'returns false' do + expect(deployment.running?).to be false + end + end + end + + context 'when deploy has failed' do + let(:desc_exec_resp) { Aws::States::Types::DescribeExecutionOutput.new(status: 'FAILED') } + + describe 'status' do + it 'returns FAILED' do + expect(deployment.status).to eq('FAILED') + end + end + + describe 'failed?' do + it 'returns true' do + expect(deployment.failed?).to be true + end + end + + describe 'running?' do + it 'returns false' do + expect(deployment.running?).to be false + end + end + + context 'when due to LambdaFunctionFailed event' do + let(:get_exec_history) do + Aws::States::Types::GetExecutionHistoryOutput.new( + events: [ + '', + Aws::States::Types::HistoryEvent.new(type: 'LambdaFunctionFailed') + ] + ) + end + + describe 'failure_reason' do + it 'calls LambdaFunctionFailedError.new(fail_event).error' do + expect_any_instance_of(Deployment::Events::LambdaFunctionFailed).to receive(:error) + subject.failure_reason + end + end + end + + context 'when due to FailStateEntered event' do + let(:get_exec_history) do + Aws::States::Types::GetExecutionHistoryOutput.new( + events: [ + '', + Aws::States::Types::HistoryEvent.new(type: 'FailStateEntered') + ] + ) + end + + describe 'failure_reason' do + it 'calls FailStateEntered.new(fail_event).error' do + expect_any_instance_of(Deployment::Events::FailStateEntered).to receive(:error) + subject.failure_reason + end + end + end + + context 'when due to and unknown event' do + let(:get_exec_history) do + Aws::States::Types::GetExecutionHistoryOutput.new( + events: [ + '', + Aws::States::Types::HistoryEvent.new(type: 'UnknownError') + ] + ) + end + + describe 'failure_reason' do + it 'returns a generic error message' do + expect(subject.failure_reason).to start_with('Uncaught failure.') + end + end + end + end +end diff --git a/spec/fail_state_entered_spec.rb b/spec/fail_state_entered_spec.rb new file mode 100644 index 0000000..d9aa4f3 --- /dev/null +++ b/spec/fail_state_entered_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Deployment::Events::FailStateEntered do + subject(:deployment) { Deployment::Events::FailStateEntered.new(event, execution_arn) } + + let(:execution_arn) { 'arn:aws:states:eu-west-1:123456789012:execution:my-execution-flow' } + + context 'when Sidekiq has failed to start' do + let(:event) do + Aws::States::Types::HistoryEvent.new( + type: 'FailStateEntered', + state_entered_event_details: Aws::States::Types::StateEnteredEventDetails.new( + input: ' + { + "Error": { + "Cause":"{\"errorMessage\":\"ECS deployment status: IN_PROGRESS\",\"errorType\":\"Function\",\"stackTrace\":[\"/var/task/ecs_deployment_handler.rb:49:in `handler\"]}", + "error":"Function", + "resource":"invoke", + "resourceType":"lambda" + } + } + ' + ) + ) + end + + describe(:error) do + it 'returns the correct error message' do + expect(deployment.error).to eq('Sidekiq workers failed to start or failed to stabilise.') + end + end + end + + context 'when there is some other failure' do + let(:event) do + Aws::States::Types::HistoryEvent.new( + type: 'FailStateEntered', + state_entered_event_details: Aws::States::Types::StateEnteredEventDetails.new( + input: ' + { + "Error": { + "Cause":"{\"errorMessage\":\"Some other error\"}", + "error":"Function", + "resource":"invoke", + "resourceType":"lambda" + } + } + ' + ) + ) + end + + describe(:error) do + it 'returns the error message' do + expect(deployment.error).to start_with('Failure message: Some other error') + end + end + end +end diff --git a/spec/lambda_function_failed_spec.rb b/spec/lambda_function_failed_spec.rb new file mode 100644 index 0000000..d17fb08 --- /dev/null +++ b/spec/lambda_function_failed_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Deployment::Events::LambdaFunctionFailed do + subject(:deployment) { Deployment::Events::LambdaFunctionFailed.new(event) } + + context 'forward deploy pre-flight check failed' do + let(:event) do + Aws::States::Types::HistoryEvent.new( + type: 'LambdaFunctionFailed', + lambda_function_failed_event_details: Aws::States::Types::LambdaFunctionFailedEventDetails.new( + cause: ' + { + "errorMessage": "Pre flight checks failed:\n{\"Checks\"=>{:RequiredParameters=>\"PASSED\", :CommitCheck=>\"PASSED\", :ScheduleCheck=>\"PASSED\", :ForwardDeployCheck=>\"FAILED\"}, \"Status\"=>\"FAILED\"}", + "errorType": "Function", + "stackTrace": [ + "/var/task/pre_flight_checks.rb:145:in `handler" + ] + }' + ) + ) + end + + describe(:error) do + it 'returns the correct error message' do + expect(deployment.error).to start_with('Forward deploy check FAILED.') + end + end + end + + context 'some other pre-flight check failed' do + let(:event) do + Aws::States::Types::HistoryEvent.new( + type: 'LambdaFunctionFailed', + lambda_function_failed_event_details: Aws::States::Types::LambdaFunctionFailedEventDetails.new( + cause: ' + { + "errorMessage": "Pre flight checks failed:\n{\"Checks\"=>{:RequiredParameters=>\"FAILED\", :CommitCheck=>\"PASSED\", :ScheduleCheck=>\"PASSED\", :ForwardDeployCheck=>\"PASSED\"}, \"Status\"=>\"FAILED\"}", + "errorType": "Function", + "stackTrace": [ + "/var/task/pre_flight_checks.rb:145:in `handler" + ] + }' + ) + ) + end + + describe(:error) do + it 'returns a generic Pre flight checks failed error message' do + expect(deployment.error).to start_with('Pre flight checks failed:') + end + end + end + + context 'some other function failed' do + let(:event) do + Aws::States::Types::HistoryEvent.new( + type: 'LambdaFunctionFailed', + lambda_function_failed_event_details: Aws::States::Types::LambdaFunctionFailedEventDetails.new( + cause: ' + { + "errorMessage": "Some other error", + "errorType": "Function", + "stackTrace": [ + "/var/task/another_function.rb:145:in `handler" + ] + }' + ) + ) + end + + describe(:error) do + it 'returns the error message' do + expect(deployment.error).to start_with('Some other error') + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..b4ce74d --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,3 @@ +$LOAD_PATH.unshift File.expand_path('../', 'lib') +require 'deployment' +require 'aws-sdk-states' From 7bce9cd8ae567c598e1acc1ac650a37f6c28be0a Mon Sep 17 00:00:00 2001 From: Nathan Meehan-Howard Date: Fri, 1 Mar 2024 14:17:03 +0000 Subject: [PATCH 2/2] Fix Dockerfile so we can run via bundle exec I had originally planned to set WORKDIR in the Dockerfile to change to the correct directory. However it turns out that Github actions has other ideas and it is ignored. Instead lets wrap everything in a shell script to change to the right directory before running. --- Dockerfile | 2 +- entrypoint.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 1f4b90b..ba95a06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,4 @@ COPY . /usr/src/app RUN cd /usr/src/app && bundle install -ENTRYPOINT ["ruby", "/usr/src/app/app.rb"] \ No newline at end of file +ENTRYPOINT ["/usr/src/app/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..7b62309 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cd /usr/src/app && bundle exec ruby app.rb