miasma 0.2.10 → 0.2.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 039c6245899398e6105945fa52ea6b60a6f8b496
4
- data.tar.gz: a50c93103661c9d7919ea9453b095d13d7cbe272
3
+ metadata.gz: b7396d4fda42e875b71624a0eca9ab303a606140
4
+ data.tar.gz: dc2d67fa4c59fdb2a0d0411bfbb099f91bc753dc
5
5
  SHA512:
6
- metadata.gz: 30654c515d23a58a58355b7b61ba4fc15179b0cb212dbeaa17bf27879bbba0494516efb1570bb45df29d5d157a6c7f243fdc57c02a0537847524ac651b734528
7
- data.tar.gz: e609e7a4a63d73df54f783ee6d33854b1dee23506a25aeaeeac66e14cf2e85a5d4e2ccae0918be602825511c55f12e1b7951e113585dafe7c059c6c6393874eb
6
+ metadata.gz: 4c58428f59a25b11576541387304375e4d7232c0016fbcef9fbaa5dfb746eda92a85cad33d7fc474e808c11ded50425b4699618fb6cec67c6a81be5728346a4c
7
+ data.tar.gz: 361b96d5a94c44335cc68f34b4cb7b36006cd85c270189aedb4356480dd17c5064d1da12a1702c20158a5ca87457681b3aca5a6d3f26a317309135ed11a83f2d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # v0.2.12
2
+ * Extract provider implementations to standalone libraries
3
+ * Add test helper executable
4
+ * Update API body extraction to retype Array contents
5
+ * Add streamable helper for consistent storage file read
6
+
1
7
  # V0.2.10
2
8
  * Add auto-follow to paginated results on aws
3
9
  * Speed up stack list building on aws
data/README.md CHANGED
@@ -141,54 +141,17 @@ model completions.
141
141
 
142
142
  ### Currently Supported Providers
143
143
 
144
- * AWS
145
- * Rackspace
146
- * OpenStack
147
-
148
- ### Models
149
-
150
- #### AWS
151
-
152
- |Model |Create|Read|Update|Delete|
153
- |--------------|------|----|------|------|
154
- |AutoScale | X | X | | |
155
- |BlockStorage | | | | |
156
- |Compute | X | X | | X |
157
- |DNS | | | | |
158
- |LoadBalancer | X | X | X | X |
159
- |Network | | | | |
160
- |Orchestration | X | X | X | X |
161
- |Queues | | | | |
162
- |Storage | X | X | X | X |
163
-
164
- #### Rackspace
165
-
166
- |Model |Create|Read|Update|Delete|
167
- |--------------|------|----|------|------|
168
- |AutoScale | X | X | | |
169
- |BlockStorage | | | | |
170
- |Compute | X | X | | X |
171
- |DNS | | | | |
172
- |LoadBalancer | | X | | |
173
- |Network | | | | |
174
- |Orchestration | X | X | X | X |
175
- |Queues | | | | |
176
- |Storage | | | | |
177
-
178
- #### OpenStack
179
-
180
- |Model |Create|Read|Update|Delete|
181
- |--------------|------|----|------|------|
182
- |AutoScale | | | | |
183
- |BlockStorage | | | | |
184
- |Compute | X | X | | X |
185
- |DNS | | | | |
186
- |LoadBalancer | | | | |
187
- |Network | | | | |
188
- |Orchestration | X | X | X | X |
189
- |Queues | | | | |
190
- |Storage | | | | |
144
+ Coverage currently varies from provider to provider
145
+ based on functionality restrictions and in progress
146
+ implementation goals. The README on each library
147
+ should provide a simple feature matrix for a quick
148
+ check on support availability:
149
+
150
+ * [AWS](https://github.com/miasma-rb/miasma-aws)
151
+ * [Rackspace](https://github.com/miasma-rb/miasma-rackspace)
152
+ * [OpenStack](https://github.com/miasma-rb/miasma-open-stack)
153
+ * [Local](https://github.com/miasma-rb/miasma-local)
191
154
 
192
155
  ## Info
193
156
 
194
- * Repository: https://github.com/chrisroberts/miasma
157
+ * Repository: https://github.com/miasma-rb/miasma
data/bin/miasma-test ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'vcr'
4
+ require 'fileutils'
5
+ require 'webmock/minitest'
6
+ require 'minitest/autorun'
7
+
8
+ require 'miasma'
9
+
10
+ miasma_spec_dir = File.join(File.dirname(File.dirname(__FILE__)), 'test', 'specs')
11
+
12
+ # Always load in generic model specs
13
+ Dir.glob(File.join(miasma_spec_dir, 'models', '*.rb')).each do |path|
14
+ require File.expand_path(path)
15
+ end
16
+
17
+ library_spec_dir = File.join(Dir.pwd, 'test', 'specs')
18
+
19
+ unless(File.directory?(library_spec_dir))
20
+ $stderr.puts "ERROR: Failed to locate expected spec directory! (#{library_spec_dir})"
21
+ exit -1
22
+ end
23
+
24
+ if(ARGV.empty?)
25
+ cassette_directory = File.join(library_spec_dir, 'cassettes')
26
+
27
+ Dir.glob(File.join(library_spec_dir, '**/**/*_spec.rb')).each do |path|
28
+ require File.expand_path(path)
29
+ end
30
+
31
+ else
32
+ cassette_directory = File.join(library_spec_dir, 'cassettes')
33
+
34
+ ARGV.each do |path|
35
+ full_path = File.expand_path(path)
36
+ if(File.exists?(full_path))
37
+ require full_path
38
+ else
39
+ $stderr.puts "ERROR: Failed to locate specified path! (#{full_path})"
40
+ exit -1
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ FileUtils.mkdir_p(cassette_directory)
47
+
48
+ VCR.configure do |c|
49
+ c.cassette_library_dir = cassette_directory
50
+ c.hook_into :webmock
51
+ end
@@ -8,6 +8,42 @@ module Miasma
8
8
  # Abstract file
9
9
  class File < Types::Model
10
10
 
11
+ # Simple wrapper to keep consistent reading behavior
12
+ class Streamable
13
+
14
+ # @return [Object] IO-ish thing
15
+ attr_reader :io
16
+
17
+ def initialize(io_item)
18
+ unless(io_item.respond_to?(:readpartial))
19
+ raise TypeError.new 'Instance must respond to `#readpartial`'
20
+ end
21
+ @io = io_item
22
+ end
23
+
24
+ # Proxy missing methods to io
25
+ def method_missing(method_name, *args, &block)
26
+ if(io.respond_to?(method_name))
27
+ io.send(method_name, *args, &block)
28
+ else
29
+ raise
30
+ end
31
+ end
32
+
33
+ # Customized readpartial to automatically hand EOF
34
+ #
35
+ # @param length [Integer] length to read
36
+ # @return [String]
37
+ def readpartial(length=nil)
38
+ begin
39
+ io.readpartial(length)
40
+ rescue EOFError
41
+ nil
42
+ end
43
+ end
44
+
45
+ end
46
+
11
47
  attribute :name, String, :required => true
12
48
  attribute :content_type, String
13
49
  attribute :content_disposition, String
@@ -123,13 +123,27 @@ module Miasma
123
123
  if(extract_body)
124
124
  if(extracted_headers[:content_type].to_s.include?('json'))
125
125
  begin
126
- extracted_body = MultiJson.load(result.body.to_s).to_smash
126
+ extracted_body = MultiJson.load(result.body.to_s)
127
+ if(extracted_body.respond_to?(:to_smash))
128
+ extracted_body = extracted_body.to_smash
129
+ elsif(extracted_body.respond_to?(:map!))
130
+ extracted_body.map! do |i|
131
+ i.respond_to?(:to_smash) ? i.to_smash : i
132
+ end
133
+ end
127
134
  rescue MultiJson::ParseError
128
135
  extracted_body = result.body.to_s
129
136
  end
130
137
  elsif(extracted_headers[:content_type].to_s.include?('xml'))
131
138
  begin
132
- extracted_body = MultiXml.parse(result.body.to_s).to_smash
139
+ extracted_body = MultiXml.parse(result.body.to_s)
140
+ if(extracted_body.respond_to?(:to_smash))
141
+ extracted_body = extracted_body.to_smash
142
+ elsif(extracted_body.respond_to?(:map!))
143
+ extracted_body.map! do |i|
144
+ i.respond_to?(:to_smash) ? i.to_smash : i
145
+ end
146
+ end
133
147
  rescue MultiXml::ParseError
134
148
  extracted_body = result.body.to_s
135
149
  end
@@ -1,4 +1,4 @@
1
1
  module Miasma
2
2
  # current library version
3
- VERSION = Gem::Version.new('0.2.10')
3
+ VERSION = Gem::Version.new('0.2.12')
4
4
  end
data/miasma.gemspec CHANGED
@@ -6,14 +6,24 @@ Gem::Specification.new do |s|
6
6
  s.summary = 'Smoggy API'
7
7
  s.author = 'Chris Roberts'
8
8
  s.email = 'code@chrisroberts.org'
9
- s.homepage = 'https://github.com/chrisroberts/miasma'
9
+ s.homepage = 'https://github.com/miasma-rb/miasma'
10
10
  s.description = 'Smoggy API'
11
11
  s.license = 'Apache 2.0'
12
12
  s.require_path = 'lib'
13
- s.add_dependency 'hashie'
14
- s.add_dependency 'http'
15
- s.add_dependency 'multi_json'
16
- s.add_dependency 'multi_xml'
17
- s.add_dependency 'xml-simple'
13
+ s.add_runtime_dependency 'hashie'
14
+ s.add_runtime_dependency 'http'
15
+ s.add_runtime_dependency 'multi_json'
16
+ s.add_runtime_dependency 'multi_xml'
17
+ s.add_runtime_dependency 'xml-simple'
18
+ # Include provider libs that do not have outside deps
19
+ s.add_runtime_dependency 'miasma-aws'
20
+ s.add_runtime_dependency 'miasma-open-stack'
21
+ s.add_runtime_dependency 'miasma-rackspace'
22
+ s.executables << 'miasma-test'
23
+ # Include development dependencies for running tests
24
+ s.add_development_dependency 'pry'
25
+ s.add_development_dependency 'minitest'
26
+ s.add_development_dependency 'vcr'
27
+ s.add_development_dependency 'webmock'
18
28
  s.files = Dir['lib/**/*'] + %w(miasma.gemspec README.md CHANGELOG.md LICENSE)
19
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: miasma
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.10
4
+ version: 0.2.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-21 00:00:00.000000000 Z
11
+ date: 2015-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashie
@@ -80,30 +80,116 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: miasma-aws
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: miasma-open-stack
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: miasma-rackspace
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: minitest
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: vcr
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: webmock
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
83
181
  description: Smoggy API
84
182
  email: code@chrisroberts.org
85
- executables: []
183
+ executables:
184
+ - miasma-test
86
185
  extensions: []
87
186
  extra_rdoc_files: []
88
187
  files:
89
188
  - CHANGELOG.md
90
189
  - LICENSE
91
190
  - README.md
191
+ - bin/miasma-test
92
192
  - lib/miasma.rb
93
- - lib/miasma/contrib/aws.rb
94
- - lib/miasma/contrib/aws/auto_scale.rb
95
- - lib/miasma/contrib/aws/compute.rb
96
- - lib/miasma/contrib/aws/load_balancer.rb
97
- - lib/miasma/contrib/aws/orchestration.rb
98
- - lib/miasma/contrib/aws/storage.rb
99
- - lib/miasma/contrib/open_stack.rb
100
- - lib/miasma/contrib/open_stack/compute.rb
101
- - lib/miasma/contrib/open_stack/orchestration.rb
102
- - lib/miasma/contrib/rackspace.rb
103
- - lib/miasma/contrib/rackspace/auto_scale.rb
104
- - lib/miasma/contrib/rackspace/compute.rb
105
- - lib/miasma/contrib/rackspace/load_balancer.rb
106
- - lib/miasma/contrib/rackspace/orchestration.rb
107
193
  - lib/miasma/error.rb
108
194
  - lib/miasma/models.rb
109
195
  - lib/miasma/models/auto_scale.rb
@@ -145,7 +231,7 @@ files:
145
231
  - lib/miasma/utils/smash.rb
146
232
  - lib/miasma/version.rb
147
233
  - miasma.gemspec
148
- homepage: https://github.com/chrisroberts/miasma
234
+ homepage: https://github.com/miasma-rb/miasma
149
235
  licenses:
150
236
  - Apache 2.0
151
237
  metadata: {}
@@ -1,444 +0,0 @@
1
- require 'miasma'
2
- require 'miasma/utils/smash'
3
-
4
- require 'time'
5
- require 'openssl'
6
-
7
- module Miasma
8
- module Contrib
9
- # Core API for AWS access
10
- class AwsApiCore
11
-
12
- module RequestUtils
13
-
14
- # Fetch all results when tokens are being used
15
- # for paging results
16
- #
17
- # @param next_token [String]
18
- # @param result_key [Array<String, Symbol>] path to result
19
- # @yield block to perform request
20
- # @yieldparam options [Hash] request parameters (token information)
21
- # @return [Array]
22
- def all_result_pages(next_token, *result_key, &block)
23
- list = []
24
- options = next_token ? Smash.new('NextToken' => next_token) : Smash.new
25
- result = block.call(options)
26
- content = result.get(*result_key.dup)
27
- if(content.is_a?(Array))
28
- list += content
29
- else
30
- list << content
31
- end
32
- set = result.get(*result_key.slice(0, 3))
33
- if(set && set['NextToken'])
34
- list += all_result_pages(set['NextToken'], *result_key, &block)
35
- end
36
- list.compact
37
- end
38
-
39
- end
40
-
41
- # @return [String] current time ISO8601 format
42
- def self.time_iso8601
43
- Time.now.utc.strftime('%Y%m%dT%H%M%SZ')
44
- end
45
-
46
- # HMAC helper class
47
- class Hmac
48
-
49
- # @return [OpenSSL::Digest]
50
- attr_reader :digest
51
- # @return [String] secret key
52
- attr_reader :key
53
-
54
- # Create new HMAC helper
55
- #
56
- # @param kind [String] digest type (sha1, sha256, sha512, etc)
57
- # @param key [String] secret key
58
- # @return [self]
59
- def initialize(kind, key)
60
- @digest = OpenSSL::Digest.new(kind)
61
- @key = key
62
- end
63
-
64
- # @return [String]
65
- def to_s
66
- "Hmac#{digest.name}"
67
- end
68
-
69
- # Generate the hexdigest of the content
70
- #
71
- # @param content [String] content to digest
72
- # @return [String] hashed result
73
- def hexdigest_of(content)
74
- digest << content
75
- hash = digest.hexdigest
76
- digest.reset
77
- hash
78
- end
79
-
80
- # Sign the given data
81
- #
82
- # @param data [String]
83
- # @param key_override [Object]
84
- # @return [Object] signature
85
- def sign(data, key_override=nil)
86
- result = OpenSSL::HMAC.digest(digest, key_override || key, data)
87
- digest.reset
88
- result
89
- end
90
-
91
- # Sign the given data and return hexdigest
92
- #
93
- # @param data [String]
94
- # @param key_override [Object]
95
- # @return [String] hex encoded signature
96
- def hex_sign(data, key_override=nil)
97
- result = OpenSSL::HMAC.hexdigest(digest, key_override || key, data)
98
- digest.reset
99
- result
100
- end
101
-
102
- end
103
-
104
- # Base signature class
105
- class Signature
106
-
107
- # Create new instance
108
- def initialize(*args)
109
- raise NotImplementedError.new 'This class should not be used directly!'
110
- end
111
-
112
- # Generate the signature
113
- #
114
- # @param http_method [Symbol] HTTP request method
115
- # @param path [String] request path
116
- # @param opts [Hash] request options
117
- # @return [String] signature
118
- def generate(http_method, path, opts={})
119
- raise NotImplementedError
120
- end
121
-
122
- # URL string escape compatible with AWS requirements
123
- #
124
- # @param string [String] string to escape
125
- # @return [String] escaped string
126
- def safe_escape(string)
127
- string.to_s.gsub(/([^a-zA-Z0-9_.\-~])/) do
128
- '%' << $1.unpack('H2' * $1.bytesize).join('%').upcase
129
- end
130
- end
131
-
132
- end
133
-
134
- # AWS signature version 4
135
- class SignatureV4 < Signature
136
-
137
- # @return [Hmac]
138
- attr_reader :hmac
139
- # @return [String] access key
140
- attr_reader :access_key
141
- # @return [String] region
142
- attr_reader :region
143
- # @return [String] service
144
- attr_reader :service
145
-
146
- # Create new signature generator
147
- #
148
- # @param access_key [String]
149
- # @param secret_key [String]
150
- # @param region [String]
151
- # @param service [String]
152
- # @return [self]
153
- def initialize(access_key, secret_key, region, service)
154
- @hmac = Hmac.new('sha256', secret_key)
155
- @access_key = access_key
156
- @region = region
157
- @service = service
158
- end
159
-
160
- # Generate the signature string for AUTH
161
- #
162
- # @param http_method [Symbol] HTTP request method
163
- # @param path [String] request path
164
- # @param opts [Hash] request options
165
- # @return [String] signature
166
- def generate(http_method, path, opts)
167
- signature = generate_signature(http_method, path, opts)
168
- "#{algorithm} Credential=#{access_key}/#{credential_scope}, SignedHeaders=#{signed_headers(opts[:headers])}, Signature=#{signature}"
169
- end
170
-
171
- # Generate URL with signed params
172
- #
173
- # @param http_method [Symbol] HTTP request method
174
- # @param path [String] request path
175
- # @param opts [Hash] request options
176
- # @return [String] signature
177
- def generate_url(http_method, path, opts)
178
- opts[:params].merge!(
179
- Smash.new(
180
- 'X-Amz-SignedHeaders' => signed_headers(opts[:headers]),
181
- 'X-Amz-Algorithm' => algorithm,
182
- 'X-Amz-Credential' => "#{access_key}/#{credential_scope}"
183
- )
184
- )
185
- signature = generate_signature(http_method, path, opts.merge(:body => 'UNSIGNED-PAYLOAD'))
186
- params = opts[:params].merge('X-Amz-Signature' => signature)
187
- "https://#{opts[:headers]['Host']}/#{path}?#{canonical_query(params)}"
188
- end
189
-
190
- # Generate the signature
191
- #
192
- # @param http_method [Symbol] HTTP request method
193
- # @param path [String] request path
194
- # @param opts [Hash] request options
195
- # @return [String] signature
196
- def generate_signature(http_method, path, opts)
197
- to_sign = [
198
- algorithm,
199
- AwsApiCore.time_iso8601,
200
- credential_scope,
201
- hashed_canonical_request(
202
- can_req = build_canonical_request(http_method, path, opts)
203
- )
204
- ].join("\n")
205
- signature = sign_request(to_sign)
206
- end
207
-
208
- # Sign the request
209
- #
210
- # @param request [String] request to sign
211
- # @return [String] signature
212
- def sign_request(request)
213
- key = hmac.sign(
214
- 'aws4_request',
215
- hmac.sign(
216
- service,
217
- hmac.sign(
218
- region,
219
- hmac.sign(
220
- Time.now.utc.strftime('%Y%m%d'),
221
- "AWS4#{hmac.key}"
222
- )
223
- )
224
- )
225
- )
226
- hmac.hex_sign(request, key)
227
- end
228
-
229
- # @return [String] signature algorithm
230
- def algorithm
231
- 'AWS4-HMAC-SHA256'
232
- end
233
-
234
- # @return [String] credential scope for request
235
- def credential_scope
236
- [
237
- Time.now.utc.strftime('%Y%m%d'),
238
- region,
239
- service,
240
- 'aws4_request'
241
- ].join('/')
242
- end
243
-
244
- # Generate the hash of the canonical request
245
- #
246
- # @param request [String] canonical request string
247
- # @return [String] hashed canonical request
248
- def hashed_canonical_request(request)
249
- hmac.hexdigest_of(request)
250
- end
251
-
252
- # Build the canonical request string used for signing
253
- #
254
- # @param http_method [Symbol] HTTP request method
255
- # @param path [String] request path
256
- # @param opts [Hash] request options
257
- # @return [String] canonical request string
258
- def build_canonical_request(http_method, path, opts)
259
- unless(path.start_with?('/'))
260
- path = "/#{path}"
261
- end
262
- [
263
- http_method.to_s.upcase,
264
- path,
265
- canonical_query(opts[:params]),
266
- canonical_headers(opts[:headers]),
267
- signed_headers(opts[:headers]),
268
- canonical_payload(opts)
269
- ].join("\n")
270
- end
271
-
272
- # Build the canonical query string used for signing
273
- #
274
- # @param params [Hash] query params
275
- # @return [String] canonical query string
276
- def canonical_query(params)
277
- params ||= {}
278
- params = Hash[params.sort_by(&:first)]
279
- query = params.map do |key, value|
280
- "#{safe_escape(key)}=#{safe_escape(value)}"
281
- end.join('&')
282
- end
283
-
284
- # Build the canonical header string used for signing
285
- #
286
- # @param headers [Hash] request headers
287
- # @return [String] canonical headers string
288
- def canonical_headers(headers)
289
- headers ||= {}
290
- headers = Hash[headers.sort_by(&:first)]
291
- headers.map do |key, value|
292
- [key.downcase, value.chomp].join(':')
293
- end.join("\n") << "\n"
294
- end
295
-
296
- # List of headers included in signature
297
- #
298
- # @param headers [Hash] request headers
299
- # @return [String] header list
300
- def signed_headers(headers)
301
- headers ||= {}
302
- headers.sort_by(&:first).map(&:first).
303
- map(&:downcase).join(';')
304
- end
305
-
306
- # Build the canonical payload string used for signing
307
- #
308
- # @param options [Hash] request options
309
- # @return [String] body checksum
310
- def canonical_payload(options)
311
- body = options.fetch(:body, '')
312
- if(options[:json])
313
- body = MultiJson.dump(options[:json])
314
- elsif(options[:form])
315
- body = URI.encode_www_form(options[:form])
316
- end
317
- if(body == 'UNSIGNED-PAYLOAD')
318
- body
319
- else
320
- hmac.hexdigest_of(body)
321
- end
322
- end
323
-
324
- end
325
-
326
- module ApiCommon
327
-
328
- def self.included(klass)
329
- klass.class_eval do
330
- attribute :aws_access_key_id, String, :required => true
331
- attribute :aws_secret_access_key, String, :required => true
332
- attribute :aws_region, String, :required => true
333
- attribute :aws_host, String
334
- attribute :aws_bucket_region, String
335
-
336
- # @return [Contrib::AwsApiCore::SignatureV4]
337
- attr_reader :signer
338
- end
339
- end
340
-
341
- # Build new API for specified type using current provider / creds
342
- #
343
- # @param type [Symbol] api type
344
- # @return [Api]
345
- def api_for(type)
346
- memoize(type) do
347
- creds = attributes.dup
348
- creds.delete(:aws_host)
349
- Miasma.api(
350
- Smash.new(
351
- :type => type,
352
- :provider => provider,
353
- :credentials => creds
354
- )
355
- )
356
- end
357
- end
358
-
359
- # Setup for API connections
360
- def connect
361
- unless(aws_host)
362
- self.aws_host = [
363
- self.class::API_SERVICE.downcase,
364
- aws_region,
365
- 'amazonaws.com'
366
- ].join('.')
367
- end
368
- @signer = Contrib::AwsApiCore::SignatureV4.new(
369
- aws_access_key_id, aws_secret_access_key, aws_region, self.class::API_SERVICE
370
- )
371
- end
372
-
373
- # @return [String] custom escape for aws compat
374
- def uri_escape(string)
375
- signer.safe_escape(string)
376
- end
377
-
378
- # @return [HTTP] connection for requests (forces headers)
379
- def connection
380
- super.with_headers(
381
- 'Host' => aws_host,
382
- 'X-Amz-Date' => Contrib::AwsApiCore.time_iso8601
383
- )
384
- end
385
-
386
- # @return [String] endpoint for request
387
- def endpoint
388
- "https://#{aws_host}"
389
- end
390
-
391
- # Override to inject signature
392
- #
393
- # @param connection [HTTP]
394
- # @param http_method [Symbol]
395
- # @param request_args [Array]
396
- # @return [HTTP::Response]
397
- # @note if http_method is :post, params will be automatically
398
- # removed and placed into :form
399
- def make_request(connection, http_method, request_args)
400
- dest, options = request_args
401
- path = URI.parse(dest).path
402
- options = options ? options.to_smash : Smash.new
403
- options[:params] = options.fetch(:params, Smash.new).to_smash.deep_merge('Version' => self.class::API_VERSION)
404
- if(http_method.to_sym == :post)
405
- if(options[:form])
406
- options[:form].merge(options.delete(:params))
407
- else
408
- options[:form] = options.delete(:params)
409
- end
410
- end
411
- update_request(connection, options)
412
- signature = signer.generate(
413
- http_method, path, options.merge(
414
- Smash.new(
415
- :headers => Smash[
416
- connection.default_headers.to_a
417
- ]
418
- )
419
- )
420
- )
421
- options = Hash[options.map{|k,v|[k.to_sym,v]}]
422
- connection.auth(signature).send(http_method, dest, options)
423
- end
424
-
425
- # Simple callback to allow request option adjustments prior to
426
- # signature calculation
427
- #
428
- # @param opts [Smash] request options
429
- # @return [TrueClass]
430
- def update_request(con, opts)
431
- true
432
- end
433
-
434
- end
435
-
436
- end
437
- end
438
-
439
- Models::Compute.autoload :Aws, 'miasma/contrib/aws/compute'
440
- Models::LoadBalancer.autoload :Aws, 'miasma/contrib/aws/load_balancer'
441
- Models::AutoScale.autoload :Aws, 'miasma/contrib/aws/auto_scale'
442
- Models::Orchestration.autoload :Aws, 'miasma/contrib/aws/orchestration'
443
- Models::Storage.autoload :Aws, 'miasma/contrib/aws/storage'
444
- end