bjn_inventory 1.3.0

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.
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