jackal-cfn 0.2.2 → 0.2.4

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: 82ce644625dd07609c7f577d4aac80ad289ce94b
4
- data.tar.gz: 3052efa5fbc0b06279e2adb15a5e829cf36c41df
3
+ metadata.gz: 03f6aca28ee41f97d470ccee32e9892ad20dd1d2
4
+ data.tar.gz: c7f4e3707f4d45ce97102a3628ba7b6f38dd148b
5
5
  SHA512:
6
- metadata.gz: bb1df43542af6ed17f680cbc28482fd4270f00b27a9487e20097f714131b61a009db69a94d1e65aa2c6bb096db5e32564919e30b9c247da9dec51f91c68a169c
7
- data.tar.gz: 77b196a91a89cafb1c1ee1d1a7f2e7ca700d4d0ee813b87b5c4a195b66cf8a718e99cac5aa01d524f2f4d6124a2a683d576068e137962ac94730252a4c5f347c
6
+ metadata.gz: 47649359b1ee36126a661a02c21f461fa2ce4f25e2f50316d655edb141de87c353c67fd90be10ec6c39b7a601883d497f6a9b232053d35da315a5c6e95021145
7
+ data.tar.gz: a12b5725e7b6f97f04ff6b7cce3e475350a45c46e9406d278e2990a0fe72115634c893264692e022524aef6fee56e6c56647b7cd06c89a9345a97cec32a712e7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # v0.2.4
2
+ * Add AmiRegister callback
3
+ * Update initial payload handling
4
+
1
5
  # v0.2.2
2
6
  * Fix credentials location in configuration
3
7
  * Fix namespacing when access fog constants
data/lib/jackal-cfn.rb CHANGED
@@ -11,5 +11,6 @@ module Jackal
11
11
  # Provided custom resources
12
12
  autoload :HashExtractor, 'jackal-cfn/resource/hash_extractor'
13
13
  autoload :AmiManager, 'jackal-cfn/resource/ami_manager'
14
+ autoload :AmiRegister, 'jackal-cfn/resource/ami_register'
14
15
  end
15
16
  end
@@ -129,7 +129,7 @@ module Jackal
129
129
  # @return [Smash]
130
130
  def unpack(message)
131
131
  payload = super
132
- if(self.class == Jackal::Cfn::Resource)
132
+ if(self.is_a?(Jackal::Cfn::Resource))
133
133
  begin
134
134
  if(payload['Body'] && payload['Body']['Message'])
135
135
  payload = MultiJson.load(payload.get('Body', 'Message')).to_smash
@@ -137,14 +137,16 @@ module Jackal
137
137
  payload[:origin_type] = message[:message].get('Body', 'Type')
138
138
  payload[:origin_subject] = message[:message].get('Body', 'Subject')
139
139
  payload[:request_type] = snakecase(payload[:request_type])
140
+ payload
141
+ else
142
+ payload.to_smash.fetch('Attributes', 'Body', payload.to_smash.fetch('Body', payload.to_smash))
140
143
  end
141
- payload
142
144
  rescue MultiJson::ParseError
143
145
  # Not our expected format so return empty payload
144
146
  Smash.new
145
147
  end
146
148
  else
147
- payload.to_smash.fetch('Body', payload.to_smash)
149
+ payload.to_smash.fetch('Attributes', 'Body', payload.to_smash.fetch('Body', payload.to_smash))
148
150
  end
149
151
  end
150
152
 
@@ -158,7 +160,7 @@ module Jackal
158
160
  :cfn_resource => data_payload
159
161
  )
160
162
  if(config[:reprocess])
161
- Carnivore::Supervisor.supervisor[destination(:input)].transmit(payload)
163
+ Carnivore::Supervisor.supervisor[destination(:input, payload)].transmit(payload)
162
164
  message.confirm!
163
165
  else
164
166
  completed(payload, message)
@@ -0,0 +1,216 @@
1
+ require 'jackal-cfn'
2
+
3
+ module Jackal
4
+ module Cfn
5
+ # Register an AMI from an EC2 resource
6
+ #
7
+ # Expected resource:
8
+ # {
9
+ # "Type": "Custom::AmiRegister",
10
+ # "Properties": {
11
+ # "Parameters": {
12
+ # "Name": String,
13
+ # "InstanceId": String,
14
+ # "Description": String,
15
+ # "NoReboot": Boolean,
16
+ # "BlockDeviceMappings": Array,
17
+ # "HaltInstance": Boolean,
18
+ # "Region": String
19
+ # }
20
+ # }
21
+ # }
22
+ #
23
+ # Response Hash:
24
+ # {
25
+ # "AmiId": String
26
+ # }
27
+ #
28
+ # Required configuration:
29
+ # {
30
+ # "config": {
31
+ # "ami": {
32
+ # "credentials": {
33
+ # "compute": {
34
+ # FOG_CREDENTIALS
35
+ # }
36
+ # }
37
+ # }
38
+ # }
39
+ # }
40
+ class AmiRegister < Jackal::Cfn::Resource
41
+
42
+ PHYSICAL_ID_JOINER = '__-__'
43
+
44
+ # Ensure fog library is loaded
45
+ def setup(*_)
46
+ require 'fog'
47
+ end
48
+
49
+ # Process message, send value back to CFN
50
+ #
51
+ # @param message [Carnivore::Message]
52
+ def execute(message)
53
+ failure_wrap(message) do |payload|
54
+ cfn_resource = rekey_hash(payload.get(:data, :cfn_resource))
55
+ properties = rekey_hash(cfn_resource[:resource_properties])
56
+ parameters = rekey_hash(properties[:parameters])
57
+ cfn_response = build_response(cfn_resource)
58
+ poll_for_available = false
59
+ case cfn_resource[:request_type].to_sym
60
+ when :create
61
+ generate_ami(cfn_response, parameters)
62
+ unless(cfn_response['Status'] == 'FAILED')
63
+ if(parameters[:halt_instance].to_s == 'true')
64
+ halt_ec2_instance(parameters)
65
+ end
66
+ end
67
+ poll_for_available = true
68
+ when :update
69
+ destroy_ami(cfn_response, cfn_resource, parameters)
70
+ unless(cfn_response['Status'] == 'FAILED')
71
+ generate_ami(cfn_response, parameters)
72
+ if(parameters[:halt_instance].to_s == 'true')
73
+ halt_ec2_instance(parameters)
74
+ end
75
+ end
76
+ poll_for_available = true
77
+ when :delete
78
+ destroy_ami(cfn_response, cfn_resource, parameters)
79
+ else
80
+ error "Unknown request type received: #{cfn_resource[:request_type].inspect}"
81
+ cfn_response['Status'] = 'FAILED'
82
+ cfn_response['Reason'] = 'Unknown request type received'
83
+ end
84
+ if(poll_for_available && cfn_response['Status'] != 'FAILED')
85
+ result = poll_ami_available(message, parameters[:region], cfn_response['Data']['AmiId'])
86
+ if(result != 'available')
87
+ cfn_response['Status'] = 'FAILED'
88
+ cfn_response['Reason'] = "Registered AMI resulted in FAILED state #{cfn_response['Data']['AmiId']}"
89
+ end
90
+ end
91
+ respond_to_stack(cfn_response, cfn_resource[:response_url])
92
+ job_completed(:jackal_cfn, payload, message)
93
+ end
94
+ end
95
+
96
+ # Poll the status of the AMI being generated and return the
97
+ # final state once no longer "pending"
98
+ #
99
+ # @param message [Carnivore::Message]
100
+ # @param region [String]
101
+ # @param ami_id [String]
102
+ # @return [String] state of image (available/failed)
103
+ def poll_ami_available(message, region, ami_id)
104
+ available = false
105
+ pause_interval = config.fetch(:ami_register_interval, 5).to_i
106
+ if(ami_id)
107
+ until(available)
108
+ debug "Pausing for AMI to become available: #{ami_id} (wait time: #{pause_interval})"
109
+ message.touch!
110
+ sleep(pause_interval)
111
+ result = compute_api(region).describe_images(
112
+ 'ImageId' => [ami_id]
113
+ )
114
+ result = result.body['imagesSet'].first
115
+ if(result['imageState'] != 'pending')
116
+ available = result['imageState']
117
+ else
118
+ debug "AMI is still in pending state: #{ami_id}"
119
+ end
120
+ end
121
+ available
122
+ else
123
+ raise ArgumentError.new 'No AMI ID was provided to poll for available state!'
124
+ end
125
+ end
126
+
127
+ # Halt an EC2 instance
128
+ #
129
+ # @param instance_id [String] EC2 instance ID
130
+ # @return [TrueClass, FalseClass]
131
+ def halt_ec2_instance(parameters)
132
+ begin
133
+ compute_api(parameters[:region]).stop_instances([parameters[:instance_id]])
134
+ info "Halted EC2 instance: #{parameters[:instance_id]}"
135
+ true
136
+ rescue => e
137
+ warn "Failed to halt requested EC2 instance: #{parameters[:instance_id]} (#{e.class}: #{e.message})"
138
+ false
139
+ end
140
+ end
141
+
142
+ # Create new AMI using provided EC2 instance
143
+ #
144
+ # @param response [Hash] cfn response
145
+ # @param parameters [Hash] resource parameters
146
+ # @return [Hash] updated response hash
147
+ def generate_ami(response, parameters)
148
+ begin
149
+ result = compute_api(parameters[:region]).create_image(
150
+ parameters[:instance_id],
151
+ parameters[:name],
152
+ parameters[:description],
153
+ parameters[:no_reboot],
154
+ :block_device_mappings => parameters.fetch(:block_device_mappings, [])
155
+ )
156
+ info "New AMI registered: #{result.body['imageId']}"
157
+ response['Data']['AmiId'] = result.body['imageId']
158
+ response['PhysicalResourceId'] = [
159
+ physical_resource_id,
160
+ result.body['imageId']
161
+ ].join(PHYSICAL_ID_JOINER)
162
+ rescue ::Fog::Compute::AWS::Error => e
163
+ warn "Failed to create AMI: #{e.class}: #{e.message}"
164
+ response['Reason'] = "AMI Creation failed: #{e.message}"
165
+ response['Status'] = 'FAILED'
166
+ rescue => e
167
+ error "Unexpected error creating AMI: #{e.class}: #{e.message}"
168
+ response['Status'] = 'FAILED'
169
+ response['Reason'] = "Unexpected error: #{e.message}"
170
+ response['PhysicalResourceId'] = [
171
+ response['PhysicalResourceId'],
172
+ parameters[:ami_id]
173
+ ].join(PHYSICAL_ID_JOINER)
174
+ end
175
+ response
176
+ end
177
+
178
+ # Destroy the AMI referenced by the resource
179
+ #
180
+ # @param response [Hash] cfn response
181
+ # @param payload [Hash] message payload
182
+ # @param parameters [Hash] resource parameters
183
+ # @return [TrueClass]
184
+ def destroy_ami(response, payload, parameters)
185
+ ami_id = payload[:physical_resource_id].split(PHYSICAL_ID_JOINER).last
186
+ begin
187
+ info "Destroying registered AMI: #{ami_id}"
188
+ compute_api(parameters[:region]).deregister_image(ami_id)
189
+ rescue ::Fog::Compute::AWS::Error => e
190
+ warn "Failed to remove AMI: #{e.class}: #{e}"
191
+ response['Reason'] = "Failed to remove AMI resource: #{e}. Ignoring."
192
+ rescue => e
193
+ error "Unexpected error removing AMI: #{e.class}: #{e}"
194
+ response['Status'] = 'FAILED'
195
+ response['Reason'] = e.message
196
+ end
197
+ true
198
+ end
199
+
200
+ # Build new compute api connection
201
+ #
202
+ # @param region [String] AWS region ami exists
203
+ # @return [Fog::Compute]
204
+ def compute_api(region)
205
+ ::Fog::Compute.new(
206
+ {:provider => :aws}.merge(
207
+ config.get(:ami, :credentials, :compute).merge(
208
+ :region => region
209
+ )
210
+ )
211
+ )
212
+ end
213
+
214
+ end
215
+ end
216
+ end
@@ -1,6 +1,6 @@
1
1
  module Jackal
2
2
  module Cfn
3
3
  # Current version
4
- VERSION = Gem::Version.new('0.2.2')
4
+ VERSION = Gem::Version.new('0.2.4')
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jackal-cfn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-17 00:00:00.000000000 Z
11
+ date: 2015-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jackal
@@ -54,6 +54,7 @@ files:
54
54
  - lib/jackal-cfn/formatter/chef_cleanup.rb
55
55
  - lib/jackal-cfn/resource.rb
56
56
  - lib/jackal-cfn/resource/ami_manager.rb
57
+ - lib/jackal-cfn/resource/ami_register.rb
57
58
  - lib/jackal-cfn/resource/hash_extractor.rb
58
59
  - lib/jackal-cfn/utils.rb
59
60
  - lib/jackal-cfn/utils/fog.rb
@@ -84,3 +85,4 @@ signing_key:
84
85
  specification_version: 4
85
86
  summary: Message processing helper
86
87
  test_files: []
88
+ has_rdoc: