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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +11 -48
- data/bin/miasma-test +51 -0
- data/lib/miasma/models/storage/file.rb +36 -0
- data/lib/miasma/types/api.rb +16 -2
- data/lib/miasma/version.rb +1 -1
- data/miasma.gemspec +16 -6
- metadata +104 -18
- data/lib/miasma/contrib/aws.rb +0 -444
- data/lib/miasma/contrib/aws/auto_scale.rb +0 -86
- data/lib/miasma/contrib/aws/compute.rb +0 -113
- data/lib/miasma/contrib/aws/load_balancer.rb +0 -187
- data/lib/miasma/contrib/aws/orchestration.rb +0 -350
- data/lib/miasma/contrib/aws/storage.rb +0 -405
- data/lib/miasma/contrib/open_stack.rb +0 -341
- data/lib/miasma/contrib/open_stack/compute.rb +0 -105
- data/lib/miasma/contrib/open_stack/orchestration.rb +0 -255
- data/lib/miasma/contrib/rackspace.rb +0 -112
- data/lib/miasma/contrib/rackspace/auto_scale.rb +0 -85
- data/lib/miasma/contrib/rackspace/compute.rb +0 -13
- data/lib/miasma/contrib/rackspace/load_balancer.rb +0 -118
- data/lib/miasma/contrib/rackspace/orchestration.rb +0 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7396d4fda42e875b71624a0eca9ab303a606140
|
|
4
|
+
data.tar.gz: dc2d67fa4c59fdb2a0d0411bfbb099f91bc753dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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/
|
|
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
|
data/lib/miasma/types/api.rb
CHANGED
|
@@ -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)
|
|
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)
|
|
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
|
data/lib/miasma/version.rb
CHANGED
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/
|
|
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.
|
|
14
|
-
s.
|
|
15
|
-
s.
|
|
16
|
-
s.
|
|
17
|
-
s.
|
|
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.
|
|
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:
|
|
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/
|
|
234
|
+
homepage: https://github.com/miasma-rb/miasma
|
|
149
235
|
licenses:
|
|
150
236
|
- Apache 2.0
|
|
151
237
|
metadata: {}
|
data/lib/miasma/contrib/aws.rb
DELETED
|
@@ -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
|