cmdb 3.0.0rc1 → 3.0.0rc2

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