bolt 2.25.0 → 2.30.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +4 -3
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
  5. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +1 -1
  6. data/lib/bolt/analytics.rb +7 -3
  7. data/lib/bolt/applicator.rb +21 -21
  8. data/lib/bolt/bolt_option_parser.rb +116 -26
  9. data/lib/bolt/catalog.rb +5 -3
  10. data/lib/bolt/cli.rb +194 -185
  11. data/lib/bolt/config.rb +61 -26
  12. data/lib/bolt/config/options.rb +35 -2
  13. data/lib/bolt/executor.rb +2 -2
  14. data/lib/bolt/inventory.rb +8 -1
  15. data/lib/bolt/inventory/group.rb +1 -1
  16. data/lib/bolt/inventory/inventory.rb +1 -1
  17. data/lib/bolt/inventory/target.rb +1 -1
  18. data/lib/bolt/logger.rb +35 -21
  19. data/lib/bolt/module_installer.rb +172 -0
  20. data/lib/bolt/outputter.rb +4 -0
  21. data/lib/bolt/outputter/human.rb +53 -11
  22. data/lib/bolt/outputter/json.rb +7 -1
  23. data/lib/bolt/outputter/logger.rb +3 -3
  24. data/lib/bolt/pal.rb +29 -20
  25. data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
  26. data/lib/bolt/plugin/module.rb +1 -1
  27. data/lib/bolt/plugin/puppetdb.rb +1 -1
  28. data/lib/bolt/project.rb +89 -28
  29. data/lib/bolt/project_migrator.rb +80 -0
  30. data/lib/bolt/project_migrator/base.rb +39 -0
  31. data/lib/bolt/project_migrator/config.rb +67 -0
  32. data/lib/bolt/project_migrator/inventory.rb +67 -0
  33. data/lib/bolt/project_migrator/modules.rb +198 -0
  34. data/lib/bolt/puppetdb/client.rb +1 -1
  35. data/lib/bolt/puppetdb/config.rb +1 -1
  36. data/lib/bolt/puppetfile.rb +142 -0
  37. data/lib/bolt/puppetfile/installer.rb +43 -0
  38. data/lib/bolt/puppetfile/module.rb +90 -0
  39. data/lib/bolt/r10k_log_proxy.rb +1 -1
  40. data/lib/bolt/rerun.rb +2 -2
  41. data/lib/bolt/result.rb +23 -0
  42. data/lib/bolt/shell.rb +1 -1
  43. data/lib/bolt/shell/bash.rb +1 -1
  44. data/lib/bolt/task.rb +1 -1
  45. data/lib/bolt/transport/base.rb +5 -5
  46. data/lib/bolt/transport/docker/connection.rb +1 -1
  47. data/lib/bolt/transport/local/connection.rb +1 -1
  48. data/lib/bolt/transport/ssh.rb +1 -1
  49. data/lib/bolt/transport/ssh/connection.rb +1 -1
  50. data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
  51. data/lib/bolt/transport/winrm.rb +1 -1
  52. data/lib/bolt/transport/winrm/connection.rb +1 -1
  53. data/lib/bolt/util.rb +52 -11
  54. data/lib/bolt/version.rb +1 -1
  55. data/lib/bolt_server/acl.rb +2 -2
  56. data/lib/bolt_server/base_config.rb +3 -3
  57. data/lib/bolt_server/config.rb +1 -1
  58. data/lib/bolt_server/file_cache.rb +12 -12
  59. data/lib/bolt_server/transport_app.rb +125 -26
  60. data/lib/bolt_spec/bolt_context.rb +4 -4
  61. data/lib/bolt_spec/plans/mock_executor.rb +1 -1
  62. metadata +15 -13
  63. data/lib/bolt/project_migrate.rb +0 -138
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/project_migrator/config'
4
+ require 'bolt/project_migrator/inventory'
5
+ require 'bolt/project_migrator/modules'
6
+
7
+ module Bolt
8
+ class ProjectMigrator
9
+ def initialize(config, outputter)
10
+ @config = config
11
+ @outputter = outputter
12
+ end
13
+
14
+ def migrate
15
+ unless $stdin.tty?
16
+ raise Bolt::Error.new(
17
+ "stdin is not a tty, unable to migrate project",
18
+ 'bolt/stdin-not-a-tty-error'
19
+ )
20
+ end
21
+
22
+ @outputter.print_message("Migrating project #{@config.project.path}\n\n")
23
+
24
+ @outputter.print_migrate_step(
25
+ "Migrating a Bolt project may make irreversible changes to the project's "\
26
+ "configuration and inventory files. Before continuing, make sure the "\
27
+ "project has a backup or uses a version control system."
28
+ )
29
+
30
+ return 0 unless Bolt::Util.prompt_yes_no("Continue with project migration?", @outputter)
31
+
32
+ @outputter.print_message('')
33
+
34
+ ok = migrate_inventory && migrate_config && migrate_modules
35
+
36
+ if ok
37
+ @outputter.print_message("Project successfully migrated")
38
+ else
39
+ @outputter.print_error("Project could not be migrated completely")
40
+ end
41
+
42
+ ok ? 0 : 1
43
+ end
44
+
45
+ # Migrates the project-level configuration file to the latest version.
46
+ #
47
+ private def migrate_config
48
+ migrator = Bolt::ProjectMigrator::Config.new(@outputter)
49
+
50
+ migrator.migrate(
51
+ @config.project.config_file,
52
+ @config.project.project_file,
53
+ @config.inventoryfile || @config.project.inventory_file,
54
+ @config.project.backup_dir
55
+ )
56
+ end
57
+
58
+ # Migrates the inventory file to the latest version.
59
+ #
60
+ private def migrate_inventory
61
+ migrator = Bolt::ProjectMigrator::Inventory.new(@outputter)
62
+
63
+ migrator.migrate(
64
+ @config.inventoryfile || @config.project.inventory_file,
65
+ @config.project.backup_dir
66
+ )
67
+ end
68
+
69
+ # Migrates the project's modules to use current best practices.
70
+ #
71
+ private def migrate_modules
72
+ migrator = Bolt::ProjectMigrator::Modules.new(@outputter)
73
+
74
+ migrator.migrate(
75
+ @config.project,
76
+ @config.modulepath
77
+ )
78
+ end
79
+ end
80
+ end
@@ -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_migrate_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_migrate_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_migrate_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_migrate_step("Renaming bolt.yaml to bolt-project.yaml")
55
+ FileUtils.mv(config_file, project_file)
56
+
57
+ @outputter.print_migrate_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_migrate_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,198 @@
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_migrate_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/puppetfile'
46
+ require 'bolt/puppetfile/installer'
47
+
48
+ begin
49
+ @outputter.print_migrate_step("Parsing Puppetfile at #{puppetfile_path}")
50
+ puppetfile = Bolt::Puppetfile.parse(puppetfile_path, skip_unsupported_modules: true)
51
+ rescue Bolt::Error => e
52
+ @outputter.print_migrate_error("#{e.message}\nSkipping module migration.")
53
+ return false
54
+ end
55
+
56
+ # Prompt for direct dependencies
57
+ modules = select_modules(puppetfile.modules)
58
+
59
+ # Create new Puppetfile object
60
+ puppetfile = Bolt::Puppetfile.new(modules)
61
+
62
+ # Attempt to resolve dependencies
63
+ begin
64
+ @outputter.print_message('')
65
+ @outputter.print_migrate_step("Resolving module dependencies, this may take a moment")
66
+ puppetfile.resolve
67
+ rescue Bolt::Error => e
68
+ @outputter.print_migrate_error("#{e.message}\nSkipping module migration.")
69
+ return false
70
+ end
71
+
72
+ migrate_managed_modules(puppetfile, puppetfile_path, managed_moduledir)
73
+
74
+ # Move remaining modules to 'modules'
75
+ consolidate_modules(modulepath)
76
+
77
+ # Delete old modules that are now managed
78
+ delete_modules(modulepath.first, puppetfile.modules)
79
+
80
+ # Add modules to project
81
+ update_project_config(modules.map(&:to_hash), config)
82
+ end
83
+
84
+ # Migrates the managed modules. If modules were selected to be managed,
85
+ # the Puppetfile is rewritten and modules are installed. If no modules
86
+ # were selected, the Puppetfile is deleted.
87
+ #
88
+ private def migrate_managed_modules(puppetfile, puppetfile_path, managed_moduledir)
89
+ if puppetfile.modules.any?
90
+ # Show the new Puppetfile content
91
+ message = "Generated new Puppetfile content:\n\n"
92
+ message += puppetfile.modules.map(&:to_spec).join("\n").to_s
93
+ @outputter.print_migrate_step(message)
94
+
95
+ # Write Puppetfile
96
+ @outputter.print_migrate_step("Updating Puppetfile at #{puppetfile_path}")
97
+ puppetfile.write(puppetfile_path, managed_moduledir)
98
+
99
+ # Install Puppetfile
100
+ @outputter.print_migrate_step("Syncing modules from #{puppetfile_path} to #{managed_moduledir}")
101
+ Bolt::Puppetfile::Installer.new({}).install(puppetfile_path, managed_moduledir)
102
+ else
103
+ @outputter.print_migrate_step(
104
+ "Project does not include any managed modules, deleting Puppetfile "\
105
+ "at #{puppetfile_path}"
106
+ )
107
+ FileUtils.rm(puppetfile_path)
108
+ end
109
+ end
110
+
111
+ # Prompts the user to select modules, returning a list of
112
+ # the selected modules.
113
+ #
114
+ private def select_modules(modules)
115
+ @outputter.print_migrate_step(
116
+ "Select modules that are direct dependencies of your project. Bolt will "\
117
+ "automatically manage dependencies for each module selected, so do not "\
118
+ "select a module's dependencies unless you use content from it directly "\
119
+ "in your project."
120
+ )
121
+
122
+ all = Bolt::Util.prompt_yes_no("Select all modules?", @outputter)
123
+ return modules if all
124
+
125
+ modules.select do |mod|
126
+ Bolt::Util.prompt_yes_no("Select #{mod.title}?", @outputter)
127
+ end
128
+ end
129
+
130
+ # Consolidates all modules on the modulepath to 'modules'.
131
+ #
132
+ private def consolidate_modules(modulepath)
133
+ moduledir, *sources = modulepath
134
+
135
+ sources.select! { |source| Dir.exist?(source) }
136
+
137
+ if sources.any?
138
+ @outputter.print_migrate_step(
139
+ "Moving modules from #{sources.join(', ')} to #{moduledir}"
140
+ )
141
+
142
+ FileUtils.mkdir_p(moduledir)
143
+ move_modules(moduledir, sources)
144
+ end
145
+ end
146
+
147
+ # Moves modules from a list of source directories to the specified
148
+ # moduledir, deleting the source directory after it's done.
149
+ #
150
+ private def move_modules(moduledir, sources)
151
+ moduledir = Pathname.new(moduledir)
152
+
153
+ sources.each do |source|
154
+ source = Pathname.new(source)
155
+
156
+ source.each_child do |mod|
157
+ next unless mod.directory?
158
+ next if (moduledir + mod.basename).directory?
159
+ FileUtils.mv(mod, moduledir)
160
+ end
161
+
162
+ FileUtils.rm_r(source)
163
+ end
164
+ end
165
+
166
+ # Deletes modules from a specified directory.
167
+ #
168
+ private def delete_modules(moduledir, modules)
169
+ @outputter.print_migrate_step("Cleaning up #{moduledir}")
170
+ moduledir = Pathname.new(moduledir)
171
+
172
+ modules.each do |mod|
173
+ path = moduledir + mod.name
174
+ FileUtils.rm_r(path) if path.directory?
175
+ end
176
+ end
177
+
178
+ # Adds a list of modules to the project configuration file.
179
+ #
180
+ private def update_project_config(modules, config_file)
181
+ @outputter.print_migrate_step("Updating project configuration at #{config_file}")
182
+ data = Bolt::Util.read_optional_yaml_hash(config_file, 'project')
183
+ data.merge!('modules' => modules)
184
+ data.delete('modulepath')
185
+
186
+ begin
187
+ File.write(config_file, data.to_yaml)
188
+ true
189
+ rescue StandardError => e
190
+ raise Bolt::FileError.new(
191
+ "Unable to write to #{config_file}: #{e.message}",
192
+ config_file
193
+ )
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end