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