cmdb 2.6.2 → 3.0.0rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|