ncedit 0.1.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +2 -2
- data/Makefile +3 -0
- data/README.md +147 -13
- data/doc/example/batch.json +9 -6
- data/doc/example/batch.yaml +7 -5
- data/exe/ncedit +115 -44
- data/lib/ncedit/cmd.rb +357 -82
- data/lib/ncedit/version.rb +1 -1
- data/ncedit.gemspec +9 -8
- metadata +20 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e96cfedf083f77d881e84689e8126fcb2eacd5e965a1c78a8bfbbb3957468ccd
|
4
|
+
data.tar.gz: b2f81404318ea89f27d49bf7f2275c570aacb89816857ad76802479ba93e1fad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47c9753e923f1c6410093a8cc55506b6344bc8c13b0ce89d6a1f295fdfd4b3a2b3d0f0ac82b9cce6566c754300954a9dd58c2b599c0b97237e470be5d4e144f1
|
7
|
+
data.tar.gz: a8d646edffb771c405fccc8726769612f66126868e07c05cafb852df35d14f6b6ebfca44f9ff02cbc255cea59d49503b6e7c7081537c1e6a50cd775d2f45156d
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Makefile
ADDED
data/README.md
CHANGED
@@ -1,21 +1,23 @@
|
|
1
|
-
[![Build Status](https://travis-ci.org/
|
1
|
+
[![Build Status](https://travis-ci.org/declarativesystems/ncedit.svg?branch=master)](https://travis-ci.org/declarativesystems/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
|
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]
|
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 already 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
|
-
That's where this tool comes in since all you need is root shell on the Puppet Master
|
8
|
+
That's where this tool comes in since all you need is root shell on the Puppet Master.
|
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
|
16
|
+
* Add or edit environment groups
|
15
17
|
|
16
|
-
All from the convenience of the CLI. This
|
18
|
+
...All from the convenience of the CLI. This allows us 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
19
|
|
18
|
-
|
20
|
+
You have a choice of running `ncedit` for each change you wish to make or collecting all of your edits into either a JSON or YAML file for bulk processing.
|
19
21
|
|
20
22
|
## Installation
|
21
23
|
Install this tool on your Puppet Master (Master-of-Masters)
|
@@ -28,7 +30,7 @@ This will install the gem into the Puppet Master's vendored ruby. You could ins
|
|
28
30
|
## Usage
|
29
31
|
|
30
32
|
### 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.
|
33
|
+
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
34
|
|
33
35
|
### Making batch changes
|
34
36
|
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.
|
@@ -48,7 +50,7 @@ In each case the file needs to be ordered as follows:
|
|
48
50
|
"OPTIONAL_PARAM_NAME": "VALUE_TO_SET"
|
49
51
|
# Array of classes to delete
|
50
52
|
"delete_classes":
|
51
|
-
|
53
|
+
- "CLASS_TO_DELETE"
|
52
54
|
# Hash classes + Array of parameter names to delete
|
53
55
|
"delete_params":
|
54
56
|
"CLASS_TO_PROCESS":
|
@@ -96,27 +98,159 @@ In each case the file needs to be ordered as follows:
|
|
96
98
|
* [Worked example](doc/example/batch.json)
|
97
99
|
|
98
100
|
#### Ensuring changes
|
99
|
-
* ncedit is idempotent so you may run the command as often as you like
|
101
|
+
* `ncedit` is idempotent so you may run the command as often as you like
|
102
|
+
* Classes used in rules *must* already exist for classification to succeed, `--smart-update` will do a code redeploy for you if specified.
|
100
103
|
|
101
104
|
##### YAML
|
102
105
|
```shell
|
103
|
-
ncedit
|
106
|
+
ncedit batch --smart-update --yaml-file /path/to/batch.yaml
|
104
107
|
```
|
105
108
|
|
106
109
|
##### JSON
|
107
110
|
```shell
|
108
|
-
ncedit
|
111
|
+
ncedit batch --smart-update --json-file /path/to/batch.json
|
109
112
|
```
|
110
113
|
|
111
114
|
## Making per-item changes
|
112
|
-
|
115
|
+
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:
|
116
|
+
|
117
|
+
|
118
|
+
### Practical examples
|
119
|
+
One of the main uses of this tool is to configure Puppet Code Manager, so here are the exact commands you need (alternatively make a `batch.yaml` file to do it all in one hit):
|
120
|
+
|
121
|
+
#### Code Manager R10K checkout URL
|
122
|
+
```shell
|
123
|
+
ncedit classes --smart-update --group-name 'PE Master' \
|
124
|
+
--class-name puppet_enterprise::profile::master \
|
125
|
+
--param-name r10k_remote \
|
126
|
+
--param-value https://github.com/puppetlabs/control-repo
|
127
|
+
```
|
128
|
+
|
129
|
+
#### Code Manager SSH key
|
130
|
+
```shell
|
131
|
+
ncedit classes --smart-update --group-name 'PE Master' \
|
132
|
+
--class-name puppet_enterprise::profile::master \
|
133
|
+
--param-name r10k_ \
|
134
|
+
--param-value /etc/puppetlabs/puppetserver/ssh/id_rsa
|
135
|
+
```
|
136
|
+
|
137
|
+
#### Code Manager proxy server
|
138
|
+
```shell
|
139
|
+
ncedit classes --smart-update --group-name 'PE Master' \
|
140
|
+
--class-name puppet_enterprise::profile::master \
|
141
|
+
--param-name r10k_proxy \
|
142
|
+
--param-value http://proxy.megacorp.com:3128
|
143
|
+
```
|
144
|
+
|
145
|
+
|
146
|
+
#### Enable Code Manager
|
147
|
+
```shell
|
148
|
+
ncedit classes --smart-update --group-name 'PE Master' \
|
149
|
+
--class-name puppet_enterprise::profile::master \
|
150
|
+
--param-name code_manager_auto_configure \
|
151
|
+
--param-value true
|
152
|
+
|
153
|
+
ncedit classes --smart-update --group-name 'PE Master' \
|
154
|
+
--class-name puppet_enterprise::profile::master \
|
155
|
+
--param-name file_sync_enabled \
|
156
|
+
--param-value automatic
|
157
|
+
```
|
158
|
+
|
159
|
+
|
160
|
+
#### Add a rule to configure the puppet master with extra classes
|
161
|
+
```shell
|
162
|
+
ncedit classes --smart-update --group-name "Puppet Masters" \
|
163
|
+
--class-name role::my_puppet_master \
|
164
|
+
--rule '["and", ["=",["fact","fqdn"],"'$(facter fqdn)'"]]' \
|
165
|
+
--rule-mode replace
|
166
|
+
```
|
167
|
+
|
168
|
+
* Run puppet and then re-deploy puppet code
|
169
|
+
* Create a new group called `Puppet Masters`
|
170
|
+
* Make it load the role `role::my_puppet_master`
|
171
|
+
* Have it match any host matching the FQDN of the host the command was run from and replace any existing rules for the group
|
172
|
+
|
173
|
+
#### Refresh the list of classes in the console
|
174
|
+
```shell
|
175
|
+
ncedit update_classes
|
176
|
+
```
|
177
|
+
|
178
|
+
### Add a class to group
|
179
|
+
```shell
|
180
|
+
ncedit classes --group-name FOO --class-name BAR
|
181
|
+
```
|
182
|
+
* Group will be created if it doesn't exist
|
183
|
+
* Class MUST exist AND puppet must be aware of it (defeat caching) for the request to be accepted
|
184
|
+
* Pass `--smart-update` to run puppet and re-deploy classes if desired
|
185
|
+
|
186
|
+
### Add a parameter to a class for a group
|
187
|
+
```shell
|
188
|
+
ncedit classes --group-name FOO --class-name BAR --param-name BAZ --param-value BAS
|
189
|
+
```
|
190
|
+
* Group will be created if it doesn't exist
|
191
|
+
* Class will be added if it isn't already
|
192
|
+
* 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
|
193
|
+
* Pass `--smart-update` to run puppet and re-deploy classes if desired
|
194
|
+
|
195
|
+
### Delete a parameter from a class inside a group
|
196
|
+
```shell
|
197
|
+
ncedit classes --group-name FOO --class-name BAR --param-name BAZ --delete-param
|
198
|
+
```
|
199
|
+
* Group will be created if it doesn't exist
|
200
|
+
* Class will be added if it isn't already
|
201
|
+
* Ensures the parameter `BAZ` is not set
|
202
|
+
|
203
|
+
### Delete a class inside a group
|
204
|
+
```shell
|
205
|
+
ncedit classes --group-name FOO --class-name BAR --delete-class
|
206
|
+
```
|
207
|
+
* Group will be created if it doesn't exist
|
208
|
+
* Ensures the class `BAR` is not defined
|
209
|
+
|
210
|
+
### Replace all rules for group
|
211
|
+
```shell
|
212
|
+
ncedit classes --group-name FOO --rule 'JSON_FRAGMENT' --rule-mode replace
|
213
|
+
```
|
214
|
+
* Group will be created if it doesn't exist
|
215
|
+
* Completely replaces existing rules for this group
|
216
|
+
* Example JSON_FRAGMENT: `["and",["=",["fact","ipaddress"],"192.168.1.1"]]`
|
217
|
+
|
218
|
+
### Append a rule to group
|
219
|
+
```shell
|
220
|
+
ncedit classes --group-name FOO --rule 'JSON_FRAGMENT' --rule-mode append
|
221
|
+
```
|
222
|
+
* Group will be created if it doesn't exist
|
223
|
+
* Appends supplied JSON_FRAGMENT to the group's rules
|
224
|
+
* Example JSON_FRAGMENT: `["and",["=",["fact","osfamily"],"RedHat"]]`
|
225
|
+
* 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
|
226
|
+
|
227
|
+
### Add/edit an environment group (experimental)
|
228
|
+
|
229
|
+
```shell
|
230
|
+
ncedit groups --group-name "Agent-specified environment" --environment agent-specified --environment-trumps --rule '["~",["fact","fqdn"],".*"]' --rule-mode replace
|
231
|
+
```
|
232
|
+
* Group will be created if it doesn't exit
|
233
|
+
* Will be nested under `All Environments`
|
234
|
+
|
235
|
+
## Smart updates
|
236
|
+
The NC API will only accept rules for classes that currently exist on the system, so its likely that your rules will be rejected if they have not yet been deployed to the master. To make this process as seamless as possible, the `--smart-update` option will perform this task for you as-and-when required. This assumes you have already setup RBAC to grant access to Code Manager, this can be done in a single command using the [pe_rbac gem](https://github.com/declarativesystems/pe_rbac#setting-up-code-manager-on-the-command-line)
|
237
|
+
|
238
|
+
Smart updates work as follows:
|
239
|
+
|
240
|
+
* If any of the `r10k_*` or `code_manager_auto_configure` parameters are set, update them immediately
|
241
|
+
* Run `puppet agent -t` then `puppet-code deploy --all --wait`
|
242
|
+
* Process any remaining directives
|
113
243
|
|
114
244
|
## Troubleshooting
|
245
|
+
* 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`.
|
246
|
+
* 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
247
|
* 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
248
|
```
|
117
249
|
bundle exec ncedit --verbosity debug batch --yaml-file /path/to/batch.yaml
|
118
250
|
```
|
119
251
|
* Ensure you are running as `root` to avoid permission errors
|
252
|
+
* 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`
|
253
|
+
* 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
254
|
|
121
255
|
## Testing
|
122
256
|
To run tests:
|
@@ -128,4 +262,4 @@ bundle exec rake spec
|
|
128
262
|
|
129
263
|
## Contributing
|
130
264
|
|
131
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
265
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/declarativesystems/ncedit.
|
data/doc/example/batch.json
CHANGED
@@ -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::
|
16
|
-
"
|
14
|
+
"puppet_enterprise::profile::mcollective::agent": [
|
15
|
+
"stomp_user"
|
17
16
|
]
|
18
17
|
}
|
19
18
|
},
|
20
19
|
"Puppet Masters": {
|
21
20
|
"classes": {
|
22
|
-
"
|
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
|
-
|
30
|
-
|
29
|
+
[
|
30
|
+
"fact",
|
31
|
+
"ipaddress"
|
32
|
+
],
|
33
|
+
"192.168.0.252"
|
31
34
|
]
|
32
35
|
]
|
33
36
|
}
|
data/doc/example/batch.yaml
CHANGED
@@ -14,14 +14,16 @@
|
|
14
14
|
- "puppet_enterprise::profile::masterbad"
|
15
15
|
|
16
16
|
"delete_params":
|
17
|
-
"puppet_enterprise::profile::
|
18
|
-
- "
|
17
|
+
"puppet_enterprise::profile::mcollective::agent":
|
18
|
+
- "stomp_user"
|
19
19
|
|
20
20
|
"Puppet Masters":
|
21
21
|
"classes":
|
22
|
-
"
|
22
|
+
"pe_r10k":
|
23
|
+
"proxy": "proxy.megacorp.com:8080"
|
23
24
|
"append_rules":
|
24
25
|
- "or"
|
25
26
|
- - "="
|
26
|
-
- "
|
27
|
-
|
27
|
+
- - "fact"
|
28
|
+
- "ipaddress"
|
29
|
+
- "192.168.0.252"
|
data/exe/ncedit
CHANGED
@@ -6,54 +6,74 @@ require 'ncedit/cmd'
|
|
6
6
|
# display help if nothing specified
|
7
7
|
ARGV.push('-h') if ARGV.empty?
|
8
8
|
|
9
|
+
# Add the path to the puppet-code command as a fallback. It's last to
|
10
|
+
# allow user to override via the real PATH if necessary
|
11
|
+
ENV['PATH'] = "#{ENV['PATH']}:/opt/puppetlabs/puppet/bin/:/opt/puppetlabs/client-tools/bin/"
|
12
|
+
|
13
|
+
|
9
14
|
Escort::App.create do |app|
|
10
15
|
app.version NCEdit::VERSION
|
11
16
|
app.summary "ncedit"
|
12
17
|
app.description "Edit PE node classification groups"
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
19
|
+
app.command :classes do |command|
|
20
|
+
command.summary "Edit classes"
|
21
|
+
command.description "Create/edit/delete class in rule"
|
22
|
+
command.options do |opts|
|
23
|
+
opts.opt(:group_name,
|
24
|
+
'NC group name',
|
25
|
+
:long => '--group-name',
|
26
|
+
:type => :string,
|
27
|
+
)
|
28
|
+
opts.opt(:class_name,
|
29
|
+
'NC class name',
|
30
|
+
:long => '--class-name',
|
31
|
+
:type => :string,
|
32
|
+
)
|
33
|
+
opts.opt(:param_name,
|
34
|
+
'NC parameter name',
|
35
|
+
:long => '--param-name',
|
36
|
+
:type => :string,
|
37
|
+
)
|
38
|
+
opts.opt(:param_value,
|
39
|
+
'NC parameter value',
|
40
|
+
:long => '--param-value',
|
41
|
+
:type => :string,
|
42
|
+
)
|
43
|
+
opts.opt(:delete_class,
|
44
|
+
'Delete this class',
|
45
|
+
:long => '--delete-class',
|
46
|
+
:type => :boolean,
|
47
|
+
:default => false,
|
48
|
+
)
|
49
|
+
opts.opt(:delete_param,
|
50
|
+
'Delete this param',
|
51
|
+
:long => '--delete-param',
|
52
|
+
:type => :boolean,
|
53
|
+
:default => false,
|
54
|
+
)
|
55
|
+
opts.opt(:rule,
|
56
|
+
'Update the NC group with this rule (JSON fragment)',
|
57
|
+
:long => '--rule',
|
58
|
+
:type => :string,
|
59
|
+
)
|
60
|
+
opts.dependency :param_value, :on => :param_name
|
61
|
+
|
62
|
+
opts.opt(:rule_mode,
|
63
|
+
"Processing instruction for rule supplied with --rule (allowed: 'replace', 'append')",
|
64
|
+
:long => '--rule-mode',
|
65
|
+
:type => :string,
|
66
|
+
)
|
67
|
+
opts.opt(:smart_update,
|
68
|
+
'Smart update',
|
69
|
+
:long => '--smart-update',
|
70
|
+
:type => :boolean,
|
71
|
+
)
|
72
|
+
end
|
73
|
+
command.action do |options, arguments|
|
74
|
+
NCEdit::Cmd::classes(options[:global][:commands][:classes][:options])
|
75
|
+
end
|
76
|
+
end
|
57
77
|
|
58
78
|
app.command :batch do |command|
|
59
79
|
command.summary "Batch processing from YAML/JSON"
|
@@ -69,12 +89,63 @@ Escort::App.create do |app|
|
|
69
89
|
:long => '--json-file',
|
70
90
|
:type => :string,
|
71
91
|
)
|
92
|
+
opts.opt(:smart_update,
|
93
|
+
'Smart update',
|
94
|
+
:long => '--smart-update',
|
95
|
+
:type => :boolean,
|
96
|
+
)
|
72
97
|
end
|
73
98
|
command.action do |options, arguments|
|
74
99
|
yaml_file = options[:global][:commands][:batch][:options][:yaml_file]
|
75
100
|
json_file = options[:global][:commands][:batch][:options][:json_file]
|
101
|
+
smart_update = options[:global][:commands][:batch][:options][:smart_update]
|
76
102
|
|
77
|
-
NCEdit::Cmd::batch(yaml_file: yaml_file, json_file: json_file)
|
103
|
+
NCEdit::Cmd::batch(yaml_file: yaml_file, json_file: json_file, smart_update: smart_update)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
app.command :groups do |command|
|
108
|
+
command.summary "Create/Edit groups"
|
109
|
+
command.description "Create or edit groups (and set environment)"
|
110
|
+
command.options do |opts|
|
111
|
+
opts.opt(:group_name,
|
112
|
+
'NC group name',
|
113
|
+
:long => '--group-name',
|
114
|
+
:type => :string,
|
115
|
+
)
|
116
|
+
opts.opt(:environment,
|
117
|
+
'Group should use this environment',
|
118
|
+
:long => '--environment',
|
119
|
+
:type => :string,
|
120
|
+
)
|
121
|
+
opts.opt(:environment_trumps,
|
122
|
+
'Group environment overrides all others',
|
123
|
+
:long => '--environment-trumps',
|
124
|
+
:type => :boolean,
|
125
|
+
)
|
126
|
+
opts.opt(:rule,
|
127
|
+
'Update the NC group with this rule (JSON fragment)',
|
128
|
+
:long => '--rule',
|
129
|
+
:type => :string,
|
130
|
+
)
|
131
|
+
|
132
|
+
opts.opt(:rule_mode,
|
133
|
+
"Processing instruction for rule supplied with --rule (allowed: 'replace', 'append')",
|
134
|
+
:long => '--rule-mode',
|
135
|
+
:type => :string,
|
136
|
+
)
|
137
|
+
end
|
138
|
+
command.action do |options, arguments|
|
139
|
+
NCEdit::Cmd::groups(options[:global][:commands][:groups][:options])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
app.command :update_classes do |command|
|
145
|
+
command.summary "Refresh the classes available in the console"
|
146
|
+
command.description "Invalidate class cache and ask puppet to re-scan classes"
|
147
|
+
command.action do |options, arguments|
|
148
|
+
NCEdit::Cmd::update_classes()
|
78
149
|
end
|
79
150
|
end
|
80
151
|
end
|
data/lib/ncedit/cmd.rb
CHANGED
@@ -2,11 +2,21 @@ require 'puppetclassify'
|
|
2
2
|
require 'yaml'
|
3
3
|
require 'json'
|
4
4
|
require 'escort'
|
5
|
+
require 'puppet_https'
|
5
6
|
|
6
7
|
module NCEdit
|
7
8
|
module Cmd
|
8
9
|
DEFAULT_RULE = "or"
|
9
10
|
|
11
|
+
R10K_SETTINGS_CLASS = "puppet_enterprise::profile::master"
|
12
|
+
R10K_SETTINGS_PARAMS = [
|
13
|
+
"code_manager_auto_configure",
|
14
|
+
"r10k_remote",
|
15
|
+
"r10k_private_key",
|
16
|
+
"r10k_proxy",
|
17
|
+
"r10k_postrun",
|
18
|
+
]
|
19
|
+
|
10
20
|
def self.init(puppetclassify = nil)
|
11
21
|
if puppetclassify
|
12
22
|
# use passed in puppetclassify if present - allows injection for easy
|
@@ -16,9 +26,12 @@ module NCEdit
|
|
16
26
|
hostname = %x(facter fqdn).strip.downcase
|
17
27
|
port = 4433
|
18
28
|
|
29
|
+
|
19
30
|
# Define the url to the classifier API - we can't just do localhost because
|
20
31
|
# the name has to match the SSL certificate
|
21
|
-
|
32
|
+
base_url = "https://#{hostname}:"
|
33
|
+
@puppet_url = "#{base_url}8140"
|
34
|
+
@rest_api_url = "#{base_url}#{port}/classifier-api"
|
22
35
|
|
23
36
|
# We need to authenticate against the REST API using a certificate
|
24
37
|
# that is whitelisted in /etc/puppetlabs/console-services/rbac-certificate-whitelist.
|
@@ -54,12 +67,15 @@ module NCEdit
|
|
54
67
|
end
|
55
68
|
end
|
56
69
|
|
57
|
-
@puppetclassify = PuppetClassify.new(rest_api_url, auth_info)
|
70
|
+
@puppetclassify = PuppetClassify.new(@rest_api_url, auth_info)
|
71
|
+
|
72
|
+
# borrow the cool HTTPS requester built into puppetclassify
|
73
|
+
@puppet_https = PuppetHttps.new(auth_info)
|
58
74
|
end
|
59
75
|
end
|
60
76
|
|
61
77
|
# Fetch a group by ID, make the group if it doesn't already exist
|
62
|
-
def self.nc_group_id(group_name)
|
78
|
+
def self.nc_group_id(group_name, parent_name: "All Nodes")
|
63
79
|
if ! @puppetclassify
|
64
80
|
init
|
65
81
|
end
|
@@ -70,7 +86,7 @@ module NCEdit
|
|
70
86
|
res = @puppetclassify.groups.create_group(
|
71
87
|
{
|
72
88
|
"name" => group_name,
|
73
|
-
"parent" => @puppetclassify.groups.get_group_id(
|
89
|
+
"parent" => @puppetclassify.groups.get_group_id(parent_name),
|
74
90
|
"classes" => {},
|
75
91
|
}
|
76
92
|
)
|
@@ -85,21 +101,48 @@ module NCEdit
|
|
85
101
|
group_id
|
86
102
|
end
|
87
103
|
|
88
|
-
def self.nc_group(group_name)
|
104
|
+
def self.nc_group(group_name, parent_name:nil)
|
89
105
|
if ! @puppetclassify
|
90
106
|
init
|
91
107
|
end
|
92
108
|
# Get the wanted group from the API
|
93
109
|
# 1. Get the id of the wanted group
|
94
110
|
# 2. Use the id to fetch the group
|
95
|
-
group_id
|
111
|
+
group_id = nc_group_id(group_name, parent_name: parent_name)
|
96
112
|
Escort::Logger.output.puts "Group #{group_name} found, getting definition"
|
97
113
|
group = @puppetclassify.groups.get_group(group_id)
|
98
114
|
|
99
115
|
group
|
100
116
|
end
|
101
117
|
|
102
|
-
|
118
|
+
# to see if our changes were saved or not we need to remove all nillified
|
119
|
+
# keys from both levels (class, parameter) of the class_delta array, since
|
120
|
+
# when we re-read from the NC our nillified data will be completely gone. A
|
121
|
+
# naive comparison would then report a failure even though the operation
|
122
|
+
# succeeded. On a practical level we must convert:
|
123
|
+
# {"puppet_enterprise"=>{"proxy"=>nil, "keep"=>"keep"}, "b"=>nil}
|
124
|
+
# ...to...
|
125
|
+
# {"puppet_enterprise"=>{"keep"=>"keep"}}
|
126
|
+
#
|
127
|
+
# @param nc_class The class hash as re-read from the NC API
|
128
|
+
# @param class_delta The class delta we originally requested (with nils for deletes)
|
129
|
+
def self.delta_saved?(nc_class, class_delta)
|
130
|
+
class_delta_reformatted = class_delta.map { |class_name, params|
|
131
|
+
if params == nil
|
132
|
+
# skip classes that are requested to be deleted for the moment since
|
133
|
+
# we will catch them on the outer pass
|
134
|
+
params_fixed = params
|
135
|
+
else
|
136
|
+
# remove all individual nullified parameters
|
137
|
+
params_fixed = params.reject{|param_name, param_value| param_value == nil}
|
138
|
+
end
|
139
|
+
[class_name,params_fixed]
|
140
|
+
}.to_h.reject { |class_name,params| params == nil}
|
141
|
+
|
142
|
+
nc_class == class_delta_reformatted
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.update_group(group_name, classes: nil, rule: nil, environment: nil, environment_trumps: nil)
|
103
146
|
# group_delta will actually replace all classes/rules with whatever is
|
104
147
|
# specified, so we need to merge this with any existing definition if
|
105
148
|
# one of these fields is not needed for a particular update otherwise
|
@@ -112,10 +155,20 @@ module NCEdit
|
|
112
155
|
rule = nc_group(group_name)["rule"]
|
113
156
|
end
|
114
157
|
|
158
|
+
if environment == nil
|
159
|
+
environment = nc_group(group_name)["environment"]
|
160
|
+
end
|
161
|
+
|
162
|
+
if ! environment_trumps
|
163
|
+
environment_trumps = nc_group(group_name)["environment_trumps"]
|
164
|
+
end
|
165
|
+
|
115
166
|
group_delta = {
|
116
|
-
'id'
|
117
|
-
'rule'
|
118
|
-
'classes'
|
167
|
+
'id' => nc_group_id(group_name),
|
168
|
+
'rule' => rule,
|
169
|
+
'classes' => classes,
|
170
|
+
'environment' => environment,
|
171
|
+
'environment_trumps' => environment_trumps,
|
119
172
|
}
|
120
173
|
res = @puppetclassify.groups.update_group(group_delta)
|
121
174
|
|
@@ -126,11 +179,14 @@ module NCEdit
|
|
126
179
|
# now present. If there was an error, then the user should have
|
127
180
|
# previously seen some output since puppetclassify prints some useful
|
128
181
|
# debug output
|
129
|
-
|
130
|
-
if
|
182
|
+
re_read_group = nc_group(group_name)
|
183
|
+
if delta_saved?(re_read_group["classes"], classes) &&
|
184
|
+
re_read_group["rule"] == rule &&
|
185
|
+
re_read_group["environment"] == environment &&
|
186
|
+
re_read_group["environment_trumps"] == environment_trumps
|
131
187
|
Escort::Logger.output.puts "changes saved"
|
132
188
|
else
|
133
|
-
Escort::Logger.error.error "re-read #{group_name} results in #{
|
189
|
+
Escort::Logger.error.error "re-read #{group_name} results in #{re_read_group} should have delta of #{group_delta}"
|
134
190
|
raise "Error saving #{group_name}"
|
135
191
|
end
|
136
192
|
end
|
@@ -179,66 +235,96 @@ module NCEdit
|
|
179
235
|
# 'badparam'
|
180
236
|
#
|
181
237
|
# 'Puppet Masters':
|
182
|
-
# '
|
238
|
+
# 'classes':
|
183
239
|
# 'role::puppet::master':
|
184
240
|
# 'append_rules':
|
185
241
|
# - - "="
|
186
242
|
# - "name"
|
187
243
|
# - "vmpump02.puppet.com"
|
188
|
-
def self.batch(yaml_file: nil, json_file: nil)
|
244
|
+
def self.batch(yaml_file: nil, json_file: nil, smart_update: false)
|
189
245
|
data = read_batch_data(yaml_file: yaml_file, json_file: json_file)
|
246
|
+
|
247
|
+
if smart_update
|
248
|
+
needs_reclassify = contains_r10k_settings(data)
|
249
|
+
if needs_reclassify
|
250
|
+
apply_r10k_settings_now(needs_reclassify)
|
251
|
+
end
|
252
|
+
puppet_code_deploy
|
253
|
+
|
254
|
+
Escort::Logger.output.puts "Sleep for 60 seconds to let classes finish their refresh..."
|
255
|
+
sleep(60)
|
256
|
+
end
|
257
|
+
|
190
258
|
data.each { |group_name, data|
|
191
259
|
|
192
260
|
Escort::Logger.output.puts "Processing #{group_name}"
|
261
|
+
group = nc_group(group_name)
|
193
262
|
|
263
|
+
#
|
264
|
+
# delete classes
|
265
|
+
#
|
194
266
|
if data.has_key?("delete_classes")
|
195
|
-
|
196
|
-
|
267
|
+
changes = false
|
268
|
+
|
269
|
+
data["delete_classes"].each { |class_name|
|
270
|
+
changes |= ensure_class(group, class_name, delete:true)
|
271
|
+
}
|
272
|
+
if changes
|
273
|
+
update_group(group_name, classes: group["classes"])
|
197
274
|
end
|
198
275
|
end
|
199
276
|
|
277
|
+
#
|
278
|
+
# delete params
|
279
|
+
#
|
200
280
|
if data.has_key?("delete_params")
|
201
|
-
|
202
|
-
|
281
|
+
changes = false
|
282
|
+
data["delete_params"].each { |class_name, delete_params|
|
283
|
+
delete_params.each { | param_name|
|
284
|
+
changes |= ensure_class(group, class_name)
|
285
|
+
changes |= ensure_param(group, class_name, param_name, nil, delete:true)
|
286
|
+
}
|
287
|
+
}
|
288
|
+
if changes
|
289
|
+
update_group(group_name, classes: group["classes"])
|
203
290
|
end
|
204
291
|
end
|
205
292
|
|
293
|
+
#
|
294
|
+
# classes (and optionally params)
|
295
|
+
#
|
206
296
|
if data.has_key?("classes")
|
207
|
-
if ensure_classes_and_params(
|
208
|
-
update_group(group_name, classes:
|
297
|
+
if ensure_classes_and_params(group, data["classes"])
|
298
|
+
update_group(group_name, classes: group["classes"])
|
209
299
|
end
|
210
300
|
end
|
211
301
|
|
302
|
+
#
|
303
|
+
# append rules
|
304
|
+
#
|
212
305
|
if data.has_key?("append_rules")
|
213
|
-
if ensure_rules(
|
214
|
-
update_group(group_name, rule:
|
306
|
+
if ensure_rules(group, data["append_rules"])
|
307
|
+
update_group(group_name, rule: group["rule"])
|
215
308
|
end
|
216
309
|
end
|
217
310
|
}
|
218
311
|
end
|
219
312
|
|
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
|
-
|
227
|
-
changes
|
228
|
-
end
|
229
313
|
|
230
|
-
|
231
|
-
|
232
|
-
|
314
|
+
# Classes are only removed when they have their parameters nilled so we must
|
315
|
+
# formulate special json to allow delete
|
316
|
+
# @see https://docs.puppet.com/pe/latest/nc_groups.html#post-v1groupsid
|
317
|
+
#
|
318
|
+
# Updates `group` to ensure that it now contains `class_name` (or marks it
|
319
|
+
# for deletion). To commit changes, need to pass the updated
|
320
|
+
# `group['class']` hash to `update_group`
|
321
|
+
def self.ensure_class(group, class_name, delete:false)
|
322
|
+
if group["classes"].has_key?(class_name) and delete
|
323
|
+
# delete class by nilling its parameters
|
324
|
+
group["classes"][class_name] = nil
|
233
325
|
changes = true
|
234
|
-
|
235
|
-
|
236
|
-
end
|
237
|
-
changes
|
238
|
-
end
|
239
|
-
|
240
|
-
def self.ensure_class(group, class_name)
|
241
|
-
if ! group["classes"].has_key?(class_name)
|
326
|
+
elsif ! group["classes"].has_key?(class_name) and ! delete
|
327
|
+
# create class because we are not deleting it and it doesn't exist yet
|
242
328
|
group["classes"][class_name] = {}
|
243
329
|
changes = true
|
244
330
|
else
|
@@ -248,12 +334,21 @@ module NCEdit
|
|
248
334
|
changes
|
249
335
|
end
|
250
336
|
|
251
|
-
|
337
|
+
# Updates `group` to ensure that it now contains `param_name` set to
|
338
|
+
# `param_value` (or marks the parameter it for deletion). To commit changes
|
339
|
+
# , need to pass the updated `group['class']` hash to `update_group`
|
340
|
+
def self.ensure_param(group, class_name, param_name, param_value, delete:false)
|
252
341
|
# ensure parameter set if specified
|
253
|
-
if !
|
254
|
-
|
342
|
+
if ! delete and (
|
343
|
+
! group["classes"][class_name].has_key?(param_name) or
|
344
|
+
group["classes"][class_name][param_name] != param_value
|
345
|
+
)
|
346
|
+
# update or add a new parameter
|
255
347
|
group["classes"][class_name][param_name] = param_value
|
256
348
|
changes = true
|
349
|
+
elsif delete and group["classes"][class_name].has_key?(param_name)
|
350
|
+
group["classes"][class_name][param_name] = nil
|
351
|
+
changes = true
|
257
352
|
else
|
258
353
|
changes = false
|
259
354
|
end
|
@@ -261,40 +356,19 @@ module NCEdit
|
|
261
356
|
changes
|
262
357
|
end
|
263
358
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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)
|
359
|
+
# Updates `group` to ensure that it now contains classes and parameters as
|
360
|
+
# specified in the `data` paramater. To commit changes, need to pass the
|
361
|
+
# updated `group['class']` hash to `update_group`
|
362
|
+
def self.ensure_classes_and_params(group, data)
|
289
363
|
updated = false
|
290
364
|
if data
|
291
365
|
data.each{ |class_name, params|
|
292
|
-
Escort::Logger.output.puts "ensuring class: #{
|
293
|
-
updated |= ensure_class(
|
366
|
+
Escort::Logger.output.puts "ensuring class: #{group['name']}->#{class_name}"
|
367
|
+
updated |= ensure_class(group, class_name)
|
294
368
|
if params
|
295
369
|
params.each { |param_name, param_value|
|
296
|
-
Escort::Logger.output.puts "ensuring param: #{
|
297
|
-
updated |= ensure_param(
|
370
|
+
Escort::Logger.output.puts "ensuring param: #{group['name']}->#{class_name}->#{param_name}=#{param_value}"
|
371
|
+
updated |= ensure_param(group, class_name, param_name, param_value)
|
298
372
|
}
|
299
373
|
end
|
300
374
|
}
|
@@ -302,7 +376,7 @@ module NCEdit
|
|
302
376
|
updated
|
303
377
|
end
|
304
378
|
|
305
|
-
# Ensure a
|
379
|
+
# Ensure a particular rule exists in the group["rule"] array
|
306
380
|
# This affects only the items in the chain, eg:
|
307
381
|
# [
|
308
382
|
# "or",
|
@@ -313,6 +387,8 @@ module NCEdit
|
|
313
387
|
#
|
314
388
|
# Only the rule to be added in should be passed as the rule parameter, eg:
|
315
389
|
# ["=", "name", "bob"]
|
390
|
+
#
|
391
|
+
# To commit changes, need to pass the updated `group['rule']` hash to `update_group`
|
316
392
|
def self.ensure_rule(group, rule)
|
317
393
|
updated = false
|
318
394
|
|
@@ -321,7 +397,7 @@ module NCEdit
|
|
321
397
|
|
322
398
|
# rules are nested like this, the "or" applies to the whole rule chain:
|
323
399
|
# "rule"=>["or", ["=", "name", "bob"], ["=", "name", "hello"]]
|
324
|
-
group["rule"]
|
400
|
+
group["rule"].drop(1).each {|system_rule|
|
325
401
|
if system_rule[0] == rule[0] and
|
326
402
|
system_rule[1] == rule[1] and
|
327
403
|
system_rule[2] == rule[2]
|
@@ -331,33 +407,43 @@ module NCEdit
|
|
331
407
|
}
|
332
408
|
if ! found
|
333
409
|
Escort::Logger.output.puts "Appending rule: #{rule}"
|
334
|
-
group["rule"]
|
410
|
+
group["rule"].push(rule)
|
335
411
|
updated = true
|
336
412
|
end
|
337
413
|
|
338
414
|
updated
|
339
415
|
end
|
340
416
|
|
417
|
+
# Modify `group` to ensure the passed in `rules` exist. To commit changes,
|
418
|
+
# need to pass the updated `group['rule']` hash to `update_group`
|
419
|
+
#
|
341
420
|
# rules need to arrive like this:
|
342
421
|
# ["or", ["=", "name", "pupper.megacorp.com"], ["=", "name", "pupper.megacorp.com"]]
|
343
422
|
# since the rule conjunction "or" can only be specified once per rule chain
|
344
423
|
# we will replace whatever already exists in the rule with what the user
|
345
424
|
# specified
|
346
|
-
def self.ensure_rules(
|
425
|
+
def self.ensure_rules(group, rules)
|
347
426
|
updated = false
|
348
|
-
|
427
|
+
|
349
428
|
if ! group["rule"] or group["rule"].empty?
|
350
429
|
# no rules yet - just add our new one
|
351
|
-
group["rule"] = [DEFAULT_RULE
|
430
|
+
group["rule"] = [DEFAULT_RULE]
|
352
431
|
end
|
353
432
|
updated |= ensure_rule_conjunction(group, rules[0])
|
354
|
-
rules
|
433
|
+
rules.drop(1).each { |rule|
|
355
434
|
updated |= ensure_rule(group, rule)
|
356
435
|
}
|
357
436
|
|
358
437
|
updated
|
359
438
|
end
|
360
439
|
|
440
|
+
# Ensure the correct boolean conjunction ('and'/'or' - 'not' is not allowed)
|
441
|
+
# is being used for a given rule chain. If user tried to append a rule with
|
442
|
+
# a different conjuction to the one currently in use we will change the
|
443
|
+
# conjuction used on the entire chain to match.
|
444
|
+
#
|
445
|
+
# Updates `group` in-place, To commit changes, need to pass the updated
|
446
|
+
# `group['rule']` hash to `update_group`
|
361
447
|
def self.ensure_rule_conjunction(group, op)
|
362
448
|
updated = false
|
363
449
|
if ["and", "or"].include?(op)
|
@@ -371,5 +457,194 @@ module NCEdit
|
|
371
457
|
|
372
458
|
updated
|
373
459
|
end
|
460
|
+
|
461
|
+
# process any rule changes separately since they are valid for all actions
|
462
|
+
# returns true if changes were made
|
463
|
+
def self.rule_change(group, rule, rule_mode)
|
464
|
+
rule_change = false
|
465
|
+
|
466
|
+
rule_modes = ['replace', 'append']
|
467
|
+
if rule and (! rule_modes.include?(rule_mode))
|
468
|
+
raise "Invalid rule mode '#{rule_mode}'. Allowed: #{rule_modes}"
|
469
|
+
end
|
470
|
+
|
471
|
+
if rule
|
472
|
+
begin
|
473
|
+
rule_json = JSON.parse(rule)
|
474
|
+
rescue JSON::ParserError
|
475
|
+
raise "Syntax error in data supplied to --rule (must be valid JSON)"
|
476
|
+
end
|
477
|
+
|
478
|
+
if rule_mode == 'replace'
|
479
|
+
if group['rule'] != rule_json
|
480
|
+
group['rule'] = rule_json
|
481
|
+
rule_change = true
|
482
|
+
end
|
483
|
+
else
|
484
|
+
rule_change = ensure_rules(group, rule_json)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
rule_change
|
489
|
+
end
|
490
|
+
|
491
|
+
def self.classes(options)
|
492
|
+
group_name = options[:group_name]
|
493
|
+
class_name = options[:class_name]
|
494
|
+
param_name = options[:param_name]
|
495
|
+
param_value = options[:param_value]
|
496
|
+
delete_class = options[:delete_class]
|
497
|
+
delete_param = options[:delete_param]
|
498
|
+
rule = options[:rule]
|
499
|
+
rule_mode = options[:rule_mode]
|
500
|
+
smart_update = options[:smart_update]
|
501
|
+
|
502
|
+
rule_change = false
|
503
|
+
class_change = false
|
504
|
+
|
505
|
+
if group_name
|
506
|
+
group = nc_group(group_name)
|
507
|
+
else
|
508
|
+
raise "All operations require a valid group_name"
|
509
|
+
end
|
510
|
+
|
511
|
+
if class_name and delete_class
|
512
|
+
# delete a class from a group
|
513
|
+
Escort::Logger.output.puts "Deleting class #{class_name} from #{group_name}"
|
514
|
+
class_change = ensure_class(group, class_name, delete:true)
|
515
|
+
elsif class_name and param_name and delete_param
|
516
|
+
# delete a parameter from a class
|
517
|
+
Escort::Logger.output.puts "Deleting parameter #{param_name} on #{class_name} from #{group_name}"
|
518
|
+
class_change = ensure_class(group, class_name)
|
519
|
+
class_change |= ensure_param(group, class_name, param_name, nil, delete:true)
|
520
|
+
elsif class_name and param_name and param_value
|
521
|
+
# set a value inside a class
|
522
|
+
if smart_update
|
523
|
+
if ! is_r10k_param(class_name, param_name)
|
524
|
+
|
525
|
+
# not an R10K parameter, do an immediate update to make sure any classes
|
526
|
+
# we need are in-place
|
527
|
+
puppet_code_deploy
|
528
|
+
end
|
529
|
+
end
|
530
|
+
Escort::Logger.output.puts "Setting parameter #{param_name} to #{param_value} on #{class_name} in #{group_name}"
|
531
|
+
class_change = ensure_class(group, class_name)
|
532
|
+
class_change |= ensure_param(group, class_name, param_name, param_value)
|
533
|
+
elsif class_name
|
534
|
+
if smart_update
|
535
|
+
puppet_code_deploy
|
536
|
+
end
|
537
|
+
Escort::Logger.output.puts "Adding #{class_name} to #{group_name}"
|
538
|
+
class_change = ensure_class(group, class_name)
|
539
|
+
end
|
540
|
+
|
541
|
+
# process any rule changes separately since they are valid for all actions
|
542
|
+
rule_change = rule_change(group, rule, rule_mode)
|
543
|
+
|
544
|
+
# save changes
|
545
|
+
if class_change or rule_change
|
546
|
+
update_group(group_name, classes: group["classes"], rule: group["rule"])
|
547
|
+
else
|
548
|
+
Escort::Logger.output.puts "Already up-to-date"
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
def self.update_classes
|
553
|
+
if ! @puppetclassify
|
554
|
+
init
|
555
|
+
end
|
556
|
+
@puppet_https.delete("#{@puppet_url}/puppet-admin-api/v1/environment-cache")
|
557
|
+
@puppet_https.post("#{@rest_api_url}/v1/update-classes")
|
558
|
+
end
|
559
|
+
|
560
|
+
def self.puppet_code_deploy
|
561
|
+
Escort::Logger.output.puts "Running puppet and deploying code..."
|
562
|
+
|
563
|
+
# if puppet run fails, just deploy the code anyway as the fix needed might
|
564
|
+
# be in the update...
|
565
|
+
system("puppet agent -t ; puppet-code deploy --all --wait")
|
566
|
+
end
|
567
|
+
|
568
|
+
# Extract the r10k settings ONLY from the passed in data has and update
|
569
|
+
# NCAPI with them immediately
|
570
|
+
def self.apply_r10k_settings_now(data)
|
571
|
+
data.each { |group_name,opts|
|
572
|
+
group = nc_group(group_name)
|
573
|
+
|
574
|
+
if opts.key?("classes")
|
575
|
+
Escort::Logger.output.puts "Setting up rules for #{group_name} immediately"
|
576
|
+
if ensure_classes_and_params(group, opts["classes"])
|
577
|
+
update_group(group_name, classes: group["classes"])
|
578
|
+
end
|
579
|
+
end
|
580
|
+
}
|
581
|
+
end
|
582
|
+
|
583
|
+
# Check if this is an r10k parameter or not
|
584
|
+
#
|
585
|
+
# @return true if this is to do with R10K otherwise false
|
586
|
+
def self.is_r10k_param(class_name, param_name)
|
587
|
+
class_name == R10K_SETTINGS_CLASS and R10K_SETTINGS_PARAMS.include?(param_name)
|
588
|
+
end
|
589
|
+
|
590
|
+
# Evaluate whether change instructions contain R10K settings or not
|
591
|
+
#
|
592
|
+
# @param data Hash of data settings to look at
|
593
|
+
# @return false if not found otherwise hash of with just the settings that
|
594
|
+
# need to be applied immediately
|
595
|
+
def self.contains_r10k_settings(data)
|
596
|
+
found = {}
|
597
|
+
data.each { |group_name, opts|
|
598
|
+
if opts.key?("classes") and opts["classes"].key?(R10K_SETTINGS_CLASS)
|
599
|
+
opts["classes"][R10K_SETTINGS_CLASS].each { |param_name,param_value|
|
600
|
+
if is_r10k_param(R10K_SETTINGS_CLASS, param_name)
|
601
|
+
# make the hash structure we need
|
602
|
+
if ! found.key?(group_name)
|
603
|
+
found[group_name] = {}
|
604
|
+
end
|
605
|
+
|
606
|
+
if ! found[group_name].key?("classes")
|
607
|
+
found[group_name]["classes"] = {}
|
608
|
+
end
|
609
|
+
|
610
|
+
if ! found[group_name]["classes"].key?(R10K_SETTINGS_CLASS)
|
611
|
+
found[group_name]["classes"][R10K_SETTINGS_CLASS] = {}
|
612
|
+
end
|
613
|
+
|
614
|
+
found[group_name]["classes"][R10K_SETTINGS_CLASS][param_name] = param_value
|
615
|
+
end
|
616
|
+
}
|
617
|
+
end
|
618
|
+
}
|
619
|
+
|
620
|
+
# Return the found elements if there were any, otherwise simplify to false
|
621
|
+
! found.empty? ? found : false
|
622
|
+
end
|
623
|
+
|
624
|
+
def self.groups(options)
|
625
|
+
group_name = options[:group_name]
|
626
|
+
environment = options[:environment]
|
627
|
+
environment_trumps = options[:environment_trumps]
|
628
|
+
rule = options[:rule]
|
629
|
+
rule_mode = options[:rule_mode]
|
630
|
+
|
631
|
+
# step 1: create the group with the parent "All Environments"
|
632
|
+
nc_group(group_name, parent_name: "All Environments")
|
633
|
+
|
634
|
+
# step 2: set the environment + environment trumps
|
635
|
+
update_group(
|
636
|
+
group_name,
|
637
|
+
environment: environment,
|
638
|
+
environment_trumps: environment_trumps,
|
639
|
+
)
|
640
|
+
|
641
|
+
# step 3: set the rules - separate step because rule_mode needs special
|
642
|
+
# handling
|
643
|
+
group = nc_group(group_name)
|
644
|
+
rule_change = rule_change(group, rule, rule_mode)
|
645
|
+
if rule_change
|
646
|
+
update_group(group_name, rule: group["rule"])
|
647
|
+
end
|
648
|
+
end
|
374
649
|
end
|
375
650
|
end
|
data/lib/ncedit/version.rb
CHANGED
data/ncedit.gemspec
CHANGED
@@ -6,12 +6,13 @@ require 'ncedit/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "ncedit"
|
8
8
|
spec.version = NCEdit::VERSION
|
9
|
-
spec.authors = ["
|
10
|
-
spec.email = ["
|
9
|
+
spec.authors = ["Declarative Systems"]
|
10
|
+
spec.email = ["sales@declarativesystems.com"]
|
11
|
+
spec.license = "Apache-2.0"
|
11
12
|
|
12
13
|
spec.summary = %q{Edit Puppet Enterprise Node Classifier rules}
|
13
14
|
spec.description = %q{Use the puppet-classify gem to create/edit NC rules}
|
14
|
-
spec.homepage = "https://github.com/
|
15
|
+
spec.homepage = "https://github.com/declarativesystems/ncedit"
|
15
16
|
|
16
17
|
|
17
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
@@ -21,11 +22,11 @@ Gem::Specification.new do |spec|
|
|
21
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
23
|
spec.require_paths = ["lib"]
|
23
24
|
|
24
|
-
spec.add_development_dependency "bundler", "~> 1
|
25
|
-
spec.add_development_dependency "rake", "~>
|
26
|
-
spec.add_development_dependency "rspec", "~> 3.
|
25
|
+
spec.add_development_dependency "bundler", "~> 2.1"
|
26
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.9"
|
27
28
|
|
28
29
|
spec.add_runtime_dependency "escort", "0.4.0"
|
29
|
-
spec.add_runtime_dependency "
|
30
|
-
spec.add_runtime_dependency "puppetclassify", "0.1.
|
30
|
+
spec.add_runtime_dependency "json_pure", "2.3.0"
|
31
|
+
spec.add_runtime_dependency "puppetclassify", "0.1.8"
|
31
32
|
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.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Declarative Systems
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,42 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
19
|
+
version: '2.1'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
26
|
+
version: '2.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '3.
|
47
|
+
version: '3.9'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
54
|
+
version: '3.9'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: escort
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,36 +67,36 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 0.4.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: json_pure
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - '='
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 2.0
|
75
|
+
version: 2.3.0
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - '='
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 2.0
|
82
|
+
version: 2.3.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: puppetclassify
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - '='
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.1.
|
89
|
+
version: 0.1.8
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - '='
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.1.
|
96
|
+
version: 0.1.8
|
97
97
|
description: Use the puppet-classify gem to create/edit NC rules
|
98
98
|
email:
|
99
|
-
-
|
99
|
+
- sales@declarativesystems.com
|
100
100
|
executables:
|
101
101
|
- ncedit
|
102
102
|
extensions: []
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- ".travis.yml"
|
108
108
|
- Gemfile
|
109
109
|
- LICENSE
|
110
|
+
- Makefile
|
110
111
|
- README.md
|
111
112
|
- Rakefile
|
112
113
|
- bin/console
|
@@ -118,8 +119,9 @@ files:
|
|
118
119
|
- lib/ncedit/cmd.rb
|
119
120
|
- lib/ncedit/version.rb
|
120
121
|
- ncedit.gemspec
|
121
|
-
homepage: https://github.com/
|
122
|
-
licenses:
|
122
|
+
homepage: https://github.com/declarativesystems/ncedit
|
123
|
+
licenses:
|
124
|
+
- Apache-2.0
|
123
125
|
metadata: {}
|
124
126
|
post_install_message:
|
125
127
|
rdoc_options: []
|
@@ -136,8 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
138
|
- !ruby/object:Gem::Version
|
137
139
|
version: '0'
|
138
140
|
requirements: []
|
139
|
-
|
140
|
-
rubygems_version: 2.5.2
|
141
|
+
rubygems_version: 3.0.3
|
141
142
|
signing_key:
|
142
143
|
specification_version: 4
|
143
144
|
summary: Edit Puppet Enterprise Node Classifier rules
|