bjn_inventory 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/README.md +227 -0
  7. data/Rakefile +17 -0
  8. data/bin/ansible-from +48 -0
  9. data/bin/aws-ec2-source +46 -0
  10. data/bin/aws-rds-source +47 -0
  11. data/bin/console +14 -0
  12. data/bin/inventory_model +34 -0
  13. data/bin/refresh_inventory_data +51 -0
  14. data/bin/setup +8 -0
  15. data/bjn_inventory.gemspec +33 -0
  16. data/lib/bjn_inventory.rb +5 -0
  17. data/lib/bjn_inventory/ansible.rb +86 -0
  18. data/lib/bjn_inventory/array.rb +22 -0
  19. data/lib/bjn_inventory/bykey.rb +7 -0
  20. data/lib/bjn_inventory/context.rb +60 -0
  21. data/lib/bjn_inventory/data_files.rb +41 -0
  22. data/lib/bjn_inventory/default_logger.rb +15 -0
  23. data/lib/bjn_inventory/device.rb +272 -0
  24. data/lib/bjn_inventory/device/map.rb +18 -0
  25. data/lib/bjn_inventory/hash.rb +6 -0
  26. data/lib/bjn_inventory/inventory.rb +105 -0
  27. data/lib/bjn_inventory/inventory/source.rb +66 -0
  28. data/lib/bjn_inventory/list.rb +11 -0
  29. data/lib/bjn_inventory/metadata.rb +7 -0
  30. data/lib/bjn_inventory/source_command.rb +41 -0
  31. data/lib/bjn_inventory/source_command/aws_ec2.rb +58 -0
  32. data/lib/bjn_inventory/source_command/aws_rds.rb +92 -0
  33. data/lib/bjn_inventory/version.rb +3 -0
  34. data/lib/inventory.rb +12 -0
  35. data/tasks/package/_package.sh +131 -0
  36. data/tasks/package/_validate.sh +36 -0
  37. data/tasks/package/run.sh +41 -0
  38. data/tasks/package/validate.sh +41 -0
  39. data/tasks/package/validate/01version.sh +11 -0
  40. data/tasks/test/Dockerfile +14 -0
  41. data/tasks/test/run.sh +23 -0
  42. data/tools/packaging_tasks.rb +123 -0
  43. metadata +188 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 92ee73b61dcc5d4e4d701b5e5007a99ad20b36a8
4
+ data.tar.gz: d2be8a63d506b5e4497e3db88a564a9a71c55c57
5
+ SHA512:
6
+ metadata.gz: e42bd1e6b24bf66b89cc20a5c50c26877110c4d6f1edfaafe224a945f4917d4f78a119ac184d07538a06079f821b57ceabe4b2a896014ffaee94a1299bde145e
7
+ data.tar.gz: e676b4aeabdf0078d3b0d5098e87c8c7260bda80f0c3bfc568802711d4ba541727b8d510e13a47869e614dc75bbfc05edab286aeeb93ea817585c0e52e2bd1f2
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ *.swp
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ /d42/
12
+ /aws/
13
+ /inv/
14
+ tasks/package/artifacts
15
+ /tasks/test/reports/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bjn_inventory.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # BjnInventory
2
+
3
+ This gem is designed to help materialize a standardized inventory of devices according to:
4
+
5
+ * A model you specify
6
+ * Multiple inventory sources you configure
7
+ * A mapping between each source type and your standard model
8
+
9
+ The materialized inventory can easily be converted to different formats:
10
+ * Raw JSON or YAML
11
+ * Ansible dynamic inventory
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'bjn_inventory'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install bjn_inventory
28
+
29
+ ## Usage
30
+
31
+ An inventory is a list of devices, created from a specification, that specifies:
32
+ * A device model
33
+ * One or more sources, each of which has:
34
+ - A source-specific filename, URL or other location information
35
+ - A source-specific origin, which maps source data to the standard model
36
+ * Context data
37
+ - These are just JSON files that you can refer to in your map and model
38
+
39
+ Example usage (for an Ansible dynamic inventory script--but see `ansible-from`):
40
+ ```ruby
41
+ require 'bjn_inventory'
42
+
43
+ manifest = JSON.parse(File.read('inventory.json'))
44
+ inventory = BjnInventory.new(manifest)
45
+ ansible = {
46
+ group_by: [
47
+ environment,
48
+ region,
49
+ roles
50
+ ],
51
+ groups: {
52
+ webservers: ['www', 'stage-www']
53
+ }
54
+ }
55
+ # Adds a method to Array to convert to Ansible dynamic inventory output
56
+ puts inventory.to_ansible(ansible)
57
+ ```
58
+
59
+ inventory.json
60
+ ```ruby
61
+ { "model": "/etc/inventory/device.json",
62
+ "context": "/etc/inventory/data/context",
63
+ "sources": [
64
+ { "file": "/etc/inventory/data/device42.json",
65
+ "rules": "/etc/inventory/maps/device42.rb" },
66
+ { "file": "/etc/inventory/data/aws.json",
67
+ "rules": "/etc/inventory/maps/aws.rb" }
68
+ ]
69
+ }
70
+ ```
71
+
72
+ ## Model
73
+
74
+ When **bjn_inventory** produces an inventory, each device conforms
75
+ to the device model. They have only the fields specified in the
76
+ model, and defaults are filled in according to the model.
77
+
78
+ Your inventory sources may not conform to the model. That's where
79
+ there are [Rules#rules] to map the source entries into proper devices,
80
+ according to the model you provide.
81
+
82
+ The model generally takes the form of a JSON file, but can be embedded
83
+ directly in the inventory specification.
84
+
85
+ ### Merge Rules
86
+
87
+ When two devices need to be merged (for example, you are invoking
88
+ **BjnInventory::Inventory#by(key)** and they have the same value
89
+ for the *key* field), a new Device object is created with field
90
+ values taken from the second, merged with the first (similar to
91
+ a Hash merge). This merge is done according to the device model
92
+ and the following rules:
93
+
94
+ * Non-**nil** values take precedence over **nil**
95
+ * Hashes are merged shallowly according to a standard
96
+ Ruby hash merge
97
+ * Arrays are concatenated, except duplicate values from
98
+ the second device are not added
99
+ * The second device's other values take precedence
100
+
101
+ The resulting merged device's **#origin** method returns an
102
+ array of the different origins used to merge it together.
103
+
104
+ ## Sources
105
+
106
+ Each source you specify is read, in order. When you invoke
107
+ **BjnInventory::Inventory#by(key)**, all sources are used. If two
108
+ entries have the same *key*, they are merged together using the
109
+ [merge rules#merge-rules] (basically, it merges intelligently based on
110
+ the default values in the model). Order is strictly preserved here, so
111
+ sources listed later in the list have data precedence over those
112
+ listed earlier (like Ruby merge logic).
113
+
114
+ Right now, only two kinds of sources are supported: inline, with the
115
+ `entries` key, where the inventory entries are specified directly in
116
+ the source; or with the `file` key, where the file must contain a JSON
117
+ array of objects.
118
+
119
+ In other words, it's assumed that you're separately downloading your
120
+ source of inventory into JSON files for **bjn_inventory** to operate on.
121
+ In the future, a download command or plugin may be allowed here.
122
+
123
+ This package also provides a downloader command for AWS EC2. Use
124
+ `aws-ec2-source` to download and minimally process an EC2 instance
125
+ list to provide an inventory source.
126
+
127
+ ### Rules
128
+
129
+ The mapping rules allow you to specify field values in the model and
130
+ what calculation to perform from a source to derive them in the
131
+ following DSL (domain-specific language). The rules consist of either
132
+ text or a filename.
133
+
134
+ The `origin` command takes a string, which specifies (for your
135
+ convenience) the origin type of the resulting devices. For example,
136
+ your AWS mapping rules might use `origin 'aws'` to identify resulting
137
+ devices as having come from the AWS source. You can reuse rules
138
+ files for different sources, or not, so it's up to you whether this
139
+ origin represents a particular source of data or a particular kind
140
+ of device.
141
+
142
+ The `map` command takes a hash with a field name as a key and a mapping
143
+ rule type (such as `ruby` or `jsonpath`). For example, the following
144
+ rule specifies that the `name` field in your device model comes from
145
+ either the `fqdn` field from the source entry, or the `name` field, if
146
+ `fqdn` is **nil**:
147
+
148
+ ```ruby
149
+ map name: ruby { |data| data['fqdn'] || data['name'] }
150
+ ```
151
+
152
+ As an alternative to this syntax, you can mention the field name
153
+ directly and omit the `ruby` keyword:
154
+
155
+ ```ruby
156
+ name { |data| data['fqdn'] || data['name'] }
157
+ ```
158
+
159
+ These are examples of **ruby** rules, which take the form of a Ruby
160
+ block. This block accepts up to two arguments: the first is the data
161
+ from the source entry (the "raw" data) in the form of a Hash, with
162
+ the default values from the device model added; the second is the
163
+ current **BjnInventory::Device** object. For example, if your model
164
+ contains the `name` and `domain` fields, the following rule specifies
165
+ a `fqdn` field that uses them:
166
+
167
+ ```ruby
168
+ fqdn { |_data, device| device.name + '.' + device.domain }
169
+ ```
170
+
171
+ There are other rule types available:
172
+
173
+ A `jsonpath` rule uses a JSONPath expression in a string to set
174
+ the device field. For example, the following rule sets the
175
+ `name` field using the value of the `Name` tag (assuming AWS
176
+ tagging, e.g. when using `aws-ec2-source`):
177
+
178
+ ```ruby
179
+ name jsonpath '$.tags[?(@["key"]=="Name")].value'
180
+ ```
181
+
182
+ A `synonym` rule simply makes the field a synonym for another
183
+ device model field. For example, the following rule makes the
184
+ field `management_ip` exactly synonymous with the `ip_address`
185
+ field:
186
+
187
+ ```ruby
188
+ management_ip synonym :ip_address
189
+ ```
190
+
191
+ An `always` rule simply uses a constant value for that field.
192
+ For example, the following rule sets the value of `system_type`
193
+ to `ec2_instance` unconditionally:
194
+
195
+ ```ruby
196
+ system_type always 'ec2_instance'
197
+ ```
198
+
199
+ ## Design Decisions
200
+
201
+ * No calculated fields in the model.
202
+ * No filtering: you can either filter the sources, or you can filter
203
+ the inventory after producing it
204
+ * No validation of model fields (exactly, though merge is sensitive to
205
+ the model)
206
+ * For now, no fancy file finding (filenames in manifests must be
207
+ absolute, or correct with respect to wherever you're running the
208
+ software). Possibly this might change with the addition of the
209
+ ability to pass a filename to **BjnInventory::Inventory.new()**.
210
+
211
+ ## Development
212
+
213
+ After checking out the repo, run `bin/setup` to install
214
+ dependencies. Then, run `rake spec` to run the tests. You can also run
215
+ `bin/console` for an interactive prompt that will allow you to
216
+ experiment.
217
+
218
+ To install this gem onto your local machine, run `bundle exec rake
219
+ install`. To release a new version, update the version number in
220
+ `version.rb`, and then run `bundle exec rake release`, which will
221
+ create a git tag for the version, push git commits and tags, and push
222
+ the `.gem` file to [rubygems.org](https://rubygems.org).
223
+
224
+ ## Contributing
225
+
226
+ Bug reports can be sent to Ops Tools <tools+bjn_inventory@bluejeans.com>.
227
+
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ task :report do
9
+ rm_r 'tasks/test/reports' if Dir.exist? 'tasks/test/reports'
10
+ mkdir_p 'tasks/test/reports'
11
+ # This command always succeeds; failed tests will be caught by the '--format p' output
12
+ sh 'rspec spec --format p --force-color --format RspecJunitFormatter --out tasks/test/reports/results.xml || :'
13
+ end
14
+
15
+ task :test do
16
+ sh 'rspec spec --format p --force-color'
17
+ end
data/bin/ansible-from ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Drive an Ansible inventory from a file manifest
4
+ # In order to use dynamically, still need to wrap
5
+ # with a shell script (Ansible won't pass arguments)
6
+
7
+ require 'trollop'
8
+ require 'json'
9
+ require 'logger'
10
+ require 'bjn_inventory'
11
+ require 'bjn_inventory/ansible'
12
+
13
+ parser = Trollop::Parser.new do
14
+ version BjnInventory::VERSION
15
+ banner <<-USAGE.gsub(/^\s{8}/,'')
16
+ Usage:
17
+ ansible-from [options]
18
+ USAGE
19
+
20
+ opt :ansible, 'Specify ansible groupings file', required: true, type: :string
21
+ opt :manifest, 'Specify the manifest that defines this inventory', required: true, type: :string
22
+ opt :debug, 'Enable debug output', :short => '-D'
23
+ opt :syslog, 'Log to Syslog', :short => '-S'
24
+ stop_on_unknown
25
+ end
26
+
27
+ opt = Trollop::with_standard_exception_handling parser do
28
+ parser.parse(ARGV)
29
+ end
30
+
31
+ if opt[:syslog]
32
+ require 'syslog/logger'
33
+ logger = Syslog::Logger.new 'ansible-from'
34
+ else
35
+ logger = Logger.new STDERR
36
+ end
37
+
38
+ if opt[:debug]
39
+ logger.level = Logger::DEBUG
40
+ else
41
+ logger.level = Logger::WARN
42
+ end
43
+
44
+ manifest = JSON.parse(File.read(opt[:manifest]))
45
+ ansible_spec = JSON.parse(File.read(opt[:ansible]))
46
+
47
+ inventory = BjnInventory::Inventory.new(manifest.merge({logger: logger}))
48
+ puts JSON.pretty_generate(inventory.by('name').to_ansible(ansible_spec))
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'trollop'
4
+ require 'logger'
5
+
6
+ require 'bjn_inventory/version'
7
+ require 'bjn_inventory/source_command/aws_ec2'
8
+
9
+ parser = Trollop::Parser.new do
10
+ version BjnInventory::VERSION
11
+ banner <<-USAGE.gsub(/^\s{8}/,'')
12
+ Usage:
13
+ aws-ec2-source [options]
14
+ USAGE
15
+
16
+ opt :region, "Specify region (default is all regions)", type: :string, multi: true
17
+ opt :profile, "Specify AWS client profile", type: :string
18
+ opt :filters, 'Specify AWS EC2 filters in JSON (e.g. `[{"name":"tag:ENVIRONMENT","values":["Production"]}]`)', type: :string
19
+ opt :output, "Send output to file instead of stdout", type: :string
20
+ opt :verbose, "Produce verbose output", short: '-v'
21
+ opt :debug, 'Enable debug output', short: '-D'
22
+ opt :syslog, 'Log to Syslog', short: '-S'
23
+ stop_on_unknown
24
+ end
25
+
26
+ opt = Trollop::with_standard_exception_handling parser do
27
+ parser.parse(ARGV)
28
+ end
29
+
30
+ if opt[:syslog]
31
+ require 'syslog/logger'
32
+ logger = Syslog::Logger.new 'aws-ec2-source'
33
+ else
34
+ logger = Logger.new STDERR
35
+ end
36
+
37
+ if opt[:debug]
38
+ logger.level = Logger::DEBUG
39
+ elsif opt[:verbose]
40
+ logger.level = Logger::INFO
41
+ else
42
+ logger.level = Logger::WARN
43
+ end
44
+
45
+ command = BjnInventory::SourceCommand::AwsEc2.new(opt.merge({ logger: logger }))
46
+ command.run
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'trollop'
4
+ require 'logger'
5
+
6
+ require 'bjn_inventory/version'
7
+ require 'bjn_inventory/source_command/aws_rds'
8
+
9
+ parser = Trollop::Parser.new do
10
+ version BjnInventory::VERSION
11
+ banner <<-USAGE.gsub(/^\s{8}/,'')
12
+ Usage:
13
+ aws-rds-source [options]
14
+ USAGE
15
+
16
+ opt :region, "Specify region (default is all regions)", type: :string, multi: true
17
+ opt :profile, "Specify AWS client profile", type: :string
18
+ opt :filters, 'Specify AWS RDS filters in JSON (e.g. `[{"name":"db-cluster-id","values":["arn..."]}]`)', type: :string
19
+ opt :tag_filters, 'Specify tags and their required values (e.g. `[{"key":"ENVIRONMENT","values":["Production"]}]`)', type: :string
20
+ opt :output, "Send output to file instead of stdout", type: :string
21
+ opt :verbose, "Produce verbose output", short: '-v'
22
+ opt :debug, 'Enable debug output', short: '-D'
23
+ opt :syslog, 'Log to Syslog', short: '-S'
24
+ stop_on_unknown
25
+ end
26
+
27
+ opt = Trollop::with_standard_exception_handling parser do
28
+ parser.parse(ARGV)
29
+ end
30
+
31
+ if opt[:syslog]
32
+ require 'syslog/logger'
33
+ logger = Syslog::Logger.new 'aws-rds-source'
34
+ else
35
+ logger = Logger.new STDERR
36
+ end
37
+
38
+ if opt[:debug]
39
+ logger.level = Logger::DEBUG
40
+ elsif opt[:verbose]
41
+ logger.level = Logger::INFO
42
+ else
43
+ logger.level = Logger::WARN
44
+ end
45
+
46
+ command = BjnInventory::SourceCommand::AwsRds.new(opt.merge({ logger: logger }))
47
+ command.run
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "bjn_inventory"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start