diff --git a/bin/aws-ha-release.rb b/bin/aws-ha-release.rb index c31fee5..7870f7e 100755 --- a/bin/aws-ha-release.rb +++ b/bin/aws-ha-release.rb @@ -1,4 +1,4 @@ #!/usr/bin/env ruby require 'aws-missing-tools' -AwsHaRelease.new(ARGV.dup).execute! +AwsMissingTools::AwsHaRelease.new(ARGV.dup).execute! diff --git a/lib/aws-missing-tools.rb b/lib/aws-missing-tools.rb index 2aea5ea..9b82205 100644 --- a/lib/aws-missing-tools.rb +++ b/lib/aws-missing-tools.rb @@ -2,6 +2,5 @@ require 'aws-missing-tools/version' module AwsMissingTools require 'aws-sdk' - require 'aws-missing-tools/aws-ha-release/aws-ha-release' end diff --git a/lib/aws-missing-tools/aws-ha-release/aws-ha-release.rb b/lib/aws-missing-tools/aws-ha-release/aws-ha-release.rb index 306d8b4..618dfe6 100755 --- a/lib/aws-missing-tools/aws-ha-release/aws-ha-release.rb +++ b/lib/aws-missing-tools/aws-ha-release/aws-ha-release.rb @@ -1,165 +1,167 @@ require 'timeout' require 'optparse' -class AwsHaRelease - attr_reader :group +module AwsMissingTools + class AwsHaRelease + attr_reader :group - def initialize(argv) - @opts = AwsHaRelease.parse_options(argv) + def initialize(argv) + @opts = AwsHaRelease.parse_options(argv) - AWS.config(access_key_id: @opts[:aws_access_key], secret_access_key: @opts[:aws_secret_key], region: @opts[:region]) + AWS.config(access_key_id: @opts[:aws_access_key], secret_access_key: @opts[:aws_secret_key], region: @opts[:region]) - @as = AWS::AutoScaling.new - @group = @as.groups[@opts[:as_group_name]] + @as = AWS::AutoScaling.new + @group = @as.groups[@opts[:as_group_name]] - if @group.nil? - raise ArgumentError, "The Auto Scaling Group named #{@opts[:as_group_name]} does not exist in #{@opts[:region]}." + if @group.nil? + raise ArgumentError, "The Auto Scaling Group named #{@opts[:as_group_name]} does not exist in #{@opts[:region]}." + end + + @max_size_change = 0 + @inservice_polling_time = 10 + @processes_to_suspend = %w(ReplaceUnhealthy AlarmNotification ScheduledActions AZRebalance) end - @max_size_change = 0 - @inservice_polling_time = 10 - @processes_to_suspend = %w(ReplaceUnhealthy AlarmNotification ScheduledActions AZRebalance) - end + def self.parse_options(arguments) + options = { + region: 'us-east-1', + elb_timeout: 60, + inservice_time_allowed: 300 + } - def self.parse_options(arguments) - options = { - region: 'us-east-1', - elb_timeout: 60, - inservice_time_allowed: 300 - } + OptionParser.new do |opts| + opts.banner = 'Usage: aws-ha-release.rb -a [options]' - OptionParser.new do |opts| - opts.banner = 'Usage: aws-ha-release.rb -a [options]' + opts.on('-a', '--as-group-name GROUP_NAME', 'AutoScaling Group Name') do |v| + options[:as_group_name] = v + end - opts.on('-a', '--as-group-name GROUP_NAME', 'AutoScaling Group Name') do |v| - options[:as_group_name] = v + opts.on('-r', '--region REGION', 'Region') do |v| + options[:region] = v + end + + opts.on('-t', '--elb-timeout TIME', 'ELB Timeout (seconds)') do |v| + options[:elb_timeout] = v.to_i + end + + opts.on('-i', '--inservice-time-allowed TIME', 'Time allowed for instance to come in service (seconds)') do |v| + options[:inservice_time_allowed] = v.to_i + end + + opts.on('-o', '--aws_access_key AWS_ACCESS_KEY', 'AWS Access Key') do |v| + options[:aws_access_key] = v + end + + opts.on('-s', '--aws_secret_key AWS_SECRET_KEY', 'AWS Secret Key') do |v| + options[:aws_secret_key] = v + end + end.parse!(arguments) + + raise OptionParser::MissingArgument, 'You must specify the AutoScaling Group Name: aws-ha-release.rb -a ' if options[:as_group_name].nil? + + if options[:aws_secret_key] && options[:aws_access_key].nil? || options[:aws_access_key] && options[:aws_secret_key].nil? + raise OptionParser::MissingArgument, 'If specifying either the AWS Access or Secret Key, then the other must also be specified. aws-ha-release.rb -a -o access_key -s secret_key' + elsif options[:aws_secret_key].nil? && options[:aws_access_key].nil? + options[:aws_access_key] = ENV['AWS_ACCESS_KEY'] + options[:aws_secret_key] = ENV['AWS_SECRET_KEY'] end - opts.on('-r', '--region REGION', 'Region') do |v| - options[:region] = v - end - - opts.on('-t', '--elb-timeout TIME', 'ELB Timeout (seconds)') do |v| - options[:elb_timeout] = v.to_i - end - - opts.on('-i', '--inservice-time-allowed TIME', 'Time allowed for instance to come in service (seconds)') do |v| - options[:inservice_time_allowed] = v.to_i - end - - opts.on('-o', '--aws_access_key AWS_ACCESS_KEY', 'AWS Access Key') do |v| - options[:aws_access_key] = v - end - - opts.on('-s', '--aws_secret_key AWS_SECRET_KEY', 'AWS Secret Key') do |v| - options[:aws_secret_key] = v - end - end.parse!(arguments) - - raise OptionParser::MissingArgument, 'You must specify the AutoScaling Group Name: aws-ha-release.rb -a ' if options[:as_group_name].nil? - - if options[:aws_secret_key] && options[:aws_access_key].nil? || options[:aws_access_key] && options[:aws_secret_key].nil? - raise OptionParser::MissingArgument, 'If specifying either the AWS Access or Secret Key, then the other must also be specified. aws-ha-release.rb -a -o access_key -s secret_key' - elsif options[:aws_secret_key].nil? && options[:aws_access_key].nil? - options[:aws_access_key] = ENV['AWS_ACCESS_KEY'] - options[:aws_secret_key] = ENV['AWS_SECRET_KEY'] + options end - options - end - - def execute! - %w(RemoveFromLoadBalancerLowPriority Terminate Launch HealthCheck AddToLoadBalancer).each do |process| - if @group.suspended_processes.keys.include? process - raise "AutoScaling process #{process} is currently suspended on #{@group.name} but is necessary for this script." + def execute! + %w(RemoveFromLoadBalancerLowPriority Terminate Launch HealthCheck AddToLoadBalancer).each do |process| + if @group.suspended_processes.keys.include? process + raise "AutoScaling process #{process} is currently suspended on #{@group.name} but is necessary for this script." + end end - end - @group.suspend_processes @processes_to_suspend + @group.suspend_processes @processes_to_suspend - if @group.max_size == @group.desired_capacity - puts "#{@group.name} has a max-size of #{@group.max_size}. In order to recycle instances max-size will be temporarily increased by 1." - @group.update(max_size: @group.max_size + 1) - @max_size_change = 1 - end + if @group.max_size == @group.desired_capacity + puts "#{@group.name} has a max-size of #{@group.max_size}. In order to recycle instances max-size will be temporarily increased by 1." + @group.update(max_size: @group.max_size + 1) + @max_size_change = 1 + end - @group.update(desired_capacity: @group.desired_capacity + 1) + @group.update(desired_capacity: @group.desired_capacity + 1) - puts "The list of Instances in Auto Scaling Group #{@group.name} that will be terminated is:\n#{@group.ec2_instances.map(&:id)}" - @group.ec2_instances.each do |instance| - time_taken = 0 + puts "The list of Instances in Auto Scaling Group #{@group.name} that will be terminated is:\n#{@group.ec2_instances.map(&:id)}" + @group.ec2_instances.each do |instance| + time_taken = 0 - begin - Timeout::timeout(@opts[:inservice_time_allowed]) do + begin + Timeout::timeout(@opts[:inservice_time_allowed]) do - until all_instances_inservice?(@group.load_balancers) - puts "#{time_taken} seconds have elapsed while waiting for an Instance to reach InService status." + until all_instances_inservice?(@group.load_balancers) + puts "#{time_taken} seconds have elapsed while waiting for an Instance to reach InService status." - time_taken += @inservice_polling_time - sleep @inservice_polling_time + time_taken += @inservice_polling_time + sleep @inservice_polling_time + end + + deregister_instance instance, @group.load_balancers + sleep @opts[:elb_timeout] + + puts "Instance #{instance.id} will now be terminated. By terminating this instance, the actual capacity will be decreased to 1 under desired-capacity." + instance.terminate false + end + rescue Timeout::Error => e + puts "\nDuring the last #{time_taken} seconds, a new AutoScaling instance failed to become healthy." + puts "The following settings were changed and will not be changed back by this script:\n" + + puts "AutoScaling processes #{@processes_to_suspend} were suspended." + puts "The desired capacity was changed from #{@group.desired_capacity - 1} to #{@group.desired_capacity}." + + if @max_size_change > 0 + puts "The maximum size was changed from #{@group.max_size - @max_size_change} to #{@group.max_size}" end - deregister_instance instance, @group.load_balancers - sleep @opts[:elb_timeout] - - puts "Instance #{instance.id} will now be terminated. By terminating this instance, the actual capacity will be decreased to 1 under desired-capacity." - instance.terminate false + raise end - rescue Timeout::Error => e - puts "\nDuring the last #{time_taken} seconds, a new AutoScaling instance failed to become healthy." - puts "The following settings were changed and will not be changed back by this script:\n" + end - puts "AutoScaling processes #{@processes_to_suspend} were suspended." - puts "The desired capacity was changed from #{@group.desired_capacity - 1} to #{@group.desired_capacity}." + puts "#{@group.name} had its desired-capacity increased temporarily by 1 to a desired-capacity of #{@group.desired_capacity}." + puts "$app_name will now return the desired-capacity of #{@group.name} to its original desired-capacity of #{@group.desired_capacity - 1}." + @group.update(desired_capacity: @group.desired_capacity - 1) - if @max_size_change > 0 - puts "The maximum size was changed from #{@group.max_size - @max_size_change} to #{@group.max_size}" - end + if @max_size_change > 0 + puts "\n#{@group.name} had its max_size increased temporarily by #{@max_size_change} to a max_size of #{@group.max_size}." + puts "The max_size of #{@group.name} will now be returned to its original max_size of #{@group.max_size - @max_size_change}." - raise + @group.update(max_size: @group.max_size - @max_size_change) + @max_size_change = 0 + end + + @group.resume_all_processes + end + + def deregister_instance(instance, load_balancers) + load_balancers.each do |load_balancer| + load_balancer.instances.deregister instance end end - puts "#{@group.name} had its desired-capacity increased temporarily by 1 to a desired-capacity of #{@group.desired_capacity}." - puts "$app_name will now return the desired-capacity of #{@group.name} to its original desired-capacity of #{@group.desired_capacity - 1}." - @group.update(desired_capacity: @group.desired_capacity - 1) + def instances_inservice?(load_balancer) + return false if load_balancer.instances.count != @group.desired_capacity - if @max_size_change > 0 - puts "\n#{@group.name} had its max_size increased temporarily by #{@max_size_change} to a max_size of #{@group.max_size}." - puts "The max_size of #{@group.name} will now be returned to its original max_size of #{@group.max_size - @max_size_change}." + load_balancer.instances.health.each do |instance_health| + unless instance_health[:state] == 'InService' + puts "Instance #{instance_health[:instance].id} is currently #{instance_health[:state]} on load balancer #{load_balancer.name}." - @group.update(max_size: @group.max_size - @max_size_change) - @max_size_change = 0 - end - - @group.resume_all_processes - end - - def deregister_instance(instance, load_balancers) - load_balancers.each do |load_balancer| - load_balancer.instances.deregister instance - end - end - - def instances_inservice?(load_balancer) - return false if load_balancer.instances.count != @group.desired_capacity - - load_balancer.instances.health.each do |instance_health| - unless instance_health[:state] == 'InService' - puts "Instance #{instance_health[:instance].id} is currently #{instance_health[:state]} on load balancer #{load_balancer.name}." - - return false + return false + end end + + true end - true - end + def all_instances_inservice?(load_balancers) + load_balancers.each do |load_balancer| + return false unless instances_inservice?(load_balancer) + end - def all_instances_inservice?(load_balancers) - load_balancers.each do |load_balancer| - return false unless instances_inservice?(load_balancer) + true end - - true end end diff --git a/spec/aws-missing-tools/aws-ha-release/aws-ha-release_spec.rb b/spec/aws-missing-tools/aws-ha-release/aws-ha-release_spec.rb index 464092d..924081f 100644 --- a/spec/aws-missing-tools/aws-ha-release/aws-ha-release_spec.rb +++ b/spec/aws-missing-tools/aws-ha-release/aws-ha-release_spec.rb @@ -19,26 +19,26 @@ describe 'aws-ha-release' do as.groups.create opts[1] AWS.should_receive(:config).with(access_key_id: 'testaccesskey', secret_access_key: 'testsecretkey', region: 'test_region') - AwsHaRelease.new(opts) + AwsMissingTools::AwsHaRelease.new(opts) end it 'ensures the as group exists' do lambda { opts[1] = 'fake_group' - AwsHaRelease.new(opts) + AwsMissingTools::AwsHaRelease.new(opts) }.should raise_error end end describe '#parse_options' do it 'requires the autoscaling group name to be passed in' do - expect{ AwsHaRelease.parse_options([]) }.to raise_error OptionParser::MissingArgument - expect(AwsHaRelease.parse_options(%w(-a test_group))[:as_group_name]).to eq 'test_group' - expect(AwsHaRelease.parse_options(%w(--as-group-name test_group))[:as_group_name]).to eq 'test_group' + expect{ AwsMissingTools::AwsHaRelease.parse_options([]) }.to raise_error OptionParser::MissingArgument + expect(AwsMissingTools::AwsHaRelease.parse_options(%w(-a test_group))[:as_group_name]).to eq 'test_group' + expect(AwsMissingTools::AwsHaRelease.parse_options(%w(--as-group-name test_group))[:as_group_name]).to eq 'test_group' end it 'sets default options' do - options = AwsHaRelease.parse_options(%w(-a test_group)) + options = AwsMissingTools::AwsHaRelease.parse_options(%w(-a test_group)) expect(options[:elb_timeout]).not_to be_nil expect(options[:region]).not_to be_nil expect(options[:inservice_time_allowed]).not_to be_nil @@ -49,27 +49,27 @@ describe 'aws-ha-release' do context 'optional params' do it 'ELB timeout' do [%w(-a test_group -t 10), %w(-a test_group --elb-timeout 10)].each do |options| - expect(AwsHaRelease.parse_options(options)[:elb_timeout]).to eq 10 + expect(AwsMissingTools::AwsHaRelease.parse_options(options)[:elb_timeout]).to eq 10 end end it 'region' do [%w(-a test_group -r test_region), %w(-a test_group --region test_region)].each do |options| - expect(AwsHaRelease.parse_options(options)[:region]).to eq 'test_region' + expect(AwsMissingTools::AwsHaRelease.parse_options(options)[:region]).to eq 'test_region' end end it 'inservice time allowed' do [%w(-a test_group -i 300), %w(-a test_group --inservice-time-allowed 300)].each do |options| - expect(AwsHaRelease.parse_options(options)[:inservice_time_allowed]).to eq 300 + expect(AwsMissingTools::AwsHaRelease.parse_options(options)[:inservice_time_allowed]).to eq 300 end end it 'aws_access_key and aws_secret_key' do - expect{ AwsHaRelease.parse_options(%w(-a test_group -o testkey)) }.to raise_error OptionParser::MissingArgument - expect{ AwsHaRelease.parse_options(%w(-a test_group -s testsecretkey)) }.to raise_error OptionParser::MissingArgument + expect{ AwsMissingTools::AwsHaRelease.parse_options(%w(-a test_group -o testkey)) }.to raise_error OptionParser::MissingArgument + expect{ AwsMissingTools::AwsHaRelease.parse_options(%w(-a test_group -s testsecretkey)) }.to raise_error OptionParser::MissingArgument - options = AwsHaRelease.parse_options(%w(-a test_group -o testkey -s testsecretkey)) + options = AwsMissingTools::AwsHaRelease.parse_options(%w(-a test_group -o testkey -s testsecretkey)) expect(options[:aws_access_key]).to eq 'testkey' expect(options[:aws_secret_key]).to eq 'testsecretkey' end @@ -79,7 +79,7 @@ describe 'aws-ha-release' do describe '#execute!' do before do @group = as.groups.create opts[1] - @aws_ha_release = AwsHaRelease.new(opts) + @aws_ha_release = AwsMissingTools::AwsHaRelease.new(opts) end it 'suspends certain autoscaling processes' do @@ -114,7 +114,7 @@ describe 'aws-ha-release' do before do @group = as.groups.create opts[1] @group.update(desired_capacity: 2) - @aws_ha_release = AwsHaRelease.new(opts) + @aws_ha_release = AwsMissingTools::AwsHaRelease.new(opts) end it 'checks all instances across a given load balancer' do @@ -194,7 +194,7 @@ describe 'aws-ha-release' do describe '#deregister_instance' do before do @group = as.groups.create opts[1] - @aws_ha_release = AwsHaRelease.new(opts) + @aws_ha_release = AwsMissingTools::AwsHaRelease.new(opts) end it 'deregisters an instance across all load balancers' do