bolt 1.11.0 → 1.12.0

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
  SHA256:
3
- metadata.gz: 29c062ea69471f56b51b9cef6ea2a3ba820773161753a13145879b7b941d2a32
4
- data.tar.gz: 0f237d5aa389dee5e8a908f4fd52268d9fd397af25894a25adeeff0bc96d2ccc
3
+ metadata.gz: 05e284722c10c61a2fe08a01b920b18739b8cc31486762db9dfadba82288f6d6
4
+ data.tar.gz: 59e57d3df534fcbae81f2f5f44000e1f1930aec4029c8d664696e156a715a922
5
5
  SHA512:
6
- metadata.gz: 79a0c7e11a5998175df643ed639f197f3e05c78895713562b220338b8c0df5f2fa1a506d5d4a7e063b38e314d4c046816cf7d4fd084296f313b3193b04b91c12
7
- data.tar.gz: b92c3bb6f4e0fcc95895d22907c7c98e7cf5b4fef4385354dfd6a53c09817306d6d99aed1266beb1c0a65cbfb804567599c29c69a08790c3ba1d04ac5e3c7392
6
+ metadata.gz: ea83de65334823f418383868b21cb5f23f501e11fbb525e5d9b9ebc8fac9f818322f56e4069f2697e9de48ce5631a349e5673cf16c0b5437e0f4799e654c6b39
7
+ data.tar.gz: b1d95304ae24c3f792ee9fecf9fe42073efc73f7fbd309654f78112886e5e55a2541913e1a1aa705c8e123113f53ba136f18731b39344d3b9bdd0ac31a96f496
@@ -32,6 +32,7 @@ Available subcommands:
32
32
  bolt plan run <plan> [params] Run a Puppet task plan
33
33
  bolt apply <manifest> Apply Puppet manifest code
34
34
  bolt puppetfile install Install modules from a Puppetfile into a Boltdir
35
+ bolt puppetfile show-modules List modules available to Bolt
35
36
 
36
37
  Run `bolt <subcommand> --help` to view specific examples.
37
38
 
@@ -101,6 +102,7 @@ Usage: bolt puppetfile <action> [options]
101
102
 
102
103
  Available actions are:
103
104
  install Install modules from a Puppetfile into a Boltdir
105
+ show-modules List modules available to Bolt
104
106
 
105
107
  Install modules into the local Boltdir
106
108
  bolt puppetfile install
@@ -33,7 +33,7 @@ module Bolt
33
33
  @path = Pathname.new(path).expand_path
34
34
  @config_file = @path + 'bolt.yaml'
35
35
  @inventory_file = @path + 'inventory.yaml'
36
- @modulepath = [(@path + 'modules').to_s, (@path + 'site').to_s]
36
+ @modulepath = [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
37
37
  @hiera_config = @path + 'hiera.yaml'
38
38
  @puppetfile = @path + 'Puppetfile'
39
39
  end
@@ -30,7 +30,7 @@ module Bolt
30
30
  'task' => %w[show run],
31
31
  'plan' => %w[show run],
32
32
  'file' => %w[upload],
33
- 'puppetfile' => %w[install],
33
+ 'puppetfile' => %w[install show-modules],
34
34
  'apply' => %w[] }.freeze
35
35
 
36
36
  attr_reader :config, :options
@@ -259,6 +259,9 @@ module Bolt
259
259
  end
260
260
  end
261
261
  return 0
262
+ elsif options[:action] == 'show-modules'
263
+ list_modules
264
+ return 0
262
265
  end
263
266
 
264
267
  message = 'There may be processes left executing on some nodes.'
@@ -400,6 +403,10 @@ module Bolt
400
403
  results.ok ? 0 : 1
401
404
  end
402
405
 
406
+ def list_modules
407
+ outputter.print_module_list(pal.list_modules)
408
+ end
409
+
403
410
  def install_puppetfile(puppetfile, modulepath)
404
411
  require 'r10k/action/puppetfile/install'
405
412
  require 'bolt/r10k_log_proxy'
@@ -191,6 +191,34 @@ module Bolt
191
191
  "details and parameters for a specific plan.")
192
192
  end
193
193
 
194
+ def print_module_list(module_list)
195
+ module_list.each do |path, modules|
196
+ if (mod = modules.find { |m| m[:internal_module_group] })
197
+ @stream.puts(mod[:internal_module_group])
198
+ else
199
+ @stream.puts(path)
200
+ end
201
+
202
+ if modules.empty?
203
+ @stream.puts('(no modules installed)')
204
+ else
205
+ module_info = modules.map do |m|
206
+ version = if m[:version].nil?
207
+ m[:internal_module_group].nil? ? '(no metadata)' : '(built-in)'
208
+ else
209
+ m[:version]
210
+ end
211
+
212
+ [m[:name], version]
213
+ end
214
+
215
+ print_table(module_info)
216
+ end
217
+
218
+ @stream.write("\n")
219
+ end
220
+ end
221
+
194
222
  # @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
195
223
  def print_apply_result(apply_result)
196
224
  apply_result.each { |result| print_result(result) }
@@ -42,6 +42,7 @@ module Bolt
42
42
  def print_table(results)
43
43
  @stream.puts results.to_json
44
44
  end
45
+ alias print_module_list print_table
45
46
 
46
47
  def print_task_info(task)
47
48
  path = task['files'][0]['path'].chomp("/tasks/#{task['files'][0]['name']}")
@@ -284,6 +284,36 @@ module Bolt
284
284
  plan_info
285
285
  end
286
286
 
287
+ # Returns a mapping of all modules available to the Bolt compiler
288
+ #
289
+ # @return [Hash{String => Array<Hash{Symbol => String,nil}>}]
290
+ # A hash that associates each directory on the module path with an array
291
+ # containing a hash of information for each module in that directory.
292
+ # The information hash provides the name, version, and a string
293
+ # indicating whether the module belongs to an internal module group.
294
+ def list_modules
295
+ internal_module_groups = { BOLTLIB_PATH => 'Plan Language Modules',
296
+ MODULES_PATH => 'Packaged Modules' }
297
+
298
+ in_bolt_compiler do
299
+ # NOTE: Can replace map+to_h with transform_values when Ruby 2.4
300
+ # is the minimum supported version.
301
+ Puppet.lookup(:current_environment).modules_by_path.map do |path, modules|
302
+ module_group = internal_module_groups[path]
303
+
304
+ values = modules.map do |mod|
305
+ mod_info = { name: (mod.forge_name || mod.name),
306
+ version: mod.version }
307
+ mod_info[:internal_module_group] = module_group unless module_group.nil?
308
+
309
+ mod_info
310
+ end
311
+
312
+ [path, values]
313
+ end.to_h
314
+ end
315
+ end
316
+
287
317
  def run_task(task_name, targets, params, executor, inventory, description = nil, &eventblock)
288
318
  in_task_compiler(executor, inventory) do |compiler|
289
319
  params = params.merge('_bolt_api_call' => true)
@@ -58,7 +58,8 @@ module Bolt
58
58
  # If we're using the default port with SSL, a timeout probably means the
59
59
  # host doesn't support SSL.
60
60
  if target.options['ssl'] && @port == HTTPS_PORT
61
- the_problem = "\nUse --no-ssl if this host isn't configured to use SSL for WinRM"
61
+ the_problem = "\nVerify that required WinRM ports are open, " \
62
+ "or use --no-ssl if this host isn't configured to use SSL for WinRM."
62
63
  end
63
64
  raise Bolt::Node::ConnectError.new(
64
65
  "Timeout after #{target.options['connect-timeout']} seconds connecting to #{endpoint}#{the_problem}",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.11.0'
4
+ VERSION = '1.12.0'
5
5
  end
@@ -50,19 +50,39 @@
50
50
  "properties": {
51
51
  "uri": {
52
52
  "type": "object",
53
- "description": "Where is the file"
53
+ "description": "Information on how to request the file contents",
54
+ "properties": {
55
+ "path": {
56
+ "description": "Relative URI for accessing task contents",
57
+ "type": "string"
58
+ },
59
+ "params": {
60
+ "description": "Map of query params when requesting task content",
61
+ "type": "object",
62
+ "properties": {
63
+ "environment": {
64
+ "description": "Environment the task is in",
65
+ "type": "string"
66
+ }
67
+ },
68
+ "required": ["environment"],
69
+ "additionalProperties": true
70
+ }
71
+ },
72
+ "required": ["path", "params"],
73
+ "additionalProperties": true
54
74
  },
55
75
  "sha256": {
56
76
  "type": "string",
57
- "description": "checksum of file"
77
+ "description": "Checksum of file"
58
78
  },
59
79
  "filename": {
60
80
  "type": "string",
61
- "description": "Name of file"
81
+ "description": "Name of file including extension"
62
82
  },
63
83
  "size": {
64
84
  "type": "number",
65
- "description": "Size of file"
85
+ "description": "Size of file in bytes"
66
86
  }
67
87
  }
68
88
  },
@@ -11,36 +11,35 @@ require 'bolt/util'
11
11
  # This is intended to provide a relatively stable method of executing bolt in process from tests.
12
12
  module BoltSpec
13
13
  module Run
14
- def run_task(task_name, targets, params = nil, config: nil, inventory: nil)
14
+ def run_task(task_name, targets, params, config: nil, inventory: nil)
15
15
  result = BoltRunner.with_runner(config, inventory) do |runner|
16
- runner.run_task(task_name, targets, params || {})
16
+ runner.run_task(task_name, targets, params)
17
17
  end
18
18
  result = result.to_a
19
19
  Bolt::Util.walk_keys(result, &:to_s)
20
20
  end
21
21
 
22
- def run_plan(plan_name, params = nil, config: nil, inventory: nil)
22
+ def run_plan(plan_name, params, config: nil, inventory: nil)
23
23
  # Users copying code from run_task may forget that targets is not a parameter for run plan
24
- params ||= {}
25
24
  raise ArgumentError, "params must be a hash" unless params.is_a?(Hash)
26
25
 
27
26
  result = BoltRunner.with_runner(config, inventory) do |runner|
28
- runner.run_plan(plan_name, params || {})
27
+ runner.run_plan(plan_name, params)
29
28
  end
30
29
 
31
30
  { "status" => result.status,
32
31
  "value" => JSON.parse(result.value.to_json) }
33
32
  end
34
33
 
35
- def run_command(command, targets, params = nil, config: nil, inventory: nil)
34
+ def run_command(command, targets, options: {}, config: nil, inventory: nil)
36
35
  result = BoltRunner.with_runner(config, inventory) do |runner|
37
- runner.run_command(command, targets, params)
36
+ runner.run_command(command, targets, options)
38
37
  end
39
38
  result = result.to_a
40
39
  Bolt::Util.walk_keys(result, &:to_s)
41
40
  end
42
41
 
43
- def run_script(script, targets, arguments = nil, options = {}, config: nil, inventory: nil)
42
+ def run_script(script, targets, arguments, options: {}, config: nil, inventory: nil)
44
43
  result = BoltRunner.with_runner(config, inventory) do |runner|
45
44
  runner.run_script(script, targets, arguments, options)
46
45
  end
@@ -116,13 +115,13 @@ module BoltSpec
116
115
  pal.run_plan(plan_name, params, executor, inventory, puppetdb_client)
117
116
  end
118
117
 
119
- def run_command(command, targets, params = nil)
118
+ def run_command(command, targets, options)
120
119
  executor = Bolt::Executor.new(config.concurrency, @analytics)
121
120
  targets = inventory.get_targets(targets)
122
- executor.run_command(targets, command, params || {})
121
+ executor.run_command(targets, command, options)
123
122
  end
124
123
 
125
- def run_script(script, targets, arguments = nil, options = {})
124
+ def run_script(script, targets, arguments, options = {})
126
125
  executor = Bolt::Executor.new(config.concurrency, @analytics)
127
126
  targets = inventory.get_targets(targets)
128
127
  executor.run_script(targets, script, arguments, options)
@@ -28,10 +28,7 @@ module PlanExecutor
28
28
  end
29
29
 
30
30
  def initialize(config)
31
- conn_opts = { 'service-url' => config['orchestrator-url'],
32
- 'cacert' => config['ssl-ca-cert'],
33
- 'User-Agent' => "Bolt/#{Bolt::VERSION}" }
34
- @client = OrchestratorClient.new(conn_opts, false)
31
+ @http_client = create_http(config)
35
32
 
36
33
  # Use an empty inventory until we figure out where this data comes from.
37
34
  @inventory = Bolt::Inventory.new(nil)
@@ -49,6 +46,15 @@ module PlanExecutor
49
46
  super(nil)
50
47
  end
51
48
 
49
+ def create_http(config)
50
+ base_url = config['orchestrator-url'].chomp('/') + '/orchestrator/v1/'
51
+ agent_name = "Bolt/#{Bolt::VERSION}"
52
+ http = JSONClient.new(base_url: base_url, agent_name: agent_name)
53
+ http.ssl_config.set_client_cert_file(config['ssl-cert'], config['ssl-key'])
54
+ http.ssl_config.add_trust_ca(config['ssl-ca-cert'])
55
+ http
56
+ end
57
+
52
58
  def validate_schema(schema, body)
53
59
  schema_error = JSON::Validator.fully_validate(schema, body)
54
60
  if schema_error.any?
@@ -87,7 +93,7 @@ module PlanExecutor
87
93
  # Errors if plan is not found
88
94
  @pal.get_plan_info(name)
89
95
 
90
- executor = PlanExecutor::Executor.new(body['job_id'], @client)
96
+ executor = PlanExecutor::Executor.new(body['job_id'], @http_client)
91
97
  applicator = PlanExecutor::Applicator.new(@inventory, executor, nil)
92
98
  params = body['params']
93
99
  # This provides a wait function, which promise doesn't
@@ -14,14 +14,14 @@ require 'plan_executor/orch_client'
14
14
  module PlanExecutor
15
15
  class Executor
16
16
  attr_reader :noop, :logger
17
- attr_accessor :transport
17
+ attr_accessor :orch_client
18
18
 
19
- def initialize(job_id, client, noop = nil)
19
+ def initialize(job_id, http_client, noop = nil)
20
20
  @logger = Logging.logger[self]
21
21
  @plan_logging = false
22
22
  @noop = noop
23
23
  @logger.debug { "Started" }
24
- @transport = PlanExecutor::OrchClient.new(job_id, client, @logger)
24
+ @orch_client = PlanExecutor::OrchClient.new(job_id, http_client, @logger)
25
25
  end
26
26
 
27
27
  # This handles running the job, catching errors, and turning the result
@@ -82,7 +82,7 @@ module PlanExecutor
82
82
  description = options.fetch('_description', "command '#{command}'")
83
83
  log_action(description, targets) do
84
84
  results = as_resultset(targets) do
85
- @transport.run_command(targets, command, options)
85
+ @orch_client.run_command(targets, command, options)
86
86
  end
87
87
 
88
88
  results
@@ -93,7 +93,7 @@ module PlanExecutor
93
93
  description = options.fetch('_description', "script #{script}")
94
94
  log_action(description, targets) do
95
95
  results = as_resultset(targets) do
96
- @transport.run_script(targets, script, arguments, options)
96
+ @orch_client.run_script(targets, script, arguments, options)
97
97
  end
98
98
 
99
99
  results
@@ -106,7 +106,7 @@ module PlanExecutor
106
106
  arguments['_task'] = task.name
107
107
 
108
108
  results = as_resultset(targets) do
109
- @transport.run_task(targets, task, arguments, options)
109
+ @orch_client.run_task(targets, task, arguments, options)
110
110
  end
111
111
 
112
112
  results
@@ -117,7 +117,7 @@ module PlanExecutor
117
117
  description = options.fetch('_description', "file upload from #{source} to #{destination}")
118
118
  log_action(description, targets) do
119
119
  results = as_resultset(targets) do
120
- @transport.file_upload(targets, source, destination, options)
120
+ @orch_client.file_upload(targets, source, destination, options)
121
121
  end
122
122
 
123
123
  results
@@ -132,7 +132,7 @@ module PlanExecutor
132
132
  retry_interval: 1)
133
133
  log_action(description, targets) do
134
134
  begin
135
- wait_until(wait_time, retry_interval) { @transport.connected?(targets) }
135
+ wait_until(wait_time, retry_interval) { @orch_client.connected?(targets) }
136
136
  targets.map { |target| Bolt::Result.new(target) }
137
137
  rescue TimeoutError => e
138
138
  targets.map { |target| Bolt::Result.from_exception(target, e) }
@@ -149,7 +149,7 @@ module PlanExecutor
149
149
  end
150
150
 
151
151
  def finish_plan(plan_result)
152
- @transport.finish_plan(plan_result)
152
+ @orch_client.finish_plan(plan_result)
153
153
  end
154
154
 
155
155
  def without_default_logging
@@ -1,30 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'jsonclient'
4
+
3
5
  module PlanExecutor
4
6
  class OrchClient
5
- attr_reader :plan_job, :client
7
+ attr_reader :plan_job, :http
6
8
 
7
9
  BOLT_COMMAND_TASK = Struct.new(:name).new('bolt_shim::command').freeze
8
10
  BOLT_SCRIPT_TASK = Struct.new(:name).new('bolt_shim::script').freeze
9
11
  BOLT_UPLOAD_TASK = Struct.new(:name).new('bolt_shim::upload').freeze
10
12
 
11
- def initialize(plan_job, client, logger)
13
+ def initialize(plan_job, http_client, logger)
12
14
  @plan_job = plan_job
13
- @client = client
14
15
  @logger = logger
15
- @client_url = client.config['service-url']
16
- logger.debug("Creating orchestrator client for #{@client_url}")
16
+ @http = http_client
17
17
  @environment = 'production'
18
18
  end
19
19
 
20
20
  def finish_plan(plan_result)
21
- result = @client.command.plan_finish(
21
+ body = {
22
22
  plan_job: @plan_job,
23
23
  result: plan_result.value || '',
24
24
  status: plan_result.status
25
- )
25
+ }
26
+ post_command('internal/plan_finish', body)
26
27
  rescue StandardError => e
27
- @logger.debug("Failed to finish plan on #{@client_url}: #{e.message}\nResult: #{result.to_json}")
28
+ @logger.error("Failed to finish plan #{plan_job}: #{e.message}")
28
29
  end
29
30
 
30
31
  def run_task_job(targets, task, arguments, options)
@@ -43,6 +44,22 @@ module PlanExecutor
43
44
  end
44
45
  end
45
46
 
47
+ def get(url)
48
+ response = @http.get(url)
49
+ if response.status != 200
50
+ raise Bolt::Error.new(response.body['msg'], response.body['kind'], response.body['details'])
51
+ end
52
+ response.body
53
+ end
54
+
55
+ def post_command(url, body)
56
+ response = @http.post(url, body)
57
+ if response.status != 202
58
+ raise Bolt::Error.new(response.body['msg'], response.body['kind'], response.body['details'])
59
+ end
60
+ response.body
61
+ end
62
+
46
63
  def send_request(targets, task, arguments, options = {})
47
64
  description = options['_description']
48
65
  body = { task: task.name,
@@ -55,7 +72,15 @@ module PlanExecutor
55
72
  body[:description] = description if description
56
73
  body[:plan_job] = @plan_job if @plan_job
57
74
 
58
- @client.run_task(body)
75
+ url = post_command('internal/plan_task', body).dig('job', 'id')
76
+
77
+ job = get(url)
78
+ until %w[stopped finished failed].include?(job['state'])
79
+ sleep 1
80
+ job = get(url)
81
+ end
82
+
83
+ get(job.dig('nodes', 'id'))['items']
59
84
  end
60
85
 
61
86
  def process_run_results(targets, results)
@@ -99,7 +124,8 @@ module PlanExecutor
99
124
  content = Base64.encode64(content)
100
125
  params = {
101
126
  'content' => content,
102
- 'arguments' => arguments
127
+ 'arguments' => arguments,
128
+ 'name' => Pathname(script).basename.to_s
103
129
  }
104
130
  callback ||= proc {}
105
131
  results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
@@ -204,8 +230,8 @@ module PlanExecutor
204
230
  end
205
231
 
206
232
  def connected?(targets)
207
- nodes = @client.post('inventory', nodes: targets.map(&:host))
208
- nodes['items'].all? { |node| node['connected'] }
233
+ response = @http.post('inventory', nodes: targets.map(&:host))
234
+ response.body['items'].all? { |node| node['connected'] }
209
235
  end
210
236
  end
211
237
  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: 1.11.0
4
+ version: 1.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-08 00:00:00.000000000 Z
11
+ date: 2019-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable