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.
@@ -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
@@ -0,0 +1,3 @@
1
+ * Jon Kåre Hellan <hellan@acm.org> - port of Riak CS provider to Radosgw
2
+ * Christopher Meiklejohn <christopher.meiklejohn@gmail.com> - Riak CS provider
3
+ and the [contributors to Fog](https://github.com/fog/fog/blob/master/CONTRIBUTORS.md)
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
@@ -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.
@@ -0,0 +1,65 @@
1
+ # Fog::Radosgw
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/fog-radosgw.svg)](http://badge.fury.io/rb/fog-radosgw) [![Build Status](https://travis-ci.org/fog/fog-radosgw.svg?branch=master)](https://travis-ci.org/fog/fog-radosgw) [![Dependency Status](https://gemnasium.com/fog/fog-radosgw.svg)](https://gemnasium.com/fog/fog-radosgw) [![Coverage Status](https://img.shields.io/coveralls/fog/fog-radosgw.svg)](https://coveralls.io/r/fog/fog-radosgw) [![Code Climate](https://codeclimate.com/github/fog/fog-radosgw.png)](https://codeclimate.com/github/fog/fog-radosgw) [![Stories in Ready](https://badge.waffle.io/fog/fog-radosgw.png?label=ready&title=Ready)](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).
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ require 'fog/radosgw/provisioning'
2
+ require 'fog/radosgw/usage'
@@ -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