cf_deployer 1.6.0 → 2.0.0
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 +5 -5
- data/.github/workflows/ci.yml +29 -0
- data/.github/workflows/publish.yml +40 -0
- data/.rspec +4 -0
- data/ChangeLog.md +7 -0
- data/QUICKSTART.md +1 -1
- data/Rakefile +1 -1
- data/cf_deployer.gemspec +16 -10
- data/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml +2 -2
- data/lib/cf_deployer/cli.rb +1 -1
- data/lib/cf_deployer/config_loader.rb +2 -2
- data/lib/cf_deployer/config_validation.rb +1 -1
- data/lib/cf_deployer/driver/auto_scaling_group.rb +22 -26
- data/lib/cf_deployer/driver/cloud_formation_driver.rb +28 -18
- data/lib/cf_deployer/driver/elb_driver.rb +11 -4
- data/lib/cf_deployer/driver/instance.rb +4 -3
- data/lib/cf_deployer/driver/route53_driver.rb +17 -9
- data/lib/cf_deployer/logger.rb +10 -11
- data/lib/cf_deployer/stack.rb +18 -30
- data/lib/cf_deployer/status_presenter.rb +1 -1
- data/lib/cf_deployer/version.rb +1 -1
- data/lib/cf_deployer.rb +10 -7
- data/spec/aws_call_count_spec_helper.rb +14 -2
- data/spec/fakes/instance.rb +5 -4
- data/spec/fakes/route53_client.rb +4 -7
- data/spec/spec_helper.rb +11 -2
- data/spec/unit/application_spec.rb +9 -9
- data/spec/unit/component_spec.rb +3 -3
- data/spec/unit/config_loader_spec.rb +70 -69
- data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +10 -10
- data/spec/unit/deployment_strategy/base_spec.rb +17 -18
- data/spec/unit/deployment_strategy/cname_swap_spec.rb +9 -9
- data/spec/unit/deployment_strategy/create_or_update_spec.rb +8 -8
- data/spec/unit/driver/auto_scaling_group_spec.rb +75 -65
- data/spec/unit/driver/aws_sdk_v3_spec.rb +163 -0
- data/spec/unit/driver/cloud_formation_spec.rb +19 -17
- data/spec/unit/driver/dry_run_spec.rb +8 -8
- data/spec/unit/driver/elb_spec.rb +10 -4
- data/spec/unit/driver/instance_spec.rb +1 -7
- data/spec/unit/driver/route53_spec.rb +34 -25
- data/spec/unit/hook_spec.rb +3 -3
- data/spec/unit/stack_spec.rb +22 -21
- metadata +134 -56
- data/.travis.yml +0 -20
data/lib/cf_deployer/stack.rb
CHANGED
|
@@ -21,7 +21,9 @@ module CfDeployer
|
|
|
21
21
|
capabilities = @context[:capabilities] || []
|
|
22
22
|
notify = @context[:notify] || []
|
|
23
23
|
tags = @context[:tags] || {}
|
|
24
|
-
params =
|
|
24
|
+
params = @context[:inputs].select{|key, value| @context[:defined_parameters].keys.include?(key)}
|
|
25
|
+
.map { |key, value| { parameter_key: key.to_s, parameter_value: value.to_s } }
|
|
26
|
+
|
|
25
27
|
CfDeployer::Driver::DryRun.guard "Skipping deploy" do
|
|
26
28
|
if exists?
|
|
27
29
|
override_policy_json = nil
|
|
@@ -56,7 +58,7 @@ module CfDeployer
|
|
|
56
58
|
def find_output key
|
|
57
59
|
begin
|
|
58
60
|
@cf_driver.query_output(key)
|
|
59
|
-
rescue
|
|
61
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
|
60
62
|
raise ResourceNotInReadyState.new("Resource stack not in ready state yet, perhaps you should provision it first?")
|
|
61
63
|
end
|
|
62
64
|
end
|
|
@@ -88,20 +90,18 @@ module CfDeployer
|
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
def resource_statuses
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
resources[
|
|
95
|
-
resources[:asg_instances][asg_name] = CfDeployer::Driver::AutoScalingGroup.new(asg_name).instance_statuses
|
|
96
|
-
end
|
|
93
|
+
resources = @cf_driver.resource_statuses.merge( { :asg_instances => {}, :instances => {} } )
|
|
94
|
+
if resources['AWS::AutoScaling::AutoScalingGroup']
|
|
95
|
+
resources['AWS::AutoScaling::AutoScalingGroup'].keys.each do |asg_name|
|
|
96
|
+
resources[:asg_instances][asg_name] = CfDeployer::Driver::AutoScalingGroup.new(asg_name).instance_statuses
|
|
97
97
|
end
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
end
|
|
99
|
+
if resources['AWS::EC2::Instance']
|
|
100
|
+
resources['AWS::EC2::Instance'].keys.each do |instance_id|
|
|
101
|
+
resources[:instances][instance_id] = CfDeployer::Driver::Instance.new(instance_id).status
|
|
102
102
|
end
|
|
103
|
-
resources
|
|
104
103
|
end
|
|
104
|
+
resources
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def name
|
|
@@ -114,10 +114,6 @@ module CfDeployer
|
|
|
114
114
|
|
|
115
115
|
private
|
|
116
116
|
|
|
117
|
-
def to_str(hash)
|
|
118
|
-
hash.each { |k,v| hash[k] = v.to_s }
|
|
119
|
-
end
|
|
120
|
-
|
|
121
117
|
def update_stack(template, params, capabilities, tags, override_policy_json)
|
|
122
118
|
Log.info "Updating stack #{@stack_name}..."
|
|
123
119
|
args = {
|
|
@@ -136,7 +132,7 @@ module CfDeployer
|
|
|
136
132
|
args = {
|
|
137
133
|
:disable_rollback => true,
|
|
138
134
|
:capabilities => capabilities,
|
|
139
|
-
:
|
|
135
|
+
:notification_arns => notify,
|
|
140
136
|
:tags => reformat_tags(tags),
|
|
141
137
|
:parameters => params
|
|
142
138
|
}
|
|
@@ -148,7 +144,7 @@ module CfDeployer
|
|
|
148
144
|
end
|
|
149
145
|
|
|
150
146
|
def stack_status
|
|
151
|
-
@cf_driver.stack_status
|
|
147
|
+
@cf_driver.stack_status || :does_not_exist
|
|
152
148
|
end
|
|
153
149
|
|
|
154
150
|
def wait_for_stack_op_terminate
|
|
@@ -164,22 +160,14 @@ module CfDeployer
|
|
|
164
160
|
def wait_for_stack_to_delete
|
|
165
161
|
Timeout::timeout(900){
|
|
166
162
|
while exists?
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
sleep 15
|
|
170
|
-
rescue AWS::CloudFormation::Errors::ValidationError => e
|
|
171
|
-
if e.message =~ /does not exist/
|
|
172
|
-
break # This is what we wanted anyways
|
|
173
|
-
else
|
|
174
|
-
raise e
|
|
175
|
-
end
|
|
176
|
-
end
|
|
163
|
+
Log.info "current status: #{stack_status}"
|
|
164
|
+
sleep 15
|
|
177
165
|
end
|
|
178
166
|
}
|
|
179
167
|
end
|
|
180
168
|
|
|
181
169
|
def reformat_tags tags_hash
|
|
182
|
-
tags_hash.keys.map { |key| {
|
|
170
|
+
tags_hash.keys.map { |key| { :key => key.to_s, :value => tags_hash[key].to_s } }
|
|
183
171
|
end
|
|
184
172
|
end
|
|
185
173
|
end
|
data/lib/cf_deployer/version.rb
CHANGED
data/lib/cf_deployer.rb
CHANGED
|
@@ -2,11 +2,17 @@ require 'digest'
|
|
|
2
2
|
require 'set'
|
|
3
3
|
require 'time'
|
|
4
4
|
require 'json'
|
|
5
|
+
require 'yaml'
|
|
5
6
|
require 'timeout'
|
|
6
|
-
require 'aws-sdk'
|
|
7
|
+
require 'aws-sdk-autoscaling'
|
|
8
|
+
require 'aws-sdk-ec2'
|
|
9
|
+
require 'aws-sdk-core'
|
|
10
|
+
require 'aws-sdk-cloudformation'
|
|
11
|
+
require 'aws-sdk-elasticloadbalancing'
|
|
12
|
+
require 'aws-sdk-route53'
|
|
7
13
|
require 'erb'
|
|
8
14
|
require 'fileutils'
|
|
9
|
-
require '
|
|
15
|
+
require 'logger'
|
|
10
16
|
require 'pp'
|
|
11
17
|
require 'forwardable'
|
|
12
18
|
|
|
@@ -38,7 +44,7 @@ require_relative 'cf_deployer/defaults'
|
|
|
38
44
|
|
|
39
45
|
module CfDeployer
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
Aws.config.update(retry_limit: 20)
|
|
42
48
|
|
|
43
49
|
def self.config opts
|
|
44
50
|
config = self.parseconfig opts, false
|
|
@@ -50,19 +56,16 @@ module CfDeployer
|
|
|
50
56
|
|
|
51
57
|
def self.deploy opts
|
|
52
58
|
config = self.parseconfig opts
|
|
53
|
-
# AWS.config(:logger => Logger.new($stdout))
|
|
54
59
|
Application.new(config).deploy
|
|
55
60
|
end
|
|
56
61
|
|
|
57
62
|
def self.runhook opts
|
|
58
63
|
config = self.parseconfig opts
|
|
59
|
-
# AWS.config(:logger => Logger.new($stdout))
|
|
60
64
|
Application.new(config).run_hook opts[:component].first, opts[:hook_name]
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
def self.destroy opts
|
|
64
68
|
config = self.parseconfig opts, false
|
|
65
|
-
# AWS.config(:logger => Logger.new($stdout))
|
|
66
69
|
Application.new(config).destroy
|
|
67
70
|
end
|
|
68
71
|
|
|
@@ -97,7 +100,7 @@ module CfDeployer
|
|
|
97
100
|
private
|
|
98
101
|
|
|
99
102
|
def self.parseconfig options, validate_inputs = true
|
|
100
|
-
|
|
103
|
+
Aws.config.update({region: options[:region]}) if options[:region]
|
|
101
104
|
options[:cli_overrides] = {:settings => options.delete(:settings), :inputs => options.delete(:inputs)}
|
|
102
105
|
config = ConfigLoader.new.load options
|
|
103
106
|
ConfigValidation.new.validate config, validate_inputs
|
|
@@ -11,8 +11,20 @@ def override_aws_environment options = {}
|
|
|
11
11
|
options[:AWS_REGION] ||= 'us-east-1'
|
|
12
12
|
options[:AWS_ACCESS_KEY_ID] ||= 'someId'
|
|
13
13
|
options[:AWS_SECRET_ACCESS_KEY] ||= 'secretKey'
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
options[:AWS_EC2_METADATA_DISABLED] ||= 'true'
|
|
15
|
+
options[:AWS_SDK_CONFIG_OPT_OUT] ||= '1'
|
|
16
|
+
|
|
17
|
+
override_environment_variables(options) do
|
|
18
|
+
previous_aws_config = Aws.config.dup
|
|
19
|
+
Aws.config.update(
|
|
20
|
+
region: options[:AWS_REGION],
|
|
21
|
+
credentials: Aws::Credentials.new(options[:AWS_ACCESS_KEY_ID], options[:AWS_SECRET_ACCESS_KEY])
|
|
22
|
+
)
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
Aws.config.clear
|
|
26
|
+
Aws.config.update(previous_aws_config)
|
|
27
|
+
end
|
|
16
28
|
end
|
|
17
29
|
|
|
18
30
|
def override_environment_variables options = {}
|
data/spec/fakes/instance.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module Fakes
|
|
2
2
|
class Instance
|
|
3
3
|
|
|
4
|
-
SIMPLE_ATTRIBS = [ :id, :
|
|
4
|
+
SIMPLE_ATTRIBS = [ :id, :public_ip_address, :private_ip_address, :image_id ]
|
|
5
5
|
|
|
6
6
|
attr_reader *SIMPLE_ATTRIBS
|
|
7
|
-
attr_reader :
|
|
7
|
+
attr_reader :state, :key_name
|
|
8
8
|
|
|
9
9
|
def initialize(options)
|
|
10
10
|
defaults = {
|
|
@@ -20,7 +20,8 @@ module Fakes
|
|
|
20
20
|
instance_variable_set "@#{attrib}", (options[attrib] || defaults[attrib])
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
@
|
|
23
|
+
@state = Aws::EC2::Types::InstanceState.new(name: (options[:status] || defaults[:status]).to_s)
|
|
24
|
+
@key_name = options[:key_pair] || defaults[:key_pair]
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def inspect
|
|
@@ -29,4 +30,4 @@ module Fakes
|
|
|
29
30
|
alias_method :to_s, :inspect
|
|
30
31
|
|
|
31
32
|
end
|
|
32
|
-
end
|
|
33
|
+
end
|
|
@@ -2,19 +2,16 @@ module Fakes
|
|
|
2
2
|
class AWSRoute53
|
|
3
3
|
attr_reader :fail_counter, :hosted_zones, :client
|
|
4
4
|
def initialize(opts = {})
|
|
5
|
-
@client = AWSRoute53Client.new(opts)
|
|
6
5
|
@hosted_zones = opts[:hosted_zones]
|
|
7
|
-
@fail_counter = 0
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
6
|
|
|
11
|
-
class AWSRoute53Client
|
|
12
|
-
attr_reader :fail_counter
|
|
13
|
-
def initialize(opts = {})
|
|
14
7
|
@times_to_fail = opts[:times_to_fail]
|
|
15
8
|
@fail_counter = 0
|
|
16
9
|
end
|
|
17
10
|
|
|
11
|
+
def list_hosted_zones_by_name(*args)
|
|
12
|
+
Struct.new(:hosted_zones).new(hosted_zones)
|
|
13
|
+
end
|
|
14
|
+
|
|
18
15
|
def change_resource_record_sets(*args)
|
|
19
16
|
@fail_counter = @fail_counter + 1
|
|
20
17
|
raise 'Error' if @fail_counter <= @times_to_fail
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
require_relative '../lib/cf_deployer'
|
|
2
2
|
Dir.glob("#{File.dirname File.absolute_path(__FILE__)}/fakes/*.rb") { |file| require file }
|
|
3
3
|
|
|
4
|
-
CfDeployer::Log.log
|
|
4
|
+
CfDeployer::Log.log = Logger.new(File::NULL)
|
|
5
5
|
|
|
6
6
|
RSPEC_LOG = Logger.new(STDOUT)
|
|
7
7
|
RSPEC_LOG.level = Logger::WARN
|
|
8
8
|
|
|
9
9
|
if ENV['DEBUG']
|
|
10
10
|
RSPEC_LOG.level = Logger::DEBUG
|
|
11
|
-
AWS.config :logger => RSPEC_LOG
|
|
11
|
+
# AWS.config :logger => RSPEC_LOG
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def puts *args
|
|
@@ -20,3 +20,12 @@ def ignore_errors
|
|
|
20
20
|
rescue => e
|
|
21
21
|
RSPEC_LOG.debug "Intentionally ignoring error: #{e.message}"
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
RSpec.configure do |config|
|
|
25
|
+
# These two settings work together to allow you to limit a spec run
|
|
26
|
+
# to individual examples or groups you care about by tagging them with
|
|
27
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
|
28
|
+
# get run.
|
|
29
|
+
config.filter_run :focus
|
|
30
|
+
config.run_all_when_everything_filtered = true
|
|
31
|
+
end
|
|
@@ -28,11 +28,11 @@ describe "application" do
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
it "application should get all components" do
|
|
31
|
-
@app.components.length.
|
|
32
|
-
@base.
|
|
33
|
-
@db.
|
|
34
|
-
@queue.
|
|
35
|
-
@web.
|
|
31
|
+
expect(@app.components.length).to eq(4)
|
|
32
|
+
expect(@base).not_to be_nil
|
|
33
|
+
expect(@db).not_to be_nil
|
|
34
|
+
expect(@queue).not_to be_nil
|
|
35
|
+
expect(@web).not_to be_nil
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
context "order components by dependencies" do
|
|
@@ -154,7 +154,7 @@ describe "application" do
|
|
|
154
154
|
context "deploy all components" do
|
|
155
155
|
it "should deploy all components if no component specified" do
|
|
156
156
|
@app.deploy
|
|
157
|
-
@log.
|
|
157
|
+
expect(@log).to eq("base db queue web ")
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
|
|
@@ -162,7 +162,7 @@ describe "application" do
|
|
|
162
162
|
it "should deploy specified components" do
|
|
163
163
|
@context[:targets] = ['web', 'db']
|
|
164
164
|
@app.deploy
|
|
165
|
-
@log.
|
|
165
|
+
expect(@log).to eq("db web ")
|
|
166
166
|
end
|
|
167
167
|
end
|
|
168
168
|
end
|
|
@@ -179,13 +179,13 @@ describe "application" do
|
|
|
179
179
|
|
|
180
180
|
it 'should get json templates for all components' do
|
|
181
181
|
@app.json
|
|
182
|
-
@log.
|
|
182
|
+
expect(@log).to eq("base db queue web ")
|
|
183
183
|
end
|
|
184
184
|
|
|
185
185
|
it 'should get json templates for components specified' do
|
|
186
186
|
@context[:targets] = ['web', 'db']
|
|
187
187
|
@app.json
|
|
188
|
-
@log.
|
|
188
|
+
expect(@log).to eq('db web ')
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
191
|
end
|
data/spec/unit/component_spec.rb
CHANGED
|
@@ -98,7 +98,7 @@ describe "component" do
|
|
|
98
98
|
|
|
99
99
|
it "should ask strategy if component exists" do
|
|
100
100
|
expect(@strategy).to receive(:exists?){ true }
|
|
101
|
-
@web.exists
|
|
101
|
+
expect(@web.exists?).to eq(true)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
it "should find direct dependencies" do
|
|
@@ -106,7 +106,7 @@ describe "component" do
|
|
|
106
106
|
base = CfDeployer::Component.new('myApp', 'uat', 'base', {})
|
|
107
107
|
web.dependencies << base
|
|
108
108
|
|
|
109
|
-
web.depends_on?(base).
|
|
109
|
+
expect(web.depends_on?(base)).to eq(true)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
it "should find transitive dependencies" do
|
|
@@ -117,7 +117,7 @@ describe "component" do
|
|
|
117
117
|
haproxy.dependencies << base
|
|
118
118
|
web.dependencies << haproxy
|
|
119
119
|
|
|
120
|
-
web.depends_on?(base).
|
|
120
|
+
expect(web.depends_on?(base)).to eq(true)
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
it "should find cyclic dependency" do
|