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.
@@ -0,0 +1,15 @@
1
+ version: "2"
2
+ networks:
3
+ default:
4
+ ipam:
5
+ config:
6
+ - subnet: 172.28.0.0/24
7
+ services:
8
+ consul:
9
+ command: "agent -client=172.28.0.2 -bootstrap -server"
10
+ image: consul
11
+ networks:
12
+ default:
13
+ ipv4_address: 172.28.0.2
14
+ ports:
15
+ - "8500"
data/exe/cmdb CHANGED
@@ -20,37 +20,64 @@ CMDB::Commands.constants.each do |konst|
20
20
  commands[name] = CMDB::Commands.const_get(konst.to_sym)
21
21
  end
22
22
 
23
- # Use a Trollop parser for help/banner display, but do not actually parse
24
- # anything just yet.
25
23
  command_list = commands.keys - ['help']
26
24
  command_info = command_list.map { |c| " * #{c}" }.join("\n")
25
+
26
+ # Use a Trollop parser for help/banner display, but do not actually parse
27
+ # anything just yet.
27
28
  p = Trollop::Parser.new do
28
29
  version "cmdb #{gemspec_version} (c) 2013-2014 RightScale, Inc."
29
30
  banner <<-EOS
30
31
  A command-line interface for configuration management.
31
32
 
32
33
  Usage:
33
- cmdb <command> [options]
34
+ cmdb [options] <command> [command-options]
34
35
 
35
36
  Where <command> is one of:
36
37
  #{command_info}
37
38
 
38
- To get help on a command:
39
- cmdb help command
39
+ To get help on a specific command and its options:
40
+ cmdb help <command>
41
+
42
+ Common options that apply to all commands:
40
43
  EOS
41
44
 
45
+ opt :source,
46
+ "Source of CMDB inputs e.g. file:///foo.yml or consul://localhost",
47
+ type: :string,
48
+ multi: true
49
+
50
+ opt :quiet,
51
+ "Suppress unnecessary output",
52
+ default: false
53
+
42
54
  stop_on commands.keys
43
55
  end
44
56
 
45
57
  Trollop.with_standard_exception_handling p do
46
58
  raise Trollop::HelpNeeded if ARGV.empty?
47
- p.parse ARGV
59
+
60
+ # Apply global options
61
+ options = p.parse ARGV
62
+ CMDB.log.level = Logger::FATAL if options[:quiet]
63
+
64
+ if options[:source].empty?
65
+ sources = CMDB::Source.detect
66
+ else
67
+ sources = options[:source].map do |source|
68
+ CMDB::Source.create(source)
69
+ end
70
+ end
71
+
72
+ interface = CMDB::Interface.new(*sources)
73
+
74
+ # Identify the subcommand and run it (or print help).
75
+ raise Trollop::HelpNeeded if ARGV.empty?
48
76
  cmd = ARGV.shift
49
77
  klass = commands[cmd]
50
-
51
78
  if klass
52
- klass.create.run
79
+ klass.create(interface).run
53
80
  else
54
- raise(ArgumentError, "Unrecognized command #{cmd}")
81
+ CMDB.log.fatal "Unrecognized command '#{cmd}'; try 'cmdb --help'"
55
82
  end
56
83
  end
@@ -1,77 +1,32 @@
1
1
  # encoding: utf-8
2
- require 'logger'
3
- require 'listen'
4
2
 
5
3
  module CMDB::Commands
6
4
  class Help
7
- def self.create
8
- options = Trollop.options do
9
- banner <<-EOS
10
- The 'shim' command adapts your applications for use with CMDB without coupling them to
11
- the CMDB RubyGem (or forcing you to write your applications in Ruby). It works by
12
- manipulating the environment or filesystem to make CMDB inputs visible, then invoking
13
- your app.
14
-
15
- To use the shim with apps that read configuration from the filesystem, use the --dir
16
- option to tell the shim where to rewrite configuration files. It will look for tokens
17
- in JSON or YML that look like <<cmdb.key.name>> and replace them with the value of
18
- the specified key.
19
-
20
- To use the shim with 12-factor apps, use the --env option to tell the shim to load
21
- every CMDB key into the environment. When using --env, the prefix of each key is
22
- omitted from the environment variable name, e.g. "common.database.host" is
23
- represented as DATABASE_HOST.
24
-
25
- To support "development mode" and reload your app whenever its files change on disk,
26
- use the --reload option and specify the name of a CMDB key that will enable this
27
- behavior.
28
-
29
- Usage:
30
- cmdb shim [options] -- <command_to_exec> [options_for_command]
31
-
32
- Where [options] are selected from:
33
- EOS
34
- opt :dir,
35
- 'Directory to scan for key-replacement tokens in data files',
36
- type: :string
37
- opt :consul_url,
38
- 'The URL for talking to consul',
39
- type: :string
40
- opt :consul_prefix,
41
- 'The prefix to use when getting keys from consul, can be specified more than once',
42
- type: :string,
43
- multi: true
44
- opt :keys,
45
- 'Override search path(s) for CMDB key files',
46
- type: :strings
47
- opt :pretend,
48
- 'Check for errors, but do not actually launch the app or rewrite files',
49
- default: false
50
- opt :quiet,
51
- "Don't print any output",
52
- default: false
53
- opt :reload,
54
- 'CMDB key that enables reload-on-edit',
55
- type: :string
56
- opt :reload_signal,
57
- 'Signal to send to app server when code is edited',
58
- type: :string,
59
- default: 'HUP'
60
- opt :env,
61
- "Add CMDB keys to the app server's process environment",
62
- default: false
63
- opt :user,
64
- 'Switch to named user before executing app',
65
- type: :string
66
- opt :root,
67
- 'Promote named subkey to the root when it is present in a namespace',
68
- type: :string
69
- end
5
+ def self.create(interface)
6
+ new(interface, ARGV.first)
7
+ end
70
8
 
71
- new(ARGV, options)
9
+ def initialize(interface, command)
10
+ @cmdb = interface
11
+ @command = command
72
12
  end
73
13
 
74
14
  def run
15
+ if @command.nil? || @command.empty?
16
+ # Same as "--help"
17
+ raise Trollop::HelpNeeded
18
+ end
19
+
20
+ # Find the command the user was talking about and print some help
21
+ konst = CMDB::Commands.constants.detect { |konst| konst.to_s.downcase == @command }
22
+ if konst
23
+ klass = CMDB::Commands.const_get(konst)
24
+ ARGV.clear ; ARGV.push('--help')
25
+ klass.create(@cmdb)
26
+ else
27
+ CMDB.log.fatal "CMDB: Unknown command '#{@command}'; try 'cmdb --help' for a list of commands"
28
+ exit 1
29
+ end
75
30
  end
76
31
  end
77
32
  end
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+
3
+ module CMDB::Commands
4
+ class Shell
5
+ # Character that acts as a separator between key components. The standard
6
+ # notation uses `.` but Shell allow allows `/` for a more filesystem-like
7
+ # user experience.
8
+ ALT_SEPARATOR = '/'.freeze
9
+
10
+ # Directory navigation shortcuts ('.' and '..')
11
+ NAVIGATION = /^#{Regexp.escape(CMDB::SEPARATOR + CMDB::SEPARATOR)}?$/.freeze
12
+
13
+ def self.create(interface)
14
+ require 'cmdb/shell'
15
+
16
+ options = Trollop.options do
17
+ banner <<-EOS
18
+ The 'shell' command enters a Unix-like shell where you can interact with
19
+ CMDB sources and inspect the value of keys.
20
+
21
+ Usage:
22
+ cmdb shell
23
+ EOS
24
+ end
25
+
26
+ new(interface)
27
+ end
28
+
29
+ # @return [CMDB::Interface]
30
+ attr_reader :cmdb
31
+
32
+ # @return [Array] the "present working directory" (i.e. key prefix) for shell commands
33
+ attr_accessor :pwd
34
+
35
+ # @return [Object] esult of the most recent command
36
+ attr_accessor :_
37
+
38
+ # @param [CMDB::Interface] interface
39
+ def initialize(interface)
40
+ @cmdb = interface
41
+ @pwd = []
42
+ @out = CMDB::Shell::Printer.new
43
+ end
44
+
45
+ # Run the shim.
46
+ #
47
+ # @raise [SystemExit] if something goes wrong
48
+ def run
49
+ @dsl = CMDB::Shell::DSL.new(self, @out)
50
+ repl
51
+ end
52
+
53
+ # Given a key name/prefix relative to `pwd`, return the absolute
54
+ # name/prefix. If `pwd` is empty, just return `subpath`. The subpath
55
+ # can use either dotted or slash notation, and directory navigation
56
+ # symbols (`.` and `..`) are applied as expected if the subpath uses
57
+ # slash notation.
58
+ #
59
+ # @return [String] absolute key in dotted notation
60
+ def expand_path(subpath)
61
+ pieces = subpath.split(ALT_SEPARATOR)
62
+
63
+ if subpath[0] == ALT_SEPARATOR
64
+ result = []
65
+ else
66
+ result = pwd.dup
67
+ end
68
+
69
+ if subpath.include?(ALT_SEPARATOR) || subpath =~ NAVIGATION
70
+ # filesystem-like subpath
71
+ # apply Unix-style directory navigation shortcuts
72
+ pieces.each do |piece|
73
+ case piece
74
+ when '..' then result.pop
75
+ when '.' then nil
76
+ else result.push(piece)
77
+ end
78
+ end
79
+
80
+ result.join(CMDB::SEPARATOR)
81
+ else
82
+ # standard dotted notation
83
+ result += pieces
84
+ end
85
+
86
+ result.join(CMDB::SEPARATOR)
87
+ end
88
+
89
+ private
90
+
91
+ def repl
92
+ require 'readline'
93
+ while line = Readline.readline(@out.prompt(self), true)
94
+ begin
95
+ line = line.chomp
96
+ next if line.nil? || line.empty?
97
+ words = line.split(/\s+/)
98
+ command, args = words.first.to_sym, words[1..-1]
99
+
100
+ run_ruby(command, args) || run_getter(line) || run_setter(line) ||
101
+ fail(CMDB::BadCommand.new(command))
102
+ handle_output(self._)
103
+ rescue => e
104
+ handle_error(e) || raise
105
+ end
106
+ end
107
+ rescue Interrupt
108
+ return 0
109
+ end
110
+
111
+ # @return [Boolean] true if line was handled as a normal command
112
+ def run_ruby(command, args)
113
+ self._ = @dsl.__send__(command, *args)
114
+ true
115
+ rescue NoMethodError
116
+ false
117
+ rescue ArgumentError => e
118
+ raise CMDB::BadCommand.new(e.message, command)
119
+ end
120
+
121
+ # @return [Boolean] true if line was handled as a getter
122
+ def run_getter(key)
123
+ if value = @dsl.get(key)
124
+ self._ = value
125
+ true
126
+ else
127
+ false
128
+ end
129
+ rescue CMDB::Error
130
+ false
131
+ end
132
+
133
+ # @return [Boolean] true if line was handled as a setter
134
+ def run_setter(line)
135
+ key, value = line.strip.split(/\s*=\s*/, 2)
136
+ return false unless key && value
137
+
138
+ value = nil unless value && value.length > 0
139
+ self._ = @dsl.set(key, value)
140
+ true
141
+ rescue CMDB::Error
142
+ false
143
+ end
144
+
145
+ def handle_output(obj)
146
+ case obj
147
+ when Hash
148
+ @out.keys_values(obj)
149
+ else
150
+ @out.value(obj)
151
+ end
152
+ end
153
+
154
+ # @return [Boolean] print message and return true if error is handled; else return false
155
+ def handle_error(e)
156
+ case e
157
+ when CMDB::BadCommand
158
+ @out.error "#{e.command}: #{e.message}"
159
+ true
160
+ else
161
+ false
162
+ end
163
+ end
164
+ end
165
+ end
@@ -1,10 +1,8 @@
1
1
  # encoding: utf-8
2
- require 'logger'
3
- require 'listen'
4
2
 
5
3
  module CMDB::Commands
6
4
  class Shim
7
- def self.create
5
+ def self.create(interface)
8
6
  options = Trollop.options do
9
7
  banner <<-EOS
10
8
  The 'shim' command adapts your applications for use with CMDB without coupling them to
@@ -12,7 +10,7 @@ the CMDB RubyGem (or forcing you to write your applications in Ruby). It works b
12
10
  manipulating the environment or filesystem to make CMDB inputs visible, then invoking
13
11
  your app.
14
12
 
15
- To use the shim with apps that read configuration from the filesystem, use the --dir
13
+ To use the shim with apps that read configuration from the filesystem, use the --rewrite
16
14
  option to tell the shim where to rewrite configuration files. It will look for tokens
17
15
  in JSON or YML that look like <<cmdb.key.name>> and replace them with the value of
18
16
  the specified key.
@@ -22,53 +20,28 @@ every CMDB key into the environment. When using --env, the prefix of each key is
22
20
  omitted from the environment variable name, e.g. "common.database.host" is
23
21
  represented as DATABASE_HOST.
24
22
 
25
- To support "development mode" and reload your app whenever its files change on disk,
26
- use the --reload option and specify the name of a CMDB key that will enable this
27
- behavior.
28
-
29
23
  Usage:
30
24
  cmdb shim [options] -- <command_to_exec> [options_for_command]
31
25
 
32
26
  Where [options] are selected from:
33
27
  EOS
34
- opt :dir,
28
+ opt :rewrite,
35
29
  'Directory to scan for key-replacement tokens in data files',
36
30
  type: :string
37
- opt :consul_url,
38
- 'The URL for talking to consul',
39
- type: :string
40
- opt :consul_prefix,
41
- 'The prefix to use when getting keys from consul, can be specified more than once',
42
- type: :string,
43
- multi: true
44
- opt :keys,
45
- 'Override search path(s) for CMDB key files',
46
- type: :strings
47
31
  opt :pretend,
48
32
  'Check for errors, but do not actually launch the app or rewrite files',
49
33
  default: false
50
- opt :quiet,
51
- "Don't print any output",
52
- default: false
53
- opt :reload,
54
- 'CMDB key that enables reload-on-edit',
55
- type: :string
56
- opt :reload_signal,
57
- 'Signal to send to app server when code is edited',
58
- type: :string,
59
- default: 'HUP'
60
34
  opt :env,
61
35
  "Add CMDB keys to the app server's process environment",
62
36
  default: false
63
37
  opt :user,
64
38
  'Switch to named user before executing app',
65
39
  type: :string
66
- opt :root,
67
- 'Promote named subkey to the root when it is present in a namespace',
68
- type: :string
69
40
  end
41
+ options.delete(:help)
70
42
 
71
- new(ARGV, options)
43
+ options.delete_if { |k, v| k.to_s =~ /_given$/i }
44
+ new(interface, ARGV, **options)
72
45
  end
73
46
 
74
47
  # Irrevocably change the current user for this Unix process by calling the
@@ -90,35 +63,18 @@ Where [options] are selected from:
90
63
 
91
64
  # Create a Shim.
92
65
  # @param [Array] command collection of string to pass to Kernel#exec; 0th element is the command name
93
- # @options [String] :condif_dir
94
- def initialize(command, options = {})
66
+ def initialize(interface, command, rewrite:, pretend:, user:)
67
+ @cmdb = interface
95
68
  @command = command
96
- @dir = options[:dir]
97
- @consul_url = options[:consul_url]
98
- @consul_prefixes = options[:consul_prefix]
99
- @keys = options[:keys] || []
100
- @pretend = options[:pretend]
101
- @reload = options[:reload]
102
- @signal = options[:reload_signal]
103
- @env = options[:env]
104
- @user = options[:user]
105
- @root = options[:root]
106
-
107
- CMDB::FileSource.base_directories = @keys unless @keys.empty?
108
- CMDB::ConsulSource.url = @consul_url unless @consul_url.nil?
109
- if !@consul_prefixes.nil? && !@consul_prefixes.empty?
110
- CMDB::ConsulSource.prefixes = @consul_prefixes
111
- end
112
-
113
- CMDB.log.level = Logger::FATAL if options[:quiet]
69
+ @dir = rewrite
70
+ @pretend = pretend
71
+ @user = user
114
72
  end
115
73
 
116
74
  # Run the shim.
117
75
  #
118
76
  # @raise [SystemExit] if something goes wrong
119
77
  def run
120
- @cmdb = CMDB::Interface.new(root: @root)
121
-
122
78
  rewrote = rewrite_files
123
79
  populated = populate_environment
124
80
 
@@ -187,8 +143,6 @@ Where [options] are selected from:
187
143
 
188
144
  # @return [Boolean]
189
145
  def populate_environment
190
- return false unless @env
191
-
192
146
  env = @cmdb.to_h
193
147
 
194
148
  env.keys.each do |k|
@@ -207,81 +161,12 @@ Where [options] are selected from:
207
161
 
208
162
  def launch_app
209
163
  if @command.any?
210
- # log this now, so that it gets logged even if @pretend
211
164
  CMDB.log.info "App will run as user #{@user}" if @user
212
-
213
- if @reload && @cmdb.get(@reload)
214
- CMDB.log.info "SIG#{@signal}-on-edit is enabled; fork app"
215
- fork_and_watch_app unless @pretend
216
- else
217
- CMDB.log.info "SIG#{@signal}-on-edit is disabled; exec app"
218
- exec_app unless @pretend
219
- end
165
+ self.class.drop_privileges(@user) if @user
166
+ exec(*@command)
220
167
  end
221
168
  end
222
169
 
223
- def exec_app
224
- self.class.drop_privileges(@user) if @user
225
- exec(*@command)
226
- end
227
-
228
- def fork_and_watch_app
229
- # let the child share our stdio handles on purpose
230
- pid = fork do
231
- exec_app
232
- end
233
-
234
- CMDB.log.info('App (pid %d) has been forked; watching %s' % [pid, ::Dir.pwd])
235
-
236
- t0 = Time.at(0)
237
-
238
- listener = Listen.to(::Dir.pwd) do |modified, added, removed|
239
- modified = modified.select { |fn| interesting?(fn) }
240
- added = added.select { |fn| interesting?(fn) }
241
- removed = removed.select { |fn| interesting?(fn) }
242
- next if modified.empty? && added.empty? && removed.empty?
243
-
244
- begin
245
- dt = Time.now - t0
246
- if dt > 15
247
- Process.kill(@signal, pid)
248
- CMDB.log.info 'Sent SIG%s to app (pid %d) because (modified,created,deleted)=(%d,%d,%d)' %
249
- [@signal, pid, modified.size, added.size, removed.size]
250
- t0 = Time.now
251
- else
252
- CMDB.log.error 'Skipped SIG%s to app (pid %d) due to timeout (%d)' %
253
- [@signal, pid, dt]
254
- end
255
- rescue
256
- CMDB.log.error 'Skipped SIG%s to app (pid %d) due to %s' %
257
- [@signal, pid, $ERROR_INFO.to_s]
258
- end
259
- end
260
-
261
- listener.start
262
-
263
- wpid = nil
264
- wstatus = nil
265
-
266
- loop do
267
- begin
268
- wpid, wstatus = Process.wait2(-1, Process::WNOHANG)
269
- if wpid
270
- break if wstatus.exited?
271
- CMDB.log.info('Descendant (pid %d) has waited with %s' % [wpid, wstatus.inspect])
272
- end
273
- rescue
274
- CMDB.log.error 'Skipped wait2 to app (pid %d) due to %s' %
275
- [pid, $ERROR_INFO.to_s]
276
- end
277
- sleep(1)
278
- end
279
-
280
- CMDB.log.info('App (pid %d) has exited with %s' % [wpid, wstatus.inspect])
281
- listener.stop
282
- exit(wstatus.exitstatus || 43)
283
- end
284
-
285
170
  def report_rewrite(total)
286
171
  CMDB.log.info "Replaced #{total} variables in #{@dir}"
287
172
  end
data/lib/cmdb/commands.rb CHANGED
@@ -5,4 +5,5 @@ module CMDB
5
5
  end
6
6
 
7
7
  require 'cmdb/commands/help'
8
+ require 'cmdb/commands/shell'
8
9
  require 'cmdb/commands/shim'