cmdb 3.0.0rc1 → 3.0.0rc2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e6aea8a8e330452b914eae78b2909200d62aca4
4
- data.tar.gz: ffcb1a5762568246b37e3924c793f29d36b7e3ab
3
+ metadata.gz: 2a5fa442a4ff4b6f1d7955c804b79bce26e232a5
4
+ data.tar.gz: 60a350561cc2612639f53f41b1427b6d0ace23b0
5
5
  SHA512:
6
- metadata.gz: f20fe6ab9ff05e6f3450117f21abbc91b215bdedb55cfee11b6ad8bfba2937826536862f8d7593a3ec60d845424288d6aab3340691bf58957d586dab37d0c4ca
7
- data.tar.gz: 59dd521b9730b77d8acea1ba8587a04bc6cb172f67e79cfbfe2eda09825bc3107fd7bc799824f7a823ae00295f3509418523066cf3cc6d55694d727aadff30a8
6
+ metadata.gz: 949bf77549aefdbdb499e4d9ab818d826f5359d026dd8bd4bf6f0046a809e69e54369cb975962c3ef0c367a8ab5e3106731050dacf71eec5149fc109fec4a6a7
7
+ data.tar.gz: f5e1c629aa3135b333c89cffa7e2f1d1a4a05266c4be9fea75e3ba0cc5751c1f89c4f70d4859b464b205bb570ff67b0fb26cb898b925fa7896ccf3e96da99a53
data/Dockerfile ADDED
@@ -0,0 +1,13 @@
1
+ FROM ruby:2-alpine
2
+ MAINTAINER Tony Spataro <tony@rightscale.com>
3
+
4
+ WORKDIR /cmdb
5
+ ENTRYPOINT ["bin/shell"]
6
+
7
+ # HACK: install runtime dependencies by hand in order to avoid depending on
8
+ # bundler + git + make + gcc at build time.
9
+ RUN gem install trollop -v '~> 2.0'
10
+
11
+ ADD bin /cmdb/bin/
12
+ ADD exe /cmdb/exe/
13
+ ADD lib /cmdb/lib/
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cmdb (3.0.0rc1)
4
+ cmdb (3.0.0rc2)
5
5
  trollop (~> 2.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -29,7 +29,7 @@ CMDB has three primary interfaces:
29
29
 
30
30
  1. The `cmdb shim` command populates the environment with values and/or rewrites hardcoded
31
31
  config files, then spawns your application.
32
- 2. The `CMDB::Interface` object provides a programmatic API for querying CMDBs. Its `#to_h`
32
+ 2. The `CMDB::Interface` object provides a programmatic API for querying CMDBs. Its `#to_env`
33
33
  method transforms the whole configuration into an environment-friendly hash if you prefer to seed the
34
34
  environment yourself, without using the shim.
35
35
  3. The `cmdb shell` command navigates your k/v store using filesystem-like
data/Rakefile CHANGED
@@ -26,5 +26,5 @@ task :sandbox do
26
26
  url = mapper.map('consul://consul:8500/sandbox')
27
27
 
28
28
  lib = File.expand_path('../lib', __FILE__)
29
- exec "ruby -I#{lib} -rpry exe/cmdb --source=#{url} shell"
29
+ exec "bin/shell --source=#{url}"
30
30
  end
data/TODO.md ADDED
@@ -0,0 +1,10 @@
1
+ 1) Move array validation stuff (same type, etc) out of File source so Consul
2
+ can use it. Or decide to ditch this constraint ... hmm ...
3
+
4
+ 2) Create a Shell.new helper to enable embedding of shells into other apps
5
+
6
+ 3) Extract padding/truncation/excerpting code into Text module; deal with
7
+ corner cases better.
8
+
9
+ 4) Move CMDB separator logic (join/split) into toplevel methods of CMDB;
10
+ DRY out Shell#expand_path
data/bin/console CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ #! /usr/bin/env ruby
2
2
  # encoding: utf-8
3
3
 
4
4
  require 'bundler/setup'
data/bin/setup CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #! /bin/bash
2
2
  set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
 
data/bin/shell ADDED
@@ -0,0 +1,23 @@
1
+ #! /bin/sh
2
+
3
+ tcptest() {
4
+ nc -w 1 $1 $2 < /dev/null > /dev/null 2> /dev/null
5
+ }
6
+
7
+ if [ $# -gt 0 ]; then
8
+ args="$@"
9
+ elif tcptest consul 8500; then
10
+ args="--source=consul://consul"
11
+ elif tcptest localhost 8500; then
12
+ args="--source=consul://localhost"
13
+ else
14
+ echo 'cmdb: No k/v source detected; running shell demo with fake source!'
15
+ args="--source=memory:"
16
+ fi
17
+
18
+ if gem list -i pry > /dev/null; then
19
+ pry="-rpry"
20
+ fi
21
+
22
+ echo "+ cmdb $args"
23
+ exec ruby -Ilib $pry exe/cmdb $args shell
data/exe/cmdb CHANGED
@@ -25,7 +25,7 @@ command_info = command_list.map { |c| " * #{c}" }.join("\n")
25
25
 
26
26
  # Use a Trollop parser for help/banner display, but do not actually parse
27
27
  # anything just yet.
28
- p = Trollop::Parser.new do
28
+ parser = Trollop::Parser.new do
29
29
  version "cmdb #{gemspec_version} (c) 2013-2014 RightScale, Inc."
30
30
  banner <<-EOS
31
31
  A command-line interface for configuration management.
@@ -54,13 +54,22 @@ Common options that apply to all commands:
54
54
  stop_on commands.keys
55
55
  end
56
56
 
57
- Trollop.with_standard_exception_handling p do
57
+ subcommand = nil
58
+ options = nil
59
+
60
+ Trollop.with_standard_exception_handling parser do
58
61
  raise Trollop::HelpNeeded if ARGV.empty?
59
62
 
60
63
  # Apply global options
61
- options = p.parse ARGV
64
+ options = parser.parse ARGV
62
65
  CMDB.log.level = Logger::FATAL if options[:quiet]
63
66
 
67
+ # Identify the subcommand and run it (or print help).
68
+ raise Trollop::HelpNeeded if ARGV.empty?
69
+ subcommand = ARGV.shift
70
+ end
71
+
72
+ begin
64
73
  if options[:source].empty?
65
74
  sources = CMDB::Source.detect
66
75
  else
@@ -69,15 +78,39 @@ Trollop.with_standard_exception_handling p do
69
78
  end
70
79
  end
71
80
 
72
- interface = CMDB::Interface.new(*sources)
73
-
74
- # Identify the subcommand and run it (or print help).
75
- raise Trollop::HelpNeeded if ARGV.empty?
76
- cmd = ARGV.shift
77
- klass = commands[cmd]
78
- if klass
79
- klass.create(interface).run
80
- else
81
+ klass = commands[subcommand]
82
+ unless klass
81
83
  CMDB.log.fatal "Unrecognized command '#{cmd}'; try 'cmdb --help'"
84
+ exit 100
85
+ end
86
+
87
+ interface = CMDB::Interface.new(*sources)
88
+ klass.create(interface).run
89
+ rescue CMDB::BadKey => e
90
+ CMDB.log.fatal "CMDB: Bad Key: malformed CMDB key '#{e.key}'"
91
+ exit 1
92
+ rescue CMDB::BadValue => e
93
+ CMDB.log.fatal "CMDB: Bad Value: illegal value for CMDB key '#{e.key}' in source #{e.url}"
94
+ exit 2
95
+ rescue CMDB::BadData => e
96
+ CMDB.log.fatal "CMDB: Bad Data: malformed CMDB data in source #{e.url}"
97
+ exit 3
98
+ rescue CMDB::ValueConflict => e
99
+ CMDB.log.fatal "CMDB: Value Conflict: #{e.message}"
100
+ e.sources.each do |s|
101
+ CMDB.log.fatal " - #{s.url}"
102
+ end
103
+ exit 4
104
+ rescue CMDB::NameConflict => e
105
+ CMDB.log.fatal "CMDB: Name Conflict: #{e.message}"
106
+ e.keys.each do |k|
107
+ CMDB.log.fatal " - #{k}"
82
108
  end
109
+ exit 4
110
+ rescue CMDB::EnvironmentConflict => e
111
+ CMDB.log.fatal "CMDB: Environment Conflict: #{e.message}"
112
+ exit 5
113
+ rescue Errno::ENOENT => e
114
+ CMDB.log.fatal "CMDB: missing file or directory #{e.message}"
115
+ exit 6
83
116
  end
@@ -29,6 +29,9 @@ cmdb shell
29
29
  # @return [CMDB::Interface]
30
30
  attr_reader :cmdb
31
31
 
32
+ # @return [CMDB::Shell::DSL]
33
+ attr_reader :dsl
34
+
32
35
  # @return [Array] the "present working directory" (i.e. key prefix) for shell commands
33
36
  attr_accessor :pwd
34
37
 
@@ -39,7 +42,9 @@ cmdb shell
39
42
  def initialize(interface)
40
43
  @cmdb = interface
41
44
  @pwd = []
42
- @out = CMDB::Shell::Printer.new
45
+ text = CMDB::Shell::Text.new(!$stdout.tty? || ENV['TERM'].nil?)
46
+ @out = CMDB::Shell::Printer.new($stdout, $stderr, text)
47
+ @in = CMDB::Shell::Prompter.new(text)
43
48
  end
44
49
 
45
50
  # Run the shim.
@@ -58,8 +63,6 @@ cmdb shell
58
63
  #
59
64
  # @return [String] absolute key in dotted notation
60
65
  def expand_path(subpath)
61
- pieces = subpath.split(ALT_SEPARATOR)
62
-
63
66
  if subpath[0] == ALT_SEPARATOR
64
67
  result = []
65
68
  else
@@ -69,6 +72,7 @@ cmdb shell
69
72
  if subpath.include?(ALT_SEPARATOR) || subpath =~ NAVIGATION
70
73
  # filesystem-like subpath
71
74
  # apply Unix-style directory navigation shortcuts
75
+ pieces = subpath.split(ALT_SEPARATOR).select { |p| !p.nil? && !p.empty? }
72
76
  pieces.each do |piece|
73
77
  case piece
74
78
  when '..' then result.pop
@@ -79,6 +83,7 @@ cmdb shell
79
83
 
80
84
  result.join(CMDB::SEPARATOR)
81
85
  else
86
+ pieces = subpath.split(CMDB::SEPARATOR).select { |p| !p.nil? && !p.empty? }
82
87
  # standard dotted notation
83
88
  result += pieces
84
89
  end
@@ -89,8 +94,7 @@ cmdb shell
89
94
  private
90
95
 
91
96
  def repl
92
- require 'readline'
93
- while line = Readline.readline(@out.prompt(self), true)
97
+ while line = @in.read(self)
94
98
  begin
95
99
  line = line.chomp
96
100
  next if line.nil? || line.empty?
@@ -100,6 +104,8 @@ cmdb shell
100
104
  run_ruby(command, args) || run_getter(line) || run_setter(line) ||
101
105
  fail(CMDB::BadCommand.new(command))
102
106
  handle_output(self._)
107
+ rescue SystemCallError => e
108
+ handle_error(e) || raise
103
109
  rescue => e
104
110
  handle_error(e) || raise
105
111
  end
@@ -112,7 +118,7 @@ cmdb shell
112
118
  def run_ruby(command, args)
113
119
  self._ = @dsl.__send__(command, *args)
114
120
  true
115
- rescue NoMethodError
121
+ rescue CMDB::BadCommand
116
122
  false
117
123
  rescue ArgumentError => e
118
124
  raise CMDB::BadCommand.new(e.message, command)
@@ -126,7 +132,7 @@ cmdb shell
126
132
  else
127
133
  false
128
134
  end
129
- rescue CMDB::Error
135
+ rescue CMDB::BadKey
130
136
  false
131
137
  end
132
138
 
@@ -138,14 +144,12 @@ cmdb shell
138
144
  value = nil unless value && value.length > 0
139
145
  self._ = @dsl.set(key, value)
140
146
  true
141
- rescue CMDB::Error
142
- false
143
147
  end
144
148
 
145
149
  def handle_output(obj)
146
150
  case obj
147
151
  when Hash
148
- @out.keys_values(obj)
152
+ @out.keys_values(obj, prefix:pwd.join('.'))
149
153
  else
150
154
  @out.value(obj)
151
155
  end
@@ -157,6 +161,11 @@ cmdb shell
157
161
  when CMDB::BadCommand
158
162
  @out.error "#{e.command}: #{e.message}"
159
163
  true
164
+ when CMDB::Error
165
+ @out.error e.message
166
+ true
167
+ when SystemCallError
168
+ @out.error "#{e.class.name}: #{e.message}"
160
169
  else
161
170
  false
162
171
  end
@@ -15,10 +15,9 @@ option to tell the shim where to rewrite configuration files. It will look for t
15
15
  in JSON or YML that look like <<cmdb.key.name>> and replace them with the value of
16
16
  the specified key.
17
17
 
18
- To use the shim with 12-factor apps, use the --env option to tell the shim to load
19
- every CMDB key into the environment. When using --env, the prefix of each key is
20
- omitted from the environment variable name, e.g. "common.database.host" is
21
- represented as DATABASE_HOST.
18
+ The shim populates the environment with all data from every source. The source-
19
+ specific prefix of each key is omitted from the environment variable name,
20
+ e.g. "common.database.host" is represented as DATABASE_HOST.
22
21
 
23
22
  Usage:
24
23
  cmdb shim [options] -- <command_to_exec> [options_for_command]
@@ -31,9 +30,6 @@ Where [options] are selected from:
31
30
  opt :pretend,
32
31
  'Check for errors, but do not actually launch the app or rewrite files',
33
32
  default: false
34
- opt :env,
35
- "Add CMDB keys to the app server's process environment",
36
- default: false
37
33
  opt :user,
38
34
  'Switch to named user before executing app',
39
35
  type: :string
@@ -75,42 +71,15 @@ Where [options] are selected from:
75
71
  #
76
72
  # @raise [SystemExit] if something goes wrong
77
73
  def run
78
- rewrote = rewrite_files
79
- populated = populate_environment
74
+ populate_environment
75
+ rewrote = rewrite_files
80
76
 
81
- if !rewrote && !populated && !@pretend && @command.empty?
82
- CMDB.log.warn 'CMDB: nothing to do; please specify --dir, --env, or a command to run'
77
+ if !rewrote && !@pretend && @command.empty?
78
+ CMDB.log.warn 'CMDB: nothing to do; please specify --dir, or -- followed by a command to run'
83
79
  exit 7
84
80
  end
85
81
 
86
82
  launch_app
87
- rescue CMDB::BadKey => e
88
- CMDB.log.fatal "CMDB: Bad Key: malformed CMDB key '#{e.key}'"
89
- exit 1
90
- rescue CMDB::BadValue => e
91
- CMDB.log.fatal "CMDB: Bad Value: illegal value for CMDB key '#{e.key}' in source #{e.url}"
92
- exit 2
93
- rescue CMDB::BadData => e
94
- CMDB.log.fatal "CMDB: Bad Data: malformed CMDB data in source #{e.url}"
95
- exit 3
96
- rescue CMDB::ValueConflict => e
97
- CMDB.log.fatal "CMDB: Value Conflict: #{e.message}"
98
- e.sources.each do |s|
99
- CMDB.log.fatal " - #{s.url}"
100
- end
101
- exit 4
102
- rescue CMDB::NameConflict => e
103
- CMDB.log.fatal "CMDB: Name Conflict: #{e.message}"
104
- e.keys.each do |k|
105
- CMDB.log.fatal " - #{k}"
106
- end
107
- exit 4
108
- rescue CMDB::EnvironmentConflict => e
109
- CMDB.log.fatal "CMDB: Environment Conflict: #{e.message}"
110
- exit 5
111
- rescue Errno::ENOENT => e
112
- CMDB.log.fatal "CMDB: missing file or directory #{e.message}"
113
- exit 6
114
83
  end
115
84
 
116
85
  private
@@ -143,8 +112,7 @@ Where [options] are selected from:
143
112
 
144
113
  # @return [Boolean]
145
114
  def populate_environment
146
- env = @cmdb.to_h
147
-
115
+ env = @cmdb.to_env
148
116
  env.keys.each do |k|
149
117
  raise CMDB::EnvironmentConflict.new(k) if ENV.key?(k)
150
118
  end
@@ -153,6 +121,7 @@ Where [options] are selected from:
153
121
  false
154
122
  else
155
123
  env.each_pair do |k, v|
124
+ raise "WOOGA #{k} #{v}" if k.kind_of?(Array) || v.kind_of?(Array)
156
125
  ENV[k] = v
157
126
  end
158
127
  true
@@ -78,7 +78,7 @@ module CMDB
78
78
  end
79
79
 
80
80
  def search(prefix)
81
- prefix = Regexp.new('^' + Regexp.escape(prefix))
81
+ prefix = Regexp.new('^' + Regexp.escape(prefix)) unless prefix.is_a?(Regexp)
82
82
  result = {}
83
83
 
84
84
  @sources.each do |s|
@@ -101,19 +101,19 @@ module CMDB
101
101
  # rather than returning bad data.
102
102
  #
103
103
  # @raise [NameConflict] if two or more key names transform to the same
104
- def to_h
104
+ def to_env
105
105
  values = {}
106
- sources = {}
106
+ originals = {}
107
107
 
108
- each_pair do |key, value|
109
- env_key = key_to_env(key)
110
- value = JSON.dump(value) unless value.is_a?(String)
111
-
112
- if sources.key?(env_key)
113
- raise NameConflict.new(env_key, [sources[env_key], key])
114
- else
115
- sources[env_key] = key
116
- values[env_key] = value_to_env(value)
108
+ @sources.each do |s|
109
+ s.each_pair do |k, v|
110
+ env = key_to_env(k, s)
111
+ if (orig = originals[env])
112
+ raise NameConflict.new(env, [orig, k])
113
+ else
114
+ values[env] = value_to_env(v)
115
+ originals[env] = k
116
+ end
117
117
  end
118
118
  end
119
119
 
@@ -135,8 +135,12 @@ module CMDB
135
135
  end
136
136
 
137
137
  # Make an environment variable out of a key name
138
- def key_to_env(key)
139
- env_name = key
138
+ def key_to_env(key, source)
139
+ if source.prefix
140
+ env_name = key[source.prefix.size+1..-1]
141
+ else
142
+ env_name = key.dup
143
+ end
140
144
  env_name.gsub!(/[^A-Za-z0-9_]+/, '_')
141
145
  env_name.upcase!
142
146
  env_name
@@ -10,6 +10,14 @@ module CMDB::Shell
10
10
  @out = out
11
11
  end
12
12
 
13
+ def class
14
+ DSL
15
+ end
16
+
17
+ def method_missing(meth, *args)
18
+ ::Kernel.raise ::CMDB::BadCommand.new(meth)
19
+ end
20
+
13
21
  def ls(path='')
14
22
  prefix = @shell.expand_path(path)
15
23
  @cmdb.search prefix
@@ -17,15 +25,16 @@ module CMDB::Shell
17
25
 
18
26
  def help
19
27
  @out.info 'Commands:'
20
- @out.info ' cd slash/sep/path - append to search prefix'
21
- @out.info ' cd /path - reset prefix'
28
+ @out.info ' cd <key> - append/remove search prefix'
29
+ @out.info ' cd / - reset search prefix'
22
30
  @out.info ' get <key> - print value of key'
23
31
  @out.info ' ls - show keys and values'
24
32
  @out.info ' set <key> <value> - print value of key'
25
33
  @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'
34
+ @out.info 'Key notation:'
35
+ @out.info ' a.b.c - relative to search prefix'
36
+ @out.info ' ../b/c - relative to parent of search prefix'
37
+ @out.info ' /a - relative to root (i.e. all sources)'
29
38
  @out.info 'Shortcuts:'
30
39
  @out.info ' <key> - for get'
31
40
  @out.info ' <key>=<value> - for set'
@@ -1,14 +1,15 @@
1
1
  module CMDB::Shell
2
2
  class Printer
3
- def initialize(out=STDOUT, err=STDERR)
3
+ def initialize(out=$stdout, err=$stderr, text=Text.new(true))
4
4
  @out = out
5
5
  @err = err
6
- @c = Text.new(!@out.tty?)
6
+ @c = text
7
7
  end
8
8
 
9
9
  # Print an informational message.
10
10
  def info(str)
11
11
  @out.puts @c.white(str)
12
+ self
12
13
  end
13
14
 
14
15
  # Print an error message.
@@ -19,7 +20,7 @@ module CMDB::Shell
19
20
 
20
21
  # Display a single CMDB value.
21
22
  def value(obj)
22
- @out.puts ' ' + color_value(obj, 76)
23
+ @out.puts ' ' + color_value(obj, @c.width-2)
23
24
  self
24
25
  end
25
26
 
@@ -27,36 +28,26 @@ module CMDB::Shell
27
28
  def keys_values(h, prefix:nil)
28
29
  wk = h.keys.inject(0) { |ax, e| e.size > ax ? e.size : ax }
29
30
  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
31
+ half = @c.width / 2
32
+ wk = [wk, half].min-3
33
33
  wv = [wv, half].min
34
- re = (width - wk - wv)
34
+ re = (@c.width - wk - wv)
35
35
  wv += re if re > 0
36
36
 
37
37
  h.each do |k, v|
38
- @out.puts format(' %s%s', color_key(k, wk+1, prefix:prefix), color_value(v, wv))
38
+ @out.puts format(' %s %s', color_key(k, wk+1, prefix:prefix), color_value(v, wv))
39
39
  end
40
40
 
41
41
  self
42
42
  end
43
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
44
  private
54
45
 
55
46
  # Colorize a key and right-pad it to fit a minimum size. Append a ':'
56
47
  # to make it YAML-esque.
57
48
  def color_key(k, size, prefix:nil)
58
49
  v = k.to_s
59
- v.sub(prefix, '') if prefix && v.index(prefix) == 0
50
+ v = v.sub(prefix, '') if prefix && v.index(prefix) == 0
60
51
  suffix = ':'
61
52
  if v.size + 1 > size
62
53
  v = v[0...size-4]
@@ -76,7 +67,6 @@ module CMDB::Shell
76
67
  else
77
68
  vv = v.inspect
78
69
  end
79
- vv << (' ' * [0, size - vv.size].max)
80
70
 
81
71
  case v
82
72
  when Symbol
@@ -0,0 +1,40 @@
1
+ module CMDB::Shell
2
+ class Prompter
3
+ def initialize(text)
4
+ require 'readline'
5
+ @c = text
6
+ end
7
+
8
+ # Prompt the user for input and read a line. Offer autocompletion services
9
+ # using Readline and CMDB/DSL searches.
10
+ def read(shell)
11
+ Readline.completion_proc = proc do |word|
12
+ commands = shell.dsl.class.instance_methods.select do |m|
13
+ m.to_s.index(word) == 0 && m !~ /[^a-z]/
14
+ end.map(&:to_s)
15
+ next commands if commands.any?
16
+
17
+ hits = shell.cmdb.search(shell.expand_path(word)).keys
18
+ hits.sort! { |x, y| x.size <=> y.size }
19
+ pwd = shell.pwd.join('.')
20
+ hits[0...CMDB::Shell::MANY].map { |k| k.sub(pwd, '') }
21
+ end
22
+
23
+ Readline.readline(prompt(shell.cmdb, shell.pwd), true)
24
+ end
25
+
26
+ private
27
+
28
+ # Return a suitable prompt for later printing.
29
+ #
30
+ # @return [String] human-readable CMDB prompt
31
+ def prompt(cmdb, pwd)
32
+ max=@c.width/2
33
+ pwd = '/' + pwd.join('/')
34
+ pwd = '...' + pwd[-max..-1] if pwd.size >= max
35
+ 'cmdb:' +
36
+ @c.green(pwd) +
37
+ @c.default('> ')
38
+ end
39
+ end
40
+ end
@@ -15,6 +15,7 @@ module CMDB::Shell
15
15
 
16
16
  def initialize(plain)
17
17
  @plain = plain
18
+ trap('SIGWINCH') { @width = nil } unless @plain
18
19
  end
19
20
 
20
21
  COLORS.each_pair do |color, value|
@@ -54,11 +55,11 @@ module CMDB::Shell
54
55
  alias_method :bright_default, :bold
55
56
 
56
57
  # @return [Integer] screen width (number of columns)
57
- def tty_columns
58
+ def width
58
59
  if @plain
59
60
  65_535
60
61
  else
61
- Integer(`stty size`.chomp.split(/ +/)[1]) rescue 80
62
+ @width ||= Integer(`stty size`.chomp.split(/ +/)[1]) rescue 80
62
63
  end
63
64
  end
64
65
  end
data/lib/cmdb/shell.rb CHANGED
@@ -1,8 +1,16 @@
1
1
  module CMDB
2
2
  module Shell
3
+ # Maximum number of "things" before the UI starts collapsing display
4
+ # elements, i.e. treating subtrees as subdirectories.
5
+ FEW = 3
6
+
7
+ # Maximum number of "things" for which to offer autocomplete and other
8
+ # shortcuts.
9
+ MANY = 25
3
10
  end
4
11
  end
5
12
 
6
13
  require 'cmdb/shell/dsl'
7
14
  require 'cmdb/shell/text'
8
15
  require 'cmdb/shell/printer'
16
+ require 'cmdb/shell/prompter'
@@ -20,11 +20,11 @@ module CMDB
20
20
  when String
21
21
  response = json_parse(response)
22
22
  item = response.first
23
- json_parse(Base64.decode64(item['Value']))
23
+ item['Value'] && json_parse(Base64.decode64(item['Value']))
24
24
  when 404
25
25
  nil
26
26
  else
27
- raise CMDB:Error.new("Unexpected consul response #{value.inspect}")
27
+ raise CMDB::Error.new("Unexpected consul response #{response.inspect}")
28
28
  end
29
29
  end
30
30
 
@@ -60,6 +60,7 @@ module CMDB
60
60
 
61
61
  result.each do |item|
62
62
  key = slash_to_dot(item['Key'])
63
+ next unless item['Value']
63
64
  value = json_parse(Base64.decode64(item['Value']))
64
65
  yield(key, value)
65
66
  end
@@ -64,14 +64,17 @@ module CMDB
64
64
  when Hash
65
65
  flatten(value, key, output)
66
66
  when Array
67
- if value.all? { |e| e.is_a?(String) } ||
67
+ if value.any? { |e| e.is_a?(Hash) }
68
+ # mismatched arrays: not allowed
69
+ raise BadValue.new(url, key, value, 'hashes not allowed inside arrays')
70
+ elsif value.all? { |e| e.is_a?(String) } ||
68
71
  value.all? { |e| e.is_a?(Numeric) } ||
69
72
  value.all? { |e| e == true } ||
70
73
  value.all? { |e| e == false }
71
74
  output[key] = value
72
75
  else
73
76
  # mismatched arrays: not allowed
74
- raise BadValue.new(url, key, value)
77
+ raise BadValue.new(url, key, value, 'mismatched/unsupported element types')
75
78
  end
76
79
  when String, Numeric, TrueClass, FalseClass
77
80
  output[key] = value
@@ -8,7 +8,7 @@ module CMDB
8
8
  # @return [String] the empty string
9
9
  attr_reader :prefix
10
10
 
11
- # Construct a new Source::Hahs.
11
+ # Construct a new Source::Memory.
12
12
  def initialize(hash, prefix)
13
13
  @hash = hash
14
14
  @prefix = prefix
@@ -10,11 +10,12 @@ module CMDB
10
10
  private
11
11
 
12
12
  # Perform a GET. On 2xx, return the response body as a String.
13
- # On 3xx, 4xx, 5xx, return an Integer status code.
13
+ # On 3xx or 4xx return an Integer status code. On 5xx, retry up to
14
+ # the specified number of times, then return an Integer status code.
14
15
  #
15
16
  # @param [String] path
16
17
  # @return [Integer,String]
17
- def http_get(path, query:nil)
18
+ def http_get(path, query:nil, retries:3)
18
19
  @http ||= Net::HTTP.start(@uri.host, @uri.port)
19
20
  uri = @uri.dup
20
21
  uri.path = path
@@ -26,8 +27,14 @@ module CMDB
26
27
  case response.code.to_i
27
28
  when 200..299
28
29
  response.body
30
+ when 500..599
31
+ if retries > 0
32
+ http_get(path, query:query, retries:retries-1)
33
+ else
34
+ response.code.to_i
35
+ end
29
36
  else
30
- return response.code.to_i
37
+ response.code.to_i
31
38
  end
32
39
  end
33
40
 
data/lib/cmdb/source.rb CHANGED
@@ -62,6 +62,8 @@ module CMDB
62
62
  Source::Consul.new(URI.parse(curi.to_s), prefix)
63
63
  when 'file'
64
64
  Source::File.new(uri.path, prefix)
65
+ when 'memory'
66
+ Source::Memory.new({},nil)
65
67
  else
66
68
  raise ArgumentError, "Unrecognized URL scheme '#{uri.scheme}'"
67
69
  end
data/lib/cmdb/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module CMDB
3
- VERSION = '3.0.0rc1'.freeze
3
+ VERSION = '3.0.0rc2'.freeze
4
4
  end
data/lib/cmdb.rb CHANGED
@@ -39,7 +39,7 @@ module CMDB
39
39
  end
40
40
  end
41
41
 
42
- # A value of an unsupported type was encountered in the CMDB.
42
+ # A value of an unsupported type was encountered in the CMDB or filesystem.
43
43
  class BadValue < Error
44
44
  # @return [URI] filesystem or network location of the bad value
45
45
  attr_reader :url
@@ -50,14 +50,16 @@ module CMDB
50
50
  # @param [URI] url filesystem or network location of the bad value
51
51
  # @param [String] key CMDB key name under which the bad value was found
52
52
  # @param [Object] value the bad value itself
53
- def initialize(url, key, value)
53
+ def initialize(url, key, value, desc=nil)
54
54
  @url = url
55
55
  @key = key
56
- super("Values of type #{value.class.name} are unsupported")
56
+ msg = "Unsupported #{value.class.name} value"
57
+ msg << ": #{desc}" if desc
58
+ super(msg)
57
59
  end
58
60
  end
59
61
 
60
- # Malformed data was encountered in the CMDB or in an app's filesystem.
62
+ # Malformed data was encountered in the CMDB or filesystem.
61
63
  class BadData < Error
62
64
  # @return [URI] filesystem or network location of the bad data
63
65
  attr_reader :url
@@ -94,7 +96,7 @@ module CMDB
94
96
  # Deprecated name for ValueConflict
95
97
  Conflict = ValueConflict
96
98
 
97
- # Two or more keys in different sources have an identical name. This isn't an error
99
+ # Two or more keys in prefixed sources have an identical name. This isn't an error
98
100
  # when CMDB is used to refer to keys by their full, prefixed name, but it can become
99
101
  # an issue when loading keys into the environment for 12-factor apps to process.
100
102
  class NameConflict < Error
@@ -127,7 +129,7 @@ module CMDB
127
129
 
128
130
  def log
129
131
  unless @log
130
- @log = Logger.new(STDOUT)
132
+ @log = Logger.new($stderr)
131
133
 
132
134
  @log.formatter = Proc.new do |severity, datetime, progname, msg|
133
135
  "#{severity}: #{msg}\n"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0rc1
4
+ version: 3.0.0rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - RightScale
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-23 00:00:00.000000000 Z
11
+ date: 2016-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trollop
@@ -52,13 +52,16 @@ files:
52
52
  - ".rubocop.yml"
53
53
  - ".travis.yml"
54
54
  - CHANGELOG.md
55
+ - Dockerfile
55
56
  - Gemfile
56
57
  - Gemfile.lock
57
58
  - LICENSE
58
59
  - README.md
59
60
  - Rakefile
61
+ - TODO.md
60
62
  - bin/console
61
63
  - bin/setup
64
+ - bin/shell
62
65
  - cmdb.gemspec
63
66
  - docker-compose.yml
64
67
  - exe/cmdb
@@ -76,6 +79,7 @@ files:
76
79
  - lib/cmdb/shell.rb
77
80
  - lib/cmdb/shell/dsl.rb
78
81
  - lib/cmdb/shell/printer.rb
82
+ - lib/cmdb/shell/prompter.rb
79
83
  - lib/cmdb/shell/text.rb
80
84
  - lib/cmdb/source.rb
81
85
  - lib/cmdb/source/consul.rb