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 +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/jackal-cfn.rb +1 -0
- data/lib/jackal-cfn/resource.rb +6 -4
- data/lib/jackal-cfn/resource/ami_register.rb +216 -0
- data/lib/jackal-cfn/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03f6aca28ee41f97d470ccee32e9892ad20dd1d2
|
4
|
+
data.tar.gz: c7f4e3707f4d45ce97102a3628ba7b6f38dd148b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47649359b1ee36126a661a02c21f461fa2ce4f25e2f50316d655edb141de87c353c67fd90be10ec6c39b7a601883d497f6a9b232053d35da315a5c6e95021145
|
7
|
+
data.tar.gz: a12b5725e7b6f97f04ff6b7cce3e475350a45c46e9406d278e2990a0fe72115634c893264692e022524aef6fee56e6c56647b7cd06c89a9345a97cec32a712e7
|
data/CHANGELOG.md
CHANGED
data/lib/jackal-cfn.rb
CHANGED
data/lib/jackal-cfn/resource.rb
CHANGED
@@ -129,7 +129,7 @@ module Jackal
|
|
129
129
|
# @return [Smash]
|
130
130
|
def unpack(message)
|
131
131
|
payload = super
|
132
|
-
if(self.
|
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
|
data/lib/jackal-cfn/version.rb
CHANGED
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.
|
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-
|
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:
|