bolt 1.44.0 → 1.45.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: 3e7033e924c3721ad51ea3ba221527831174bf0a962a09be2d01af56eea9d29a
4
- data.tar.gz: 400125a4441d5df92ac45fae11e222be1b0d8925ea501144e61f82f4a60fc62a
3
+ metadata.gz: 28b4c5859fab6ec57cac9d5ad791434aabfea666711f773f307d296df25274a9
4
+ data.tar.gz: 8dceb70532864e93b15c9237fff7ef803dba83650519f54dc60bbbce7e85a991
5
5
  SHA512:
6
- metadata.gz: b25b113a4b89a14c84aec41fe2e8e1fad3a41a82ac4b6e58e8ff3901e46ac2be2a71eafee0cc80545f9d03cdb6d93affd1e44dc90f426fdb415bbfd26e17d16d
7
- data.tar.gz: 6b08cf2a56959c286622c1279b2583ef312c673cb08c719727ba4c29142f8e9ea2d449f12a811eba671a7658b1619a735203905d98bae6dfc66ecb7c00b958a0
6
+ metadata.gz: 88716d23b4acaf2bf97fa73dbd96d802c185591bc1ffa1115268c1c27362be35e6a3e94e8f9a75c3d65e437c5aaf176776a2789ce2c9d7cbeeddd3db88496659
7
+ data.tar.gz: 8875843ed41a1cba383997f5fbc7e865ae0f8e0a401e84196d9339fd9fdde58652ca99128ed1527a61afa46fbb526abbf13dafd67f22729b14db5739a176f53c
@@ -5,13 +5,13 @@ require 'bolt/error'
5
5
  # Makes a query to {https://puppet.com/docs/puppetdb/latest/index.html puppetdb}
6
6
  # using Bolt's PuppetDB client.
7
7
  Puppet::Functions.create_function(:puppetdb_query) do
8
- # rubocop:disable Metrics/LineLength
8
+ # rubocop:disable Layout/LineLength
9
9
  # @param query A PQL query.
10
10
  # {https://puppet.com/docs/puppetdb/latest/api/query/tutorial-pql.html Learn more about Puppet's query language, PQL}
11
11
  # @return Results of the PuppetDB query.
12
12
  # @example Request certnames for all nodes
13
13
  # puppetdb_query('nodes[certname] {}')
14
- # rubocop:enable Metrics/LineLength
14
+ # rubocop:enable Layout/LineLength
15
15
  dispatch :make_query do
16
16
  param 'Variant[String, Array[Data]]', :query
17
17
  return_type 'Array[Data]'
@@ -82,7 +82,11 @@ module Bolt
82
82
  end
83
83
 
84
84
  def compile(target, ast, plan_vars)
85
- trusted = Puppet::Context::TrustedInformation.new('local', target.name, {})
85
+ # This simplified Puppet node object is what .local uses to determine the
86
+ # certname of the target
87
+ node = Puppet::Node.from_data_hash('name' => target.name,
88
+ 'parameters' => { 'clientcert' => target.name })
89
+ trusted = Puppet::Context::TrustedInformation.local(node)
86
90
  facts = @inventory.facts(target).merge('bolt' => true)
87
91
 
88
92
  catalog_input = {
@@ -131,7 +135,11 @@ module Bolt
131
135
  end
132
136
 
133
137
  def future_compile(target, catalog_input)
134
- trusted = Puppet::Context::TrustedInformation.new('local', target.name, {})
138
+ # This simplified Puppet node object is what .local uses to determine the
139
+ # certname of the target
140
+ node = Puppet::Node.from_data_hash('name' => target.name,
141
+ 'parameters' => { 'clientcert' => target.name })
142
+ trusted = Puppet::Context::TrustedInformation.local(node)
135
143
  catalog_input[:target] = {
136
144
  name: target.name,
137
145
  facts: @inventory.facts(target).merge('bolt' => true),
@@ -53,9 +53,9 @@ module Bolt
53
53
 
54
54
  unless missing_keys.empty?
55
55
  if result['_output']
56
- # rubocop:disable Metrics/LineLength
56
+ # rubocop:disable Layout/LineLength
57
57
  msg = "Report result contains an '_output' key. Catalog application may have printed extraneous output to stdout: #{result['_output']}"
58
- # rubocop:enable Metrics/LineLength
58
+ # rubocop:enable Layout/LineLength
59
59
  else
60
60
  msg = "Report did not contain all expected keys missing: #{missing_keys.join(' ,')}"
61
61
  end
@@ -96,7 +96,26 @@ module Bolt
96
96
  Puppet[:strict] = options['strict'] || :warning
97
97
  Puppet[:strict_variables] = options['strict_variables'] || false
98
98
  ast = Puppet::Pops::Serialization::FromDataConverter.convert(pal_main)
99
+ # This will be a Program when running via `bolt apply`, but will
100
+ # only be a subset of the AST when compiling an apply block in a
101
+ # plan. In that case, we need to discover the definitions (which
102
+ # would ordinarily be stored on the Program) and construct a Program object.
103
+ unless ast.is_a?(Puppet::Pops::Model::Program)
104
+ # Node definitions must be at the top level of the apply block.
105
+ # That means the apply body either a) consists of just a
106
+ # NodeDefinition, b) consists of a BlockExpression which may
107
+ # contain NodeDefinitions, or c) doesn't contain NodeDefinitions.
108
+ definitions = if ast.is_a?(Puppet::Pops::Model::BlockExpression)
109
+ ast.statements.select { |st| st.is_a?(Puppet::Pops::Model::NodeDefinition) }
110
+ elsif ast.is_a?(Puppet::Pops::Model::NodeDefinition)
111
+ [ast]
112
+ else
113
+ []
114
+ end
115
+ ast = Puppet::Pops::Model::Factory.PROGRAM(ast, definitions, ast.locator).model
116
+ end
99
117
  compiler.evaluate(ast)
118
+ compiler.instance_variable_get(:@internal_compiler).send(:evaluate_ast_node)
100
119
  compiler.compile_additions
101
120
  compiler.with_json_encoding(&:encode)
102
121
  end
@@ -664,7 +664,8 @@ module Bolt
664
664
  @pal ||= Bolt::PAL.new(config.modulepath,
665
665
  config.hiera_config,
666
666
  config.boltdir.resource_types,
667
- config.compile_concurrency)
667
+ config.compile_concurrency,
668
+ config.trusted_external)
668
669
  end
669
670
 
670
671
  def convert_plan(plan)
@@ -33,7 +33,7 @@ module Bolt
33
33
  class Config
34
34
  attr_accessor :concurrency, :format, :trace, :log, :puppetdb, :color, :save_rerun,
35
35
  :transport, :transports, :inventoryfile, :compile_concurrency, :boltdir,
36
- :puppetfile_config, :plugins, :plugin_hooks, :future
36
+ :puppetfile_config, :plugins, :plugin_hooks, :future, :trusted_external
37
37
  attr_writer :modulepath
38
38
 
39
39
  TRANSPORT_OPTIONS = %i[password run-as sudo-password extensions sudo-executable
@@ -162,6 +162,9 @@ module Bolt
162
162
  end
163
163
 
164
164
  @hiera_config = File.expand_path(data['hiera-config'], @boltdir.path) if data.key?('hiera-config')
165
+ @trusted_external = if data.key?('trusted-external-command')
166
+ File.expand_path(data['trusted-external-command'], @boltdir.path)
167
+ end
165
168
  @compile_concurrency = data['compile-concurrency'] if data.key?('compile-concurrency')
166
169
 
167
170
  @save_rerun = data['save-rerun'] if data.key?('save-rerun')
@@ -291,6 +294,7 @@ module Bolt
291
294
  end
292
295
 
293
296
  Bolt::Util.validate_file('hiera-config', @hiera_config) if @hiera_config
297
+ Bolt::Util.validate_file('trusted-external-command', @trusted_external) if @trusted_external
294
298
 
295
299
  unless @transport.nil? || Bolt::TRANSPORTS.include?(@transport.to_sym)
296
300
  raise UnknownTransportError, @transport
@@ -90,6 +90,8 @@ module Bolt
90
90
 
91
91
  @groups.resolve_aliases(@groups.node_aliases)
92
92
  collect_groups
93
+
94
+ deprecation unless @data.empty?
93
95
  end
94
96
 
95
97
  def validate
@@ -100,6 +102,17 @@ module Bolt
100
102
  1
101
103
  end
102
104
 
105
+ def deprecation
106
+ msg = <<~MSG
107
+ Deprecation Warning: Starting with Bolt 2.0, inventory file v1 will no longer
108
+ be supported. v1 inventory files can be automatically migrated to v2 using
109
+ 'bolt project migrate'. Inventory files are modified in place and will not
110
+ preserve formatting or comments.
111
+ MSG
112
+ @logger.warn(msg)
113
+ end
114
+ private :deprecation
115
+
103
116
  def collect_groups
104
117
  # Provide a lookup map for finding a group by name
105
118
  @group_lookup = @groups.collect_groups
@@ -235,7 +235,7 @@ module Bolt
235
235
 
236
236
  # DEPRECATION : remove this before finalization
237
237
  if input.key?('target-lookups')
238
- msg = "'target-lookups' are no longer a separate key. Merge 'target-lookups' and 'targets' lists and replace 'plugin' with '_plugin'" # rubocop:disable Metrics/LineLength
238
+ msg = "'target-lookups' are no longer a separate key. Merge 'target-lookups' and 'targets' lists and replace 'plugin' with '_plugin'" # rubocop:disable Layout/LineLength
239
239
  raise ValidationError.new(msg, @name)
240
240
  end
241
241
 
@@ -39,7 +39,7 @@ module Bolt
39
39
 
40
40
  attr_reader :modulepath
41
41
 
42
- def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors)
42
+ def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors, trusted_external = nil)
43
43
  # Nothing works without initialized this global state. Reinitializing
44
44
  # is safe and in practice only happens in tests
45
45
  self.class.load_puppet
@@ -47,6 +47,7 @@ module Bolt
47
47
  @original_modulepath = modulepath
48
48
  @modulepath = [BOLTLIB_PATH, *modulepath, MODULES_PATH]
49
49
  @hiera_config = hiera_config
50
+ @trusted_external = trusted_external
50
51
  @max_compiles = max_compiles
51
52
  @resource_types = resource_types
52
53
 
@@ -211,6 +212,7 @@ module Bolt
211
212
  Puppet.settings.send(:clear_everything_for_tests)
212
213
  Puppet.initialize_settings(cli)
213
214
  Puppet::GettextConfig.create_default_text_domain
215
+ Puppet[:trusted_external_command] = @trusted_external
214
216
  self.class.configure_logging
215
217
  yield
216
218
  end
@@ -157,14 +157,14 @@ module Bolt
157
157
  .map { |str| JSON.parse(str) }
158
158
  end
159
159
 
160
- # rubocop:disable Metrics/LineLength
160
+ # rubocop:disable Layout/LineLength
161
161
  # Executes a Docker CLI command
162
162
  #
163
163
  # @param subcommand [String] The docker subcommand to run e.g. 'inspect' for `docker inspect`
164
164
  # @param command_options [Array] Additional command options e.g. ['--size'] for `docker inspect --size`
165
165
  # @param redir_stdin [IO] IO object which will be use to as STDIN in the docker command. Default is nil, which does not perform redirection
166
166
  # @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
167
- # rubocop:enable Metrics/LineLength
167
+ # rubocop:enable Layout/LineLength
168
168
  def execute_local_docker_command(subcommand, command_options = [], redir_stdin = nil)
169
169
  env_hash = {}
170
170
  # Set the DOCKER_HOST if we are using a non-default service-url
@@ -81,7 +81,7 @@ module Bolt
81
81
  target,
82
82
  value: { '_error' => {
83
83
  'kind' => 'puppetlabs.tasks/skipped-node',
84
- 'msg' => "Node #{target.safe_name} was skipped",
84
+ 'msg' => "Target #{target.safe_name} was skipped",
85
85
  'details' => {}
86
86
  } },
87
87
  action: 'task', object: task_name
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.44.0'
4
+ VERSION = '1.45.0'
5
5
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'bolt/puppetdb'
5
+ require 'bolt/logger'
5
6
  require 'json'
6
7
  require 'optparse'
7
8
  require 'yaml'
@@ -10,6 +11,8 @@ module Bolt
10
11
  class PuppetDBInventory
11
12
  class CLI
12
13
  def initialize(args)
14
+ Bolt::Logger.initialize_logging
15
+ @logger = Logging.logger[self]
13
16
  @args = args
14
17
  @cli_opts = {}
15
18
  @parser = build_parser
@@ -66,6 +69,17 @@ query results.
66
69
  end
67
70
 
68
71
  def run
72
+ Bolt::Logger.configure({ "console" => {} }, true)
73
+ msg = <<~MSG
74
+ Deprecation Warning: Starting with Bolt 2.0, inventory file v1 and the
75
+ bolt-inventory-pdb command will no longer be supported. Use a v2 inventory
76
+ file instead with the PuppetDB plugin to lookup targets from PuppetDB.
77
+
78
+ v1 inventory files can be automatically migrated to v2 using 'bolt project migrate'.
79
+ Inventory files are modified in place and will not preserve formatting or comments.
80
+ MSG
81
+ @logger.warn(msg)
82
+
69
83
  positional_args = @parser.permute(@args)
70
84
 
71
85
  if @show_help
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/pal'
4
+ require 'bolt/util'
5
+
6
+ module BoltServer
7
+ module PE
8
+ class PAL < Bolt::PAL
9
+ # PE_BOLTLIB_PATH is intended to function exactly like the BOLTLIB_PATH used
10
+ # in Bolt::PAL. Paths and variable names are similar to what exists in
11
+ # Bolt::PAL, but with a 'PE' prefix.
12
+ PE_BOLTLIB_PATH = '/opt/puppetlabs/server/apps/bolt-server/pe-bolt-modules'
13
+
14
+ # For now at least, we maintain an entirely separate codedir from
15
+ # puppetserver by default, so that filesync can work properly. If filesync
16
+ # is not used, this can instead match the usual puppetserver codedir.
17
+ # See the `orchestrator.bolt.codedir` tk config setting.
18
+ DEFAULT_BOLT_CODEDIR = '/opt/puppetlabs/server/data/orchestration-services/code'
19
+
20
+ # This function is nearly identical to Bolt::Pal's `with_puppet_settings` with the
21
+ # one difference that we set the codedir to point to actual code, rather than the
22
+ # tmpdir. We only use this funtion inside the PEBolt::PAL initializer so that Puppet
23
+ # is correctly configured to pull environment configuration correctly. If we don't
24
+ # set codedir in this way: when we try to load and interpolate the modulepath it
25
+ # won't correctly load.
26
+ def with_pe_pal_init_settings(codedir, environmentpath, basemodulepath)
27
+ Dir.mktmpdir('pe-bolt') do |dir|
28
+ cli = []
29
+ Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
30
+ dir = setting == :codedir ? codedir : dir
31
+ cli << "--#{setting}" << dir
32
+ end
33
+ cli << "--environmentpath" << environmentpath
34
+ cli << "--basemodulepath" << basemodulepath
35
+ Puppet.settings.send(:clear_everything_for_tests)
36
+ Puppet.initialize_settings(cli)
37
+ yield
38
+ # Ensure the puppet settings go back to what bolt expects after
39
+ # we finish with the settings we need for PEBolt::PAL init.
40
+ with_puppet_settings { |_| nil }
41
+ end
42
+ end
43
+
44
+ def initialize(plan_executor_config, environment_name, hiera_config = nil, max_compiles = nil)
45
+ # Bolt::PAL#initialize takes the modulepath as its first argument, but we
46
+ # want to customize it later, so we pass an empty value:
47
+ super([], hiera_config, max_compiles)
48
+
49
+ codedir = plan_executor_config['codedir'] || DEFAULT_BOLT_CODEDIR
50
+ environmentpath = plan_executor_config['environmentpath'] || "#{codedir}/environments"
51
+ basemodulepath = plan_executor_config['basemodulepath'] || "#{codedir}/modules:/opt/puppetlabs/puppet/modules"
52
+
53
+ with_pe_pal_init_settings(codedir, environmentpath, basemodulepath) do
54
+ environment = Puppet.lookup(:environments).get!(environment_name)
55
+
56
+ # In the instance where the environment is "production" but no production dir
57
+ # exists, the lookup will succeed, but the configuration will be mostly empty.
58
+ # For other environments the lookup will fail, but for production we don't
59
+ # want cryptic messages sent to the user about combining `nil` with a string.
60
+ # Thus if we do get here and `path_to_env` is empty, just assume it's the
61
+ # default production environment and continue.
62
+ #
63
+ # This should hopefully match puppet's behavior for the default 'production'
64
+ # environment: _technically_ that environment always exists, but if the dir
65
+ # isn't there it won't find the module and fail with "plan not found" rather
66
+ # than "environment doesn't exist"
67
+ if environment.configuration.path_to_env
68
+ bolt_config = File.join(environment.configuration.path_to_env, 'bolt.yaml')
69
+ end
70
+ # If we find a bolt.yaml config in the environment root, we load that instead of
71
+ # environment.conf to find the modulepath. modulepath will be the _only_ setting
72
+ # that will work from bolt.yaml in plans in PE.
73
+ modulepath_dirs = if bolt_config && File.exist?(bolt_config)
74
+ Bolt::Util.read_config_file(bolt_config)['modulepath'].split(File::PATH_SEPARATOR)
75
+ else
76
+ environment.modulepath
77
+ end
78
+
79
+ # A new modulepath is created from scratch (rather than using super's @modulepath)
80
+ # so that we can have full control over all the entries in modulepath. In the future
81
+ # it's likely we will need to preceed _both_ Bolt::PAL::BOLTLIB_PATH _and_
82
+ # Bolt::PAL::MODULES_PATH which would be more complex if we tried to use @modulepath since
83
+ # we need to append our modulepaths and exclude modules shiped in bolt gem code
84
+ @original_modulepath = modulepath_dirs
85
+ @modulepath = [PE_BOLTLIB_PATH, Bolt::PAL::BOLTLIB_PATH, *modulepath_dirs]
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -10,6 +10,10 @@ require 'bolt/task/puppet_server'
10
10
  require 'json'
11
11
  require 'json-schema'
12
12
 
13
+ # These are only needed for the `/plans` endpoint.
14
+ require 'puppet'
15
+ require 'bolt_server/pe/pal'
16
+
13
17
  module BoltServer
14
18
  class TransportApp < Sinatra::Base
15
19
  # This disables Sinatra's error page generation
@@ -45,6 +49,9 @@ module BoltServer
45
49
 
46
50
  @file_cache = BoltServer::FileCache.new(@config).setup
47
51
 
52
+ # This is needed until the PAL is threadsafe.
53
+ @pal_mutex = Mutex.new
54
+
48
55
  super(nil)
49
56
  end
50
57
 
@@ -173,6 +180,26 @@ module BoltServer
173
180
  [@executor.run_script(target, file_location, body['arguments'])]
174
181
  end
175
182
 
183
+ def in_pe_pal_env(environment)
184
+ if environment.nil?
185
+ [400, '`environment` is a required argument']
186
+ else
187
+ @pal_mutex.synchronize do
188
+ begin
189
+ pal = BoltServer::PE::PAL.new({}, environment)
190
+ yield pal
191
+ rescue Puppet::Environments::EnvironmentNotFound
192
+ [400, {
193
+ "class" => 'bolt/unknown-environment',
194
+ "message" => "Environment #{environment} not found"
195
+ }.to_json]
196
+ rescue Bolt::Error => e
197
+ [400, e.to_json]
198
+ end
199
+ end
200
+ end
201
+ end
202
+
176
203
  get '/' do
177
204
  200
178
205
  end
@@ -273,6 +300,36 @@ module BoltServer
273
300
  [200, result_set_to_status_hash(result_set, aggregate: aggregate).to_json]
274
301
  end
275
302
 
303
+ # Fetches the metadata for a single plan
304
+ #
305
+ # @param environment [String] the environment to fetch the plan from
306
+ get '/plans/:module_name/:plan_name' do
307
+ in_pe_pal_env(params['environment']) do |pal|
308
+ plan_info = pal.get_plan_info("#{params[:module_name]}::#{params[:plan_name]}")
309
+ [200, plan_info.to_json]
310
+ end
311
+ end
312
+
313
+ # Fetches the list of plans for an environment, optionally fetching all metadata for each plan
314
+ #
315
+ # @param environment [String] the environment to fetch the list of plans from
316
+ # @param metadata [Boolean] Set to true to fetch all metadata for each plan. Defaults to false
317
+ get '/plans' do
318
+ in_pe_pal_env(params['environment']) do |pal|
319
+ plans = pal.list_plans.flatten
320
+ if params['metadata']
321
+ plan_info = plans.each_with_object({}) { |plan_name, acc| acc[plan_name] = pal.get_plan_info(plan_name) }
322
+ [200, plan_info.to_json]
323
+ else
324
+ # We structure this array of plans to be an array of hashes so that it matches the structure
325
+ # returned by the puppetserver API that serves data like this. Structuring the output this way
326
+ # makes switching between puppetserver and bolt-server easier, which makes changes to switch
327
+ # to bolt-server smaller/simpler.
328
+ [200, plans.map { |plan| { 'name' => plan } }.to_json]
329
+ end
330
+ end
331
+ end
332
+
276
333
  error 404 do
277
334
  err = Bolt::Error.new("Could not find route #{request.path}",
278
335
  'boltserver/not-found')
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.44.0
4
+ version: 1.45.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-09 00:00:00.000000000 Z
11
+ date: 2020-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -464,6 +464,7 @@ files:
464
464
  - lib/bolt_server/base_config.rb
465
465
  - lib/bolt_server/config.rb
466
466
  - lib/bolt_server/file_cache.rb
467
+ - lib/bolt_server/pe/pal.rb
467
468
  - lib/bolt_server/schemas/action-check_node_connections.json
468
469
  - lib/bolt_server/schemas/action-run_command.json
469
470
  - lib/bolt_server/schemas/action-run_script.json