ceph_storage 0.1.0
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.
- checksums.yaml +7 -0
- data/.cluster.yml.example +19 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +110 -0
- data/Guardfile +20 -0
- data/README.md +108 -0
- data/Rakefile +7 -0
- data/ceph_storage.gemspec +36 -0
- data/lib/ceph_storage.rb +28 -0
- data/lib/ceph_storage/cluster.rb +93 -0
- data/lib/ceph_storage/cluster_factory.rb +24 -0
- data/lib/ceph_storage/cluster_wrapper.rb +16 -0
- data/lib/ceph_storage/pool.rb +126 -0
- data/lib/ceph_storage/pool_enumerator.rb +24 -0
- data/lib/ceph_storage/pool_factory.rb +15 -0
- data/lib/ceph_storage/pool_wrapper.rb +17 -0
- data/lib/ceph_storage/storage_object.rb +48 -0
- data/lib/ceph_storage/storage_object/file_storage_object.rb +28 -0
- data/lib/ceph_storage/storage_object/rados_storage_object.rb +41 -0
- data/lib/ceph_storage/storage_object/rados_storage_object_enumerator.rb +29 -0
- data/lib/ceph_storage/storage_object/rados_wrapper.rb +21 -0
- data/lib/ceph_storage/storage_object/url_storage_object.rb +34 -0
- data/lib/ceph_storage/storage_object/xattr.rb +18 -0
- data/lib/ceph_storage/storage_object/xattr_enumerator.rb +29 -0
- data/lib/ceph_storage/version.rb +3 -0
- data/spec/ceph_storage_cluster_factory_spec.rb +14 -0
- data/spec/ceph_storage_cluster_spec.rb +76 -0
- data/spec/ceph_storage_file_storage_object_spec.rb +31 -0
- data/spec/ceph_storage_pool_enumerator.rb +34 -0
- data/spec/ceph_storage_pool_factory_spec.rb +14 -0
- data/spec/ceph_storage_pool_spec.rb +167 -0
- data/spec/ceph_storage_rados_storage_object_enumerator_spec.rb +72 -0
- data/spec/ceph_storage_rados_storage_object_spec.rb +112 -0
- data/spec/ceph_storage_storage_object_spec.rb +98 -0
- data/spec/ceph_storage_xattr_enumerator_spec.rb +75 -0
- data/spec/ceph_storage_xattr_spec.rb +69 -0
- data/spec/spec_helper.rb +30 -0
- data/tasks/rspec.rake +4 -0
- data/tasks/rubocop.rake +13 -0
- metadata +281 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f11517603a21b107977fee01ece18f2974dc0d98
|
4
|
+
data.tar.gz: 033f874f008ff2e9e30f658bb68a8ed195877dd4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79aa89777a93a9fa26d30e2db0886b6aa70ba0ab6ee008e7a3be6d0abf46597246fc56851cc3e9bda9921af26358e758eedea21237ebf26eaf754ecebe8e1506
|
7
|
+
data.tar.gz: dbe0c85a8d9739bc50f78ac44783341c8717ca13f7692d8fc0bfec40af556812a3c2bb1b2ba61ccdb26c2c07e46049d78e3574a543758a5879e07900dc1654c5
|
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
:cluster: ceph
|
3
|
+
:config_dir: "/etc/ceph"
|
4
|
+
:user: client.admin
|
5
|
+
:flags: 0
|
6
|
+
:readable: true # User has +r mon cap
|
7
|
+
:writable: true # User has +w mon cap
|
8
|
+
:pool:
|
9
|
+
:name: rspec_test
|
10
|
+
:create_delete: true # Means you can run create and delete on this pool {beware of dragons!}
|
11
|
+
# If you accidentally set this and you have a pool with the same 'name' (see above) it will
|
12
|
+
# delete it at the end of the rspec test along with all of the data in it!
|
13
|
+
# This only works if the user has +w mon cap
|
14
|
+
:rule_id: 2
|
15
|
+
:writable: true # Means this pool has read/write permissions {beware of dragons!}
|
16
|
+
# If you accidentally set this to true and you have objects in your pool
|
17
|
+
# that you want to keep with name = object_name, it will overwrite and then delete them!
|
18
|
+
# this only works if the user has +w osd cap at least on pool[:name]
|
19
|
+
:object_name: rspec_test_object
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.cluster.yml
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ceph_storage (0.1.0)
|
5
|
+
activesupport (~> 3.0)
|
6
|
+
ceph-ruby-livelink (~> 1.5)
|
7
|
+
facets (~> 3.0)
|
8
|
+
ffi (~> 1.9)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
activesupport (3.2.22.2)
|
14
|
+
i18n (~> 0.6, >= 0.6.4)
|
15
|
+
multi_json (~> 1.0)
|
16
|
+
ast (2.2.0)
|
17
|
+
ceph-ruby-livelink (1.5.1)
|
18
|
+
activesupport (~> 3.0)
|
19
|
+
ffi (~> 1.9)
|
20
|
+
coderay (1.1.1)
|
21
|
+
diff-lcs (1.2.5)
|
22
|
+
facets (3.1.0)
|
23
|
+
ffi (1.9.10)
|
24
|
+
formatador (0.2.5)
|
25
|
+
guard (2.14.0)
|
26
|
+
formatador (>= 0.2.4)
|
27
|
+
listen (>= 2.7, < 4.0)
|
28
|
+
lumberjack (~> 1.0)
|
29
|
+
nenv (~> 0.1)
|
30
|
+
notiffany (~> 0.0)
|
31
|
+
pry (>= 0.9.12)
|
32
|
+
shellany (~> 0.0)
|
33
|
+
thor (>= 0.18.1)
|
34
|
+
guard-bundler (2.1.0)
|
35
|
+
bundler (~> 1.0)
|
36
|
+
guard (~> 2.2)
|
37
|
+
guard-compat (~> 1.1)
|
38
|
+
guard-compat (1.2.1)
|
39
|
+
guard-rake (1.0.0)
|
40
|
+
guard
|
41
|
+
rake
|
42
|
+
guard-rspec (4.7.0)
|
43
|
+
guard (~> 2.1)
|
44
|
+
guard-compat (~> 1.1)
|
45
|
+
rspec (>= 2.99.0, < 4.0)
|
46
|
+
i18n (0.7.0)
|
47
|
+
listen (3.0.6)
|
48
|
+
rb-fsevent (>= 0.9.3)
|
49
|
+
rb-inotify (>= 0.9.7)
|
50
|
+
lumberjack (1.0.10)
|
51
|
+
method_source (0.8.2)
|
52
|
+
multi_json (1.12.1)
|
53
|
+
nenv (0.3.0)
|
54
|
+
notiffany (0.1.0)
|
55
|
+
nenv (~> 0.1)
|
56
|
+
shellany (~> 0.0)
|
57
|
+
parser (2.3.1.0)
|
58
|
+
ast (~> 2.2)
|
59
|
+
powerpack (0.1.1)
|
60
|
+
pry (0.10.3)
|
61
|
+
coderay (~> 1.1.0)
|
62
|
+
method_source (~> 0.8.1)
|
63
|
+
slop (~> 3.4)
|
64
|
+
rainbow (2.1.0)
|
65
|
+
rake (11.1.2)
|
66
|
+
rb-fsevent (0.9.7)
|
67
|
+
rb-inotify (0.9.7)
|
68
|
+
ffi (>= 0.5.0)
|
69
|
+
rspec (3.4.0)
|
70
|
+
rspec-core (~> 3.4.0)
|
71
|
+
rspec-expectations (~> 3.4.0)
|
72
|
+
rspec-mocks (~> 3.4.0)
|
73
|
+
rspec-core (3.4.4)
|
74
|
+
rspec-support (~> 3.4.0)
|
75
|
+
rspec-expectations (3.4.0)
|
76
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
77
|
+
rspec-support (~> 3.4.0)
|
78
|
+
rspec-mocks (3.4.1)
|
79
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
80
|
+
rspec-support (~> 3.4.0)
|
81
|
+
rspec-support (3.4.1)
|
82
|
+
rubocop (0.40.0)
|
83
|
+
parser (>= 2.3.1.0, < 3.0)
|
84
|
+
powerpack (~> 0.1)
|
85
|
+
rainbow (>= 1.99.1, < 3.0)
|
86
|
+
ruby-progressbar (~> 1.7)
|
87
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
88
|
+
ruby-progressbar (1.8.1)
|
89
|
+
shellany (0.0.1)
|
90
|
+
slop (3.6.0)
|
91
|
+
thor (0.19.1)
|
92
|
+
unicode-display_width (1.0.5)
|
93
|
+
|
94
|
+
PLATFORMS
|
95
|
+
ruby
|
96
|
+
|
97
|
+
DEPENDENCIES
|
98
|
+
bundler (~> 1.3)
|
99
|
+
ceph_storage!
|
100
|
+
guard (~> 2.13)
|
101
|
+
guard-bundler (~> 2.1)
|
102
|
+
guard-rake (~> 1.0)
|
103
|
+
guard-rspec (~> 4.6)
|
104
|
+
listen (= 3.0.6)
|
105
|
+
rake (~> 11.1)
|
106
|
+
rspec (~> 3.4)
|
107
|
+
rubocop (~> 0.39)
|
108
|
+
|
109
|
+
BUNDLED WITH
|
110
|
+
1.11.2
|
data/Guardfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
guard 'rake', task: 'test' do
|
19
|
+
watch(%r{^(spec|lib/.*)/.*.rb})
|
20
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# Ceph::Storage
|
2
|
+
|
3
|
+
A userspace wrapper around the [http://github.com/ceph/ceph-ruby/](Ceph::Ruby) library to allow applications to pool their requests to the ceph backend and conduct thread safe operations with ceph
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
Add this line to your appication's Gemfile
|
7
|
+
gem 'ceph_storage'
|
8
|
+
|
9
|
+
And then execute:
|
10
|
+
$ bundle
|
11
|
+
|
12
|
+
Or install it yourself as:
|
13
|
+
$gem install ceph_storage
|
14
|
+
|
15
|
+
|
16
|
+
## About
|
17
|
+
The Ceph-ruby gem provides an FFI wrapper around librados in order to facilitate connections to ceph and to provide an easy management of objects and cluster settings.
|
18
|
+
There are some limitations of this model in that when using it to download large amounts of files in many scenarios there can be significant overhead of connecting to
|
19
|
+
the cluster.
|
20
|
+
|
21
|
+
This library uses the Facets::Multiton model in order to ensure that if you pass the same attributes into cluster and pool objects, you will always get the same objects
|
22
|
+
back.
|
23
|
+
|
24
|
+
## Pool collection
|
25
|
+
In order to provide thread safety, the Pool object carries with it a collection of `::CephRuby::Pool` objects. As such operations on this will reuse an unused connection or spawn a new pool
|
26
|
+
connection if no free one exists. On completion (or push) the pool object will continue to stay open to serve further objects.
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
require 'ceph_storage'
|
31
|
+
config = { cluster: 'ceph', user: 'client.admin', flags: 0, config_dir: '/etc/ceph' } # defaults
|
32
|
+
|
33
|
+
cluster1 = CephStorage::ClusterFactory.build(config)
|
34
|
+
|
35
|
+
cluster2 = CephStorage::ClusterFactory.build(config)
|
36
|
+
|
37
|
+
cluster1.equal? cluster2 # => true
|
38
|
+
|
39
|
+
pool1 = cluster1.pool('foo')
|
40
|
+
|
41
|
+
pool2 = cluster2.pool('foo')
|
42
|
+
|
43
|
+
pool1.equal? pool2 # => true
|
44
|
+
|
45
|
+
|
46
|
+
pool1.storage_object('bar') do |file|
|
47
|
+
puts file.read_file
|
48
|
+
end
|
49
|
+
|
50
|
+
### Using blocks
|
51
|
+
# Will connect to ceph, open a pool, download the contents
|
52
|
+
# of a file
|
53
|
+
CephStorage::ClusterFactory.build(config) do |c|
|
54
|
+
c.pool('foo') do |p|
|
55
|
+
# NB Storage_object as opposed to rados_object
|
56
|
+
p.storage_object('bar') |f|
|
57
|
+
puts f.read_file
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Does the same thing, but should reuse the same objects
|
63
|
+
# Hence the only latency is downloading the file contents
|
64
|
+
CephStorage::ClusterFactory.build(config) do |c|
|
65
|
+
c.pool('foo') do |p|
|
66
|
+
p.storage_object('bar') |f|
|
67
|
+
puts f.read_file
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
## Raw Pool object
|
73
|
+
|
74
|
+
If there is some functionality that you need that isn't provided, you can expose the raw `::CephRuby::Pool` objects by calling `pool.rados_pool`. This will return an object and mark it 'in use' until you push it back into the collection via `rados_object=` Be sure to do this lest your memory leak.
|
75
|
+
|
76
|
+
### Storage Object interfaces
|
77
|
+
|
78
|
+
The `StorageObject` interface provides some additional functionality to ceph on top of the CephRuby bindings
|
79
|
+
|
80
|
+
* `CephStorage::StorageObject::FileStorageObject` (wrapper for ::File)
|
81
|
+
* `CephStorage::StorageObject::URLStorageObject` (wrapper for url-open)
|
82
|
+
|
83
|
+
These classes are designed to allow you to copy and move files from ceph to other storage mechanisms. It is designed to be extensible
|
84
|
+
|
85
|
+
f_storage_obj1 = CephStorage::StorageObject::FileStorageObject.new 'path/to/file1'
|
86
|
+
f_storage_obj2 = CephStorage::StorageObject::URLStorageObject.new 'http://example.com'
|
87
|
+
|
88
|
+
CephStorage::ClusterFactory.build(config) do |c|
|
89
|
+
c.pool('foo') do |p|
|
90
|
+
p.storage_object('bar') |f|
|
91
|
+
f.move f_storage_obj1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
CephStorage::ClusterFactory.build(config) do |c|
|
97
|
+
c.pool('foo') do |p|
|
98
|
+
p.storage_object('baz') |f|
|
99
|
+
f_storage_obj1.copy f
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
In order for this to work, you class should extend CephStorage::StorageObject and implement `read_file` and `write_file` methods.
|
106
|
+
URLStorageObject will raise exception for `write_file` as not implemented (hence move will not work).
|
107
|
+
|
108
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ceph_storage/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'ceph_storage'
|
8
|
+
gem.version = CephStorage::VERSION
|
9
|
+
gem.authors = ['Livelink Technology LTD', 'Stuart Harland']
|
10
|
+
gem.email = ['infraops@livelinktechnology.net']
|
11
|
+
gem.description = 'Easy access of Objects in ceph'
|
12
|
+
gem.summary = 'Ceph-ruby provides an API, however this gem provides '\
|
13
|
+
'consistent connection to ceph pools and clusters '\
|
14
|
+
'using multiton objects'
|
15
|
+
gem.homepage = 'https://github.com/livelink/ceph_storage'
|
16
|
+
gem.license = 'MIT'
|
17
|
+
|
18
|
+
gem.files = `git ls-files`.split($RS)
|
19
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
20
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
21
|
+
gem.require_paths = ['lib']
|
22
|
+
|
23
|
+
gem.add_dependency('ffi', '~> 1.9')
|
24
|
+
gem.add_dependency('ceph-ruby-livelink', '~> 1.5')
|
25
|
+
gem.add_dependency('activesupport', '~> 3.0')
|
26
|
+
gem.add_dependency('facets', '~> 3.0')
|
27
|
+
gem.add_development_dependency 'guard', '~> 2.13'
|
28
|
+
gem.add_development_dependency 'listen', '= 3.0.6'
|
29
|
+
gem.add_development_dependency 'guard-rake', '~> 1.0'
|
30
|
+
gem.add_development_dependency 'guard-rspec', '~> 4.6'
|
31
|
+
gem.add_development_dependency 'guard-bundler', '~> 2.1'
|
32
|
+
gem.add_development_dependency 'rubocop', '~> 0.39'
|
33
|
+
gem.add_development_dependency 'rspec', '~> 3.4'
|
34
|
+
gem.add_development_dependency 'rake', '~> 11.1'
|
35
|
+
gem.add_development_dependency 'bundler', '~> 1.3'
|
36
|
+
end
|
data/lib/ceph_storage.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'ceph-ruby'
|
3
|
+
require 'facets/multiton'
|
4
|
+
require 'ceph_storage/cluster_wrapper'
|
5
|
+
require 'ceph_storage/cluster_factory'
|
6
|
+
require 'ceph_storage/cluster'
|
7
|
+
require 'ceph_storage/pool_wrapper'
|
8
|
+
require 'ceph_storage/pool_factory'
|
9
|
+
require 'ceph_storage/pool'
|
10
|
+
require 'ceph_storage/pool_enumerator'
|
11
|
+
require 'ceph_storage/storage_object'
|
12
|
+
require 'ceph_storage/storage_object/rados_wrapper'
|
13
|
+
require 'ceph_storage/storage_object/rados_storage_object'
|
14
|
+
require 'ceph_storage/storage_object/rados_storage_object_enumerator'
|
15
|
+
require 'ceph_storage/storage_object/file_storage_object'
|
16
|
+
require 'ceph_storage/storage_object/url_storage_object'
|
17
|
+
require 'ceph_storage/storage_object/xattr'
|
18
|
+
require 'ceph_storage/storage_object/xattr_enumerator'
|
19
|
+
|
20
|
+
# An application for moving files into and out of Ceph
|
21
|
+
module CephStorage
|
22
|
+
mattr_accessor :logger
|
23
|
+
|
24
|
+
def self.log(message)
|
25
|
+
return unless logger
|
26
|
+
logger.info("CephStorage: #{message}")
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module CephStorage
|
2
|
+
# Create a cluster object
|
3
|
+
# Returns only a single cluster object for given settings
|
4
|
+
class Cluster < ClusterFactory
|
5
|
+
extend CephStorage::ClusterWrapper
|
6
|
+
include Multiton
|
7
|
+
# Configure default values for this object
|
8
|
+
attr_reader :cluster_fd, :cluster, :config_dir, :user, :flags
|
9
|
+
|
10
|
+
wrap_me :pool_id_by_name, :pool_name_by_id, :status, :fsid
|
11
|
+
|
12
|
+
private :connect, :setup_using_file
|
13
|
+
|
14
|
+
# Create a new object
|
15
|
+
def initialize(config_dir: CONFIG_DIR, cluster: CLUSTER,
|
16
|
+
user: USER, flags: FLAGS)
|
17
|
+
CephRuby.logger = CephStorage.logger
|
18
|
+
init(config_dir, cluster, user, flags)
|
19
|
+
log("init config_dir #{config_dir}, cluster #{cluster}, user: #{user}")
|
20
|
+
|
21
|
+
open
|
22
|
+
|
23
|
+
yield(@cluster_fd) if block_given?
|
24
|
+
rescue StandardError
|
25
|
+
log("unable to open cluster #{cluster}")
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
|
29
|
+
def rados_cluster
|
30
|
+
return @cluster_fd unless block_given?
|
31
|
+
yield(@cluster_fd)
|
32
|
+
end
|
33
|
+
|
34
|
+
def pool(name)
|
35
|
+
ensure_open
|
36
|
+
p = CephStorage::PoolFactory.build(self, name)
|
37
|
+
yield(p) if block_given?
|
38
|
+
p
|
39
|
+
end
|
40
|
+
|
41
|
+
def pools
|
42
|
+
CephStorage::PoolEnumerator.new(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def shutdown
|
46
|
+
return unless open?
|
47
|
+
log('shutdown')
|
48
|
+
@cluster_fd.shutdown
|
49
|
+
end
|
50
|
+
|
51
|
+
def open?
|
52
|
+
!@cluster_fd.nil? && !@cluster_fd.handle.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def ensure_open
|
56
|
+
return if open?
|
57
|
+
open
|
58
|
+
end
|
59
|
+
|
60
|
+
def open
|
61
|
+
return if open?
|
62
|
+
log('open')
|
63
|
+
fd_init unless @cluster_fd
|
64
|
+
@cluster_fd.connect unless open?
|
65
|
+
end
|
66
|
+
|
67
|
+
def path
|
68
|
+
"ceph://#{cluster}"
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def log(message)
|
74
|
+
CephStorage.log("cluster #{cluster} #{message}")
|
75
|
+
end
|
76
|
+
|
77
|
+
def fd_init
|
78
|
+
@cluster_fd = ::CephRuby::Cluster.new(
|
79
|
+
config_path: config_dir,
|
80
|
+
cluster: cluster,
|
81
|
+
user: user,
|
82
|
+
flags: flags
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def init(config_dir, cluster, user, flags)
|
87
|
+
@cluster = cluster
|
88
|
+
@user = user
|
89
|
+
@config_dir = config_dir
|
90
|
+
@flags = flags
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|