bolt 2.28.0 → 2.33.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +15 -14
  3. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +1 -1
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +1 -1
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +1 -1
  10. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +2 -2
  12. data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
  13. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
  14. data/guides/logging.txt +18 -0
  15. data/guides/module.txt +19 -0
  16. data/guides/modulepath.txt +25 -0
  17. data/lib/bolt/bolt_option_parser.rb +50 -28
  18. data/lib/bolt/catalog.rb +1 -1
  19. data/lib/bolt/cli.rb +159 -112
  20. data/lib/bolt/config.rb +13 -1
  21. data/lib/bolt/config/modulepath.rb +30 -0
  22. data/lib/bolt/config/options.rb +38 -9
  23. data/lib/bolt/config/transport/options.rb +2 -2
  24. data/lib/bolt/error.rb +4 -0
  25. data/lib/bolt/executor.rb +13 -13
  26. data/lib/bolt/inventory.rb +10 -9
  27. data/lib/bolt/logger.rb +26 -19
  28. data/lib/bolt/module_installer.rb +198 -0
  29. data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
  30. data/lib/bolt/module_installer/puppetfile.rb +117 -0
  31. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  32. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  33. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  34. data/lib/bolt/module_installer/resolver.rb +76 -0
  35. data/lib/bolt/module_installer/specs.rb +93 -0
  36. data/lib/bolt/module_installer/specs/forge_spec.rb +84 -0
  37. data/lib/bolt/module_installer/specs/git_spec.rb +178 -0
  38. data/lib/bolt/outputter.rb +2 -45
  39. data/lib/bolt/outputter/human.rb +78 -18
  40. data/lib/bolt/outputter/json.rb +22 -7
  41. data/lib/bolt/outputter/logger.rb +2 -2
  42. data/lib/bolt/pal.rb +55 -45
  43. data/lib/bolt/pal/yaml_plan.rb +4 -2
  44. data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
  45. data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
  46. data/lib/bolt/plugin.rb +1 -1
  47. data/lib/bolt/plugin/module.rb +1 -1
  48. data/lib/bolt/project.rb +32 -22
  49. data/lib/bolt/project_migrator.rb +80 -0
  50. data/lib/bolt/project_migrator/base.rb +39 -0
  51. data/lib/bolt/project_migrator/config.rb +67 -0
  52. data/lib/bolt/project_migrator/inventory.rb +67 -0
  53. data/lib/bolt/project_migrator/modules.rb +200 -0
  54. data/lib/bolt/result.rb +23 -11
  55. data/lib/bolt/shell/bash.rb +15 -9
  56. data/lib/bolt/shell/powershell.rb +11 -6
  57. data/lib/bolt/transport/base.rb +18 -18
  58. data/lib/bolt/transport/docker.rb +23 -6
  59. data/lib/bolt/transport/orch.rb +23 -14
  60. data/lib/bolt/transport/remote.rb +2 -2
  61. data/lib/bolt/transport/simple.rb +6 -6
  62. data/lib/bolt/transport/ssh/connection.rb +1 -1
  63. data/lib/bolt/util.rb +41 -0
  64. data/lib/bolt/version.rb +1 -1
  65. data/lib/bolt_server/acl.rb +2 -2
  66. data/lib/bolt_server/base_config.rb +3 -3
  67. data/lib/bolt_server/schemas/partials/task.json +17 -2
  68. data/lib/bolt_server/transport_app.rb +93 -13
  69. data/lib/bolt_spec/bolt_context.rb +4 -2
  70. data/lib/bolt_spec/plans.rb +1 -1
  71. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
  72. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
  73. data/lib/bolt_spec/plans/mock_executor.rb +6 -6
  74. data/lib/bolt_spec/run.rb +1 -1
  75. metadata +31 -12
  76. data/lib/bolt/project_migrate.rb +0 -138
  77. data/lib/bolt/puppetfile.rb +0 -160
  78. data/lib/bolt/puppetfile/module.rb +0 -66
  79. data/lib/bolt_server/pe/pal.rb +0 -67
  80. data/modules/secure_env_vars/plans/init.pp +0 -20
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'bolt/error'
5
+
6
+ module Bolt
7
+ class ProjectMigrator
8
+ class Base
9
+ def initialize(outputter)
10
+ @outputter = outputter
11
+ end
12
+
13
+ protected def backup_file(origin_path, backup_dir)
14
+ unless File.exist?(origin_path)
15
+ @outputter.print_action_step(
16
+ "Could not find file #{origin_path}, skipping backup."
17
+ )
18
+ return
19
+ end
20
+
21
+ date = Time.new.strftime("%Y%m%d_%H%M%S%L")
22
+ FileUtils.mkdir_p(backup_dir)
23
+
24
+ filename = File.basename(origin_path)
25
+ backup_path = File.join(backup_dir, "#{filename}.#{date}.bak")
26
+
27
+ @outputter.print_action_step(
28
+ "Backing up #{filename} from #{origin_path} to #{backup_path}"
29
+ )
30
+
31
+ begin
32
+ FileUtils.cp(origin_path, backup_path)
33
+ rescue StandardError => e
34
+ raise Bolt::FileError.new("#{e.message}; unable to create backup of #{filename}.", origin_path)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/project_migrator/base'
4
+
5
+ module Bolt
6
+ class ProjectMigrator
7
+ class Config < Base
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)
10
+ end
11
+
12
+ private def bolt_yaml_to_bolt_project(config_file, project_file, inventory_file, backup_dir)
13
+ if File.exist?(project_file)
14
+ return true
15
+ end
16
+
17
+ unless File.exist?(config_file)
18
+ return true
19
+ end
20
+
21
+ @outputter.print_message "Migrating project configuration\n\n"
22
+
23
+ config_data = Bolt::Util.read_optional_yaml_hash(config_file, 'config')
24
+ transport_data, project_data = config_data.partition do |k, _|
25
+ Bolt::Config::INVENTORY_OPTIONS.keys.include?(k)
26
+ end.map(&:to_h)
27
+
28
+ if transport_data.any?
29
+ if File.exist?(inventory_file)
30
+ inventory_data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
31
+ merged = Bolt::Util.deep_merge(transport_data, inventory_data['config'] || {})
32
+ inventory_data['config'] = merged
33
+ backup_file(inventory_file, backup_dir)
34
+ else
35
+ FileUtils.touch(inventory_file)
36
+ inventory_data = { 'config' => transport_data }
37
+ end
38
+
39
+ backup_file(config_file, backup_dir)
40
+
41
+ begin
42
+ @outputter.print_action_step(
43
+ "Moving transportation configuration options '#{transport_data.keys.join(', ')}' "\
44
+ "from bolt.yaml to inventory.yaml"
45
+ )
46
+
47
+ File.write(inventory_file, inventory_data.to_yaml)
48
+ File.write(config_file, project_data.to_yaml)
49
+ rescue StandardError => e
50
+ raise Bolt::FileError.new("#{e.message}; unable to write inventory.", inventory_file)
51
+ end
52
+ end
53
+
54
+ @outputter.print_action_step("Renaming bolt.yaml to bolt-project.yaml")
55
+ FileUtils.mv(config_file, project_file)
56
+
57
+ @outputter.print_action_step(
58
+ "Successfully migrated config. Please add a 'name' key to bolt-project.yaml "\
59
+ "to use project-level tasks and plans. Learn more about projects by running "\
60
+ "'bolt guide project'."
61
+ )
62
+
63
+ true
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/project_migrator/base'
4
+
5
+ module Bolt
6
+ class ProjectMigrator
7
+ class Inventory < Base
8
+ def migrate(inventory_file, backup_dir)
9
+ inventory_1_to_2(inventory_file, backup_dir)
10
+ end
11
+
12
+ # Migrates an inventory v1 file to inventory v2.
13
+ #
14
+ private def inventory_1_to_2(inventory_file, backup_dir)
15
+ unless File.exist?(inventory_file)
16
+ return true
17
+ end
18
+
19
+ data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
20
+ data.delete('version') if data['version'] != 2
21
+ migrated = migrate_group(data)
22
+
23
+ return true unless migrated
24
+
25
+ @outputter.print_message "Migrating inventory\n\n"
26
+
27
+ backup_file(inventory_file, backup_dir)
28
+
29
+ begin
30
+ File.write(inventory_file, data.to_yaml)
31
+ @outputter.print_action_step(
32
+ "Successfully migrated Bolt inventory to the latest version."
33
+ )
34
+ true
35
+ rescue StandardError => e
36
+ raise Bolt::FileError.new(
37
+ "Unable to write to #{inventory_file}: #{e.message}. See "\
38
+ "http://pup.pt/bolt-inventory to manually update.",
39
+ inventory_file
40
+ )
41
+ end
42
+ end
43
+
44
+ # Walks an inventory hash and replaces all 'nodes' keys with 'targets'
45
+ # keys and all 'name' keys nested in a 'targets' hash with 'uri' keys.
46
+ # Data is modified in place.
47
+ #
48
+ private def migrate_group(group)
49
+ migrated = false
50
+ if group.key?('nodes')
51
+ migrated = true
52
+ targets = group['nodes'].map do |target|
53
+ target['uri'] = target.delete('name') if target.is_a?(Hash)
54
+ target
55
+ end
56
+ group.delete('nodes')
57
+ group['targets'] = targets
58
+ end
59
+ (group['groups'] || []).each do |subgroup|
60
+ migrated_group = migrate_group(subgroup)
61
+ migrated ||= migrated_group
62
+ end
63
+ migrated
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/project_migrator/base'
4
+
5
+ module Bolt
6
+ class ProjectMigrator
7
+ class Modules < Base
8
+ def migrate(project, configured_modulepath)
9
+ return true unless project.modules.nil?
10
+
11
+ @outputter.print_message "Migrating project modules\n\n"
12
+
13
+ config = project.project_file
14
+ puppetfile = project.puppetfile
15
+ managed_moduledir = project.managed_moduledir
16
+ modulepath = [(project.path + 'modules').to_s,
17
+ (project.path + 'site-modules').to_s,
18
+ (project.path + 'site').to_s]
19
+
20
+ # Notify user to manually migrate modules if using non-default modulepath
21
+ if configured_modulepath != modulepath
22
+ @outputter.print_action_step(
23
+ "Project has a non-default configured modulepath, unable to automatically "\
24
+ "migrate project modules. To migrate project modules manually, see "\
25
+ "http://pup.pt/bolt-modules"
26
+ )
27
+ true
28
+ # Migrate modules from Puppetfile
29
+ elsif File.exist?(puppetfile)
30
+ migrate_modules_from_puppetfile(config, puppetfile, managed_moduledir, modulepath)
31
+ # Migrate modules to updated modulepath
32
+ else
33
+ consolidate_modules(modulepath)
34
+ update_project_config([], config)
35
+ end
36
+ end
37
+
38
+ # Migrates modules by reading a Puppetfile and prompting the user for
39
+ # which ones are direct dependencies for the project. Once the user has
40
+ # selected the direct dependencies, this will resolve the modules, write a
41
+ # new Puppetfile, install the modules, and then move any remaining modules
42
+ # to the new moduledir.
43
+ #
44
+ private def migrate_modules_from_puppetfile(config, puppetfile_path, managed_moduledir, modulepath)
45
+ require 'bolt/module_installer/installer'
46
+ require 'bolt/module_installer/puppetfile'
47
+ require 'bolt/module_installer/resolver'
48
+ require 'bolt/module_installer/specs'
49
+
50
+ begin
51
+ @outputter.print_action_step("Parsing Puppetfile at #{puppetfile_path}")
52
+ puppetfile = Bolt::ModuleInstaller::Puppetfile.parse(puppetfile_path, skip_unsupported_modules: true)
53
+ rescue Bolt::Error => e
54
+ @outputter.print_action_error("#{e.message}\nSkipping module migration.")
55
+ return false
56
+ end
57
+
58
+ # Prompt for direct dependencies
59
+ modules = select_modules(puppetfile.modules)
60
+
61
+ # Create specs to resolve from
62
+ specs = Bolt::ModuleInstaller::Specs.new(modules.map(&:to_hash))
63
+
64
+ # Attempt to resolve dependencies
65
+ begin
66
+ @outputter.print_message('')
67
+ @outputter.print_action_step("Resolving module dependencies, this may take a moment")
68
+ puppetfile = Bolt::ModuleInstaller::Resolver.new.resolve(specs)
69
+ rescue Bolt::Error => e
70
+ @outputter.print_action_error("#{e.message}\nSkipping module migration.")
71
+ return false
72
+ end
73
+
74
+ migrate_managed_modules(puppetfile, puppetfile_path, managed_moduledir)
75
+
76
+ # Move remaining modules to 'modules'
77
+ consolidate_modules(modulepath)
78
+
79
+ # Delete old modules that are now managed
80
+ delete_modules(modulepath.first, puppetfile.modules)
81
+
82
+ # Add modules to project
83
+ update_project_config(modules.map(&:to_hash), config)
84
+ end
85
+
86
+ # Migrates the managed modules. If modules were selected to be managed,
87
+ # the Puppetfile is rewritten and modules are installed. If no modules
88
+ # were selected, the Puppetfile is deleted.
89
+ #
90
+ private def migrate_managed_modules(puppetfile, puppetfile_path, managed_moduledir)
91
+ if puppetfile.modules.any?
92
+ # Show the new Puppetfile content
93
+ message = "Generated new Puppetfile content:\n\n"
94
+ message += puppetfile.modules.map(&:to_spec).join("\n").to_s
95
+ @outputter.print_action_step(message)
96
+
97
+ # Write Puppetfile
98
+ @outputter.print_action_step("Updating Puppetfile at #{puppetfile_path}")
99
+ puppetfile.write(puppetfile_path, managed_moduledir)
100
+
101
+ # Install Puppetfile
102
+ @outputter.print_action_step("Syncing modules from #{puppetfile_path} to #{managed_moduledir}")
103
+ Bolt::ModuleInstaller::Installer.new.install(puppetfile_path, managed_moduledir)
104
+ else
105
+ @outputter.print_action_step(
106
+ "Project does not include any managed modules, deleting Puppetfile "\
107
+ "at #{puppetfile_path}"
108
+ )
109
+ FileUtils.rm(puppetfile_path)
110
+ end
111
+ end
112
+
113
+ # Prompts the user to select modules, returning a list of
114
+ # the selected modules.
115
+ #
116
+ private def select_modules(modules)
117
+ @outputter.print_action_step(
118
+ "Select modules that are direct dependencies of your project. Bolt will "\
119
+ "automatically manage dependencies for each module selected, so do not "\
120
+ "select a module's dependencies unless you use content from it directly "\
121
+ "in your project."
122
+ )
123
+
124
+ all = Bolt::Util.prompt_yes_no("Select all modules?", @outputter)
125
+ return modules if all
126
+
127
+ modules.select do |mod|
128
+ Bolt::Util.prompt_yes_no("Select #{mod.full_name}?", @outputter)
129
+ end
130
+ end
131
+
132
+ # Consolidates all modules on the modulepath to 'modules'.
133
+ #
134
+ private def consolidate_modules(modulepath)
135
+ moduledir, *sources = modulepath
136
+
137
+ sources.select! { |source| Dir.exist?(source) }
138
+
139
+ if sources.any?
140
+ @outputter.print_action_step(
141
+ "Moving modules from #{sources.join(', ')} to #{moduledir}"
142
+ )
143
+
144
+ FileUtils.mkdir_p(moduledir)
145
+ move_modules(moduledir, sources)
146
+ end
147
+ end
148
+
149
+ # Moves modules from a list of source directories to the specified
150
+ # moduledir, deleting the source directory after it's done.
151
+ #
152
+ private def move_modules(moduledir, sources)
153
+ moduledir = Pathname.new(moduledir)
154
+
155
+ sources.each do |source|
156
+ source = Pathname.new(source)
157
+
158
+ source.each_child do |mod|
159
+ next unless mod.directory?
160
+ next if (moduledir + mod.basename).directory?
161
+ FileUtils.mv(mod, moduledir)
162
+ end
163
+
164
+ FileUtils.rm_r(source)
165
+ end
166
+ end
167
+
168
+ # Deletes modules from a specified directory.
169
+ #
170
+ private def delete_modules(moduledir, modules)
171
+ @outputter.print_action_step("Cleaning up #{moduledir}")
172
+ moduledir = Pathname.new(moduledir)
173
+
174
+ modules.each do |mod|
175
+ path = moduledir + mod.name
176
+ FileUtils.rm_r(path) if path.directory?
177
+ end
178
+ end
179
+
180
+ # Adds a list of modules to the project configuration file.
181
+ #
182
+ private def update_project_config(modules, config_file)
183
+ @outputter.print_action_step("Updating project configuration at #{config_file}")
184
+ data = Bolt::Util.read_optional_yaml_hash(config_file, 'project')
185
+ data.merge!('modules' => modules)
186
+ data.delete('modulepath')
187
+
188
+ begin
189
+ File.write(config_file, data.to_yaml)
190
+ true
191
+ rescue StandardError => e
192
+ raise Bolt::FileError.new(
193
+ "Unable to write to #{config_file}: #{e.message}",
194
+ config_file
195
+ )
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -7,47 +7,58 @@ module Bolt
7
7
  class Result
8
8
  attr_reader :target, :value, :action, :object
9
9
 
10
- def self.from_exception(target, exception, action: 'action')
10
+ def self.from_exception(target, exception, action: 'action', position: [])
11
+ details = create_details(position)
11
12
  if exception.is_a?(Bolt::Error)
12
- error = exception.to_h
13
+ error = Bolt::Util.deep_merge({ 'details' => details }, exception.to_h)
13
14
  else
15
+ details['class'] = exception.class.to_s
14
16
  error = {
15
17
  'kind' => 'puppetlabs.tasks/exception-error',
16
18
  'issue_code' => 'EXCEPTION',
17
19
  'msg' => exception.message,
18
- 'details' => { 'class' => exception.class.to_s }
20
+ 'details' => details
19
21
  }
20
22
  error['details']['stack_trace'] = exception.backtrace.join('\n') if exception.backtrace
21
23
  end
22
24
  Result.new(target, error: error, action: action)
23
25
  end
24
26
 
25
- def self.for_command(target, stdout, stderr, exit_code, action, command)
27
+ def self.create_details(position)
28
+ %w[file line].zip(position).to_h.compact
29
+ end
30
+
31
+ def self.for_command(target, stdout, stderr, exit_code, action, command, position)
26
32
  value = {
27
33
  'stdout' => stdout,
28
34
  'stderr' => stderr,
29
35
  'exit_code' => exit_code
30
36
  }
37
+
38
+ details = create_details(position)
31
39
  unless exit_code == 0
40
+ details['exit_code'] = exit_code
32
41
  value['_error'] = {
33
42
  'kind' => 'puppetlabs.tasks/command-error',
34
43
  'issue_code' => 'COMMAND_ERROR',
35
44
  'msg' => "The command failed with exit code #{exit_code}",
36
- 'details' => { 'exit_code' => exit_code }
45
+ 'details' => details
37
46
  }
38
47
  end
39
48
  new(target, value: value, action: action, object: command)
40
49
  end
41
50
 
42
- def self.for_task(target, stdout, stderr, exit_code, task)
51
+ def self.for_task(target, stdout, stderr, exit_code, task, position)
43
52
  stdout.force_encoding('utf-8') unless stdout.encoding == Encoding::UTF_8
53
+
54
+ details = create_details(position)
44
55
  value = if stdout.valid_encoding?
45
56
  parse_hash(stdout) || { '_output' => stdout }
46
57
  else
47
58
  { '_error' => { 'kind' => 'puppetlabs.tasks/task-error',
48
59
  'issue_code' => 'TASK_ERROR',
49
60
  'msg' => 'The task result contained invalid UTF-8 on stdout',
50
- 'details' => {} } }
61
+ 'details' => details } }
51
62
  end
52
63
 
53
64
  if exit_code != 0 && value['_error'].nil?
@@ -60,24 +71,26 @@ module Bolt
60
71
  else
61
72
  "The task failed with exit code #{exit_code}"
62
73
  end
74
+ details['exit_code'] = exit_code
63
75
  value['_error'] = { 'kind' => 'puppetlabs.tasks/task-error',
64
76
  'issue_code' => 'TASK_ERROR',
65
77
  'msg' => msg,
66
- 'details' => { 'exit_code' => exit_code } }
78
+ 'details' => details }
67
79
  end
68
80
 
69
81
  if value.key?('_error')
70
82
  unless value['_error'].is_a?(Hash) && value['_error'].key?('msg')
83
+ details['original_error'] = value['_error']
71
84
  value['_error'] = {
72
85
  'msg' => "Invalid error returned from task #{task}: #{value['_error'].inspect}. Error "\
73
86
  "must be an object with a msg key.",
74
87
  'kind' => 'bolt/invalid-task-error',
75
- 'details' => { 'original_error' => value['_error'] }
88
+ 'details' => details
76
89
  }
77
90
  end
78
91
 
79
92
  value['_error']['kind'] ||= 'bolt/error'
80
- value['_error']['details'] ||= {}
93
+ value['_error']['details'] ||= details
81
94
  end
82
95
 
83
96
  if value.key?('_sensitive')
@@ -221,7 +234,6 @@ module Bolt
221
234
  def error
222
235
  if error_hash
223
236
  Puppet::DataTypes::Error.from_asserted_hash(error_hash)
224
-
225
237
  end
226
238
  end
227
239