confman 0.0.2
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 +22 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +27 -0
- data/Rakefile +1 -0
- data/bin/confman +9 -0
- data/config/test.yml +4 -0
- data/confman.gemspec +26 -0
- data/lib/confman.rb +79 -0
- data/lib/confman/access.rb +107 -0
- data/lib/confman/api.rb +176 -0
- data/lib/confman/cli.rb +106 -0
- data/lib/confman/data_store.rb +36 -0
- data/lib/confman/version.rb +3 -0
- data/spec/confman_spec.rb +216 -0
- data/spec/data_store_spec.rb +53 -0
- metadata +129 -0
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
.testconfman/
|
19
|
+
bin/.confman
|
20
|
+
|
21
|
+
# vim artifacts
|
22
|
+
**.swp
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Masahji Stewart
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Confman
|
2
|
+
|
3
|
+
Ruby client to access Confman Server
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'confman'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install confman
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Confman.server = '<confman server>'
|
22
|
+
Confman.key = '<key>'
|
23
|
+
Confman.secret = '<secret>'
|
24
|
+
|
25
|
+
Confman.load_conf_set('somename', 'One')
|
26
|
+
# Assuming the presence of conf pair ('foo', 'bar')
|
27
|
+
Confman.somename.foo.should == 'bar'
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/confman
ADDED
data/config/test.yml
ADDED
data/confman.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'confman/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "confman"
|
8
|
+
spec.version = Confman::VERSION
|
9
|
+
spec.authors = ["Masahji Stewart"]
|
10
|
+
spec.email = ["masahji@synctree.com"]
|
11
|
+
spec.description = 'Confman ruby client'
|
12
|
+
spec.summary = ''
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "rest-client", "~> 1"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rspec", "~> 2.6"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
end
|
data/lib/confman.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require "confman/version"
|
2
|
+
require "confman/data_store"
|
3
|
+
require "confman/api"
|
4
|
+
require "confman/access"
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
module Confman
|
8
|
+
class << self
|
9
|
+
attr_writer :logger
|
10
|
+
|
11
|
+
def load_conf_set(getter, conf_set_name)
|
12
|
+
pairs = begin
|
13
|
+
conf_set = Confman.api.find_by_name(conf_set_name)
|
14
|
+
DataStore.write(getter, conf_set_name, conf_set.pairs.to_json)
|
15
|
+
conf_set.pairs
|
16
|
+
rescue => ex
|
17
|
+
logger.error("load_conf_set(#{getter}, #{conf_set_name}): #{ex}")
|
18
|
+
logger.error(ex.backtrace[0..5].join("\n"))
|
19
|
+
d = DataStore.read(getter, conf_set_name)
|
20
|
+
raise(ex) if d.nil? || d.blank?
|
21
|
+
JSON.parse(d)
|
22
|
+
end
|
23
|
+
|
24
|
+
self.define_singleton_method(getter.to_s.to_sym) do
|
25
|
+
OpenStruct.new(pairs)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def api
|
30
|
+
@api ||= Confman::API.new
|
31
|
+
@api
|
32
|
+
end
|
33
|
+
|
34
|
+
def access
|
35
|
+
@access ||= Confman::Access.new(api)
|
36
|
+
@access
|
37
|
+
end
|
38
|
+
|
39
|
+
def logger
|
40
|
+
@logger ||= Logger.new(STDERR)
|
41
|
+
@logger
|
42
|
+
end
|
43
|
+
|
44
|
+
end # end class << self
|
45
|
+
|
46
|
+
class ConfFileNotFoundError < StandardError
|
47
|
+
def initialize(msg)
|
48
|
+
super(msg)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
unless Hash.new.respond_to?(:symbolize_keys!)
|
55
|
+
class Hash
|
56
|
+
def symbolize_keys!
|
57
|
+
keys.each do |key|
|
58
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
59
|
+
end
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
unless nil.respond_to?(:blank?)
|
66
|
+
class NilClass
|
67
|
+
def blank?
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
unless "".respond_to?(:blank?)
|
74
|
+
class String
|
75
|
+
def blank?
|
76
|
+
self !~ /[^[:space:]]/
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'confman'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
class Confman::Access
|
5
|
+
attr_accessor :api
|
6
|
+
|
7
|
+
def initialize(api = Confman.api)
|
8
|
+
self.api = api
|
9
|
+
end
|
10
|
+
|
11
|
+
def extract_keys(file = authorized_keys_location)
|
12
|
+
r = { :keys => [] }
|
13
|
+
return r unless File.exists?(file)
|
14
|
+
File.readlines(file).each do |key|
|
15
|
+
key.chomp!
|
16
|
+
if key =~ /^#\sAM\s([^=]+)=(.*)$/
|
17
|
+
r[$1.to_sym] = $2
|
18
|
+
elsif key =~ /^ssh-/
|
19
|
+
r[:keys].push(key.chomp)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
r
|
23
|
+
end
|
24
|
+
|
25
|
+
def save_keys(key_info, file = "#{authorized_keys_location}.#{Time.now.to_i}")
|
26
|
+
File.open(file, "w") do |fh|
|
27
|
+
key_info.each do |attribute, value|
|
28
|
+
|
29
|
+
if attribute == :manual_keys
|
30
|
+
key_info[attribute].each do |k|
|
31
|
+
fh.puts k
|
32
|
+
end
|
33
|
+
|
34
|
+
elsif attribute == :users
|
35
|
+
key_info[attribute].each do |u, keys|
|
36
|
+
keys.each do |k|
|
37
|
+
fh.puts("environment=\"AM_USER=#{u}\" #{k}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
else
|
42
|
+
fh.puts("# AM #{attribute}=#{value}")
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
File.chmod(0600, file)
|
49
|
+
|
50
|
+
file
|
51
|
+
end
|
52
|
+
|
53
|
+
def reset_keys
|
54
|
+
current_keys = extract_keys
|
55
|
+
new_keys = request_new_keys
|
56
|
+
|
57
|
+
if new_keys[:updated_at] != current_keys[:updated_at]
|
58
|
+
new_keys[:manual_keys] = current_keys[:keys].reject { |key| key =~ /AM_USER/ }
|
59
|
+
|
60
|
+
new_authorized_keys_file = "#{authorized_keys_location}.#{Time.now.to_i}"
|
61
|
+
save_keys(new_keys, new_authorized_keys_file)
|
62
|
+
FileUtils.cp(new_authorized_keys_file, authorized_keys_location)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
def authorized_keys_location
|
68
|
+
ssh_dir = "#{ENV["HOME"]}/.ssh"
|
69
|
+
FileUtils.mkdir_p(ssh_dir) unless File.exists?(ssh_dir)
|
70
|
+
return "#{ssh_dir}/authorized_keys"
|
71
|
+
end
|
72
|
+
|
73
|
+
def decrypt(encrypted_data)
|
74
|
+
encrypted_data = Base64.decode64(encrypted_data)
|
75
|
+
if encrypted_data =~ /^0A/
|
76
|
+
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
|
77
|
+
cipher.decrypt
|
78
|
+
cipher.key = rsa.public_decrypt(encrypted_data[2, 128])
|
79
|
+
cipher.iv = rsa.public_decrypt(encrypted_data[130, 128])
|
80
|
+
data = cipher.update(encrypted_data[258..-1])
|
81
|
+
data << cipher.final
|
82
|
+
return data
|
83
|
+
else
|
84
|
+
raise "invalid data found"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def rsa
|
89
|
+
@rsa ||= OpenSSL::PKey::RSA.new(File.read("#{self.api.config_dir}/.asym.key"))
|
90
|
+
@rsa
|
91
|
+
end
|
92
|
+
|
93
|
+
def request_new_keys
|
94
|
+
begin
|
95
|
+
results, response = Confman.api.request(:get, "computing_resources/access_keys", {})
|
96
|
+
rescue RestClient::Conflict
|
97
|
+
STDERR.puts("This instance has no key. Please run 'confman init' as root to reset your key for this instance")
|
98
|
+
return nil
|
99
|
+
end
|
100
|
+
|
101
|
+
results.update(JSON.parse(decrypt(results[:data])))
|
102
|
+
results.symbolize_keys!
|
103
|
+
results.delete(:data)
|
104
|
+
return results
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
data/lib/confman/api.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'json'
|
4
|
+
require 'timeout'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
|
8
|
+
module Confman
|
9
|
+
class API
|
10
|
+
attr_accessor :endpoint_url
|
11
|
+
attr_accessor :api_key
|
12
|
+
attr_accessor :secret
|
13
|
+
attr_accessor :no_cloud
|
14
|
+
attr_writer :config_dir
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
self.config_dir = "/etc/confman"
|
18
|
+
end
|
19
|
+
|
20
|
+
def config_dir
|
21
|
+
# Create the directory
|
22
|
+
unless Dir.exists?(@config_dir)
|
23
|
+
FileUtils.mkdir_p(@config_dir)
|
24
|
+
File.chmod(0755, @config_dir)
|
25
|
+
end
|
26
|
+
|
27
|
+
@config_dir
|
28
|
+
end
|
29
|
+
|
30
|
+
def config_path
|
31
|
+
File.join(config_dir, "config.json")
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_config(path = config_path)
|
35
|
+
if File.exists?(path)
|
36
|
+
config_hash = JSON.parse IO.read(path)
|
37
|
+
|
38
|
+
config_hash.each do |key, value|
|
39
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
40
|
+
end
|
41
|
+
return config_hash
|
42
|
+
end
|
43
|
+
return nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_by_name(name)
|
47
|
+
begin
|
48
|
+
results = search(name: name)
|
49
|
+
raise RestClient::ResourceNotFound if results[:count] == 0
|
50
|
+
conf_set_hash = results[:results].first
|
51
|
+
DataStore.write(@api_key, name, conf_set_hash.to_json)
|
52
|
+
ConfSet.new(self, conf_set_hash)
|
53
|
+
rescue => ex
|
54
|
+
Confman.logger.error("find_by_name(#{name}): #{ex}")
|
55
|
+
Confman.logger.error(ex.backtrace[0..5].join("\n"))
|
56
|
+
if conf_set_str = DataStore.read(@api_key, name)
|
57
|
+
ConfSet.new(self, JSON.parse(conf_set_str))
|
58
|
+
else
|
59
|
+
raise ex
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def search(query = {})
|
65
|
+
results, response = request(:get, "confman/sets/search", query)
|
66
|
+
results
|
67
|
+
end
|
68
|
+
|
69
|
+
def conf_sets(query = {})
|
70
|
+
results, response = request(:get, "confman/sets", query)
|
71
|
+
response
|
72
|
+
end
|
73
|
+
|
74
|
+
def reset_server_key!
|
75
|
+
results, response = Confman.api.request(:get, "computing_resources/key", {})
|
76
|
+
File.open("#{config_dir}/.asym.key", "w") do |fh|
|
77
|
+
fh.write results[:data]
|
78
|
+
end
|
79
|
+
File.chmod(0644, "#{config_dir}/.asym.key")
|
80
|
+
end
|
81
|
+
|
82
|
+
def request(method, path, object)
|
83
|
+
request_args = [{ accept: :json, content_type: :json }]
|
84
|
+
|
85
|
+
if method == :get
|
86
|
+
request_args.first[:params] = object
|
87
|
+
else
|
88
|
+
request_args.unshift(object.to_json)
|
89
|
+
end
|
90
|
+
|
91
|
+
response = resource[path].send(method, *request_args)
|
92
|
+
results = JSON.parse(response.to_s) if response.to_s.length > 0
|
93
|
+
results.symbolize_keys! if results.kind_of?(Hash)
|
94
|
+
return results, response
|
95
|
+
end
|
96
|
+
|
97
|
+
def resource
|
98
|
+
timestamp = Time.now.utc.to_i
|
99
|
+
secret_hash = Digest::MD5.hexdigest("#{secret}:#{timestamp}")
|
100
|
+
|
101
|
+
@resource ||= RestClient::Resource.new(endpoint_url, :headers => {
|
102
|
+
:cloud_meta => Base64.encode64(cloud_metadata.to_json).gsub(/\s+/, "")
|
103
|
+
})
|
104
|
+
@resource.options[:user] = api_key
|
105
|
+
@resource.options[:password] = "#{secret_hash}:#{timestamp}"
|
106
|
+
@resource
|
107
|
+
end
|
108
|
+
|
109
|
+
def cloud_metadata
|
110
|
+
return nil if no_cloud
|
111
|
+
@cloud_metadata ||= aws_metadata
|
112
|
+
@cloud_metadata
|
113
|
+
end
|
114
|
+
|
115
|
+
def aws_metadata(fields = %w(
|
116
|
+
ami-id availability-zone
|
117
|
+
instance-id instance-type
|
118
|
+
kernel-id local-hostname
|
119
|
+
mac public-hostname))
|
120
|
+
|
121
|
+
meta = {}
|
122
|
+
begin
|
123
|
+
fields.each do |key|
|
124
|
+
Timeout::timeout(1) do
|
125
|
+
begin
|
126
|
+
meta[key] = RestClient.get("http://169.254.169.254/latest/meta-data/#{key}").strip
|
127
|
+
rescue RestClient::ResourceNotFound => ex
|
128
|
+
Confman.logger.info("aws_metadata:#{key} #{ex}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
rescue => ex
|
133
|
+
Confman.logger.info("aws_metadata:#{ex}")
|
134
|
+
end
|
135
|
+
meta['type'] = 'aws' if meta.size > 0
|
136
|
+
return meta
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
class ConfSet
|
142
|
+
attr_reader :api
|
143
|
+
attr_accessor :id, :created_at, :updated_at, :name, :conf_pairs
|
144
|
+
|
145
|
+
def initialize(api, fields = {})
|
146
|
+
@api = api
|
147
|
+
fields.each do |k,v|
|
148
|
+
self.send("#{k}=", v) if self.respond_to?("#{k}=")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def update_pairs(pairs)
|
153
|
+
results, response = api.request(:put, "confman/sets/#{id}/update_pairs", conf_pairs: pairs)
|
154
|
+
end
|
155
|
+
|
156
|
+
def pairs
|
157
|
+
Hash[conf_pairs.collect { |p| p.symbolize_keys!; [p[:name], p[:value]] }]
|
158
|
+
end
|
159
|
+
|
160
|
+
def as_json
|
161
|
+
{
|
162
|
+
:id => id,
|
163
|
+
:name => name,
|
164
|
+
:created_at => created_at,
|
165
|
+
:updated_at => updated_at,
|
166
|
+
:conf_pairs => conf_pairs
|
167
|
+
}
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_json
|
171
|
+
as_json.to_json
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
data/lib/confman/cli.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'confman'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
class Confman::CLI
|
5
|
+
@@options = {}
|
6
|
+
@@opt_parser = nil
|
7
|
+
|
8
|
+
def self.start(args)
|
9
|
+
Confman.logger.level = 2
|
10
|
+
|
11
|
+
command = args.shift
|
12
|
+
|
13
|
+
begin
|
14
|
+
Confman.api.load_config
|
15
|
+
rescue Errno::EACCES => ex
|
16
|
+
Confman.logger.error("Unable to load config: #{ex}\nPlease run 'confman init' as root to continue")
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
@@opt_parser = OptionParser.new do |opt|
|
21
|
+
opt.banner = "Usage: confman COMMAND OPTIONS"
|
22
|
+
opt.separator ""
|
23
|
+
opt.separator "Commands"
|
24
|
+
opt.separator " exportall -f <outputfile>: Imports all the conf sets into a file."
|
25
|
+
opt.separator " export -n <name> -f <outputfile>: Imports conf_set named 'name' into a file."
|
26
|
+
opt.separator " init -e <endpoint> -k <key>: Sets up config. Run this before querying the ConfMan server.(run as root)"
|
27
|
+
opt.separator " reset_keys: Resets authorized keys for the current user."
|
28
|
+
opt.separator ""
|
29
|
+
opt.separator "Options"
|
30
|
+
|
31
|
+
opt.on('-k key', String, 'ConfMan API Key') do |key|
|
32
|
+
@@options[:key] = key
|
33
|
+
end
|
34
|
+
|
35
|
+
opt.on('-e endpoint', String, 'ConfMan API Endpoint') do |ep|
|
36
|
+
@@options[:endpoint] = ep
|
37
|
+
end
|
38
|
+
|
39
|
+
opt.on('-f outputfile', String, 'Output file') do |of|
|
40
|
+
@@options[:outputfile] = of
|
41
|
+
end
|
42
|
+
|
43
|
+
opt.on('-n name', String, 'Name') do |name|
|
44
|
+
@@options[:name] = name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@@opt_parser.parse!(args)
|
49
|
+
|
50
|
+
if respond_to?(command)
|
51
|
+
send(command)
|
52
|
+
else
|
53
|
+
puts @@opt_parser
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def self.init
|
59
|
+
|
60
|
+
config_hash = Confman.api.load_config || {}
|
61
|
+
config_hash.symbolize_keys!
|
62
|
+
|
63
|
+
%w(endpoint_url api_key secret).each do |c|
|
64
|
+
|
65
|
+
if @@options[c.to_sym].blank?
|
66
|
+
puts "Please enter your #{c}: [#{config_hash[c.to_sym]}]"
|
67
|
+
value = STDIN.gets.chomp
|
68
|
+
unless value.blank?
|
69
|
+
config_hash[c.to_sym] = value
|
70
|
+
end
|
71
|
+
|
72
|
+
else
|
73
|
+
config_hash[c.to_sym] = @@options[c.to_sym]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Write to file
|
78
|
+
File.open Confman.api.config_path, "w" do |file|
|
79
|
+
file.write(config_hash.to_json)
|
80
|
+
end
|
81
|
+
|
82
|
+
Confman.api.load_config
|
83
|
+
Confman.api.reset_server_key!
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.reset_keys
|
87
|
+
Confman.access.reset_keys
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.exportall
|
91
|
+
out = @@options[:outputfile] ? File.open(@@options[:outputfile], "w") : STDOUT
|
92
|
+
out.write(Confman.api.conf_sets)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.export
|
96
|
+
out = @@options[:outputfile] ? File.open(@@options[:outputfile], "w") : STDOUT
|
97
|
+
|
98
|
+
begin
|
99
|
+
conf_set = Confman.api.find_by_name(@@options[:name])
|
100
|
+
out.write(conf_set.to_json)
|
101
|
+
rescue RestClient::ResourceNotFound
|
102
|
+
Confman.logger.error("ConfSet #{@@options[:name]} not found")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Confman
|
2
|
+
class DataStore
|
3
|
+
@@dir = '.confman'
|
4
|
+
|
5
|
+
def self.dir= dir
|
6
|
+
@@dir = dir
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.dir
|
10
|
+
@@dir
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.write(key, conf_set_name, data)
|
14
|
+
create_directory_if_not_present
|
15
|
+
|
16
|
+
filepath = compute_file_path(key, conf_set_name)
|
17
|
+
File.open filepath, "w" do |file|
|
18
|
+
file.write(data)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.read(key, conf_set_name)
|
23
|
+
filepath = compute_file_path(key, conf_set_name)
|
24
|
+
IO.read(filepath) if File.exists?(filepath)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create_directory_if_not_present
|
28
|
+
Dir.mkdir(@@dir) unless Dir.exists?(dir)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.compute_file_path(key, conf_set_name)
|
32
|
+
"#{dir}/#{key}_#{Digest::MD5.hexdigest conf_set_name}"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'confman'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
describe Confman do
|
5
|
+
before :all do
|
6
|
+
DataStore.dir = '.testconfman'
|
7
|
+
@fake_search_results = {
|
8
|
+
count: 1,
|
9
|
+
results: [
|
10
|
+
{
|
11
|
+
name:"One",
|
12
|
+
notification_url:"",
|
13
|
+
read_permission:"public", write_permission:"public",
|
14
|
+
conf_pairs: [
|
15
|
+
{ name:"foo", value:"bar" },
|
16
|
+
{ name:"password", value:"something" }
|
17
|
+
]
|
18
|
+
}
|
19
|
+
]
|
20
|
+
}
|
21
|
+
|
22
|
+
@config = YAML.load(File.open(File.join("config", "#{ENV['TEST_ENV'] || 'test'}.yml")))
|
23
|
+
Confman.api.endpoint_url = @config['confman']['endpoint_url']
|
24
|
+
Confman.api.api_key = @config['confman']['api_key']
|
25
|
+
Confman.api.secret = @config['confman']['secret']
|
26
|
+
end
|
27
|
+
|
28
|
+
describe :load_conf_set do
|
29
|
+
it 'can be loaded twice' do
|
30
|
+
Confman.api.should_receive(:search).and_return(@fake_search_results)
|
31
|
+
Confman.load_conf_set('somename', 'some_conf_set')
|
32
|
+
Confman.api.should_receive(:search).and_return(@fake_search_results)
|
33
|
+
lambda {Confman.load_conf_set('somename', 'some_conf_set')}.should_not raise_error
|
34
|
+
lambda {Confman.somename}.should_not raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'creates a getter function which returns the conf_set' do
|
38
|
+
Confman.api.should_receive(:search).and_return(@fake_search_results)
|
39
|
+
Confman.load_conf_set('somename', 'some_conf_set')
|
40
|
+
lambda {Confman.somename}.should_not raise_error
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'even works with symbols' do
|
44
|
+
Confman.api.should_receive(:search).and_return(@fake_search_results)
|
45
|
+
Confman.load_conf_set(:oauth, 'some_conf_set')
|
46
|
+
lambda {Confman.oauth}.should_not raise_error
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'loads the data into an OpenStruct object' do
|
50
|
+
Confman.api.should_receive(:search).and_return(@fake_search_results)
|
51
|
+
Confman.load_conf_set('somename', 'One')
|
52
|
+
Confman.somename.class == OpenStruct
|
53
|
+
Confman.somename.foo.should == 'bar'
|
54
|
+
end
|
55
|
+
|
56
|
+
describe :not_found do
|
57
|
+
it 'throws an ConfSetNotFound exception when the conf_set is not found in the server' do
|
58
|
+
Confman.api.should_receive(:search).and_return({count: 0})
|
59
|
+
|
60
|
+
expect {Confman.load_conf_set('somename', 'non_existent_conf_set')}.to raise_error(RestClient::ResourceNotFound)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'throws an ConfSetNotFound exception when get_conf_set raises any kind of error' do
|
64
|
+
Confman.api.should_receive(:search).and_return({count: 0})
|
65
|
+
expect {Confman.load_conf_set('somename', 'network_error')}.to raise_error(RestClient::ResourceNotFound)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Really an integration test - Uncomment and run it wrt your local server.
|
71
|
+
describe 'read:public:write:public' do
|
72
|
+
before :all do
|
73
|
+
Confman.api.should_receive(:aws_metadata).and_return({"ami-id"=>"ami-137bcf7a", "instance-id"=>"i-3be58146", "instance-type"=>"m1.small", "kernel-id"=>"aki-825ea7eb", "local-hostname"=>"ip-10-86-154-126.ec2.internal", "mac"=>"12:31:38:19:9C:90", "public-hostname"=>"ec2-107-20-165-98.compute-1.amazonaws.com"})
|
74
|
+
@conf_set = Confman.api.find_by_name('read:public:write:public')
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'has a @conf_set with an id' do
|
78
|
+
@conf_set.id.should be_a_kind_of(Fixnum)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'calls a real server and retrieves the result' do
|
82
|
+
Confman.load_conf_set('o', 'read:public:write:public')
|
83
|
+
Confman.o.boolean_attribute.should == 'true'
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'updates a confset' do
|
87
|
+
@conf_set.update_pairs(updated_boolean: false).should be_true
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'cannot set a non boolean value to a boolean attribute' do
|
91
|
+
expect {@conf_set.update_pairs(updated_boolean: "bleh")}.to raise_error(RestClient::UnprocessableEntity)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'updates a confset' do
|
95
|
+
@conf_set.update_pairs(password_attribute: '12345').should be_true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
=begin
|
100
|
+
describe 'ravi integration tests' do
|
101
|
+
before :all do
|
102
|
+
Confman.api.endpoint_url = "http://localhost:3000/api/confman"
|
103
|
+
Confman.api.api_key = "e2a5ee9cdc6b41ef72f614cd2078c61"
|
104
|
+
Confman.api.secret = "8XxvpFAuq+s99lawHi2gJ4W3fGJ0IlhJSjHUzcis"
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'updates the confset' do
|
108
|
+
@conf_set = Confman.api.find_by_name('Set1')
|
109
|
+
@conf_set.update_pairs(foo: 'fromruby').should be_true
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'retrieves all the confsets' do
|
113
|
+
conf_sets_json = Confman.api.conf_sets
|
114
|
+
JSON.parse(conf_sets_json).class.should == Array
|
115
|
+
end
|
116
|
+
|
117
|
+
describe :find_by_name do
|
118
|
+
it 'retrieves the confset' do
|
119
|
+
conf_set = Confman.api.find_by_name('Set1')
|
120
|
+
conf_set.name.should == 'Set1'
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'caches the confset locally' do
|
124
|
+
conf_set = Confman.api.find_by_name('Set1')
|
125
|
+
file_path = DataStore.compute_file_path(Confman.api.api_key, 'Set1')
|
126
|
+
File.exists?(file_path).should == true
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'raises ResourceNotFound if the confset is not present' do
|
130
|
+
expect {Confman.api.find_by_name('NonExistant')}.to raise_error(RestClient::ResourceNotFound)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'raises exception if endpoint is not reachable and there is no local copy' do
|
134
|
+
FileUtils.rm_rf(DataStore.dir, secure:true)
|
135
|
+
Confman.api.endpoint_url = "http://localhost:3001/api/confman"
|
136
|
+
# Since resource is memoized
|
137
|
+
Confman.api.instance_variable_set(:@resource, nil)
|
138
|
+
expect {Confman.api.find_by_name('Set1')}.to raise_error(Exception)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'reads the confset locally in case of network errors' do
|
142
|
+
FileUtils.rm_rf(DataStore.dir, secure:true)
|
143
|
+
Confman.api.endpoint_url = "http://localhost:3000/api/confman"
|
144
|
+
|
145
|
+
# Since resource is memoized
|
146
|
+
Confman.api.instance_variable_set(:@resource, nil)
|
147
|
+
conf_set = Confman.api.find_by_name('Set1')
|
148
|
+
Confman.api.endpoint_url = "http://localhost:3001/api/confman"
|
149
|
+
Confman.api.instance_variable_set(:@resource, nil)
|
150
|
+
conf_set_dup = Confman.api.find_by_name('Set1')
|
151
|
+
conf_set_dup.name.should == 'Set1'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
=end
|
156
|
+
|
157
|
+
|
158
|
+
describe 'read:public:write:private' do
|
159
|
+
before :all do
|
160
|
+
@conf_set = Confman.api.find_by_name('read:public:write:private')
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'has a @conf_set with an id' do
|
164
|
+
@conf_set.id.should be_a_kind_of(Fixnum)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'calls a real server and retrieves the result' do
|
168
|
+
Confman.load_conf_set('o', 'read:public:write:public')
|
169
|
+
Confman.o.boolean_attribute.should == 'true'
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'cannot update a confset boolean' do
|
173
|
+
expect{@conf_set.update_pairs(updated_boolean: false)}.to raise_error(RestClient::PermissionDenied)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'cannot set a non boolean value to a boolean attribute' do
|
177
|
+
expect {@conf_set.update_pairs(updated_boolean: "bleh")}.to raise_error(RestClient::PermissionDenied)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'cannot update a confset password' do
|
181
|
+
expect {@conf_set.update_pairs(password_attribute: '12345')}.to raise_error(RestClient::PermissionDenied)
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
read_private_write_private = proc do
|
187
|
+
before :all do
|
188
|
+
@conf_set = Confman.api.find_by_name('read:private:write:private')
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'has a @conf_set with an id' do
|
192
|
+
@conf_set.id.should be_a_kind_of(Fixnum)
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'calls a real server and retrieves the result' do
|
196
|
+
Confman.load_conf_set('o', 'read:public:write:public')
|
197
|
+
Confman.o.boolean_attribute.should == 'true'
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'updates a confset' do
|
201
|
+
@conf_set.update_pairs(updated_boolean: false).should be_true
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'cannot set a non boolean value to a boolean attribute' do
|
205
|
+
expect {@conf_set.update_pairs(updated_boolean: "bleh")}.to raise_error(RestClient::UnprocessableEntity)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'updates a confset' do
|
209
|
+
@conf_set.update_pairs(password_attribute: '12345').should be_true
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe 'read:private:write:private', read_private_write_private
|
214
|
+
describe 'read:private:write:public', read_private_write_private
|
215
|
+
|
216
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'confman'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
include Confman
|
5
|
+
|
6
|
+
describe Confman::DataStore do
|
7
|
+
before :all do
|
8
|
+
DataStore.dir = '.testconfman'
|
9
|
+
end
|
10
|
+
|
11
|
+
describe :create_directory_if_not_present do
|
12
|
+
it 'creates the directory if not present' do
|
13
|
+
FileUtils.rm_rf(DataStore.dir, secure:true)
|
14
|
+
DataStore.create_directory_if_not_present
|
15
|
+
Dir.exists?(DataStore.dir).should be_true
|
16
|
+
Dir.rmdir(DataStore.dir) if Dir.exists?(DataStore.dir)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe :write do
|
21
|
+
before :each do
|
22
|
+
FileUtils.rm_rf(DataStore.dir, secure:true)
|
23
|
+
DataStore.write('key','foo','some_content')
|
24
|
+
end
|
25
|
+
|
26
|
+
after :each do
|
27
|
+
FileUtils.rm_rf(DataStore.dir, secure:true)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'creates the signature file name' do
|
31
|
+
filename = "key_#{Digest::MD5.hexdigest 'foo'}"
|
32
|
+
File.exists?("#{DataStore.dir}/#{filename}").should be_true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe :read do
|
37
|
+
after :each do
|
38
|
+
FileUtils.rm_rf(DataStore.dir, secure:true)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'reads back the written content' do
|
42
|
+
Dir.rmdir(DataStore.dir) if Dir.exists?(DataStore.dir)
|
43
|
+
DataStore.write(2,'foo','some_content')
|
44
|
+
DataStore.read(2,'foo').should == 'some_content'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns nil if the conf_set was not previously written' do
|
48
|
+
Dir.rmdir(DataStore.dir) if Dir.exists?(DataStore.dir)
|
49
|
+
DataStore.read(2,'foo').should be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: confman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Masahji Stewart
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rest-client
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.6'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.6'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Confman ruby client
|
79
|
+
email:
|
80
|
+
- masahji@synctree.com
|
81
|
+
executables:
|
82
|
+
- confman
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- bin/confman
|
92
|
+
- config/test.yml
|
93
|
+
- confman.gemspec
|
94
|
+
- lib/confman.rb
|
95
|
+
- lib/confman/access.rb
|
96
|
+
- lib/confman/api.rb
|
97
|
+
- lib/confman/cli.rb
|
98
|
+
- lib/confman/data_store.rb
|
99
|
+
- lib/confman/version.rb
|
100
|
+
- spec/confman_spec.rb
|
101
|
+
- spec/data_store_spec.rb
|
102
|
+
homepage: ''
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.8.23
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: ''
|
127
|
+
test_files:
|
128
|
+
- spec/confman_spec.rb
|
129
|
+
- spec/data_store_spec.rb
|