cfn-flow 0.2.1 → 0.5.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.
@@ -0,0 +1,58 @@
1
+ require_relative '../helper'
2
+
3
+ describe 'EventPresenter' do
4
+
5
+ subject { CfnFlow::EventPresenter }
6
+ after { CfnFlow::EventPresenter.seen_event_ids.clear }
7
+
8
+ let(:event) { stub_event }
9
+ let(:event_with_reason) { stub_event(resource_status_reason: 'stubbed-reason') }
10
+
11
+ describe '.seen_event_ids' do
12
+ it 'should be a set' do
13
+ subject.seen_event_ids.must_be_kind_of Set
14
+ end
15
+ end
16
+
17
+ describe '.present' do
18
+ it 'should present the right number of events' do
19
+ events = [event, event_with_reason]
20
+ result = subject.present(events) {|e| e}
21
+
22
+ result.size.must_equal 2
23
+ result.each {|e| e.must_be_kind_of CfnFlow::EventPresenter }
24
+ end
25
+
26
+ it 'should omit dupe events' do
27
+ subject.present([event]) {}
28
+ subject.present([event]) {}.must_equal []
29
+ end
30
+
31
+ it 'should render the status' do
32
+ out, _ = capture_io do
33
+ subject.present([event]) { |e| puts e.to_s }
34
+ end
35
+
36
+ out.must_match event.resource_status
37
+ end
38
+ end
39
+
40
+ describe '#initialize' do
41
+ it 'should add to .seen_event_ids' do
42
+ subject.new(event)
43
+ subject.seen_event_ids.include?(event.id).must_equal true
44
+ end
45
+ end
46
+
47
+ describe '#to_s' do
48
+ it 'should show the appropriate details' do
49
+ str = subject.new(event).to_s
50
+ str.must_match event.logical_resource_id
51
+ end
52
+
53
+ it 'should show a reason' do
54
+ str = subject.new(event_with_reason).to_s
55
+ str.must_match event_with_reason.resource_status_reason
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,140 @@
1
+ require_relative '../helper'
2
+
3
+ describe 'CfnFlow::Template' do
4
+
5
+ let(:template) {
6
+ CfnFlow::Template.new('spec/data/sqs.template')
7
+ }
8
+
9
+ let(:yml_template) {
10
+ CfnFlow::Template.new('spec/data/sqs.yml')
11
+ }
12
+
13
+ let(:not_a_template) {
14
+ CfnFlow::Template.new('spec/data/cfn-flow.yml')
15
+ }
16
+
17
+ let(:release) { 'deadbeef' }
18
+
19
+ describe '#initialize' do
20
+ subject { CfnFlow::Template }
21
+
22
+ it('succeeds') do
23
+ subject.new('f').must_be_kind_of CfnFlow::Template
24
+ end
25
+
26
+ it('requires args') do
27
+ -> { subject.new }.must_raise(ArgumentError)
28
+ end
29
+ end
30
+
31
+ describe '#yaml?' do
32
+ it 'works' do
33
+ yml_template.yaml?.must_equal true
34
+ yml_template.json?.must_equal false
35
+ template.yaml?.must_equal false
36
+ template.json?.must_equal true
37
+ end
38
+ end
39
+
40
+ describe '#is_cfn_template?' do
41
+ it 'works' do
42
+ yml_template.is_cfn_template?.must_equal true
43
+ template.is_cfn_template?.must_equal true
44
+ not_a_template.is_cfn_template?.must_equal false
45
+ end
46
+ end
47
+
48
+ describe '#bucket' do
49
+ it 'uses CfnFlow.template_s3_bucket' do
50
+ template.bucket.must_equal CfnFlow.template_s3_bucket
51
+ end
52
+ it 'has the correct value' do
53
+ template.bucket.must_equal 'test-bucket'
54
+ end
55
+ end
56
+
57
+ describe '#s3_prefix' do
58
+ it 'uses CfnFlow.template_s3_prefix' do
59
+ template.s3_prefix.must_equal CfnFlow.template_s3_prefix
60
+ end
61
+ it 'has the correct value' do
62
+ template.s3_prefix.must_equal 'test-prefix'
63
+ end
64
+ end
65
+
66
+ describe '#key' do
67
+ it 'has the correct value' do
68
+ expected = File.join(template.s3_prefix, release, template.local_path)
69
+ template.key(release).must_equal expected
70
+ end
71
+
72
+ it "removes leading './'" do
73
+ CfnFlow::Template.new('./foo').key(release).must_equal "test-prefix/#{release}/foo"
74
+ end
75
+
76
+ it "can have a empty s3_prefix" do
77
+ CfnFlow.instance_variable_set(:@config, {'templates' => {'s3_bucket' => 'foo'}})
78
+ expected = File.join(release, template.local_path)
79
+ template.key(release).must_equal expected
80
+ end
81
+ end
82
+
83
+ describe '#s3_object' do
84
+ it 'is an S3::Object' do
85
+ subject = template.s3_object(release)
86
+ subject.must_be_kind_of Aws::S3::Object
87
+ subject.bucket.name.must_equal template.bucket
88
+ subject.key.must_equal template.key(release)
89
+ end
90
+ end
91
+
92
+ describe '#url' do
93
+ it 'is the correct S3 url' do
94
+ uri = URI.parse(template.url(release))
95
+ uri.scheme.must_equal 'https'
96
+ uri.host.must_match(/\A#{template.bucket}\.s3\..+\.amazonaws\.com\z/)
97
+ uri.path.must_equal('/' + template.key(release))
98
+ end
99
+ end
100
+
101
+ describe '#upload' do
102
+ it 'succeeds' do
103
+ template.upload(release)
104
+ end
105
+ end
106
+
107
+ describe '#local_data' do
108
+ it 'should read valid data' do
109
+ template.local_data.must_be_kind_of Hash
110
+ template.local_data.must_be_kind_of Hash
111
+ end
112
+
113
+ it 'should raise an error on invalid json data' do
114
+ -> { CfnFlow::Template.new('spec/data/invalid.json').local_data }.must_raise CfnFlow::Template::Error
115
+ end
116
+
117
+ it 'should raise an error on invalid YAML data' do
118
+ -> { CfnFlow::Template.new('spec/data/invalid.yml').local_data }.must_raise CfnFlow::Template::Error
119
+ end
120
+ it 'should raise an on a missing file' do
121
+ -> { CfnFlow::Template.new('no/such/file').local_data }.must_raise CfnFlow::Template::Error
122
+ end
123
+ end
124
+
125
+ describe '#to_json' do
126
+ it 'should work' do
127
+ template.to_json.must_equal MultiJson.dump(template.local_data, pretty: true)
128
+ end
129
+ end
130
+
131
+ describe '#validate!' do
132
+ it 'succeeds' do
133
+ template.validate!
134
+ end
135
+ it 'can raise an error' do
136
+ Aws.config[:cloudformation] = {stub_responses: {validate_template: 'ValidationError'}}
137
+ -> { template.validate! }.must_raise Aws::CloudFormation::Errors::ValidationError
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,179 @@
1
+ require_relative 'helper'
2
+
3
+ describe 'CfnFlow' do
4
+ subject { CfnFlow }
5
+
6
+ describe '.config_path' do
7
+ it 'should be ./cfn-flow.yml by default' do
8
+ ENV.delete('CFN_FLOW_CONFIG_PATH')
9
+ subject.config_path.must_equal 'cfn-flow.yml'
10
+ end
11
+
12
+ it 'can be overridden with ENV[CFN_FLOW_CONFIG_PATH]' do
13
+ ENV['CFN_FLOW_CONFIG_PATH'] = 'foo/bar'
14
+ subject.config_path.must_equal 'foo/bar'
15
+ end
16
+ end
17
+
18
+ describe '.config_loaded?' do
19
+ it 'should be false by default' do
20
+ subject.config_loaded?.must_equal false
21
+ end
22
+
23
+ it 'should be true after loading' do
24
+ subject.load_config
25
+ subject.config_loaded?.must_equal true
26
+ end
27
+ end
28
+
29
+ describe '.config' do
30
+ it('should be a hash') { subject.config.must_be_kind_of(Hash) }
31
+ end
32
+
33
+ describe '.service' do
34
+ it('raises an error when missing') do
35
+ subject.instance_variable_set(:@config, {})
36
+ error = -> { subject.service }.must_raise(Thor::Error)
37
+ error.message.must_match 'No service name'
38
+ end
39
+
40
+ it('returns the service') do
41
+ subject.instance_variable_set(:@config, {'service' => 'RoflScaler'})
42
+ subject.service.must_equal 'RoflScaler'
43
+ end
44
+ end
45
+
46
+ describe '.stack_params' do
47
+ it('raises an error when missing') do
48
+ subject.instance_variable_set(:@config, {})
49
+ error = -> { subject.stack_params('env') }.must_raise(Thor::Error)
50
+ error.message.must_match 'No stack defined'
51
+ end
52
+
53
+ it('expands parameters') do
54
+ stack = {'parameters' => {'ami' => 'ami-12345' } }
55
+ subject.instance_variable_set(:@config, {'service' => 'myservice', 'stack' => stack})
56
+ subject.stack_params('env')[:parameters].must_equal [ { parameter_key: 'ami', parameter_value: 'ami-12345' } ]
57
+ end
58
+
59
+ it('expands tags') do
60
+ stack = {'tags' => {'Deployer' => 'Aaron' } }
61
+ subject.instance_variable_set(:@config, {'service' => 'myservice', 'stack' => stack})
62
+ expected = [
63
+ { key: 'Deployer', value: 'Aaron' },
64
+ { key: 'CfnFlowService', value: 'myservice' },
65
+ { key: 'CfnFlowEnvironment', value: 'env' }
66
+ ]
67
+
68
+ subject.stack_params('env')[:tags].must_equal expected
69
+ end
70
+
71
+ it 'appends CfnFlow tags' do
72
+ subject.instance_variable_set(:@config, {'service' => 'myservice', 'stack' => {}})
73
+ expected = [
74
+ { key: 'CfnFlowService', value: 'myservice' },
75
+ { key: 'CfnFlowEnvironment', value: 'env' }
76
+ ]
77
+
78
+ subject.stack_params('env')[:tags].must_equal expected
79
+ end
80
+
81
+ it 'expands template body' do
82
+ template_path = 'spec/data/sqs.template'
83
+ stack = {'template_body' => template_path}
84
+ subject.instance_variable_set(:@config, {'service' => 'myservice', 'stack' => stack})
85
+ subject.stack_params('env')[:template_body].must_equal CfnFlow::Template.new(template_path).to_json
86
+ end
87
+
88
+ end
89
+
90
+ describe '.template_s3_bucket' do
91
+ it('raises an error when missing') do
92
+ subject.instance_variable_set(:@config, {})
93
+ error = -> { subject.template_s3_bucket }.must_raise(Thor::Error)
94
+ error.message.must_match 'No s3_bucket defined'
95
+
96
+ subject.instance_variable_set(:@config, {'templates' => {}})
97
+ error = -> { subject.template_s3_bucket }.must_raise(Thor::Error)
98
+ error.message.must_match 'No s3_bucket defined'
99
+ end
100
+
101
+ it 'succeeds' do
102
+ subject.instance_variable_set(:@config, {'templates' => {'s3_bucket' => 'hello'}})
103
+ subject.template_s3_bucket.must_equal 'hello'
104
+ end
105
+ end
106
+
107
+ describe '.template_s3_prefix' do
108
+ it('raises an error when missing') do
109
+ subject.instance_variable_set(:@config, {})
110
+ error = -> { subject.template_s3_prefix }.must_raise(Thor::Error)
111
+ error.message.must_match 'No templates defined'
112
+ end
113
+
114
+ it 'succeeds' do
115
+ subject.instance_variable_set(:@config, {'templates' => {'s3_prefix' => 'hello'}})
116
+ subject.template_s3_prefix.must_equal 'hello'
117
+ end
118
+
119
+ it 'can be nil' do
120
+ subject.instance_variable_set(:@config, {'templates' => {}})
121
+ subject.template_s3_prefix.must_equal nil
122
+ end
123
+ end
124
+
125
+ describe '.cfn_client' do
126
+ it 'should work' do
127
+ subject.cfn_client.must_be_kind_of Aws::CloudFormation::Client
128
+ end
129
+
130
+ describe 'aws region' do
131
+ it 'should default to the env region' do
132
+ ENV['AWS_REGION'] = 'env-region'
133
+ subject.cfn_client.config.region.must_equal 'env-region'
134
+ end
135
+
136
+ it 'can be overridden with config' do
137
+ ENV['AWS_REGION'] = 'env-region'
138
+ subject.instance_variable_set(:@config, {region: 'config-region' })
139
+ subject.cfn_client.config.region.must_equal 'config-region'
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ describe '.cfn_resource' do
146
+ it 'should work' do
147
+ subject.cfn_resource.must_be_kind_of Aws::CloudFormation::Resource
148
+ end
149
+
150
+ it 'should set a retry_limit' do
151
+ subject.cfn_resource.client.config.retry_limit.must_equal 10
152
+ end
153
+
154
+ describe 'aws region' do
155
+ it 'should default to the env region' do
156
+ ENV['AWS_REGION'] = 'env-region'
157
+ subject.cfn_client.config.region.must_equal 'env-region'
158
+ end
159
+
160
+ it 'can be overridden with config' do
161
+ ENV['AWS_REGION'] = 'env-region'
162
+ subject.instance_variable_set(:@config, {region: 'config-region' })
163
+ subject.cfn_client.config.region.must_equal 'config-region'
164
+ end
165
+ end
166
+ end
167
+
168
+ describe '.exit_on_failure?' do
169
+ it 'is true by default' do
170
+ CfnFlow.remove_instance_variable(:@exit_on_failure) if CfnFlow.instance_variable_defined?(:@exit_on_failure)
171
+ CfnFlow.exit_on_failure?.must_equal true
172
+ end
173
+
174
+ it 'can be set' do
175
+ CfnFlow.exit_on_failure = false
176
+ CfnFlow.exit_on_failure?.must_equal false
177
+ end
178
+ end
179
+ end
@@ -1,6 +1,14 @@
1
1
  ---
2
- bucket: test-bucket
3
- prefix: test-prefix
4
- from: spec/data
5
- to: test-to
2
+ service: cfn-flow-specs
6
3
  region: us-east-1
4
+ templates:
5
+ s3_bucket: test-bucket
6
+ s3_prefix: test-prefix
7
+
8
+ stack:
9
+ stack_name: cfn-flow-spec-stack
10
+ template_body: sqs.yml
11
+ parameters:
12
+ AlarmEMail: test@example.com
13
+ tags:
14
+ test_tag: hello world
@@ -0,0 +1 @@
1
+ {
@@ -0,0 +1,3 @@
1
+ ---
2
+ foo:
3
+ bar
data/spec/helper.rb CHANGED
@@ -16,23 +16,56 @@ ENV['AWS_REGION'] = 'us-east-1'
16
16
  ENV['AWS_ACCESS_KEY_ID'] = 'test-key'
17
17
  ENV['AWS_SECRET_ACCESS_KEY'] = 'test-secret'
18
18
  ENV['CFN_FLOW_DEV_NAME'] = 'cfn-flow-specs'
19
+ ENV['CFN_FLOW_CONFIG_PATH'] = 'spec/data/cfn-flow.yml'
20
+ ENV['CFN_FLOW_EVENT_POLL_INTERVAL'] = '0'
19
21
 
20
22
  class Minitest::Spec
21
- # From http://git.io/bcfh
22
- def capture(stream = :stdout)
23
- begin
24
- stream = stream.to_s
25
- eval "$#{stream} = StringIO.new"
26
- yield
27
- result = eval("$#{stream}").string
28
- ensure
29
- eval("$#{stream} = #{stream.upcase}")
30
- end
31
-
32
- result
23
+ before do
24
+ # Reset env between tests:
25
+ @orig_env = ENV.to_hash
26
+
27
+ # Disable exit on failure so CLI tests don't bomb out
28
+ CfnFlow.exit_on_failure = false
29
+ end
30
+
31
+ after do
32
+ # Reset env
33
+ ENV.clear
34
+ ENV.update(@orig_env)
35
+
36
+ # Reset stubs
37
+ CfnFlow.clear!
38
+ Aws.config.delete(:cloudformation)
39
+ end
40
+
41
+ def stub_stack_data(attrs = {})
42
+ {
43
+ stack_name: "mystack",
44
+ stack_status: 'CREATE_COMPLETE',
45
+ creation_time: Time.now,
46
+ tags: [
47
+ {key: 'CfnFlowService', value: CfnFlow.service},
48
+ {key: 'CfnFlowEnvironment', value: 'production'}
49
+ ]
50
+ }.merge(attrs)
51
+ end
52
+
53
+ def stub_event_data(attrs = {})
54
+ {
55
+ stack_id: 'mystack',
56
+ stack_name: 'mystack',
57
+ event_id: SecureRandom.hex,
58
+ resource_status: 'CREATE_COMPLETE',
59
+ logical_resource_id: 'stubbed-resource-id',
60
+ resource_type: 'stubbed-resource-type',
61
+ timestamp: Time.now
62
+ }.merge(attrs)
63
+ end
64
+
65
+ def stub_event(attrs = {})
66
+ data = stub_event_data(attrs)
67
+ id = data.delete(:event_id)
68
+ Aws::CloudFormation::Event.new(id: id, data: data)
33
69
  end
34
70
 
35
- # Reset env between tests
36
- before { @orig_env = ENV.to_hash }
37
- after { ENV.clear; ENV.update(@orig_env) }
38
71
  end