bolt 2.21.0 → 2.22.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: ef2991c1d3979e03b9070dc168be0e8c44c309914fd7eb02badc60e5b5d907bf
4
- data.tar.gz: 9508d434488486b8b16d062f910f6d3bcf8d7b3e89121ccb8033dd393a7a21db
3
+ metadata.gz: 60e995704052fee2778474c532df844e51ca0888017e28c883866e6b61a9678f
4
+ data.tar.gz: 8133b33137d67ba8880270f4ed20aa8346e13b2aa7ae2b0c390e3fbbba660172
5
5
  SHA512:
6
- metadata.gz: 351084ca010d6f3d051a588e0d216f07d32473026703378dd7003c9f71f411fa2499926d4e1c6272df55cffc8eeb360fbc6fff4c5a0930c14cba006dfbf3874c
7
- data.tar.gz: eef7d1b6b0dfe5584bd216bb5ebe421b61b9e25a4131509cf310f33fc75f7a3afa072e919215eefadb43ac7415cdd51546e6b634daf53d8a7a568366bb31ee68
6
+ metadata.gz: a057aebe5bd7ce01a8369f3c7a05371f0dab79143cd465f6bd2f0a0ad23fe897fcff6624d03f07ead07b86c888512166232182c294cd1188fcd8fb45b55f463d
7
+ data.tar.gz: 4c30348d86636398f25568dcd77af25e6eb8e54af5c9ce44125a110b8089c15415a4a051aa8aac066f7404f9ca7faf253dd69e350780ef8d24e34f385c1dd8de
@@ -66,6 +66,9 @@ module Bolt
66
66
  when 'convert'
67
67
  { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
68
68
  banner: PLAN_CONVERT_HELP }
69
+ when 'new'
70
+ { flags: OPTIONS[:global] + %w[configfile project],
71
+ banner: PLAN_NEW_HELP }
69
72
  when 'run'
70
73
  { flags: ACTION_OPTS + %w[params compile-concurrency tmpdir hiera-config],
71
74
  banner: PLAN_RUN_HELP }
@@ -159,10 +162,10 @@ module Bolt
159
162
  SUBCOMMANDS
160
163
  apply Apply Puppet manifest code
161
164
  command Run a command remotely
162
- file Upload a local file or directory
165
+ file Copy files between the controller and targets
163
166
  group Show the list of groups in the inventory
164
167
  inventory Show the list of targets an action would run on
165
- plan Convert, show, and run Bolt plans
168
+ plan Convert, create, show, and run Bolt plans
166
169
  project Create and migrate Bolt projects
167
170
  puppetfile Install and list modules and generate type references
168
171
  script Upload a local script and run it remotely
@@ -319,10 +322,11 @@ module Bolt
319
322
  bolt plan <action> [parameters] [options]
320
323
 
321
324
  DESCRIPTION
322
- Convert, show, and run Bolt plans.
325
+ Convert, create, show, and run Bolt plans.
323
326
 
324
327
  ACTIONS
325
328
  convert Convert a YAML plan to a Bolt plan
329
+ new Create a new plan in the current project
326
330
  run Run a plan on the specified targets
327
331
  show Show available plans and plan documentation
328
332
  HELP
@@ -345,6 +349,20 @@ module Bolt
345
349
  bolt plan convert path/to/plan/myplan.yaml
346
350
  HELP
347
351
 
352
+ PLAN_NEW_HELP = <<~HELP
353
+ NAME
354
+ new
355
+
356
+ USAGE
357
+ bolt plan new <plan> [options]
358
+
359
+ DESCRIPTION
360
+ Create a new plan in the current project.
361
+
362
+ EXAMPLES
363
+ bolt plan new myproject::myplan
364
+ HELP
365
+
348
366
  PLAN_RUN_HELP = <<~HELP
349
367
  NAME
350
368
  run
@@ -76,7 +76,15 @@ module Bolt
76
76
  target = request['target']
77
77
  plan_vars = shadow_vars('plan', request['plan_vars'], target['facts'])
78
78
  target_vars = shadow_vars('target', target['variables'], target['facts'])
79
- topscope_vars = target_vars.merge(plan_vars)
79
+
80
+ # Merge plan vars with target vars, while maintaining the order of the plan
81
+ # vars. It's critical that the order of plan vars is not changed, as Puppet
82
+ # will deserialize the variables in the order they appear. Variables may
83
+ # contain local references to variables that appear earlier in a plan. If
84
+ # these variables are moved before the variable they reference, Puppet will
85
+ # be unable to deserialize the data and raise an error.
86
+ topscope_vars = target_vars.reject { |k, _v| plan_vars.key?(k) }.merge(plan_vars)
87
+
80
88
  env_conf = { modulepath: request['modulepath'],
81
89
  facts: target['facts'],
82
90
  variables: topscope_vars }
@@ -31,7 +31,7 @@ module Bolt
31
31
  COMMANDS = { 'command' => %w[run],
32
32
  'script' => %w[run],
33
33
  'task' => %w[show run],
34
- 'plan' => %w[show run convert],
34
+ 'plan' => %w[show run convert new],
35
35
  'file' => %w[download upload],
36
36
  'puppetfile' => %w[install show-modules generate-types],
37
37
  'secret' => %w[encrypt decrypt createkeys],
@@ -283,6 +283,10 @@ module Bolt
283
283
  raise Bolt::CLIError, "Must specify a value to #{options[:action]}"
284
284
  end
285
285
 
286
+ if options[:subcommand] == 'plan' && options[:action] == 'new' && !options[:object]
287
+ raise Bolt::CLIError, "Must specify a plan name."
288
+ end
289
+
286
290
  if options.key?(:debug) && options.key?(:log)
287
291
  raise Bolt::CLIError, "Only one of '--debug' or '--log-level' may be specified"
288
292
  end
@@ -350,19 +354,11 @@ module Bolt
350
354
  # Initialize inventory and targets. Errors here are better to catch early.
351
355
  # options[:target_args] will contain a string/array version of the targetting options this is passed to plans
352
356
  # options[:targets] will contain a resolved set of Target objects
353
- unless options[:subcommand] == 'puppetfile' ||
354
- options[:subcommand] == 'secret' ||
355
- options[:subcommand] == 'project' ||
356
- options[:action] == 'show' ||
357
- options[:action] == 'convert'
357
+ unless %w[project puppetfile secret].include?(options[:subcommand]) ||
358
+ %w[convert new show].include?(options[:action])
358
359
  update_targets(options)
359
360
  end
360
361
 
361
- if options[:action] == 'convert'
362
- convert_plan(options[:object])
363
- return 0
364
- end
365
-
366
362
  screen = "#{options[:subcommand]}_#{options[:action]}"
367
363
  # submit a different screen for `bolt task show` and `bolt task show foo`
368
364
  if options[:action] == 'show' && options[:object]
@@ -414,6 +410,9 @@ module Bolt
414
410
  when 'show-modules'
415
411
  list_modules
416
412
  return 0
413
+ when 'convert'
414
+ convert_plan(options[:object])
415
+ return 0
417
416
  end
418
417
 
419
418
  message = 'There may be processes left executing on some nodes.'
@@ -431,7 +430,12 @@ module Bolt
431
430
  code = migrate_project
432
431
  end
433
432
  when 'plan'
434
- code = run_plan(options[:object], options[:task_options], options[:target_args], options)
433
+ case options[:action]
434
+ when 'new'
435
+ code = new_plan(options[:object])
436
+ when 'run'
437
+ code = run_plan(options[:object], options[:task_options], options[:target_args], options)
438
+ end
435
439
  when 'puppetfile'
436
440
  case options[:action]
437
441
  when 'generate-types'
@@ -551,6 +555,118 @@ module Bolt
551
555
  outputter.print_groups(groups)
552
556
  end
553
557
 
558
+ def new_plan(plan_name)
559
+ @logger.warn("Command 'bolt plan new' is experimental and subject to changes.")
560
+
561
+ if config.project.name.nil?
562
+ raise Bolt::Error.new(
563
+ "Project directory '#{config.project.path}' is not a named project. Unable to create "\
564
+ "a project-level plan. To name a project, set the 'name' key in the 'bolt-project.yaml' "\
565
+ "configuration file.",
566
+ "bolt/unnamed-project-error"
567
+ )
568
+ end
569
+
570
+ if plan_name !~ Bolt::Module::CONTENT_NAME_REGEX
571
+ message = <<~MESSAGE.chomp
572
+ Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
573
+ separated by double colons '::'.
574
+
575
+ Each name segment must begin with a lowercase letter, and may only include lowercase
576
+ letters, digits, and underscores.
577
+
578
+ Examples of valid plan names:
579
+ - #{config.project.name}
580
+ - #{config.project.name}::my_plan
581
+ MESSAGE
582
+
583
+ raise Bolt::ValidationError, message
584
+ end
585
+
586
+ prefix, *name_segments, basename = plan_name.split('::')
587
+
588
+ # If the plan name is just the project name, then create an 'init' plan.
589
+ # Otherwise, use the last name segment for the plan's filename.
590
+ basename ||= 'init'
591
+
592
+ unless prefix == config.project.name
593
+ message = "First segment of plan name '#{plan_name}' must match project name '#{config.project.name}'. "\
594
+ "Did you mean '#{config.project.name}::#{plan_name}'?"
595
+
596
+ raise Bolt::ValidationError, message
597
+ end
598
+
599
+ dir_path = config.project.plans_path.join(*name_segments)
600
+
601
+ %w[pp yaml].each do |ext|
602
+ next unless (path = config.project.plans_path + "#{basename}.#{ext}").exist?
603
+ raise Bolt::Error.new(
604
+ "A plan with the name '#{plan_name}' already exists at '#{path}', nothing to do.",
605
+ 'bolt/existing-plan-error'
606
+ )
607
+ end
608
+
609
+ begin
610
+ FileUtils.mkdir_p(dir_path)
611
+ rescue Errno::EEXIST => e
612
+ raise Bolt::Error.new(
613
+ "#{e.message}; unable to create plan directory '#{dir_path}'",
614
+ 'bolt/existing-file-error'
615
+ )
616
+ end
617
+
618
+ plan_path = dir_path + "#{basename}.yaml"
619
+
620
+ plan_template = <<~PLAN
621
+ # This is the structure of a simple plan. To learn more about writing
622
+ # YAML plans, see the documentation: http://pup.pt/bolt-yaml-plans
623
+
624
+ # The description sets the description of the plan that will appear
625
+ # in 'bolt plan show' output.
626
+ description: A plan created with bolt plan new
627
+
628
+ # The parameters key defines the parameters that can be passed to
629
+ # the plan.
630
+ parameters:
631
+ targets:
632
+ type: TargetSpec
633
+ description: A list of targets to run actions on
634
+ default: localhost
635
+
636
+ # The steps key defines the actions the plan will take in order.
637
+ steps:
638
+ - message: Hello from #{plan_name}
639
+ - name: command_step
640
+ command: whoami
641
+ targets: $targets
642
+
643
+ # The return key sets the return value of the plan.
644
+ return: $command_step
645
+ PLAN
646
+
647
+ begin
648
+ File.write(plan_path, plan_template)
649
+ rescue Errno::EACCES => e
650
+ raise Bolt::FileError.new(
651
+ "#{e.message}; unable to create plan",
652
+ plan_path
653
+ )
654
+ end
655
+
656
+ output = <<~OUTPUT
657
+ Created plan '#{plan_name}' at '#{plan_path}'
658
+
659
+ Show this plan with:
660
+ bolt plan show #{plan_name}
661
+ Run this plan with:
662
+ bolt plan run #{plan_name}
663
+ OUTPUT
664
+
665
+ outputter.print_message(output)
666
+
667
+ 0
668
+ end
669
+
554
670
  def run_plan(plan_name, plan_arguments, nodes, options)
555
671
  unless nodes.empty?
556
672
  if plan_arguments['nodes'] || plan_arguments['targets']
@@ -3,7 +3,8 @@
3
3
  # Is this Bolt::Pobs::Module?
4
4
  module Bolt
5
5
  class Module
6
- MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
6
+ CONTENT_NAME_REGEX = /\A[a-z][a-z0-9_]*(::[a-z][a-z0-9_]*)*\z/.freeze
7
+ MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
7
8
 
8
9
  def self.discover(modulepath)
9
10
  modulepath.each_with_object({}) do |path, mods|
@@ -3,6 +3,7 @@
3
3
  require 'pathname'
4
4
  require 'bolt/config'
5
5
  require 'bolt/pal'
6
+ require 'bolt/module'
6
7
 
7
8
  module Bolt
8
9
  class Project
@@ -17,7 +18,7 @@ module Bolt
17
18
 
18
19
  attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
19
20
  :puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file,
20
- :deprecations, :downloads
21
+ :deprecations, :downloads, :plans_path
21
22
 
22
23
  def self.default_project
23
24
  create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
@@ -82,6 +83,7 @@ module Bolt
82
83
  @resource_types = @path + '.resource_types'
83
84
  @type = type
84
85
  @downloads = @path + 'downloads'
86
+ @plans_path = @path + 'plans'
85
87
 
86
88
  tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
87
89
  if tc.any?
@@ -138,11 +140,9 @@ module Bolt
138
140
 
139
141
  def validate
140
142
  if name
141
- name_regex = /^[a-z][a-z0-9_]*$/
142
- if name !~ name_regex
143
- raise Bolt::ValidationError, <<~ERROR_STRING
144
- Invalid project name '#{name}' in bolt-project.yaml; project name must match #{name_regex.inspect}
145
- ERROR_STRING
143
+ if name !~ Bolt::Module::MODULE_NAME_REGEX
144
+ raise Bolt::ValidationError, "Invalid project name #{name.inspect.tr('"', "'")} in bolt-project.yaml; "\
145
+ "project name must match #{Bolt::Module::MODULE_NAME_REGEX.inspect}"
146
146
  elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
147
147
  raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
148
148
  "with a built-in Bolt module of the same name."
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.21.0'
4
+ VERSION = '2.22.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: 2.21.0
4
+ version: 2.22.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-08-03 00:00:00.000000000 Z
11
+ date: 2020-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable