bolt 3.7.1 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 741f798df2b4c7a46e45c5c87a41cd4e994506cdd202d26b6acafae7774135f8
4
- data.tar.gz: 803789a5f6872e27656a733e94998acc9dde4d325d2d920b345a1c2848683f2f
3
+ metadata.gz: 1f4c856f3322c88dabb51efe39bc7373d96e33f6274785188900e8fbb5488436
4
+ data.tar.gz: 5e5da3f150093febfc2b6d7e343e9995ea33b044fb4941ed9229944e4735aa72
5
5
  SHA512:
6
- metadata.gz: 85cb83b0a0bf02d1ba15b14ba5e5e75c6ff290e1acee71b0de06ea716f3698e91358aa6f9961757b088afa3c98d519ba10f79a6195c767492a87603759c2fc81
7
- data.tar.gz: 8180f0827c3576e676e6fca59af7744d3e375023b44c0ebc2e691801a25f8fc1cac2487276661f7b40503e8ab1164ae07ee5f8343be9bb5be3088851e81eafaf
6
+ metadata.gz: 8f88f29b98a7bab28a4bbae5e860bb4c94fca20dd994241b71f2e5327934a92db1370c77fdb03d5f9a033e626b726e379e3eec510f0316b67b0807aa86920b67
7
+ data.tar.gz: 8cf9a3bbfde78b7930089a7ada109e03bba5439ea8ef3049d3fa58bc589914881969be783fe667ef5c238613e126c7f79d7dd0db30fcd2f5d9147999086d1df7
data/Puppetfile CHANGED
@@ -6,7 +6,7 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '2.0.0'
9
- mod 'puppetlabs-puppet_agent', '4.5.0'
9
+ mod 'puppetlabs-puppet_agent', '4.6.1'
10
10
  mod 'puppetlabs-facts', '1.4.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
@@ -17,7 +17,7 @@ mod 'puppetlabs-sshkeys_core', '2.2.0'
17
17
  mod 'puppetlabs-zfs_core', '1.2.0'
18
18
  mod 'puppetlabs-cron_core', '1.0.5'
19
19
  mod 'puppetlabs-mount_core', '1.0.4'
20
- mod 'puppetlabs-selinux_core', '1.0.4'
20
+ mod 'puppetlabs-selinux_core', '1.1.0'
21
21
  mod 'puppetlabs-yumrepo_core', '1.0.7'
22
22
  mod 'puppetlabs-zone_core', '1.0.3'
23
23
 
@@ -29,7 +29,7 @@ mod 'puppetlabs-python_task_helper', '0.5.0'
29
29
  mod 'puppetlabs-reboot', '4.0.2'
30
30
  mod 'puppetlabs-ruby_task_helper', '0.6.0'
31
31
  mod 'puppetlabs-ruby_plugin_helper', '0.2.0'
32
- mod 'puppetlabs-stdlib', '7.0.0'
32
+ mod 'puppetlabs-stdlib', '7.0.1'
33
33
 
34
34
  # Plugin modules
35
35
  mod 'puppetlabs-aws_inventory', '0.7.0'
@@ -108,7 +108,11 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
108
108
  # Send Analytics Report
109
109
  executor.report_function_call(self.class.name)
110
110
 
111
- found = Puppet::Parser::Files.find_file(script, scope.compiler.environment)
111
+ future = executor&.future || {}
112
+ fallback = future.fetch('file_paths', false)
113
+
114
+ # Find the file path if it exists, otherwise return nil
115
+ found = Bolt::Util.find_file_from_scope(script, scope, fallback)
112
116
  unless found && Puppet::FileSystem.exist?(found)
113
117
  raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
114
118
  Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: script
@@ -70,7 +70,11 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
70
70
  # Send Analytics Report
71
71
  executor.report_function_call(self.class.name)
72
72
 
73
- found = Puppet::Parser::Files.find_file(source, scope.compiler.environment)
73
+ future = executor&.future || {}
74
+ fallback = future.fetch('file_paths', false)
75
+
76
+ # Find the file path if it exists, otherwise return nil
77
+ found = Bolt::Util.find_file_from_scope(source, scope, fallback)
74
78
  unless found && Puppet::FileSystem.exist?(found)
75
79
  raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
76
80
  Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: source
@@ -12,14 +12,20 @@ Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFu
12
12
  # file::exists('example/VERSION')
13
13
  dispatch :exists do
14
14
  scope_param
15
- required_param 'String', :filename
15
+ required_param 'String[1]', :filename
16
16
  return_type 'Boolean'
17
17
  end
18
18
 
19
19
  def exists(scope, filename)
20
20
  # Send Analytics Report
21
- Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
22
- found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
21
+ executor = Puppet.lookup(:bolt_executor) {}
22
+ executor&.report_function_call(self.class.name)
23
+
24
+ future = executor&.future || Puppet.lookup(:future) || {}
25
+ fallback = future.fetch('file_paths', false)
26
+
27
+ # Find the file path if it exists, otherwise return nil
28
+ found = Bolt::Util.find_file_from_scope(filename, scope, fallback)
23
29
  found ? Puppet::FileSystem.exist?(found) : false
24
30
  end
25
31
  end
@@ -11,7 +11,7 @@ Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunc
11
11
  # file::read('example/VERSION')
12
12
  dispatch :read do
13
13
  scope_param
14
- required_param 'String', :filename
14
+ required_param 'String[1]', :filename
15
15
  return_type 'String'
16
16
  end
17
17
 
@@ -20,7 +20,11 @@ Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunc
20
20
  executor = Puppet.lookup(:bolt_executor) {}
21
21
  executor&.report_function_call(self.class.name)
22
22
 
23
- found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
23
+ future = executor&.future || Puppet.lookup(:future) || {}
24
+ fallback = future.fetch('file_paths', false)
25
+
26
+ # Find the file path if it exists, otherwise return nil
27
+ found = Bolt::Util.find_file_from_scope(filename, scope, fallback)
24
28
  unless found && Puppet::FileSystem.exist?(found)
25
29
  raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
26
30
  Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: filename
@@ -12,15 +12,20 @@ Puppet::Functions.create_function(:'file::readable', Puppet::Functions::Internal
12
12
  # file::readable('example/VERSION')
13
13
  dispatch :readable do
14
14
  scope_param
15
- required_param 'String', :filename
15
+ required_param 'String[1]', :filename
16
16
  return_type 'Boolean'
17
17
  end
18
18
 
19
19
  def readable(scope, filename)
20
20
  # Send Analytics Report
21
- Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
21
+ executor = Puppet.lookup(:bolt_executor) {}
22
+ executor&.report_function_call(self.class.name)
22
23
 
23
- found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
24
+ future = executor&.future || Puppet.lookup(:future) || {}
25
+ fallback = future.fetch('file_paths', false)
26
+
27
+ # Find the file path if it exists, otherwise return nil
28
+ found = Bolt::Util.find_file_from_scope(filename, scope, fallback)
24
29
  found ? File.readable?(found) : false
25
30
  end
26
31
  end
@@ -87,7 +87,7 @@ module Bolt
87
87
  variables: @inventory.vars(target),
88
88
  trusted: trusted.to_h
89
89
  }
90
- catalog_request = scope.merge(target: target_data)
90
+ catalog_request = scope.merge(target: target_data).merge(future: @executor.future || {})
91
91
 
92
92
  bolt_catalog_exe = File.join(libexec, 'bolt_catalog')
93
93
  old_path = ENV['PATH']
@@ -66,6 +66,9 @@ module Bolt
66
66
  when 'guide'
67
67
  { flags: OPTIONS[:global] + %w[format],
68
68
  banner: GUIDE_HELP }
69
+ when 'lookup'
70
+ { flags: ACTION_OPTS + %w[hiera-config],
71
+ banner: LOOKUP_HELP }
69
72
  when 'module'
70
73
  case action
71
74
  when 'add'
@@ -187,6 +190,7 @@ module Bolt
187
190
  guide View guides for Bolt concepts and features
188
191
  inventory Show the list of targets an action would run on
189
192
  module Manage Bolt project modules
193
+ lookup Look up a value with Hiera
190
194
  plan Convert, create, show, and run Bolt plans
191
195
  project Create and migrate Bolt projects
192
196
  script Upload a local script and run it remotely
@@ -398,6 +402,24 @@ module Bolt
398
402
  To learn more about the inventory run 'bolt guide inventory'.
399
403
  HELP
400
404
 
405
+ LOOKUP_HELP = <<~HELP
406
+ #{colorize(:cyan, 'Name')}
407
+ lookup
408
+
409
+ #{colorize(:cyan, 'Usage')}
410
+ bolt lookup <key> {--targets TARGETS | --query QUERY | --rerun FILTER}
411
+ [options]
412
+
413
+ #{colorize(:cyan, 'Description')}
414
+ Look up a value with Hiera.
415
+
416
+ #{colorize(:cyan, 'Documentation')}
417
+ Learn more about using Hiera with Bolt at https://pup.pt/bolt-hiera.
418
+
419
+ #{colorize(:cyan, 'Examples')}
420
+ bolt lookup password --targets servers
421
+ HELP
422
+
401
423
  MODULE_HELP = <<~HELP
402
424
  #{colorize(:cyan, 'Name')}
403
425
  module
data/lib/bolt/catalog.rb CHANGED
@@ -65,7 +65,8 @@ module Bolt
65
65
  puppet_overrides = {
66
66
  bolt_pdb_client: pdb_client,
67
67
  bolt_inventory: inv,
68
- bolt_project: bolt_project
68
+ bolt_project: bolt_project,
69
+ future: request['future']
69
70
  }
70
71
 
71
72
  # Facts will be set by the catalog compiler, so we need to ensure
data/lib/bolt/cli.rb CHANGED
@@ -33,18 +33,19 @@ module Bolt
33
33
 
34
34
  class CLI
35
35
  COMMANDS = {
36
- 'command' => %w[run],
37
- 'script' => %w[run],
38
- 'task' => %w[show run],
39
- 'plan' => %w[show run convert new],
40
- 'file' => %w[download upload],
41
- 'secret' => %w[encrypt decrypt createkeys],
36
+ 'apply' => %w[],
37
+ 'command' => %w[run],
38
+ 'file' => %w[download upload],
39
+ 'group' => %w[show],
40
+ 'guide' => %w[],
42
41
  'inventory' => %w[show],
43
- 'group' => %w[show],
44
- 'project' => %w[init migrate],
45
- 'module' => %w[add generate-types install show],
46
- 'apply' => %w[],
47
- 'guide' => %w[]
42
+ 'lookup' => %w[],
43
+ 'module' => %w[add generate-types install show],
44
+ 'plan' => %w[show run convert new],
45
+ 'project' => %w[init migrate],
46
+ 'script' => %w[run],
47
+ 'secret' => %w[encrypt decrypt createkeys],
48
+ 'task' => %w[show run]
48
49
  }.freeze
49
50
 
50
51
  TARGETING_OPTIONS = %i[query rerun targets].freeze
@@ -326,6 +327,10 @@ module Bolt
326
327
  raise Bolt::CLIError, "a manifest file or --execute is required"
327
328
  end
328
329
 
330
+ if options[:subcommand] == 'lookup' && !options[:object]
331
+ raise Bolt::CLIError, "Must specify a key to look up"
332
+ end
333
+
329
334
  if options[:subcommand] == 'command' && (!options[:object] || options[:object].empty?)
330
335
  raise Bolt::CLIError, "Must specify a command to run"
331
336
  end
@@ -511,6 +516,8 @@ module Bolt
511
516
  when 'migrate'
512
517
  code = Bolt::ProjectManager.new(config, outputter, pal).migrate
513
518
  end
519
+ when 'lookup'
520
+ code = lookup(options[:object], options[:targets])
514
521
  when 'plan'
515
522
  case options[:action]
516
523
  when 'new'
@@ -563,7 +570,8 @@ module Bolt
563
570
  when 'command'
564
571
  executor.run_command(targets, options[:object], executor_opts)
565
572
  when 'script'
566
- script_path = find_file(options[:object])
573
+ script_path = find_file(options[:object], executor.future&.fetch('file_paths', false))
574
+ validate_file('script', script_path)
567
575
  executor.run_script(targets, script_path, options[:leftovers], executor_opts)
568
576
  when 'task'
569
577
  pal.run_task(options[:object],
@@ -588,8 +596,9 @@ module Bolt
588
596
  dest = File.expand_path(dest, Dir.pwd)
589
597
  executor.download_file(targets, src, dest, executor_opts)
590
598
  when 'upload'
591
- validate_file('source file', src, true)
592
- executor.upload_file(targets, src, dest, executor_opts)
599
+ src_path = find_file(src, executor.future&.fetch('file_paths', false))
600
+ validate_file('source file', src_path, true)
601
+ executor.upload_file(targets, src_path, dest, executor_opts)
593
602
  end
594
603
  end
595
604
  end
@@ -686,6 +695,38 @@ module Bolt
686
695
  outputter.print_groups(inventory.group_names.sort, inventory.source, config.default_inventoryfile)
687
696
  end
688
697
 
698
+ # Looks up a value with Hiera, using targets as the contexts to perform the
699
+ # look ups in.
700
+ #
701
+ def lookup(key, targets)
702
+ executor = Bolt::Executor.new(
703
+ config.concurrency,
704
+ analytics,
705
+ options[:noop],
706
+ config.modified_concurrency,
707
+ config.future
708
+ )
709
+
710
+ executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
711
+ executor.subscribe(log_outputter)
712
+ executor.publish_event(type: :plan_start, plan: nil)
713
+
714
+ results = outputter.spin do
715
+ pal.lookup(
716
+ key,
717
+ targets,
718
+ inventory,
719
+ executor,
720
+ config.concurrency
721
+ )
722
+ end
723
+
724
+ executor.shutdown
725
+ outputter.print_result_set(results)
726
+
727
+ results.ok ? 0 : 1
728
+ end
729
+
689
730
  def run_plan(plan_name, plan_arguments, nodes, options)
690
731
  unless nodes.empty?
691
732
  if plan_arguments['nodes'] || plan_arguments['targets']
@@ -929,20 +970,17 @@ module Bolt
929
970
  # the path is a Puppet file path and looks for the file in a module's files
930
971
  # directory.
931
972
  #
932
- def find_file(path)
933
- unless File.exist?(path) || Pathname.new(path).absolute?
934
- modulepath = Bolt::Config::Modulepath.new(config.modulepath)
935
- modules = Bolt::Module.discover(modulepath.full_modulepath, config.project)
936
- mod, file = path.split(File::SEPARATOR, 2)
937
-
938
- if modules[mod]
939
- @logger.debug("Did not find file at #{File.expand_path(path)}, checking in module '#{mod}'")
940
- path = File.join(modules[mod].path, 'files', file)
941
- end
973
+ def find_file(path, future_file_paths)
974
+ return path if File.exist?(path) || Pathname.new(path).absolute?
975
+ modulepath = Bolt::Config::Modulepath.new(config.modulepath)
976
+ modules = Bolt::Module.discover(modulepath.full_modulepath, config.project)
977
+ mod, file = path.split(File::SEPARATOR, 2)
978
+
979
+ if modules[mod]
980
+ @logger.debug("Did not find file at #{File.expand_path(path)}, checking in module '#{mod}'")
981
+ found = Bolt::Util.find_file_in_module(modules[mod].path, file || "", future_file_paths)
982
+ path = found.nil? ? File.join(modules[mod].path, 'files', file) : found
942
983
  end
943
-
944
- Bolt::Util.validate_file('script', path)
945
-
946
984
  path
947
985
  end
948
986
 
@@ -174,12 +174,16 @@ module Bolt
174
174
  @stream.puts(remove_trail(indent(2, result.message)))
175
175
  end
176
176
 
177
- # Use special handling if the result looks like a command or script result
178
- if result.generic_value.keys == %w[stdout stderr merged_output exit_code]
177
+ case result.action
178
+ when 'command', 'script'
179
179
  safe_value = result.safe_value
180
180
  @stream.puts(indent(2, safe_value['merged_output'])) unless safe_value['merged_output'].strip.empty?
181
- elsif result.generic_value.any?
182
- @stream.puts(indent(2, ::JSON.pretty_generate(result.generic_value)))
181
+ when 'lookup'
182
+ @stream.puts(indent(2, result['value']))
183
+ else
184
+ if result.generic_value.any?
185
+ @stream.puts(indent(2, ::JSON.pretty_generate(result.generic_value)))
186
+ end
183
187
  end
184
188
  end
185
189
  end
@@ -83,6 +83,10 @@ module Bolt
83
83
  @stream.puts result.to_json
84
84
  end
85
85
 
86
+ def print_result_set(result_set)
87
+ @stream.puts result_set.to_json
88
+ end
89
+
86
90
  def print_topics(topics)
87
91
  print_table('topics' => topics)
88
92
  end
data/lib/bolt/pal.rb CHANGED
@@ -615,5 +615,50 @@ module Bolt
615
615
  rescue Bolt::Error => e
616
616
  Bolt::PlanResult.new(e, 'failure')
617
617
  end
618
+
619
+ def lookup(key, targets, inventory, executor, _concurrency)
620
+ # Install the puppet-agent package and collect facts. Facts are
621
+ # automatically added to the targets.
622
+ in_plan_compiler(executor, inventory, nil) do |compiler|
623
+ compiler.call_function('apply_prep', targets)
624
+ end
625
+
626
+ overrides = {
627
+ bolt_inventory: inventory,
628
+ bolt_project: @project
629
+ }
630
+
631
+ # Do a lookup with a catalog compiler, which uses the 'hierarchy' key in
632
+ # Hiera config.
633
+ results = targets.map do |target|
634
+ node = Puppet::Node.from_data_hash(
635
+ 'name' => target.name,
636
+ 'parameters' => { 'clientcert' => target.name }
637
+ )
638
+
639
+ trusted = Puppet::Context::TrustedInformation.local(node).to_h
640
+
641
+ env_conf = {
642
+ modulepath: @modulepath.full_modulepath,
643
+ facts: target.facts,
644
+ variables: target.vars
645
+ }
646
+
647
+ with_puppet_settings do
648
+ Puppet::Pal.in_tmp_environment(target.name, **env_conf) do |pal|
649
+ Puppet.override(overrides) do
650
+ Puppet.lookup(:pal_current_node).trusted_data = trusted
651
+ pal.with_catalog_compiler do |compiler|
652
+ Bolt::Result.for_lookup(target, key, compiler.call_function('lookup', key))
653
+ rescue StandardError => e
654
+ Bolt::Result.from_exception(target, e)
655
+ end
656
+ end
657
+ end
658
+ end
659
+ end
660
+
661
+ Bolt::ResultSet.new(results)
662
+ end
618
663
  end
619
664
  end
data/lib/bolt/result.rb CHANGED
@@ -28,6 +28,11 @@ module Bolt
28
28
  %w[file line].zip(position).to_h.compact
29
29
  end
30
30
 
31
+ def self.for_lookup(target, key, value)
32
+ val = { 'value' => value }
33
+ new(target, value: val, action: 'lookup', object: key)
34
+ end
35
+
31
36
  def self.for_command(target, value, action, command, position)
32
37
  details = create_details(position)
33
38
  unless value['exit_code'] == 0
data/lib/bolt/util.rb CHANGED
@@ -85,6 +85,66 @@ module Bolt
85
85
  Bolt::Config.user_path && !File.exist?(first_runs_free)
86
86
  end
87
87
 
88
+ # If Puppet is loaded, we aleady have the path to the module and should
89
+ # just get it. This takes the path to a file provided by the user and a
90
+ # Puppet Parser scope object and tries to find the file, either as an
91
+ # absolute path or Puppet module syntax lookup. Returns the path to the
92
+ # file if found, or nil.
93
+ #
94
+ def find_file_from_scope(file, scope, fallback = false)
95
+ # If we got an absolute path, just return that.
96
+ return file if Pathname.new(file).absolute?
97
+
98
+ module_name, file_pattern = Bolt::Util.split_path(file)
99
+ # Get the absolute path to the module root from the scope
100
+ mod_path = scope.compiler.environment.module(module_name)&.path
101
+
102
+ # Search the module for the file, falling back to new-style paths if enabled.
103
+ find_file_in_module(mod_path, file_pattern, fallback) if mod_path
104
+ end
105
+
106
+ # This method is used by Bolt to find files when provided a
107
+ # module-style path without loading Puppet. It takes the absolute path to
108
+ # the module root and the module-style path minus the module name and
109
+ # searches subdirectories in the module in order of precedence.
110
+ #
111
+ def find_file_in_module(module_path, module_file, fallback = false)
112
+ # If the first part of the path is 'scripts' or 'files', the path may
113
+ # be a new-style file location and should fall back to the new path.
114
+ subdir_or_file = split_path(module_file).first
115
+ case subdir_or_file
116
+ # For any subdirs that may indicate the user passed a new-style path,
117
+ # first look in 'mymod/files/<relative_path>' (old-style) then fall
118
+ # back to 'mymod/<relative_path>' (new-style) if enabled.
119
+ when 'scripts', 'files'
120
+ search_module(module_path, module_file, fallback)
121
+ else
122
+ # If the path definitely isn't new-style, only look in the 'files/'
123
+ # directory.
124
+ search_module(module_path, module_file)
125
+ end
126
+ end
127
+
128
+ # This searches a module for files under 'files/' or 'scripts/',
129
+ # optionally falling back to the new style of file loading. It takes the
130
+ # absolute path to the module root, the relative path provided by the
131
+ # user, and whether to fall back to the new-style script loading if the
132
+ # file isn't found in 'files/'.
133
+ #
134
+ private def search_module(module_path, module_file, fallback = false)
135
+ if File.exist?(File.join(module_path, 'files', module_file))
136
+ File.join(module_path, 'files', module_file)
137
+ elsif File.exist?(File.join(module_path, module_file)) && fallback
138
+ File.join(module_path, module_file)
139
+ end
140
+ end
141
+
142
+ # Copied directly from puppet/lib/puppet/parser/files.rb
143
+ #
144
+ def split_path(path)
145
+ path.split(File::SEPARATOR, 2)
146
+ end
147
+
88
148
  # Accepts a path with either 'plans' or 'tasks' in it and determines
89
149
  # the name of the module
90
150
  def module_name(path)
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.7.1'
4
+ VERSION = '3.8.0'
5
5
  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: 3.7.1
4
+ version: 3.8.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-04-26 00:00:00.000000000 Z
11
+ date: 2021-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable