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
data/spec/fakes/stack.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Fakes
|
2
|
+
class Stack
|
3
|
+
attr_reader :outputs, :parameters
|
4
|
+
def initialize(options)
|
5
|
+
@exists = options[:exists?].nil? ? true : options[:exists?]
|
6
|
+
@outputs = options[:outputs] || {}
|
7
|
+
@parameters = options[:parameters] || {}
|
8
|
+
@name = options[:name] || 'Unnamed'
|
9
|
+
@status = options[:status] || :ready
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
"#{self.class}<#{@name}>"
|
14
|
+
end
|
15
|
+
alias_method :to_s, :inspect
|
16
|
+
|
17
|
+
def output(key)
|
18
|
+
raise 'Stack is dead' unless @exists
|
19
|
+
@outputs[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_output(key, value)
|
23
|
+
@outputs[key] = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def live!
|
27
|
+
@exists = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def die!
|
31
|
+
@exists = false
|
32
|
+
end
|
33
|
+
|
34
|
+
def exists?
|
35
|
+
@exists
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete
|
39
|
+
@exists = false
|
40
|
+
@deployed = false
|
41
|
+
@deleted = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def deploy
|
45
|
+
@exists = true
|
46
|
+
@deployed = true
|
47
|
+
end
|
48
|
+
|
49
|
+
def deployed?
|
50
|
+
@deployed
|
51
|
+
end
|
52
|
+
|
53
|
+
def deleted?
|
54
|
+
@deleted
|
55
|
+
end
|
56
|
+
|
57
|
+
def name
|
58
|
+
@name
|
59
|
+
end
|
60
|
+
|
61
|
+
def status
|
62
|
+
@status
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'functional_spec_helper'
|
2
|
+
|
3
|
+
describe 'Deploy' do
|
4
|
+
context 'create-or-update' do
|
5
|
+
|
6
|
+
let(:asg_driver) { double('asg_driver') }
|
7
|
+
let(:stack) { Fakes::Stack.new(name: 'stack', outputs: {'AutoScalingGroupID' => 'myASG'})}
|
8
|
+
|
9
|
+
it 'should create a stack in Cloud Formation' do
|
10
|
+
stack.die!
|
11
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-create-or-update-test-web', 'web', anything()) { stack }
|
12
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('myASG') { asg_driver }
|
13
|
+
allow(asg_driver).to receive(:describe) {{desired: 1, min: 1, max: 2}}
|
14
|
+
allow(asg_driver).to receive(:warm_up).with(1)
|
15
|
+
CfDeployer::CLI.start(['deploy', 'test', 'web', '-f', 'samples/create-or-update/cf_deployer.yml'])
|
16
|
+
expect(stack).to be_deployed
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'cname-swap' do
|
21
|
+
let(:blue_asg_driver) { double('blue_asg_driver') }
|
22
|
+
let(:green_asg_driver) { double('green_asg_driver') }
|
23
|
+
let(:dns_driver) { double('route53') }
|
24
|
+
let(:elb_driver) { double('elb') }
|
25
|
+
let(:blue_stack) { Fakes::Stack.new(name: 'BLUE', outputs: {'ELBName' => 'BLUE-elb', 'AutoScalingGroupName' => 'blueASG'}, parameters: {:name => 'blue'}) }
|
26
|
+
let(:green_stack) { Fakes::Stack.new(name: 'GREEN', outputs: {'ELBName' => 'GREEN-elb', 'AutoScalingGroupName' => 'greenASG'}, parameters: {:name => 'green'}) }
|
27
|
+
|
28
|
+
before :each do
|
29
|
+
allow(Kernel).to receive(:sleep)
|
30
|
+
end
|
31
|
+
it 'should recreate inactive stack and set CNAME map to its ELB dns' do
|
32
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-cname-swap-dev-web-B', 'web', anything()) { blue_stack }
|
33
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-cname-swap-dev-web-G', 'web', anything()) { green_stack }
|
34
|
+
allow(CfDeployer::Driver::Elb).to receive(:new) { elb_driver }
|
35
|
+
allow(CfDeployer::Driver::Route53).to receive(:new) { dns_driver }
|
36
|
+
allow(dns_driver).to receive(:find_alias_target).with('zhao.com', 'test1.zhao.com'){ 'BLUE-elb.zhao.com' }
|
37
|
+
allow(elb_driver).to receive(:find_dns_and_zone_id).with('BLUE-elb') { {:dns_name => 'blue-elb.zhao.com', :canonical_hosted_zone_name_id => 'BLUE111'}}
|
38
|
+
allow(elb_driver).to receive(:find_dns_and_zone_id).with('GREEN-elb') { {:dns_name => 'green-elb.zhao.com', :canonical_hosted_zone_name_id => 'GREEN111'}}
|
39
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
40
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
41
|
+
allow(green_asg_driver).to receive(:describe) {{desired: 0, min: 0, max: 0}}
|
42
|
+
allow(blue_asg_driver).to receive(:describe) {{desired: 2, min: 1, max: 5}}
|
43
|
+
allow(dns_driver).to receive(:set_alias_target).with('zhao.com', 'test1.zhao.com', 'GREEN111', 'green-elb.zhao.com')
|
44
|
+
expect(green_asg_driver).to receive(:warm_up).with(2)
|
45
|
+
CfDeployer::CLI.start(['deploy', 'dev', 'web', '-f', 'samples/cname-swap/cf_deployer.yml'])
|
46
|
+
expect(green_stack).to be_deleted
|
47
|
+
expect(green_stack).to be_deployed
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'autoscaling-swap' do
|
52
|
+
let(:blue_asg_driver) { double('blue_asg_driver') }
|
53
|
+
let(:green_asg_driver) { double('green_asg_driver') }
|
54
|
+
let(:blue_stack) { Fakes::Stack.new(name: 'BLUE', outputs: {'web-elb-name' => 'BLUE-elb', 'AutoScalingGroupName' => 'blueASG'}, parameters: { name: 'blue'}) }
|
55
|
+
let(:green_stack) { Fakes::Stack.new(name: 'GREEN', outputs: {'web-elb-name' => 'GREEN-elb', 'AutoScalingGroupName' => 'greenASG'}, parameters: { name: 'green'}) }
|
56
|
+
let(:base_stack) { Fakes::Stack.new(name: 'base') }
|
57
|
+
|
58
|
+
it 'should re-create and warm up inactive stack and cool down the active stack' do
|
59
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-asg-swap-dev-web-B', 'web', anything) { blue_stack }
|
60
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-asg-swap-dev-web-G', 'web', anything) { green_stack }
|
61
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-asg-swap-dev-base', 'base', anything) { base_stack }
|
62
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
63
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
64
|
+
allow(blue_asg_driver).to receive(:describe) {{desired: 0, min: 0, max: 0}}
|
65
|
+
allow(green_asg_driver).to receive(:describe) {{desired: 2, min: 1, max: 5}}
|
66
|
+
expect(blue_asg_driver).to receive(:warm_up).with(2)
|
67
|
+
|
68
|
+
CfDeployer::CLI.start(['deploy', 'dev', 'web', '-f', 'samples/simple/cf_deployer.yml'])
|
69
|
+
expect(blue_stack).to be_deleted
|
70
|
+
expect(blue_stack).to be_deployed
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'functional_spec_helper'
|
2
|
+
|
3
|
+
describe 'Kill Inactive' do
|
4
|
+
context 'create-or-update' do
|
5
|
+
let(:stack) { Fakes::Stack.new(name: 'stack', outputs: {'AutoScalingGroupID' => 'myASG'})}
|
6
|
+
|
7
|
+
it 'should raise an error that there is no inactive for create-or-update staks' do
|
8
|
+
stack.die!
|
9
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-create-or-update-test-web', 'web', anything()) { stack }
|
10
|
+
expect { CfDeployer::CLI.start(['kill_inactive', 'test', 'web', '-f', 'samples/create-or-update/cf_deployer.yml']) }.to raise_error CfDeployer::ApplicationError, 'There is no inactive version to kill for Create or Update Deployments.'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'cname-swap' do
|
15
|
+
let(:blue_stack) { Fakes::Stack.new(name: 'BLUE', outputs: {'ELBName' => 'BLUE-elb', 'AutoScalingGroupID' => 'blueASG'}, parameters: {:name => 'blue'}) }
|
16
|
+
let(:green_stack) { Fakes::Stack.new(name: 'GREEN', outputs: {'ELBName' => 'GREEN-elb', 'AutoScalingGroupID' => 'greenASG'}, parameters: {:name => 'green'}) }
|
17
|
+
|
18
|
+
it 'should delete the stack that is not being pointed to by dns' do
|
19
|
+
blue_stack.live!
|
20
|
+
green_stack.live!
|
21
|
+
elb_driver = double('elb_driver')
|
22
|
+
allow(CfDeployer::Driver::Elb).to receive(:new) { elb_driver }
|
23
|
+
allow(elb_driver).to receive(:find_dns_and_zone_id).with('BLUE-elb') { {:dns_name => 'blue-elb.aws.amazon.com', :canonical_hosted_zone_name_id => 'BLUE111'}}
|
24
|
+
allow(elb_driver).to receive(:find_dns_and_zone_id).with('GREEN-elb') { {:dns_name => 'green-elb.aws.amazon.com', :canonical_hosted_zone_name_id => 'GREEN111'}}
|
25
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-cname-swap-test-web-B', 'web', anything) { blue_stack }
|
26
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-cname-swap-test-web-G', 'web', anything) { green_stack }
|
27
|
+
dns_driver = double('route53 driver')
|
28
|
+
allow(CfDeployer::Driver::Route53).to receive(:new) { dns_driver }
|
29
|
+
allow(dns_driver).to receive(:find_alias_target).with('zhao.com', 'test1.zhao.com'){ 'BLUE-elb.aws.amazon.com' }
|
30
|
+
|
31
|
+
CfDeployer::CLI.start(['kill_inactive', 'test', 'web', '-f', 'samples/cname-swap/cf_deployer.yml'])
|
32
|
+
expect(green_stack).to be_deleted
|
33
|
+
expect(blue_stack).not_to be_deleted
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'asg-swap' do
|
38
|
+
let(:blue_stack) { Fakes::Stack.new(name: 'BLUE', outputs: {'ELBName' => 'BLUE-elb', 'AutoScalingGroupName' => 'blueASG'}, parameters: {:name => 'blue'}) }
|
39
|
+
let(:green_stack) { Fakes::Stack.new(name: 'GREEN', outputs: {'ELBName' => 'GREEN-elb', 'AutoScalingGroupName' => 'greenASG'}, parameters: {:name => 'green'}) }
|
40
|
+
let(:blue_asg_driver) { double('blue_asg_driver') }
|
41
|
+
let(:green_asg_driver) { double('green_asg_driver') }
|
42
|
+
|
43
|
+
it 'should delete the stack that has no active instances' do
|
44
|
+
blue_stack.live!
|
45
|
+
green_stack.live!
|
46
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-asg-swap-test-web-B', 'web', anything) { blue_stack }
|
47
|
+
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-asg-swap-test-web-G', 'web', anything) { green_stack }
|
48
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
49
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
50
|
+
allow(green_asg_driver).to receive(:describe) { {desired: 0, min: 0, max: 0} }
|
51
|
+
allow(blue_asg_driver).to receive(:describe) { {desired: 1, min: 1, max: 2} }
|
52
|
+
CfDeployer::CLI.start(['kill_inactive', 'test', 'web', '-f', 'samples/simple/cf_deployer.yml'])
|
53
|
+
expect(green_stack).to be_deleted
|
54
|
+
expect(blue_stack).not_to be_deleted
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "application" do
|
4
|
+
before :each do
|
5
|
+
@context = {
|
6
|
+
:application => 'myApp',
|
7
|
+
:environment => 'dev',
|
8
|
+
:targets => ['queue', 'web', 'db', 'base'],
|
9
|
+
:components => {
|
10
|
+
:queue => {
|
11
|
+
:'depends-on' => [ 'base', 'db' ]
|
12
|
+
},
|
13
|
+
:web => {
|
14
|
+
:'depends-on' => [ 'db', 'queue' ]
|
15
|
+
},
|
16
|
+
:db => {
|
17
|
+
:'depends-on' => [ 'base']
|
18
|
+
},
|
19
|
+
:base => {
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
@app = CfDeployer::Application.new(@context)
|
24
|
+
@base = @app.components.find{ |c| c.name == 'base' }
|
25
|
+
@db = @app.components.find{ |c| c.name == 'db' }
|
26
|
+
@queue = @app.components.find{ |c| c.name == 'queue' }
|
27
|
+
@web = @app.components.find{ |c| c.name == 'web' }
|
28
|
+
end
|
29
|
+
|
30
|
+
it "application should get all components" do
|
31
|
+
@app.components.length.should eq(4)
|
32
|
+
@base.should_not be_nil
|
33
|
+
@db.should_not be_nil
|
34
|
+
@queue.should_not be_nil
|
35
|
+
@web.should_not be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
context "order components by dependencies" do
|
39
|
+
it "should get components ordered by dependencies" do
|
40
|
+
expect(@app.components).to eq([@base, @db, @queue, @web])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'destroy' do
|
45
|
+
it 'should destroy components starting with components without dependents' do
|
46
|
+
log = ""
|
47
|
+
allow(@base).to receive(:destroy) { log += "base "}
|
48
|
+
allow(@db).to receive(:destroy) { log += "db "}
|
49
|
+
allow(@queue).to receive(:destroy) { log += "queue "}
|
50
|
+
allow(@web).to receive(:destroy) { log += "web "}
|
51
|
+
@app.destroy
|
52
|
+
expect(log).to eql("web queue db base ")
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should destroy specified components' do
|
56
|
+
log = ""
|
57
|
+
allow(@base).to receive(:destroy) { log += "base "}
|
58
|
+
allow(@db).to receive(:destroy) { log += "db "}
|
59
|
+
allow(@queue).to receive(:destroy) { log += "queue "}
|
60
|
+
allow(@web).to receive(:destroy) { log += "web "}
|
61
|
+
@context[:targets] = ['db', 'web', 'queue']
|
62
|
+
@app.destroy
|
63
|
+
expect(log).to eql("web queue db ")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "application should get components with their dependencies" do
|
68
|
+
expect(@base.dependencies).to match_array([])
|
69
|
+
expect(@db.dependencies).to match_array([@base])
|
70
|
+
expect(@queue.dependencies).to match_array([@base, @db])
|
71
|
+
expect(@web.dependencies).to match_array([@db, @queue])
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should get add components with their children' do
|
75
|
+
expect(@base.children).to match_array([@db, @queue])
|
76
|
+
expect(@db.children).to match_array([@web, @queue])
|
77
|
+
expect(@queue.children).to match_array([@web])
|
78
|
+
expect(@web.children).to match_array([])
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#switch' do
|
82
|
+
|
83
|
+
let(:app) { CfDeployer::Application.new(@context.merge(:targets => ['base'])) }
|
84
|
+
|
85
|
+
it 'should switch the specified component' do
|
86
|
+
base = app.components.find { |c| c.name == 'base' }
|
87
|
+
expect(base).to receive :switch
|
88
|
+
app.switch
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should not switch components not specified' do
|
92
|
+
db = app.components.find { |c| c.name == 'db' }
|
93
|
+
queue = app.components.find { |c| c.name == 'queue' }
|
94
|
+
web = app.components.find { |c| c.name == 'web' }
|
95
|
+
base = app.components.find { |c| c.name == 'base' }
|
96
|
+
allow(base).to receive :switch
|
97
|
+
expect(db).not_to receive :switch
|
98
|
+
expect(queue).not_to receive :switch
|
99
|
+
expect(web).not_to receive :switch
|
100
|
+
app.switch
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#kill_inactive' do
|
105
|
+
it 'should kill the inactive piece of a component' do
|
106
|
+
@context[:targets] = ['base']
|
107
|
+
app = CfDeployer::Application.new(@context)
|
108
|
+
base = app.components.find{ |c| c.name == 'base' }
|
109
|
+
expect(base).to receive(:kill_inactive)
|
110
|
+
app.kill_inactive
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#status' do
|
115
|
+
it "should get each component's status" do
|
116
|
+
expect(@base).to receive(:status)
|
117
|
+
expect(@db).to receive(:status)
|
118
|
+
expect(@queue).to receive(:status)
|
119
|
+
expect(@web).to receive(:status)
|
120
|
+
|
121
|
+
@app.status nil, 'all'
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should pass the get_resource_statuses flag down to the components' do
|
125
|
+
expect(@base).to receive(:status).with(true)
|
126
|
+
expect(@db).to receive(:status).with(true)
|
127
|
+
expect(@queue).to receive(:status).with(true)
|
128
|
+
expect(@web).to receive(:status).with(true)
|
129
|
+
|
130
|
+
@app.status nil, 'all'
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should filter by component if specified' do
|
134
|
+
expect(@base).not_to receive(:status)
|
135
|
+
expect(@db).not_to receive(:status)
|
136
|
+
expect(@queue).not_to receive(:status)
|
137
|
+
|
138
|
+
expect(@web).to receive(:status).with(true)
|
139
|
+
|
140
|
+
@app.status 'web', 'all'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "deploy components" do
|
145
|
+
before :each do
|
146
|
+
@log = ""
|
147
|
+
allow(@base).to receive(:deploy) { @log += "base "}
|
148
|
+
allow(@db).to receive(:deploy) { @log += "db "}
|
149
|
+
allow(@queue).to receive(:deploy) { @log += "queue "}
|
150
|
+
allow(@web).to receive(:deploy) { @log += "web "}
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
context "deploy all components" do
|
155
|
+
it "should deploy all components if no component specified" do
|
156
|
+
@app.deploy
|
157
|
+
@log.should eq("base db queue web ")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "deploy some components" do
|
162
|
+
it "should deploy specified components" do
|
163
|
+
@context[:targets] = ['web', 'db']
|
164
|
+
@app.deploy
|
165
|
+
@log.should eq("db web ")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context '#json' do
|
171
|
+
|
172
|
+
before :each do
|
173
|
+
@log = ''
|
174
|
+
allow(@base).to receive(:json) { @log += "base " }
|
175
|
+
allow(@db).to receive(:json) { @log += "db " }
|
176
|
+
allow(@queue).to receive(:json) { @log += "queue " }
|
177
|
+
allow(@web).to receive(:json) { @log += "web " }
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should get json templates for all components' do
|
181
|
+
@app.json
|
182
|
+
@log.should eq("base db queue web ")
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should get json templates for components specified' do
|
186
|
+
@context[:targets] = ['web', 'db']
|
187
|
+
@app.json
|
188
|
+
@log.should eq('db web ')
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "component" do
|
4
|
+
before :each do
|
5
|
+
@strategy = double('deployment_strategy')
|
6
|
+
|
7
|
+
allow(CfDeployer::DeploymentStrategy).to receive(:create).and_return(@strategy)
|
8
|
+
|
9
|
+
@context = {
|
10
|
+
:'deployment-strategy' => 'create-or-update',
|
11
|
+
:inputs => {
|
12
|
+
:'vpc-subnets' => {
|
13
|
+
:component => 'base',
|
14
|
+
:'output-key' => 'subnets'
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
@base = CfDeployer::Component.new('myApp', 'uat', 'base', {})
|
19
|
+
@db = CfDeployer::Component.new('myApp', 'uat', 'db', {})
|
20
|
+
|
21
|
+
@web = CfDeployer::Component.new('myApp', 'uat', 'web', @context)
|
22
|
+
@web.dependencies << @base
|
23
|
+
@web.dependencies << @db
|
24
|
+
@base.children << @web
|
25
|
+
@db.children << @web
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'json' do
|
29
|
+
|
30
|
+
it 'should revolve settings from parent components if parent components has been deployed' do
|
31
|
+
allow(@base).to receive(:exists?){ true }
|
32
|
+
allow(@base).to receive(:output_value).with('subnets') { 'abcd1234, edfas1234' }
|
33
|
+
expect(CfDeployer::ConfigLoader).to receive(:component_json).with('web', @context)
|
34
|
+
@web.json
|
35
|
+
|
36
|
+
expect(@context[:inputs][:'vpc-subnets']).to eq('abcd1234, edfas1234')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
it "should destroy component" do
|
42
|
+
expect(@strategy).to receive(:destroy)
|
43
|
+
@web.destroy
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should not destroy a component that is depended on' do
|
47
|
+
allow(@web).to receive(:exists?){ true }
|
48
|
+
expect(@strategy).not_to receive(:destroy)
|
49
|
+
expect{ @base.destroy }.to raise_error("Unable to destroy #{@base.name}, it is depended on by other components")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should get output value" do
|
53
|
+
expect(@strategy).to receive(:output_value).with('key1'){ 'value1' }
|
54
|
+
@web.output_value('key1')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "deployment should only deploy depends-on if the depends-on do not exists" do
|
58
|
+
expect(@base).to receive(:exists?){ false }
|
59
|
+
expect(@db).to receive(:exists?) { true }
|
60
|
+
expect(@base).to receive(:deploy)
|
61
|
+
expect(@db).to_not receive(:deploy)
|
62
|
+
expect(@strategy).to receive(:deploy)
|
63
|
+
expect(@base).to receive(:output_value).with('subnets') { 'abcd1234, edfas1234' }
|
64
|
+
@web.deploy
|
65
|
+
|
66
|
+
expect(@context[:inputs][:'vpc-subnets']).to eq('abcd1234, edfas1234')
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should ask strategy if component exists" do
|
70
|
+
expect(@strategy).to receive(:exists?){ true }
|
71
|
+
@web.exists?.should eq(true)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should find direct dependencies" do
|
75
|
+
web = CfDeployer::Component.new('myApp', 'uat', 'web', {})
|
76
|
+
base = CfDeployer::Component.new('myApp', 'uat', 'base', {})
|
77
|
+
web.dependencies << base
|
78
|
+
|
79
|
+
web.depends_on?(base).should eq(true)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should find transitive dependencies" do
|
83
|
+
web = CfDeployer::Component.new('myApp', 'uat', 'web', {})
|
84
|
+
haproxy = CfDeployer::Component.new('myApp', 'uat', 'haproxy', {})
|
85
|
+
base = CfDeployer::Component.new('myApp', 'uat', 'base', {})
|
86
|
+
|
87
|
+
haproxy.dependencies << base
|
88
|
+
web.dependencies << haproxy
|
89
|
+
|
90
|
+
web.depends_on?(base).should eq(true)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should find cyclic dependency" do
|
94
|
+
web = CfDeployer::Component.new('myApp', 'uat', 'web', {})
|
95
|
+
haproxy = CfDeployer::Component.new('myApp', 'uat', 'haproxy', {})
|
96
|
+
base = CfDeployer::Component.new('myApp', 'uat', 'base', {})
|
97
|
+
foo = CfDeployer::Component.new('myApp', 'uat', 'foo', {})
|
98
|
+
|
99
|
+
haproxy.dependencies << base
|
100
|
+
web.dependencies << haproxy
|
101
|
+
base.dependencies << web
|
102
|
+
|
103
|
+
expect{haproxy.depends_on? foo}.to raise_error("Cyclic dependency")
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#status' do
|
107
|
+
it "should ask strategy for status" do
|
108
|
+
expect(@strategy).to receive(:status){ true }
|
109
|
+
@web.status false
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should pass get_resource_statuses down to the strategy" do
|
113
|
+
expect(@strategy).to receive(:status).with(true)
|
114
|
+
@web.status(true)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#kill_inactive' do
|
119
|
+
it 'should tell the strategy to kill the inactive piece' do
|
120
|
+
expect(@strategy).to receive(:kill_inactive)
|
121
|
+
@web.kill_inactive
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '#switch' do
|
126
|
+
context 'if no stack exists' do
|
127
|
+
it 'should raise an error that there is no stack for the component' do
|
128
|
+
allow(@strategy).to receive(:exists?) { false }
|
129
|
+
expect(@strategy).not_to receive(:switch)
|
130
|
+
expect { @web.switch }.to raise_error 'No stack exists for component: web'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'a stack exists' do
|
135
|
+
it 'should use the deployment strategy to switch' do
|
136
|
+
allow(@strategy).to receive(:exists?) { true }
|
137
|
+
expect(@strategy).to receive(:switch)
|
138
|
+
@web.switch
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|