bolt 3.14.1 → 3.17.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.

Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +137 -104
  4. data/guides/debugging.yaml +27 -0
  5. data/guides/inventory.yaml +23 -0
  6. data/guides/links.yaml +12 -0
  7. data/guides/logging.yaml +17 -0
  8. data/guides/module.yaml +18 -0
  9. data/guides/modulepath.yaml +24 -0
  10. data/guides/project.yaml +21 -0
  11. data/guides/targets.yaml +28 -0
  12. data/guides/transports.yaml +22 -0
  13. data/lib/bolt/analytics.rb +2 -19
  14. data/lib/bolt/application.rb +634 -0
  15. data/lib/bolt/bolt_option_parser.rb +28 -4
  16. data/lib/bolt/cli.rb +592 -788
  17. data/lib/bolt/fiber_executor.rb +7 -3
  18. data/lib/bolt/inventory/inventory.rb +68 -39
  19. data/lib/bolt/inventory.rb +2 -9
  20. data/lib/bolt/module_installer/puppetfile.rb +24 -10
  21. data/lib/bolt/outputter/human.rb +83 -32
  22. data/lib/bolt/outputter/json.rb +63 -38
  23. data/lib/bolt/pal/yaml_plan/transpiler.rb +1 -1
  24. data/lib/bolt/pal.rb +31 -11
  25. data/lib/bolt/plan_creator.rb +84 -25
  26. data/lib/bolt/plan_future.rb +11 -6
  27. data/lib/bolt/plan_result.rb +1 -1
  28. data/lib/bolt/plugin/task.rb +1 -1
  29. data/lib/bolt/plugin.rb +11 -17
  30. data/lib/bolt/project.rb +0 -7
  31. data/lib/bolt/result_set.rb +2 -1
  32. data/lib/bolt/transport/local/connection.rb +17 -1
  33. data/lib/bolt/transport/orch/connection.rb +13 -1
  34. data/lib/bolt/version.rb +1 -1
  35. data/lib/bolt_server/file_cache.rb +12 -0
  36. data/lib/bolt_server/schemas/action-apply.json +32 -0
  37. data/lib/bolt_server/schemas/action-apply_prep.json +19 -0
  38. data/lib/bolt_server/transport_app.rb +113 -24
  39. data/lib/bolt_spec/bolt_context.rb +1 -1
  40. data/lib/bolt_spec/run.rb +1 -1
  41. metadata +14 -3
  42. data/lib/bolt/secret.rb +0 -37
data/lib/bolt/project.rb CHANGED
@@ -209,12 +209,5 @@ module Bolt
209
209
  Bolt::Logger.warn("missing_project_name", message)
210
210
  end
211
211
  end
212
-
213
- def check_deprecated_file
214
- if (@path + 'project.yaml').file?
215
- msg = "Project configuration file 'project.yaml' is deprecated; use 'bolt-project.yaml' instead."
216
- Bolt::Logger.warn("project_yaml", msg)
217
- end
218
- end
219
212
  end
220
213
  end
@@ -2,7 +2,8 @@
2
2
 
3
3
  module Bolt
4
4
  class ResultSet
5
- attr_reader :results
5
+ attr_accessor :elapsed_time
6
+ attr_reader :results
6
7
 
7
8
  include Enumerable
8
9
 
@@ -10,6 +10,8 @@ module Bolt
10
10
  module Transport
11
11
  class Local < Simple
12
12
  class Connection
13
+ RUBY_ENV_VARS = %w[GEM_PATH GEM_HOME RUBYLIB RUBYLIB_PREFIX RUBYOPT RUBYPATH RUBYSHELL].freeze
14
+
13
15
  attr_accessor :user, :logger, :target
14
16
 
15
17
  def initialize(target)
@@ -68,7 +70,21 @@ module Bolt
68
70
  end
69
71
  end
70
72
 
71
- Open3.popen3(*command)
73
+ # Only do this if bundled-ruby is set to false, not nil
74
+ ruby_env_vars = if target.transport_config['bundled-ruby'] == false
75
+ RUBY_ENV_VARS.each_with_object({}) do |e, acc|
76
+ acc[e] = ENV["BOLT_ORIG_#{e}"] if ENV["BOLT_ORIG_#{e}"]
77
+ end
78
+ end
79
+
80
+ if target.transport_config['bundled-ruby'] == false &&
81
+ Gem.loaded_specs.keys.include?('bundler')
82
+ Bundler.with_unbundled_env do
83
+ Open3.popen3(ruby_env_vars || {}, *command)
84
+ end
85
+ else
86
+ Open3.popen3(ruby_env_vars || {}, *command)
87
+ end
72
88
  end
73
89
 
74
90
  # This is used by the Bash shell to decide whether to `cd` before
@@ -36,7 +36,8 @@ module Bolt
36
36
  end
37
37
  logger.debug("Creating orchestrator client for #{client_opts}")
38
38
  @client = OrchestratorClient.new(client_opts, true)
39
- @plan_job = start_plan(plan_context)
39
+ @plan_context = plan_context
40
+ @plan_job = start_plan(@plan_context)
40
41
  logger.debug("Started plan #{@plan_job}")
41
42
  @environment = opts["task-environment"]
42
43
  end
@@ -87,6 +88,17 @@ module Bolt
87
88
  def run_task(targets, task, arguments, options)
88
89
  body = build_request(targets, task, arguments, options[:description])
89
90
  @client.run_task(body)
91
+ rescue OrchestratorClient::ApiError => e
92
+ if e.data['kind'] == 'puppetlabs.orchestrator/plan-already-finished'
93
+ @logger.debug("Retrying the task")
94
+ # Instead of recursing, just retry once
95
+ @plan_job = start_plan(@plan_context)
96
+ # Rebuild the request with the new plan job ID
97
+ body = build_request(targets, task, arguments, options[:description])
98
+ @client.run_task(body)
99
+ else
100
+ raise e
101
+ end
90
102
  end
91
103
 
92
104
  def query_inventory(targets)
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '3.14.1'
4
+ VERSION = '3.17.0'
5
5
  end
@@ -182,5 +182,17 @@ module BoltServer
182
182
  end
183
183
  end
184
184
  end
185
+
186
+ def get_cached_project_file(versioned_project, file_name)
187
+ file_dir = create_cache_dir(versioned_project)
188
+ file_path = File.join(file_dir, file_name)
189
+ serial_execute { File.read(file_path) if File.exist?(file_path) }
190
+ end
191
+
192
+ def cache_project_file(versioned_project, file_name, data)
193
+ file_dir = create_cache_dir(versioned_project)
194
+ file_path = File.join(file_dir, file_name)
195
+ serial_execute { File.open(file_path, 'w') { |f| f.write(data) } }
196
+ end
185
197
  end
186
198
  end
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "apply request",
4
+ "description": "POST <transport>/apply request schema",
5
+ "type": "object",
6
+ "properties": {
7
+ "versioned_project": {
8
+ "type": "string",
9
+ "description": "Project from which to load code"
10
+ },
11
+ "parameters": {
12
+ "type": "object",
13
+ "properties": {
14
+ "catalog": {
15
+ "type": "object",
16
+ "description": "Compiled catalog to apply"
17
+ },
18
+ "apply_options": {
19
+ "type": "object",
20
+ "description": "Options for application of a catalog"
21
+ }
22
+ }
23
+ },
24
+ "job_id": {
25
+ "type": "integer",
26
+ "description": "job-id associated with request"
27
+ },
28
+ "target": { "$ref": "partial:target-any" }
29
+ },
30
+ "required": ["target", "versioned_project", "parameters", "job_id"],
31
+ "additionalProperties": false
32
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "apply_prep request",
4
+ "description": "POST <transport>/apply_prep request schema",
5
+ "type": "object",
6
+ "properties": {
7
+ "versioned_project": {
8
+ "type": "String",
9
+ "description": "Project from which to load code"
10
+ },
11
+ "target": { "$ref": "partial:target-any" },
12
+ "job_id": {
13
+ "type": "integer",
14
+ "description": "job-id associated with request"
15
+ }
16
+ },
17
+ "required": ["target", "versioned_project", "job_id"],
18
+ "additionalProperties": false
19
+ }
@@ -43,6 +43,8 @@ module BoltServer
43
43
  transport-ssh
44
44
  transport-winrm
45
45
  connect-data
46
+ action-apply_prep
47
+ action-apply
46
48
  ].freeze
47
49
 
48
50
  # PE_BOLTLIB_PATH is intended to function exactly like the BOLTLIB_PATH used
@@ -75,6 +77,12 @@ module BoltServer
75
77
  # This is needed until the PAL is threadsafe.
76
78
  @pal_mutex = Mutex.new
77
79
 
80
+ # Avoid redundant plugin tarbal construction
81
+ @plugin_mutex = Mutex.new
82
+
83
+ # Avoid redundant project_task metadata construction
84
+ @task_metadata_mutex = Mutex.new
85
+
78
86
  @logger = Bolt::Logger.logger(self)
79
87
 
80
88
  super(nil)
@@ -118,12 +126,7 @@ module BoltServer
118
126
  end
119
127
  end
120
128
 
121
- def run_task(target, body)
122
- validate_schema(@schemas["action-run_task"], body)
123
-
124
- task_data = body['task']
125
- task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
126
- parameters = body['parameters'] || {}
129
+ def task_helper(target, task, parameters)
127
130
  # Wrap parameters marked with '"sensitive": true' in the task metadata with a
128
131
  # Sensitive wrapper type. This way it's not shown in logs.
129
132
  if (param_spec = task.parameters)
@@ -142,6 +145,101 @@ module BoltServer
142
145
  end
143
146
  end
144
147
 
148
+ def run_task(target, body)
149
+ validate_schema(@schemas["action-run_task"], body)
150
+
151
+ task_data = body['task']
152
+ task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
153
+ task_helper(target, task, body['parameters'] || {})
154
+ end
155
+
156
+ def extract_install_task(target)
157
+ unless target.plugin_hooks['puppet_library']['task']
158
+ raise BoltServer::RequestError,
159
+ "Target must have 'task' plugin hook"
160
+ end
161
+ install_task = target.plugin_hooks['puppet_library']['task'].split('::', 2)
162
+ install_task << 'init' if install_task.count == 1
163
+ install_task
164
+ end
165
+
166
+ # This helper is responsible for computing or retrieving from the cache a plugin tarball. There are
167
+ # two supported plugin types 'fact_plugins', and 'all_plugins'. Note that this is cached based on
168
+ # versioned_project as there are no plugins in the "builtin content" directory
169
+ def plugin_tarball(versioned_project, tarball_type)
170
+ tarball_types = %w[fact_plugins all_plugins]
171
+ unless tarball_types.include?(tarball_type)
172
+ raise ArgumentError,
173
+ "tarball_type must be one of: #{tarball_types.join(', ')}"
174
+ end
175
+ # lock this so that in the case an apply/apply_prep with multiple targets hits this endpoint
176
+ # the tarball computation only happens once (all the other targets will just need to read the cached data)
177
+ @plugin_mutex.synchronize do
178
+ if (tarball = @file_cache.get_cached_project_file(versioned_project, tarball_type))
179
+ tarball
180
+ else
181
+ new_tarball = build_project_plugins_tarball(versioned_project) do |mod|
182
+ search_dirs = []
183
+ search_dirs << mod.plugins if mod.plugins?
184
+ search_dirs << mod.pluginfacts if mod.pluginfacts?
185
+ if tarball_type == 'all_plugins'
186
+ search_dirs << mod.files if mod.files?
187
+ type_files = "#{mod.path}/types"
188
+ search_dirs << type_files if File.exist?(type_files)
189
+ end
190
+ search_dirs
191
+ end
192
+ @file_cache.cache_project_file(versioned_project, tarball_type, new_tarball)
193
+ new_tarball
194
+ end
195
+ end
196
+ end
197
+
198
+ # This helper is responsible for computing or retrieving task metadata for a project.
199
+ # It expects task name in segments and uses the combination of task name and versioned_project
200
+ # as a unique identifier for caching in addition to the job_id. The job id is added to protect against
201
+ # a case where the buildtin content is update (where the apply_helpers would be managed)
202
+ def project_task_metadata(versioned_project, task_name_segments, job_id)
203
+ cached_file_name = "#{task_name_segments.join('_')}_#{job_id}"
204
+ # lock this so that in the case an apply/apply_prep with multiple targets hits this endpoint the
205
+ # metadata computation will only be computed once, then the cache will be read.
206
+ @task_metadata_mutex.synchronize do
207
+ if (metadata = @file_cache.get_cached_project_file(versioned_project, cached_file_name))
208
+ JSON.parse(metadata)
209
+ else
210
+ new_metadata = in_bolt_project(versioned_project) do |context|
211
+ ps_parameters = {
212
+ 'versioned_project' => versioned_project
213
+ }
214
+ pe_task_info(context[:pal], *task_name_segments, ps_parameters)
215
+ end
216
+ @file_cache.cache_project_file(versioned_project, cached_file_name, new_metadata.to_json)
217
+ new_metadata
218
+ end
219
+ end
220
+ end
221
+
222
+ def apply_prep(target, body)
223
+ validate_schema(@schemas["action-apply_prep"], body)
224
+ plugins_tarball = plugin_tarball(body['versioned_project'], 'fact_plugins')
225
+ install_task_segments = extract_install_task(target.first)
226
+ task_data = project_task_metadata(body['versioned_project'], install_task_segments, body["job_id"])
227
+ task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
228
+ install_task_result = task_helper(target, task, target.first.plugin_hooks['puppet_library']['parameters'] || {})
229
+ return install_task_result unless install_task_result.ok
230
+ task_data = project_task_metadata(body['versioned_project'], %w[apply_helpers custom_facts], body["job_id"])
231
+ task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
232
+ task_helper(target, task, { 'plugins' => plugins_tarball })
233
+ end
234
+
235
+ def apply(target, body)
236
+ validate_schema(@schemas["action-apply"], body)
237
+ plugins_tarball = plugin_tarball(body['versioned_project'], 'all_plugins')
238
+ task_data = project_task_metadata(body['versioned_project'], %w[apply_helpers apply_catalog], body["job_id"])
239
+ task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
240
+ task_helper(target, task, body['parameters'].merge({ 'plugins' => plugins_tarball }))
241
+ end
242
+
145
243
  def run_command(target, body)
146
244
  validate_schema(@schemas["action-run_command"], body)
147
245
  command = body['command']
@@ -476,6 +574,8 @@ module BoltServer
476
574
  run_task
477
575
  run_script
478
576
  upload_file
577
+ apply
578
+ apply_prep
479
579
  ].freeze
480
580
 
481
581
  def make_ssh_target(target_hash)
@@ -499,7 +599,8 @@ module BoltServer
499
599
  'config' => {
500
600
  'transport' => 'ssh',
501
601
  'ssh' => opts.slice(*Bolt::Config::Transport::SSH.options)
502
- }
602
+ },
603
+ 'plugin_hooks' => target_hash['plugin_hooks']
503
604
  }
504
605
 
505
606
  inventory = Bolt::Inventory.empty
@@ -537,7 +638,8 @@ module BoltServer
537
638
  'config' => {
538
639
  'transport' => 'winrm',
539
640
  'winrm' => opts.slice(*Bolt::Config::Transport::WinRM.options)
540
- }
641
+ },
642
+ 'plugin_hooks' => target_hash['plugin_hooks']
541
643
  }
542
644
 
543
645
  inventory = Bolt::Inventory.empty
@@ -730,7 +832,7 @@ module BoltServer
730
832
  }
731
833
 
732
834
  connect_plugin = BoltServer::Plugin::PuppetConnectData.new(body['puppet_connect_data'])
733
- plugins = Bolt::Plugin.setup(context[:config], context[:pal], load_plugins: false)
835
+ plugins = Bolt::Plugin.new(context[:config], context[:pal], load_plugins: false)
734
836
  plugins.add_plugin(connect_plugin)
735
837
  %w[aws_inventory azure_inventory gcloud_inventory].each do |plugin_name|
736
838
  plugins.add_module_plugin(plugin_name) if plugins.known_plugin?(plugin_name)
@@ -756,12 +858,7 @@ module BoltServer
756
858
  raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
757
859
  content_type :json
758
860
 
759
- plugins_tarball = build_project_plugins_tarball(params['versioned_project']) do |mod|
760
- search_dirs = []
761
- search_dirs << mod.plugins if mod.plugins?
762
- search_dirs << mod.pluginfacts if mod.pluginfacts?
763
- search_dirs
764
- end
861
+ plugins_tarball = plugin_tarball(params['versioned_project'], 'fact_plugins')
765
862
 
766
863
  [200, plugins_tarball.to_json]
767
864
  end
@@ -773,15 +870,7 @@ module BoltServer
773
870
  raise BoltServer::RequestError, "'versioned_project' is a required argument" if params['versioned_project'].nil?
774
871
  content_type :json
775
872
 
776
- plugins_tarball = build_project_plugins_tarball(params['versioned_project']) do |mod|
777
- search_dirs = []
778
- search_dirs << mod.plugins if mod.plugins?
779
- search_dirs << mod.pluginfacts if mod.pluginfacts?
780
- search_dirs << mod.files if mod.files?
781
- type_files = "#{mod.path}/types"
782
- search_dirs << type_files if File.exist?(type_files)
783
- search_dirs
784
- end
873
+ plugins_tarball = plugin_tarball(params['versioned_project'], 'all_plugins')
785
874
 
786
875
  [200, plugins_tarball.to_json]
787
876
  end
@@ -154,7 +154,7 @@ module BoltSpec
154
154
  end
155
155
 
156
156
  def plugins
157
- @plugins ||= Bolt::Plugin.setup(config, pal)
157
+ @plugins ||= Bolt::Plugin.new(config, pal)
158
158
  end
159
159
 
160
160
  def pal
data/lib/bolt_spec/run.rb CHANGED
@@ -171,7 +171,7 @@ module BoltSpec
171
171
  end
172
172
 
173
173
  def plugins
174
- @plugins ||= Bolt::Plugin.setup(config, pal)
174
+ @plugins ||= Bolt::Plugin.new(config, pal)
175
175
  end
176
176
 
177
177
  def puppetdb_client
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: 3.14.1
4
+ version: 3.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-26 00:00:00.000000000 Z
11
+ date: 2021-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -466,8 +466,18 @@ files:
466
466
  - bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb
467
467
  - bolt-modules/system/lib/puppet/functions/system/env.rb
468
468
  - exe/bolt
469
+ - guides/debugging.yaml
470
+ - guides/inventory.yaml
471
+ - guides/links.yaml
472
+ - guides/logging.yaml
473
+ - guides/module.yaml
474
+ - guides/modulepath.yaml
475
+ - guides/project.yaml
476
+ - guides/targets.yaml
477
+ - guides/transports.yaml
469
478
  - lib/bolt.rb
470
479
  - lib/bolt/analytics.rb
480
+ - lib/bolt/application.rb
471
481
  - lib/bolt/applicator.rb
472
482
  - lib/bolt/apply_inventory.rb
473
483
  - lib/bolt/apply_result.rb
@@ -561,7 +571,6 @@ files:
561
571
  - lib/bolt/resource_instance.rb
562
572
  - lib/bolt/result.rb
563
573
  - lib/bolt/result_set.rb
564
- - lib/bolt/secret.rb
565
574
  - lib/bolt/shell.rb
566
575
  - lib/bolt/shell/bash.rb
567
576
  - lib/bolt/shell/bash/tmpdir.rb
@@ -601,6 +610,8 @@ files:
601
610
  - lib/bolt_server/plugin.rb
602
611
  - lib/bolt_server/plugin/puppet_connect_data.rb
603
612
  - lib/bolt_server/request_error.rb
613
+ - lib/bolt_server/schemas/action-apply.json
614
+ - lib/bolt_server/schemas/action-apply_prep.json
604
615
  - lib/bolt_server/schemas/action-check_node_connections.json
605
616
  - lib/bolt_server/schemas/action-run_command.json
606
617
  - lib/bolt_server/schemas/action-run_script.json
data/lib/bolt/secret.rb DELETED
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bolt/plugin'
4
-
5
- module Bolt
6
- class Secret
7
- KNOWN_KEYS = {
8
- 'createkeys' => %w[keysize private_key public_key],
9
- 'encrypt' => %w[public_key],
10
- 'decrypt' => %w[private_key public_key]
11
- }.freeze
12
-
13
- def self.execute(plugins, outputter, options)
14
- name = options[:plugin] || 'pkcs7'
15
- plugin = plugins.by_name(name)
16
-
17
- unless plugin
18
- raise Bolt::Plugin::PluginError::Unknown, name
19
- end
20
-
21
- case options[:action]
22
- when 'createkeys'
23
- opts = { 'force' => options[:force] }.compact
24
- result = plugins.get_hook(name, :secret_createkeys).call(opts)
25
- outputter.print_message(result)
26
- when 'encrypt'
27
- encrypted = plugins.get_hook(name, :secret_encrypt).call('plaintext_value' => options[:object])
28
- outputter.print_message(encrypted)
29
- when 'decrypt'
30
- decrypted = plugins.get_hook(name, :secret_decrypt).call('encrypted_value' => options[:object])
31
- outputter.print_message(decrypted)
32
- end
33
-
34
- 0
35
- end
36
- end
37
- end