ncedit 0.1.0 → 0.2.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: 326ad90c579b6ab122bc1aca4e92f2ba9409f556
4
- data.tar.gz: 58fb80c7453318166436e8fac0d84a3d57b52c56
3
+ metadata.gz: af182264e160a6970386cf7c82f89d633562e83b
4
+ data.tar.gz: de7185420068f8de02cc2eb5eb1aafe57f52d939
5
5
  SHA512:
6
- metadata.gz: a3053204bc285ae301a4848391e7bf52d46baf3feb4c40c8beac48998710df92888927e4b1fe4072affc4415823b28874bb9cdac81f8631765de60a44567a9ef
7
- data.tar.gz: 0d0c2c813e97aad9b2d41a1bf3ae8c9cce8b34ac310c3b8f3ab0b1e98b2ac4a1f8148ac45f8394e21e46e6fdbac4c85d0f17beb24accb7ddd25db3792dd388cb
6
+ metadata.gz: a8f5623802fd951fc3da86d6613ded5c934af43fdd6fdf01697abe2a5f237d65092ebf4e0520edc7aeb5e6fe12439a16357332c564cea2547205d5db2058b8fb
7
+ data.tar.gz: 1a7e2ca2245765be503c10572da54a7b59e0d5e1b17857637cbda2df93aee58bc33b40d19cfe88560b5fdac681cba1d84f5a7b7cb66f057c8a29bf26bd719471
data/README.md CHANGED
@@ -1,19 +1,20 @@
1
1
  [![Build Status](https://travis-ci.org/GeoffWilliams/ncedit.svg?branch=master)](https://travis-ci.org/GeoffWilliams/ncedit)
2
2
  # ncedit - Puppet Node Classifier CLI editor
3
3
 
4
- ncedit is a small utility program that lets you edit the Puppet Enterprise Node Classifier rules from the commandline.
4
+ ncedit is a small utility program that lets you edit the Puppet Enterprise Node Classifier rules from the command line.
5
5
 
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.
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
7
 
8
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
9
 
10
10
  ## Features
11
+ You can:
11
12
  * Create node groups
12
13
  * Add or remove classes
13
14
  * Add or remove class parameters
14
15
  * Add or remove rules
15
16
 
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
+ ...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
 
18
19
  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.
19
20
 
@@ -28,7 +29,7 @@ This will install the gem into the Puppet Master's vendored ruby. You could ins
28
29
  ## Usage
29
30
 
30
31
  ### 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
+ ncedit is intended to be run on the Puppet Master as root. Doing so avoids having to deal with networks, firewalls, certificate 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
 
33
34
  ### Making batch changes
34
35
  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.
@@ -109,14 +110,65 @@ ncedit batch --json-file /path/to/batch.json
109
110
  ```
110
111
 
111
112
  ## Making per-item changes
112
- * coming? (soon?) -- anyone want this?
113
+ If you don't want to go to the hassle of creating a batch file or you have small, dynamic edits you wish to perform, your able to perform individual edits on the command line:
114
+
115
+ ### Add a class to group
116
+ ```shell
117
+ ncedit --group-name FOO --class-name BAR
118
+ ```
119
+ * Group will be created if it doesn't exist
120
+ * Class MUST exist AND puppet must be aware of it (defeat caching) for the request to be accepted
121
+
122
+ ### Add a parameter to a class for a group
123
+ ```shell
124
+ ncedit --group-name FOO --class-name BAR --param-name BAZ --param-value BAS
125
+ ```
126
+ * Group will be created if it doesn't exist
127
+ * Class will be added if it isn't already
128
+ * Class MUST exist AND puppet must be aware of it (defeat caching) AND the parameter must exist on the class for the request to be accepted
129
+
130
+ ### Delete a parameter from a class inside a group
131
+ ```shell
132
+ ncedit --group-name FOO --class-name BAR --param-name BAZ --delete-param
133
+ ```
134
+ * Group will be created if it doesn't exist
135
+ * Class will be added if it isn't already
136
+ * Ensures the parameter `BAZ` is not set
137
+
138
+ ### Delete a class inside a group
139
+ ```shell
140
+ ncedit --group-name FOO --class-name BAR --delete-class
141
+ ```
142
+ * Group will be created if it doesn't exist
143
+ * Ensures the class `BAR` is not defined
144
+
145
+ ### Replace all rules for group
146
+ ```shell
147
+ ncedit --group-name FOO --rule 'JSON_FRAGMENT' --rule-mode replace
148
+ ```
149
+ * Group will be created if it doesn't exist
150
+ * Completely replaces existing rules for this group
151
+ * Example JSON_FRAGMENT: `["and",["=",["fact","ipaddress"],"192.168.1.1"]]`
152
+
153
+ ### Append a rule to group
154
+ ```shell
155
+ ncedit --group-name FOO --rule 'JSON_FRAGMENT' --rule-mode append
156
+ ```
157
+ * Group will be created if it doesn't exist
158
+ * Appends supplied JSON_FRAGMENT to the group's rules
159
+ * Example JSON_FRAGMENT: `["and",["=",["fact","osfamily"],"RedHat"]]`
160
+ * Notice in our example JSON_FRAGMENT that we have *kept* the outer `and` rule. If we supply a conjuctive different to the rule's current value we will change it for the whole rule
113
161
 
114
162
  ## Troubleshooting
163
+ * If you cannot install the `ncedit` gem and you are behind a corporate proxy, ensure that you have correctly set your `http_proxy` and `https_proxy` variables on the shell before running `gem install`.
164
+ * Some corporate proxies will attempt to eavesdrop on all SSL connections which will cause downloads to fail. This can be resolved by installing a CA bundle (be sure you understand the implications of doing so) or domain whitelisting on the proxy itself
115
165
  * 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
166
  ```
117
167
  bundle exec ncedit --verbosity debug  batch --yaml-file /path/to/batch.yaml
118
168
  ```
119
169
  * Ensure you are running as `root` to avoid permission errors
170
+ * If your shell can't find `ncedit` after successful installation and you installed into Puppet Enterprise's vendored ruby, make sure that your `PATH` contains `/opt/puppetlabs/puppet/bin` or run ncedit directly: `/opt/puppetlabs/puppet/bin/ncedit`
171
+ * Don't attempt to debug the NC API through the PE console by capturing traffic, it uses a completely different API all of its own (In particular, there is another layer of boolean logic around rules). Instead, use [NCIO](https://github.com/jeffmccune/ncio) to dump the current state of the NC API or see [Puppet's NC API documentation](https://docs.puppet.com/pe/latest/nc_index.html)
120
172
 
121
173
  ## Testing
122
174
  To run tests:
@@ -3,7 +3,6 @@
3
3
  "classes": {
4
4
  "puppet_enterprise::profile::master": {
5
5
  "code_manager_auto_configure": true,
6
-
7
6
  "r10k_remote": "https://github.com/GeoffWilliams/r10k-control",
8
7
  "r10k_private_key": "/etc/puppetlabs/puppetserver/ssh/id-control_repo.rsa"
9
8
  }
@@ -12,22 +11,26 @@
12
11
  "puppet_enterprise::profile::masterbad"
13
12
  ],
14
13
  "delete_params": {
15
- "puppet_enterprise::profile::redo:": [
16
- "badparam"
14
+ "puppet_enterprise::profile::mcollective::agent": [
15
+ "stomp_user"
17
16
  ]
18
17
  }
19
18
  },
20
19
  "Puppet Masters": {
21
20
  "classes": {
22
- "r_role::puppet::master_minimal": {
21
+ "pe_r10k": {
22
+ "proxy": "proxy.megacorp.com:8080"
23
23
  }
24
24
  },
25
25
  "append_rules": [
26
26
  "or",
27
27
  [
28
28
  "=",
29
- "name",
30
- "pupper.puppet.com"
29
+ [
30
+ "fact",
31
+ "ipaddress"
32
+ ],
33
+ "192.168.0.252"
31
34
  ]
32
35
  ]
33
36
  }
@@ -14,14 +14,16 @@
14
14
  - "puppet_enterprise::profile::masterbad"
15
15
 
16
16
  "delete_params":
17
- "puppet_enterprise::profile::redo:":
18
- - "badparam"
17
+ "puppet_enterprise::profile::mcollective::agent":
18
+ - "stomp_user"
19
19
 
20
20
  "Puppet Masters":
21
21
  "classes":
22
- "r_role::puppet::master_minimal": {}
22
+ "pe_r10k":
23
+ "proxy": "proxy.megacorp.com:8080"
23
24
  "append_rules":
24
25
  - "or"
25
26
  - - "="
26
- - "name"
27
- - "pupper.puppet.com"
27
+ - - "fact"
28
+ - "ipaddress"
29
+ - "192.168.0.252"
data/exe/ncedit CHANGED
@@ -11,49 +11,60 @@ Escort::App.create do |app|
11
11
  app.summary "ncedit"
12
12
  app.description "Edit PE node classification groups"
13
13
 
14
- # not currently working...
15
- # app.command :classes do |command|
16
- # command.summary "Install all known nodes"
17
- # command.description "Install Puppet Enterprise master and agents on all known nodes"
18
- # command.options do |opts|
19
- # opts.opt(:group_name,
20
- # 'NC group name',
21
- # :long => '--group-name',
22
- # :type => :string,
23
- # )
24
- # opts.opt(:class_name,
25
- # 'NC class name',
26
- # :long => '--class-name',
27
- # :type => :string,
28
- # )
29
- # opts.opt(:param_name,
30
- # 'NC parameter name',
31
- # :long => '--param-name',
32
- # :type => :string,
33
- # )
34
- # opts.opt(:param_value,
35
- # 'NC parameter value',
36
- # :long => '--param-value',
37
- # :type => :string,
38
- # )
39
- # opts.opt(:delete_class,
40
- # 'delete this rule',
41
- # :long => '--delete-class',
42
- # :type => :boolean,
43
- # :default => false,
44
- # )
45
- # opts.opt(:delete_param,
46
- # 'delete this rule',
47
- # :long => '--delete-param',
48
- # :type => :boolean,
49
- # :default => false,
50
- # )
51
- # opts.dependency :param_value, :on => :param_name
52
- # end
53
- # command.action do |options, arguments|
54
- # NCEdit::Cmd::classes(options, arguments)
55
- # end
56
- # end
14
+ app.command :classes do |command|
15
+ command.summary "Edit classes"
16
+ command.description "Create/edit/delete class in rule"
17
+ command.options do |opts|
18
+ opts.opt(:group_name,
19
+ 'NC group name',
20
+ :long => '--group-name',
21
+ :type => :string,
22
+ )
23
+ opts.opt(:class_name,
24
+ 'NC class name',
25
+ :long => '--class-name',
26
+ :type => :string,
27
+ )
28
+ opts.opt(:param_name,
29
+ 'NC parameter name',
30
+ :long => '--param-name',
31
+ :type => :string,
32
+ )
33
+ opts.opt(:param_value,
34
+ 'NC parameter value',
35
+ :long => '--param-value',
36
+ :type => :string,
37
+ )
38
+ opts.opt(:delete_class,
39
+ 'Delete this class',
40
+ :long => '--delete-class',
41
+ :type => :boolean,
42
+ :default => false,
43
+ )
44
+ opts.opt(:delete_param,
45
+ 'Delete this param',
46
+ :long => '--delete-param',
47
+ :type => :boolean,
48
+ :default => false,
49
+ )
50
+ opts.opt(:rule,
51
+ 'Update the NC group with this rule (JSON fragment)',
52
+ :long => '--rule',
53
+ :type => :string,
54
+ )
55
+ opts.dependency :param_value, :on => :param_name
56
+
57
+ opts.opt(:rule_mode,
58
+ "Processing instruction for rule supplied with --rule (allowed: 'replace', 'append')",
59
+ :long => '--rule-mode',
60
+ :type => :string,
61
+ )
62
+
63
+ end
64
+ command.action do |options, arguments|
65
+ NCEdit::Cmd::classes(options[:global][:commands][:classes][:options])
66
+ end
67
+ end
57
68
 
58
69
  app.command :batch do |command|
59
70
  command.summary "Batch processing from YAML/JSON"
@@ -99,6 +99,33 @@ module NCEdit
99
99
  group
100
100
  end
101
101
 
102
+ # to see if our changes were saved or not we need to remove all nillified
103
+ # keys from both levels (class, parameter) of the class_delta array, since
104
+ # when we re-read from the NC our nillified data will be completely gone. A
105
+ # naive comparison would then report a failure even though the operation
106
+ # succeeded. On a practical level we must convert:
107
+ # {"puppet_enterprise"=>{"proxy"=>nil, "keep"=>"keep"}, "b"=>nil}
108
+ # ...to...
109
+ # {"puppet_enterprise"=>{"keep"=>"keep"}}
110
+ #
111
+ # @param nc_class The class hash as re-read from the NC API
112
+ # @param class_delta The class delta we originally requested (with nils for deletes)
113
+ def self.delta_saved?(nc_class, class_delta)
114
+ class_delta_reformatted = class_delta.map { |class_name, params|
115
+ if params == nil
116
+ # skip classes that are requested to be deleted for the moment since
117
+ # we will catch them on the outer pass
118
+ params_fixed = params
119
+ else
120
+ # remove all individual nullified parameters
121
+ params_fixed = params.reject{|param_name, param_value| param_value == nil}
122
+ end
123
+ [class_name,params_fixed]
124
+ }.to_h.reject { |class_name,params| params == nil}
125
+
126
+ nc_class == class_delta_reformatted
127
+ end
128
+
102
129
  def self.update_group(group_name, classes: nil, rule: nil)
103
130
  # group_delta will actually replace all classes/rules with whatever is
104
131
  # specified, so we need to merge this with any existing definition if
@@ -126,11 +153,11 @@ module NCEdit
126
153
  # now present. If there was an error, then the user should have
127
154
  # previously seen some output since puppetclassify prints some useful
128
155
  # debug output
129
- saved_group = nc_group(group_name)
130
- if saved_group["classes"] == classes and saved_group["rule"] == rule
156
+ re_read_group = nc_group(group_name)
157
+ if delta_saved?(re_read_group["classes"], classes) and re_read_group["rule"] == rule
131
158
  Escort::Logger.output.puts "changes saved"
132
159
  else
133
- Escort::Logger.error.error "re-read #{group_name} results in #{saved_group} should have delta of #{group_delta}"
160
+ Escort::Logger.error.error "re-read #{group_name} results in #{re_read_group} should have delta of #{group_delta}"
134
161
  raise "Error saving #{group_name}"
135
162
  end
136
163
  end
@@ -190,55 +217,73 @@ module NCEdit
190
217
  data.each { |group_name, data|
191
218
 
192
219
  Escort::Logger.output.puts "Processing #{group_name}"
220
+ group = nc_group(group_name)
193
221
 
222
+ #
223
+ # delete classes
224
+ #
194
225
  if data.has_key?("delete_classes")
195
- if delete_classes(group_name, data["delete_classes"])
196
- update_group(group_name, classes: data["delete_classes"])
226
+ changes = false
227
+
228
+ data["delete_classes"].each { |class_name|
229
+ changes |= ensure_class(group, class_name, delete:true)
230
+ }
231
+ if changes
232
+ update_group(group_name, classes: group["classes"])
197
233
  end
198
234
  end
199
235
 
236
+ #
237
+ # delete params
238
+ #
200
239
  if data.has_key?("delete_params")
201
- if delete_params(group_name, data["delete_params"])
202
- update_group(group_name, classes: data["delete_params"])
240
+ changes = false
241
+ data["delete_params"].each { |class_name, delete_params|
242
+ delete_params.each { | param_name|
243
+ changes |= ensure_class(group, class_name)
244
+ changes |= ensure_param(group, class_name, param_name, nil, delete:true)
245
+ }
246
+ }
247
+ if changes
248
+ update_group(group_name, classes: group["classes"])
203
249
  end
204
250
  end
205
251
 
252
+ #
253
+ # classes (and optionally params)
254
+ #
206
255
  if data.has_key?("classes")
207
- if ensure_classes_and_params(group_name, data["classes"])
208
- update_group(group_name, classes: data["classes"])
256
+ if ensure_classes_and_params(group, data["classes"])
257
+ update_group(group_name, classes: group["classes"])
209
258
  end
210
259
  end
211
260
 
261
+ #
262
+ # append rules
263
+ #
212
264
  if data.has_key?("append_rules")
213
- if ensure_rules(group_name, data["append_rules"])
214
- update_group(group_name, rule: data["append_rules"])
265
+ if ensure_rules(group, data["append_rules"])
266
+ update_group(group_name, rule: group["rule"])
215
267
  end
216
268
  end
217
269
  }
218
270
  end
219
271
 
220
- def self.delete_class(group, class_name)
221
- if group["classes"].delete(class_name)
222
- changes = true
223
- else
224
- changes = false
225
- end
226
272
 
227
- changes
228
- end
229
-
230
- def self.delete_param(group, class_name, param_name)
231
- if group["classes"].has_key?(class_name) and
232
- group["classes"][class_name].delete(param_name)
273
+ # Classes are only removed when they have their parameters nilled so we must
274
+ # formulate special json to allow delete
275
+ # @see https://docs.puppet.com/pe/latest/nc_groups.html#post-v1groupsid
276
+ #
277
+ # Updates `group` to ensure that it now contains `class_name` (or marks it
278
+ # for deletion). To commit changes, need to pass the updated
279
+ # `group['class']` hash to `update_group`
280
+ def self.ensure_class(group, class_name, delete:false)
281
+ if group["classes"].has_key?(class_name) and delete
282
+ # delete class by nilling its parameters
283
+ group["classes"][class_name] = nil
233
284
  changes = true
234
- else
235
- changes = false
236
- end
237
- changes
238
- end
239
-
240
- def self.ensure_class(group, class_name)
241
- if ! group["classes"].has_key?(class_name)
285
+ elsif ! group["classes"].has_key?(class_name) and ! delete
286
+ # create class because we are not deleting it and it doesn't exist yet
242
287
  group["classes"][class_name] = {}
243
288
  changes = true
244
289
  else
@@ -248,12 +293,21 @@ module NCEdit
248
293
  changes
249
294
  end
250
295
 
251
- def self.ensure_param(group, class_name, param_name, param_value)
296
+ # Updates `group` to ensure that it now contains `param_name` set to
297
+ # `param_value` (or marks the parameter it for deletion). To commit changes
298
+ # , need to pass the updated `group['class']` hash to `update_group`
299
+ def self.ensure_param(group, class_name, param_name, param_value, delete:false)
252
300
  # ensure parameter set if specified
253
- if ! group["classes"][class_name].has_key?(param_name) or
254
- group["classes"][class_name][param_name] != param_value
301
+ if ! delete and (
302
+ ! group["classes"][class_name].has_key?(param_name) or
303
+ group["classes"][class_name][param_name] != param_value
304
+ )
305
+ # update or add a new parameter
255
306
  group["classes"][class_name][param_name] = param_value
256
307
  changes = true
308
+ elsif delete and group["classes"][class_name].has_key?(param_name)
309
+ group["classes"][class_name][param_name] = nil
310
+ changes = true
257
311
  else
258
312
  changes = false
259
313
  end
@@ -261,40 +315,19 @@ module NCEdit
261
315
  changes
262
316
  end
263
317
 
264
- def self.delete_classes(group_name, data)
265
- updated = false
266
- if data
267
- data.each{ |class_name|
268
- Escort::Logger.output.puts "Deleting class: #{group_name}->#{class_name}"
269
- updated |= delete_class(nc_group(group_name), class_name)
270
- }
271
- end
272
- updated
273
- end
274
-
275
- def self.delete_params(group_name, data)
276
- updated = false
277
- if data
278
- data.each{ |class_name, param_names|
279
- param_names.each { |param_name|
280
- Escort::Logger.output.puts "Deleting param: #{group_name}->#{class_name}=>#{param_name}"
281
- updated |= delete_param(nc_group(group_name), class_name, param_name)
282
- }
283
- }
284
- end
285
- updated
286
- end
287
-
288
- def self.ensure_classes_and_params(group_name, data)
318
+ # Updates `group` to ensure that it now contains classes and parameters as
319
+ # specified in the `data` paramater. To commit changes, need to pass the
320
+ # updated `group['class']` hash to `update_group`
321
+ def self.ensure_classes_and_params(group, data)
289
322
  updated = false
290
323
  if data
291
324
  data.each{ |class_name, params|
292
- Escort::Logger.output.puts "ensuring class: #{group_name}->#{class_name}"
293
- updated |= ensure_class(nc_group(group_name), class_name)
325
+ Escort::Logger.output.puts "ensuring class: #{group['name']}->#{class_name}"
326
+ updated |= ensure_class(group, class_name)
294
327
  if params
295
328
  params.each { |param_name, param_value|
296
- Escort::Logger.output.puts "ensuring param: #{group_name}->#{class_name}->#{param_name}=#{param_value}"
297
- updated |= ensure_param(nc_group(group_name), class_name, param_name, param_value)
329
+ Escort::Logger.output.puts "ensuring param: #{group['name']}->#{class_name}->#{param_name}=#{param_value}"
330
+ updated |= ensure_param(group, class_name, param_name, param_value)
298
331
  }
299
332
  end
300
333
  }
@@ -313,6 +346,8 @@ module NCEdit
313
346
  #
314
347
  # Only the rule to be added in should be passed as the rule parameter, eg:
315
348
  # ["=", "name", "bob"]
349
+ #
350
+ # To commit changes, need to pass the updated `group['rule']` hash to `update_group`
316
351
  def self.ensure_rule(group, rule)
317
352
  updated = false
318
353
 
@@ -321,7 +356,7 @@ module NCEdit
321
356
 
322
357
  # rules are nested like this, the "or" applies to the whole rule chain:
323
358
  # "rule"=>["or", ["=", "name", "bob"], ["=", "name", "hello"]]
324
- group["rule"][1].each {|system_rule|
359
+ group["rule"].drop(1).each {|system_rule|
325
360
  if system_rule[0] == rule[0] and
326
361
  system_rule[1] == rule[1] and
327
362
  system_rule[2] == rule[2]
@@ -331,33 +366,43 @@ module NCEdit
331
366
  }
332
367
  if ! found
333
368
  Escort::Logger.output.puts "Appending rule: #{rule}"
334
- group["rule"][1] << rule
369
+ group["rule"].push(rule)
335
370
  updated = true
336
371
  end
337
372
 
338
373
  updated
339
374
  end
340
375
 
376
+ # Modify `group` to ensure the passed in `rules` exist. To commit changes,
377
+ # need to pass the updated `group['rule']` hash to `update_group`
378
+ #
341
379
  # rules need to arrive like this:
342
380
  # ["or", ["=", "name", "pupper.megacorp.com"], ["=", "name", "pupper.megacorp.com"]]
343
381
  # since the rule conjunction "or" can only be specified once per rule chain
344
382
  # we will replace whatever already exists in the rule with what the user
345
383
  # specified
346
- def self.ensure_rules(group_name, rules)
384
+ def self.ensure_rules(group, rules)
347
385
  updated = false
348
- group = nc_group(group_name)
386
+
349
387
  if ! group["rule"] or group["rule"].empty?
350
388
  # no rules yet - just add our new one
351
- group["rule"] = [DEFAULT_RULE,[]]
389
+ group["rule"] = [DEFAULT_RULE]
352
390
  end
353
391
  updated |= ensure_rule_conjunction(group, rules[0])
354
- rules[1].each { |rule|
392
+ rules.drop(1).each { |rule|
355
393
  updated |= ensure_rule(group, rule)
356
394
  }
357
395
 
358
396
  updated
359
397
  end
360
398
 
399
+ # Ensure the correct boolean conjunction ('and'/'or' - 'not' is not allowed)
400
+ # is being used for a given rule chain. If user tried to append a rule with
401
+ # a different conjuction to the one currently in use we will change the
402
+ # conjuction used on the entire chain to match.
403
+ #
404
+ # Updates `group` in-place, To commit changes, need to pass the updated
405
+ # `group['rule']` hash to `update_group`
361
406
  def self.ensure_rule_conjunction(group, op)
362
407
  updated = false
363
408
  if ["and", "or"].include?(op)
@@ -371,5 +416,75 @@ module NCEdit
371
416
 
372
417
  updated
373
418
  end
419
+
420
+
421
+ def self.classes(options)
422
+ group_name = options[:group_name]
423
+ class_name = options[:class_name]
424
+ param_name = options[:param_name]
425
+ param_value = options[:param_value]
426
+ delete_class = options[:delete_class]
427
+ delete_param = options[:delete_param]
428
+ rule = options[:rule]
429
+ rule_mode = options[:rule_mode]
430
+
431
+ rule_change = false
432
+ class_change = false
433
+
434
+ if group_name
435
+ group = nc_group(group_name)
436
+ else
437
+ raise "All operations require a valid group_name"
438
+ end
439
+
440
+ rule_modes = ['replace', 'append']
441
+ if rule and (! rule_modes.include?(rule_mode))
442
+ raise "Invalid rule mode '#{rule_mode}'. Allowed: #{rule_modes}"
443
+ end
444
+
445
+ if class_name and delete_class
446
+ # delete a class from a group
447
+ Escort::Logger.output.puts "Deleting class #{class_name} from #{group_name}"
448
+ class_change = ensure_class(group, class_name, delete:true)
449
+ elsif class_name and param_name and delete_param
450
+ # delete a parameter from a class
451
+ Escort::Logger.output.puts "Deleting parameter #{param_name} on #{class_name} from #{group_name}"
452
+ class_change = ensure_class(group, class_name)
453
+ class_change |= ensure_param(group, class_name, param_name, nil, delete:true)
454
+ elsif class_name and param_name and param_value
455
+ # set a value inside a class
456
+ Escort::Logger.output.puts "Setting parameter #{param_name} to #{param_value} on #{class_name} in #{group_name}"
457
+ class_change = ensure_class(group, class_name)
458
+ class_change |= ensure_param(group, class_name, param_name, param_value)
459
+ elsif class_name
460
+ Escort::Logger.output.puts "Adding #{class_name} to #{group_name}"
461
+ class_change = ensure_class(group, class_name)
462
+ end
463
+
464
+ # process any rule changes separately since they are valid for all actions
465
+ if rule
466
+ begin
467
+ rule_json = JSON.parse(rule)
468
+ rescue JSON::ParserError
469
+ raise "Syntax error in data supplied to --rule (must be valid JSON)"
470
+ end
471
+
472
+ if rule_mode == 'replace'
473
+ if group['rule'] != rule_json
474
+ group['rule'] = rule_json
475
+ rule_change = true
476
+ end
477
+ else
478
+ rule_change = ensure_rules(group, rule_json)
479
+ end
480
+ end
481
+
482
+ # save changes
483
+ if class_change or rule_change
484
+ update_group(group_name, classes: group["classes"], rule: group["rule"])
485
+ else
486
+ Escort::Logger.output.puts "Already up-to-date"
487
+ end
488
+ end
374
489
  end
375
490
  end
@@ -1,3 +1,3 @@
1
1
  module NCEdit
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ncedit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geoff Williams
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-15 00:00:00.000000000 Z
11
+ date: 2017-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler