nephophobia 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.autotest +29 -0
  2. data/.gitignore +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +35 -0
  6. data/README.md +23 -0
  7. data/Rakefile +11 -0
  8. data/lib/aws.rb +51 -0
  9. data/lib/hashify.rb +29 -0
  10. data/lib/nephophobia.rb +30 -0
  11. data/lib/nephophobia/client.rb +73 -0
  12. data/lib/nephophobia/compute.rb +150 -0
  13. data/lib/nephophobia/image.rb +78 -0
  14. data/lib/nephophobia/project.rb +113 -0
  15. data/lib/nephophobia/role.rb +5 -0
  16. data/lib/nephophobia/user.rb +86 -0
  17. data/lib/nephophobia/version.rb +3 -0
  18. data/nephophobia.gemspec +30 -0
  19. data/test/fixtures/cassettes/compute_all.yml +28 -0
  20. data/test/fixtures/cassettes/compute_all_with_filter.yml +30 -0
  21. data/test/fixtures/cassettes/compute_all_with_string_into_int_error.yml +28 -0
  22. data/test/fixtures/cassettes/compute_create.yml +28 -0
  23. data/test/fixtures/cassettes/compute_create_with_optional_params.yml +28 -0
  24. data/test/fixtures/cassettes/compute_destroy.yml +28 -0
  25. data/test/fixtures/cassettes/compute_find.yml +28 -0
  26. data/test/fixtures/cassettes/compute_reboot.yml +28 -0
  27. data/test/fixtures/cassettes/compute_start.yml +30 -0
  28. data/test/fixtures/cassettes/compute_stop.yml +30 -0
  29. data/test/fixtures/cassettes/image_all.yml +28 -0
  30. data/test/fixtures/cassettes/image_all_with_filter.yml +28 -0
  31. data/test/fixtures/cassettes/image_all_with_string_into_int_error.yml +28 -0
  32. data/test/fixtures/cassettes/image_find.yml +28 -0
  33. data/test/fixtures/cassettes/project_add_member.yml +28 -0
  34. data/test/fixtures/cassettes/project_all.yml +28 -0
  35. data/test/fixtures/cassettes/project_all_with_string_into_int_error.yml +28 -0
  36. data/test/fixtures/cassettes/project_create.yml +28 -0
  37. data/test/fixtures/cassettes/project_destroy.yml +28 -0
  38. data/test/fixtures/cassettes/project_find.yml +28 -0
  39. data/test/fixtures/cassettes/project_members.yml +28 -0
  40. data/test/fixtures/cassettes/project_remove_member.yml +28 -0
  41. data/test/fixtures/cassettes/user_add_role.yml +28 -0
  42. data/test/fixtures/cassettes/user_create.yml +28 -0
  43. data/test/fixtures/cassettes/user_destroy.yml +28 -0
  44. data/test/fixtures/cassettes/user_find.yml +28 -0
  45. data/test/fixtures/cassettes/user_remove_role.yml +28 -0
  46. data/test/lib/aws_test.rb +22 -0
  47. data/test/lib/hashify_test.rb +15 -0
  48. data/test/lib/nephophobia/compute_test.rb +144 -0
  49. data/test/lib/nephophobia/image_test.rb +76 -0
  50. data/test/lib/nephophobia/project_test.rb +104 -0
  51. data/test/lib/nephophobia/user_test.rb +71 -0
  52. data/test/lib/nephophobia_test.rb +17 -0
  53. data/test/test_helper.rb +47 -0
  54. 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
@@ -0,0 +1,2 @@
1
+ .bundle
2
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.9.2-p136@nephophobia
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ..gemspec
4
+ gemspec
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
@@ -0,0 +1,11 @@
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rake/testtask"
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << "./lib" << "./test"
7
+ test.pattern = "test/**/*_test.rb"
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
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
@@ -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