jackal-cfn 0.1.4 → 0.1.6

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 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
 
@@ -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
- payload = format_event(payload.fetch('Body', 'Message', payload))
15
- payload[:origin_type] = message[:message].get('Body', 'Type')
16
- payload[:origin_subject] = message[:message].get('Body', 'Subject')
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
- result = payload[:origin_type] == 'Notification' &&
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
- result
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 = parts.unshift.strip
43
- value = parts.join.strip.sub(/^'/, '').sub(/'$/, '').strip
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
- failure_wrap do |payload|
65
- job_completed(
66
- new_payload(
67
- config[:name],
68
- :cfn_event => payload
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 Cfn
5
- class Resource
6
- # Manage AMI Resources
7
- #
8
- # Expected resource:
9
- # {
10
- # "Type": "Custom::AmiManager",
11
- # "Properties": {
12
- # "Parameters": {
13
- # "InstanceId": "",
14
- # "AmiId": "",
15
- # "Action": "",
16
- # "Region": ""
17
- # }
18
- # }
19
- # }
20
- #
21
- # Required configuration:
22
- # {
23
- # "config": {
24
- # "credentials": {
25
- # "compute": {
26
- # FOG_CREDENTIALS
27
- # }
28
- # }
29
- # }
30
- # }
31
- class AmiManager < Resource
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
- PHYSICAL_ID_JOINER = '__-__'
34
+ PHYSICAL_ID_JOINER = '__-__'
34
35
 
35
- # Process message, send value back to CFN
36
- #
37
- # @param message [Carnivore::Message]
38
- def execute(message)
39
- payload = unpack(message)
40
- parameters = transform_parameters(payload[:resource_properties]['Parameters'])
41
- debug "Processing payload: #{payload.inspect}"
42
- response = build_response(payload)
43
- case payload[:request_type]
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(response, parameters)
47
+ ami_response_update(cfn_response, parameters)
46
48
  when :delete
47
- destroy_ami(response, payload, parameters)
49
+ destroy_ami(cfn_response, cfn_resource, parameters)
48
50
  when :update
49
51
  else
50
- error "Unknown request type received: #{payload[:request_type].inspect}"
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(response, payload[:response_url])
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
- # Destroy the AMI referenced by the resource
78
- #
79
- # @param response [Hash] cfn response
80
- # @param payload [Hash] message payload
81
- # @param parameters [Hash] resource parameters
82
- # @return [TrueClass]
83
- def destroy_ami(response, payload, parameters)
84
- ami_id = payload[:physical_resource_id].split(PHYSICAL_ID_JOINER).last
85
- begin
86
- compute_api(parameters[:region]).deregister_image(ami_id)
87
- rescue Fog::Compute::AWS::Error => e
88
- warn "Non-fatal error encountered on AMI removal: #{e.class}: #{e}"
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
- # Build new compute api connection
99
- #
100
- # @param region [String] AWS region ami exists
101
- # @return [Fog::Compute]
102
- def compute_api(region)
103
- Fog::Compute.new({:provider => :aws}.merge(config.fetch(:credentials, :compute, {}).merge(:region => region)))
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 Cfn
5
- class Resource
6
- # Extract value from hash
7
- #
8
- # Expected resource:
9
- # {
10
- # "Type": "Custom::HashExtractor",
11
- # "Properties": {
12
- # "Parameters": {
13
- # "Key": "path.to.value.in.hash",
14
- # "Value": Hash_or_JSON_string
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
- # Process message, send value back to CFN
21
- #
22
- # @param message [Carnivore::Message]
23
- def execute(message)
24
- payload = transform_parameters(unpack(message))
25
- debug "Processing payload: #{payload.inspect}"
26
- properties = transform_parameters(payload[:resource_properties])
27
- cfn_response = build_response(payload)
28
- parameters = transform_parameters(properties[:parameters])
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
- if(value.is_a?(String))
32
- value = MultiJson.load(value).to_smash
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
@@ -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
- autoload :HashExtractor, 'jackal-cfn/resource/hash_extractor'
11
- autoload :AmiManager, 'jackal-cfn/resource/ami_manager'
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 payload [Hash]
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(payload)
32
- args = transform_parameters(payload)
83
+ def build_response(cfn_resource)
33
84
  Smash.new(
34
- 'LogicalResourceId' => args[:logical_resource_id],
35
- 'PhysicalResourceId' => args.fetch(:physical_resource_id, physical_resource_id),
36
- 'StackId' => args[:stack_id],
37
- 'RequestId' => args[:request_id],
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
- 'Reason' => 'Not provided',
40
- 'Data' => Smash.new
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
- payload = Smash.new(
97
- MultiJson.load(
98
- payload.fetch('Body', 'Message', payload)
99
- )
100
- )
101
- payload = transform_parameters(payload)
102
- payload[:origin_type] = message[:message].get('Body', 'Type')
103
- payload[:origin_subject] = message[:message].get('Body', 'Subject')
104
- payload[:request_type] = snakecase(payload[:request_type])
105
- payload
106
- end
107
-
108
- # Determine message validity
109
- #
110
- # @param message [Carnivore::Message]
111
- # @return [TrueClass, FalseClass]
112
- def valid?(message)
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
- failure_wrap(message) do |payload|
131
- job_completed(
132
- new_payload(
133
- config[:name],
134
- :cfn_resource => payload
135
- )
136
- )
137
- end
138
- end
139
-
140
- # Snake case top level keys in hash
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
@@ -1,6 +1,6 @@
1
1
  module Jackal
2
2
  module Cfn
3
3
  # Current version
4
- VERSION = Gem::Version.new('0.1.4')
4
+ VERSION = Gem::Version.new('0.1.6')
5
5
  end
6
6
  end
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.4
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-07-07 00:00:00.000000000 Z
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.gemspec
59
- - README.md
60
- - CHANGELOG.md
61
- - CONTRIBUTING.md
62
- - LICENSE
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: 1.8.24
81
+ rubygems_version: 2.2.2
85
82
  signing_key:
86
- specification_version: 3
83
+ specification_version: 4
87
84
  summary: Message processing helper
88
85
  test_files: []
89
86
  has_rdoc: