nephophobia 0.0.1
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.
- data/.autotest +29 -0
- data/.gitignore +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +35 -0
- data/README.md +23 -0
- data/Rakefile +11 -0
- data/lib/aws.rb +51 -0
- data/lib/hashify.rb +29 -0
- data/lib/nephophobia.rb +30 -0
- data/lib/nephophobia/client.rb +73 -0
- data/lib/nephophobia/compute.rb +150 -0
- data/lib/nephophobia/image.rb +78 -0
- data/lib/nephophobia/project.rb +113 -0
- data/lib/nephophobia/role.rb +5 -0
- data/lib/nephophobia/user.rb +86 -0
- data/lib/nephophobia/version.rb +3 -0
- data/nephophobia.gemspec +30 -0
- data/test/fixtures/cassettes/compute_all.yml +28 -0
- data/test/fixtures/cassettes/compute_all_with_filter.yml +30 -0
- data/test/fixtures/cassettes/compute_all_with_string_into_int_error.yml +28 -0
- data/test/fixtures/cassettes/compute_create.yml +28 -0
- data/test/fixtures/cassettes/compute_create_with_optional_params.yml +28 -0
- data/test/fixtures/cassettes/compute_destroy.yml +28 -0
- data/test/fixtures/cassettes/compute_find.yml +28 -0
- data/test/fixtures/cassettes/compute_reboot.yml +28 -0
- data/test/fixtures/cassettes/compute_start.yml +30 -0
- data/test/fixtures/cassettes/compute_stop.yml +30 -0
- data/test/fixtures/cassettes/image_all.yml +28 -0
- data/test/fixtures/cassettes/image_all_with_filter.yml +28 -0
- data/test/fixtures/cassettes/image_all_with_string_into_int_error.yml +28 -0
- data/test/fixtures/cassettes/image_find.yml +28 -0
- data/test/fixtures/cassettes/project_add_member.yml +28 -0
- data/test/fixtures/cassettes/project_all.yml +28 -0
- data/test/fixtures/cassettes/project_all_with_string_into_int_error.yml +28 -0
- data/test/fixtures/cassettes/project_create.yml +28 -0
- data/test/fixtures/cassettes/project_destroy.yml +28 -0
- data/test/fixtures/cassettes/project_find.yml +28 -0
- data/test/fixtures/cassettes/project_members.yml +28 -0
- data/test/fixtures/cassettes/project_remove_member.yml +28 -0
- data/test/fixtures/cassettes/user_add_role.yml +28 -0
- data/test/fixtures/cassettes/user_create.yml +28 -0
- data/test/fixtures/cassettes/user_destroy.yml +28 -0
- data/test/fixtures/cassettes/user_find.yml +28 -0
- data/test/fixtures/cassettes/user_remove_role.yml +28 -0
- data/test/lib/aws_test.rb +22 -0
- data/test/lib/hashify_test.rb +15 -0
- data/test/lib/nephophobia/compute_test.rb +144 -0
- data/test/lib/nephophobia/image_test.rb +76 -0
- data/test/lib/nephophobia/project_test.rb +104 -0
- data/test/lib/nephophobia/user_test.rb +71 -0
- data/test/lib/nephophobia_test.rb +17 -0
- data/test/test_helper.rb +47 -0
- metadata +221 -0
data/.autotest
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
Autotest.add_hook :initialize do |at|
|
6
|
+
at.testlib = 'minitest/spec'
|
7
|
+
|
8
|
+
%w{.git .svn .hg .swp .DS_Store ._* tmp}.each do |exception|
|
9
|
+
at.add_exception(exception)
|
10
|
+
end
|
11
|
+
|
12
|
+
at.clear_mappings
|
13
|
+
tests = %r%^test/lib/.*_test\.rb$%
|
14
|
+
|
15
|
+
at.add_mapping(%r%^lib/(.*)\.rb$%) do |_, m|
|
16
|
+
["test/lib/#{m[1]}_test.rb"]
|
17
|
+
end
|
18
|
+
|
19
|
+
at.add_mapping(tests) do |filename, _|
|
20
|
+
filename
|
21
|
+
end
|
22
|
+
|
23
|
+
at.add_mapping(%r%^test/.*\.rb$%) do
|
24
|
+
at.files_matching tests
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# require 'autotest/rcov'
|
29
|
+
# Autotest::RCov.command = 'rcov_info'
|
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm ruby-1.9.2-p136@nephophobia
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
nephophobia (0.0.1)
|
5
|
+
hugs (~> 2.5.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
autotest-standalone (4.5.5)
|
11
|
+
fakeweb (1.3.0)
|
12
|
+
hugs (2.5.0)
|
13
|
+
multipart-post (~> 1.0.1)
|
14
|
+
net-http-persistent (~> 1.4.1)
|
15
|
+
nokogiri (~> 1.4.4)
|
16
|
+
yajl-ruby (~> 0.7.9)
|
17
|
+
minitest (2.0.2)
|
18
|
+
multipart-post (1.0.1)
|
19
|
+
net-http-persistent (1.4.1)
|
20
|
+
nokogiri (1.4.4)
|
21
|
+
rake (0.8.7)
|
22
|
+
vcr (1.5.0)
|
23
|
+
yajl-ruby (0.7.9)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
autotest-standalone
|
30
|
+
fakeweb (~> 1.3.0)
|
31
|
+
minitest (~> 2.0.2)
|
32
|
+
nephophobia!
|
33
|
+
nokogiri (~> 1.4.4)
|
34
|
+
rake (~> 0.8.7)
|
35
|
+
vcr (= 1.5.0)
|
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Nephophobia
|
2
|
+
|
3
|
+
Lean Ruby bindings to OpenStack's AWS EC2 endpoint. Hands back a Hash (for now).
|
4
|
+
|
5
|
+
## Why
|
6
|
+
|
7
|
+
Compatibility with [VCR](https://github.com/myronmarston/vcr) largely drove this effort.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
### Bundler
|
12
|
+
|
13
|
+
gem "nephophobia"
|
14
|
+
|
15
|
+
### Examples
|
16
|
+
|
17
|
+
See the examples section in the [wiki](http://github.com/retr0h/nephophobia/wiki).
|
18
|
+
|
19
|
+
## Testing
|
20
|
+
|
21
|
+
Tests can run offline thanks to [VCR](https://github.com/myronmarston/vcr).
|
22
|
+
|
23
|
+
$ bundle exec rake
|
data/Rakefile
ADDED
data/lib/aws.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
##
|
2
|
+
# Taken from geemus/fog
|
3
|
+
#
|
4
|
+
# TODO: document
|
5
|
+
|
6
|
+
require "cgi"
|
7
|
+
require "openssl"
|
8
|
+
require "base64"
|
9
|
+
|
10
|
+
class AWS
|
11
|
+
def initialize options
|
12
|
+
@host = options[:host]
|
13
|
+
@port = options[:port]
|
14
|
+
@path = options[:path]
|
15
|
+
@access_key = options[:access_key]
|
16
|
+
@secret_key = options[:secret_key]
|
17
|
+
@project = options[:project]
|
18
|
+
end
|
19
|
+
|
20
|
+
def signed_params method, params
|
21
|
+
sign method, common_params.merge(params)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def common_params
|
26
|
+
{
|
27
|
+
"AWSAccessKeyId" => @project ? "#{@access_key}:#{@project}" : @access_key,
|
28
|
+
"SignatureMethod" => "HmacSHA256",
|
29
|
+
"SignatureVersion" => "2",
|
30
|
+
"Timestamp" => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
31
|
+
"Version" => "2010-11-15"
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def sign method, params
|
36
|
+
sorted_params = sort_params params
|
37
|
+
|
38
|
+
data = "#{method.upcase}\n#{@host}:#{@port}\n#{@path}\n" << sorted_params.chop
|
39
|
+
digest = OpenSSL::Digest::Digest.new "sha256"
|
40
|
+
signed = OpenSSL::HMAC.digest digest, @secret_key, data
|
41
|
+
sorted_params << "Signature=#{CGI.escape(Base64.encode64(signed).chomp!).gsub /\+/, '%20'}"
|
42
|
+
|
43
|
+
sorted_params
|
44
|
+
end
|
45
|
+
|
46
|
+
def sort_params params
|
47
|
+
params.keys.sort.inject("") do |result, key|
|
48
|
+
result << "#{key}=#{CGI.escape(params[key]).gsub(/\+/, '%20')}&" if params[key]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/hashify.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Hashify
|
2
|
+
##
|
3
|
+
# Return a Hash from a Nokogiri::XML::Document.
|
4
|
+
#
|
5
|
+
# +node+: An Nokogiri::XML::Document.
|
6
|
+
|
7
|
+
def self.convert node
|
8
|
+
children = {}
|
9
|
+
child_nodes = node.children
|
10
|
+
|
11
|
+
if child_nodes.first.nil?
|
12
|
+
children = nil
|
13
|
+
elsif child_nodes.first.text?
|
14
|
+
children = child_nodes.first.text
|
15
|
+
else
|
16
|
+
child_nodes.each do |child|
|
17
|
+
convert(child).each_pair do |k, v|
|
18
|
+
children[k] = if children.key? k
|
19
|
+
children[k].is_a?(Array) ? (children[k] << v) : ([children[k], v])
|
20
|
+
else
|
21
|
+
v
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
{ node.name => children }
|
28
|
+
end
|
29
|
+
end
|
data/lib/nephophobia.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "aws"
|
2
|
+
require "hashify"
|
3
|
+
require "nephophobia/client"
|
4
|
+
require "nephophobia/compute"
|
5
|
+
require "nephophobia/image"
|
6
|
+
require "nephophobia/project"
|
7
|
+
require "nephophobia/role"
|
8
|
+
require "nephophobia/user"
|
9
|
+
|
10
|
+
require "hugs"
|
11
|
+
|
12
|
+
module Nephophobia
|
13
|
+
class ResponseData
|
14
|
+
attr_reader :return, :request_id
|
15
|
+
|
16
|
+
def initialize hash
|
17
|
+
@request_id = hash["requestId"]
|
18
|
+
@return = hash["return"] == "true"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Wraps a Hash with an Array.
|
24
|
+
#
|
25
|
+
# +obj+: The Object to be tested for wrapping.
|
26
|
+
|
27
|
+
def self.coerce obj
|
28
|
+
(obj.is_a? Hash) ? [obj] : obj
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Nephophobia
|
2
|
+
class Client
|
3
|
+
##
|
4
|
+
# Required:
|
5
|
+
# +host+: A String with the host to connect.
|
6
|
+
# +access_key+: A String containing the AWS Access Key.
|
7
|
+
# +secret_key+: A String containing the AWS Secret Key.
|
8
|
+
# +project+: A String containing the "Project Name" the
|
9
|
+
# Acccess Key is intended for.
|
10
|
+
|
11
|
+
def initialize options
|
12
|
+
@port = options[:port] || 8773
|
13
|
+
@path = options[:path] || "/services/Cloud"
|
14
|
+
|
15
|
+
@connection = Hugs::Client.new(
|
16
|
+
:host => options[:host],
|
17
|
+
:scheme => options[:scheme] || "http",
|
18
|
+
:port => @port,
|
19
|
+
:type => options[:type] || :xml
|
20
|
+
)
|
21
|
+
@connection.raise_4xx = true
|
22
|
+
@connection.raise_5xx = true
|
23
|
+
|
24
|
+
@aws = AWS.new(
|
25
|
+
:host => options[:host],
|
26
|
+
:port => @port,
|
27
|
+
:path => @path,
|
28
|
+
:access_key => options[:access_key],
|
29
|
+
:secret_key => options[:secret_key],
|
30
|
+
:project => options[:project]
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Worker method which constructs the properly signed URL, and
|
36
|
+
# performs the Net::HTTP call.
|
37
|
+
# Returns a typical Net::HTTP response with a Hash body.
|
38
|
+
#
|
39
|
+
# +method+: The HTTP method used for the request.
|
40
|
+
# +params+: A Hash containing the
|
41
|
+
|
42
|
+
def raw method, params
|
43
|
+
response = @connection.send method, @path, :query => @aws.signed_params(method, params)
|
44
|
+
response.body = Hashify.convert response.body.root
|
45
|
+
|
46
|
+
response
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Vanity wrapper around #raw.
|
51
|
+
#
|
52
|
+
# +inflict+: A String with the EC2 API action to execute.
|
53
|
+
# +filter+: An optional Hash containing the EC2 API filters.
|
54
|
+
|
55
|
+
def action inflict, filter
|
56
|
+
raw "get", { "Action" => inflict }.merge(filter)
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Provide a simple interface to the EC2 Compute resources.
|
61
|
+
|
62
|
+
def compute
|
63
|
+
@compute ||= Nephophobia::Compute.new self
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Provide a simple interface to the EC2 Image resources.
|
68
|
+
|
69
|
+
def image
|
70
|
+
@image ||= Nephophobia::Image.new self
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module Nephophobia
|
2
|
+
class ComputeData
|
3
|
+
attr_reader :description, :dns_name, :image_id, :instance_id, :instance_type
|
4
|
+
attr_reader :key_name, :launch_time, :name, :owner_id, :placement
|
5
|
+
attr_reader :private_dns_name, :project_id, :public_dns_name, :state
|
6
|
+
|
7
|
+
def initialize hash
|
8
|
+
@project_id = hash['ownerId']
|
9
|
+
item = hash['instancesSet']['item']
|
10
|
+
@description = item['displayDescription']
|
11
|
+
@name = item['displayName']
|
12
|
+
@key_name = item['keyName']
|
13
|
+
@instance_id = item['instanceId']
|
14
|
+
@state = item['instanceState']['name']
|
15
|
+
@public_dns_name = item['publicDnsName']
|
16
|
+
@private_dns_name = item['privateDnsName']
|
17
|
+
@image_id = item['imageId']
|
18
|
+
@dns_name = item['dnsName']
|
19
|
+
@launch_time = Time.new(item['launchTime']).utc
|
20
|
+
@placement = item['placement']['availabilityZone']
|
21
|
+
@instance_type = item['instanceType']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Compute
|
26
|
+
def initialize client
|
27
|
+
@client = client
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Returns information about instances that +@client+ owns.
|
32
|
+
#
|
33
|
+
# +filter+: An optional Hash, intended for filtering.
|
34
|
+
# See the API Reference for further details.
|
35
|
+
# {
|
36
|
+
# "Filter.1.Name" => "instance-type",
|
37
|
+
# "Filter.1.Value.1" => "m1.small"
|
38
|
+
# }
|
39
|
+
|
40
|
+
def all filter = {}
|
41
|
+
response = @client.action "DescribeInstances", filter
|
42
|
+
|
43
|
+
Nephophobia.coerce(response.body['DescribeInstancesResponse']['reservationSet']['item']).collect do |data|
|
44
|
+
ComputeData.new data
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Create the compute instance identified by +instance_id+.
|
50
|
+
# Returns information about the new instance.
|
51
|
+
#
|
52
|
+
# +image_id+: A String representing the ID of the image.
|
53
|
+
# +params+: An optional Hash.
|
54
|
+
# See the API Reference for further details.
|
55
|
+
# {
|
56
|
+
# "DisplayName" => "testserver1",
|
57
|
+
# "DisplayDescription" => "test description"
|
58
|
+
# }
|
59
|
+
|
60
|
+
def create image_id, params = {}
|
61
|
+
filter = {
|
62
|
+
"ImageId" => image_id
|
63
|
+
}.merge params
|
64
|
+
|
65
|
+
response = @client.action "RunInstances", filter
|
66
|
+
|
67
|
+
ComputeData.new response.body['RunInstancesResponse']
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Shuts down the given 'instance_id'. This operation is idempotent; if
|
72
|
+
# you terminate an instance more than once, each call will succeed.
|
73
|
+
# Returns instances response to a state change.
|
74
|
+
#
|
75
|
+
# +instance_id+: A String representing the ID of the instance.
|
76
|
+
|
77
|
+
def destroy instance_id
|
78
|
+
filter = {
|
79
|
+
"InstanceId.1" => instance_id
|
80
|
+
}
|
81
|
+
|
82
|
+
response = @client.action "TerminateInstances", filter
|
83
|
+
|
84
|
+
ResponseData.new response.body['TerminateInstancesResponse']
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Returns information about the given 'instance_id' +@client+ owns.
|
89
|
+
#
|
90
|
+
# +instance_id+: A String representing the ID of the instance.
|
91
|
+
|
92
|
+
def find instance_id
|
93
|
+
filter = {
|
94
|
+
"InstanceId.1" => instance_id
|
95
|
+
}
|
96
|
+
|
97
|
+
response = @client.action "DescribeInstances", filter
|
98
|
+
|
99
|
+
ComputeData.new response.body['DescribeInstancesResponse']['reservationSet']['item']
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Reboot the compute instance identified by +instance_id+.
|
104
|
+
# Returns instances response to a state change.
|
105
|
+
#
|
106
|
+
# +instance_id+: A String representing the ID of the instance.
|
107
|
+
|
108
|
+
def reboot instance_id
|
109
|
+
filter = {
|
110
|
+
"InstanceId.1" => instance_id
|
111
|
+
}
|
112
|
+
|
113
|
+
response = @client.action "RebootInstances", filter
|
114
|
+
|
115
|
+
ResponseData.new response.body['RebootInstancesResponse']
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Starts the compute instance identified by +instance_id+.
|
120
|
+
# Returns instances current and previous state.
|
121
|
+
#
|
122
|
+
# +instance_id+: A String representing the ID of the instance.
|
123
|
+
|
124
|
+
def start instance_id
|
125
|
+
filter = {
|
126
|
+
"InstanceId.1" => instance_id
|
127
|
+
}
|
128
|
+
|
129
|
+
response = @client.action "StopInstances", filter
|
130
|
+
|
131
|
+
ResponseData.new response.body
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Stops the compute instance identified by +instance_id+.
|
136
|
+
# Returns instances current and previous state.
|
137
|
+
#
|
138
|
+
# +instance_id+: A String representing the ID of the instance.
|
139
|
+
|
140
|
+
def stop instance_id
|
141
|
+
filter = {
|
142
|
+
"InstanceId.1" => instance_id
|
143
|
+
}
|
144
|
+
|
145
|
+
response = @client.action "StartInstances", filter
|
146
|
+
|
147
|
+
ResponseData.new response.body
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|