jackal-cfn 0.1.4 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/lib/jackal-cfn/event.rb +63 -16
- data/lib/jackal-cfn/resource/ami_manager.rb +90 -88
- data/lib/jackal-cfn/resource/hash_extractor.rb +28 -34
- data/lib/jackal-cfn/resource.rb +94 -79
- data/lib/jackal-cfn/utils/fog.rb +78 -0
- data/lib/jackal-cfn/utils/http.rb +29 -0
- data/lib/jackal-cfn/utils.rb +33 -0
- data/lib/jackal-cfn/version.rb +1 -1
- data/lib/jackal-cfn.rb +7 -0
- metadata +21 -24
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4082d5d4a5132d52521ed5904e2f94aa18602848
|
4
|
+
data.tar.gz: d04357c2f76bf711546f6013dac469f2961842f9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9b2206efcf53158bc40e26ab9ddb86152d815c2981f6a3fbed99a4b0ed5df2b965879652d0f069dec0b2b1c7dfa1f2b1fa6acf08e552a673599981957f952bb8
|
7
|
+
data.tar.gz: c209041a994159fa81d27be044178fa6ee90b2dc52de92d808652bf1670fcebb8cdb1a258dd2a38769793c04bf2c8819940c722bebdf41e97d74b6f570731e31
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# v0.1.6
|
2
|
+
* Isolate event and resource to simple handle format and forward
|
3
|
+
* Move builtin custom resources within new module `CfnTools`
|
4
|
+
* Update `Resource` and `Event` to subclass gracefully
|
5
|
+
|
1
6
|
# v0.1.4
|
2
7
|
* Include ami manager resource (handle deletion of amis only for now)
|
3
8
|
|
data/lib/jackal-cfn/event.rb
CHANGED
@@ -5,15 +5,58 @@ module Jackal
|
|
5
5
|
# Callback for event types
|
6
6
|
class Event < Jackal::Callback
|
7
7
|
|
8
|
+
module InheritedValidity
|
9
|
+
# Determine message validity
|
10
|
+
#
|
11
|
+
# @param message [Carnivore::Message]
|
12
|
+
# @return [TrueClass, FalseClass]
|
13
|
+
def valid?(message)
|
14
|
+
super do |payload|
|
15
|
+
data = payload.fetch(:data, :cfn_event, Smash.new)
|
16
|
+
result = data[:origin_type] == 'Notification' &&
|
17
|
+
data[:origin_subject].to_s.downcase.include?('cloudformation notification')
|
18
|
+
if(result && block_given?)
|
19
|
+
yield payload
|
20
|
+
else
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
include Jackal::Cfn::Utils
|
29
|
+
include Jackal::Cfn::Utils::Http
|
30
|
+
|
31
|
+
# Update validity checks in subclasses
|
32
|
+
#
|
33
|
+
# @param klass [Class]
|
34
|
+
def self.inherited(klass)
|
35
|
+
klass.class_eval do
|
36
|
+
include InheritedValidity
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
8
40
|
# Unpack message and create payload
|
9
41
|
#
|
10
42
|
# @param message [Carnivore::Message]
|
11
43
|
# @return [Smash]
|
12
44
|
def unpack(message)
|
13
45
|
payload = super
|
14
|
-
|
15
|
-
|
16
|
-
|
46
|
+
if(self.class == Jackal::Cfn::Event)
|
47
|
+
begin
|
48
|
+
unless(payload.get(:data, :cfn_event))
|
49
|
+
payload = format_event(payload.fetch('Body', 'Message', payload))
|
50
|
+
payload[:origin_type] = message[:message].get('Body', 'Type')
|
51
|
+
payload[:origin_subject] = message[:message].get('Body', 'Subject')
|
52
|
+
end
|
53
|
+
payload
|
54
|
+
rescue
|
55
|
+
Smash.new
|
56
|
+
end
|
57
|
+
else
|
58
|
+
payload.to_smash.fetch('Body', payload.to_smash)
|
59
|
+
end
|
17
60
|
end
|
18
61
|
|
19
62
|
# Determine message validity
|
@@ -22,12 +65,11 @@ module Jackal
|
|
22
65
|
# @return [TrueClass, FalseClass]
|
23
66
|
def valid?(message)
|
24
67
|
super do |payload|
|
25
|
-
|
26
|
-
payload[:origin_subject].downcase.include?('cloudformation notification')
|
27
|
-
if(result && block_given?)
|
68
|
+
if(block_given?)
|
28
69
|
yield payload
|
29
70
|
else
|
30
|
-
|
71
|
+
payload[:origin_type] == 'Notification' &&
|
72
|
+
payload[:origin_subject].downcase.include?('cloudformation notification')
|
31
73
|
end
|
32
74
|
end
|
33
75
|
end
|
@@ -39,8 +81,8 @@ module Jackal
|
|
39
81
|
def format_event(evt)
|
40
82
|
parts = evt.split("\n").map do |entry|
|
41
83
|
chunks = entry.split('=')
|
42
|
-
key =
|
43
|
-
value =
|
84
|
+
key = chunks.shift.strip
|
85
|
+
value = chunks.join.strip.sub(/^'/, '').sub(/'$/, '').strip
|
44
86
|
[key, value]
|
45
87
|
end
|
46
88
|
event = Smash[parts]
|
@@ -61,13 +103,18 @@ module Jackal
|
|
61
103
|
#
|
62
104
|
# @param message [Carnivore::Message]
|
63
105
|
def execute(message)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
106
|
+
data_payload = unpack(message)
|
107
|
+
payload = new_payload(
|
108
|
+
config[:name],
|
109
|
+
:cfn_event => data_payload
|
110
|
+
)
|
111
|
+
if(config[:reprocess])
|
112
|
+
my_input = "#{source_prefix}_input"
|
113
|
+
debug "Reprocessing payload through current source (#{my_input})"
|
114
|
+
Carnivore::Supervisor.supervisor[my_input].transmit(payload)
|
115
|
+
message.confirm!
|
116
|
+
else
|
117
|
+
completed(payload, message)
|
71
118
|
end
|
72
119
|
end
|
73
120
|
|
@@ -1,109 +1,111 @@
|
|
1
1
|
require 'jackal-cfn'
|
2
2
|
|
3
3
|
module Jackal
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
4
|
+
module CfnTools
|
5
|
+
# Manage AMI Resources
|
6
|
+
#
|
7
|
+
# Expected resource:
|
8
|
+
# {
|
9
|
+
# "Type": "Custom::AmiManager",
|
10
|
+
# "Properties": {
|
11
|
+
# "Parameters": {
|
12
|
+
# "InstanceId": "",
|
13
|
+
# "AmiId": "",
|
14
|
+
# "Action": "",
|
15
|
+
# "Region": ""
|
16
|
+
# }
|
17
|
+
# }
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# Required configuration:
|
21
|
+
# {
|
22
|
+
# "config": {
|
23
|
+
# "ami": {
|
24
|
+
# "credentials": {
|
25
|
+
# "compute": {
|
26
|
+
# FOG_CREDENTIALS
|
27
|
+
# }
|
28
|
+
# }
|
29
|
+
# }
|
30
|
+
# }
|
31
|
+
# }
|
32
|
+
class AmiManager < Jackal::Cfn::Resource
|
32
33
|
|
33
|
-
|
34
|
+
PHYSICAL_ID_JOINER = '__-__'
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
36
|
+
# Process message, send value back to CFN
|
37
|
+
#
|
38
|
+
# @param message [Carnivore::Message]
|
39
|
+
def execute(message)
|
40
|
+
failure_wrap do |payload|
|
41
|
+
cfn_resource = rekey_hash(payload.get(:data, :cfn_resource))
|
42
|
+
properties = rekey_hash(cfn_resource[:resource_properties])
|
43
|
+
parameters = rekey_hash(properties[:parameters])
|
44
|
+
cfn_response = build_response(cfn_resource)
|
45
|
+
case cfn_resource[:request_type]
|
44
46
|
when :create
|
45
|
-
ami_response_update(
|
47
|
+
ami_response_update(cfn_response, parameters)
|
46
48
|
when :delete
|
47
|
-
destroy_ami(
|
49
|
+
destroy_ami(cfn_response, cfn_resource, parameters)
|
48
50
|
when :update
|
49
51
|
else
|
50
|
-
error "Unknown request type received: #{
|
52
|
+
error "Unknown request type received: #{cfn_resource[:request_type].inspect}"
|
51
53
|
response['Status'] = 'FAILED'
|
52
54
|
response['Reason'] = 'Unknown request type received'
|
53
55
|
end
|
54
|
-
respond_to_stack(
|
55
|
-
completed(
|
56
|
-
new_payload(
|
57
|
-
config.fetch(:name, :ami_manager),
|
58
|
-
:cfn_resource => payload
|
59
|
-
),
|
60
|
-
message
|
61
|
-
)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Update the physical resource id to include the ami id
|
65
|
-
#
|
66
|
-
# @param response [Hash] cfn response
|
67
|
-
# @param parameters [Hash] resource parameters
|
68
|
-
# @return [Hash] updated response hash
|
69
|
-
def ami_response_update(response, parameters)
|
70
|
-
response['PhysicalResourceId'] = [
|
71
|
-
response['PhysicalResourceId'],
|
72
|
-
parameters[:ami_id]
|
73
|
-
].join(PHYSICAL_ID_JOINER)
|
74
|
-
response
|
56
|
+
respond_to_stack(cfn_response, cfn_resource[:response_url])
|
57
|
+
completed(payload, message)
|
75
58
|
end
|
59
|
+
end
|
76
60
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
response['Reason'] = e.message
|
90
|
-
rescue => e
|
91
|
-
error "Failed to remove AMI: #{e.class}: #{e}"
|
92
|
-
response['Status'] = 'FAILED'
|
93
|
-
response['Reason'] = e.message
|
94
|
-
end
|
95
|
-
true
|
96
|
-
end
|
61
|
+
# Update the physical resource id to include the ami id
|
62
|
+
#
|
63
|
+
# @param response [Hash] cfn response
|
64
|
+
# @param parameters [Hash] resource parameters
|
65
|
+
# @return [Hash] updated response hash
|
66
|
+
def ami_response_update(response, parameters)
|
67
|
+
response['PhysicalResourceId'] = [
|
68
|
+
response['PhysicalResourceId'],
|
69
|
+
parameters[:ami_id]
|
70
|
+
].join(PHYSICAL_ID_JOINER)
|
71
|
+
response
|
72
|
+
end
|
97
73
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
74
|
+
# Destroy the AMI referenced by the resource
|
75
|
+
#
|
76
|
+
# @param response [Hash] cfn response
|
77
|
+
# @param payload [Hash] message payload
|
78
|
+
# @param parameters [Hash] resource parameters
|
79
|
+
# @return [TrueClass]
|
80
|
+
def destroy_ami(response, payload, parameters)
|
81
|
+
ami_id = payload[:physical_resource_id].split(PHYSICAL_ID_JOINER).last
|
82
|
+
begin
|
83
|
+
compute_api(parameters[:region]).deregister_image(ami_id)
|
84
|
+
rescue Fog::Compute::AWS::Error => e
|
85
|
+
warn "Non-fatal error encountered on AMI removal: #{e.class}: #{e}"
|
86
|
+
response['Reason'] = e.message
|
87
|
+
rescue => e
|
88
|
+
error "Failed to remove AMI: #{e.class}: #{e}"
|
89
|
+
response['Status'] = 'FAILED'
|
90
|
+
response['Reason'] = e.message
|
104
91
|
end
|
92
|
+
true
|
93
|
+
end
|
105
94
|
|
95
|
+
# Build new compute api connection
|
96
|
+
#
|
97
|
+
# @param region [String] AWS region ami exists
|
98
|
+
# @return [Fog::Compute]
|
99
|
+
def compute_api(region)
|
100
|
+
Fog::Compute.new(
|
101
|
+
{:provider => :aws}.merge(
|
102
|
+
config.fetch(:ami, :credentials, :compute).merge(
|
103
|
+
:region => region
|
104
|
+
)
|
105
|
+
)
|
106
|
+
)
|
106
107
|
end
|
108
|
+
|
107
109
|
end
|
108
110
|
end
|
109
111
|
end
|
@@ -1,52 +1,46 @@
|
|
1
1
|
require 'jackal-cfn'
|
2
2
|
|
3
3
|
module Jackal
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
class HashExtractor < Resource
|
4
|
+
module CfnTools
|
5
|
+
# Extract value from hash
|
6
|
+
#
|
7
|
+
# Expected resource:
|
8
|
+
# {
|
9
|
+
# "Type": "Custom::HashExtractor",
|
10
|
+
# "Properties": {
|
11
|
+
# "Parameters": {
|
12
|
+
# "Key": "path.to.value.in.hash",
|
13
|
+
# "Value": Hash_or_JSON_string
|
14
|
+
# }
|
15
|
+
# }
|
16
|
+
# }
|
17
|
+
class HashExtractor < Jackal::Cfn::Resource
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
properties =
|
27
|
-
|
28
|
-
|
19
|
+
# Process message, send value back to CFN
|
20
|
+
#
|
21
|
+
# @param message [Carnivore::Message]
|
22
|
+
def execute(message)
|
23
|
+
failure_wrap do |payload|
|
24
|
+
cfn_resource = rekey_hash(payload.get(:data, :cfn_resource))
|
25
|
+
properties = rekey_hash(cfn_resource[:resource_properties])
|
26
|
+
parameters = rekey_hash(properties[:parameters])
|
27
|
+
cfn_response = build_response(cfn_resource)
|
29
28
|
key = parameters[:key].split('.')
|
30
29
|
value = parameters[:value]
|
31
|
-
|
32
|
-
value
|
30
|
+
unless(value.is_a?(String))
|
31
|
+
raise TypeError.new("Expecting `String` value but received `#{value.class}`")
|
33
32
|
end
|
33
|
+
value = MultiJson.load(value).to_smash
|
34
34
|
return_value = value.get(*key)
|
35
35
|
if(return_value.is_a?(Enumerable))
|
36
36
|
return_value = MultiJson.dump(return_value)
|
37
37
|
end
|
38
38
|
cfn_response['Data']['Payload'] = return_value
|
39
39
|
respond_to_stack(cfn_response, payload[:response_url])
|
40
|
-
completed(
|
41
|
-
new_payload(
|
42
|
-
config.fetch(:name, :hash_extractor),
|
43
|
-
:cfn_resource => payload
|
44
|
-
),
|
45
|
-
message
|
46
|
-
)
|
40
|
+
completed(payload, message)
|
47
41
|
end
|
48
|
-
|
49
42
|
end
|
43
|
+
|
50
44
|
end
|
51
45
|
end
|
52
46
|
end
|
data/lib/jackal-cfn/resource.rb
CHANGED
@@ -5,10 +5,58 @@ module Jackal
|
|
5
5
|
# Callback for resource types
|
6
6
|
class Resource < Jackal::Callback
|
7
7
|
|
8
|
+
# Validity method for subclasses
|
9
|
+
module InheritedValidity
|
10
|
+
|
11
|
+
# Determine message validity
|
12
|
+
#
|
13
|
+
# @param message [Carnivore::Message]
|
14
|
+
# @return [TrueClass, FalseClass]
|
15
|
+
def valid?(message)
|
16
|
+
super do |payload|
|
17
|
+
data = payload.fetch(:data, :cfn_resource, Smash.new)
|
18
|
+
resource_type = data[:resource_type].to_s.split('::').last
|
19
|
+
result = data[:origin_type] == 'Notification' &&
|
20
|
+
data[:origin_subject].to_s.downcase.include?('cloudformation custom resource') &&
|
21
|
+
self.class.to_s.split('::').last == resource_type
|
22
|
+
if(result && block_given?)
|
23
|
+
yield payload
|
24
|
+
else
|
25
|
+
result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
include Jackal::Cfn::Utils
|
33
|
+
include Jackal::Cfn::Utils::Http
|
34
|
+
|
8
35
|
VALID_RESOURCE_STATUS = ['SUCCESS', 'FAILED']
|
9
36
|
|
10
|
-
|
11
|
-
|
37
|
+
# Update validity checks in subclasses
|
38
|
+
#
|
39
|
+
# @param klass [Class]
|
40
|
+
def self.inherited(klass)
|
41
|
+
klass.class_eval do
|
42
|
+
include InheritedValidity
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Determine message validity
|
47
|
+
#
|
48
|
+
# @param message [Carnivore::Message]
|
49
|
+
# @return [TrueClass, FalseClass]
|
50
|
+
def valid?(message)
|
51
|
+
super do |payload|
|
52
|
+
if(block_given?)
|
53
|
+
yield payload
|
54
|
+
else
|
55
|
+
payload[:origin_type] == 'Notification' &&
|
56
|
+
payload[:origin_subject].to_s.downcase.include?('cloudformation custom resource')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
12
60
|
|
13
61
|
# Setup the dependency requirements for the callback
|
14
62
|
def setup(*_)
|
@@ -21,40 +69,30 @@ module Jackal
|
|
21
69
|
# @note this should be overridden in subclasses when actual
|
22
70
|
# resources are being created
|
23
71
|
def physical_resource_id
|
24
|
-
"#{self.class.name}-#{Celluloid.uuid}"
|
72
|
+
"#{self.class.name.split('::').last}-#{Celluloid.uuid}"
|
25
73
|
end
|
26
74
|
|
27
75
|
# Generate response hash
|
28
76
|
#
|
29
|
-
# @param
|
77
|
+
# @param cfn_resource [Hash]
|
78
|
+
# @option cfn_resource [String] :logical_resource_id
|
79
|
+
# @option cfn_resource [String] :physical_resource_id
|
80
|
+
# @option cfn_resource [String] :stack_id
|
81
|
+
# @option cfn_resource [String] :request_id
|
30
82
|
# @return [Hash] default response content
|
31
|
-
def build_response(
|
32
|
-
args = transform_parameters(payload)
|
83
|
+
def build_response(cfn_resource)
|
33
84
|
Smash.new(
|
34
|
-
'LogicalResourceId' =>
|
35
|
-
'PhysicalResourceId' =>
|
36
|
-
'StackId' =>
|
37
|
-
'RequestId' =>
|
85
|
+
'LogicalResourceId' => cfn_resource[:logical_resource_id],
|
86
|
+
'PhysicalResourceId' => cfn_resource.fetch(:physical_resource_id, physical_resource_id),
|
87
|
+
'StackId' => cfn_resource[:stack_id],
|
88
|
+
'RequestId' => cfn_resource[:request_id],
|
38
89
|
'Status' => 'SUCCESS',
|
39
|
-
'
|
40
|
-
|
90
|
+
'Data' => Smash.new(
|
91
|
+
'Reason' => 'None'
|
92
|
+
)
|
41
93
|
)
|
42
94
|
end
|
43
95
|
|
44
|
-
# Provide remote endpoint session for sending response
|
45
|
-
#
|
46
|
-
# @param host [String] end point host
|
47
|
-
# @param scheme [String] end point scheme
|
48
|
-
# @return [Patron::Session]
|
49
|
-
def response_endpoint(host, scheme)
|
50
|
-
session = Patron::Session.new
|
51
|
-
session.timeout = config.fetch(:response_timeout, 20)
|
52
|
-
session.connect_timeout = config.fetch(:connection_timeout, 10)
|
53
|
-
session.base_url = "#{scheme}://#{host}"
|
54
|
-
session.headers['User-Agent'] = "JackalCfn/#{Jackal::Cfn::VERSION.version}"
|
55
|
-
session
|
56
|
-
end
|
57
|
-
|
58
96
|
# Send response to the waiting stack
|
59
97
|
#
|
60
98
|
# @param response [Hash]
|
@@ -93,33 +131,26 @@ module Jackal
|
|
93
131
|
# @return [Smash]
|
94
132
|
def unpack(message)
|
95
133
|
payload = super
|
96
|
-
|
97
|
-
|
98
|
-
payload
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
super do |payload|
|
114
|
-
resource_type = payload[:resource_type].split('::').last
|
115
|
-
result = payload[:origin_type] == 'Notification' &&
|
116
|
-
payload[:origin_subject].downcase.include?('cloudformation custom resource') &&
|
117
|
-
resource_type == self.class.name.split('::').last
|
118
|
-
if(result && block_given?)
|
119
|
-
yield payload
|
120
|
-
else
|
121
|
-
result
|
134
|
+
if(self.class == Jackal::Cfn::Resource)
|
135
|
+
begin
|
136
|
+
if(payload['Body'])
|
137
|
+
payload = Smash.new(
|
138
|
+
MultiJson.load(
|
139
|
+
payload.fetch('Body', 'Message', payload)
|
140
|
+
)
|
141
|
+
)
|
142
|
+
payload = transform_parameters(payload)
|
143
|
+
payload[:origin_type] = message[:message].get('Body', 'Type')
|
144
|
+
payload[:origin_subject] = message[:message].get('Body', 'Subject')
|
145
|
+
payload[:request_type] = snakecase(payload[:request_type])
|
146
|
+
end
|
147
|
+
payload
|
148
|
+
rescue MultiJson::ParseError
|
149
|
+
# Not our expected format so return empty payload
|
150
|
+
Smash.new
|
122
151
|
end
|
152
|
+
else
|
153
|
+
payload.to_smash.fetch('Body', payload.to_smash)
|
123
154
|
end
|
124
155
|
end
|
125
156
|
|
@@ -127,36 +158,20 @@ module Jackal
|
|
127
158
|
#
|
128
159
|
# @param message [Carnivore::Message]
|
129
160
|
def execute(message)
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
#
|
142
|
-
# @param params [Hash]
|
143
|
-
# @return [Hash] new hash with snake cased toplevel keys
|
144
|
-
def transform_parameters(params)
|
145
|
-
Smash.new.tap do |new_hash|
|
146
|
-
params.each do |key, value|
|
147
|
-
new_hash[snakecase(key)] = value
|
148
|
-
end
|
161
|
+
data_payload = unpack(message)
|
162
|
+
payload = new_payload(
|
163
|
+
config[:name],
|
164
|
+
:cfn_resource => data_payload
|
165
|
+
)
|
166
|
+
if(config[:reprocess])
|
167
|
+
my_input = "#{source_prefix}_input"
|
168
|
+
Carnivore::Supervisor.supervisor[my_input].transmit(payload)
|
169
|
+
message.confirm!
|
170
|
+
else
|
171
|
+
completed(payload, message)
|
149
172
|
end
|
150
173
|
end
|
151
174
|
|
152
|
-
# Snake case string
|
153
|
-
#
|
154
|
-
# @param v [String]
|
155
|
-
# @return [Symbol]
|
156
|
-
def snakecase(v)
|
157
|
-
v.to_s.gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym
|
158
|
-
end
|
159
|
-
|
160
175
|
end
|
161
176
|
end
|
162
177
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'jackal-cfn'
|
2
|
+
|
3
|
+
module Jackal
|
4
|
+
module Cfn
|
5
|
+
module Utils
|
6
|
+
# Helper module for loading Fog APIs
|
7
|
+
module Fog
|
8
|
+
|
9
|
+
include Utils
|
10
|
+
|
11
|
+
# Provide API for given type
|
12
|
+
#
|
13
|
+
# @param type [Symbol] Fog API (compute, orchestration, etc)
|
14
|
+
# @return [Fog::Service]
|
15
|
+
# @note extracts credentials from confg at :api -> [type | :default]
|
16
|
+
def api_for(type)
|
17
|
+
klass = Fog.constants.detect do |const|
|
18
|
+
snakecase(const).to_s == type.to_s
|
19
|
+
end
|
20
|
+
if(klass)
|
21
|
+
credentials = config.fetch(
|
22
|
+
:api, type, config.get(
|
23
|
+
:api, :default
|
24
|
+
)
|
25
|
+
)
|
26
|
+
if(credentials)
|
27
|
+
key = credentials.to_a.flatten.push(klass).sort.hash
|
28
|
+
Thread.current[:cfn_apis] ||= Smash.new
|
29
|
+
unless(Thread.current[:cfn_apis][key])
|
30
|
+
Thread.current[:cfn_apis][key] = Fog.const_get(klass).new(credentials)
|
31
|
+
end
|
32
|
+
Thread.current[:cfn_apis][key]
|
33
|
+
else
|
34
|
+
ArgumentError.new 'No credentials provided in configuration!'
|
35
|
+
end
|
36
|
+
else
|
37
|
+
raise TypeError.new "Unknown API type requested (#{type})"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Assume the role for the API connection
|
42
|
+
#
|
43
|
+
# @param api [Fog::Service]
|
44
|
+
# @param role [String] name of role to assume
|
45
|
+
# @return [Fog::Service] assumed service
|
46
|
+
# @note this is AWS specific
|
47
|
+
def api_assume_for(api, role)
|
48
|
+
Thread.current[:cfn_assume_apis] ||= Smash.new
|
49
|
+
key = api.to_yaml_properties.group_by do |item|
|
50
|
+
item.to_s.split('_').first
|
51
|
+
end.values.sort_by(&:size).last.map do |var|
|
52
|
+
[var, api.instance_variable_get(var)]
|
53
|
+
end.flatten.compact.map(&:to_s).push(api.service_name).sort.hash
|
54
|
+
if(Thread.current[:cfn_assume_apis].get(key, :expires).to_i < Time.now.to_i + 5)
|
55
|
+
sts = Fog::AWS::STS.new(
|
56
|
+
config.fetch(
|
57
|
+
:api, :sts, config.get(
|
58
|
+
:api, :default
|
59
|
+
)
|
60
|
+
)
|
61
|
+
)
|
62
|
+
result = sts.assume_role("jackal-cfn-#{Celluloid.uuid}", role).body
|
63
|
+
Thread.current[:cfn_assume_apis][key] = Smash.new(
|
64
|
+
:expires => Time.parse(result['Expiration']).to_i,
|
65
|
+
:api => api.class.new(
|
66
|
+
:aws_access_key_id => result['AccessKeyId'],
|
67
|
+
:aws_secret_access_key => result['SecretAccessKey'],
|
68
|
+
:aws_session_token => result['SessionToken']
|
69
|
+
)
|
70
|
+
)
|
71
|
+
end
|
72
|
+
Thread.current[:cfn_assume_apis].get(key, :api)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'patron'
|
2
|
+
require 'jackal-cfn'
|
3
|
+
|
4
|
+
module Jackal
|
5
|
+
module Cfn
|
6
|
+
module Utils
|
7
|
+
# Helper module for HTTP interactions
|
8
|
+
module Http
|
9
|
+
|
10
|
+
# Provide remote endpoint session for sending response
|
11
|
+
#
|
12
|
+
# @param host [String] end point host
|
13
|
+
# @param scheme [String] end point scheme
|
14
|
+
# @return [Patron::Session]
|
15
|
+
def response_endpoint(host, scheme)
|
16
|
+
session = Patron::Session.new
|
17
|
+
session.timeout = config.fetch(:response_timeout, 20)
|
18
|
+
session.connect_timeout = config.fetch(:connection_timeout, 10)
|
19
|
+
session.base_url = "#{scheme}://#{host}"
|
20
|
+
session.headers['User-Agent'] = "JackalCfn/#{Jackal::Cfn::VERSION.version}"
|
21
|
+
session
|
22
|
+
end
|
23
|
+
alias_method :http_endpoint, :response_endpoint
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'jackal-cfn'
|
2
|
+
|
3
|
+
module Jackal
|
4
|
+
module Cfn
|
5
|
+
module Utils
|
6
|
+
|
7
|
+
autoload :Fog, 'jackal-cfn/utils/fog'
|
8
|
+
autoload :Http, 'jackal-cfn/utils/http'
|
9
|
+
|
10
|
+
# Snake case top level keys in hash
|
11
|
+
#
|
12
|
+
# @param params [Hash]
|
13
|
+
# @return [Hash] new hash with snake cased toplevel keys
|
14
|
+
def transform_parameters(params)
|
15
|
+
Smash.new.tap do |new_hash|
|
16
|
+
params.each do |key, value|
|
17
|
+
new_hash[snakecase(key)] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
alias_method :rekey_hash, :transform_parameters
|
22
|
+
|
23
|
+
# Snake case string
|
24
|
+
#
|
25
|
+
# @param v [String]
|
26
|
+
# @return [Symbol]
|
27
|
+
def snakecase(v)
|
28
|
+
v.to_s.gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/jackal-cfn/version.rb
CHANGED
data/lib/jackal-cfn.rb
CHANGED
@@ -6,5 +6,12 @@ module Jackal
|
|
6
6
|
module Cfn
|
7
7
|
autoload :Event, 'jackal-cfn/event'
|
8
8
|
autoload :Resource, 'jackal-cfn/resource'
|
9
|
+
autoload :Utils, 'jackal-cfn/utils'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Tools
|
13
|
+
module CfnTools
|
14
|
+
autoload :HashExtractor, 'jackal-cfn/resource/hash_extractor'
|
15
|
+
autoload :AmiManager, 'jackal-cfn/resource/ami_manager'
|
9
16
|
end
|
10
17
|
end
|
metadata
CHANGED
@@ -1,46 +1,41 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jackal-cfn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.6
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Chris Roberts
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2014-
|
11
|
+
date: 2014-08-14 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: jackal
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ">="
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: patron
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - ">="
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - ">="
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
description: CFN callback helpers
|
@@ -49,41 +44,43 @@ executables: []
|
|
49
44
|
extensions: []
|
50
45
|
extra_rdoc_files: []
|
51
46
|
files:
|
47
|
+
- CHANGELOG.md
|
48
|
+
- CONTRIBUTING.md
|
49
|
+
- LICENSE
|
50
|
+
- README.md
|
51
|
+
- jackal-cfn.gemspec
|
52
52
|
- lib/jackal-cfn.rb
|
53
53
|
- lib/jackal-cfn/event.rb
|
54
|
-
- lib/jackal-cfn/version.rb
|
55
54
|
- lib/jackal-cfn/resource.rb
|
56
|
-
- lib/jackal-cfn/resource/hash_extractor.rb
|
57
55
|
- lib/jackal-cfn/resource/ami_manager.rb
|
58
|
-
- jackal-cfn.
|
59
|
-
-
|
60
|
-
-
|
61
|
-
-
|
62
|
-
-
|
56
|
+
- lib/jackal-cfn/resource/hash_extractor.rb
|
57
|
+
- lib/jackal-cfn/utils.rb
|
58
|
+
- lib/jackal-cfn/utils/fog.rb
|
59
|
+
- lib/jackal-cfn/utils/http.rb
|
60
|
+
- lib/jackal-cfn/version.rb
|
63
61
|
homepage: https://github.com/carnivore-rb/jackal-cfn
|
64
62
|
licenses:
|
65
63
|
- Apache 2.0
|
64
|
+
metadata: {}
|
66
65
|
post_install_message:
|
67
66
|
rdoc_options: []
|
68
67
|
require_paths:
|
69
68
|
- lib
|
70
69
|
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
-
none: false
|
72
70
|
requirements:
|
73
|
-
- -
|
71
|
+
- - ">="
|
74
72
|
- !ruby/object:Gem::Version
|
75
73
|
version: '0'
|
76
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
-
none: false
|
78
75
|
requirements:
|
79
|
-
- -
|
76
|
+
- - ">="
|
80
77
|
- !ruby/object:Gem::Version
|
81
78
|
version: '0'
|
82
79
|
requirements: []
|
83
80
|
rubyforge_project:
|
84
|
-
rubygems_version:
|
81
|
+
rubygems_version: 2.2.2
|
85
82
|
signing_key:
|
86
|
-
specification_version:
|
83
|
+
specification_version: 4
|
87
84
|
summary: Message processing helper
|
88
85
|
test_files: []
|
89
86
|
has_rdoc:
|