cf_deployer 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|