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