cmdb 0.1.0 → 2.6.0

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: 9212599111d82e3f236f06fb05aaf87ec0932155
4
- data.tar.gz: 87200c6133a80e8f2bd02f02697aa0d42c694622
3
+ metadata.gz: df72629dd0ba9fcb24a33607ca6216e9a70ff188
4
+ data.tar.gz: 2daf8320e35c3cd58234d86b452b8574cc76e944
5
5
  SHA512:
6
- metadata.gz: 86b209d6f11b166e981cd7a0eeac50f10a78a8efa5ef4db7c973642c4eb26aa519741b7ffbdbb3e1e266f794e9429c19fb646bdddb749ae18cdd0429f4fc3fa6
7
- data.tar.gz: 33d7b4855c49ece993167e1dbfd7da504a337fae128a60096d037fcaa51f8b0815658b2ac2ffb32d1875563464b16e1447a85316429dc0d08a0e0c760fbf48e0
6
+ metadata.gz: 97c03113ce4aa1c50718edadd6c9649096a22916bf14a16721df59987f56745c8150131a5bbb63d6d6f2c3c9e4db59fff2568f2480e0e3488468400b4cc92253
7
+ data.tar.gz: 38cc4d3c2701dfc66b2d3f27470851bd14128e5ec4b3548e85fa292daced4d609cb43f4d63746a73e449cd410e78f109ea27f8f822b99e73a0d4a45f4fa6fd7c
data/.travis.yml CHANGED
@@ -8,6 +8,5 @@ before_install:
8
8
  before_script:
9
9
  - bundle install
10
10
  script:
11
- - bundle exec rake ci:spec
12
- - bundle exec rake ci:cucumber
13
-
11
+ - bundle exec rake spec
12
+ - bundle exec rake cucumber
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cmdb (0.1.0)
4
+ cmdb (2.6.0)
5
5
  diplomat (~> 0.15)
6
6
  listen (~> 3.0)
7
7
  trollop (~> 2.0)
data/README.md CHANGED
@@ -1,47 +1,50 @@
1
1
  # CMDB
2
2
 
3
- [![TravisCI][travis_ci_img]](https://magnum.travis-ci.com/rightscale/cmdb)
4
- [travis_ci_img]: https://magnum.travis-ci.com/rightscale/cmdb.svg?token=ETyVMY7RGypDf6U8z6jc&branch=master
3
+ [![TravisCI][travis_ci_img]](https://travis-ci.org/rightscale/cmdb)
4
+ [travis_ci_img]: https://travis-ci.org/rightscale/cmdb.svg?branch=master
5
+
6
+ *NOTE:* this gem is under heavy development and it is likely that v3 will contain several interface-breaking
7
+ changes and simplifications. We encourage you to play with our toys and give us feedback on how you would
8
+ like to see the project evolve, but if you use this gem for production-grade software, please make sure to
9
+ pin to version `~> 2.6` in your Gemfile to avoid breakage!
5
10
 
6
11
  CMDB is a Ruby interface for consuming data from one or more configuration management databases
7
- (CMDBs). It is intended to support multiple CM technologies, including:
12
+ (CMDBs) and making that information available to Web applications.
13
+
14
+ It is intended to support multiple CM technologies, including:
8
15
  - JSON/YAML files on a local disk
9
16
  - consul
10
17
  - (someday) etcd
11
18
  - (someday) ZooKeeper
12
19
 
13
- The CMDB's command-line tool can also facilitate debugging by watching your application's files
14
- and sending SIGHUP (or the signal of your choice) to the app server if anything changes.
15
-
16
20
  Maintained by
17
21
  - [RightScale Inc.](https://www.rightscale.com)
18
22
 
19
23
  ## Why should I use this gem?
20
24
 
21
- By using a standardized API for querying inputs, your application is decoupled from the underlying
22
- configuration management database that our Operations team is using. Today we happen to use JSON
23
- files that live under /etc/rightscale, but tomorrow we might use a key/value store such as etcd or
24
- consul (possibly in combination with flat files). This gem will deal with all of that complexity
25
- and provide you with a simple interface for reading your app's configuration.
25
+ CMDB supports two primary use cases:
26
+
27
+ 1. Decouple your modern (12-factor) application from the CM mechanism that is used to deploy it,
28
+ transforming CMDB keys and values into the enviroment variables that your app expects.
29
+ 2. Help you deploy your "legacy" application that expects its configuration to be written to
30
+ disk files, rewriting those files with data taken from the CMDB.
26
31
 
27
32
  The gem has two primary interfaces:
28
- - The `cmdb shim` command populates the environment with config data and/or rewrites hardcoded
33
+ - The `cmdb shim` command populates the environment with values and/or rewrites hardcoded
29
34
  config files, then spawns your application. It can also be told to watch the filesystem for changes and
30
35
  send a signal e.g. `SIGHUP` to your application, bringing reload-on-edit functionality to any app.
31
- - The `CMDB::Interface` object provides a programmatic API for querying configuration. Its `#to_h`
36
+ - The `CMDB::Interface` object provides a programmatic API for querying CMDBs. Its `#to_h`
32
37
  method transforms the whole configuration into an environment-friendly hash if you prefer to seed the
33
- environment yourself.
34
-
35
- The CMDB gem is friendly to 12-factor and "Rails-style" apps and can be used with or without the shim,
36
- depending on your application's needs.
38
+ environment yourself, without using the shim.
37
39
 
38
40
  # Getting Started
39
41
 
40
42
  ## Create CMDB Data Files
41
-
43
+
42
44
  The shim looks in two locations to find data files. In order of precedence:
43
- 1. `/var/lib/cmdb` -- typically used at deployment time
44
- 2. `~/.cmdb` -- useful for developers when testing the app
45
+
46
+ 1. `/var/lib/cmdb` -- typically at deployment time
47
+ 2. `~/.cmdb` -- useful for developers when testing the app
45
48
 
46
49
  The base name (minus extension) of each file is important; it determines the top-level name of
47
50
  the keys in that file and it *must be unique* across all of the directories. For instance,
@@ -59,7 +62,7 @@ the following contents:
59
62
 
60
63
  ## Invoke the CMDB Shim
61
64
 
62
- For non-Ruby applications, or for situations where CMDB values are required
65
+ For non-Ruby applications, or for situations where CMDB values are required
63
66
  outside of the context of interpreted code, use `cmdb shim` to run
64
67
  your application. The shim can do several things for you:
65
68
 
@@ -83,8 +86,8 @@ for CMDB values that are serialized to the environment (as a JSON document, in t
83
86
 
84
87
  ### Rewriting configuration files with CMDB values
85
88
 
86
- If the `--dir` option is provided, the shim recursively scans your working
87
- directory (`Dir.pwd`) for data files that contain replacement tokens; when a token is
89
+ If the `--dir` option is provided, the shim recursively scans your working
90
+ directory (`Dir.pwd`) for data files that contain replacement tokens; when a token is
88
91
  found, it substitutes the corresponding CMDB key's value.
89
92
 
90
93
  Replacement tokens look like this: `<<name.of.my.key>>` and can appear anywhere in a file as a YAML
@@ -93,7 +96,7 @@ or JSON _value_ (but never a key).
93
96
  Replacement tokens should appear inside string literals in your configuration files so they don't
94
97
  invalidate syntax or render the files unparsable by other tools.
95
98
 
96
- The shim performs replacement in-memory and saves all of the edits at once, making the rewrite
99
+ The shim performs replacement in-memory and saves all of the edits at once, making the rewrite
97
100
  operation nearly atomic. If any keys are missing, then no files are changed on disk and the shim
98
101
  exits with a helpful error message.
99
102
 
@@ -115,7 +118,7 @@ I can run the following command in my application's root directory:
115
118
 
116
119
  bundle exec cmdb shim --dir=config rackup
117
120
 
118
- This will rewrite the files under config, replacing my configuration files as
121
+ This will rewrite the files under config, replacing my configuration files as
119
122
  follows:
120
123
 
121
124
  # config/database.yml
@@ -172,6 +175,27 @@ value of a CMDB key can be a string, boolean, number, nil, or a list of any of t
172
175
 
173
176
  CMDB keys *cannot* contain maps/hashes, nor can lists contain differently-typed data.
174
177
 
178
+ When a CMDB key is accessed through the Ruby API or referenced with a file-rewrite <<token>>, its
179
+ name always begins with the file or path name of its *source* (JSON file, consul path, etc).
180
+
181
+ When a CMDB key is written into the process environment or accessed via `Source#to_h`, its name
182
+ is "bare" and the source name is irrelevant.
183
+
184
+ If we use a `--consul-prefix` of `/kv/rightscale/intregration/shard403/common`
185
+ then a key names would look like `common.debug.enabled` and environment names
186
+ would look like `DEBUG_ENABLED`. The same is true if we load a `common.json`
187
+ file source from `/var/lib/cmdb`.
188
+
189
+ A future version of cmdb will harmonize the treatment of names; the prefix
190
+ will be insignificant to the key name and keys will look like environment
191
+ variables.
192
+
193
+ ## Network Data Sources
194
+
195
+ To read from a consul server, pass `--consul-url` with a consul server address
196
+ and `--consul-prefix` one or more times with a top-level path to treat as a
197
+ named source.
198
+
175
199
  ## Disk-Based Data Sources
176
200
 
177
201
  When the CMDB interface is initialized, it searches two directories for YAML files:
@@ -212,10 +236,10 @@ on the value of RACK_ENV or RAILS_ENV:
212
236
  - unset, development or test: CMDB chooses the highest-precedence file and ignores the others
213
237
  after printing a warning. Files in `/etc` win over files in `$HOME`, which win over
214
238
  files in the working directory.
215
-
239
+
216
240
  - any other environment: CMDB fails with an error message that describes the problem and
217
241
  the locations of the overlapping files.
218
-
242
+
219
243
  ### Ambiguous Key Names
220
244
 
221
245
  Consider a file that defines the following variables:
@@ -244,7 +268,3 @@ of the keys could change if the structure of the YML file changes.
244
268
  For this reason, any YAML file that defines an "ambiguous" key name will cause an error at
245
269
  initialization time. To avoid ambiguous key names, think of your YAML file as a tree and remember
246
270
  that _leaf nodes must define data_ and _internal nodes must define structure_.
247
-
248
- ## Network Data Sources
249
-
250
- TODO: add support for etcd or similar
data/cmdb.gemspec CHANGED
@@ -1,6 +1,11 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cmdb/version'
5
+
1
6
  Gem::Specification.new do |spec|
2
7
  spec.name = 'cmdb'
3
- spec.version = '0.1.0'
8
+ spec.version = CMDB::VERSION
4
9
  spec.authors = ['RightScale']
5
10
  spec.email = ['rubygems@rightscale.com']
6
11
 
@@ -10,6 +15,8 @@ Gem::Specification.new do |spec|
10
15
  spec.license = 'MIT'
11
16
 
12
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
13
20
  spec.require_paths = ['lib']
14
21
 
15
22
  spec.required_ruby_version = Gem::Requirement.new("~> 2.0")
data/exe/cmdb ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'trollop'
5
+ require 'cmdb'
6
+
7
+ if gemspec = Gem.loaded_specs['cmdb']
8
+ gemspec_version = gemspec.version
9
+ else
10
+ require_relative '../lib/cmdb/version'
11
+ gemspec_version = CMDB::VERSION
12
+ end
13
+
14
+ commands = {}
15
+ CMDB::Commands.constants.each do |konst|
16
+ name = konst.to_s.downcase
17
+ commands[name] = CMDB::Commands.const_get(konst.to_sym)
18
+ end
19
+
20
+ # Use a Trollop parser for help/banner display, but do not actually parse
21
+ # anything just yet.
22
+ command_list = commands.keys - ['help']
23
+ command_info = command_list.map { |c| " * #{c}" }.join("\n")
24
+ p = Trollop::Parser.new do
25
+ version "cmdb #{gemspec_version} (c) 2013-2014 RightScale, Inc."
26
+ banner <<-EOS
27
+ A command-line interface for configuration management.
28
+
29
+ Usage:
30
+ cmdb <command> [options]
31
+
32
+ Where <command> is one of:
33
+ #{command_info}
34
+
35
+ To get help on a command:
36
+ cmdb help command
37
+ EOS
38
+
39
+ stop_on commands.keys
40
+ end
41
+
42
+ opts = Trollop::with_standard_exception_handling p do
43
+ raise Trollop::HelpNeeded if ARGV.empty?
44
+ p.parse ARGV
45
+ cmd = ARGV.shift
46
+ klass = commands[cmd]
47
+
48
+ if klass
49
+ klass.create.run
50
+ else
51
+ raise ArgumentError, "Unrecognized command #{cmd}"
52
+ end
53
+ end
@@ -0,0 +1,75 @@
1
+ require 'logger'
2
+ require 'listen'
3
+
4
+ module CMDB::Commands
5
+ class Help
6
+ def self.create
7
+ options = Trollop.options do
8
+ banner <<-EOS
9
+ The 'shim' command adapts your applications for use with CMDB without coupling them to
10
+ the CMDB RubyGem (or forcing you to write your applications in Ruby). It works by
11
+ manipulating the environment or filesystem to make CMDB inputs visible, then invoking
12
+ your app.
13
+
14
+ To use the shim with apps that read configuration from the filesystem, use the --dir
15
+ option to tell the shim where to rewrite configuration files. It will look for tokens
16
+ in JSON or YML that look like <<cmdb.key.name>> and replace them with the value of
17
+ the specified key.
18
+
19
+ To use the shim with 12-factor apps, use the --env option to tell the shim to load
20
+ every CMDB key into the environment. When using --env, the prefix of each key is
21
+ omitted from the environment variable name, e.g. "common.database.host" is
22
+ represented as DATABASE_HOST.
23
+
24
+ To support "development mode" and reload your app whenever its files change on disk,
25
+ use the --reload option and specify the name of a CMDB key that will enable this
26
+ behavior.
27
+
28
+ Usage:
29
+ cmdb shim [options] -- <command_to_exec> [options_for_command]
30
+
31
+ Where [options] are selected from:
32
+ EOS
33
+ opt :dir,
34
+ "Directory to scan for key-replacement tokens in data files",
35
+ :type => :string
36
+ opt :consul_url,
37
+ "The URL for talking to consul",
38
+ :type => :string
39
+ opt :consul_prefix,
40
+ "The prefix to use when getting keys from consul, can be specified more than once",
41
+ :type => :string,
42
+ :multi => true
43
+ opt :keys,
44
+ "Override search path(s) for CMDB key files",
45
+ :type => :strings
46
+ opt :pretend,
47
+ "Check for errors, but do not actually launch the app or rewrite files",
48
+ :default => false
49
+ opt :quiet,
50
+ "Don't print any output",
51
+ :default => false
52
+ opt :reload,
53
+ "CMDB key that enables reload-on-edit",
54
+ :type => :string
55
+ opt :reload_signal,
56
+ "Signal to send to app server when code is edited",
57
+ :type => :string,
58
+ :default => "HUP"
59
+ opt :env,
60
+ "Add CMDB keys to the app server's process environment",
61
+ :default => false
62
+ opt :user,
63
+ "Switch to named user before executing app",
64
+ :type => :string
65
+ opt :root,
66
+ "Promote named subkey to the root when it is present in a namespace",
67
+ :type => :string
68
+ end
69
+
70
+ self.new(ARGV, options)
71
+ end
72
+
73
+
74
+ end
75
+ end
@@ -0,0 +1,298 @@
1
+ require 'logger'
2
+ require 'listen'
3
+
4
+ module CMDB::Commands
5
+ class Shim
6
+ def self.create
7
+ options = Trollop.options do
8
+ banner <<-EOS
9
+ The 'shim' command adapts your applications for use with CMDB without coupling them to
10
+ the CMDB RubyGem (or forcing you to write your applications in Ruby). It works by
11
+ manipulating the environment or filesystem to make CMDB inputs visible, then invoking
12
+ your app.
13
+
14
+ To use the shim with apps that read configuration from the filesystem, use the --dir
15
+ option to tell the shim where to rewrite configuration files. It will look for tokens
16
+ in JSON or YML that look like <<cmdb.key.name>> and replace them with the value of
17
+ the specified key.
18
+
19
+ To use the shim with 12-factor apps, use the --env option to tell the shim to load
20
+ every CMDB key into the environment. When using --env, the prefix of each key is
21
+ omitted from the environment variable name, e.g. "common.database.host" is
22
+ represented as DATABASE_HOST.
23
+
24
+ To support "development mode" and reload your app whenever its files change on disk,
25
+ use the --reload option and specify the name of a CMDB key that will enable this
26
+ behavior.
27
+
28
+ Usage:
29
+ cmdb shim [options] -- <command_to_exec> [options_for_command]
30
+
31
+ Where [options] are selected from:
32
+ EOS
33
+ opt :dir,
34
+ "Directory to scan for key-replacement tokens in data files",
35
+ :type => :string
36
+ opt :consul_url,
37
+ "The URL for talking to consul",
38
+ :type => :string
39
+ opt :consul_prefix,
40
+ "The prefix to use when getting keys from consul, can be specified more than once",
41
+ :type => :string,
42
+ :multi => true
43
+ opt :keys,
44
+ "Override search path(s) for CMDB key files",
45
+ :type => :strings
46
+ opt :pretend,
47
+ "Check for errors, but do not actually launch the app or rewrite files",
48
+ :default => false
49
+ opt :quiet,
50
+ "Don't print any output",
51
+ :default => false
52
+ opt :reload,
53
+ "CMDB key that enables reload-on-edit",
54
+ :type => :string
55
+ opt :reload_signal,
56
+ "Signal to send to app server when code is edited",
57
+ :type => :string,
58
+ :default => "HUP"
59
+ opt :env,
60
+ "Add CMDB keys to the app server's process environment",
61
+ :default => false
62
+ opt :user,
63
+ "Switch to named user before executing app",
64
+ :type => :string
65
+ opt :root,
66
+ "Promote named subkey to the root when it is present in a namespace",
67
+ :type => :string
68
+ end
69
+
70
+ self.new(ARGV, options)
71
+ end
72
+
73
+ # Irrevocably change the current user for this Unix process by calling the
74
+ # setresuid system call. This sets both the uid and gid (to the user's primary
75
+ # group).
76
+ #
77
+ # @param [String] login name of user to switch to
78
+ # @return [true]
79
+ # @raise [ArgumentError] if the named user does not exist
80
+ def self.drop_privileges(login)
81
+ pwent = Etc.getpwnam(login)
82
+ Process::Sys.setresgid(pwent.gid, pwent.gid, pwent.gid)
83
+ Process::Sys.setresuid(pwent.uid, pwent.uid, pwent.uid)
84
+ true
85
+ end
86
+
87
+ # @return [CMDB::Interface]
88
+ attr_reader :cmdb
89
+
90
+ # Create a Shim.
91
+ # @param [Array] command collection of string to pass to Kernel#exec; 0th element is the command name
92
+ # @options [String] :condif_dir
93
+ def initialize(command, options={})
94
+ @command = command
95
+ @dir = options[:dir]
96
+ @consul_url = options[:consul_url]
97
+ @consul_prefixes = options[:consul_prefix]
98
+ @keys = options[:keys] || []
99
+ @pretend = options[:pretend]
100
+ @reload = options[:reload]
101
+ @signal = options[:reload_signal]
102
+ @env = options[:env]
103
+ @user = options[:user]
104
+ @root = options[:root]
105
+
106
+ unless @keys.empty?
107
+ CMDB::FileSource.base_directories = @keys
108
+ end
109
+ unless @consul_url.nil?
110
+ CMDB::ConsulSource.url = @consul_url
111
+ end
112
+ if !@consul_prefixes.nil? && !@consul_prefixes.empty?
113
+ CMDB::ConsulSource.prefixes = @consul_prefixes
114
+ end
115
+
116
+ if options[:quiet]
117
+ CMDB.log.level = Logger::FATAL
118
+ end
119
+ end
120
+
121
+ # Run the shim.
122
+ #
123
+ # @raise [SystemExit] if something goes wrong
124
+ def run
125
+ @cmdb = CMDB::Interface.new(:root=>@root)
126
+
127
+ rewrote = rewrite_files
128
+ populated = populate_environment
129
+
130
+ if (!rewrote && !populated && !@pretend && @command.empty?)
131
+ CMDB.log.warn "CMDB: nothing to do; please specify --dir, --env, or a command to run"
132
+ exit 7
133
+ end
134
+
135
+ launch_app
136
+ rescue CMDB::BadKey => e
137
+ CMDB.log.fatal "CMDB: Bad Key: malformed CMDB key '#{e.key}'"
138
+ exit 1
139
+ rescue CMDB::BadValue => e
140
+ CMDB.log.fatal "CMDB: Bad Value: illegal value for CMDB key '#{e.key}' in source #{e.url}"
141
+ exit 2
142
+ rescue CMDB::BadData => e
143
+ CMDB.log.fatal "CMDB: Bad Data: malformed CMDB data in source #{e.url}"
144
+ exit 3
145
+ rescue CMDB::ValueConflict => e
146
+ CMDB.log.fatal "CMDB: Value Conflict: #{e.message}"
147
+ e.sources.each do |s|
148
+ CMDB.log.fatal " - #{s.url}"
149
+ end
150
+ exit 4
151
+ rescue CMDB::NameConflict => e
152
+ CMDB.log.fatal "CMDB: Name Conflict: #{e.message}"
153
+ e.keys.each do |k|
154
+ CMDB.log.fatal " - #{k}"
155
+ end
156
+ exit 4
157
+ rescue CMDB::EnvironmentConflict => e
158
+ CMDB.log.fatal "CMDB: Environment Conflict: #{e.message}"
159
+ exit 5
160
+ rescue Errno::ENOENT => e
161
+ CMDB.log.fatal "CMDB: missing file or directory #{e.message}"
162
+ exit 6
163
+ end
164
+
165
+ private
166
+
167
+ # @return [Boolean]
168
+ def rewrite_files
169
+ return false unless @dir
170
+
171
+ CMDB.log.info 'Starting rewrite of configuration...'
172
+
173
+ rewriter = CMDB::Rewriter.new(@dir)
174
+
175
+ total = rewriter.rewrite(@cmdb)
176
+
177
+ if rewriter.missing_keys.any?
178
+ missing = rewriter.missing_keys.map { |k| " #{k}" }.join("\n")
179
+ CMDB.log.error "Cannot rewrite configuration; #{rewriter.missing_keys.size} missing keys:\n#{missing}"
180
+
181
+ exit -rewriter.missing_keys.size
182
+ end
183
+
184
+ report_rewrite(total)
185
+
186
+ if @pretend
187
+ false
188
+ else
189
+ rewriter.save
190
+ end
191
+ end
192
+
193
+ # @return [Boolean]
194
+ def populate_environment
195
+ return false unless @env
196
+
197
+ env = @cmdb.to_h
198
+
199
+ env.keys.each do |k|
200
+ raise CMDB::EnvironmentConflict.new(k) if ENV.key?(k)
201
+ end
202
+
203
+ if @pretend
204
+ false
205
+ else
206
+ env.each_pair do |k, v|
207
+ ENV[k] = v
208
+ end
209
+ true
210
+ end
211
+ end
212
+
213
+ def launch_app
214
+ if @command.any?
215
+ # log this now, so that it gets logged even if @pretend
216
+ CMDB.log.info "App will run as user #{@user}" if @user
217
+
218
+ if @reload && @cmdb.get(@reload)
219
+ CMDB.log.info "SIG#{@signal}-on-edit is enabled; fork app"
220
+ fork_and_watch_app unless @pretend
221
+ else
222
+ CMDB.log.info "SIG#{@signal}-on-edit is disabled; exec app"
223
+ exec_app unless @pretend
224
+ end
225
+ end
226
+ end
227
+
228
+ def exec_app
229
+ self.class.drop_privileges(@user) if @user
230
+ exec(*@command)
231
+ end
232
+
233
+ def fork_and_watch_app
234
+ # let the child share our stdio handles on purpose
235
+ pid = fork do
236
+ exec_app
237
+ end
238
+
239
+ CMDB.log.info("App (pid %d) has been forked; watching %s" % [pid, ::Dir.pwd])
240
+
241
+ t0 = Time.at(0)
242
+
243
+ listener = Listen.to(::Dir.pwd) do |modified, added, removed|
244
+ modified = modified.select { |fn| interesting?(fn) }
245
+ added = added.select { |fn| interesting?(fn) }
246
+ removed = removed.select { |fn| interesting?(fn) }
247
+ next if modified.empty? && added.empty? && removed.empty?
248
+
249
+ begin
250
+ dt = Time.now - t0
251
+ if dt > 15
252
+ Process.kill(@signal, pid)
253
+ CMDB.log.info "Sent SIG%s to app (pid %d) because (modified,created,deleted)=(%d,%d,%d)" %
254
+ [@signal, pid, modified.size, added.size, removed.size]
255
+ t0 = Time.now
256
+ else
257
+ CMDB.log.error "Skipped SIG%s to app (pid %d) due to timeout (%d)" %
258
+ [@signal, pid, dt]
259
+ end
260
+ rescue
261
+ CMDB.log.error "Skipped SIG%s to app (pid %d) due to %s" %
262
+ [@signal, pid, $!.to_s]
263
+ end
264
+ end
265
+
266
+ listener.start
267
+
268
+ wpid, wstatus = nil, nil
269
+
270
+ loop do
271
+ begin
272
+ wpid, wstatus = Process.wait2(-1, Process::WNOHANG)
273
+ if wpid == pid && wstatus.exited?
274
+ break
275
+ elsif wpid
276
+ CMDB.log.info("Descendant (pid %d) has waited with %s" % [wpid, wstatus.inspect])
277
+ end
278
+ rescue
279
+ CMDB.log.error "Skipped wait2 to app (pid %d) due to %s" %
280
+ [pid, $!.to_s]
281
+ end
282
+ sleep(1)
283
+ end
284
+
285
+ CMDB.log.info("App (pid %d) has exited with %s" % [wpid, wstatus.inspect])
286
+ listener.stop
287
+ exit(wstatus.exitstatus || 43)
288
+ end
289
+
290
+ def report_rewrite(total)
291
+ CMDB.log.info "Replaced #{total} variables in #{@dir}"
292
+ end
293
+
294
+ def interesting?(fn)
295
+ !File.basename(fn).start_with?('.')
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,7 @@
1
+ module CMDB
2
+ module Commands
3
+ end
4
+ end
5
+
6
+ require 'cmdb/commands/help'
7
+ require 'cmdb/commands/shim'
@@ -0,0 +1,77 @@
1
+ require 'diplomat'
2
+
3
+ module CMDB
4
+ class ConsulSource
5
+ # Regular expression to match array values
6
+ ARRAY_VALUE = /^\[(.*)\]$/
7
+
8
+ # The url to communicate with consul
9
+ @@url = nil
10
+ # The prefixes to use when getting keys from consul
11
+ @@prefixes = nil
12
+
13
+ def self.url=(url)
14
+ @@url = url
15
+ end
16
+
17
+ def self.url
18
+ @@url
19
+ end
20
+
21
+ def self.prefixes=(prefixes)
22
+ @@prefixes = prefixes
23
+ end
24
+
25
+ def self.prefixes
26
+ @@prefixes
27
+ end
28
+
29
+ # Initialize the configuration for consul source
30
+ def initialize(prefix)
31
+ Diplomat.configure do |config|
32
+ config.url = @@url
33
+ end
34
+ @prefix = prefix
35
+ end
36
+
37
+ # Get a single key from consul
38
+ def get(key)
39
+ value = Diplomat::Kv.get(dot_to_slash(key))
40
+ process_value(value)
41
+ rescue Diplomat::KeyNotFound
42
+ nil
43
+ end
44
+
45
+ # Not implemented for consul source
46
+ def each_pair(&block)
47
+ prefix = @prefix || ''
48
+ all = Diplomat::Kv.get(prefix, recurse: true)
49
+ all.each do |item|
50
+ dotted_prefix = prefix.split('/').join('.')
51
+ dotted_key = item[:key].split('/').join('.')
52
+ key = dotted_prefix == '' ? dotted_key : dotted_key.split("#{dotted_prefix}.").last
53
+ value = process_value(item[:value])
54
+ puts "Key: #{key}, Value: #{value}"
55
+ block.call(dotted_key.split("#{dotted_prefix}.").last, process_value(item[:value]))
56
+ end
57
+ rescue Diplomat::KeyNotFound
58
+ end
59
+
60
+ private
61
+
62
+ def process_value(val)
63
+ return JSON.load(val)
64
+ rescue Exception
65
+ return val
66
+ end
67
+
68
+ # Converts the dotted notation to a slashed notation. If a @@prefix is set, it applies the prefix.
69
+ # @example
70
+ # "common.proxy.endpoints" => common/proxy/endpoints (or) shard403/common/proxy/endpoints
71
+ def dot_to_slash(key)
72
+ key = "#{@prefix}.#{key}" unless @prefix.nil?
73
+ key.split('.').join('/')
74
+ end
75
+ end
76
+ end
77
+
@@ -0,0 +1,102 @@
1
+ require 'uri'
2
+
3
+ module CMDB
4
+ # Data source that is backed by a YAML file that lives in the filesystem. The name of the YAML
5
+ # file becomes the top-level key under which all values in the YAML are exposed, preserving
6
+ # their exact structure as parsed by YAML.
7
+ #
8
+ # @example Use my.yml as a CMDB source
9
+ # source = FileSource.new('/tmp/my.yml') # contains a top-level stanza named "database"
10
+ # source['my']['database']['host'] # => 'db1-1.example.com'
11
+ class FileSource
12
+ # @return [URI] a file:// URL describing where this source's data comes from
13
+ attr_reader :prefix, :url
14
+
15
+ @@base_directories = ['/var/lib/cmdb', File.expand_path('~/.cmdb')]
16
+
17
+ # List of directories that will be searched (in order) for YML files at load time.
18
+ # @return [Array] collection of String
19
+ def self.base_directories
20
+ @@base_directories
21
+ end
22
+
23
+ # @param [Array] bd collection of String absolute paths to search for YML files
24
+ def self.base_directories=(bd)
25
+ @@base_directories = bd
26
+ end
27
+
28
+ # Construct a new FileSource from an input YML file.
29
+ # @param [String,Pathname] filename path to a YAML file
30
+ # @raise [BadData] if the file's content is malformed
31
+ def initialize(filename, root=nil)
32
+ filename = File.expand_path(filename)
33
+ @url = URI.parse("file://#{filename}")
34
+ @prefix = File.basename(filename, ".*")
35
+ @extension = File.extname(filename)
36
+ @data = {}
37
+
38
+ raw_bytes = File.read(filename)
39
+ raw_data = nil
40
+
41
+ begin
42
+ case @extension
43
+ when /jso?n?$/i
44
+ raw_data = JSON.load(raw_bytes)
45
+ when /ya?ml$/i
46
+ raw_data = YAML.load(raw_bytes)
47
+ else
48
+ raise BadData.new(self.url, 'file with unknown extension; expected js(on) or y(a)ml')
49
+ end
50
+ rescue Exception
51
+ raise BadData.new(self.url, 'CMDB data file')
52
+ end
53
+
54
+ raw_data = raw_data[root] if !root.nil? && raw_data.key?(root)
55
+ flatten(raw_data, @prefix, @data)
56
+ end
57
+
58
+ # Get the value of key.
59
+ #
60
+ # @return [nil,String,Numeric,TrueClass,FalseClass,Array] the key's value, or nil if not found
61
+ def get(key)
62
+ @data[key]
63
+ end
64
+
65
+ # Enumerate the keys in this source, and their values.
66
+ #
67
+ # @yield every key/value in the source
68
+ # @yieldparam [String] key
69
+ # @yieldparam [Object] value
70
+ def each_pair(&block)
71
+ # Strip the prefix in the key and call the block
72
+ @data.each_pair { |k, v| block.call(k.split("#{@prefix}.").last, v) }
73
+ end
74
+
75
+ private
76
+
77
+ def flatten(data, prefix, output)
78
+ data.each_pair do |key, value|
79
+ key = "#{prefix}.#{key}"
80
+ case value
81
+ when Hash
82
+ flatten(value, key, output)
83
+ when Array
84
+ if value.all? { |e| e.is_a?(String) } ||
85
+ value.all? { |e| e.is_a?(Numeric) } ||
86
+ value.all? { |e| e == true } ||
87
+ value.all? { |e| e == false }
88
+ output[key] = value
89
+ else
90
+ # mismatched arrays: not allowed
91
+ raise BadValue.new(self.url, key, value)
92
+ end
93
+ when String, Numeric, TrueClass, FalseClass
94
+ output[key] = value
95
+ else
96
+ # nil and anything else: not allowed
97
+ raise BadValue.new(self.url, key, value)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,165 @@
1
+ require 'json'
2
+
3
+ module CMDB
4
+ class Interface
5
+ # Create a new instance of the CMDB interface.
6
+ # @option settings [String] root name of subkey to consider as root
7
+ def initialize(settings={})
8
+ @root = settings[:root] if settings
9
+
10
+ namespaces = {}
11
+
12
+ load_file_sources(namespaces)
13
+ check_overlap(namespaces)
14
+
15
+ @sources = []
16
+ # Load from consul source first if one is available.
17
+ if !ConsulSource.url.nil?
18
+ if ConsulSource.prefixes.nil? || ConsulSource.prefixes.empty?
19
+ @sources << ConsulSource.new('')
20
+ else
21
+ ConsulSource.prefixes.each do |prefix|
22
+ @sources << ConsulSource.new(prefix)
23
+ end
24
+ end
25
+ end
26
+ # Register valid sources with CMDB
27
+ namespaces.each do |_, v|
28
+ @sources << v.first
29
+ end
30
+ end
31
+
32
+ # Retrieve the value of a CMDB key, searching all sources in the order they were initialized.
33
+ #
34
+ # @return [Object,nil] the value of the key, or nil if key not found
35
+ # @param [String] key
36
+ # @raise [BadKey] if the key name is malformed
37
+ def get(key)
38
+ raise BadKey.new(key) unless key =~ VALID_KEY
39
+ value = nil
40
+
41
+ @sources.each do |s|
42
+ value = s.get(key)
43
+ break unless value.nil?
44
+ end
45
+
46
+ value
47
+ end
48
+
49
+ # Retrieve the value of a CMDB key; raise an exception if the key is not found.
50
+ #
51
+ # @return [Object,nil] the value of the key
52
+ # @param [String] key
53
+ # @raise [MissingKey] if the key is absent from the CMDB
54
+ # @raise [BadKey] if the key name is malformed
55
+ def get!(key)
56
+ get(key) || raise(MissingKey.new(key))
57
+ end
58
+
59
+ # Enumerate all of the keys in the CMDB.
60
+ #
61
+ # @yield every key/value in the CMDB
62
+ # @yieldparam [String] key
63
+ # @yieldparam [Object] value
64
+ # @return [Interface] always returns self
65
+ def each_pair(&block)
66
+ @sources.each do |s|
67
+ s.each_pair(&block)
68
+ end
69
+
70
+ self
71
+ end
72
+
73
+ # Transform the entire CMDB into a flat Hash that can be merged into ENV.
74
+ # Key names are transformed into underscore-separated, uppercase strings;
75
+ # all runs of non-alphanumeric, non-underscore characters are tranformed
76
+ # into a single underscore.
77
+ #
78
+ # The transformation rules make it possible for key names to conflict,
79
+ # e.g. "apple.orange.pear" and "apple.orange_pear" cannot exist in
80
+ # the same flat hash. This method checks for such conflicts and raises
81
+ # rather than returning bad data.
82
+ #
83
+ # @raise [NameConflict] if two or more key names transform to the same
84
+ def to_h
85
+ values = {}
86
+ sources = {}
87
+
88
+ each_pair do |key, value|
89
+ env_key = key_to_env(key)
90
+ value = JSON.dump(value) unless value.is_a?(String)
91
+
92
+ if sources.key?(env_key)
93
+ raise NameConflict.new(env_key, [sources[env_key], key])
94
+ else
95
+ sources[env_key] = key
96
+ values[env_key] = value_to_env(value)
97
+ end
98
+ end
99
+
100
+ values
101
+ end
102
+
103
+ private
104
+
105
+ # Scan for CMDB data files and index them by namespace
106
+ def load_file_sources(namespaces)
107
+ # Consult standard base directories for data files
108
+ directories = FileSource.base_directories
109
+
110
+ # Also consult working dir in development environments
111
+ if CMDB.development?
112
+ local_dir = File.join(Dir.pwd, '.cmdb')
113
+ directories += [local_dir]
114
+ end
115
+
116
+ directories.each do |dir|
117
+ (Dir.glob(File.join(dir, '*.js')) + Dir.glob(File.join(dir, '*.json'))).each do |filename|
118
+ source = FileSource.new(filename, @root)
119
+ namespaces[source.prefix] ||= []
120
+ namespaces[source.prefix] << source
121
+ end
122
+
123
+ (Dir.glob(File.join(dir, '*.yml')) + Dir.glob(File.join(dir, '*.yaml'))).each do |filename|
124
+ source = FileSource.new(filename, @root)
125
+ namespaces[source.prefix] ||= []
126
+ namespaces[source.prefix] << source
127
+ end
128
+ end
129
+ end
130
+
131
+ # Check for overlapping namespaces and react appropriately. This can happen when a file
132
+ # of a given name is located in more than one of the key-search directories. We tolerate
133
+ # this in development mode, but raise an exception otherwise.
134
+ def check_overlap(namespaces)
135
+ overlapping = namespaces.select { |_, sources| sources.size > 1 }
136
+ overlapping.each do |ns, sources|
137
+ exc = ValueConflict.new(ns, sources)
138
+
139
+ if CMDB.development?
140
+ CMDB.log.warn exc.message
141
+ else
142
+ raise exc
143
+ end
144
+ end
145
+ end
146
+
147
+ # Make an environment variable out of a key name
148
+ def key_to_env(key)
149
+ env_name = key
150
+ env_name.gsub!(/[^A-Za-z0-9_]+/,'_')
151
+ env_name.upcase!
152
+ env_name
153
+ end
154
+
155
+ # Make a CMDB value storable in the process environment (ENV hash)
156
+ def value_to_env(value)
157
+ case value
158
+ when String
159
+ value
160
+ else
161
+ JSON.dump(value)
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,141 @@
1
+ require 'yaml'
2
+ require 'json'
3
+
4
+ module CMDB
5
+ # Tool that visits every file in a hierarchy and rewrites its references to CMDB inputs.
6
+ #
7
+ # References look like <<name.of.key>> and may be replaced with a scalar value
8
+ # or an array depending on the type of the input value.
9
+ class Rewriter
10
+ attr_reader :missing_keys
11
+
12
+ # Create a new shim to rewrite config files in a chosen dir and all subdirs.
13
+ # config_dir is transformed into an absolute path (if it isn't already)
14
+ # before any rewrite operations occur.
15
+ #
16
+ # @param [String,Pathname] config_dir
17
+ def initialize(config_dir)
18
+ @dir = File.expand_path(config_dir)
19
+ @rewriters = []
20
+ end
21
+
22
+ # Substitute CMDB input values into config files whenever a replacement token is encountered.
23
+ #
24
+ # @param [CMDB::Interface] cmdb
25
+ # @return [Integer] the number of variables replaced
26
+ def rewrite(cmdb)
27
+ raise Errno::ENOENT.new(@dir) unless File.directory?(@dir)
28
+
29
+ visit(@dir)
30
+ total = 0
31
+ @rewriters.each { |rw| total += rw.rewrite(cmdb) }
32
+
33
+ @missing_keys = @rewriters.map { |rw| rw.missing_keys}.flatten.uniq.sort
34
+
35
+ total
36
+ end
37
+
38
+ def save
39
+ @rewriters.each { |rw| rw.save }
40
+ true
41
+ end
42
+
43
+ private
44
+
45
+ # Recursively scan location for files to rewrite.
46
+ def visit(location)
47
+ if File.file?(location)
48
+ scan(location)
49
+ elsif File.directory?(location)
50
+ entries = Dir.glob("#{location}/*")
51
+ subdirs = entries.select { |e| File.directory?(e) }
52
+ files = entries.select { |e| File.file?(e) }
53
+
54
+ subdirs.each do |entry|
55
+ visit(entry)
56
+ end
57
+
58
+ files.each do |entry|
59
+ visit(entry)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Load a data file and attach a rewriter to it.
65
+ def scan(file)
66
+ case File.extname(file)
67
+ when '.yml', '.yaml'
68
+ @rewriters << FileRewriter.new(file, YAML)
69
+ when '.js', '.json'
70
+ @rewriters << FileRewriter.new(file, JSON)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Tool that rewrites the contents of a single YAML, JSON or similar data file.
76
+ # The rewriting is done in-memory and isn't saved back to disk until someone
77
+ # calls #save, allowing the caller to check #missing_keys before making a
78
+ # decision whether to proceed.
79
+ class FileRewriter
80
+ # Regexp that matches a well-formed replacement token in YML or JSON
81
+ REPLACEMENT_TOKEN = /^<<(.*)>>$/
82
+
83
+ attr_reader :missing_keys
84
+
85
+ # Load YAML, JSON or similar into memory as a Ruby object graph.
86
+ def initialize(file, encoder)
87
+ @file = file
88
+ @encoder = encoder
89
+ @data = @encoder.load(File.read(file))
90
+ end
91
+
92
+ # Peform CMDB input replacement on in-memory objects. Validate that the result can be saved.
93
+ #
94
+ # @return [Integer] number of variables replaced
95
+ def rewrite(cmdb)
96
+ @total = 0
97
+ @missing_keys = []
98
+ @data = visit(cmdb, @data)
99
+
100
+ # Very important; DO NOT REMOVE. This is how we validate that #save will work.
101
+ @encoder.dump(@data)
102
+ raise Errno::EACCES.new(@file) unless File.writable?(@file)
103
+
104
+ @total
105
+ end
106
+
107
+ def save
108
+ File.open(@file, 'w') { |f| f.write @encoder.dump(@data) }
109
+ end
110
+
111
+ private
112
+
113
+ # Recurse through an object graph, finding replacement tokens and substituting the
114
+ # corresponding CMDB values.
115
+ def visit(cmdb, node)
116
+ if node.is_a?(Hash)
117
+ result = {}
118
+ node.each_pair do |k, v|
119
+ result[k] = visit(cmdb, v)
120
+ end
121
+ elsif node.is_a?(Array)
122
+ result = []
123
+ node.each do |v|
124
+ result << visit(cmdb, v)
125
+ end
126
+ elsif node.is_a?(String) && (m = REPLACEMENT_TOKEN.match(node))
127
+ value = cmdb.get(m[1])
128
+ if value.nil?
129
+ @missing_keys << m[1]
130
+ else
131
+ result = value
132
+ @total += 1
133
+ end
134
+ else
135
+ result = node
136
+ end
137
+
138
+ result
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,3 @@
1
+ module CMDB
2
+ VERSION = '2.6.0'
3
+ end
data/lib/cmdb.rb CHANGED
@@ -1,2 +1,140 @@
1
+ require 'set'
2
+ require 'singleton'
3
+
1
4
  module CMDB
5
+ # Values of RACK_ENV/RAILS_ENV that are considered to be "development," which relaxes
6
+ # certain runtime sanity checks.
7
+ DEVELOPMENT_ENVIRONMENTS = [nil, 'development', 'test'].freeze
8
+
9
+ # Regexp that matches valid key names. Key names consist of one or more dot-separated words;
10
+ # each word must begin with a lowercase alpha character and may contain alphanumerics or
11
+ # underscores.
12
+ VALID_KEY = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*$/
13
+
14
+ class Error < StandardError; end
15
+
16
+ # Client asserted the existence of a key that does not exist in the CMDB.
17
+ class MissingKey < Error
18
+ # @return [String] the name of the offending key
19
+ attr_reader :key
20
+
21
+ # @param [String] name
22
+ def initialize(key)
23
+ @key = key
24
+ super("Key '#{key}' not found in CMDB")
25
+ end
26
+ end
27
+
28
+ # Client asked for an invalid or malformed key name.
29
+ class BadKey < Error
30
+ # @return [String] the name of the offending key
31
+ attr_reader :key
32
+
33
+ # @param [String] name
34
+ def initialize(key)
35
+ @key = key
36
+ super("Malformed key '#{key}'")
37
+ end
38
+ end
39
+
40
+ # A value of an unsupported type was encountered in the CMDB.
41
+ class BadValue < Error
42
+ # @return [URI] filesystem or network location of the bad value
43
+ attr_reader :url
44
+
45
+ # @return [String] the name of the key that contained the bad value
46
+ attr_reader :key
47
+
48
+ # @param [URI] url filesystem or network location of the bad value
49
+ # @param [String] key CMDB key name under which the bad value was found
50
+ # @param [Object] value the bad value itself
51
+ def initialize(url, key, value)
52
+ @url = url
53
+ @key = key
54
+ super("Values of type #{value.class.name} are unsupported")
55
+ end
56
+ end
57
+
58
+ # Malformed data was encountered in the CMDB or in an app's filesystem.
59
+ class BadData < Error
60
+ # @return [URI] filesystem or network location of the bad data
61
+ attr_reader :url
62
+
63
+ # @param [URI] url filesystem or network location of the bad value
64
+ # @param [String] context brief description of where data was found e.g. 'CMDB data file' or 'input config file'
65
+ def initialize(url, context=nil)
66
+ @url = url
67
+ super("Malformed data encountered #{(' in ' + context) if context}")
68
+ end
69
+ end
70
+
71
+ # Two or more sources contain keys for the same namespace; this is only allowed in development
72
+ # environments.
73
+ class ValueConflict < Error
74
+ attr_reader :sources
75
+
76
+ def initialize(ns, sources)
77
+ @sources = sources
78
+ super("Keys for namespace #{ns} are defined in #{sources.size} overlapping sources")
79
+ end
80
+ end
81
+
82
+ # Deprecated name for ValueConflict
83
+ Conflict = ValueConflict
84
+
85
+ # Two or more keys in different namespaces have an identical name. This isn't an error
86
+ # when CMDB is used to refer to keys by their full, qualified name, but it can become
87
+ # an issue when loading keys into the environment for 12-factor apps to process.
88
+ class NameConflict < Error
89
+ attr_reader :env
90
+ attr_reader :keys
91
+
92
+ def initialize(env, keys)
93
+ @env = env
94
+ @keys = keys
95
+ super("#{env} corresponds to #{keys.size} different keys")
96
+ end
97
+ end
98
+
99
+ # The CMDB is being exported to ENV, but one of its keys would overwrite a value that
100
+ # is already present in ENV. This should never happen, because the CMDB is designed to
101
+ # _augment_ the environment by providing a place to store boring (static, non-secret)
102
+ # inputs, and a given input should be set either in the CMDB or the environment, never
103
+ # both.
104
+ #
105
+ class EnvironmentConflict < Error
106
+ attr_reader :key
107
+
108
+ def initialize(key)
109
+ @key = key
110
+ super("#{key} is already present in the environment; cannot override with CMDB values")
111
+ end
112
+ end
113
+
114
+ module_function
115
+
116
+ def log
117
+ unless @log
118
+ @log = Logger.new(STDOUT)
119
+ @log.level = Logger::WARN
120
+ end
121
+
122
+ @log
123
+ end
124
+
125
+ def log=(log)
126
+ @log = log
127
+ end
128
+
129
+ # Determine whether CMDB is running in a development environment.
130
+ # @return [Boolean]
131
+ def development?
132
+ DEVELOPMENT_ENVIRONMENTS.include?(ENV['RACK_ENV'] || ENV['RAILS_ENV'])
133
+ end
2
134
  end
135
+
136
+ require 'cmdb/consul_source'
137
+ require 'cmdb/file_source'
138
+ require 'cmdb/interface'
139
+ require 'cmdb/rewriter'
140
+ require 'cmdb/commands'
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: 0.1.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - RightScale
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-27 00:00:00.000000000 Z
11
+ date: 2016-02-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: listen
@@ -69,7 +69,8 @@ dependencies:
69
69
  description: Reads CMDB variables from files, Consul, and elsewhere.
70
70
  email:
71
71
  - rubygems@rightscale.com
72
- executables: []
72
+ executables:
73
+ - cmdb
73
74
  extensions: []
74
75
  extra_rdoc_files: []
75
76
  files:
@@ -85,11 +86,20 @@ files:
85
86
  - bin/console
86
87
  - bin/setup
87
88
  - cmdb.gemspec
89
+ - exe/cmdb
88
90
  - fixtures/Gemfile
89
91
  - fixtures/Gemfile.lock
90
92
  - fixtures/app/widgets.rb
91
93
  - fixtures/config.ru
92
94
  - lib/cmdb.rb
95
+ - lib/cmdb/commands.rb
96
+ - lib/cmdb/commands/help.rb
97
+ - lib/cmdb/commands/shim.rb
98
+ - lib/cmdb/consul_source.rb
99
+ - lib/cmdb/file_source.rb
100
+ - lib/cmdb/interface.rb
101
+ - lib/cmdb/rewriter.rb
102
+ - lib/cmdb/version.rb
93
103
  homepage: https://github.com/rightscale/cmdb
94
104
  licenses:
95
105
  - MIT