ncedit 0.0.1 → 0.1.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: 509d160cbfff794ee5d1880dbde293aa6fedb7dd
4
- data.tar.gz: cccf9de5724dc7e43397625985d2cdf9a9eb625e
3
+ metadata.gz: 326ad90c579b6ab122bc1aca4e92f2ba9409f556
4
+ data.tar.gz: 58fb80c7453318166436e8fac0d84a3d57b52c56
5
5
  SHA512:
6
- metadata.gz: b195209858825b5924aa7e975525760622caa1ba60a1fea8f12c6a4ac7f28318ea2e82f29849276433d7ff465cae8fc1719cdf88a03a29a6f934f6acad537a60
7
- data.tar.gz: bbc67d80b0b6d7c6ed5f00dc646dd03cfc06ce0bff54db67fabc48025fd3d09b26f6e5d9c3d6f7072f4db921ad7dec6a4ba5d243877f06fc15d6a61ec24c09cc
6
+ metadata.gz: a3053204bc285ae301a4848391e7bf52d46baf3feb4c40c8beac48998710df92888927e4b1fe4072affc4415823b28874bb9cdac81f8631765de60a44567a9ef
7
+ data.tar.gz: 0d0c2c813e97aad9b2d41a1bf3ae8c9cce8b34ac310c3b8f3ab0b1e98b2ac4a1f8148ac45f8394e21e46e6fdbac4c85d0f17beb24accb7ddd25db3792dd388cb
data/.gitignore CHANGED
@@ -3,10 +3,10 @@
3
3
  /Gemfile.lock
4
4
  /_yardoc/
5
5
  /coverage/
6
- /doc/
7
6
  /pkg/
8
7
  /spec/reports/
9
8
  /tmp/
10
9
 
11
10
  # rspec failure tracking
12
11
  .rspec_status
12
+ *.gem
data/README.md CHANGED
@@ -1,36 +1,131 @@
1
1
  [![Build Status](https://travis-ci.org/GeoffWilliams/ncedit.svg?branch=master)](https://travis-ci.org/GeoffWilliams/ncedit)
2
- # Ncedit
2
+ # ncedit - Puppet Node Classifier CLI editor
3
3
 
4
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ncedit`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+ ncedit is a small utility program that lets you edit the Puppet Enterprise Node Classifier rules from the commandline.
5
5
 
6
- TODO: Delete this and the text above, and describe your gem
6
+ Why would you want to do this given that we have the excellent [node_manager] (https://forge.puppet.com/WhatsARanjit/node_manager) module already on the forge? Well... lots of reasons. First off, using puppet code to drive the Node Classifier means that you have to have the `node_manager` module alread installed, which means that you must already have your [classification rules](https://docs.puppet.com/pe/latest/console_classes_groups_getting_started.html) in to reference the module through [Code Manager](https://docs.puppet.com/pe/latest/code_mgr.html). You could in-theory use Puppet Enterprise's new idempotent installer (just reinstall puppet over the top of itself) to fix this exact issue but then you still have the problem of how to classify your master in order to activate any other new rules (eg node-ttl) you want to use, which are associated with a new [role](https://docs.puppet.com/pe/latest/r_n_p_intro.html) for the Puppet Master.
7
+
8
+ That's where this tool comes in since all you need is root shell on the Puppet Master and a YAML or JSON file with the changes you want to make...
9
+
10
+ ## Features
11
+ * Create node groups
12
+ * Add or remove classes
13
+ * Add or remove class parameters
14
+ * Add or remove rules
15
+
16
+ All from the convenience of the CLI. This also allows this tool to be called from scripts and other systems in order to setup Puppet Enterprise the way you want and with the minimum of effort.
17
+
18
+ For the moment, the edits to be carried out need to be placed into either a JSON or YAML file for bulk processing. If there is interest, the tool will be enhanced to allow the above operations to be specified individually on the command line so to avoid the need to write YAML/JSON.
7
19
 
8
20
  ## Installation
21
+ Install this tool on your Puppet Master (Master-of-Masters)
9
22
 
10
- Add this line to your application's Gemfile:
23
+ ```shell
24
+ $ sudo /opt/puppetlabs/puppet/bin/gem install ncedit
25
+ ```
26
+ This will install the gem into the Puppet Master's vendored ruby. You could instead use your OS ruby and it should work, just make sure you have a recent-ish version of ruby.
27
+
28
+ ## Usage
11
29
 
12
- ```ruby
13
- gem 'ncedit'
30
+ ### Node Classifier API access
31
+ ncedit is intended to be run on the Puppet Master as root. Doing so avoids having to deal with networks, firewalls, whitelists and the Puppet Enterprise RBAC API. While it would be cool to expand the tool to deal with these issues, its just a whole lot simpler to just run from the Puppet Master so right now that's all thats supported.
32
+
33
+ ### Making batch changes
34
+ To avoid the need to repeatedly invoke this tool using say, a bash script, ncedit natively supports reading a file of batch changes to make that is written in either YAML or JSON.
35
+
36
+ #### Batch data file
37
+ * Since ncedit internally represents all from the Node Classifier API as hashes, its easy to support both YAML and JSON since they both resolve to this format
38
+ * It's possible to add multiple groups at a time, you just need another `NAME_OF_GROUP` stanza
39
+
40
+ In each case the file needs to be ordered as follows:
41
+
42
+ ##### YAML
43
+ ```yaml
44
+ "NAME_OF_GROUP":
45
+ # hash of classes to edit/create
46
+ "classes":
47
+ "CLASS_TO_ENSURE":
48
+ "OPTIONAL_PARAM_NAME": "VALUE_TO_SET"
49
+ # Array of classes to delete
50
+ "delete_classes":
51
+ - "CLASS_TO_DELETE"
52
+ # Hash classes + Array of parameter names to delete
53
+ "delete_params":
54
+ "CLASS_TO_PROCESS":
55
+ - "PARAMETER_TO_DELETE"
56
+ # Rules to append to group
57
+ "append_rules":
58
+ - "CONDITIONAL" # 'and'/'or'
59
+ - - "=" # rule tuple 0 - comparator, eg '='
60
+ - "VARIABLE" # rule tuple 1 - variable, eg 'fqdn'
61
+ - "VALUE" # rule tuple 2 - value to match, eg 'pupper.puppet.com'
14
62
  ```
15
63
 
16
- And then execute:
64
+ * [Worked example](doc/example/batch.yaml)
65
+
66
+ ##### JSON
67
+ ```json
68
+ {
69
+ "NAME_OF_GROUP": {
70
+ "classes": {
71
+ "CLASS_TO_ENSURE": {
72
+ "OPTIONAL_PARAM_NAME": "VALUE_TO_SET"
73
+ }
74
+ },
75
+ "delete_classes": [
76
+ "CLASS_TO_DELETE"
77
+ ],
78
+ "delete_params": {
79
+ "CLASS_TO_PROCESS": [
80
+ "PARAMETER_TO_DELETE"
81
+ ]
82
+ },
83
+ "append_rules": [
84
+ "CONDITIONAL",
85
+ [
86
+ "=",
87
+ "VARIABLE",
88
+ "VALUE"
89
+ ]
90
+ ]
91
+ }
92
+ }
93
+ ```
17
94
 
18
- $ bundle
95
+ * JSON doesn't support comments natively so please see above YAML example for notes
96
+ * [Worked example](doc/example/batch.json)
19
97
 
20
- Or install it yourself as:
98
+ #### Ensuring changes
99
+ * ncedit is idempotent so you may run the command as often as you like
21
100
 
22
- $ gem install ncedit
101
+ ##### YAML
102
+ ```shell
103
+ ncedit  batch --yaml-file /path/to/batch.yaml
104
+ ```
23
105
 
24
- ## Usage
106
+ ##### JSON
107
+ ```shell
108
+ ncedit batch --json-file /path/to/batch.json
109
+ ```
25
110
 
26
- TODO: Write usage instructions here
111
+ ## Making per-item changes
112
+ * coming? (soon?) -- anyone want this?
27
113
 
28
- ## Development
114
+ ## Troubleshooting
115
+ * If the ncedit fails, it will swallow stack traces by default, pass the `--verbosity debug` argument if you need to obtain one. Note the position of the argument before the command name:
116
+ ```
117
+ bundle exec ncedit --verbosity debug  batch --yaml-file /path/to/batch.yaml
118
+ ```
119
+ * Ensure you are running as `root` to avoid permission errors
29
120
 
30
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
121
+ ## Testing
122
+ To run tests:
31
123
 
32
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
124
+ ```shell
125
+ bundle install
126
+ bundle exec rake spec
127
+ ```
33
128
 
34
129
  ## Contributing
35
130
 
36
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ncedit.
131
+ Bug reports and pull requests are welcome on GitHub at https://github.com/GeoffWilliams/ncedit.
@@ -0,0 +1,34 @@
1
+ {
2
+ "PE Master": {
3
+ "classes": {
4
+ "puppet_enterprise::profile::master": {
5
+ "code_manager_auto_configure": true,
6
+
7
+ "r10k_remote": "https://github.com/GeoffWilliams/r10k-control",
8
+ "r10k_private_key": "/etc/puppetlabs/puppetserver/ssh/id-control_repo.rsa"
9
+ }
10
+ },
11
+ "delete_classes": [
12
+ "puppet_enterprise::profile::masterbad"
13
+ ],
14
+ "delete_params": {
15
+ "puppet_enterprise::profile::redo:": [
16
+ "badparam"
17
+ ]
18
+ }
19
+ },
20
+ "Puppet Masters": {
21
+ "classes": {
22
+ "r_role::puppet::master_minimal": {
23
+ }
24
+ },
25
+ "append_rules": [
26
+ "or",
27
+ [
28
+ "=",
29
+ "name",
30
+ "pupper.puppet.com"
31
+ ]
32
+ ]
33
+ }
34
+ }
@@ -9,18 +9,19 @@
9
9
  "code_manager_auto_configure": true
10
10
  "r10k_remote": "https://github.com/GeoffWilliams/r10k-control"
11
11
  "r10k_private_key": "/etc/puppetlabs/puppetserver/ssh/id-control_repo.rsa"
12
- #
13
- # "delete_classes":
14
- # - "puppet_enterprise::profile::masterbad"
15
- #
16
- # "delete_params":
17
- # "puppet_enterprise::profile::redo:":
18
- # - "badparam"
12
+
13
+ "delete_classes":
14
+ - "puppet_enterprise::profile::masterbad"
15
+
16
+ "delete_params":
17
+ "puppet_enterprise::profile::redo:":
18
+ - "badparam"
19
19
 
20
20
  "Puppet Masters":
21
21
  "classes":
22
22
  "r_role::puppet::master_minimal": {}
23
- # "append_rules":
24
- # - - "="
25
- # - "name"
26
- # - "vmpump02.puppet.com"
23
+ "append_rules":
24
+ - "or"
25
+ - - "="
26
+ - "name"
27
+ - "pupper.puppet.com"
data/exe/ncedit CHANGED
@@ -56,22 +56,25 @@ Escort::App.create do |app|
56
56
  # end
57
57
 
58
58
  app.command :batch do |command|
59
- command.summary "Batch processing from YAML"
60
- command.description "Process a YAML file to add/delete classes, parameters and rules"
59
+ command.summary "Batch processing from YAML/JSON"
60
+ command.description "Process a YAML/JSON file to add/delete classes, parameters and rules"
61
61
  command.options do |opts|
62
- opts.opt(:filename,
62
+ opts.opt(:yaml_file,
63
63
  'YAML file',
64
- :long => '--filename',
64
+ :long => '--yaml-file',
65
+ :type => :string,
66
+ )
67
+ opts.opt(:json_file,
68
+ 'JSON file',
69
+ :long => '--json-file',
65
70
  :type => :string,
66
71
  )
67
72
  end
68
73
  command.action do |options, arguments|
69
- filename = options[:global][:commands][:batch][:options][:filename]
70
- if filename == nil
71
- Escort::Logger.error.error "Filename is required for batch updates"
72
- else
73
- NCEdit::Cmd::batch(filename)
74
- end
74
+ yaml_file = options[:global][:commands][:batch][:options][:yaml_file]
75
+ json_file = options[:global][:commands][:batch][:options][:json_file]
76
+
77
+ NCEdit::Cmd::batch(yaml_file: yaml_file, json_file: json_file)
75
78
  end
76
79
  end
77
80
  end
@@ -1,9 +1,11 @@
1
1
  require 'puppetclassify'
2
2
  require 'yaml'
3
+ require 'json'
3
4
  require 'escort'
4
5
 
5
6
  module NCEdit
6
7
  module Cmd
8
+ DEFAULT_RULE = "or"
7
9
 
8
10
  def self.init(puppetclassify = nil)
9
11
  if puppetclassify
@@ -133,6 +135,35 @@ module NCEdit
133
135
  end
134
136
  end
135
137
 
138
+ def self.read_batch_data(yaml_file: nil, json_file:nil)
139
+ if yaml_file == nil and json_file == nil
140
+ raise "YAML or JSON file must be specified for batch updates"
141
+ elsif yaml_file and json_file
142
+ raise "Cannot process both YAML and JSON at the same time"
143
+ elsif yaml_file
144
+ if File.exists?(yaml_file)
145
+ begin
146
+ data = YAML.load_file(yaml_file)
147
+ rescue Psych::SyntaxError
148
+ raise "syntax error parsing #{yaml_file}"
149
+ end
150
+ else
151
+ raise "YAML file not found: #{yaml_file}"
152
+ end
153
+ elsif json_file
154
+ if File.exists?(json_file)
155
+ begin
156
+ data = JSON.parse(IO.read(json_file))
157
+ rescue JSON::ParserError
158
+ raise "syntax error parsing #{json_file}"
159
+ end
160
+ else
161
+ raise "JSON file not found: #{json_file}"
162
+ end
163
+ end
164
+ data
165
+ end
166
+
136
167
  # Batch entry from YAML file, example file format:
137
168
  # 'PE Master':
138
169
  # 'classes':
@@ -154,44 +185,36 @@ module NCEdit
154
185
  # - - "="
155
186
  # - "name"
156
187
  # - "vmpump02.puppet.com"
157
- def self.batch(filename)
158
- if File.exists?(filename)
159
- begin
160
- yaml = YAML.load_file(filename)
161
-
162
- yaml.each { |group_name, data|
163
- Escort::Logger.output.puts "Processing #{group_name}"
164
-
165
- if data.has_key?("delete_classes")
166
- if delete_classes(group_name, data["delete_classes"])
167
- update_group(group_name, classes: data["delete_classes"])
168
- end
169
- end
188
+ def self.batch(yaml_file: nil, json_file: nil)
189
+ data = read_batch_data(yaml_file: yaml_file, json_file: json_file)
190
+ data.each { |group_name, data|
170
191
 
171
- if data.has_key?("delete_params")
172
- if delete_params(group_name, data["delete_params"])
173
- update_group(group_name, classes: data["delete_params"])
174
- end
175
- end
192
+ Escort::Logger.output.puts "Processing #{group_name}"
176
193
 
177
- if data.has_key?("classes")
178
- if ensure_classes_and_params(group_name, data["classes"])
179
- update_group(group_name, classes: data["classes"])
180
- end
181
- end
194
+ if data.has_key?("delete_classes")
195
+ if delete_classes(group_name, data["delete_classes"])
196
+ update_group(group_name, classes: data["delete_classes"])
197
+ end
198
+ end
182
199
 
183
- if data.has_key?("append_rules")
184
- if ensure_rules(group_name, data["append_rules"])
185
- update_group(group_name, rule: data["append_rules"])
186
- end
187
- end
188
- }
189
- rescue Psych::SyntaxError
190
- Escort::Logger.error.error "Syntax error found in #{filename}, please fix and retry"
200
+ if data.has_key?("delete_params")
201
+ if delete_params(group_name, data["delete_params"])
202
+ update_group(group_name, classes: data["delete_params"])
203
+ end
191
204
  end
192
- else
193
- Escort::Logger.error.error "File not found: #{filename}"
194
- end
205
+
206
+ if data.has_key?("classes")
207
+ if ensure_classes_and_params(group_name, data["classes"])
208
+ update_group(group_name, classes: data["classes"])
209
+ end
210
+ end
211
+
212
+ if data.has_key?("append_rules")
213
+ if ensure_rules(group_name, data["append_rules"])
214
+ update_group(group_name, rule: data["append_rules"])
215
+ end
216
+ end
217
+ }
195
218
  end
196
219
 
197
220
  def self.delete_class(group, class_name)
@@ -279,16 +302,26 @@ module NCEdit
279
302
  updated
280
303
  end
281
304
 
305
+ # Ensure a partualar rule exists in the group["rule"] array
306
+ # This affects only the items in the chain, eg:
307
+ # [
308
+ # "or",
309
+ # [
310
+ # <--- here!
311
+ # ]
312
+ # ]
313
+ #
314
+ # Only the rule to be added in should be passed as the rule parameter, eg:
315
+ # ["=", "name", "bob"]
282
316
  def self.ensure_rule(group, rule)
283
317
  updated = false
284
- if ! group["rules"]
285
- # no rules yet - just add our new one
286
- group["rules"] = []
287
- end
288
318
 
289
319
  # see if rule already exists, if it doesn't, append it
290
320
  found = false
291
- group["rules"].each {|system_rule|
321
+
322
+ # rules are nested like this, the "or" applies to the whole rule chain:
323
+ # "rule"=>["or", ["=", "name", "bob"], ["=", "name", "hello"]]
324
+ group["rule"][1].each {|system_rule|
292
325
  if system_rule[0] == rule[0] and
293
326
  system_rule[1] == rule[1] and
294
327
  system_rule[2] == rule[2]
@@ -298,20 +331,45 @@ module NCEdit
298
331
  }
299
332
  if ! found
300
333
  Escort::Logger.output.puts "Appending rule: #{rule}"
301
- group["rules"] << rule
334
+ group["rule"][1] << rule
302
335
  updated = true
303
336
  end
304
337
 
305
338
  updated
306
339
  end
307
340
 
341
+ # rules need to arrive like this:
342
+ # ["or", ["=", "name", "pupper.megacorp.com"], ["=", "name", "pupper.megacorp.com"]]
343
+ # since the rule conjunction "or" can only be specified once per rule chain
344
+ # we will replace whatever already exists in the rule with what the user
345
+ # specified
308
346
  def self.ensure_rules(group_name, rules)
309
347
  updated = false
310
- rules.each { |rule|
311
- updated |= ensure_rule(nc_group(group_name), rule)
348
+ group = nc_group(group_name)
349
+ if ! group["rule"] or group["rule"].empty?
350
+ # no rules yet - just add our new one
351
+ group["rule"] = [DEFAULT_RULE,[]]
352
+ end
353
+ updated |= ensure_rule_conjunction(group, rules[0])
354
+ rules[1].each { |rule|
355
+ updated |= ensure_rule(group, rule)
312
356
  }
313
357
 
314
358
  updated
315
359
  end
360
+
361
+ def self.ensure_rule_conjunction(group, op)
362
+ updated = false
363
+ if ["and", "or"].include?(op)
364
+ if group["rule"][0] != op
365
+ group["rule"][0] = op
366
+ updated = true
367
+ end
368
+ else
369
+ raise "Illegal rule conjunction #{op}, allowed: 'and', 'or'"
370
+ end
371
+
372
+ updated
373
+ end
316
374
  end
317
375
  end
@@ -1,3 +1,3 @@
1
1
  module NCEdit
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ncedit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geoff Williams
@@ -109,9 +109,10 @@ files:
109
109
  - LICENSE
110
110
  - README.md
111
111
  - Rakefile
112
- - batch.yaml
113
112
  - bin/console
114
113
  - bin/setup
114
+ - doc/example/batch.json
115
+ - doc/example/batch.yaml
115
116
  - exe/ncedit
116
117
  - lib/ncedit.rb
117
118
  - lib/ncedit/cmd.rb