bolt 0.16.3 → 0.16.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a4e9a05003d30c06467737a20e98334060a3954
4
- data.tar.gz: ed19ba7afaab8ea3ff68b2dd5f53bc2830e7b6db
3
+ metadata.gz: dabc4d0d53c4dc91e19ca332cc074c70026d9934
4
+ data.tar.gz: e4bd3c566ddb2cb37724de78063c62eafc4e2fef
5
5
  SHA512:
6
- metadata.gz: 6836a6e4a272e4a95e3152b011cfc49af3fd1cc290eaf5660b241b5e2a96784d8b80e0b7c6dcd1ac3bca0452e9b53e10ffe1b1db2b7c9398f34f762f1a50585f
7
- data.tar.gz: deb82053d48301c05eb9c51eda38232e8baf9f211c720d45c657b91cedf0cce3fdd8fab0cfd3e957ee667e7b18461a4033925b3e3f7fe3a0ba92f6bf460d833d
6
+ metadata.gz: 579ed3e93ab4dc82d5ded1dedac8f42793217a6832360c13b8594cab22e5b2a6f6222ecb2a280bb7d6024f93374e3caca9037350c6dc05e80856202be813f84c
7
+ data.tar.gz: 25bff2cb74e014d109f3de109a1bf330a169367a528ffa40b29246020ca905578e90d0c4db08334455cb0d1b2a7ad98e1b8039fce5393546feb0de6947c85528
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bolt_ext/puppetdb_inventory'
4
+
5
+ exitcode = Bolt::PuppetDBInventory::CLI.new(ARGV).run
6
+ exit exitcode
@@ -84,7 +84,7 @@ module Bolt
84
84
  next if promise.fulfilled?
85
85
  error = $ERROR_INFO || Bolt::Error.new("No result was returned for #{target.uri}",
86
86
  "puppetlabs.bolt/missing-result-error")
87
- promise.set(Bolt::Result.from_exception(error))
87
+ promise.set(Bolt::Result.from_exception(target, error))
88
88
  end
89
89
  end
90
90
  end
@@ -91,9 +91,9 @@ module Bolt
91
91
  # Pass a target to get_targets for a public version of this
92
92
  # Should this reconfigure configured targets?
93
93
  def update_target(target)
94
- inv_conf = config_for(target.host)
94
+ inv_conf = config_for(target.name)
95
95
  unless inv_conf
96
- @logger.debug("Did not find #{target.host} in inventory")
96
+ @logger.debug("Did not find #{target.name} in inventory")
97
97
  inv_conf = {}
98
98
  end
99
99
 
@@ -6,19 +6,21 @@ module Bolt
6
6
  attr_accessor :name, :nodes, :groups, :config, :rest
7
7
 
8
8
  def initialize(data)
9
+ @logger = Logging.logger[self]
9
10
  @name = data['name']
10
11
 
11
- @nodes = if data['nodes']
12
- data['nodes'].map do |n|
13
- if n.is_a? String
14
- { 'name' => n }
15
- else
16
- n
17
- end
18
- end
19
- else
20
- []
21
- end
12
+ @nodes = {}
13
+ if data['nodes']
14
+ data['nodes'].each do |n|
15
+ n = { 'name' => n } if n.is_a? String
16
+ if @nodes.include? n['name']
17
+ @logger.warn("Ignoring duplicate node in #{@name}: #{n}")
18
+ else
19
+ @nodes[n['name']] = n
20
+ end
21
+ end
22
+ end
23
+
22
24
  @config = data['config'] || {}
23
25
  @groups = if data['groups']
24
26
  data['groups'].map { |g| Group.new(g) }
@@ -44,11 +46,12 @@ module Bolt
44
46
 
45
47
  used_names << @name
46
48
 
47
- @nodes.each do |n|
48
- # Require nodes to be referenced only by their host name
49
- host = Addressable::URI.parse('//' + n['name']).host
50
- ipv6host = Addressable::URI.parse('//[' + n['name'] + ']').host
51
- if n['name'] != host && n['name'] != ipv6host
49
+ @nodes.each_value do |n|
50
+ # Require nodes to be parseable as a Target.
51
+ begin
52
+ Target.new(n['name'])
53
+ rescue Addressable::URI::InvalidURIError => e
54
+ @logger.debug(e)
52
55
  raise ValidationError.new("Invalid node name #{n['name']}", n['name'])
53
56
  end
54
57
 
@@ -80,7 +83,7 @@ module Bolt
80
83
  end
81
84
 
82
85
  def node_data(node_name)
83
- if (data = @nodes.find { |n| n['name'] == node_name })
86
+ if (data = @nodes[node_name])
84
87
  { 'config' => data['config'] || {},
85
88
  # groups come from group_data
86
89
  'groups' => [] }
@@ -123,7 +126,7 @@ module Bolt
123
126
  end
124
127
 
125
128
  def local_node_names
126
- @_node_names ||= Set.new(nodes.map { |n| n['name'] })
129
+ Set.new(@nodes.keys)
127
130
  end
128
131
  private :local_node_names
129
132
 
@@ -149,7 +152,7 @@ module Bolt
149
152
 
150
153
  if data
151
154
  data_merge(group_data, data)
152
- elsif local_node_names.include?(node_name)
155
+ elsif @nodes.include?(node_name)
153
156
  group_data
154
157
  end
155
158
  end
@@ -11,15 +11,18 @@ module Bolt
11
11
  CONF_FILE = File.expand_path('~/.puppetlabs/client-tools/orchestrator.conf')
12
12
  BOLT_MOCK_TASK = Struct.new(:name, :executable).new('bolt', 'bolt/tasks/init').freeze
13
13
 
14
- def initialize(config)
15
- super
16
-
14
+ def create_client(opts)
17
15
  client_keys = %i[service-url token-file cacert]
18
- @client_opts = config.select { |k, _v| client_keys.include?(k) }
19
- end
16
+ client_opts = opts.reduce({}) do |acc, (k, v)|
17
+ if client_keys.include?(k)
18
+ acc.merge(k.to_s => v)
19
+ else
20
+ acc
21
+ end
22
+ end
23
+ @logger.debug("Creating orchestrator client for #{client_opts}")
20
24
 
21
- def create_client
22
- OrchestratorClient.new(@client_opts, true)
25
+ OrchestratorClient.new(client_opts, true)
23
26
  end
24
27
 
25
28
  def build_request(targets, task, arguments)
@@ -113,7 +116,11 @@ module Bolt
113
116
  end
114
117
 
115
118
  def batches(targets)
116
- targets.group_by { |target| target.options[:orch_task_environment] }.values
119
+ targets.group_by do |target|
120
+ [target.options[:orch_task_environment],
121
+ target.options[:"service-url"],
122
+ target.options[:"token-file"]]
123
+ end.values
117
124
  end
118
125
 
119
126
  def run_task_job(targets, task, arguments)
@@ -124,9 +131,13 @@ module Bolt
124
131
  end
125
132
 
126
133
  begin
127
- results = create_client.run_task(body)
134
+ results = create_client(targets.first.options).run_task(body)
128
135
 
129
136
  process_run_results(targets, results)
137
+ rescue OrchestratorClient::ApiError => e
138
+ targets.map do |target|
139
+ Bolt::Result.new(target, error: e.data)
140
+ end
130
141
  rescue StandardError => e
131
142
  targets.map do |target|
132
143
  Bolt::Result.from_exception(target, e)
@@ -1,3 +1,3 @@
1
1
  module Bolt
2
- VERSION = '0.16.3'.freeze
2
+ VERSION = '0.16.4'.freeze
3
3
  end
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'httpclient'
5
+ require 'optparse'
6
+ require 'yaml'
7
+
8
+ module Bolt
9
+ class PuppetDBInventory
10
+ class Client
11
+ def self.from_config(config)
12
+ uri = URI.parse(config['server_urls'].first)
13
+ uri.port ||= 8081
14
+
15
+ cacert = File.expand_path(config['cacert'])
16
+ token = config.token
17
+
18
+ cert = config['cert']
19
+ key = config['key']
20
+
21
+ new(uri, cacert, token: token, cert: cert, key: key)
22
+ end
23
+
24
+ def initialize(uri, cacert, token: nil, cert: nil, key: nil)
25
+ @uri = uri
26
+ @cacert = cacert
27
+ @token = token
28
+ @cert = cert
29
+ @key = key
30
+ end
31
+
32
+ def query_certnames(query)
33
+ return [] unless query
34
+
35
+ body = JSON.generate(query: query)
36
+
37
+ response = http_client.post("#{@uri}/pdb/query/v4", body: body, header: headers)
38
+ if response.code != 200
39
+ raise "Failed to query PuppetDB: #{response.body}"
40
+ else
41
+ results = JSON.parse(response.body)
42
+ if results.first && !results.first.key?('certname')
43
+ raise "Query results did not contain a 'certname' field: got #{results.first.keys.join(', ')}"
44
+ end
45
+ results.map { |result| result['certname'] }.uniq
46
+ end
47
+ end
48
+
49
+ def http_client
50
+ return @http if @http
51
+ @http = HTTPClient.new
52
+ @http.ssl_config.set_client_cert_file(@cert, @key)
53
+ @http.ssl_config.add_trust_ca(@cacert)
54
+
55
+ @http
56
+ end
57
+
58
+ def headers
59
+ headers = { 'Content-Type' => 'application/json' }
60
+ headers['X-Authentication'] = @token if @token
61
+ headers
62
+ end
63
+ end
64
+
65
+ class Config
66
+ DEFAULT_TOKEN = File.expand_path('~/.puppetlabs/token')
67
+ DEFAULT_CONFIG = File.expand_path('~/.puppetlabs/client-tools/puppetdb.conf')
68
+
69
+ def initialize(config_file, options)
70
+ @settings = load_config(config_file)
71
+ @settings.merge!(options)
72
+
73
+ expand_paths
74
+ validate
75
+ end
76
+
77
+ def load_config(filename)
78
+ if filename
79
+ if File.exist?(filename)
80
+ config = JSON.parse(File.read(filename))
81
+ else
82
+ raise "config file #{filename} does not exist"
83
+ end
84
+ elsif File.exist?(DEFAULT_CONFIG)
85
+ config = JSON.parse(File.read(DEFAULT_CONFIG))
86
+ end
87
+ config.fetch('puppetdb', {})
88
+ end
89
+
90
+ def token
91
+ return @token if @token
92
+ if @settings['token']
93
+ File.read(@settings['token'])
94
+ elsif File.exist?(DEFAULT_TOKEN)
95
+ File.read(DEFAULT_TOKEN)
96
+ end
97
+ end
98
+
99
+ def [](key)
100
+ @settings[key]
101
+ end
102
+
103
+ def expand_paths
104
+ %w[cacert cert key token].each do |file|
105
+ @settings[file] = File.expand_path(@settings[file]) if @settings[file]
106
+ end
107
+ end
108
+
109
+ def validate_file_exists(file)
110
+ if @settings[file] && !File.exist?(@settings[file])
111
+ raise "#{file} file #{@settings[file]} does not exist"
112
+ end
113
+ end
114
+
115
+ def validate
116
+ unless @settings['server_urls']
117
+ raise "server_urls must be specified in the config file or with --url"
118
+ end
119
+ unless @settings['cacert']
120
+ raise "cacert must be specified in the config file or with --cacert"
121
+ end
122
+
123
+ if (@settings['cert'] && !@settings['key']) ||
124
+ (!@settings['cert'] && @settings['key'])
125
+ raise "cert and key must be specified together"
126
+ end
127
+
128
+ validate_file_exists('cacert')
129
+ validate_file_exists('cert')
130
+ validate_file_exists('key')
131
+ end
132
+ end
133
+
134
+ class CLI
135
+ def initialize(args)
136
+ @args = args
137
+ @cli_opts = {}
138
+ @parser = build_parser
139
+ end
140
+
141
+ def build_parser
142
+ parser = OptionParser.new('') do |opts|
143
+ opts.on('--cacert CACERT', "Path to the CA certificate") do |cacert|
144
+ @cli_opts['cacert'] = cacert
145
+ end
146
+ opts.on('--cert CERT', "Path to the certificate") do |cert|
147
+ @cli_opts['cert'] = cert
148
+ end
149
+ opts.on('--key KEY', "Path to the private key") do |key|
150
+ @cli_opts['key'] = key
151
+ end
152
+ opts.on('--token-file TOKEN',
153
+ "Path to the token file",
154
+ "Default: #{Config::DEFAULT_TOKEN} if present") do |token|
155
+ @cli_opts['token'] = token
156
+ end
157
+ opts.on('--url URL', "The URL of the PuppetDB server to connect to") do |url|
158
+ @cli_opts['server_urls'] = [url]
159
+ end
160
+ opts.on('--config CONFIG',
161
+ "The puppetdb.conf file to read configuration from",
162
+ "Default: #{Config::DEFAULT_CONFIG} if present") do |file|
163
+ @config_file = File.expand_path(file)
164
+ end
165
+ opts.on('--output FILE', '-o FILE',
166
+ "Where to write the generated inventory file, defaults to stdout") do |file|
167
+ @output_file = file
168
+ end
169
+ opts.on('--trace', "Show stacktraces for exceptions") do |trace|
170
+ @trace = trace
171
+ end
172
+ opts.on('-h', '--help', "Display help") do |_|
173
+ @show_help = true
174
+ end
175
+ end
176
+ parser.banner = <<-BANNER
177
+ Usage: bolt-inventory-pdb <input-file> [--output <output-file>] [--url <url>] [auth-options]
178
+
179
+ Populate the nodes in an inventory file based on PuppetDB queries.
180
+
181
+ The input file should be a Bolt inventory file, where each 'nodes' entry is
182
+ replaced with a 'query' entry to be executed against PuppetDB. The output will
183
+ be the input file, with the 'nodes' entry for each group populated with the
184
+ query results.
185
+
186
+ BANNER
187
+ parser
188
+ end
189
+
190
+ def run
191
+ positional_args = @parser.permute(@args)
192
+
193
+ if @show_help
194
+ puts @parser.help
195
+ return 0
196
+ end
197
+
198
+ inventory_file = positional_args.shift
199
+ unless inventory_file
200
+ raise "--inventory is a required option"
201
+ end
202
+
203
+ if positional_args.any?
204
+ raise "Unknown argument(s) #{positional_args.join(', ')}"
205
+ end
206
+
207
+ config = Config.new(@config_file, @cli_opts)
208
+ @puppetdb_client = Client.from_config(config)
209
+
210
+ unless File.readable?(inventory_file)
211
+ raise "Can't read the inventory file #{inventory_file}"
212
+ end
213
+
214
+ inventory = YAML.load_file(inventory_file)
215
+ resolve_group(inventory)
216
+
217
+ result = inventory.to_yaml
218
+
219
+ if @output_file
220
+ File.write(@output_file, result)
221
+ else
222
+ puts result
223
+ end
224
+
225
+ return 0
226
+ rescue StandardError => e
227
+ puts "Error: #{e}"
228
+ puts e.backtrace if @trace
229
+ return 1
230
+ end
231
+
232
+ def resolve_group(group)
233
+ group['nodes'] = @puppetdb_client.query_certnames(group['query'])
234
+
235
+ group.fetch('groups', []).each do |child|
236
+ resolve_group(child)
237
+ end
238
+
239
+ group
240
+ end
241
+ end
242
+ end
243
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.3
4
+ version: 0.16.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-21 00:00:00.000000000 Z
11
+ date: 2018-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.2.1
75
+ version: 0.2.3
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: 0.2.1
82
+ version: 0.2.3
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: terminal-table
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -329,6 +329,7 @@ email:
329
329
  - puppet@puppet.com
330
330
  executables:
331
331
  - bolt
332
+ - bolt-inventory-pdb
332
333
  extensions: []
333
334
  extra_rdoc_files: []
334
335
  files:
@@ -344,6 +345,7 @@ files:
344
345
  - bolt-modules/boltlib/lib/puppet/functions/run_task.rb
345
346
  - bolt-modules/boltlib/types/targetspec.pp
346
347
  - exe/bolt
348
+ - exe/bolt-inventory-pdb
347
349
  - lib/bolt.rb
348
350
  - lib/bolt/cli.rb
349
351
  - lib/bolt/config.rb
@@ -370,6 +372,7 @@ files:
370
372
  - lib/bolt/transport/winrm/connection.rb
371
373
  - lib/bolt/util.rb
372
374
  - lib/bolt/version.rb
375
+ - lib/bolt_ext/puppetdb_inventory.rb
373
376
  - vendored/facter/lib/facter.rb
374
377
  - vendored/facter/lib/facter/Cfkey.rb
375
378
  - vendored/facter/lib/facter/application.rb