Skip to content

Commit 13e1567

Browse files
committed
Add Riak CS provider in Fog.
1 parent 1e6da32 commit 13e1567

File tree

19 files changed

+851
-2
lines changed

19 files changed

+851
-2
lines changed

lib/fog/aws/requests/storage/get_object.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Real
1414
# @option options If-Unmodified-Since [Time] Returns object only if it has not been modified since this time, otherwise returns 412 (Precodition Failed).
1515
# @option options Range [String] Range of object to download
1616
# @option options versionId [String] specify a particular version to retrieve
17+
# @option options query[Hash] specify additional query string
1718
#
1819
# @return [Excon::Response] response:
1920
# * body [String]- Contents of object
@@ -34,8 +35,11 @@ def get_object(bucket_name, object_name, options = {}, &block)
3435
end
3536

3637
params = { :headers => {} }
38+
39+
params[:query] = options.delete('query') || {}
40+
3741
if version_id = options.delete('versionId')
38-
params[:query] = {'versionId' => version_id}
42+
params[:query] = params[:query].merge({'versionId' => version_id})
3943
end
4044
params[:headers].merge!(options)
4145
if options['If-Modified-Since']
@@ -54,7 +58,7 @@ def get_object(bucket_name, object_name, options = {}, &block)
5458
:host => "#{bucket_name}.#{@host}",
5559
:idempotent => true,
5660
:method => 'GET',
57-
:path => CGI.escape(object_name),
61+
:path => CGI.escape(object_name)
5862
}))
5963
end
6064

lib/fog/bin.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def collections
8181
require 'fog/bin/bare_metal_cloud'
8282
require 'fog/bin/ninefold'
8383
require 'fog/bin/rackspace'
84+
require 'fog/bin/riakcs'
8485
require 'fog/bin/openstack'
8586
require 'fog/bin/ovirt'
8687
require 'fog/bin/serverlove'

lib/fog/bin/riakcs.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class RiakCS < Fog::Bin
2+
class << self
3+
4+
def class_for(key)
5+
case key
6+
when :provisioning
7+
Fog::RiakCS::Provisioning
8+
when :usage
9+
Fog::RiakCS::Usage
10+
else
11+
raise ArgumentError, "Unrecognized service: #{key}"
12+
end
13+
end
14+
15+
def [](service)
16+
@@connections ||= Hash.new do |hash, key|
17+
hash[key] = class_for(key)
18+
end
19+
@@connections[service]
20+
end
21+
22+
def services
23+
Fog::RiakCS.services
24+
end
25+
26+
end
27+
end

lib/fog/core/errors.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ def self.missing_credentials
7171
:rackspace_username:
7272
:rackspace_servicenet:
7373
:rackspace_cdn_ssl:
74+
:riakcs_access_key_id:
75+
:riakcs_secret_access_key:
7476
:stormondemand_username:
7577
:stormondemand_password:
7678
:terremark_username:

lib/fog/providers.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
require 'fog/bare_metal_cloud'
2323
require 'fog/ninefold'
2424
require 'fog/rackspace'
25+
require 'fog/riakcs'
2526
require 'fog/openstack'
2627
require 'fog/ovirt'
2728
require 'fog/serverlove'

lib/fog/riakcs.rb

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
require(File.expand_path(File.join(File.dirname(__FILE__), 'core')))
2+
3+
module Fog
4+
module RiakCS
5+
6+
module MultipartUtils
7+
require 'net/http'
8+
9+
class Headers
10+
include Net::HTTPHeader
11+
12+
def initialize
13+
initialize_http_header({})
14+
end
15+
16+
# Parse a single header line into its key and value
17+
# @param [String] chunk a single header line
18+
def self.parse(chunk)
19+
line = chunk.strip
20+
# thanks Net::HTTPResponse
21+
return [nil,nil] if chunk =~ /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in
22+
m = /\A([^:]+):\s*/.match(line)
23+
[m[1], m.post_match] rescue [nil, nil]
24+
end
25+
26+
# Parses a header line and adds it to the header collection
27+
# @param [String] chunk a single header line
28+
def parse(chunk)
29+
key, value = self.class.parse(chunk)
30+
add_field(key, value) if key && value
31+
end
32+
end
33+
34+
def parse(data, boundary)
35+
contents = data.match(end_boundary_regex(boundary)).pre_match rescue ""
36+
contents.split(inner_boundary_regex(boundary)).reject(&:empty?).map do |part|
37+
parse_multipart_section(part)
38+
end.compact
39+
end
40+
41+
def extract_boundary(header_string)
42+
$1 if header_string =~ /boundary=([A-Za-z0-9\'()+_,-.\/:=?]+)/
43+
end
44+
45+
private
46+
def end_boundary_regex(boundary)
47+
/\r?\n--#{Regexp.escape(boundary)}--\r?\n?/
48+
end
49+
50+
def inner_boundary_regex(boundary)
51+
/\r?\n--#{Regexp.escape(boundary)}\r?\n/
52+
end
53+
54+
def parse_multipart_section(part)
55+
headers = Headers.new
56+
if md = part.match(/\r?\n\r?\n/)
57+
body = md.post_match
58+
md.pre_match.split(/\r?\n/).each do |line|
59+
headers.parse(line)
60+
end
61+
62+
if headers["content-type"] =~ /multipart\/mixed/
63+
boundary = extract_boundary(headers.to_hash["content-type"].first)
64+
parse(body, boundary)
65+
else
66+
{:headers => headers.to_hash, :body => body}
67+
end
68+
end
69+
end
70+
end
71+
72+
module UserUtils
73+
def update_riakcs_user(key_id, user)
74+
response = @s3_connection.put_object('riak-cs', "user/#{key_id}", MultiJson.encode(user), { 'Content-Type' => 'application/json' })
75+
if !response.body.empty?
76+
response.body = MultiJson.decode(response.body)
77+
end
78+
response
79+
end
80+
81+
def update_mock_user(key_id, user)
82+
if data[key_id]
83+
if status = user[:status]
84+
data[key_id][:status] = status
85+
end
86+
87+
if regrant = user[:new_key_secret]
88+
data[key_id][:key_secret] = rand(100).to_s
89+
end
90+
91+
Excon::Response.new.tap do |response|
92+
response.status = 200
93+
response.body = data[key_id]
94+
end
95+
else
96+
Excon::Response.new.tap do |response|
97+
response.status = 403
98+
end
99+
end
100+
end
101+
end
102+
103+
module Utils
104+
def configure_uri_options(options = {})
105+
@host = options[:host] || 'localhost'
106+
@persistent = options[:persistent] || true
107+
@port = options[:port] || 8080
108+
@scheme = options[:scheme] || 'http'
109+
end
110+
111+
def riakcs_uri
112+
"#{@scheme}://#{@host}:#{@port}"
113+
end
114+
end
115+
116+
extend Fog::Provider
117+
118+
service(:provisioning, 'riakcs/provisioning', 'Provisioning')
119+
service(:usage, 'riakcs/usage', 'Usage')
120+
121+
end
122+
end

lib/fog/riakcs/provisioning.rb

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'riakcs'))
2+
3+
module Fog
4+
module RiakCS
5+
class Provisioning < Fog::Service
6+
7+
class UserAlreadyExists < Fog::RiakCS::Provisioning::Error; end
8+
class ServiceUnavailable < Fog::RiakCS::Provisioning::Error; end
9+
10+
requires :riakcs_access_key_id, :riakcs_secret_access_key
11+
recognizes :host, :path, :port, :scheme, :persistent
12+
13+
request_path 'fog/riakcs/requests/provisioning'
14+
request :create_user
15+
request :update_user
16+
request :disable_user
17+
request :enable_user
18+
request :list_users
19+
request :get_user
20+
request :regrant_secret
21+
22+
class Mock
23+
include Utils
24+
25+
def self.data
26+
@data ||= Hash.new({})
27+
end
28+
29+
def self.reset
30+
@data = nil
31+
end
32+
33+
def initialize(options = {})
34+
configure_uri_options(options)
35+
end
36+
37+
def data
38+
self.class.data[riakcs_uri]
39+
end
40+
41+
def reset_data
42+
self.class.data.delete(riakcs_uri)
43+
end
44+
end
45+
46+
class Real
47+
include Utils
48+
49+
def initialize(options = {})
50+
require 'mime/types'
51+
require 'multi_json'
52+
53+
configure_uri_options(options)
54+
@riakcs_access_key_id = options[:riakcs_access_key_id]
55+
@riakcs_secret_access_key = options[:riakcs_secret_access_key]
56+
@connection_options = options[:connection_options] || {}
57+
@persistent = options[:persistent] || false
58+
59+
@raw_connection = Fog::Connection.new(riakcs_uri, @persistent, @connection_options)
60+
61+
@s3_connection = Fog::Storage.new(
62+
:provider => 'AWS',
63+
:aws_access_key_id => @riakcs_access_key_id,
64+
:aws_secret_access_key => @riakcs_secret_access_key,
65+
:host => @host,
66+
:port => @port,
67+
:scheme => @scheme
68+
)
69+
end
70+
71+
def request(params, parse_response = true, &block)
72+
begin
73+
response = @raw_connection.request(params.merge({
74+
:host => @host,
75+
:path => "#{@path}/#{params[:path]}",
76+
}), &block)
77+
rescue Excon::Errors::HTTPStatusError => error
78+
if match = error.message.match(/<Code>(.*?)<\/Code>(?:.*<Message>(.*?)<\/Message>)?/m)
79+
case match[1]
80+
when 'UserAlreadyExists'
81+
raise Fog::RiakCS::Provisioning.const_get(match[1]).new
82+
when 'ServiceUnavailable'
83+
raise Fog::RiakCS::Provisioning.const_get(match[1]).new
84+
else
85+
raise error
86+
end
87+
else
88+
raise error
89+
end
90+
end
91+
if !response.body.empty? && parse_response
92+
response.body = MultiJson.decode(response.body)
93+
end
94+
response
95+
end
96+
end
97+
98+
end
99+
end
100+
end
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
module Fog
2+
module RiakCS
3+
class Provisioning
4+
class Real
5+
def create_user(email, name, options = {})
6+
payload = MultiJson.encode({ :email => email, :name => name })
7+
headers = { 'Content-Type' => 'application/json' }
8+
9+
if(options[:anonymous])
10+
request(
11+
:expects => [201],
12+
:method => 'POST',
13+
:path => 'user',
14+
:body => payload,
15+
:headers => headers
16+
)
17+
else
18+
begin
19+
response = @s3_connection.put_object('riak-cs', 'user', payload, headers)
20+
if !response.body.empty?
21+
case response.headers['Content-Type']
22+
when 'application/json'
23+
response.body = MultiJson.decode(response.body)
24+
end
25+
end
26+
response
27+
rescue Excon::Errors::Conflict => e
28+
raise Fog::RiakCS::Provisioning::UserAlreadyExists.new
29+
rescue Excon::Errors::BadRequest => e
30+
raise Fog::RiakCS::Provisioning::ServiceUnavailable.new
31+
end
32+
end
33+
end
34+
end
35+
36+
class Mock
37+
def invalid_email?(email)
38+
!email.include?('@')
39+
end
40+
41+
def user_exists?(email)
42+
data.detect do |key, value|
43+
value[:email] == email
44+
end
45+
end
46+
47+
def create_user(email, name, options = {})
48+
if invalid_email?(email)
49+
raise Fog::RiakCS::Provisioning::ServiceUnavailable, "The email address you provided is not a valid."
50+
end
51+
52+
if user_exists?(email)
53+
raise Fog::RiakCS::Provisioning::UserAlreadyExists, "User with email #{email} already exists."
54+
end
55+
56+
key_id = rand(1000).to_s
57+
key_secret = rand(1000).to_s
58+
data[key_id] = { :email => email, :name => name, :status => 'enabled', :key_secret => key_secret }
59+
60+
Excon::Response.new.tap do |response|
61+
response.status = 200
62+
response.headers['Content-Type'] = 'application/json'
63+
response.body = {
64+
"email" => data[:email],
65+
"display_name" => data[:name],
66+
"name" => "user123",
67+
"key_id" => key_id,
68+
"key_secret" => key_secret,
69+
"id" => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
70+
"status" => "enabled"
71+
}
72+
end
73+
end
74+
end
75+
end
76+
end
77+
end

0 commit comments

Comments
 (0)