formatron 0.1.11 → 0.1.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2901f1a146d9b969d04f487d57f0ff26a85db1b
4
- data.tar.gz: 154a154b9e9f920fcccb41add3563dddbb6f4b18
3
+ metadata.gz: 2c47341bd39e30cd27955cba1ae8bab5a79135e6
4
+ data.tar.gz: 69129c0a0edd68e080e37af928954c4a0ecaae34
5
5
  SHA512:
6
- metadata.gz: 0a120f2f78037c737316853b5aedf94baed0d0e6dadc90ed55a636e3a165c94e6992f5b37fa68f12408d61f9fe7b69c23d6a0e295d924888a86a47deb47988d0
7
- data.tar.gz: 8389d7122ed58e0ba66408c674da5e9a6123fbb1c255a78502ea2b706741039f99e9e491f687612e8a80620ba8203377daf78b15dda79411b33b04a0aa38119b
6
+ metadata.gz: a6f0aeebee3996040189a535ddf6144a62f8eb6f6930e2167b7bfabff77cf00e46a8858a2061ffc7ca106586a5c67d303d2ffa2ce53ea5e1096296cbc40e7e8d
7
+ data.tar.gz: a2dac2aa24c3d8587c654b4d0556f0a60476a47acd36be9c60f82545531f297a1324709ce5b02aeaa4f311c40085eca4840b47e1208b5f7a46a7918ccec8c607
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Formatron
2
2
 
3
3
  [![Build Status](https://travis-ci.org/formatron/formatron.svg?branch=master)](https://travis-ci.org/formatron/formatron?branch=master)
4
- [![Coverage Status](https://coveralls.io/repos/pghalliday/formatron/badge.svg?branch=master&service=github)](https://coveralls.io/github/pghalliday/formatron?branch=master)
5
- [![Dependency Status](https://gemnasium.com/pghalliday/formatron.svg)](https://gemnasium.com/pghalliday/formatron)
4
+ [![Coverage Status](https://coveralls.io/repos/formatron/formatron/badge.svg?branch=master&service=github)](https://coveralls.io/github/formatron/formatron?branch=master)
5
+ [![Dependency Status](https://gemnasium.com/formatron/formatron.svg)](https://gemnasium.com/formatron/formatron)
6
6
 
7
7
  Simple AWS CloudFormation configuration with Chef Server
8
8
 
@@ -49,4 +49,5 @@ EOH
49
49
  spec.add_runtime_dependency 'deep_merge', '~> 1.0'
50
50
  spec.add_runtime_dependency 'berkshelf', '~> 4.0'
51
51
  spec.add_runtime_dependency 'chef', '~> 12.5'
52
+ spec.add_runtime_dependency 'net-ssh', '~> 2.9'
52
53
  end
@@ -1,4 +1,5 @@
1
1
  require 'aws-sdk'
2
+ require_relative 'aws/cloud_formation_stack'
2
3
 
3
4
  class Formatron
4
5
  # shared AWS clients
@@ -102,19 +103,21 @@ class Formatron
102
103
  use_previous_value: false
103
104
  }
104
105
  end
105
- @cloudformation_client.create_stack(
106
+ cloud_formation_stack = Formatron::AWS::CloudFormationStack.new(
106
107
  stack_name: stack_name,
107
- template_url: template_url,
108
- capabilities: CAPABILITIES,
109
- on_failure: 'DO_NOTHING',
110
- parameters: aws_parameters
111
- )
112
- rescue Aws::CloudFormation::Errors::AlreadyExistsException
113
- _update_stack(
114
- stack_name: stack_name,
115
- template_url: template_url,
116
- parameters: aws_parameters
108
+ client: @cloudformation_client
117
109
  )
110
+ if !cloud_formation_stack.exists?
111
+ cloud_formation_stack.create(
112
+ template_url: template_url,
113
+ parameters: aws_parameters
114
+ )
115
+ else
116
+ cloud_formation_stack.update(
117
+ template_url: template_url,
118
+ parameters: aws_parameters
119
+ )
120
+ end
118
121
  end
119
122
  # rubocop:enable Metrics/MethodLength
120
123
 
@@ -124,23 +127,12 @@ class Formatron
124
127
  ).hosted_zone.name.chomp '.'
125
128
  end
126
129
 
127
- def _update_stack(stack_name:, template_url:, parameters:)
128
- @cloudformation_client.update_stack(
129
- stack_name: stack_name,
130
- template_url: template_url,
131
- capabilities: CAPABILITIES,
132
- parameters: parameters
133
- )
134
- rescue Aws::CloudFormation::Errors::ValidationError => error
135
- raise error unless error.message.eql?(
136
- 'No updates are to be performed.'
137
- )
138
- end
139
-
140
130
  def delete_stack(stack_name:)
141
- @cloudformation_client.delete_stack(
142
- stack_name: stack_name
131
+ cloud_formation_stack = Formatron::AWS::CloudFormationStack.new(
132
+ stack_name: stack_name,
133
+ client: @cloudformation_client
143
134
  )
135
+ cloud_formation_stack.delete if cloud_formation_stack.exists?
144
136
  end
145
137
 
146
138
  def stack_outputs(stack_name:)
@@ -196,8 +188,7 @@ class Formatron
196
188
  :_create_aws_credentials,
197
189
  :_create_s3_client,
198
190
  :_create_cloudformation_client,
199
- :_create_route53_client,
200
- :_update_stack
191
+ :_create_route53_client
201
192
  )
202
193
  end
203
194
  # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,144 @@
1
+ require 'aws-sdk'
2
+
3
+ class Formatron
4
+ class AWS
5
+ # utilities for monitoring CloudFormation stack activities
6
+ # rubocop:disable Metrics/ClassLength
7
+ class CloudFormationStack
8
+ CAPABILITIES = %w(CAPABILITY_IAM)
9
+
10
+ CREATE_COMPLETE_STATUS = 'CREATE_COMPLETE'
11
+ CREATE_FINAL_STATUSES = %W(
12
+ #{CREATE_COMPLETE_STATUS}
13
+ CREATE_FAILED
14
+ ROLLBACK_COMPLETE
15
+ ROLLBACK_FAILED
16
+ )
17
+
18
+ UPDATE_COMPLETE_STATUS = 'UPDATE_COMPLETE'
19
+ UPDATE_FINAL_STATUSES = %W(
20
+ #{UPDATE_COMPLETE_STATUS}
21
+ UPDATE_ROLLBACK_COMPLETE
22
+ UPDATE_ROLLBACK_FAILED
23
+ )
24
+
25
+ DELETE_COMPLETE_STATUS = 'DELETE_COMPLETE'
26
+ DELETE_FINAL_STATUSES = %W(
27
+ #{DELETE_COMPLETE_STATUS}
28
+ DELETE_FAILED
29
+ )
30
+
31
+ def initialize(stack_name:, client:)
32
+ @stack_name = stack_name
33
+ @client = client
34
+ @stack = Aws::CloudFormation::Stack.new(
35
+ stack_name,
36
+ client: @client
37
+ )
38
+ end
39
+
40
+ def exists?
41
+ @stack.exists?
42
+ end
43
+
44
+ def create(template_url:, parameters:)
45
+ @client.create_stack(
46
+ stack_name: @stack_name,
47
+ template_url: template_url,
48
+ capabilities: CAPABILITIES,
49
+ on_failure: 'DO_NOTHING',
50
+ parameters: parameters
51
+ )
52
+ @stack.wait_until_exists
53
+ status = _wait_for_status statuses: CREATE_FINAL_STATUSES
54
+ fail status unless status.eql? CREATE_COMPLETE_STATUS
55
+ end
56
+
57
+ def update(template_url:, parameters:)
58
+ last_event_id = _last_event_id
59
+ return unless _update_unless_no_changes(
60
+ template_url: template_url,
61
+ parameters: parameters
62
+ )
63
+ status = _wait_for_status(
64
+ statuses: UPDATE_FINAL_STATUSES,
65
+ last_event_id: last_event_id
66
+ )
67
+ fail status unless status.eql? UPDATE_COMPLETE_STATUS
68
+ end
69
+
70
+ # rubocop:disable Metrics/MethodLength
71
+ def _update_unless_no_changes(template_url:, parameters:)
72
+ @stack.update(
73
+ template_url: template_url,
74
+ parameters: parameters,
75
+ capabilities: CAPABILITIES
76
+ )
77
+ true
78
+ rescue Aws::CloudFormation::Errors::ValidationError => error
79
+ raise error unless error.message.eql?(
80
+ 'No updates are to be performed.'
81
+ )
82
+ Formatron::LOG.info do
83
+ 'No updates are to be performed for CloudFormation stack'
84
+ end
85
+ false
86
+ end
87
+ # rubocop:enable Metrics/MethodLength
88
+
89
+ def delete
90
+ last_event_id = _last_event_id
91
+ @stack.delete
92
+ status = _wait_for_status(
93
+ statuses: DELETE_FINAL_STATUSES,
94
+ last_event_id: last_event_id
95
+ )
96
+ fail status unless status.eql? DELETE_COMPLETE_STATUS
97
+ end
98
+
99
+ def _last_event_id
100
+ @stack.events.each do |event|
101
+ return event.event_id
102
+ end
103
+ end
104
+
105
+ # rubocop:disable Metrics/MethodLength
106
+ # rubocop:disable Metrics/AbcSize
107
+ def _wait_for_status(statuses:, last_event_id: nil)
108
+ loop do
109
+ events = []
110
+ @stack.events.each do |event|
111
+ break if event.event_id.eql? last_event_id
112
+ events.push event
113
+ end
114
+ events.reverse!
115
+ events.each do |event|
116
+ status = event.resource_status
117
+ timestamp = event.timestamp
118
+ type = event.resource_type
119
+ logical_id = event.logical_resource_id
120
+ reason = event.resource_status_reason
121
+ Formatron::LOG.info do
122
+ "#{timestamp} - #{status} - #{type} - #{logical_id} - #{reason}"
123
+ end
124
+ return status if
125
+ statuses.include?(status) &&
126
+ logical_id.eql?(@stack_name) &&
127
+ type.eql?('AWS::CloudFormation::Stack')
128
+ last_event_id = event.event_id
129
+ end
130
+ sleep 1
131
+ end
132
+ end
133
+ # rubocop:enable Metrics/AbcSize
134
+ # rubocop:enable Metrics/MethodLength
135
+
136
+ private(
137
+ :_update_unless_no_changes,
138
+ :_last_event_id,
139
+ :_wait_for_status
140
+ )
141
+ end
142
+ # rubocop:enable Metrics/ClassLength
143
+ end
144
+ end
@@ -1,4 +1,4 @@
1
1
  # add version to class
2
2
  class Formatron
3
- VERSION = '0.1.11'
3
+ VERSION = '0.1.12'
4
4
  end
@@ -0,0 +1,67 @@
1
+ require 'aws-sdk'
2
+
3
+ class Formatron
4
+ module Support
5
+ # Stub CloudFormation describe_stacks response class
6
+ class CloudformationStackEventsResponses
7
+ include RSpec::Mocks::ExampleMethods
8
+
9
+ attr_reader :responses
10
+
11
+ # rubocop:disable Metrics/MethodLength
12
+ def initialize(stack_name:, final_status:)
13
+ @next_event_id = 0
14
+ first_response = [
15
+ event,
16
+ event,
17
+ event
18
+ ]
19
+ second_response = [
20
+ event,
21
+ event,
22
+ event,
23
+ *first_response
24
+ ]
25
+ third_response = [
26
+ event(
27
+ logical_resource_id: stack_name,
28
+ resource_status: final_status,
29
+ resource_type: 'AWS::CloudFormation::Stack'
30
+ ),
31
+ event,
32
+ event,
33
+ *second_response
34
+ ]
35
+ @responses = [
36
+ first_response,
37
+ second_response,
38
+ third_response
39
+ ]
40
+ end
41
+ # rubocop:enable Metrics/MethodLength
42
+
43
+ # rubocop:disable Metrics/MethodLength
44
+ # rubocop:disable Metrics/AbcSize
45
+ def event(
46
+ logical_resource_id: 'logical_resource_id',
47
+ resource_status: 'resource_status',
48
+ resource_type: 'resource_type'
49
+ )
50
+ event = instance_double(Aws::CloudFormation::Event)
51
+ allow(event).to receive(:logical_resource_id) { logical_resource_id }
52
+ allow(event).to receive(:resource_status) { resource_status }
53
+ allow(event).to receive(:resource_type) { resource_type }
54
+ allow(event).to receive(:timestamp) { 'timestamp' }
55
+ allow(event).to receive(
56
+ :resource_status_reason
57
+ ) { 'resource_status_reason' }
58
+ allow(event).to receive(:event_id) do
59
+ @next_event_id += 1
60
+ end
61
+ event
62
+ end
63
+ # rubocop:enable Metrics/AbcSize
64
+ # rubocop:enable Metrics/MethodLength
65
+ end
66
+ end
67
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formatron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Halliday
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-01-07 00:00:00.000000000 Z
11
+ date: 2016-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -276,6 +276,20 @@ dependencies:
276
276
  - - "~>"
277
277
  - !ruby/object:Gem::Version
278
278
  version: '12.5'
279
+ - !ruby/object:Gem::Dependency
280
+ name: net-ssh
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - "~>"
284
+ - !ruby/object:Gem::Version
285
+ version: '2.9'
286
+ type: :runtime
287
+ prerelease: false
288
+ version_requirements: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - "~>"
291
+ - !ruby/object:Gem::Version
292
+ version: '2.9'
279
293
  description: |
280
294
  AWS/Chef deployment tool based around Chef Server and AWS CloudFormation
281
295
  email:
@@ -303,6 +317,7 @@ files:
303
317
  - formatron.gemspec
304
318
  - lib/formatron.rb
305
319
  - lib/formatron/aws.rb
320
+ - lib/formatron/aws/cloud_formation_stack.rb
306
321
  - lib/formatron/chef.rb
307
322
  - lib/formatron/chef/berkshelf.rb
308
323
  - lib/formatron/chef/keys.rb
@@ -390,6 +405,7 @@ files:
390
405
  - lib/formatron/util/vpc.rb
391
406
  - lib/formatron/version.rb
392
407
  - support/cloudformation_describe_stacks_response.rb
408
+ - support/cloudformation_stack_events_responses.rb
393
409
  - support/dsl_test.rb
394
410
  - support/route53_get_hosted_zone_response.rb
395
411
  - support/s3_get_object_response.rb