fog-radosgw 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/.gitignore +23 -0
- data/CONTRIBUTORS.md +3 -0
- data/Gemfile +2 -0
- data/LICENSE.md +20 -0
- data/README.md +65 -0
- data/Rakefile +82 -0
- data/fog-radosgw.gemspec +29 -0
- data/lib/fog/radosgw.rb +2 -0
- data/lib/fog/radosgw/core.rb +144 -0
- data/lib/fog/radosgw/provisioning.rb +101 -0
- data/lib/fog/radosgw/requests/provisioning/create_user.rb +83 -0
- data/lib/fog/radosgw/requests/provisioning/delete_user.rb +57 -0
- data/lib/fog/radosgw/requests/provisioning/disable_user.rb +23 -0
- data/lib/fog/radosgw/requests/provisioning/enable_user.rb +23 -0
- data/lib/fog/radosgw/requests/provisioning/get_user.rb +66 -0
- data/lib/fog/radosgw/requests/provisioning/list_users.rb +70 -0
- data/lib/fog/radosgw/requests/provisioning/update_user.rb +23 -0
- data/lib/fog/radosgw/requests/provisioning/user.json +17 -0
- data/lib/fog/radosgw/requests/usage/get_usage.rb +60 -0
- data/lib/fog/radosgw/usage.rb +62 -0
- data/lib/fog/radosgw/version.rb +5 -0
- data/tests/helper.rb +9 -0
- data/tests/helpers/mock_helper.rb +15 -0
- data/tests/radosgw/helper.rb +1 -0
- data/tests/radosgw/requests/provisioning/provisioning_tests.rb +170 -0
- data/tests/radosgw/requests/usage/usage_tests.rb +23 -0
- metadata +156 -0
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*~
|
2
|
+
*.gem
|
3
|
+
*.rbc
|
4
|
+
.rbenv
|
5
|
+
.rvmrc
|
6
|
+
.ruby-gemset
|
7
|
+
.ruby-version
|
8
|
+
.bundle
|
9
|
+
.DS_Store
|
10
|
+
.idea
|
11
|
+
.yardoc
|
12
|
+
/tests/.fog
|
13
|
+
bin/*
|
14
|
+
!bin/fog
|
15
|
+
.fog
|
16
|
+
coverage
|
17
|
+
doc/*
|
18
|
+
docs/_site/*
|
19
|
+
Gemfile.lock
|
20
|
+
gemfiles/*.lock
|
21
|
+
yardoc
|
22
|
+
pkg
|
23
|
+
tags
|
data/CONTRIBUTORS.md
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2009-2014 [CONTRIBUTORS.md](https://github.com/fog/fog-radosgw/blob/master/CONTRIBUTORS.md)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Fog::Radosgw
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/fog-radosgw) [](https://travis-ci.org/fog/fog-radosgw) [](https://gemnasium.com/fog/fog-radosgw) [](https://coveralls.io/r/fog/fog-radosgw) [](https://codeclimate.com/github/fog/fog-radosgw) [](https://waffle.io/fog/fog-radosgw)
|
4
|
+
|
5
|
+
Fog backend for provisioning Ceph Radosgw - the Swift and S3 compatible REST API for Ceph.
|
6
|
+
Currently, the gem only supports the S3 API, not Swift.
|
7
|
+
|
8
|
+
Based on the Riak CS backend.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'fog-radosgw'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install fog-radosgw
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Example:
|
27
|
+
|
28
|
+
require 'fog/radosgw'
|
29
|
+
|
30
|
+
provision_client = Fog::Radosgw::Provisioning.new(
|
31
|
+
host: 'objects.example.com',
|
32
|
+
radosgw_access_key_id: 'CHIA1GEE0EIJ9OHSAIK6',
|
33
|
+
radosgw_secret_access_key: 'IFooJ7airaebaele6baihaiw2fequuto9Foh7Sei'
|
34
|
+
)
|
35
|
+
|
36
|
+
response = provision_client.create_user('fog', 'Fog User', 'fog@example.com')
|
37
|
+
userkey = response.body['keys'][0]['access_key']
|
38
|
+
usersecret = response.body['keys'][0]['secret_key']
|
39
|
+
|
40
|
+
## Tests
|
41
|
+
|
42
|
+
Mock tests:
|
43
|
+
|
44
|
+
bundle exec rake mock[radosgw]
|
45
|
+
|
46
|
+
Live tests:
|
47
|
+
|
48
|
+
bundle exec rake live[radosgw]
|
49
|
+
|
50
|
+
To run live tests, you have to place credentials in `tests/.fog`, e.g.:
|
51
|
+
|
52
|
+
default:
|
53
|
+
host: objects.example.com
|
54
|
+
radosgw_access_key_id: CHIA1GEE0EIJ9OHSAIK6
|
55
|
+
radosgw_secret_access_key: IFooJ7airaebaele6baihaiw2fequuto9Foh7Sei
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
Please refer to the
|
60
|
+
[CONTRIBUTING.md](https://github.com/fog/fog/blob/master/CONTRIBUTING.md)
|
61
|
+
page for the main Fog project.
|
62
|
+
|
63
|
+
## License
|
64
|
+
|
65
|
+
Please refer to [LICENSE.md](https://github.com/fog/fog-radosgw/blob/master/LICENSE.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'date'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rubygems/package_task'
|
7
|
+
require 'yard'
|
8
|
+
require 'fog/core'
|
9
|
+
require 'fog/json'
|
10
|
+
|
11
|
+
#############################################################################
|
12
|
+
#
|
13
|
+
# Helper functions
|
14
|
+
#
|
15
|
+
#############################################################################
|
16
|
+
|
17
|
+
def name
|
18
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
19
|
+
end
|
20
|
+
|
21
|
+
def version
|
22
|
+
Fog::Radosgw::VERSION
|
23
|
+
end
|
24
|
+
|
25
|
+
def date
|
26
|
+
Date.today.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def rubyforge_project
|
30
|
+
name
|
31
|
+
end
|
32
|
+
|
33
|
+
def gemspec_file
|
34
|
+
"#{name}.gemspec"
|
35
|
+
end
|
36
|
+
|
37
|
+
def gem_file
|
38
|
+
"#{name}-#{version}.gem"
|
39
|
+
end
|
40
|
+
|
41
|
+
def replace_header(head, header_name)
|
42
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
43
|
+
end
|
44
|
+
|
45
|
+
#############################################################################
|
46
|
+
#
|
47
|
+
# Standard tasks
|
48
|
+
#
|
49
|
+
#############################################################################
|
50
|
+
|
51
|
+
GEM_NAME = "#{name}"
|
52
|
+
task :default => :test
|
53
|
+
task :travis => ['test', 'test:travis']
|
54
|
+
|
55
|
+
Rake::TestTask.new do |t|
|
56
|
+
t.pattern = File.join("spec", "**", "*_spec.rb")
|
57
|
+
end
|
58
|
+
|
59
|
+
namespace :test do
|
60
|
+
mock = ENV['FOG_MOCK'] || 'true'
|
61
|
+
task :travis do
|
62
|
+
sh("export FOG_MOCK=#{mock} && bundle exec shindont")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
desc 'Run mocked tests for a specific provider'
|
67
|
+
task :mock, :provider do |t, args|
|
68
|
+
if args.to_a.size != 1
|
69
|
+
fail 'USAGE: rake mock[<provider>]'
|
70
|
+
end
|
71
|
+
provider = args[:provider]
|
72
|
+
sh("export FOG_MOCK=true && bundle exec shindont tests/#{provider}")
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'Run live tests against a specific provider'
|
76
|
+
task :live, :provider do |t, args|
|
77
|
+
if args.to_a.size != 1
|
78
|
+
fail 'USAGE: rake live[<provider>]'
|
79
|
+
end
|
80
|
+
provider = args[:provider]
|
81
|
+
sh("export FOG_MOCK=false PROVIDER=#{provider} && bundle exec shindont tests/#{provider}")
|
82
|
+
end
|
data/fog-radosgw.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fog/radosgw/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'fog-radosgw'
|
8
|
+
s.version = Fog::Radosgw::VERSION
|
9
|
+
s.authors = %q(Jon Kåre Hellan)
|
10
|
+
s.email = %q(hellan@acm.org)
|
11
|
+
s.summary = %q{Fog backend for provisioning Ceph Radosgw.}
|
12
|
+
s.description = %q{Fog backend for provisioning users on Ceph Radosgw - the Swift and S3 compatible REST API for Ceph.}
|
13
|
+
s.homepage = 'https://github.com/fog/fog-radosgw'
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.files = `git ls-files -z`.split("\x0")
|
17
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
|
+
s.require_paths = %w(lib)
|
20
|
+
|
21
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
22
|
+
s.extra_rdoc_files = %w[README.md LICENSE.md]
|
23
|
+
|
24
|
+
s.add_dependency 'fog-json'
|
25
|
+
s.add_dependency 'fog', '>=1.21.0'
|
26
|
+
s.add_development_dependency 'rake'
|
27
|
+
s.add_development_dependency 'yard'
|
28
|
+
s.add_development_dependency 'shindo'
|
29
|
+
end
|
data/lib/fog/radosgw.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'fog'
|
2
|
+
|
3
|
+
module Fog
|
4
|
+
module Radosgw
|
5
|
+
module MultipartUtils
|
6
|
+
require 'net/http'
|
7
|
+
|
8
|
+
class Headers
|
9
|
+
include Net::HTTPHeader
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
initialize_http_header({})
|
13
|
+
end
|
14
|
+
|
15
|
+
# Parse a single header line into its key and value
|
16
|
+
# @param [String] chunk a single header line
|
17
|
+
def self.parse(chunk)
|
18
|
+
line = chunk.strip
|
19
|
+
# thanks Net::HTTPResponse
|
20
|
+
return [nil,nil] if chunk =~ /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in
|
21
|
+
m = /\A([^:]+):\s*/.match(line)
|
22
|
+
[m[1], m.post_match] rescue [nil, nil]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parses a header line and adds it to the header collection
|
26
|
+
# @param [String] chunk a single header line
|
27
|
+
def parse(chunk)
|
28
|
+
key, value = self.class.parse(chunk)
|
29
|
+
add_field(key, value) if key && value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse(data, boundary)
|
34
|
+
contents = data.match(end_boundary_regex(boundary)).pre_match rescue ""
|
35
|
+
contents.split(inner_boundary_regex(boundary)).reject(&:empty?).map do |part|
|
36
|
+
parse_multipart_section(part)
|
37
|
+
end.compact
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_boundary(header_string)
|
41
|
+
$1 if header_string =~ /boundary=([A-Za-z0-9\'()+_,-.\/:=?]+)/
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def end_boundary_regex(boundary)
|
46
|
+
/\r?\n--#{Regexp.escape(boundary)}--\r?\n?/
|
47
|
+
end
|
48
|
+
|
49
|
+
def inner_boundary_regex(boundary)
|
50
|
+
/\r?\n--#{Regexp.escape(boundary)}\r?\n/
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_multipart_section(part)
|
54
|
+
headers = Headers.new
|
55
|
+
if md = part.match(/\r?\n\r?\n/)
|
56
|
+
body = md.post_match
|
57
|
+
md.pre_match.split(/\r?\n/).each do |line|
|
58
|
+
headers.parse(line)
|
59
|
+
end
|
60
|
+
|
61
|
+
if headers["content-type"] =~ /multipart\/mixed/
|
62
|
+
boundary = extract_boundary(headers.to_hash["content-type"].first)
|
63
|
+
parse(body, boundary)
|
64
|
+
else
|
65
|
+
{:headers => headers.to_hash, :body => body}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module UserUtils
|
72
|
+
|
73
|
+
def update_radosgw_user(user_id, user)
|
74
|
+
path = "admin/user"
|
75
|
+
user_id = Fog::AWS.escape(user_id)
|
76
|
+
params = {
|
77
|
+
:method => 'POST',
|
78
|
+
:path => path,
|
79
|
+
}
|
80
|
+
query = "?uid=#{user_id}&format=json&suspended=#{user[:suspended]}"
|
81
|
+
begin
|
82
|
+
response = Excon.post("#{@scheme}://#{@host}/#{path}#{query}",
|
83
|
+
:headers => signed_headers(params))
|
84
|
+
if !response.body.empty?
|
85
|
+
case response.headers['Content-Type']
|
86
|
+
when 'application/json'
|
87
|
+
response.body = Fog::JSON.decode(response.body)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
response
|
91
|
+
rescue Excon::Errors::NotFound => e
|
92
|
+
raise Fog::Radosgw::Provisioning::NoSuchUser.new
|
93
|
+
rescue Excon::Errors::BadRequest => e
|
94
|
+
raise Fog::Radosgw::Provisioning::ServiceUnavailable.new
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def update_mock_user(user_id, user)
|
99
|
+
if data[user_id]
|
100
|
+
if suspended = user[:suspended]
|
101
|
+
data[user_id][:suspended] = suspended
|
102
|
+
end
|
103
|
+
|
104
|
+
Excon::Response.new.tap do |response|
|
105
|
+
response.status = 200
|
106
|
+
response.body = data[user_id]
|
107
|
+
end
|
108
|
+
else
|
109
|
+
Excon::Response.new.tap do |response|
|
110
|
+
response.status = 403
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module Utils
|
117
|
+
def configure_uri_options(options = {})
|
118
|
+
@host = options[:host] || 'localhost'
|
119
|
+
@persistent = options[:persistent] || true
|
120
|
+
@port = options[:port] || 8080
|
121
|
+
@scheme = options[:scheme] || 'http'
|
122
|
+
end
|
123
|
+
|
124
|
+
def radosgw_uri
|
125
|
+
"#{@scheme}://#{@host}:#{@port}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def signed_headers(params)
|
129
|
+
expires = Fog::Time.now.to_date_header
|
130
|
+
auth = @s3_connection.signature(params,expires)
|
131
|
+
awskey = @radosgw_access_key_id
|
132
|
+
headers = {
|
133
|
+
'Date' => expires,
|
134
|
+
'Authorization' => "AWS #{awskey}:#{auth}"
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
extend Fog::Provider
|
140
|
+
|
141
|
+
service(:provisioning, 'Provisioning')
|
142
|
+
service(:usage, 'Usage')
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'fog/radosgw/core'
|
2
|
+
|
3
|
+
module Fog
|
4
|
+
module Radosgw
|
5
|
+
class Provisioning < Fog::Service
|
6
|
+
|
7
|
+
class UserAlreadyExists < Fog::Radosgw::Provisioning::Error; end
|
8
|
+
class NoSuchUser < Fog::Radosgw::Provisioning::Error; end
|
9
|
+
class ServiceUnavailable < Fog::Radosgw::Provisioning::Error; end
|
10
|
+
|
11
|
+
requires :radosgw_access_key_id, :radosgw_secret_access_key
|
12
|
+
recognizes :host, :path, :port, :scheme, :persistent, :path_style
|
13
|
+
|
14
|
+
request_path 'fog/radosgw/requests/provisioning'
|
15
|
+
request :create_user
|
16
|
+
request :delete_user
|
17
|
+
request :update_user
|
18
|
+
request :disable_user
|
19
|
+
request :enable_user
|
20
|
+
request :list_users
|
21
|
+
request :get_user
|
22
|
+
|
23
|
+
class Mock
|
24
|
+
include Utils
|
25
|
+
|
26
|
+
def self.data
|
27
|
+
@data ||= Hash.new({})
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.reset
|
31
|
+
@data = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(options = {})
|
35
|
+
configure_uri_options(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def data
|
39
|
+
self.class.data[radosgw_uri]
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset_data
|
43
|
+
self.class.data.delete(radosgw_uri)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Real
|
48
|
+
include Utils
|
49
|
+
|
50
|
+
def initialize(options = {})
|
51
|
+
configure_uri_options(options)
|
52
|
+
@radosgw_access_key_id = options[:radosgw_access_key_id]
|
53
|
+
@radosgw_secret_access_key = options[:radosgw_secret_access_key]
|
54
|
+
@connection_options = options[:connection_options] || {}
|
55
|
+
@persistent = options[:persistent] || false
|
56
|
+
@path_style = options[:path_style] || false
|
57
|
+
|
58
|
+
@raw_connection = Fog::XML::Connection.new(radosgw_uri, @persistent, @connection_options)
|
59
|
+
|
60
|
+
@s3_connection = Fog::Storage.new(
|
61
|
+
:provider => 'AWS',
|
62
|
+
:aws_access_key_id => @radosgw_access_key_id,
|
63
|
+
:aws_secret_access_key => @radosgw_secret_access_key,
|
64
|
+
:host => @host,
|
65
|
+
:port => @port,
|
66
|
+
:scheme => @scheme,
|
67
|
+
:path_style => @path_style,
|
68
|
+
:connection_options => @connection_options
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def request(params, parse_response = true, &block)
|
73
|
+
begin
|
74
|
+
response = @raw_connection.request(params.merge({
|
75
|
+
:host => @host,
|
76
|
+
:path => "#{@path}/#{params[:path]}",
|
77
|
+
}), &block)
|
78
|
+
rescue Excon::Errors::HTTPStatusError => error
|
79
|
+
if match = error.message.match(/<Code>(.*?)<\/Code>(?:.*<Message>(.*?)<\/Message>)?/m)
|
80
|
+
case match[1]
|
81
|
+
when 'UserAlreadyExists'
|
82
|
+
raise Fog::Radosgw::Provisioning.const_get(match[1]).new
|
83
|
+
when 'ServiceUnavailable'
|
84
|
+
raise Fog::Radosgw::Provisioning.const_get(match[1]).new
|
85
|
+
else
|
86
|
+
raise error
|
87
|
+
end
|
88
|
+
else
|
89
|
+
raise error
|
90
|
+
end
|
91
|
+
end
|
92
|
+
if !response.body.empty? && parse_response
|
93
|
+
response.body = Fog::JSON.decode(response.body)
|
94
|
+
end
|
95
|
+
response
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|