cf_deployer 1.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +29 -0
- data/ChangeLog.md +16 -0
- data/DETAILS.md +268 -0
- data/FAQ.md +61 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +51 -0
- data/LICENSE +22 -0
- data/QUICKSTART.md +96 -0
- data/README.md +36 -0
- data/Rakefile +32 -0
- data/bin/cf_deploy +10 -0
- data/cf_deployer.gemspec +23 -0
- data/lib/cf_deployer/application.rb +74 -0
- data/lib/cf_deployer/application_error.rb +4 -0
- data/lib/cf_deployer/aws_constants.rb +3 -0
- data/lib/cf_deployer/cli.rb +111 -0
- data/lib/cf_deployer/component.rb +103 -0
- data/lib/cf_deployer/config_loader.rb +189 -0
- data/lib/cf_deployer/config_validation.rb +138 -0
- data/lib/cf_deployer/defaults.rb +10 -0
- data/lib/cf_deployer/deployment_strategy/auto_scaling_group_swap.rb +102 -0
- data/lib/cf_deployer/deployment_strategy/base.rb +88 -0
- data/lib/cf_deployer/deployment_strategy/blue_green.rb +70 -0
- data/lib/cf_deployer/deployment_strategy/cname_swap.rb +108 -0
- data/lib/cf_deployer/deployment_strategy/create_or_update.rb +57 -0
- data/lib/cf_deployer/driver/auto_scaling_group.rb +86 -0
- data/lib/cf_deployer/driver/cloud_formation_driver.rb +85 -0
- data/lib/cf_deployer/driver/dry_run.rb +27 -0
- data/lib/cf_deployer/driver/elb_driver.rb +17 -0
- data/lib/cf_deployer/driver/instance.rb +29 -0
- data/lib/cf_deployer/driver/route53_driver.rb +79 -0
- data/lib/cf_deployer/driver/verisign_driver.rb +21 -0
- data/lib/cf_deployer/hook.rb +32 -0
- data/lib/cf_deployer/logger.rb +34 -0
- data/lib/cf_deployer/stack.rb +154 -0
- data/lib/cf_deployer/status_presenter.rb +195 -0
- data/lib/cf_deployer/version.rb +3 -0
- data/lib/cf_deployer.rb +97 -0
- data/spec/fakes/instance.rb +32 -0
- data/spec/fakes/route53_client.rb +23 -0
- data/spec/fakes/stack.rb +65 -0
- data/spec/functional/deploy_spec.rb +73 -0
- data/spec/functional/kill_inactive_spec.rb +57 -0
- data/spec/functional_spec_helper.rb +3 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/unit/application_spec.rb +191 -0
- data/spec/unit/component_spec.rb +142 -0
- data/spec/unit/config_loader_spec.rb +356 -0
- data/spec/unit/config_validation_spec.rb +480 -0
- data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +435 -0
- data/spec/unit/deployment_strategy/base_spec.rb +44 -0
- data/spec/unit/deployment_strategy/cname_swap_spec.rb +294 -0
- data/spec/unit/deployment_strategy/create_or_update_spec.rb +113 -0
- data/spec/unit/deployment_strategy/deployment_strategy_spec.rb +29 -0
- data/spec/unit/driver/auto_scaling_group_spec.rb +127 -0
- data/spec/unit/driver/cloud_formation_spec.rb +32 -0
- data/spec/unit/driver/elb_spec.rb +11 -0
- data/spec/unit/driver/instance_spec.rb +30 -0
- data/spec/unit/driver/route53_spec.rb +85 -0
- data/spec/unit/driver/verisign_spec.rb +18 -0
- data/spec/unit/hook_spec.rb +64 -0
- data/spec/unit/stack_spec.rb +150 -0
- data/spec/unit/status_presenter_spec.rb +108 -0
- metadata +197 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module DeploymentStrategy
|
3
|
+
class AutoScalingGroupSwap < BlueGreen
|
4
|
+
|
5
|
+
|
6
|
+
def deploy
|
7
|
+
check_blue_green_not_both_active 'Deployment'
|
8
|
+
Log.info "Found active stack #{active_stack.name}" if active_stack
|
9
|
+
delete_stack inactive_stack
|
10
|
+
create_inactive_stack
|
11
|
+
swap_group
|
12
|
+
run_hook(:'after-swap')
|
13
|
+
Log.info "Active stack has been set to #{inactive_stack.name}"
|
14
|
+
delete_stack(active_stack) if active_stack && !keep_previous_stack
|
15
|
+
Log.info "#{component_name} deployed successfully"
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def kill_inactive
|
20
|
+
check_blue_green_not_both_active 'Kill Inactive'
|
21
|
+
raise ApplicationError.new('Only one color stack exists, cannot kill a non-existant version!') unless both_stacks_exist?
|
22
|
+
delete_stack inactive_stack
|
23
|
+
end
|
24
|
+
|
25
|
+
def switch
|
26
|
+
check_blue_green_not_both_active 'Switch'
|
27
|
+
raise ApplicationError.new('Only one color stack exists, cannot switch to a non-existent version!') unless both_stacks_exist?
|
28
|
+
warm_up_cooled = true
|
29
|
+
swap_group warm_up_cooled
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
|
35
|
+
def check_blue_green_not_both_active action
|
36
|
+
active_stacks = get_active_asg(active_stack) + get_active_asg(inactive_stack)
|
37
|
+
raise BothStacksActiveError.new("Found both auto-scaling-groups, #{active_stacks}, in green and blue stacks are active. #{action} aborted!") if both_stacks_active?
|
38
|
+
end
|
39
|
+
|
40
|
+
def swap_group is_switching_to_cooled = false
|
41
|
+
is_switching_to_cooled ? warm_up_cooled_stack : warm_up_inactive_stack
|
42
|
+
cool_down_active_stack if active_stack && (is_switching_to_cooled || keep_previous_stack)
|
43
|
+
end
|
44
|
+
|
45
|
+
def keep_previous_stack
|
46
|
+
context[:settings][:'keep-previous-stack']
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_inactive_stack
|
50
|
+
inactive_stack.deploy
|
51
|
+
get_parameters_outputs(inactive_stack)
|
52
|
+
run_hook(:'after-create')
|
53
|
+
end
|
54
|
+
|
55
|
+
def both_stacks_active?
|
56
|
+
active_stack && stack_active?(inactive_stack)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def warm_up_cooled_stack
|
61
|
+
group_ids(active_stack).each_with_index do |id, index|
|
62
|
+
min_max_desired = asg_driver(id).describe
|
63
|
+
asg_driver(group_ids(inactive_stack)[index]).warm_up_cooled_group min_max_desired
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def cool_down_active_stack
|
68
|
+
group_ids(active_stack).each do |id|
|
69
|
+
asg_driver(id).cool_down
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def stack_active?(stack)
|
76
|
+
return false unless stack.exists?
|
77
|
+
get_active_asg(stack).any?
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
def get_active_asg stack
|
83
|
+
return [] unless stack && stack.exists?
|
84
|
+
group_ids(stack).select do |id|
|
85
|
+
result = asg_driver(id).describe
|
86
|
+
result[:min] > 0 && result[:max] > 0 && result[:desired] > 0
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def asg_driver name
|
91
|
+
@auto_scaling_group_drivers[name] ||= CfDeployer::Driver::AutoScalingGroup.new name
|
92
|
+
end
|
93
|
+
|
94
|
+
def asg_id_outputs
|
95
|
+
@context[:settings][:'auto-scaling-group-name-output']
|
96
|
+
end
|
97
|
+
|
98
|
+
class BothStacksActiveError < ApplicationError
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
|
2
|
+
module CfDeployer
|
3
|
+
module DeploymentStrategy
|
4
|
+
|
5
|
+
def self.create application_name, environment_name, component_name, context
|
6
|
+
context[:'deployment-strategy'] ||= 'create-or-update'
|
7
|
+
strategy_class_name = 'CfDeployer::DeploymentStrategy::' + context[:'deployment-strategy'].split('-').map(&:capitalize).join
|
8
|
+
begin
|
9
|
+
eval(strategy_class_name).new application_name, component_name, environment_name, context
|
10
|
+
rescue
|
11
|
+
raise ApplicationError.new 'strategy_name: ' + strategy_class_name + ' not supported'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Base
|
16
|
+
BLUE_GREEN_STRATEGY = true
|
17
|
+
|
18
|
+
attr_reader :context, :component_name, :application_name, :environment_name
|
19
|
+
def initialize(application_name, component_name, environment_name, context)
|
20
|
+
@application_name = application_name
|
21
|
+
@component_name = component_name
|
22
|
+
@environment_name = environment_name
|
23
|
+
@context = context
|
24
|
+
@auto_scaling_group_drivers = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def blue_green_strategy?
|
28
|
+
BLUE_GREEN_STRATEGY
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def stack_prefix
|
34
|
+
"#{@application_name}-#{@environment_name}-#{@component_name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_stack(stack)
|
38
|
+
# Should this be stack.ready? Outputs won't exist if the stack is still starting.
|
39
|
+
unless stack.exists?
|
40
|
+
CfDeployer::Log.info "Skipping deleting stack #{stack.name} since it doesn't exist."
|
41
|
+
return
|
42
|
+
end
|
43
|
+
get_parameters_outputs stack
|
44
|
+
run_hook :'before-destroy'
|
45
|
+
stack.delete
|
46
|
+
end
|
47
|
+
|
48
|
+
def run_hook(hook_name)
|
49
|
+
CfDeployer::Driver::DryRun.guard "Skipping hook #{hook_name}" do
|
50
|
+
hook = Hook.new hook_name, context[hook_name]
|
51
|
+
hook.run context
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_parameters_outputs(stack)
|
56
|
+
CfDeployer::Driver::DryRun.guard "Skipping get_parameters_outputs" do
|
57
|
+
context[:parameters] = stack.parameters
|
58
|
+
context[:outputs] = stack.outputs
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def warm_up_inactive_stack
|
63
|
+
group_ids(inactive_stack).each_with_index do |id, index|
|
64
|
+
asg_driver(id).warm_up get_desired(id, index)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_desired(id, index)
|
69
|
+
group_id = active_stack ? group_ids(active_stack)[index] : id
|
70
|
+
asg_driver(group_id).describe[:desired]
|
71
|
+
end
|
72
|
+
|
73
|
+
def group_ids(stack)
|
74
|
+
return [] unless asg_id_outputs
|
75
|
+
asg_id_outputs.map { |id| stack.output id }
|
76
|
+
end
|
77
|
+
|
78
|
+
def asg_driver name
|
79
|
+
@auto_scaling_group_drivers[name] ||= CfDeployer::Driver::AutoScalingGroup.new name
|
80
|
+
end
|
81
|
+
|
82
|
+
def asg_id_outputs
|
83
|
+
@context[:settings][:'auto-scaling-group-name-output']
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module DeploymentStrategy
|
3
|
+
class BlueGreen < Base
|
4
|
+
|
5
|
+
def exists?
|
6
|
+
green_stack.exists? || blue_stack.exists?
|
7
|
+
end
|
8
|
+
|
9
|
+
def destroy
|
10
|
+
delete_stack green_stack
|
11
|
+
delete_stack blue_stack
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def output_value(key)
|
16
|
+
active_stack ? active_stack.output(key) : "The value will be referenced from the output #{key} of undeployed component #{component_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def status get_resource_statuses = false
|
20
|
+
my_status = {}
|
21
|
+
[blue_stack, green_stack].each do |the_stack|
|
22
|
+
my_status[the_stack.name] = {}
|
23
|
+
my_status[the_stack.name][:active] = stack_active?(the_stack)
|
24
|
+
my_status[the_stack.name][:status] = the_stack.status
|
25
|
+
my_status[the_stack.name][:resources] = the_stack.resource_statuses if the_stack.exists? && get_resource_statuses
|
26
|
+
end
|
27
|
+
my_status
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def blue_stack
|
33
|
+
name = "#{stack_prefix}-B"
|
34
|
+
Stack.new(name, component_name, context)
|
35
|
+
end
|
36
|
+
|
37
|
+
def green_stack
|
38
|
+
name = "#{stack_prefix}-G"
|
39
|
+
Stack.new(name, component_name, context)
|
40
|
+
end
|
41
|
+
|
42
|
+
def both_stacks_exist?
|
43
|
+
blue_stack.exists? && green_stack.exists?
|
44
|
+
end
|
45
|
+
|
46
|
+
def active_stack
|
47
|
+
@active_stack = get_active_stack unless @active_stack_checked
|
48
|
+
@active_stack
|
49
|
+
end
|
50
|
+
|
51
|
+
def inactive_stack
|
52
|
+
@inactive_stack ||= get_inactive_stack
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_inactive_stack
|
56
|
+
return blue_stack unless active_stack
|
57
|
+
stack_active?(green_stack) ? blue_stack : green_stack
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_active_stack
|
61
|
+
@active_stack_checked = true
|
62
|
+
return green_stack if stack_active?(green_stack)
|
63
|
+
return blue_stack if stack_active?(blue_stack)
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module DeploymentStrategy
|
3
|
+
class CnameSwap < BlueGreen
|
4
|
+
|
5
|
+
def deploy
|
6
|
+
Log.info "Found active stack #{active_stack.name}" if active_stack
|
7
|
+
delete_stack inactive_stack
|
8
|
+
create_inactive_stack
|
9
|
+
warm_up_inactive_stack
|
10
|
+
swap_cname
|
11
|
+
Kernel.sleep 60
|
12
|
+
run_hook(:'after-swap')
|
13
|
+
Log.info "Active stack has been set to #{inactive_stack.name}"
|
14
|
+
delete_stack(active_stack) if active_stack && !settings[:'keep-previous-stack']
|
15
|
+
Log.info "#{component_name} deployed successfully"
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def kill_inactive
|
20
|
+
raise ApplicationError.new("Stack: #{inactive_stack.name} does not exist, cannot kill it.") unless inactive_stack.exists?
|
21
|
+
delete_stack inactive_stack
|
22
|
+
end
|
23
|
+
|
24
|
+
def switch
|
25
|
+
raise ApplicationError.new('There is only one color stack active, you cannot switch back to a non-existent version') unless both_stacks_exist?
|
26
|
+
swap_cname
|
27
|
+
Log.info "Active stack has been set to #{inactive_stack.name}"
|
28
|
+
Log.info "#{component_name} switched successfully"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
|
34
|
+
def active_cname
|
35
|
+
@active_cname ||= get_active_cname
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
def create_inactive_stack
|
41
|
+
inactive_stack.deploy
|
42
|
+
get_parameters_outputs(inactive_stack)
|
43
|
+
run_hook(:'after-create')
|
44
|
+
end
|
45
|
+
|
46
|
+
def swap_cname
|
47
|
+
set_cname_to(inactive_stack)
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_cname_to(stack)
|
51
|
+
cname, zone_id = find_elb_cname_for_stack(stack, elb_output_key)
|
52
|
+
dns_driver.set_alias_target(dns_zone, dns_fqdn, zone_id, cname)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def stack_active?(stack)
|
57
|
+
return false unless stack.exists?
|
58
|
+
return false unless active_cname.length > 0
|
59
|
+
cname, zone_id = find_elb_cname_for_stack(stack, elb_output_key)
|
60
|
+
active_cname.downcase == cname.downcase
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def get_active_cname
|
65
|
+
dns_driver.find_alias_target(dns_zone, dns_fqdn) || ""
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def elb_output_key
|
70
|
+
settings[:'elb-name-output']
|
71
|
+
end
|
72
|
+
|
73
|
+
def dns_fqdn
|
74
|
+
settings[:'dns-fqdn']
|
75
|
+
end
|
76
|
+
|
77
|
+
def dns_zone
|
78
|
+
settings[:'dns-zone']
|
79
|
+
end
|
80
|
+
|
81
|
+
def dns_driver
|
82
|
+
context[:dns_driver] || string_to_class(settings[:'dns-driver'])
|
83
|
+
end
|
84
|
+
|
85
|
+
def elb_driver
|
86
|
+
context[:elb_driver] || CfDeployer::Driver::Elb.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def settings
|
90
|
+
context[:settings]
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_elb_cname_for_stack(stack, elb_name_output_key)
|
94
|
+
return ['', ''] unless stack.exists?
|
95
|
+
elb_id = stack.output(elb_name_output_key)
|
96
|
+
attrs = elb_driver.find_dns_and_zone_id(elb_id)
|
97
|
+
[attrs[:dns_name] || '', attrs[:canonical_hosted_zone_name_id] || '']
|
98
|
+
end
|
99
|
+
|
100
|
+
def string_to_class class_string
|
101
|
+
class_string.split('::').inject(Object) do |mod, class_name|
|
102
|
+
mod.const_get(class_name)
|
103
|
+
end.new
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module DeploymentStrategy
|
3
|
+
class CreateOrUpdate < Base
|
4
|
+
BLUE_GREEN_STRATEGY = false
|
5
|
+
|
6
|
+
def exists?
|
7
|
+
stack.exists?
|
8
|
+
end
|
9
|
+
|
10
|
+
def status get_resource_statuses = false
|
11
|
+
my_status = {}
|
12
|
+
my_status[stack.name] = {}
|
13
|
+
my_status[stack.name][:status] = stack.status
|
14
|
+
my_status[stack.name][:resources] = stack.resource_statuses if stack.exists? && get_resource_statuses
|
15
|
+
my_status
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def deploy
|
20
|
+
stack.deploy
|
21
|
+
warm_up_inactive_stack
|
22
|
+
get_parameters_outputs(inactive_stack)
|
23
|
+
run_hook(:'after-create')
|
24
|
+
end
|
25
|
+
|
26
|
+
def output_value(key)
|
27
|
+
exists? ? stack.output(key) : "The value will be referenced from the output #{key} of undeployed component #{component_name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy
|
31
|
+
delete_stack stack
|
32
|
+
end
|
33
|
+
|
34
|
+
def kill_inactive
|
35
|
+
raise ApplicationError.new('There is no inactive version to kill for Create or Update Deployments.')
|
36
|
+
end
|
37
|
+
|
38
|
+
def switch
|
39
|
+
raise ApplicationError.new('There is no inactive version to switch to for Create or Update Deployments. Redeploy the version you want')
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def stack
|
45
|
+
Stack.new(stack_prefix, @component_name, @context)
|
46
|
+
end
|
47
|
+
|
48
|
+
def inactive_stack
|
49
|
+
stack
|
50
|
+
end
|
51
|
+
|
52
|
+
def active_stack
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module Driver
|
3
|
+
class AutoScalingGroup
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :aws_group, :auto_scaling_instances, :ec2_instances, :load_balancers
|
7
|
+
|
8
|
+
attr_reader :group_name, :group
|
9
|
+
|
10
|
+
def initialize name, timeout = CfDeployer::Defaults::Timeout
|
11
|
+
@group_name = name
|
12
|
+
@timeout = timeout
|
13
|
+
end
|
14
|
+
|
15
|
+
def describe
|
16
|
+
{ desired: aws_group.desired_capacity, min: aws_group.min_size, max: aws_group.max_size }
|
17
|
+
end
|
18
|
+
|
19
|
+
def warm_up desired
|
20
|
+
return if desired < aws_group.min_size
|
21
|
+
desired = aws_group.max_size if desired > aws_group.max_size
|
22
|
+
Log.info "warming up auto scaling group #{group_name} to #{desired}"
|
23
|
+
|
24
|
+
CfDeployer::Driver::DryRun.guard "Skipping ASG warmup" do
|
25
|
+
aws_group.set_desired_capacity desired
|
26
|
+
wait_for_healthy_instance desired
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def warm_up_cooled_group options
|
31
|
+
CfDeployer::Driver::DryRun.guard 'Skipping update of ASG min & max instance count' do
|
32
|
+
aws_group.update :min_size => options[:min], :max_size => options[:max]
|
33
|
+
end
|
34
|
+
warm_up options[:desired]
|
35
|
+
end
|
36
|
+
|
37
|
+
def cool_down
|
38
|
+
Log.info "Cooling down #{group_name}"
|
39
|
+
CfDeployer::Driver::DryRun.guard "Skipping ASG cooldown" do
|
40
|
+
aws_group.update :min_size => 0, :max_size => 0
|
41
|
+
aws_group.set_desired_capacity 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def instance_statuses
|
46
|
+
instance_info = {}
|
47
|
+
ec2_instances.each do |instance|
|
48
|
+
instance_info[instance.id] = CfDeployer::Driver::Instance.new(instance).status
|
49
|
+
end
|
50
|
+
instance_info
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def healthy_instance_count
|
56
|
+
count = auto_scaling_instances.count do |instance|
|
57
|
+
instance.health_status == 'HEALTHY' && (load_balancers.empty? || instance_in_service?( instance.ec2_instance ))
|
58
|
+
end
|
59
|
+
Log.info "Healthy instance count: #{count}"
|
60
|
+
count
|
61
|
+
end
|
62
|
+
|
63
|
+
def instance_in_service? instance
|
64
|
+
load_balancers.all? do |load_balancer|
|
65
|
+
load_balancer.instances.health.any? do |health|
|
66
|
+
health[:instance] == instance ? health[:state] == 'InService' : false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def wait_for_healthy_instance number
|
72
|
+
Timeout::timeout(@timeout){
|
73
|
+
while healthy_instance_count != number
|
74
|
+
sleep 15
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def aws_group
|
81
|
+
@my_group ||= AWS::AutoScaling.new.groups[group_name]
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module Driver
|
3
|
+
class CloudFormation
|
4
|
+
|
5
|
+
def initialize stack_name
|
6
|
+
@stack_name = stack_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def stack_exists?
|
10
|
+
aws_stack.exists?
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_stack template, opts
|
14
|
+
CfDeployer::Driver::DryRun.guard "Skipping create_stack" do
|
15
|
+
cloud_formation.stacks.create @stack_name, template, opts
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_stack template, opts
|
20
|
+
begin
|
21
|
+
CfDeployer::Driver::DryRun.guard "Skipping update_stack" do
|
22
|
+
aws_stack.update opts.merge(:template => template)
|
23
|
+
end
|
24
|
+
rescue AWS::CloudFormation::Errors::ValidationError => e
|
25
|
+
if e.message =~ /No updates are to be performed/
|
26
|
+
Log.info e.message
|
27
|
+
else
|
28
|
+
raise
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def stack_status
|
34
|
+
aws_stack.status.downcase.to_sym
|
35
|
+
end
|
36
|
+
|
37
|
+
def outputs
|
38
|
+
aws_stack.outputs.inject({}) do |memo, o|
|
39
|
+
memo[o.key] = o.value
|
40
|
+
memo
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def parameters
|
45
|
+
aws_stack.parameters
|
46
|
+
end
|
47
|
+
|
48
|
+
def query_output key
|
49
|
+
output = aws_stack.outputs.find { |o| o.key == key }
|
50
|
+
output && output.value
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_stack
|
54
|
+
if stack_exists?
|
55
|
+
CfDeployer::Driver::DryRun.guard "Skipping create_stack" do
|
56
|
+
aws_stack.delete
|
57
|
+
end
|
58
|
+
else
|
59
|
+
Log.info "Stack #{@stack_name} does not exist!"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def resource_statuses
|
64
|
+
resources = {}
|
65
|
+
aws_stack.resource_summaries.each do |rs|
|
66
|
+
resources[rs[:resource_type]] ||= {}
|
67
|
+
resources[rs[:resource_type]][rs[:physical_resource_id]] = rs[:resource_status]
|
68
|
+
end
|
69
|
+
resources
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def cloud_formation
|
75
|
+
AWS::CloudFormation.new
|
76
|
+
end
|
77
|
+
|
78
|
+
def aws_stack
|
79
|
+
cloud_formation.stacks[@stack_name]
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module Driver
|
3
|
+
class DryRun
|
4
|
+
|
5
|
+
@@enabled = false
|
6
|
+
|
7
|
+
def self.enable
|
8
|
+
CfDeployer::Log.info "Enabling Dry-Run Mode"
|
9
|
+
@@enabled = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.disable
|
13
|
+
CfDeployer::Log.info "Disabling Dry-Run Mode"
|
14
|
+
@@enabled = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.guard description
|
18
|
+
if @@enabled
|
19
|
+
CfDeployer::Log.info "<Dry Run Enabled> #{description}"
|
20
|
+
else
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module Driver
|
3
|
+
class Elb
|
4
|
+
def find_dns_and_zone_id elb_id
|
5
|
+
elb = elb_driver.load_balancers[elb_id]
|
6
|
+
{ :canonical_hosted_zone_name_id => elb.canonical_hosted_zone_name_id, :dns_name => elb.dns_name }
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def elb_driver
|
12
|
+
AWS::ELB.new
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CfDeployer
|
2
|
+
module Driver
|
3
|
+
class Instance
|
4
|
+
|
5
|
+
GOOD_STATUSES = [ :running, :pending ]
|
6
|
+
|
7
|
+
def initialize instance_obj_or_id
|
8
|
+
if instance_obj_or_id.is_a?(String)
|
9
|
+
@id = instance_obj_or_id
|
10
|
+
else
|
11
|
+
@instance_obj = instance_obj_or_id
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def status
|
16
|
+
instance_info = { }
|
17
|
+
[:status, :public_ip_address, :private_ip_address, :image_id].each do |stat|
|
18
|
+
instance_info[stat] = aws_instance.send(stat)
|
19
|
+
end
|
20
|
+
instance_info[:key_pair] = aws_instance.key_pair.name
|
21
|
+
instance_info
|
22
|
+
end
|
23
|
+
|
24
|
+
def aws_instance
|
25
|
+
@instance_obj ||= AWS::EC2.new.instances[@id]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|