formatron 0.1.11 → 0.1.12

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