cmdb 2.6.2 → 3.0.0rc1
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 +4 -4
- data/.dockerignore +7 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +71 -34
- data/Gemfile +4 -3
- data/Gemfile.lock +6 -19
- data/LICENSE +25 -0
- data/README.md +138 -166
- data/Rakefile +13 -0
- data/cmdb.gemspec +1 -3
- data/docker-compose.yml +15 -0
- data/exe/cmdb +36 -9
- data/lib/cmdb/commands/help.rb +21 -66
- data/lib/cmdb/commands/shell.rb +165 -0
- data/lib/cmdb/commands/shim.rb +13 -128
- data/lib/cmdb/commands.rb +1 -0
- data/lib/cmdb/interface.rb +51 -59
- data/lib/cmdb/shell/dsl.rb +73 -0
- data/lib/cmdb/shell/printer.rb +115 -0
- data/lib/cmdb/shell/text.rb +65 -0
- data/lib/cmdb/shell.rb +8 -0
- data/lib/cmdb/source/consul.rb +90 -0
- data/lib/cmdb/{file_source.rb → source/file.rb} +15 -34
- data/lib/cmdb/source/memory.rb +39 -0
- data/lib/cmdb/source/network.rb +104 -0
- data/lib/cmdb/source.rb +94 -0
- data/lib/cmdb/version.rb +1 -1
- data/lib/cmdb.rb +26 -17
- metadata +18 -36
- data/TODO.md +0 -3
- data/lib/cmdb/consul_source.rb +0 -83
data/lib/cmdb/interface.rb
CHANGED
@@ -3,31 +3,20 @@ require 'json'
|
|
3
3
|
|
4
4
|
module CMDB
|
5
5
|
class Interface
|
6
|
-
# Create a new instance of the CMDB interface.
|
7
|
-
# @
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@sources = []
|
17
|
-
# Load from consul source first if one is available.
|
18
|
-
unless ConsulSource.url.nil?
|
19
|
-
if ConsulSource.prefixes.nil? || ConsulSource.prefixes.empty?
|
20
|
-
@sources << ConsulSource.new('')
|
21
|
-
else
|
22
|
-
ConsulSource.prefixes.each do |prefix|
|
23
|
-
@sources << ConsulSource.new(prefix)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
# Register valid sources with CMDB
|
28
|
-
namespaces.each do |_, v|
|
29
|
-
@sources << v.first
|
6
|
+
# Create a new instance of the CMDB interface with the specified sources.
|
7
|
+
# @param [Array] sources list of String or URI source locations
|
8
|
+
# @see Source.create for information on how to specify source URLs
|
9
|
+
def initialize(*sources)
|
10
|
+
# ensure no two sources share a prefix
|
11
|
+
prefixes = {}
|
12
|
+
sources.each do |s|
|
13
|
+
next if s.prefix.nil?
|
14
|
+
prefixes[s.prefix] ||= []
|
15
|
+
prefixes[s.prefix] << s
|
30
16
|
end
|
17
|
+
check_overlap(prefixes)
|
18
|
+
|
19
|
+
@sources = sources.dup
|
31
20
|
end
|
32
21
|
|
33
22
|
# Retrieve the value of a CMDB key, searching all sources in the order they were initialized.
|
@@ -57,6 +46,23 @@ module CMDB
|
|
57
46
|
get(key) || raise(MissingKey.new(key))
|
58
47
|
end
|
59
48
|
|
49
|
+
# Set the value of a CMDB key.
|
50
|
+
#
|
51
|
+
# @return [Source,ni] the source that accepted the write, if any
|
52
|
+
# @raise [BadKey] if the key name is malformed
|
53
|
+
def set(key, value)
|
54
|
+
raise BadKey.new(key) unless key =~ VALID_KEY
|
55
|
+
|
56
|
+
@sources.reverse.each do |s|
|
57
|
+
if s.respond_to?(:set)
|
58
|
+
s.set(key, value)
|
59
|
+
return s
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
60
66
|
# Enumerate all of the keys in the CMDB.
|
61
67
|
#
|
62
68
|
# @yield every key/value in the CMDB
|
@@ -71,6 +77,19 @@ module CMDB
|
|
71
77
|
self
|
72
78
|
end
|
73
79
|
|
80
|
+
def search(prefix)
|
81
|
+
prefix = Regexp.new('^' + Regexp.escape(prefix))
|
82
|
+
result = {}
|
83
|
+
|
84
|
+
@sources.each do |s|
|
85
|
+
s.each_pair do |k, v|
|
86
|
+
result[k] = v if k =~ prefix
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
74
93
|
# Transform the entire CMDB into a flat Hash that can be merged into ENV.
|
75
94
|
# Key names are transformed into underscore-separated, uppercase strings;
|
76
95
|
# all runs of non-alphanumeric, non-underscore characters are tranformed
|
@@ -103,42 +122,15 @@ module CMDB
|
|
103
122
|
|
104
123
|
private
|
105
124
|
|
106
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
if CMDB.development?
|
113
|
-
local_dir = File.join(Dir.pwd, '.cmdb')
|
114
|
-
directories += [local_dir]
|
115
|
-
end
|
116
|
-
|
117
|
-
directories.each do |dir|
|
118
|
-
(Dir.glob(File.join(dir, '*.js')) + Dir.glob(File.join(dir, '*.json'))).each do |filename|
|
119
|
-
source = FileSource.new(filename, @root)
|
120
|
-
namespaces[source.prefix] ||= []
|
121
|
-
namespaces[source.prefix] << source
|
122
|
-
end
|
123
|
-
|
124
|
-
(Dir.glob(File.join(dir, '*.yml')) + Dir.glob(File.join(dir, '*.yaml'))).each do |filename|
|
125
|
-
source = FileSource.new(filename, @root)
|
126
|
-
namespaces[source.prefix] ||= []
|
127
|
-
namespaces[source.prefix] << source
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
# Check for overlapping namespaces and react appropriately. This can happen when a file
|
133
|
-
# of a given name is located in more than one of the key-search directories. We tolerate
|
134
|
-
# this in development mode, but raise an exception otherwise.
|
135
|
-
def check_overlap(namespaces)
|
136
|
-
overlapping = namespaces.select { |_, sources| sources.size > 1 }
|
137
|
-
overlapping.each do |ns, sources|
|
138
|
-
exc = ValueConflict.new(ns, sources)
|
125
|
+
# Check that no two sources share a prefix. Raise an exception if any
|
126
|
+
# overlap is detected.
|
127
|
+
def check_overlap(prefix_sources)
|
128
|
+
overlapping = prefix_sources.select { |_, sources| sources.size > 1 }
|
129
|
+
overlapping.each do |p, sources|
|
130
|
+
exc = ValueConflict.new(p, sources)
|
139
131
|
|
140
|
-
CMDB.log.
|
141
|
-
raise exc
|
132
|
+
CMDB.log.error exc.message
|
133
|
+
raise exc
|
142
134
|
end
|
143
135
|
end
|
144
136
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module CMDB::Shell
|
2
|
+
# Host for CMDB command methods. Every public method of this class is
|
3
|
+
# a CMDB command and its parameters represent the arguments to the
|
4
|
+
# command. If a command is successful, it always updates the `_` attribute
|
5
|
+
# with the output (return value) of the command.
|
6
|
+
class DSL < BasicObject
|
7
|
+
def initialize(shell, out)
|
8
|
+
@shell = shell
|
9
|
+
@cmdb = @shell.cmdb
|
10
|
+
@out = out
|
11
|
+
end
|
12
|
+
|
13
|
+
def ls(path='')
|
14
|
+
prefix = @shell.expand_path(path)
|
15
|
+
@cmdb.search prefix
|
16
|
+
end
|
17
|
+
|
18
|
+
def help
|
19
|
+
@out.info 'Commands:'
|
20
|
+
@out.info ' cd slash/sep/path - append to search prefix'
|
21
|
+
@out.info ' cd /path - reset prefix'
|
22
|
+
@out.info ' get <key> - print value of key'
|
23
|
+
@out.info ' ls - show keys and values'
|
24
|
+
@out.info ' set <key> <value> - print value of key'
|
25
|
+
@out.info ' quit - exit the shell'
|
26
|
+
@out.info 'Notation:'
|
27
|
+
@out.info ' a.b.c - (sub)key relative to search prefix'
|
28
|
+
@out.info ' ../b/c - the b.c subkey relative to parent of search prefix'
|
29
|
+
@out.info 'Shortcuts:'
|
30
|
+
@out.info ' <key> - for get'
|
31
|
+
@out.info ' <key>=<value> - for set'
|
32
|
+
@out.info ' cat,rm,unset,... - as expected'
|
33
|
+
end
|
34
|
+
|
35
|
+
def get(key)
|
36
|
+
key = @shell.expand_path(key)
|
37
|
+
|
38
|
+
@cmdb.get(key)
|
39
|
+
end
|
40
|
+
alias cat get
|
41
|
+
|
42
|
+
def set(key, value)
|
43
|
+
key = @shell.expand_path key
|
44
|
+
|
45
|
+
if @cmdb.set(key, value)
|
46
|
+
@cmdb.get(key)
|
47
|
+
else
|
48
|
+
::Kernel.raise ::CMDB::BadCommand.new('set', 'No source is capable of accepting writes')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def unset(key)
|
53
|
+
@cmdb.set(key, nil)
|
54
|
+
end
|
55
|
+
alias rm unset
|
56
|
+
|
57
|
+
def cd(path)
|
58
|
+
pwd = @shell.expand_path(path)
|
59
|
+
@shell.pwd = pwd.split(::CMDB::SEPARATOR)
|
60
|
+
pwd.to_sym
|
61
|
+
end
|
62
|
+
alias chdir cd
|
63
|
+
|
64
|
+
def pry
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def quit
|
69
|
+
::Kernel.raise ::Interrupt
|
70
|
+
end
|
71
|
+
alias exit quit
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module CMDB::Shell
|
2
|
+
class Printer
|
3
|
+
def initialize(out=STDOUT, err=STDERR)
|
4
|
+
@out = out
|
5
|
+
@err = err
|
6
|
+
@c = Text.new(!@out.tty?)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Print an informational message.
|
10
|
+
def info(str)
|
11
|
+
@out.puts @c.white(str)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Print an error message.
|
15
|
+
def error(str)
|
16
|
+
@err.puts @c.bright_red(str)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
# Display a single CMDB value.
|
21
|
+
def value(obj)
|
22
|
+
@out.puts ' ' + color_value(obj, 76)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Display a table of keys/values.
|
27
|
+
def keys_values(h, prefix:nil)
|
28
|
+
wk = h.keys.inject(0) { |ax, e| e.size > ax ? e.size : ax }
|
29
|
+
wv = h.values.inject(0) { |ax, e| es = e.inspect.size; es > ax ? es : ax }
|
30
|
+
width = @c.tty_columns
|
31
|
+
half = width / 2 - 2
|
32
|
+
wk = [wk, half].min
|
33
|
+
wv = [wv, half].min
|
34
|
+
re = (width - wk - wv)
|
35
|
+
wv += re if re > 0
|
36
|
+
|
37
|
+
h.each do |k, v|
|
38
|
+
@out.puts format(' %s%s', color_key(k, wk+1, prefix:prefix), color_value(v, wv))
|
39
|
+
end
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String] human-readable CMDB prompt
|
45
|
+
def prompt(cmdb)
|
46
|
+
pwd = '/' + cmdb.pwd.join('/')
|
47
|
+
pwd = pwd[0...40] + '...' if pwd.size >= 40
|
48
|
+
'cmdb:' +
|
49
|
+
@c.green(pwd) +
|
50
|
+
@c.default('> ')
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Colorize a key and right-pad it to fit a minimum size. Append a ':'
|
56
|
+
# to make it YAML-esque.
|
57
|
+
def color_key(k, size, prefix:nil)
|
58
|
+
v = k.to_s
|
59
|
+
v.sub(prefix, '') if prefix && v.index(prefix) == 0
|
60
|
+
suffix = ':'
|
61
|
+
if v.size + 1 > size
|
62
|
+
v = v[0...size-4]
|
63
|
+
suffix = '...:'
|
64
|
+
end
|
65
|
+
pad = [0, size - v.size - suffix.size].max
|
66
|
+
@c.blue(v) << suffix << (' ' * pad)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Colorize a value and right-pad it to fit a minimum size.
|
70
|
+
def color_value(v, size)
|
71
|
+
case v
|
72
|
+
when Symbol
|
73
|
+
vv = v.to_s
|
74
|
+
when nil
|
75
|
+
vv = 'null'
|
76
|
+
else
|
77
|
+
vv = v.inspect
|
78
|
+
end
|
79
|
+
vv << (' ' * [0, size - vv.size].max)
|
80
|
+
|
81
|
+
case v
|
82
|
+
when Symbol
|
83
|
+
@c.blue(vv)
|
84
|
+
when String
|
85
|
+
@c.bright_green(vv)
|
86
|
+
when Numeric
|
87
|
+
@c.bright_magenta(vv)
|
88
|
+
when true, false
|
89
|
+
@c.cyan(vv)
|
90
|
+
when nil
|
91
|
+
@c.yellow(vv)
|
92
|
+
when Array
|
93
|
+
str = @c.bold('[')
|
94
|
+
remain = size-2
|
95
|
+
v.each_with_index do |e, i|
|
96
|
+
ei = e.inspect
|
97
|
+
if remain >= ei.size + 3
|
98
|
+
str << ',' if i > 0
|
99
|
+
str << color_value(e, ei.size)
|
100
|
+
elsif remain >= 3
|
101
|
+
str << @c.default('...')
|
102
|
+
remain = 1
|
103
|
+
end
|
104
|
+
remain -= (ei.size + 1)
|
105
|
+
end
|
106
|
+
str << @c.bold(']')
|
107
|
+
|
108
|
+
str
|
109
|
+
else
|
110
|
+
@c.default(vv)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module CMDB::Shell
|
2
|
+
# Adapted from pry: https://github.com/pry/pry
|
3
|
+
class Text
|
4
|
+
COLORS = {
|
5
|
+
'black' => 0,
|
6
|
+
'red' => 1,
|
7
|
+
'green' => 2,
|
8
|
+
'yellow' => 3,
|
9
|
+
'blue' => 4,
|
10
|
+
'purple' => 5,
|
11
|
+
'magenta' => 5,
|
12
|
+
'cyan' => 6,
|
13
|
+
'white' => 7
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def initialize(plain)
|
17
|
+
@plain = plain
|
18
|
+
end
|
19
|
+
|
20
|
+
COLORS.each_pair do |color, value|
|
21
|
+
define_method color do |text|
|
22
|
+
@plain && text || "\033[0;#{30+value}m#{text}\033[0m"
|
23
|
+
end
|
24
|
+
|
25
|
+
define_method "bright_#{color}" do |text|
|
26
|
+
@plain && text || "\033[1;#{30+value}m#{text}\033[0m"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Remove any color codes from _text_.
|
31
|
+
#
|
32
|
+
# @param [String, #to_s] text
|
33
|
+
# @return [String] _text_ stripped of any color codes.
|
34
|
+
def strip_color(text)
|
35
|
+
text.to_s.gsub(/\e\[.*?(\d)+m/ , '')
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns _text_ as bold text for use on a terminal.
|
39
|
+
#
|
40
|
+
# @param [String, #to_s] text
|
41
|
+
# @return [String] _text_
|
42
|
+
def bold(text)
|
43
|
+
@plain && text || "\e[1m#{text}\e[0m"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns `text` in the default foreground colour.
|
47
|
+
# Use this instead of "black" or "white" when you mean absence of colour.
|
48
|
+
#
|
49
|
+
# @param [String, #to_s] text
|
50
|
+
# @return [String]
|
51
|
+
def default(text)
|
52
|
+
text.to_s
|
53
|
+
end
|
54
|
+
alias_method :bright_default, :bold
|
55
|
+
|
56
|
+
# @return [Integer] screen width (number of columns)
|
57
|
+
def tty_columns
|
58
|
+
if @plain
|
59
|
+
65_535
|
60
|
+
else
|
61
|
+
Integer(`stty size`.chomp.split(/ +/)[1]) rescue 80
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/cmdb/shell.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'base64'
|
3
|
+
require 'net/http'
|
4
|
+
require 'open-uri'
|
5
|
+
|
6
|
+
module CMDB
|
7
|
+
class Source::Consul < Source::Network
|
8
|
+
# Regular expression to match array values
|
9
|
+
ARRAY_VALUE = /^\[(.*)\]$/
|
10
|
+
|
11
|
+
# Get a single key from consul. If the key is not found, return nil.
|
12
|
+
#
|
13
|
+
# @param [String] key dot-notation key
|
14
|
+
# @return [Object]
|
15
|
+
def get(key)
|
16
|
+
return nil unless prefixed?(key)
|
17
|
+
key = dot_to_slash(key)
|
18
|
+
response = http_get path_to(key)
|
19
|
+
case response
|
20
|
+
when String
|
21
|
+
response = json_parse(response)
|
22
|
+
item = response.first
|
23
|
+
json_parse(Base64.decode64(item['Value']))
|
24
|
+
when 404
|
25
|
+
nil
|
26
|
+
else
|
27
|
+
raise CMDB:Error.new("Unexpected consul response #{value.inspect}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set a single key in consul. If value is nil, then delete the key
|
32
|
+
# entirely from consul.
|
33
|
+
#
|
34
|
+
# @param [String] key dot-notation key
|
35
|
+
# @param [Object] value new value of key
|
36
|
+
def set(key, value)
|
37
|
+
key = dot_to_slash(key)
|
38
|
+
if value.nil?
|
39
|
+
http_delete path_to(key)
|
40
|
+
else
|
41
|
+
http_put path_to(key), value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Iterate through all keys in this source.
|
46
|
+
# @return [Integer] number of key/value pairs that were yielded
|
47
|
+
def each_pair(&_block)
|
48
|
+
path = path_to('/')
|
49
|
+
|
50
|
+
case result = http_get(path, query:'recurse')
|
51
|
+
when String
|
52
|
+
result = json_parse(result)
|
53
|
+
when 404
|
54
|
+
return # no keys!
|
55
|
+
end
|
56
|
+
|
57
|
+
unless result.is_a?(Array)
|
58
|
+
raise CMDB::Error.new("Consul 'GET #{path}': expected Array, got #{all.class.name}")
|
59
|
+
end
|
60
|
+
|
61
|
+
result.each do |item|
|
62
|
+
key = slash_to_dot(item['Key'])
|
63
|
+
value = json_parse(Base64.decode64(item['Value']))
|
64
|
+
yield(key, value)
|
65
|
+
end
|
66
|
+
|
67
|
+
result.size
|
68
|
+
end
|
69
|
+
|
70
|
+
# Test connectivity to consul agent.
|
71
|
+
#
|
72
|
+
# @return [Boolean]
|
73
|
+
def ping
|
74
|
+
http_get('/') == 'Consul Agent'
|
75
|
+
rescue
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Given a key's relative path, return its absolute REST path in the consul
|
82
|
+
# kv, including prefix if appropriate.
|
83
|
+
def path_to(subkey)
|
84
|
+
p = '/v1/kv/'
|
85
|
+
p << prefix << '/' if prefix
|
86
|
+
p << subkey unless (subkey == '/' && p[-1] == '/')
|
87
|
+
p
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -2,42 +2,25 @@
|
|
2
2
|
require 'uri'
|
3
3
|
|
4
4
|
module CMDB
|
5
|
-
# Data source that is backed by a YAML file that lives in the filesystem. The name of the
|
6
|
-
# file becomes the top-level key under which all values in the
|
7
|
-
# their exact structure as parsed by YAML.
|
5
|
+
# Data source that is backed by a YAML/JSON file that lives in the filesystem. The name of the
|
6
|
+
# file becomes the top-level key under which all values in the file are exposed, preserving
|
7
|
+
# their exact structure as parsed by YAML/JSON.
|
8
8
|
#
|
9
9
|
# @example Use my.yml as a CMDB source
|
10
|
-
# source =
|
10
|
+
# source = Source::File.new('/tmp/my.yml') # contains a top-level stanza named "database"
|
11
11
|
# source['my']['database']['host'] # => 'db1-1.example.com'
|
12
|
-
class
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
@base_directories = ['/var/lib/cmdb', File.expand_path('~/.cmdb')]
|
17
|
-
|
18
|
-
# List of directories that will be searched (in order) for YML files at load time.
|
19
|
-
# @return [Array] collection of String
|
20
|
-
class << self
|
21
|
-
attr_reader :base_directories
|
22
|
-
end
|
23
|
-
|
24
|
-
# @param [Array] bd collection of String absolute paths to search for YML files
|
25
|
-
class << self
|
26
|
-
attr_writer :base_directories
|
27
|
-
end
|
28
|
-
|
29
|
-
# Construct a new FileSource from an input YML file.
|
30
|
-
# @param [String,Pathname] filename path to a YAML file
|
31
|
-
# @param [String] root optional subpath in data to "mount"
|
12
|
+
class Source::File < Source
|
13
|
+
# Construct a new Source::File from an input file.
|
14
|
+
# @param [String,Pathname] filename path to a file
|
32
15
|
# @param [String] prefix optional prefix of
|
33
16
|
# @raise [BadData] if the file's content is malformed
|
34
|
-
def initialize(filename,
|
17
|
+
def initialize(filename, prefix)
|
35
18
|
@data = {}
|
36
19
|
@prefix = prefix
|
37
|
-
filename = File.expand_path(filename)
|
20
|
+
filename = ::File.expand_path(filename)
|
38
21
|
@url = URI.parse("file://#{filename}")
|
39
|
-
@extension = File.extname(filename)
|
40
|
-
raw_bytes = File.read(filename)
|
22
|
+
@extension = ::File.extname(filename)
|
23
|
+
raw_bytes = ::File.read(filename)
|
41
24
|
raw_data = nil
|
42
25
|
|
43
26
|
begin
|
@@ -53,32 +36,30 @@ module CMDB
|
|
53
36
|
raise BadData.new(url, 'CMDB data file')
|
54
37
|
end
|
55
38
|
|
56
|
-
raw_data = raw_data[root] if !root.nil? && raw_data.key?(root)
|
57
39
|
flatten(raw_data, @prefix, @data)
|
58
40
|
end
|
59
41
|
|
60
42
|
# Get the value of key.
|
61
43
|
#
|
62
|
-
# @return [
|
44
|
+
# @return [Object] the key's value, or nil if not found
|
63
45
|
def get(key)
|
64
46
|
@data[key]
|
65
47
|
end
|
66
48
|
|
67
|
-
# Enumerate the keys in this source
|
49
|
+
# Enumerate the keys and values in this source.
|
68
50
|
#
|
69
51
|
# @yield every key/value in the source
|
70
52
|
# @yieldparam [String] key
|
71
53
|
# @yieldparam [Object] value
|
72
54
|
def each_pair(&_block)
|
73
|
-
|
74
|
-
@data.each_pair { |k, v| yield(k.split("#{@prefix}.").last, v) }
|
55
|
+
@data.each_pair { |k, v| yield(k, v) }
|
75
56
|
end
|
76
57
|
|
77
58
|
private
|
78
59
|
|
79
60
|
def flatten(data, prefix, output)
|
80
61
|
data.each_pair do |key, value|
|
81
|
-
key = "#{prefix}
|
62
|
+
key = "#{prefix}#{CMDB::SEPARATOR}#{key}"
|
82
63
|
case value
|
83
64
|
when Hash
|
84
65
|
flatten(value, key, output)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module CMDB
|
5
|
+
# Data source that is backed by the an in-memory Ruby hash; used solely for
|
6
|
+
# testing.
|
7
|
+
class Source::Memory
|
8
|
+
# @return [String] the empty string
|
9
|
+
attr_reader :prefix
|
10
|
+
|
11
|
+
# Construct a new Source::Hahs.
|
12
|
+
def initialize(hash, prefix)
|
13
|
+
@hash = hash
|
14
|
+
@prefix = prefix
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the value of key.
|
18
|
+
#
|
19
|
+
# @return [nil,String,Numeric,TrueClass,FalseClass,Array] the key's value, or nil if not found
|
20
|
+
def get(key)
|
21
|
+
@hash[key]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Set the value of a key.
|
25
|
+
def set(key, value)
|
26
|
+
value = JSON.dump(value) unless value.is_a?(String)
|
27
|
+
@hash[key] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
# Enumerate the keys in this source, and their values.
|
31
|
+
#
|
32
|
+
# @yield every key/value in the source
|
33
|
+
# @yieldparam [String] name of key
|
34
|
+
# @yieldparam [Object] value of key
|
35
|
+
def each_pair(&block)
|
36
|
+
@hash.each_pair(&block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|