bolt 2.32.0 → 2.36.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +6 -6
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +1 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -3
  5. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
  6. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
  8. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
  10. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
  11. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
  12. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
  13. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
  14. data/guides/logging.txt +18 -0
  15. data/lib/bolt/analytics.rb +27 -8
  16. data/lib/bolt/apply_result.rb +3 -3
  17. data/lib/bolt/bolt_option_parser.rb +43 -15
  18. data/lib/bolt/cli.rb +79 -227
  19. data/lib/bolt/config.rb +131 -52
  20. data/lib/bolt/config/options.rb +46 -8
  21. data/lib/bolt/config/transport/base.rb +10 -19
  22. data/lib/bolt/config/transport/local.rb +0 -7
  23. data/lib/bolt/config/transport/options.rb +1 -1
  24. data/lib/bolt/config/transport/ssh.rb +8 -14
  25. data/lib/bolt/config/validator.rb +231 -0
  26. data/lib/bolt/error.rb +37 -3
  27. data/lib/bolt/executor.rb +103 -17
  28. data/lib/bolt/inventory/group.rb +2 -1
  29. data/lib/bolt/module_installer.rb +2 -1
  30. data/lib/bolt/module_installer/specs/forge_spec.rb +5 -4
  31. data/lib/bolt/module_installer/specs/git_spec.rb +4 -3
  32. data/lib/bolt/outputter/human.rb +21 -9
  33. data/lib/bolt/outputter/rainbow.rb +1 -1
  34. data/lib/bolt/pal.rb +48 -30
  35. data/lib/bolt/pal/yaml_plan.rb +11 -2
  36. data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
  37. data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
  38. data/lib/bolt/plan_creator.rb +160 -0
  39. data/lib/bolt/plugin.rb +1 -8
  40. data/lib/bolt/project.rb +30 -36
  41. data/lib/bolt/project_manager.rb +199 -0
  42. data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +43 -5
  43. data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +5 -5
  44. data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
  45. data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +3 -3
  46. data/lib/bolt/puppetdb/client.rb +3 -2
  47. data/lib/bolt/puppetdb/config.rb +9 -8
  48. data/lib/bolt/result.rb +23 -11
  49. data/lib/bolt/shell/bash.rb +12 -7
  50. data/lib/bolt/shell/powershell.rb +12 -7
  51. data/lib/bolt/task/run.rb +1 -1
  52. data/lib/bolt/transport/base.rb +18 -18
  53. data/lib/bolt/transport/docker.rb +23 -6
  54. data/lib/bolt/transport/orch.rb +23 -19
  55. data/lib/bolt/transport/orch/connection.rb +10 -3
  56. data/lib/bolt/transport/remote.rb +3 -3
  57. data/lib/bolt/transport/simple.rb +6 -6
  58. data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
  59. data/lib/bolt/util.rb +19 -7
  60. data/lib/bolt/version.rb +1 -1
  61. data/lib/bolt/yarn.rb +23 -0
  62. data/lib/bolt_server/base_config.rb +3 -1
  63. data/lib/bolt_server/config.rb +3 -1
  64. data/lib/bolt_server/file_cache.rb +2 -0
  65. data/lib/bolt_server/schemas/partials/task.json +2 -2
  66. data/lib/bolt_server/transport_app.rb +42 -11
  67. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
  68. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
  69. data/lib/bolt_spec/plans/mock_executor.rb +9 -6
  70. data/libexec/apply_catalog.rb +1 -1
  71. data/libexec/custom_facts.rb +1 -1
  72. data/libexec/query_resources.rb +1 -1
  73. metadata +12 -14
  74. data/lib/bolt/project_migrator.rb +0 -80
  75. data/modules/secure_env_vars/plans/init.pp +0 -20
@@ -122,13 +122,6 @@ module Bolt
122
122
  def self.setup(config, pal, analytics = Bolt::Analytics::NoopClient.new)
123
123
  plugins = new(config, pal, analytics)
124
124
 
125
- # Initialize any plugins referenced in plugin config. This will also indirectly
126
- # initialize any plugins they depend on.
127
- if plugins.reference?(config.plugins)
128
- msg = "The 'plugins' setting cannot be set by a plugin reference"
129
- raise PluginError.new(msg, 'bolt/plugin-error')
130
- end
131
-
132
125
  config.plugins.each_key do |plugin|
133
126
  plugins.by_name(plugin)
134
127
  end
@@ -290,7 +283,7 @@ module Bolt
290
283
  begin
291
284
  validate_proc = get_hook(plugin_name, :validate_resolve_reference)
292
285
  rescue PluginError
293
- validate_proc = proc { |*args| }
286
+ validate_proc = proc { |*args| } # Nothing to do
294
287
  end
295
288
 
296
289
  validate_proc.call(reference)
@@ -2,19 +2,14 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'bolt/config'
5
+ require 'bolt/config/validator'
5
6
  require 'bolt/pal'
6
7
  require 'bolt/module'
7
8
 
8
9
  module Bolt
9
10
  class Project
10
11
  BOLTDIR_NAME = 'Boltdir'
11
- PROJECT_SETTINGS = {
12
- "name" => "The name of the project",
13
- "plans" => "An array of plan names to show, if they exist in the project."\
14
- "These plans are included in `bolt plan show` output",
15
- "tasks" => "An array of task names to show, if they exist in the project."\
16
- "These tasks are included in `bolt task show` output"
17
- }.freeze
12
+ CONFIG_NAME = 'bolt-project.yaml'
18
13
 
19
14
  attr_reader :path, :data, :config_file, :inventory_file, :hiera_config,
20
15
  :puppetfile, :rerunfile, :type, :resource_types, :logs, :project_file,
@@ -37,7 +32,7 @@ module Bolt
37
32
 
38
33
  if (dir + BOLTDIR_NAME).directory?
39
34
  create_project(dir + BOLTDIR_NAME, 'embedded', logs)
40
- elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
35
+ elsif (dir + 'bolt.yaml').file? || (dir + CONFIG_NAME).file?
41
36
  create_project(dir, 'local', logs)
42
37
  elsif dir.root?
43
38
  default_project(logs)
@@ -73,21 +68,37 @@ module Bolt
73
68
  )
74
69
  end
75
70
 
76
- project_file = File.join(fullpath, 'bolt-project.yaml')
77
- data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
78
- default = type =~ /user|system/ ? 'default ' : ''
79
- exist = File.exist?(File.expand_path(project_file))
71
+ project_file = File.join(fullpath, CONFIG_NAME)
72
+ data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
73
+ default = type =~ /user|system/ ? 'default ' : ''
74
+ exist = File.exist?(File.expand_path(project_file))
75
+ deprecations = []
76
+
80
77
  logs << { info: "Loaded #{default}project from '#{fullpath}'" } if exist
81
- new(data, path, type, logs)
82
- end
83
78
 
84
- def initialize(raw_data, path, type = 'option', logs = [])
85
- @path = Pathname.new(path).expand_path
79
+ # Validate the config against the schema. This will raise a single error
80
+ # with all validation errors.
81
+ schema = Bolt::Config::OPTIONS.slice(*Bolt::Config::BOLT_PROJECT_OPTIONS)
82
+
83
+ Bolt::Config::Validator.new.tap do |validator|
84
+ validator.validate(data, schema, project_file)
85
+
86
+ validator.warnings.each { |warning| logs << { warn: warning } }
87
+
88
+ validator.deprecations.each do |dep|
89
+ deprecations << { type: "#{CONFIG_NAME} #{dep[:option]}", msg: dep[:message] }
90
+ end
91
+ end
92
+
93
+ new(data, path, type, logs, deprecations)
94
+ end
86
95
 
87
- @project_file = @path + 'bolt-project.yaml'
96
+ def initialize(raw_data, path, type = 'option', logs = [], deprecations = [])
97
+ @path = Pathname.new(path).expand_path
98
+ @project_file = @path + CONFIG_NAME
99
+ @logs = logs
100
+ @deprecations = deprecations
88
101
 
89
- @logs = logs
90
- @deprecations = []
91
102
  if (@path + 'bolt.yaml').file? && project_file?
92
103
  msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
93
104
  "Transport config should be set in inventory.yaml, all other config should be set in "\
@@ -198,23 +209,6 @@ module Bolt
198
209
  message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
199
210
  @logs << { warn: message }
200
211
  end
201
-
202
- %w[tasks plans].each do |conf|
203
- unless @data.fetch(conf, []).is_a?(Array)
204
- raise Bolt::ValidationError, "'#{conf}' in bolt-project.yaml must be an array"
205
- end
206
- end
207
-
208
- if @data['modules']
209
- unless @data['modules'].is_a?(Array)
210
- raise Bolt::ValidationError, "'modules' in bolt-project.yaml must be an array"
211
- end
212
-
213
- @data['modules'].each do |spec|
214
- next if spec.is_a?(Hash) || spec.is_a?(String)
215
- raise Bolt::ValidationError, "Module specification #{spec.inspect} must be a hash or string"
216
- end
217
- end
218
212
  end
219
213
 
220
214
  def check_deprecated_file
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/project_manager/config_migrator'
4
+ require 'bolt/project_manager/inventory_migrator'
5
+ require 'bolt/project_manager/module_migrator'
6
+
7
+ module Bolt
8
+ class ProjectManager
9
+ INVENTORY_TEMPLATE = <<~INVENTORY
10
+ # This is an example inventory.yaml
11
+ # To read more about inventory files, see https://pup.pt/bolt-inventory
12
+ #
13
+ # groups:
14
+ # - name: linux
15
+ # targets:
16
+ # - target1.example.com
17
+ # - target2.example.com
18
+ # config:
19
+ # transport: ssh
20
+ # ssh:
21
+ # private-key: /path/to/private_key.pem
22
+ # - name: windows
23
+ # targets:
24
+ # - name: win1
25
+ # uri: target3.example.com
26
+ # - name: win2
27
+ # uri: target4.example.com
28
+ # config:
29
+ # transport: winrm
30
+ # config:
31
+ # ssh:
32
+ # host-key-check: false
33
+ # winrm:
34
+ # user: Administrator
35
+ # password: Bolt!
36
+ # ssl: false
37
+ INVENTORY
38
+
39
+ def initialize(config, outputter, pal)
40
+ @config = config
41
+ @outputter = outputter
42
+ @pal = pal
43
+ end
44
+
45
+ # Creates a new project at the specified directory.
46
+ #
47
+ def create(path, name, modules)
48
+ require 'bolt/module_installer'
49
+
50
+ project = Pathname.new(File.expand_path(path))
51
+ old_config = project + 'bolt.yaml'
52
+ config = project + 'bolt-project.yaml'
53
+ puppetfile = project + 'Puppetfile'
54
+ moduledir = project + '.modules'
55
+ inventoryfile = project + 'inventory.yaml'
56
+ project_name = name || File.basename(project)
57
+
58
+ if config.exist?
59
+ if modules
60
+ command = Bolt::Util.powershell? ? 'Add-BoltModule -Module' : 'bolt module add'
61
+ raise Bolt::Error.new(
62
+ "Found existing project directory with #{config.basename} at #{project}, "\
63
+ "unable to initialize project with modules. To add modules to the project, "\
64
+ "run '#{command} <module>' instead.",
65
+ 'bolt/existing-project-error'
66
+ )
67
+ else
68
+ raise Bolt::Error.new(
69
+ "Found existing project directory with #{config.basename} at #{project}, "\
70
+ "unable to initialize project.",
71
+ 'bolt/existing-project-error'
72
+ )
73
+ end
74
+ elsif old_config.exist?
75
+ command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
76
+ raise Bolt::Error.new(
77
+ "Found existing project directory with #{old_config.basename} at #{project}, "\
78
+ "unable to initialize project. #{old_config.basename} is deprecated. To "\
79
+ "update the project to current best practices, run '#{command}'.",
80
+ 'bolt/existing-project-error'
81
+ )
82
+ elsif modules && puppetfile.exist?
83
+ raise Bolt::Error.new(
84
+ "Found existing Puppetfile at #{puppetfile}, unable to initialize project "\
85
+ "with modules.",
86
+ 'bolt/existing-puppetfile-error'
87
+ )
88
+ elsif project_name !~ Bolt::Module::MODULE_NAME_REGEX
89
+ if name
90
+ raise Bolt::ValidationError,
91
+ "The provided project name '#{project_name}' is invalid; project name must "\
92
+ "begin with a lowercase letter and can include lowercase letters, "\
93
+ "numbers, and underscores."
94
+ else
95
+ command = Bolt::Util.powershell? ? 'New-BoltProject -Name' : 'bolt project init'
96
+ raise Bolt::ValidationError,
97
+ "The current directory name '#{project_name}' is an invalid project name. "\
98
+ "Please specify a name using '#{command} <name>'."
99
+ end
100
+ end
101
+
102
+ # If modules were specified, resolve and install first. We want to error
103
+ # early here and not initialize the project if the modules cannot be
104
+ # resolved and installed.
105
+ if modules
106
+ Bolt::ModuleInstaller.new(@outputter, @pal).install(modules, puppetfile, moduledir)
107
+ end
108
+
109
+ data = { 'name' => project_name }
110
+ data['modules'] = modules || []
111
+
112
+ begin
113
+ File.write(config.to_path, data.to_yaml)
114
+ rescue StandardError => e
115
+ raise Bolt::FileError.new("Could not create bolt-project.yaml at #{project}: #{e.message}", nil)
116
+ end
117
+
118
+ unless inventoryfile.exist?
119
+ begin
120
+ File.write(inventoryfile.to_path, INVENTORY_TEMPLATE)
121
+ rescue StandardError => e
122
+ raise Bolt::FileError.new("Could not create inventory.yaml at #{project}: #{e.message}", nil)
123
+ end
124
+ end
125
+
126
+ @outputter.print_message("Successfully created Bolt project at #{project}")
127
+
128
+ 0
129
+ end
130
+
131
+ # Migrates a project to use the latest file versions and best practices.
132
+ #
133
+ def migrate
134
+ unless $stdin.tty?
135
+ raise Bolt::Error.new(
136
+ "stdin is not a tty, unable to migrate project",
137
+ 'bolt/stdin-not-a-tty-error'
138
+ )
139
+ end
140
+
141
+ @outputter.print_message("Migrating project #{@config.project.path}\n\n")
142
+
143
+ @outputter.print_action_step(
144
+ "Migrating a Bolt project may make irreversible changes to the project's "\
145
+ "configuration and inventory files. Before continuing, make sure the "\
146
+ "project has a backup or uses a version control system."
147
+ )
148
+
149
+ return 0 unless Bolt::Util.prompt_yes_no("Continue with project migration?", @outputter)
150
+
151
+ @outputter.print_message('')
152
+
153
+ ok = migrate_inventory && migrate_config && migrate_modules
154
+
155
+ if ok
156
+ @outputter.print_message("Project successfully migrated")
157
+ else
158
+ @outputter.print_error("Project could not be migrated completely")
159
+ end
160
+
161
+ ok ? 0 : 1
162
+ end
163
+
164
+ # Migrates the project-level configuration file to the latest version.
165
+ #
166
+ private def migrate_config
167
+ migrator = ConfigMigrator.new(@outputter)
168
+
169
+ migrator.migrate(
170
+ @config.project.config_file,
171
+ @config.project.project_file,
172
+ @config.inventoryfile || @config.project.inventory_file,
173
+ @config.project.backup_dir
174
+ )
175
+ end
176
+
177
+ # Migrates the inventory file to the latest version.
178
+ #
179
+ private def migrate_inventory
180
+ migrator = InventoryMigrator.new(@outputter)
181
+
182
+ migrator.migrate(
183
+ @config.inventoryfile || @config.project.inventory_file,
184
+ @config.project.backup_dir
185
+ )
186
+ end
187
+
188
+ # Migrates the project's modules to use current best practices.
189
+ #
190
+ private def migrate_modules
191
+ migrator = ModuleMigrator.new(@outputter)
192
+
193
+ migrator.migrate(
194
+ @config.project,
195
+ @config.modulepath
196
+ )
197
+ end
198
+ end
199
+ end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bolt/project_migrator/base'
3
+ require 'bolt/project_manager/migrator'
4
4
 
5
5
  module Bolt
6
- class ProjectMigrator
7
- class Config < Base
6
+ class ProjectManager
7
+ class ConfigMigrator < Migrator
8
8
  def migrate(config_file, project_file, inventory_file, backup_dir)
9
- bolt_yaml_to_bolt_project(config_file, project_file, inventory_file, backup_dir)
9
+ bolt_yaml_to_bolt_project(config_file, project_file, inventory_file, backup_dir) &&
10
+ update_options(project_file)
10
11
  end
11
12
 
12
13
  private def bolt_yaml_to_bolt_project(config_file, project_file, inventory_file, backup_dir)
@@ -54,14 +55,51 @@ module Bolt
54
55
  @outputter.print_action_step("Renaming bolt.yaml to bolt-project.yaml")
55
56
  FileUtils.mv(config_file, project_file)
56
57
 
58
+ command = Bolt::Util.powershell? ? 'Get-Help about_bolt_project' : 'bolt guide project'
57
59
  @outputter.print_action_step(
58
60
  "Successfully migrated config. Please add a 'name' key to bolt-project.yaml "\
59
61
  "to use project-level tasks and plans. Learn more about projects by running "\
60
- "'bolt guide project'."
62
+ "'#{command}'."
61
63
  )
62
64
 
63
65
  true
64
66
  end
67
+
68
+ private def update_options(project_file)
69
+ return true unless File.exist?(project_file)
70
+
71
+ @outputter.print_message("Updating project configuration options\n\n")
72
+ data = Bolt::Util.read_yaml_hash(project_file, 'config')
73
+ modified = false
74
+
75
+ [%w[apply_settings apply-settings], %w[plugin_hooks plugin-hooks]].each do |old, new|
76
+ next unless data.key?(old)
77
+
78
+ if data.key?(new)
79
+ @outputter.print_action_step("Removing deprecated option '#{old}'")
80
+ data.delete(old)
81
+ else
82
+ @outputter.print_action_step("Updating deprecated option '#{old}' to '#{new}'")
83
+ data[new] = data.delete(old)
84
+ end
85
+
86
+ modified = true
87
+ end
88
+
89
+ if modified
90
+ begin
91
+ File.write(project_file, data.to_yaml)
92
+ rescue StandardError => e
93
+ raise Bolt::FileError.new("#{e.message}; unable to write config.", project_file)
94
+ end
95
+
96
+ @outputter.print_action_step("Successfully updated project configuration #{project_file}")
97
+ else
98
+ @outputter.print_action_step("Project configuration is up to date, nothing to do.")
99
+ end
100
+
101
+ true
102
+ end
65
103
  end
66
104
  end
67
105
  end
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bolt/project_migrator/base'
3
+ require 'bolt/project_manager/migrator'
4
4
 
5
5
  module Bolt
6
- class ProjectMigrator
7
- class Inventory < Base
6
+ class ProjectManager
7
+ class InventoryMigrator < Migrator
8
8
  def migrate(inventory_file, backup_dir)
9
- inventory_1_to_2(inventory_file, backup_dir)
9
+ inventory1to2(inventory_file, backup_dir)
10
10
  end
11
11
 
12
12
  # Migrates an inventory v1 file to inventory v2.
13
13
  #
14
- private def inventory_1_to_2(inventory_file, backup_dir)
14
+ private def inventory1to2(inventory_file, backup_dir)
15
15
  unless File.exist?(inventory_file)
16
16
  return true
17
17
  end
@@ -4,8 +4,8 @@ require 'fileutils'
4
4
  require 'bolt/error'
5
5
 
6
6
  module Bolt
7
- class ProjectMigrator
8
- class Base
7
+ class ProjectManager
8
+ class Migrator
9
9
  def initialize(outputter)
10
10
  @outputter = outputter
11
11
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bolt/project_migrator/base'
3
+ require 'bolt/project_manager/migrator'
4
4
 
5
5
  module Bolt
6
- class ProjectMigrator
7
- class Modules < Base
6
+ class ProjectManager
7
+ class ModuleMigrator < Migrator
8
8
  def migrate(project, configured_modulepath)
9
9
  return true unless project.modules.nil?
10
10
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'json'
4
4
  require 'logging'
5
- require 'uri'
6
5
 
7
6
  module Bolt
8
7
  module PuppetDB
@@ -108,13 +107,15 @@ module Bolt
108
107
  end
109
108
 
110
109
  def uri
110
+ require 'addressable/uri'
111
+
111
112
  @current_url ||= (@config.server_urls - @bad_urls).first
112
113
  unless @current_url
113
114
  msg = "Failed to connect to all PuppetDB server_urls: #{@config.server_urls.to_a.join(', ')}."
114
115
  raise Bolt::PuppetDBError, msg
115
116
  end
116
117
 
117
- uri = URI.parse(@current_url)
118
+ uri = Addressable::URI.parse(@current_url)
118
119
  uri.port ||= 8081
119
120
  uri
120
121
  end