cfn-flow 0.2.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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