nephophobia 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|