miasma 0.0.1 → 0.1.0
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 +2 -0
- data/README.md +179 -0
- data/lib/miasma.rb +52 -0
- data/lib/miasma/contrib/aws.rb +390 -0
- data/lib/miasma/contrib/aws/auto_scale.rb +85 -0
- data/lib/miasma/contrib/aws/compute.rb +112 -0
- data/lib/miasma/contrib/aws/load_balancer.rb +185 -0
- data/lib/miasma/contrib/aws/orchestration.rb +338 -0
- data/lib/miasma/contrib/rackspace.rb +164 -0
- data/lib/miasma/contrib/rackspace/auto_scale.rb +84 -0
- data/lib/miasma/contrib/rackspace/compute.rb +104 -0
- data/lib/miasma/contrib/rackspace/load_balancer.rb +117 -0
- data/lib/miasma/contrib/rackspace/orchestration.rb +255 -0
- data/lib/miasma/error.rb +89 -0
- data/lib/miasma/models.rb +14 -0
- data/lib/miasma/models/auto_scale.rb +55 -0
- data/lib/miasma/models/auto_scale/group.rb +64 -0
- data/lib/miasma/models/auto_scale/groups.rb +34 -0
- data/lib/miasma/models/block_storage.rb +0 -0
- data/lib/miasma/models/compute.rb +70 -0
- data/lib/miasma/models/compute/server.rb +71 -0
- data/lib/miasma/models/compute/servers.rb +35 -0
- data/lib/miasma/models/dns.rb +0 -0
- data/lib/miasma/models/load_balancer.rb +55 -0
- data/lib/miasma/models/load_balancer/balancer.rb +72 -0
- data/lib/miasma/models/load_balancer/balancers.rb +34 -0
- data/lib/miasma/models/monitoring.rb +0 -0
- data/lib/miasma/models/orchestration.rb +127 -0
- data/lib/miasma/models/orchestration/event.rb +38 -0
- data/lib/miasma/models/orchestration/events.rb +64 -0
- data/lib/miasma/models/orchestration/resource.rb +79 -0
- data/lib/miasma/models/orchestration/resources.rb +55 -0
- data/lib/miasma/models/orchestration/stack.rb +144 -0
- data/lib/miasma/models/orchestration/stacks.rb +46 -0
- data/lib/miasma/models/queues.rb +0 -0
- data/lib/miasma/models/storage.rb +60 -0
- data/lib/miasma/models/storage/bucket.rb +36 -0
- data/lib/miasma/models/storage/buckets.rb +41 -0
- data/lib/miasma/models/storage/file.rb +45 -0
- data/lib/miasma/models/storage/files.rb +52 -0
- data/lib/miasma/types.rb +13 -0
- data/lib/miasma/types/api.rb +145 -0
- data/lib/miasma/types/collection.rb +116 -0
- data/lib/miasma/types/data.rb +53 -0
- data/lib/miasma/types/model.rb +118 -0
- data/lib/miasma/types/thin_model.rb +76 -0
- data/lib/miasma/utils.rb +12 -0
- data/lib/miasma/utils/animal_strings.rb +29 -0
- data/lib/miasma/utils/immutable.rb +36 -0
- data/lib/miasma/utils/lazy.rb +231 -0
- data/lib/miasma/utils/memoization.rb +55 -0
- data/lib/miasma/utils/smash.rb +149 -0
- data/lib/miasma/version.rb +4 -0
- data/miasma.gemspec +18 -0
- metadata +57 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f4c2fc901adb9940a31de3c3379646e1225eea6
|
4
|
+
data.tar.gz: c45a82a7de2a1004a51a6c1a3b5fb10c60028088
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e0147bc6672e477a407ff627ea6e37839cae93bc16c6f26499cd5f57a4069358a2e6849062d153bfd1b9940b23fe0568f0e5c6cf40b561be0bdfcd4476ff461
|
7
|
+
data.tar.gz: cdec65223d5ede929aad74fdf520b1a99c9a67a5d63a4559fff2cf414c5bad11eba6342da21fe9a5f27b89ff2f2b71488452e65123da99cac6a7add616902e74
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
```
|
2
|
+
███▄ ▄███▓ ██▓ ▄▄▄ ██████ ███▄ ▄███▓ ▄▄▄
|
3
|
+
▓██▒▀█▀ ██▒▓██▒▒████▄ ▒██ ▒ ▓██▒▀█▀ ██▒▒████▄
|
4
|
+
▓██ ▓██░▒██▒▒██ ▀█▄ ░ ▓██▄ ▓██ ▓██░▒██ ▀█▄
|
5
|
+
▒██ ▒██ ░██░░██▄▄▄▄██ ▒ ██▒▒██ ▒██ ░██▄▄▄▄██
|
6
|
+
▒██▒ ░██▒░██░ ▓█ ▓██▒▒██████▒▒▒██▒ ░██▒ ▓█ ▓██▒
|
7
|
+
░ ▒░ ░ ░░▓ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░░ ▒░ ░ ░ ▒▒ ▓▒█░
|
8
|
+
░ ░ ░ ▒ ░ ▒ ▒▒ ░░ ░▒ ░ ░░ ░ ░ ▒ ▒▒ ░
|
9
|
+
░ ░ ▒ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ▒
|
10
|
+
░ ░ ░ ░ ░ ░ ░ ░
|
11
|
+
```
|
12
|
+
|
13
|
+
## Overview
|
14
|
+
|
15
|
+
Miasma is YACACL (Yet Another Cloud API Client Library). Instead of
|
16
|
+
attempting to cover all the functionalities of all the different
|
17
|
+
cloud and virt APIs, miasma is focused on providing a common modeling
|
18
|
+
that will "just work". This means there may be many things that seem
|
19
|
+
to be missing, but that's probably okay. Consistency is more important
|
20
|
+
than overall completeness. Miasma isn't trying to be a replacement
|
21
|
+
for libraries like fog, rather it's attempting to supplement those
|
22
|
+
libraries by providing a consistent modeling API.
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Example
|
27
|
+
|
28
|
+
Lets have a look at using the compute model:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
compute = Miasma.api(
|
32
|
+
:type => :compute,
|
33
|
+
:provider => :aws,
|
34
|
+
:credentials => {
|
35
|
+
:aws_secret_access_key => 'SECRET',
|
36
|
+
:aws_access_key_id => 'KEY_ID',
|
37
|
+
:aws_region => 'us-west-2'
|
38
|
+
}
|
39
|
+
)
|
40
|
+
```
|
41
|
+
|
42
|
+
With this we can now list existing servers:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
compute.servers.all
|
46
|
+
```
|
47
|
+
|
48
|
+
This will provide an array of `Miasma::Models::Compute::Server`
|
49
|
+
instances. It will also cache the result so subsequent calls
|
50
|
+
will not require another API call. The list can be refreshed
|
51
|
+
by requesting a reload:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
compute.servers.reload
|
55
|
+
```
|
56
|
+
|
57
|
+
### Switching providers
|
58
|
+
|
59
|
+
Switching providers requires modification to the API parameters:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
compute = Miasma.api(
|
63
|
+
:provider => :rackspace,
|
64
|
+
:credentials => {
|
65
|
+
:rackspace_username => 'USER',
|
66
|
+
:rackspace_api_key => 'KEY',
|
67
|
+
:rackspace_region => 'REGION'
|
68
|
+
}
|
69
|
+
)
|
70
|
+
```
|
71
|
+
|
72
|
+
The `compute` API will act exactly the same as before, now using
|
73
|
+
Rackspace instead of AWS.
|
74
|
+
|
75
|
+
## Design Objectives
|
76
|
+
|
77
|
+
This library is following a few simple objectives:
|
78
|
+
|
79
|
+
* Light weight
|
80
|
+
* Consistent API
|
81
|
+
|
82
|
+
The availabile API is defined first via the models,
|
83
|
+
then concrete implementations are built via available
|
84
|
+
provider interfaces. This means the provider code is
|
85
|
+
structured to support the models instead of the models
|
86
|
+
being built around specific providers. The result is
|
87
|
+
a clean model interface providing consistency regardless
|
88
|
+
of the provider backend.
|
89
|
+
|
90
|
+
The "weight" of the library is kept light by using a
|
91
|
+
few simple approaches. All code is lazy loaded, so nothing
|
92
|
+
will be loaded into the runtime until it is actually required.
|
93
|
+
Dependencies are also kept very light, to reduce the number
|
94
|
+
of required libraries needing to be loaded. Parser wrapping
|
95
|
+
libraries are also used (`multi_json` and `multi_xml`) allowing
|
96
|
+
a choice of actual parsing backends in use. This removes
|
97
|
+
dependencies on nokogiri unless it's actually desired and
|
98
|
+
increases installation speeds.
|
99
|
+
|
100
|
+
## Features
|
101
|
+
|
102
|
+
### Thin Models
|
103
|
+
|
104
|
+
Thin models are a stripped down model that provides a subset
|
105
|
+
of information that an actual instance of the model may
|
106
|
+
contain. For instance, an `AutoScale::Group` may contain
|
107
|
+
a list of `Compute::Server`s. The collection provided within
|
108
|
+
the `AutoScale::Group` will be created via the resulting
|
109
|
+
API information on the group itself. However, since
|
110
|
+
we can provide expected mappings to what API provides
|
111
|
+
`Compute` and know these instances will be within the
|
112
|
+
`servers` collection, we can use the `#expand` helper to
|
113
|
+
automatically load the full instance:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
auto_scale = Miasma.api(:type => :auto_scale, :provider ...)
|
117
|
+
group = auto_scale.groups.first
|
118
|
+
|
119
|
+
# this list will provide the `ThinModel` instances:
|
120
|
+
p group.servers.all
|
121
|
+
|
122
|
+
# this list will provide the full instances:
|
123
|
+
p group.servers.all.map(&:expand)
|
124
|
+
```
|
125
|
+
|
126
|
+
### Automatic API switching
|
127
|
+
|
128
|
+
Resources within a specific provider can span multiple
|
129
|
+
API endpoints. To deal with this, the provider API
|
130
|
+
implemenetations provide an `#api_for` method which
|
131
|
+
will automatically build a new API instance. This
|
132
|
+
allows Miasma to hop APIs under the hood to expand
|
133
|
+
`ThinModels` as shown above.
|
134
|
+
|
135
|
+
## Current status
|
136
|
+
|
137
|
+
Miasma is currently under active development and is
|
138
|
+
in a beta state. Models are still being implemented
|
139
|
+
and spec coverage is following closely behind the
|
140
|
+
model completions.
|
141
|
+
|
142
|
+
### Currently Supported Providers
|
143
|
+
|
144
|
+
* AWS
|
145
|
+
* Rackspace
|
146
|
+
|
147
|
+
### Models
|
148
|
+
|
149
|
+
#### AWS
|
150
|
+
|
151
|
+
|Model |Create|Read|Update|Delete|
|
152
|
+
|--------------|------|----|------|------|
|
153
|
+
|AutoScale | X | | | |
|
154
|
+
|BlockStorage | | | | |
|
155
|
+
|Compute | X | X | | X |
|
156
|
+
|DNS | | | | |
|
157
|
+
|LoadBalancer | X | X | X | X |
|
158
|
+
|Network | | | | |
|
159
|
+
|Orchestration | X | X | X | X |
|
160
|
+
|Queues | | | | |
|
161
|
+
|Storage | | | | |
|
162
|
+
|
163
|
+
#### Rackspace
|
164
|
+
|
165
|
+
|Model |Create|Read|Update|Delete|
|
166
|
+
|--------------|------|----|------|------|
|
167
|
+
|AutoScale | X | | | |
|
168
|
+
|BlockStorage | | | | |
|
169
|
+
|Compute | X | X | | X |
|
170
|
+
|DNS | | | | |
|
171
|
+
|LoadBalancer | | X | | |
|
172
|
+
|Network | | | | |
|
173
|
+
|Orchestration | X | X | X | X |
|
174
|
+
|Queues | | | | |
|
175
|
+
|Storage | | | | |
|
176
|
+
|
177
|
+
## Info
|
178
|
+
|
179
|
+
* Repository: https://github.com/chrisroberts/miasma
|
data/lib/miasma.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Load in dependencies
|
2
|
+
require 'http'
|
3
|
+
require 'multi_json'
|
4
|
+
require 'multi_xml'
|
5
|
+
|
6
|
+
# Make version available
|
7
|
+
require 'miasma/version'
|
8
|
+
|
9
|
+
module Miasma
|
10
|
+
autoload :Error, 'miasma/error'
|
11
|
+
autoload :Models, 'miasma/models'
|
12
|
+
autoload :Types, 'miasma/types'
|
13
|
+
autoload :Utils, 'miasma/utils'
|
14
|
+
|
15
|
+
# Generate and API connection
|
16
|
+
#
|
17
|
+
# @param args [Hash]
|
18
|
+
# @option args [String, Symbol] :type API type (:compute, :dns, etc)
|
19
|
+
# @option args [String, Symbol] :provider Service provider
|
20
|
+
# @option args [Hash] :credentials Service provider credentials
|
21
|
+
def self.api(args={})
|
22
|
+
args = Utils::Smash.new(args)
|
23
|
+
[:type, :provider, :credentials].each do |key|
|
24
|
+
unless(args[key])
|
25
|
+
raise ArgumentError.new "Missing required api argument `#{key.inspect}`!"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
args[:type] = Utils.camel(args[:type].to_s).to_sym
|
29
|
+
args[:provider] = Utils.camel(args[:provider].to_s).to_sym
|
30
|
+
begin
|
31
|
+
require "miasma/contrib/#{Utils.snake(args[:provider])}"
|
32
|
+
rescue LoadError
|
33
|
+
# just ignore
|
34
|
+
end
|
35
|
+
base_klass = Models.const_get(args[:type])
|
36
|
+
begin
|
37
|
+
if(base_klass)
|
38
|
+
api_klass = base_klass.const_get(args[:provider])
|
39
|
+
if(api_klass)
|
40
|
+
api_klass.new(args[:credentials].to_smash)
|
41
|
+
else
|
42
|
+
raise Error.new "Failed to locate #{args[:type]} API for #{args[:provider].inspect}"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
raise Error.new "Failed to locate request API type #{args[:type].inspect}"
|
46
|
+
end
|
47
|
+
rescue NameError
|
48
|
+
raise Error.new "Failed to locate request API type #{args[:type].inspect}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,390 @@
|
|
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
|
+
if(token = result.get(:body, 'NextToken'))
|
33
|
+
list += all_result_pages(token, *result_key, &block)
|
34
|
+
end
|
35
|
+
list.compact
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String] current time ISO8601 format
|
41
|
+
def self.time_iso8601
|
42
|
+
Time.now.utc.strftime('%Y%m%dT%H%M%SZ')
|
43
|
+
end
|
44
|
+
|
45
|
+
# HMAC helper class
|
46
|
+
class Hmac
|
47
|
+
|
48
|
+
# @return [OpenSSL::Digest]
|
49
|
+
attr_reader :digest
|
50
|
+
# @return [String] secret key
|
51
|
+
attr_reader :key
|
52
|
+
|
53
|
+
# Create new HMAC helper
|
54
|
+
#
|
55
|
+
# @param kind [String] digest type (sha1, sha256, sha512, etc)
|
56
|
+
# @param key [String] secret key
|
57
|
+
# @return [self]
|
58
|
+
def initialize(kind, key)
|
59
|
+
@digest = OpenSSL::Digest.new(kind)
|
60
|
+
@key = key
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String]
|
64
|
+
def to_s
|
65
|
+
"Hmac#{digest.name}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Generate the hexdigest of the content
|
69
|
+
#
|
70
|
+
# @param content [String] content to digest
|
71
|
+
# @return [String] hashed result
|
72
|
+
def hexdigest_of(content)
|
73
|
+
digest << content
|
74
|
+
hash = digest.hexdigest
|
75
|
+
digest.reset
|
76
|
+
hash
|
77
|
+
end
|
78
|
+
|
79
|
+
# Sign the given data
|
80
|
+
#
|
81
|
+
# @param data [String]
|
82
|
+
# @param key_override [Object]
|
83
|
+
# @return [Object] signature
|
84
|
+
def sign(data, key_override=nil)
|
85
|
+
result = OpenSSL::HMAC.digest(digest, key_override || key, data)
|
86
|
+
digest.reset
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
# Sign the given data and return hexdigest
|
91
|
+
#
|
92
|
+
# @param data [String]
|
93
|
+
# @param key_override [Object]
|
94
|
+
# @return [String] hex encoded signature
|
95
|
+
def hex_sign(data, key_override=nil)
|
96
|
+
result = OpenSSL::HMAC.hexdigest(digest, key_override || key, data)
|
97
|
+
digest.reset
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
# Base signature class
|
104
|
+
class Signature
|
105
|
+
|
106
|
+
# Create new instance
|
107
|
+
def initialize(*args)
|
108
|
+
raise NotImplementedError.new 'This class should not be used directly!'
|
109
|
+
end
|
110
|
+
|
111
|
+
# Generate the signature
|
112
|
+
#
|
113
|
+
# @param http_method [Symbol] HTTP request method
|
114
|
+
# @param path [String] request path
|
115
|
+
# @param opts [Hash] request options
|
116
|
+
# @return [String] signature
|
117
|
+
def generate(http_method, path, opts={})
|
118
|
+
raise NotImplementedError
|
119
|
+
end
|
120
|
+
|
121
|
+
# URL string escape compatible with AWS requirements
|
122
|
+
#
|
123
|
+
# @param string [String] string to escape
|
124
|
+
# @return [String] escaped string
|
125
|
+
def safe_escape(string)
|
126
|
+
string.to_s.gsub(/([^a-zA-Z0-9_.\-~])/) do
|
127
|
+
'%' << $1.unpack('H2' * $1.bytesize).join('%').upcase
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
# AWS signature version 4
|
134
|
+
class SignatureV4 < Signature
|
135
|
+
|
136
|
+
# @return [Hmac]
|
137
|
+
attr_reader :hmac
|
138
|
+
# @return [String] access key
|
139
|
+
attr_reader :access_key
|
140
|
+
# @return [String] region
|
141
|
+
attr_reader :region
|
142
|
+
# @return [String] service
|
143
|
+
attr_reader :service
|
144
|
+
|
145
|
+
# Create new signature generator
|
146
|
+
#
|
147
|
+
# @param access_key [String]
|
148
|
+
# @param secret_key [String]
|
149
|
+
# @param region [String]
|
150
|
+
# @param service [String]
|
151
|
+
# @return [self]
|
152
|
+
def initialize(access_key, secret_key, region, service)
|
153
|
+
@hmac = Hmac.new('sha256', secret_key)
|
154
|
+
@access_key = access_key
|
155
|
+
@region = region
|
156
|
+
@service = service
|
157
|
+
end
|
158
|
+
|
159
|
+
# Generate the signature
|
160
|
+
#
|
161
|
+
# @param http_method [Symbol] HTTP request method
|
162
|
+
# @param path [String] request path
|
163
|
+
# @param opts [Hash] request options
|
164
|
+
# @return [String] signature
|
165
|
+
def generate(http_method, path, opts)
|
166
|
+
to_sign = [
|
167
|
+
algorithm,
|
168
|
+
AwsApiCore.time_iso8601,
|
169
|
+
credential_scope,
|
170
|
+
hashed_canonical_request(
|
171
|
+
build_canonical_request(http_method, path, opts)
|
172
|
+
)
|
173
|
+
].join("\n")
|
174
|
+
signature = sign_request(to_sign)
|
175
|
+
"#{algorithm} Credential=#{access_key}/#{credential_scope}, SignedHeaders=#{signed_headers(opts[:headers])}, Signature=#{signature}"
|
176
|
+
end
|
177
|
+
|
178
|
+
# Sign the request
|
179
|
+
#
|
180
|
+
# @param request [String] request to sign
|
181
|
+
# @return [String] signature
|
182
|
+
def sign_request(request)
|
183
|
+
key = hmac.sign(
|
184
|
+
'aws4_request',
|
185
|
+
hmac.sign(
|
186
|
+
service,
|
187
|
+
hmac.sign(
|
188
|
+
region,
|
189
|
+
hmac.sign(
|
190
|
+
Time.now.utc.strftime('%Y%m%d'),
|
191
|
+
"AWS4#{hmac.key}"
|
192
|
+
)
|
193
|
+
)
|
194
|
+
)
|
195
|
+
)
|
196
|
+
hmac.hex_sign(request, key)
|
197
|
+
end
|
198
|
+
|
199
|
+
# @return [String] signature algorithm
|
200
|
+
def algorithm
|
201
|
+
'AWS4-HMAC-SHA256'
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return [String] credential scope for request
|
205
|
+
def credential_scope
|
206
|
+
[
|
207
|
+
Time.now.utc.strftime('%Y%m%d'),
|
208
|
+
region,
|
209
|
+
service,
|
210
|
+
'aws4_request'
|
211
|
+
].join('/')
|
212
|
+
end
|
213
|
+
|
214
|
+
# Generate the hash of the canonical request
|
215
|
+
#
|
216
|
+
# @param request [String] canonical request string
|
217
|
+
# @return [String] hashed canonical request
|
218
|
+
def hashed_canonical_request(request)
|
219
|
+
hmac.hexdigest_of(request)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Build the canonical request string used for signing
|
223
|
+
#
|
224
|
+
# @param http_method [Symbol] HTTP request method
|
225
|
+
# @param path [String] request path
|
226
|
+
# @param opts [Hash] request options
|
227
|
+
# @return [String] canonical request string
|
228
|
+
def build_canonical_request(http_method, path, opts)
|
229
|
+
[
|
230
|
+
http_method.to_s.upcase,
|
231
|
+
path,
|
232
|
+
canonical_query(opts[:params]),
|
233
|
+
canonical_headers(opts[:headers]),
|
234
|
+
signed_headers(opts[:headers]),
|
235
|
+
canonical_payload(opts)
|
236
|
+
].join("\n")
|
237
|
+
end
|
238
|
+
|
239
|
+
# Build the canonical query string used for signing
|
240
|
+
#
|
241
|
+
# @param params [Hash] query params
|
242
|
+
# @return [String] canonical query string
|
243
|
+
def canonical_query(params)
|
244
|
+
params ||= {}
|
245
|
+
params = Hash[params.sort_by(&:first)]
|
246
|
+
query = params.map do |key, value|
|
247
|
+
"#{safe_escape(key)}=#{safe_escape(value)}"
|
248
|
+
end.join('&')
|
249
|
+
end
|
250
|
+
|
251
|
+
# Build the canonical header string used for signing
|
252
|
+
#
|
253
|
+
# @param headers [Hash] request headers
|
254
|
+
# @return [String] canonical headers string
|
255
|
+
def canonical_headers(headers)
|
256
|
+
headers ||= {}
|
257
|
+
headers = Hash[headers.sort_by(&:first)]
|
258
|
+
headers.map do |key, value|
|
259
|
+
[key.downcase, value.chomp].join(':')
|
260
|
+
end.join("\n") << "\n"
|
261
|
+
end
|
262
|
+
|
263
|
+
# List of headers included in signature
|
264
|
+
#
|
265
|
+
# @param headers [Hash] request headers
|
266
|
+
# @return [String] header list
|
267
|
+
def signed_headers(headers)
|
268
|
+
headers ||= {}
|
269
|
+
headers.sort_by(&:first).map(&:first).
|
270
|
+
map(&:downcase).join(';')
|
271
|
+
end
|
272
|
+
|
273
|
+
# Build the canonical payload string used for signing
|
274
|
+
#
|
275
|
+
# @param options [Hash] request options
|
276
|
+
# @return [String] body checksum
|
277
|
+
def canonical_payload(options)
|
278
|
+
body = options.fetch(:body, '')
|
279
|
+
if(options[:json])
|
280
|
+
body = MultiJson.dump(options[:json])
|
281
|
+
elsif(options[:form])
|
282
|
+
body = URI.encode_www_form(options[:form])
|
283
|
+
end
|
284
|
+
hmac.hexdigest_of(body)
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
module ApiCommon
|
290
|
+
|
291
|
+
def self.included(klass)
|
292
|
+
klass.class_eval do
|
293
|
+
attribute :aws_access_key_id, String, :required => true
|
294
|
+
attribute :aws_secret_access_key, String, :required => true
|
295
|
+
attribute :aws_region, String, :required => true
|
296
|
+
attribute :aws_host, String
|
297
|
+
|
298
|
+
# @return [Contrib::AwsApiCore::SignatureV4]
|
299
|
+
attr_reader :signer
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Build new API for specified type using current provider / creds
|
304
|
+
#
|
305
|
+
# @param type [Symbol] api type
|
306
|
+
# @return [Api]
|
307
|
+
def api_for(type)
|
308
|
+
memoize(type) do
|
309
|
+
creds = attributes.dup
|
310
|
+
creds.delete(:aws_host)
|
311
|
+
Miasma.api(
|
312
|
+
Smash.new(
|
313
|
+
:type => type,
|
314
|
+
:provider => provider,
|
315
|
+
:credentials => creds
|
316
|
+
)
|
317
|
+
)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Setup for API connections
|
322
|
+
def connect
|
323
|
+
unless(aws_host)
|
324
|
+
self.aws_host = [
|
325
|
+
self.class::API_SERVICE.downcase,
|
326
|
+
aws_region,
|
327
|
+
'amazonaws.com'
|
328
|
+
].join('.')
|
329
|
+
end
|
330
|
+
@signer = Contrib::AwsApiCore::SignatureV4.new(
|
331
|
+
aws_access_key_id, aws_secret_access_key, aws_region, self.class::API_SERVICE
|
332
|
+
)
|
333
|
+
end
|
334
|
+
|
335
|
+
# @return [HTTP] connection for requests (forces headers)
|
336
|
+
def connection
|
337
|
+
super.with_headers(
|
338
|
+
'Host' => aws_host,
|
339
|
+
'X-Amz-Date' => Contrib::AwsApiCore.time_iso8601
|
340
|
+
)
|
341
|
+
end
|
342
|
+
|
343
|
+
# @return [String] endpoint for request
|
344
|
+
def endpoint
|
345
|
+
"https://#{aws_host}"
|
346
|
+
end
|
347
|
+
|
348
|
+
# Override to inject signature
|
349
|
+
#
|
350
|
+
# @param connection [HTTP]
|
351
|
+
# @param http_method [Symbol]
|
352
|
+
# @param request_args [Array]
|
353
|
+
# @return [HTTP::Response]
|
354
|
+
# @note if http_method is :post, params will be automatically
|
355
|
+
# removed and placed into :form
|
356
|
+
def make_request(connection, http_method, request_args)
|
357
|
+
dest, options = request_args
|
358
|
+
path = URI.parse(dest).path
|
359
|
+
options = options.to_smash
|
360
|
+
options[:params] = options.fetch(:params, Smash.new).to_smash.deep_merge('Version' => self.class::API_VERSION)
|
361
|
+
if(http_method.to_sym == :post)
|
362
|
+
if(options[:form])
|
363
|
+
options[:form].merge(options.delete(:params))
|
364
|
+
else
|
365
|
+
options[:form] = options.delete(:params)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
signature = signer.generate(
|
369
|
+
http_method, path, options.merge(
|
370
|
+
Smash.new(
|
371
|
+
:headers => Smash[
|
372
|
+
connection.default_headers.to_a
|
373
|
+
]
|
374
|
+
)
|
375
|
+
)
|
376
|
+
)
|
377
|
+
options = Hash[options.map{|k,v|[k.to_sym,v]}]
|
378
|
+
connection.auth(signature).send(http_method, dest, options)
|
379
|
+
end
|
380
|
+
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
Models::Compute.autoload :Aws, 'miasma/contrib/aws/compute'
|
387
|
+
Models::LoadBalancer.autoload :Aws, 'miasma/contrib/aws/load_balancer'
|
388
|
+
Models::AutoScale.autoload :Aws, 'miasma/contrib/aws/auto_scale'
|
389
|
+
Models::Orchestration.autoload :Aws, 'miasma/contrib/aws/orchestration'
|
390
|
+
end
|