Skip to content

Instantly share code, notes, and snippets.

@woodhull
Created July 31, 2015 02:01
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save woodhull/c56cbd0a68cb9b3fd1f4 to your computer and use it in GitHub Desktop.
Save woodhull/c56cbd0a68cb9b3fd1f4 to your computer and use it in GitHub Desktop.
random bits of ruby for doing green/blue deploys
require "timeout"
class AutoScalingGroup
attr_accessor :name
def initialize(attrs = {})
attrs.each do |key,value|
if self.respond_to?("#{key}=")
self.send("#{key}=", value)
end
end
end
def wait_until_instances_ready
groups.each do |group|
wait_until do
group = autoscaling.describe_auto_scaling_groups(auto_scaling_group_names: [group.auto_scaling_group_name]).auto_scaling_groups.first
group.instances.any?
end
group = autoscaling.describe_auto_scaling_groups(auto_scaling_group_names: [group.auto_scaling_group_name]).auto_scaling_groups.first
instance_ids = group.instances.collect{|i| i.instance_id}
puts "waiting for #{group.auto_scaling_group_name} servers to come online"
ec2.wait_until(:instance_running, instance_ids: instance_ids) if instance_ids.any?
wait_for_activities_to_complete(group)
end
end
def delete!
groups.each do |group|
wait_for_instances_to_delete(group)
autoscaling.delete_auto_scaling_group(auto_scaling_group_name: group.auto_scaling_group_name)
puts "waiting for #{group.auto_scaling_group_name} to delete"
wait_until do
autoscaling.describe_auto_scaling_groups(auto_scaling_group_names: [group.auto_scaling_group_name]).auto_scaling_groups.empty?
end
autoscaling.delete_launch_configuration(launch_configuration_name: group.launch_configuration_name)
end
end
private
def wait_for_activities_to_complete(group)
autoscaling.describe_scaling_activities(auto_scaling_group_name: group.auto_scaling_group_name).activities.each do |activity|
if activity.status_code != 'Successful'
wait_until do
activity = autoscaling.describe_scaling_activities(auto_scaling_group_name: group.auto_scaling_group_name, activity_ids: [activity.activity_id]).activities.first
activity.status_code == 'Successful' || activity.status_code == 'Failed'
end
end
end
end
def autoscaling
@autoscaling ||= Aws::AutoScaling::Client.new(region: 'us-east-1')
end
def ec2
@ec2 ||= Aws::EC2::Client.new(region: 'us-east-1')
end
def groups
autoscaling.describe_auto_scaling_groups(auto_scaling_group_names: [name]).auto_scaling_groups
end
def wait_for_instances_to_delete(group)
autoscaling.update_auto_scaling_group(auto_scaling_group_name: group.auto_scaling_group_name, min_size: 0, max_size: 0, desired_capacity: 0)
instance_ids = group.instances.collect{|i| i.instance_id}
puts "waiting for #{name} to empty"
ec2.wait_until(:instance_terminated, instance_ids: instance_ids) if instance_ids.any?
wait_until do
autoscaling.describe_auto_scaling_groups(auto_scaling_group_names: [group.auto_scaling_group_name]).auto_scaling_groups.first.instances.empty?
end
wait_for_activities_to_complete(group)
end
def wait_until
Timeout.timeout(120) do
sleep(1) until value = yield
value
end
end
end
currently_blue = current_tfstate['modules'].first['resources']['aws_autoscaling_group.haproxy_blue'].present?
currently_green = current_tfstate['modules'].first['resources']['aws_autoscaling_group.haproxy_green'].present?
if currently_blue || currently_green
if currently_blue && currently_green
puts "app is in inconsistent state, removing all ASGs"
['blue', 'green'].each do |color|
delete_autoscaling_group(color)
end
refresh
return
end
current_tfstate_ami = if currently_blue
current_tfstate['modules'].first['outputs']['current_haproxy_blue_ami']
else
current_tfstate['modules'].first['outputs']['current_haproxy_green_ami']
end
new_ami = current_ami('agra-haproxy-server').image_id
if current_tfstate_ami != new_ami
puts "performing multi-stage blue/green apply"
if currently_blue
_apply(['blue', 'green'], {ha_proxy_server_blue_ami: current_tfstate_ami, ha_proxy_server_green_ami: new_ami})
else
_apply(['blue', 'green'], {ha_proxy_server_blue_ami: new_ami, ha_proxy_server_green_ami: current_tfstate_ami})
end
['blue', 'green'].each do |color|
AutoScalingGroup.new(name: "haproxy_asg_#{color}").wait_until_instances_ready
end
# TODO some sort of health check
puts "destroying old asg and launch configuration"
if currently_blue
# if currently blue, then transition to new green ASG
delete_autoscaling_group('blue')
refresh
generate_blue_green_haproxy_tf(['green'])
else
# if currently green, then transition to new blue ASG
delete_autoscaling_group('green')
refresh
generate_blue_green_haproxy_tf(['blue'])
end
else
puts "latest ami is already live, running normal apply"
if currently_blue
_apply(['blue'], {ha_proxy_server_blue_ami: new_ami})
else
_apply(['green'], {ha_proxy_server_green_ami: new_ami})
end
end
else
puts "asg blue/green resources not present, performing a standard apply to default app to blue"
_apply(['blue'])
AutoScalingGroup.new(name: "haproxy_asg_blue").wait_until_instances_ready
end
@woodhull
Copy link
Author

We generate the green/blue terraform scripts with erb:

def generate_elb_terraform_file
template = ERB.new File.new("config/templates/terraform/elb.tf.erb").read, nil, "%"
result = template.result(OpenStruct.new(elb_mapping: elb_mapping).instance_eval { binding })
File.write("#{config_directory}/elb.tf", result)
end

def generate_blue_green_haproxy_tf(colors)
template = ERB.new File.new("config/templates/terraform/haproxy.tf.erb").read, nil, "%"
result = template.result(OpenStruct.new(colors: colors, elb_mapping: elb_mapping).instance_eval { binding })
File.write("#{config_directory}/haproxy.tf", result)
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment