jackal-cfn 0.2.2 → 0.2.4

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: 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: