bjn_inventory 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +227 -0
- data/Rakefile +17 -0
- data/bin/ansible-from +48 -0
- data/bin/aws-ec2-source +46 -0
- data/bin/aws-rds-source +47 -0
- data/bin/console +14 -0
- data/bin/inventory_model +34 -0
- data/bin/refresh_inventory_data +51 -0
- data/bin/setup +8 -0
- data/bjn_inventory.gemspec +33 -0
- data/lib/bjn_inventory.rb +5 -0
- data/lib/bjn_inventory/ansible.rb +86 -0
- data/lib/bjn_inventory/array.rb +22 -0
- data/lib/bjn_inventory/bykey.rb +7 -0
- data/lib/bjn_inventory/context.rb +60 -0
- data/lib/bjn_inventory/data_files.rb +41 -0
- data/lib/bjn_inventory/default_logger.rb +15 -0
- data/lib/bjn_inventory/device.rb +272 -0
- data/lib/bjn_inventory/device/map.rb +18 -0
- data/lib/bjn_inventory/hash.rb +6 -0
- data/lib/bjn_inventory/inventory.rb +105 -0
- data/lib/bjn_inventory/inventory/source.rb +66 -0
- data/lib/bjn_inventory/list.rb +11 -0
- data/lib/bjn_inventory/metadata.rb +7 -0
- data/lib/bjn_inventory/source_command.rb +41 -0
- data/lib/bjn_inventory/source_command/aws_ec2.rb +58 -0
- data/lib/bjn_inventory/source_command/aws_rds.rb +92 -0
- data/lib/bjn_inventory/version.rb +3 -0
- data/lib/inventory.rb +12 -0
- data/tasks/package/_package.sh +131 -0
- data/tasks/package/_validate.sh +36 -0
- data/tasks/package/run.sh +41 -0
- data/tasks/package/validate.sh +41 -0
- data/tasks/package/validate/01version.sh +11 -0
- data/tasks/test/Dockerfile +14 -0
- data/tasks/test/run.sh +23 -0
- data/tools/packaging_tasks.rb +123 -0
- 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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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))
|
data/bin/aws-ec2-source
ADDED
@@ -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
|
data/bin/aws-rds-source
ADDED
@@ -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
|