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
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
|