openbolt 5.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. checksums.yaml +7 -0
  2. data/Puppetfile +52 -0
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +60 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
  5. data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
  6. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +71 -0
  7. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +55 -0
  8. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +65 -0
  9. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +93 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +33 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +38 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +208 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/background.rb +62 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +57 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +130 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +31 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +52 -0
  18. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +87 -0
  19. data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +34 -0
  20. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +35 -0
  21. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +74 -0
  22. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +97 -0
  23. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +47 -0
  24. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +52 -0
  25. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +40 -0
  26. data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +42 -0
  27. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
  28. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +106 -0
  29. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  30. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +291 -0
  31. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +145 -0
  32. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +164 -0
  33. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +211 -0
  34. data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +48 -0
  35. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +43 -0
  36. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +145 -0
  37. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +38 -0
  38. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +101 -0
  39. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +29 -0
  40. data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +131 -0
  41. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +59 -0
  42. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +39 -0
  43. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +50 -0
  44. data/bolt-modules/boltlib/types/planresult.pp +18 -0
  45. data/bolt-modules/boltlib/types/targetspec.pp +7 -0
  46. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +42 -0
  47. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +20 -0
  48. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
  49. data/bolt-modules/file/lib/puppet/functions/file/delete.rb +21 -0
  50. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +28 -0
  51. data/bolt-modules/file/lib/puppet/functions/file/join.rb +20 -0
  52. data/bolt-modules/file/lib/puppet/functions/file/read.rb +33 -0
  53. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +28 -0
  54. data/bolt-modules/file/lib/puppet/functions/file/write.rb +24 -0
  55. data/bolt-modules/log/lib/puppet/functions/log/debug.rb +39 -0
  56. data/bolt-modules/log/lib/puppet/functions/log/error.rb +40 -0
  57. data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +40 -0
  58. data/bolt-modules/log/lib/puppet/functions/log/info.rb +39 -0
  59. data/bolt-modules/log/lib/puppet/functions/log/trace.rb +39 -0
  60. data/bolt-modules/log/lib/puppet/functions/log/warn.rb +41 -0
  61. data/bolt-modules/out/lib/puppet/functions/out/message.rb +36 -0
  62. data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +35 -0
  63. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  64. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +65 -0
  65. data/bolt-modules/system/lib/puppet/functions/system/env.rb +20 -0
  66. data/exe/bolt +17 -0
  67. data/guides/debugging.yaml +27 -0
  68. data/guides/inventory.yaml +23 -0
  69. data/guides/links.yaml +12 -0
  70. data/guides/logging.yaml +17 -0
  71. data/guides/module.yaml +18 -0
  72. data/guides/modulepath.yaml +24 -0
  73. data/guides/project.yaml +21 -0
  74. data/guides/targets.yaml +28 -0
  75. data/guides/transports.yaml +22 -0
  76. data/lib/bolt/analytics.rb +233 -0
  77. data/lib/bolt/application.rb +806 -0
  78. data/lib/bolt/applicator.rb +368 -0
  79. data/lib/bolt/apply_inventory.rb +93 -0
  80. data/lib/bolt/apply_result.rb +154 -0
  81. data/lib/bolt/apply_target.rb +90 -0
  82. data/lib/bolt/bolt_option_parser.rb +1226 -0
  83. data/lib/bolt/catalog/logging.rb +15 -0
  84. data/lib/bolt/catalog.rb +144 -0
  85. data/lib/bolt/cli.rb +949 -0
  86. data/lib/bolt/config/modulepath.rb +30 -0
  87. data/lib/bolt/config/options.rb +673 -0
  88. data/lib/bolt/config/transport/base.rb +133 -0
  89. data/lib/bolt/config/transport/docker.rb +34 -0
  90. data/lib/bolt/config/transport/jail.rb +33 -0
  91. data/lib/bolt/config/transport/local.rb +39 -0
  92. data/lib/bolt/config/transport/lxd.rb +34 -0
  93. data/lib/bolt/config/transport/options.rb +431 -0
  94. data/lib/bolt/config/transport/orch.rb +41 -0
  95. data/lib/bolt/config/transport/podman.rb +33 -0
  96. data/lib/bolt/config/transport/remote.rb +24 -0
  97. data/lib/bolt/config/transport/ssh.rb +138 -0
  98. data/lib/bolt/config/transport/winrm.rb +63 -0
  99. data/lib/bolt/config.rb +515 -0
  100. data/lib/bolt/container_result.rb +105 -0
  101. data/lib/bolt/error.rb +194 -0
  102. data/lib/bolt/executor.rb +539 -0
  103. data/lib/bolt/fiber_executor.rb +190 -0
  104. data/lib/bolt/inventory/group.rb +446 -0
  105. data/lib/bolt/inventory/inventory.rb +391 -0
  106. data/lib/bolt/inventory/options.rb +139 -0
  107. data/lib/bolt/inventory/target.rb +293 -0
  108. data/lib/bolt/inventory.rb +120 -0
  109. data/lib/bolt/logger.rb +252 -0
  110. data/lib/bolt/module.rb +54 -0
  111. data/lib/bolt/module_installer/installer.rb +44 -0
  112. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  113. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  114. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  115. data/lib/bolt/module_installer/puppetfile.rb +131 -0
  116. data/lib/bolt/module_installer/resolver.rb +129 -0
  117. data/lib/bolt/module_installer/specs/forge_spec.rb +91 -0
  118. data/lib/bolt/module_installer/specs/git_spec.rb +150 -0
  119. data/lib/bolt/module_installer/specs/id/base.rb +116 -0
  120. data/lib/bolt/module_installer/specs/id/gitclone.rb +120 -0
  121. data/lib/bolt/module_installer/specs/id/github.rb +90 -0
  122. data/lib/bolt/module_installer/specs/id/gitlab.rb +92 -0
  123. data/lib/bolt/module_installer/specs.rb +95 -0
  124. data/lib/bolt/module_installer.rb +208 -0
  125. data/lib/bolt/node/errors.rb +55 -0
  126. data/lib/bolt/node/output.rb +29 -0
  127. data/lib/bolt/outputter/human.rb +958 -0
  128. data/lib/bolt/outputter/json.rb +205 -0
  129. data/lib/bolt/outputter/logger.rb +76 -0
  130. data/lib/bolt/outputter/rainbow.rb +118 -0
  131. data/lib/bolt/outputter.rb +57 -0
  132. data/lib/bolt/pal/issues.rb +19 -0
  133. data/lib/bolt/pal/logging.rb +17 -0
  134. data/lib/bolt/pal/yaml_plan/evaluator.rb +83 -0
  135. data/lib/bolt/pal/yaml_plan/loader.rb +94 -0
  136. data/lib/bolt/pal/yaml_plan/parameter.rb +63 -0
  137. data/lib/bolt/pal/yaml_plan/step/command.rb +45 -0
  138. data/lib/bolt/pal/yaml_plan/step/download.rb +37 -0
  139. data/lib/bolt/pal/yaml_plan/step/eval.rb +42 -0
  140. data/lib/bolt/pal/yaml_plan/step/message.rb +31 -0
  141. data/lib/bolt/pal/yaml_plan/step/plan.rb +42 -0
  142. data/lib/bolt/pal/yaml_plan/step/resources.rb +170 -0
  143. data/lib/bolt/pal/yaml_plan/step/script.rb +62 -0
  144. data/lib/bolt/pal/yaml_plan/step/task.rb +42 -0
  145. data/lib/bolt/pal/yaml_plan/step/upload.rb +37 -0
  146. data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
  147. data/lib/bolt/pal/yaml_plan/step.rb +223 -0
  148. data/lib/bolt/pal/yaml_plan/transpiler.rb +90 -0
  149. data/lib/bolt/pal/yaml_plan.rb +172 -0
  150. data/lib/bolt/pal.rb +847 -0
  151. data/lib/bolt/plan_creator.rb +219 -0
  152. data/lib/bolt/plan_future.rb +86 -0
  153. data/lib/bolt/plan_result.rb +44 -0
  154. data/lib/bolt/plugin/cache.rb +76 -0
  155. data/lib/bolt/plugin/env_var.rb +54 -0
  156. data/lib/bolt/plugin/module.rb +276 -0
  157. data/lib/bolt/plugin/prompt.rb +36 -0
  158. data/lib/bolt/plugin/puppet_connect_data.rb +84 -0
  159. data/lib/bolt/plugin/puppetdb.rb +124 -0
  160. data/lib/bolt/plugin/task.rb +72 -0
  161. data/lib/bolt/plugin.rb +380 -0
  162. data/lib/bolt/project.rb +219 -0
  163. data/lib/bolt/project_manager/config_migrator.rb +113 -0
  164. data/lib/bolt/project_manager/inventory_migrator.rb +67 -0
  165. data/lib/bolt/project_manager/migrator.rb +39 -0
  166. data/lib/bolt/project_manager/module_migrator.rb +203 -0
  167. data/lib/bolt/project_manager.rb +221 -0
  168. data/lib/bolt/puppetdb/client.rb +153 -0
  169. data/lib/bolt/puppetdb/config.rb +176 -0
  170. data/lib/bolt/puppetdb/instance.rb +146 -0
  171. data/lib/bolt/puppetdb.rb +15 -0
  172. data/lib/bolt/r10k_log_proxy.rb +30 -0
  173. data/lib/bolt/rerun.rb +55 -0
  174. data/lib/bolt/resource_instance.rb +133 -0
  175. data/lib/bolt/result.rb +247 -0
  176. data/lib/bolt/result_set.rb +128 -0
  177. data/lib/bolt/shell/bash/tmpdir.rb +62 -0
  178. data/lib/bolt/shell/bash.rb +516 -0
  179. data/lib/bolt/shell/powershell/snippets.rb +181 -0
  180. data/lib/bolt/shell/powershell.rb +365 -0
  181. data/lib/bolt/shell.rb +105 -0
  182. data/lib/bolt/target.rb +174 -0
  183. data/lib/bolt/task/puppet_server.rb +27 -0
  184. data/lib/bolt/task/run.rb +55 -0
  185. data/lib/bolt/task.rb +163 -0
  186. data/lib/bolt/transport/base.rb +252 -0
  187. data/lib/bolt/transport/docker/connection.rb +150 -0
  188. data/lib/bolt/transport/docker.rb +23 -0
  189. data/lib/bolt/transport/jail/connection.rb +81 -0
  190. data/lib/bolt/transport/jail.rb +21 -0
  191. data/lib/bolt/transport/local/connection.rb +106 -0
  192. data/lib/bolt/transport/local.rb +20 -0
  193. data/lib/bolt/transport/lxd/connection.rb +115 -0
  194. data/lib/bolt/transport/lxd.rb +26 -0
  195. data/lib/bolt/transport/orch/connection.rb +111 -0
  196. data/lib/bolt/transport/orch.rb +271 -0
  197. data/lib/bolt/transport/podman/connection.rb +102 -0
  198. data/lib/bolt/transport/podman.rb +19 -0
  199. data/lib/bolt/transport/remote.rb +41 -0
  200. data/lib/bolt/transport/simple.rb +54 -0
  201. data/lib/bolt/transport/ssh/connection.rb +321 -0
  202. data/lib/bolt/transport/ssh/exec_connection.rb +140 -0
  203. data/lib/bolt/transport/ssh.rb +48 -0
  204. data/lib/bolt/transport/winrm/connection.rb +378 -0
  205. data/lib/bolt/transport/winrm.rb +33 -0
  206. data/lib/bolt/util/format.rb +68 -0
  207. data/lib/bolt/util/puppet_log_level.rb +21 -0
  208. data/lib/bolt/util.rb +465 -0
  209. data/lib/bolt/validator.rb +227 -0
  210. data/lib/bolt/version.rb +5 -0
  211. data/lib/bolt.rb +8 -0
  212. data/lib/bolt_server/acl.rb +39 -0
  213. data/lib/bolt_server/base_config.rb +112 -0
  214. data/lib/bolt_server/config.rb +64 -0
  215. data/lib/bolt_server/file_cache.rb +200 -0
  216. data/lib/bolt_server/request_error.rb +11 -0
  217. data/lib/bolt_server/schemas/action-check_node_connections.json +14 -0
  218. data/lib/bolt_server/schemas/action-run_command.json +12 -0
  219. data/lib/bolt_server/schemas/action-run_script.json +47 -0
  220. data/lib/bolt_server/schemas/action-run_task.json +20 -0
  221. data/lib/bolt_server/schemas/action-upload_file.json +47 -0
  222. data/lib/bolt_server/schemas/partials/target-any.json +10 -0
  223. data/lib/bolt_server/schemas/partials/target-ssh.json +88 -0
  224. data/lib/bolt_server/schemas/partials/target-winrm.json +67 -0
  225. data/lib/bolt_server/schemas/partials/task.json +94 -0
  226. data/lib/bolt_server/schemas/transport-ssh.json +25 -0
  227. data/lib/bolt_server/schemas/transport-winrm.json +19 -0
  228. data/lib/bolt_server/transport_app.rb +554 -0
  229. data/lib/bolt_spec/bolt_context.rb +226 -0
  230. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +51 -0
  231. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
  232. data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +55 -0
  233. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +59 -0
  234. data/lib/bolt_spec/plans/action_stubs/task_stub.rb +57 -0
  235. data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +65 -0
  236. data/lib/bolt_spec/plans/action_stubs.rb +196 -0
  237. data/lib/bolt_spec/plans/mock_executor.rb +361 -0
  238. data/lib/bolt_spec/plans/publish_stub.rb +49 -0
  239. data/lib/bolt_spec/plans.rb +190 -0
  240. data/lib/bolt_spec/run.rb +246 -0
  241. data/lib/logging_extensions/logging.rb +13 -0
  242. data/libexec/apply_catalog.rb +130 -0
  243. data/libexec/bolt_catalog +68 -0
  244. data/libexec/custom_facts.rb +63 -0
  245. data/libexec/query_resources.rb +75 -0
  246. data/modules/aggregate/lib/puppet/functions/aggregate/count.rb +21 -0
  247. data/modules/aggregate/lib/puppet/functions/aggregate/nodes.rb +22 -0
  248. data/modules/aggregate/lib/puppet/functions/aggregate/targets.rb +21 -0
  249. data/modules/aggregate/plans/count.pp +56 -0
  250. data/modules/aggregate/plans/targets.pp +56 -0
  251. data/modules/canary/lib/puppet/functions/canary/merge.rb +13 -0
  252. data/modules/canary/lib/puppet/functions/canary/random_split.rb +22 -0
  253. data/modules/canary/lib/puppet/functions/canary/skip.rb +25 -0
  254. data/modules/canary/plans/init.pp +100 -0
  255. data/modules/puppet_connect/plans/test_input_data.pp +94 -0
  256. data/modules/puppetdb_fact/plans/init.pp +20 -0
  257. data/resources/bolt_bash_completion.sh +214 -0
  258. metadata +735 -0
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../bolt/project_manager/migrator'
4
+
5
+ module Bolt
6
+ class ProjectManager
7
+ class InventoryMigrator < Migrator
8
+ def migrate(inventory_file, backup_dir)
9
+ inventory1to2(inventory_file, backup_dir)
10
+ end
11
+
12
+ # Migrates an inventory v1 file to inventory v2.
13
+ #
14
+ private def inventory1to2(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,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require_relative '../../bolt/error'
5
+
6
+ module Bolt
7
+ class ProjectManager
8
+ class Migrator
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,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../bolt/project_manager/migrator'
4
+
5
+ module Bolt
6
+ class ProjectManager
7
+ class ModuleMigrator < Migrator
8
+ def migrate(project, configured_modulepath)
9
+ return true if project.managed_moduledir.exist?
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
+ new_modulepath = [(project.path + 'modules').to_s]
17
+ old_modulepath = [(project.path + 'modules').to_s,
18
+ (project.path + 'site-modules').to_s,
19
+ (project.path + 'site').to_s]
20
+
21
+ # Notify user to manually migrate modules if using non-default modulepath
22
+ if configured_modulepath != new_modulepath && configured_modulepath != old_modulepath
23
+ @outputter.print_action_step(
24
+ "Project has a non-default configured modulepath, unable to automatically "\
25
+ "migrate project modules. To migrate project modules manually, see "\
26
+ "http://pup.pt/bolt-modules"
27
+ )
28
+ true
29
+ # Migrate modules from Puppetfile
30
+ elsif File.exist?(puppetfile)
31
+ migrate_modules_from_puppetfile(config, puppetfile, managed_moduledir, old_modulepath)
32
+ # Migrate modules to updated modulepath
33
+ else
34
+ consolidate_modules(old_modulepath)
35
+ update_project_config([], config)
36
+ end
37
+ end
38
+
39
+ # Migrates modules by reading a Puppetfile and prompting the user for
40
+ # which ones are direct dependencies for the project. Once the user has
41
+ # selected the direct dependencies, this will resolve the modules, write a
42
+ # new Puppetfile, install the modules, and then move any remaining modules
43
+ # to the new moduledir.
44
+ #
45
+ private def migrate_modules_from_puppetfile(config, puppetfile_path, managed_moduledir, modulepath)
46
+ require_relative '../../bolt/module_installer/installer'
47
+ require_relative '../../bolt/module_installer/puppetfile'
48
+ require_relative '../../bolt/module_installer/resolver'
49
+ require_relative '../../bolt/module_installer/specs'
50
+
51
+ begin
52
+ @outputter.print_action_step("Parsing Puppetfile at #{puppetfile_path}")
53
+ puppetfile = Bolt::ModuleInstaller::Puppetfile.parse(puppetfile_path, skip_unsupported_modules: true)
54
+ rescue Bolt::Error => e
55
+ @outputter.print_action_error("#{e.message}\nSkipping module migration.")
56
+ return false
57
+ end
58
+
59
+ # Prompt for direct dependencies
60
+ modules = select_modules(puppetfile.modules)
61
+
62
+ # Create specs to resolve from
63
+ specs = Bolt::ModuleInstaller::Specs.new(modules.map(&:to_hash))
64
+
65
+ @outputter.start_spin
66
+ # Attempt to resolve dependencies
67
+ begin
68
+ @outputter.print_message('')
69
+ @outputter.print_action_step("Resolving module dependencies, this might take a moment")
70
+ puppetfile = Bolt::ModuleInstaller::Resolver.new.resolve(specs)
71
+ rescue Bolt::Error => e
72
+ @outputter.print_action_error("#{e.message}\nSkipping module migration.")
73
+ return false
74
+ end
75
+
76
+ migrate_managed_modules(puppetfile, puppetfile_path, managed_moduledir)
77
+ @outputter.stop_spin
78
+
79
+ # Move remaining modules to 'modules'
80
+ consolidate_modules(modulepath)
81
+
82
+ # Delete old modules that are now managed
83
+ delete_modules(modulepath.first, puppetfile.modules)
84
+
85
+ # Add modules to project
86
+ update_project_config(modules.map(&:to_hash), config)
87
+ end
88
+
89
+ # Migrates the managed modules. If modules were selected to be managed,
90
+ # the Puppetfile is rewritten and modules are installed. If no modules
91
+ # were selected, the Puppetfile is deleted.
92
+ #
93
+ private def migrate_managed_modules(puppetfile, puppetfile_path, managed_moduledir)
94
+ if puppetfile.modules.any?
95
+ # Show the new Puppetfile content
96
+ message = "Generated new Puppetfile content:\n\n"
97
+ message += puppetfile.modules.map(&:to_spec).join("\n").to_s
98
+ @outputter.print_action_step(message)
99
+
100
+ # Write Puppetfile
101
+ @outputter.print_action_step("Updating Puppetfile at #{puppetfile_path}")
102
+ puppetfile.write(puppetfile_path, managed_moduledir)
103
+
104
+ # Install Puppetfile
105
+ @outputter.print_action_step("Syncing modules from #{puppetfile_path} to #{managed_moduledir}")
106
+ Bolt::ModuleInstaller::Installer.new.install(puppetfile_path, managed_moduledir)
107
+ else
108
+ @outputter.print_action_step(
109
+ "Project does not include any managed modules, deleting Puppetfile "\
110
+ "at #{puppetfile_path}"
111
+ )
112
+ FileUtils.rm(puppetfile_path)
113
+ end
114
+ end
115
+
116
+ # Prompts the user to select modules, returning a list of
117
+ # the selected modules.
118
+ #
119
+ private def select_modules(modules)
120
+ @outputter.print_action_step(
121
+ "Select modules that are direct dependencies of your project. Bolt will "\
122
+ "automatically manage dependencies for each module selected, so do not "\
123
+ "select a module's dependencies unless you use content from it directly "\
124
+ "in your project."
125
+ )
126
+
127
+ all = Bolt::Util.prompt_yes_no("Select all modules?", @outputter)
128
+ return modules if all
129
+
130
+ modules.select do |mod|
131
+ Bolt::Util.prompt_yes_no("Select #{mod.full_name}?", @outputter)
132
+ end
133
+ end
134
+
135
+ # Consolidates all modules on the modulepath to 'modules'.
136
+ #
137
+ private def consolidate_modules(modulepath)
138
+ moduledir, *sources = modulepath
139
+
140
+ sources.select! { |source| Dir.exist?(source) }
141
+
142
+ if sources.any?
143
+ @outputter.print_action_step(
144
+ "Moving modules from #{sources.join(', ')} to #{moduledir}"
145
+ )
146
+
147
+ FileUtils.mkdir_p(moduledir)
148
+ move_modules(moduledir, sources)
149
+ end
150
+ end
151
+
152
+ # Moves modules from a list of source directories to the specified
153
+ # moduledir, deleting the source directory after it's done.
154
+ #
155
+ private def move_modules(moduledir, sources)
156
+ moduledir = Pathname.new(moduledir)
157
+
158
+ sources.each do |source|
159
+ source = Pathname.new(source)
160
+
161
+ source.each_child do |mod|
162
+ next unless mod.directory?
163
+ next if (moduledir + mod.basename).directory?
164
+ FileUtils.mv(mod, moduledir)
165
+ end
166
+
167
+ FileUtils.rm_r(source)
168
+ end
169
+ end
170
+
171
+ # Deletes modules from a specified directory.
172
+ #
173
+ private def delete_modules(moduledir, modules)
174
+ @outputter.print_action_step("Cleaning up #{moduledir}")
175
+ moduledir = Pathname.new(moduledir)
176
+
177
+ modules.each do |mod|
178
+ path = moduledir + mod.name
179
+ FileUtils.rm_r(path) if path.directory?
180
+ end
181
+ end
182
+
183
+ # Adds a list of modules to the project configuration file.
184
+ #
185
+ private def update_project_config(modules, config_file)
186
+ @outputter.print_action_step("Updating project configuration at #{config_file}")
187
+ data = Bolt::Util.read_optional_yaml_hash(config_file, 'project')
188
+ data.merge!('modules' => modules)
189
+ data.delete('modulepath')
190
+
191
+ begin
192
+ File.write(config_file, data.to_yaml)
193
+ true
194
+ rescue StandardError => e
195
+ raise Bolt::FileError.new(
196
+ "Unable to write to #{config_file}: #{e.message}",
197
+ config_file
198
+ )
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'project_manager/config_migrator'
4
+ require_relative 'project_manager/inventory_migrator'
5
+ require_relative '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
+ GITIGNORE_CONTENT = <<~GITIGNORE
40
+ .modules/
41
+ .resource_types/
42
+ bolt-debug.log
43
+ .plan_cache.json
44
+ .plugin_cache.json
45
+ .task_cache.json
46
+ .rerun.json
47
+ GITIGNORE
48
+
49
+ def initialize(config, outputter, pal)
50
+ @config = config
51
+ @outputter = outputter
52
+ @pal = pal
53
+ end
54
+
55
+ # Creates a new project at the specified directory.
56
+ #
57
+ def create(path, name, modules)
58
+ require_relative '../bolt/module_installer'
59
+
60
+ project = Pathname.new(File.expand_path(path))
61
+ old_config = project + 'bolt.yaml'
62
+ config = project + 'bolt-project.yaml'
63
+ puppetfile = project + 'Puppetfile'
64
+ moduledir = project + '.modules'
65
+ inventoryfile = project + 'inventory.yaml'
66
+ gitignore = project + '.gitignore'
67
+ project_name = name || File.basename(project)
68
+
69
+ if config.exist?
70
+ if modules
71
+ command = Bolt::Util.powershell? ? 'Add-BoltModule -Module' : 'bolt module add'
72
+ raise Bolt::Error.new(
73
+ "Found existing project directory with #{config.basename} at #{project}, "\
74
+ "unable to initialize project with modules. To add modules to the project, "\
75
+ "run '#{command} <module>' instead.",
76
+ 'bolt/existing-project-error'
77
+ )
78
+ else
79
+ raise Bolt::Error.new(
80
+ "Found existing project directory with #{config.basename} at #{project}, "\
81
+ "unable to initialize project.",
82
+ 'bolt/existing-project-error'
83
+ )
84
+ end
85
+ elsif old_config.exist?
86
+ command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
87
+ raise Bolt::Error.new(
88
+ "Found existing project directory with #{old_config.basename} at #{project}, "\
89
+ "unable to initialize project. #{old_config.basename} is deprecated. To "\
90
+ "update the project to current best practices, run '#{command}'.",
91
+ 'bolt/existing-project-error'
92
+ )
93
+ elsif modules && puppetfile.exist?
94
+ raise Bolt::Error.new(
95
+ "Found existing Puppetfile at #{puppetfile}, unable to initialize project "\
96
+ "with modules.",
97
+ 'bolt/existing-puppetfile-error'
98
+ )
99
+ elsif project_name !~ Bolt::Module::MODULE_NAME_REGEX
100
+ if name
101
+ raise Bolt::ValidationError,
102
+ "The provided project name '#{project_name}' is invalid; project name must "\
103
+ "begin with a lowercase letter and can include lowercase letters, "\
104
+ "numbers, and underscores."
105
+ else
106
+ command = Bolt::Util.powershell? ? 'New-BoltProject -Name' : 'bolt project init'
107
+ raise Bolt::ValidationError,
108
+ "The current directory name '#{project_name}' is an invalid project name. "\
109
+ "Please specify a name using '#{command} <name>'."
110
+ end
111
+ end
112
+
113
+ # If modules were specified, resolve and install first. We want to error
114
+ # early here and not initialize the project if the modules cannot be
115
+ # resolved and installed.
116
+ if modules
117
+ @outputter.start_spin
118
+ Bolt::ModuleInstaller.new(@outputter, @pal).install(modules, puppetfile, moduledir)
119
+ @outputter.stop_spin
120
+ end
121
+
122
+ data = { 'name' => project_name }
123
+ data['modules'] = modules || []
124
+
125
+ begin
126
+ File.write(config.to_path, data.to_yaml)
127
+ rescue StandardError => e
128
+ raise Bolt::FileError.new("Could not create bolt-project.yaml at #{project}: #{e.message}", nil)
129
+ end
130
+
131
+ unless inventoryfile.exist?
132
+ begin
133
+ File.write(inventoryfile.to_path, INVENTORY_TEMPLATE)
134
+ rescue StandardError => e
135
+ raise Bolt::FileError.new("Could not create inventory.yaml at #{project}: #{e.message}", nil)
136
+ end
137
+ end
138
+
139
+ unless gitignore.exist?
140
+ begin
141
+ File.write(gitignore.to_path, GITIGNORE_CONTENT)
142
+ rescue StandardError => e
143
+ raise Bolt::FileError.new("Could not create .gitignore at #{project}: #{e.message}", nil)
144
+ end
145
+ end
146
+
147
+ @outputter.print_message("Successfully created Bolt project at #{project}")
148
+
149
+ 0
150
+ end
151
+
152
+ # Migrates a project to use the latest file versions and best practices.
153
+ #
154
+ def migrate
155
+ unless $stdin.tty?
156
+ raise Bolt::Error.new(
157
+ "stdin is not a tty, unable to migrate project",
158
+ 'bolt/stdin-not-a-tty-error'
159
+ )
160
+ end
161
+
162
+ @outputter.print_message("Migrating project #{@config.project.path}\n\n")
163
+
164
+ @outputter.print_action_step(
165
+ "Migrating a Bolt project might make irreversible changes to the project's "\
166
+ "configuration and inventory files. Before continuing, make sure the "\
167
+ "project has a backup or uses a version control system."
168
+ )
169
+
170
+ return 0 unless Bolt::Util.prompt_yes_no("Continue with project migration?", @outputter)
171
+
172
+ @outputter.print_message('')
173
+
174
+ ok = migrate_inventory && migrate_config && migrate_modules
175
+
176
+ if ok
177
+ @outputter.print_message("Project successfully migrated")
178
+ else
179
+ @outputter.print_error("Project could not be migrated completely")
180
+ end
181
+
182
+ ok ? 0 : 1
183
+ end
184
+
185
+ # Migrates the project-level configuration file to the latest version.
186
+ #
187
+ private def migrate_config
188
+ migrator = ConfigMigrator.new(@outputter)
189
+ configfile = @config.project.path + 'bolt.yaml'
190
+
191
+ migrator.migrate(
192
+ configfile,
193
+ @config.project.project_file,
194
+ @config.inventoryfile || @config.project.inventory_file,
195
+ @config.project.backup_dir
196
+ )
197
+ end
198
+
199
+ # Migrates the inventory file to the latest version.
200
+ #
201
+ private def migrate_inventory
202
+ migrator = InventoryMigrator.new(@outputter)
203
+
204
+ migrator.migrate(
205
+ @config.inventoryfile || @config.project.inventory_file,
206
+ @config.project.backup_dir
207
+ )
208
+ end
209
+
210
+ # Migrates the project's modules to use current best practices.
211
+ #
212
+ private def migrate_modules
213
+ migrator = ModuleMigrator.new(@outputter)
214
+
215
+ migrator.migrate(
216
+ @config.project,
217
+ @config.modulepath[0...-1]
218
+ )
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'logging'
5
+ require_relative '../../bolt/puppetdb/instance'
6
+
7
+ module Bolt
8
+ module PuppetDB
9
+ class Client
10
+ # @param config [Hash] A map of default PuppetDB configuration.
11
+ # @param instances [Hash] A map of configuration for named PuppetDB instances.
12
+ # @param default [String] The name of PuppetDB instance to use as the default.
13
+ # @param project [String] The path to the Bolt project.
14
+ #
15
+ def initialize(config:, instances: {}, default: nil, project: nil)
16
+ @logger = Bolt::Logger.logger(self)
17
+
18
+ @instances = instances.transform_values do |instance_config|
19
+ Bolt::PuppetDB::Instance.new(config: instance_config, project: project)
20
+ end
21
+
22
+ @default_instance = if default
23
+ validate_instance(default)
24
+ @instances[default]
25
+ else
26
+ Bolt::PuppetDB::Instance.new(config: config, project: project, load_defaults: true)
27
+ end
28
+ end
29
+
30
+ # Checks whether a given named instance is configured, erroring if not.
31
+ #
32
+ # @param name [String] The name of the PuppetDB instance.
33
+ #
34
+ private def validate_instance(name)
35
+ unless @instances[name]
36
+ raise Bolt::PuppetDBError, "PuppetDB instance '#{name}' has not been configured, unable to connect"
37
+ end
38
+ end
39
+
40
+ # Yields the PuppetDB instance to connect to.
41
+ #
42
+ # @param name [String] The name of the PuppetDB instance.
43
+ # @yield [Bolt::PuppetDB::Instance]
44
+ #
45
+ private def with_instance(name = nil)
46
+ yield instance(name)
47
+ end
48
+
49
+ # Selects the PuppetDB instance to connect to. If an instance is not specified,
50
+ # the default instance is used.
51
+ #
52
+ # @param name [String] The name of the PuppetDB instance.
53
+ # @return [Bolt::PuppetDB::Instance]
54
+ #
55
+ def instance(name = nil)
56
+ if name
57
+ validate_instance(name)
58
+ @instances[name]
59
+ else
60
+ @default_instance
61
+ end
62
+ end
63
+
64
+ # Queries certnames from the PuppetDB instance.
65
+ #
66
+ # @param query [String] The PDB query.
67
+ # @param instance [String] The name of the PuppetDB instance.
68
+ #
69
+ def query_certnames(query, instance = nil)
70
+ return [] unless query
71
+
72
+ @logger.debug("Querying certnames")
73
+ results = make_query(query, nil, instance)
74
+
75
+ if results&.first && !results.first&.key?('certname')
76
+ fields = results.first&.keys
77
+ raise Bolt::PuppetDBError, "Query results did not contain a 'certname' field: got #{fields.join(', ')}"
78
+ end
79
+
80
+ results&.map { |result| result['certname'] }&.uniq
81
+ end
82
+
83
+ # Retrieve facts from PuppetDB for a list of nodes.
84
+ #
85
+ # @param certnames [Array] The list of certnames to retrieve facts for.
86
+ # @param instance [String] The name of the PuppetDB instance.
87
+ #
88
+ def facts_for_node(certnames, instance = nil)
89
+ return {} if certnames.empty? || certnames.nil?
90
+
91
+ certnames.uniq!
92
+ name_query = certnames.map { |c| ["=", "certname", c] }
93
+ name_query.insert(0, "or")
94
+
95
+ @logger.debug("Querying certnames")
96
+ result = make_query(name_query, 'inventory', instance)
97
+
98
+ result&.each_with_object({}) do |node, coll|
99
+ coll[node['certname']] = node['facts']
100
+ end
101
+ end
102
+
103
+ # Retrive fact values for a list of nodes.
104
+ #
105
+ # @param certnames [Array] The list of certnames to retrieve fact values for.
106
+ # @param facts [Array] The list of facts to retrive.
107
+ # @param instance [String] The name of the PuppetDB instance.
108
+ #
109
+ def fact_values(certnames = [], facts = [], instance = nil)
110
+ return {} if certnames.empty? || facts.empty?
111
+
112
+ certnames.uniq!
113
+ name_query = certnames.each_slice(100).map { |slice| ["in", "certname", ["array", slice]] }
114
+ name_query.unshift("or")
115
+
116
+ facts_query = facts.map { |f| ["=", "path", f] }
117
+ facts_query.unshift("or")
118
+
119
+ query = ['and', name_query, facts_query]
120
+
121
+ @logger.debug("Querying certnames")
122
+ result = make_query(query, 'fact-contents', instance)
123
+ result.map! { |h| h.delete_if { |k, _v| %w[environment name].include?(k) } }
124
+ result.group_by { |c| c['certname'] }
125
+ end
126
+
127
+ # Sends a command to PuppetDB using the commands API.
128
+ #
129
+ # @param command [String] The command to invoke.
130
+ # @param version [Integer] The version of the command to invoke.
131
+ # @param payload [Hash] The payload to send with the command.
132
+ # @param instance [String] The name of the PuppetDB instance.
133
+ #
134
+ def send_command(command, version, payload, instance = nil)
135
+ with_instance(instance) do |pdb|
136
+ pdb.send_command(command, version, payload)
137
+ end
138
+ end
139
+
140
+ # Sends a query to PuppetDB.
141
+ #
142
+ # @param query [String] The query to send to PuppetDB.
143
+ # @param path [String] The API path to append to the query URL.
144
+ # @param instance [String] The name of the PuppetDB instance.
145
+ #
146
+ def make_query(query, path = nil, instance = nil)
147
+ with_instance(instance) do |pdb|
148
+ pdb.make_query(query, path)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end