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.
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