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,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'open3'
6
+ require 'set'
7
+
8
+ require_relative '../../../bolt/error'
9
+ require_relative '../../../bolt/logger'
10
+ require_relative '../../../bolt/module_installer/specs/id/gitclone'
11
+ require_relative '../../../bolt/module_installer/specs/id/github'
12
+ require_relative '../../../bolt/module_installer/specs/id/gitlab'
13
+
14
+ # This class represents a Git module specification.
15
+ #
16
+ module Bolt
17
+ class ModuleInstaller
18
+ class Specs
19
+ class GitSpec
20
+ NAME_REGEX = %r{\A(?:[a-zA-Z0-9]+[-/])?(?<name>[a-z][a-z0-9_]*)\z}.freeze
21
+ REQUIRED_KEYS = Set.new(%w[git ref]).freeze
22
+ KNOWN_KEYS = Set.new(%w[git name ref resolve]).freeze
23
+
24
+ attr_reader :git, :ref, :resolve, :type
25
+
26
+ def initialize(init_hash, config = {})
27
+ @logger = Bolt::Logger.logger(self)
28
+ @resolve = init_hash.key?('resolve') ? init_hash['resolve'] : true
29
+ @git = init_hash['git']
30
+ @ref = init_hash['ref']
31
+ @name = parse_name(init_hash['name'])
32
+ @proxy = config.dig('proxy')
33
+ @type = :git
34
+
35
+ unless @resolve == true || @resolve == false
36
+ raise Bolt::ValidationError,
37
+ "Option 'resolve' for module spec #{@git} must be a Boolean"
38
+ end
39
+
40
+ if @name.nil? && @resolve == false
41
+ raise Bolt::ValidationError,
42
+ "Missing name for Git module specification: #{@git}. Git module specifications "\
43
+ "must include a 'name' key when 'resolve' is false."
44
+ end
45
+
46
+ unless valid_url?(@git)
47
+ raise Bolt::ValidationError,
48
+ "Invalid URI #{@git}. Valid URIs must begin with 'git@', 'http://', 'https://' or 'ssh://'."
49
+ end
50
+ end
51
+
52
+ def self.implements?(hash)
53
+ KNOWN_KEYS.superset?(hash.keys.to_set) && REQUIRED_KEYS.subset?(hash.keys.to_set)
54
+ end
55
+
56
+ # Parses the name into owner and name segments, and formats the full
57
+ # name.
58
+ #
59
+ private def parse_name(name)
60
+ return unless name
61
+
62
+ unless (match = name.match(NAME_REGEX))
63
+ raise Bolt::ValidationError,
64
+ "Invalid name for Git module specification: #{name}. Name must match "\
65
+ "'name' or 'owner/name'. Owner segment can only include letters or digits. "\
66
+ "Name segment must start with a lowercase letter and can only include "\
67
+ "lowercase letters, digits, and underscores."
68
+ end
69
+
70
+ match[:name]
71
+ end
72
+
73
+ # Returns true if the specification is satisfied by the module.
74
+ #
75
+ def satisfied_by?(mod)
76
+ @type == mod.type && @git == mod.git
77
+ end
78
+
79
+ # Returns a hash matching the module spec in bolt-project.yaml
80
+ #
81
+ def to_hash
82
+ {
83
+ 'git' => @git,
84
+ 'ref' => @ref
85
+ }
86
+ end
87
+
88
+ # Returns a PuppetfileResolver::Model::GitModule object for resolving.
89
+ #
90
+ def to_resolver_module
91
+ require 'puppetfile-resolver'
92
+
93
+ PuppetfileResolver::Puppetfile::GitModule.new(name).tap do |mod|
94
+ mod.remote = @git
95
+ mod.ref = sha
96
+ end
97
+ end
98
+
99
+ # Returns the module's name.
100
+ #
101
+ def name
102
+ @name ||= parse_name(id.name)
103
+ end
104
+
105
+ # Returns the SHA for the module's ref.
106
+ #
107
+ def sha
108
+ id.sha
109
+ end
110
+
111
+ # Gets the ID for the module based on the specified ref and git URL.
112
+ # This is lazily resolved since Bolt does not always need this information,
113
+ # and requesting it is expensive.
114
+ #
115
+ private def id
116
+ @id ||= begin
117
+ # The request methods here return an ID object if the module name and SHA
118
+ # were found and nil otherwise. This lets Bolt try multiple methods for
119
+ # finding the module name and SHA, and short circuiting as soon as it does.
120
+ module_id = Bolt::ModuleInstaller::Specs::ID::GitHub.request(@git, @ref, @proxy) ||
121
+ Bolt::ModuleInstaller::Specs::ID::GitLab.request(@git, @ref, @proxy) ||
122
+ Bolt::ModuleInstaller::Specs::ID::GitClone.request(@git, @ref, @proxy)
123
+
124
+ unless module_id
125
+ raise Bolt::Error.new(
126
+ "Unable to locate metadata and calculate SHA for ref #{@ref} at #{@git}. This may "\
127
+ "not be a valid module. For more information about how Bolt attempted to locate "\
128
+ "this information, check the debugging logs.",
129
+ 'bolt/missing-module-metadata-error'
130
+ )
131
+ end
132
+
133
+ module_id
134
+ end
135
+ end
136
+
137
+ # Returns true if the URL is valid.
138
+ #
139
+ private def valid_url?(url)
140
+ return true if url.start_with?('git@')
141
+
142
+ uri = URI.parse(url)
143
+ (uri.is_a?(URI::HTTP) || uri.scheme == "ssh") && uri.host
144
+ rescue URI::InvalidURIError
145
+ false
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ require_relative '../../../../bolt/error'
7
+ require_relative '../../../../bolt/logger'
8
+
9
+ module Bolt
10
+ class ModuleInstaller
11
+ class Specs
12
+ class ID
13
+ class Base
14
+ attr_reader :name, :sha
15
+
16
+ # @param name [String] The module's name.
17
+ # @param sha [String] The ref's SHA1.
18
+ #
19
+ def initialize(name, sha)
20
+ @name = name
21
+ @sha = sha
22
+ end
23
+
24
+ # Request the name and SHA for a module and ref.
25
+ # This method must return either an ID object or nil. The GitSpec
26
+ # class relies on this class to return an ID object to indicate
27
+ # the module was found, or nil to indicate that it should try to
28
+ # find it another way (such as cloning the repo).
29
+ #
30
+ # @param git [String] The URL to the git repo.
31
+ # @param ref [String] The ref to checkout.
32
+ # @param proxy [String] A proxy to use when making requests.
33
+ #
34
+ def self.request(git, ref, proxy)
35
+ name, sha = name_and_sha(git, ref, proxy)
36
+ name && sha ? new(name, sha) : nil
37
+ end
38
+
39
+ # Stub method for retrieving the module's name and SHA. Must
40
+ # be implemented by all sub classes.
41
+ #
42
+ private_class_method def self.name_and_sha(_git, _ref, _proxy)
43
+ raise NotImplementedError, 'Class does not implemented #name_and_sha'
44
+ end
45
+
46
+ # Makes a HTTP request.
47
+ #
48
+ # @param url [String] The URL to make the request to.
49
+ # @param proxy [String] A proxy to use when making the request.
50
+ # @param headers [Hash] Headers to send with the request.
51
+ #
52
+ private_class_method def self.make_request(url, proxy, headers = {})
53
+ uri = URI.parse(url)
54
+ opts = { use_ssl: uri.scheme == 'https' }
55
+ args = [uri.host, uri.port]
56
+
57
+ if proxy
58
+ proxy = URI.parse(proxy)
59
+ args += [proxy.host, proxy.port, proxy.user, proxy.password]
60
+ end
61
+
62
+ Bolt::Logger.debug("Making request to #{loc(url, proxy)}")
63
+
64
+ Net::HTTP.start(*args, opts) do |client|
65
+ client.request(Net::HTTP::Get.new(uri, headers))
66
+ end
67
+ rescue StandardError => e
68
+ raise Bolt::Error.new(
69
+ "Failed to connect to #{loc(uri, proxy)}: #{e.message}",
70
+ "bolt/http-connect-error"
71
+ )
72
+ end
73
+
74
+ # Returns a formatted string describing the URL and proxy used when making
75
+ # a request.
76
+ #
77
+ # @param url [String, URI::HTTP] The URL used.
78
+ # @param proxy [String, URI::HTTP] The proxy used.
79
+ #
80
+ private_class_method def self.loc(url, proxy)
81
+ proxy ? "#{url} with proxy #{proxy}" : url.to_s
82
+ end
83
+
84
+ # Parses the metadata and validates that it is a Hash.
85
+ #
86
+ # @param metadata [String] The JSON data to parse.
87
+ #
88
+ private_class_method def self.parse_name_from_metadata(metadata)
89
+ metadata = JSON.parse(metadata)
90
+
91
+ unless metadata.is_a?(Hash)
92
+ raise Bolt::Error.new(
93
+ "Invalid metadata. Expected a Hash, got a #{metadata.class}: #{metadata}",
94
+ "bolt/invalid-module-metadata-error"
95
+ )
96
+ end
97
+
98
+ unless metadata.key?('name')
99
+ raise Bolt::Error.new(
100
+ "Invalid metadata. Metadata must include a 'name' key.",
101
+ "bolt/missing-module-name-error"
102
+ )
103
+ end
104
+
105
+ metadata['name']
106
+ rescue JSON::ParserError => e
107
+ raise Bolt::Error.new(
108
+ "Unable to parse metadata as JSON: #{e.message}",
109
+ "bolt/metadata-parse-error"
110
+ )
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../bolt/module_installer/specs/id/base'
4
+
5
+ module Bolt
6
+ class ModuleInstaller
7
+ class Specs
8
+ class ID
9
+ class GitClone < Base
10
+ # Returns the name and SHA for the module at the given ref.
11
+ #
12
+ # @param git [String] The URL to the git repo.
13
+ # @param ref [String] The ref to checkout.
14
+ # @param proxy [String] The proxy to use when cloning.
15
+ #
16
+ private_class_method def self.name_and_sha(git, ref, proxy)
17
+ require 'open3'
18
+
19
+ unless git?
20
+ Bolt::Logger.debug("'git' executable not found, unable to use git clone resolution.")
21
+ return nil
22
+ end
23
+
24
+ # Clone the repo into a temp directory that will be automatically cleaned up.
25
+ Dir.mktmpdir do |dir|
26
+ return nil unless clone_repo(git, ref, dir, proxy)
27
+
28
+ # Extract the name from the metadata file and calculate the SHA.
29
+ Dir.chdir(dir) do
30
+ [request_name(git, ref), request_sha(git, ref)]
31
+ end
32
+ end
33
+ end
34
+
35
+ # Requests a module's metadata and returns the name from it.
36
+ #
37
+ # @param git [String] The URL to the git repo.
38
+ # @param ref [String] The ref to checkout.
39
+ #
40
+ private_class_method def self.request_name(git, ref)
41
+ command = %W[git show #{ref}:metadata.json]
42
+ Bolt::Logger.debug("Executing command '#{command.join(' ')}'")
43
+
44
+ out, err, status = Open3.capture3(*command)
45
+
46
+ unless status.success?
47
+ raise Bolt::Error.new(
48
+ "Unable to find metadata file at #{git}: #{err}",
49
+ "bolt/missing-metadata-file-error"
50
+ )
51
+ end
52
+
53
+ Bolt::Logger.debug("Found metadata file at #{git}")
54
+ parse_name_from_metadata(out)
55
+ end
56
+
57
+ # Requests the SHA for the specified ref.
58
+ #
59
+ # @param git [String] The URL to the git repo.
60
+ # @param ref [String] The ref to checkout.
61
+ #
62
+ private_class_method def self.request_sha(git, ref)
63
+ command = %W[git rev-parse #{ref}^{commit}]
64
+ Bolt::Logger.debug("Executing command '#{command.join(' ')}'")
65
+
66
+ out, err, status = Open3.capture3(*command)
67
+
68
+ if status.success?
69
+ out.strip
70
+ else
71
+ raise Bolt::Error.new(
72
+ "Unable to calculate SHA for ref #{ref} at #{git}: #{err}",
73
+ "bolt/invalid-ref-error"
74
+ )
75
+ end
76
+ end
77
+
78
+ # Clones the repository. First attempts to clone a bare repository
79
+ # and falls back to cloning the full repository if that fails. Cloning
80
+ # a bare repository is significantly faster for large modules, but
81
+ # cloning a bare repository using a commit is not supported.
82
+ #
83
+ # @param git [String] The URL to the git repo.
84
+ # @param ref [String] The ref to checkout.
85
+ # @param dir [String] The directory to clone the repo to.
86
+ # @param proxy [String] The proxy to use when cloning.
87
+ #
88
+ private_class_method def self.clone_repo(git, ref, dir, proxy)
89
+ clone = %W[git clone #{git} #{dir}]
90
+ clone += %W[--config "http.proxy=#{proxy}" --config "https.proxy=#{proxy}"] if proxy
91
+
92
+ bare_clone = clone + %w[--bare --depth=1]
93
+ bare_clone.push("--branch=#{ref}") unless ref == 'HEAD'
94
+
95
+ # Attempt to clone a bare repository
96
+ Bolt::Logger.debug("Executing command '#{bare_clone.join(' ')}'")
97
+ _out, err, status = Open3.capture3(*bare_clone)
98
+ return true if status.success?
99
+ Bolt::Logger.debug("Unable to clone bare repository at #{loc(git, proxy)}: #{err}")
100
+
101
+ # Fall back to cloning the full repository
102
+ Bolt::Logger.debug("Executing command '#{clone.join(' ')}'")
103
+ _out, err, status = Open3.capture3(*clone)
104
+ Bolt::Logger.debug("Unable to clone repository at #{loc(git, proxy)}: #{err}") unless status.success?
105
+ status.success?
106
+ end
107
+
108
+ # Returns true if the 'git' executable is available.
109
+ #
110
+ private_class_method def self.git?
111
+ Open3.capture3('git', '--version')
112
+ true
113
+ rescue Errno::ENOENT
114
+ false
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../bolt/module_installer/specs/id/base'
4
+
5
+ module Bolt
6
+ class ModuleInstaller
7
+ class Specs
8
+ class ID
9
+ class GitHub < Base
10
+ # Returns the name and SHA for the module at the given ref.
11
+ #
12
+ # @param git [String] The URL to the git repo.
13
+ # @param ref [String] The ref to use.
14
+ # @param proxy [String] The proxy to use when making requests.
15
+ #
16
+ private_class_method def self.name_and_sha(git, ref, proxy)
17
+ repo = parse_repo(git)
18
+ return nil unless repo
19
+ [request_name(repo, ref, proxy), request_sha(repo, ref, proxy)]
20
+ end
21
+
22
+ # Parses the repo path out of the URL.
23
+ #
24
+ # @param git [String] The URL to the git repo.
25
+ #
26
+ private_class_method def self.parse_repo(git)
27
+ if git.start_with?('git@github.com:')
28
+ git.split('git@github.com:').last.split('.git').first
29
+ elsif git.start_with?('https://github.com')
30
+ git.split('https://github.com/').last.split('.git').first
31
+ end
32
+ end
33
+
34
+ # Requests a module's metadata and returns the name from it.
35
+ #
36
+ # @param repo [String] The repo ID, i.e. 'owner/repo'
37
+ # @param ref [String] The ref to use.
38
+ # @param proxy [String] The proxy to use when making requests.
39
+ #
40
+ private_class_method def self.request_name(repo, ref, proxy)
41
+ metadata_url = "https://raw.githubusercontent.com/#{repo}/#{ref}/metadata.json"
42
+ response = make_request(metadata_url, proxy)
43
+
44
+ case response
45
+ when Net::HTTPOK
46
+ Bolt::Logger.debug("Found metadata file at #{loc(metadata_url, proxy)}")
47
+ parse_name_from_metadata(response.body)
48
+ else
49
+ Bolt::Logger.debug("Unable to find metadata file at #{loc(metadata_url, proxy)}")
50
+ nil
51
+ end
52
+ end
53
+
54
+ # Requests the SHA for the specified ref.
55
+ #
56
+ # @param repo [String] The repo ID, i.e. 'owner/repo'
57
+ # @param ref [String] The ref to resolve.
58
+ # @param proxy [String] The proxy to use when making requests.
59
+ #
60
+ private_class_method def self.request_sha(repo, ref, proxy)
61
+ url = "https://api.github.com/repos/#{repo}/commits/#{ref}"
62
+ headers = ENV['GITHUB_TOKEN'] ? { "Authorization" => "token #{ENV['GITHUB_TOKEN']}" } : {}
63
+ response = make_request(url, proxy, headers)
64
+
65
+ case response
66
+ when Net::HTTPOK
67
+ JSON.parse(response.body).fetch('sha', nil)
68
+ when Net::HTTPUnauthorized
69
+ Bolt::Logger.debug("Invalid token at GITHUB_TOKEN, unable to calculate SHA.")
70
+ nil
71
+ when Net::HTTPForbidden
72
+ message = "GitHub API rate limit exceeded, unable to calculate SHA."
73
+
74
+ unless ENV['GITHUB_TOKEN']
75
+ message += " To increase your rate limit, set the GITHUB_TOKEN environment "\
76
+ "variable with a GitHub personal access token."
77
+ end
78
+
79
+ Bolt::Logger.debug(message)
80
+ nil
81
+ else
82
+ Bolt::Logger.debug("Unable to calculate SHA for ref #{ref}")
83
+ nil
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../bolt/module_installer/specs/id/base'
4
+
5
+ module Bolt
6
+ class ModuleInstaller
7
+ class Specs
8
+ class ID
9
+ class GitLab < Base
10
+ # Returns the name and SHA for the module at the given ref.
11
+ #
12
+ # @param git [String] The URL to the git repo.
13
+ # @param ref [String] The ref to use.
14
+ # @param proxy [String] The proxy to use when making requests.
15
+ #
16
+ private_class_method def self.name_and_sha(git, ref, proxy)
17
+ repo = parse_repo(git)
18
+ return nil unless repo
19
+ [request_name(repo, ref, proxy), request_sha(repo, ref, proxy)]
20
+ end
21
+
22
+ # Parses the repo path out of the URL.
23
+ #
24
+ # @param git [String] The URL to the git repo.
25
+ #
26
+ private_class_method def self.parse_repo(git)
27
+ if git.start_with?('git@gitlab.com:')
28
+ git.split('git@gitlab.com:').last.split('.git').first
29
+ elsif git.start_with?('https://gitlab.com')
30
+ git.split('https://gitlab.com/').last.split('.git').first
31
+ end
32
+ end
33
+
34
+ # Requests a module's metadata and returns the name from it.
35
+ #
36
+ # @param repo [String] The repo ID, i.e. 'owner/repo'
37
+ # @param ref [String] The ref to use.
38
+ # @param proxy [String] The proxy to use when making requests.
39
+ #
40
+ private_class_method def self.request_name(repo, ref, proxy)
41
+ metadata_url = "https://gitlab.com/#{repo}/-/raw/#{ref}/metadata.json"
42
+ response = make_request(metadata_url, proxy)
43
+
44
+ case response
45
+ when Net::HTTPOK
46
+ Bolt::Logger.debug("Found metadata file at #{loc(metadata_url, proxy)}")
47
+ parse_name_from_metadata(response.body)
48
+ else
49
+ Bolt::Logger.debug("Unable to find metadata file at #{loc(metadata_url, proxy)}")
50
+ nil
51
+ end
52
+ end
53
+
54
+ # Requests the SHA for the specified ref.
55
+ #
56
+ # @param repo [String] The repo ID, i.e. 'owner/repo'
57
+ # @param ref [String] The ref to resolve.
58
+ # @param proxy [String] The proxy to use when making requests.
59
+ #
60
+ private_class_method def self.request_sha(repo, ref, proxy)
61
+ require 'cgi'
62
+
63
+ url = "https://gitlab.com/api/v4/projects/#{CGI.escape(repo)}/repository/commits/#{ref}"
64
+ headers = ENV['GITLAB_TOKEN'] ? { "PRIVATE-TOKEN" => ENV['GITLAB_TOKEN'] } : {}
65
+ response = make_request(url, proxy, headers)
66
+
67
+ case response
68
+ when Net::HTTPOK
69
+ JSON.parse(response.body).fetch('id', nil)
70
+ when Net::HTTPUnauthorized
71
+ Bolt::Logger.debug("Invalid token at GITLAB_TOKEN, unable to calculate SHA.")
72
+ nil
73
+ when Net::HTTPForbidden
74
+ message = "GitLab API rate limit exceeded, unable to calculate SHA."
75
+
76
+ unless ENV['GITLAB_TOKEN']
77
+ message += " To increase your rate limit, set the GITLAB_TOKEN environment "\
78
+ "variable with a GitLab personal access token."
79
+ end
80
+
81
+ Bolt::Logger.debug(message)
82
+ nil
83
+ else
84
+ Bolt::Logger.debug("Unable to calculate SHA for ref #{ref}")
85
+ nil
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../bolt/error'
4
+ require_relative 'specs/forge_spec'
5
+ require_relative 'specs/git_spec'
6
+
7
+ module Bolt
8
+ class ModuleInstaller
9
+ class Specs
10
+ def initialize(specs = [], config = {})
11
+ @specs = []
12
+ @config = config
13
+
14
+ add_specs(specs)
15
+ assert_unique_names
16
+ end
17
+
18
+ # Creates a list of specs from the modules in a Puppetfile object.
19
+ #
20
+ def self.from_puppetfile(puppetfile)
21
+ new(puppetfile.modules.map(&:to_hash))
22
+ end
23
+
24
+ # Returns a list of specs.
25
+ #
26
+ def specs
27
+ @specs.uniq(&:name)
28
+ end
29
+
30
+ # Returns true if the specs includes the given name.
31
+ #
32
+ def include?(name)
33
+ _owner, name = name.tr('-', '/').split('/', 2)
34
+ @specs.any? { |spec| spec.name == name }
35
+ end
36
+
37
+ # Adds a spec.
38
+ #
39
+ def add_specs(*specs)
40
+ specs.flatten.map do |spec|
41
+ case spec
42
+ when Hash
43
+ @specs.unshift spec_from_hash(spec)
44
+ else
45
+ @specs.unshift spec
46
+ end
47
+ end
48
+ end
49
+
50
+ # Parses a spec hash into a spec object.
51
+ #
52
+ private def spec_from_hash(hash)
53
+ return ForgeSpec.new(hash) if ForgeSpec.implements?(hash)
54
+ return GitSpec.new(hash, @config) if GitSpec.implements?(hash)
55
+
56
+ raise Bolt::ValidationError, <<~MESSAGE.chomp
57
+ Invalid module specification:
58
+ #{hash.to_yaml.lines.drop(1).join.chomp}
59
+
60
+ To read more about specifying modules, see https://pup.pt/bolt-module-specs
61
+ MESSAGE
62
+ end
63
+
64
+ # Returns true if all specs are satisfied by the modules in a Puppetfile.
65
+ #
66
+ def satisfied_by?(puppetfile)
67
+ @specs.all? do |spec|
68
+ puppetfile.modules.any? do |mod|
69
+ spec.satisfied_by?(mod)
70
+ end
71
+ end
72
+ end
73
+
74
+ # Asserts that all specs are unique by name. The puppetfile-resolver
75
+ # library also does this, but the error it raises isn't as helpful.
76
+ #
77
+ private def assert_unique_names
78
+ duplicates = @specs.group_by(&:name).select { |_name, specs| specs.count > 1 }
79
+
80
+ if duplicates.any?
81
+ message = String.new
82
+
83
+ duplicates.each do |name, duplicate_specs|
84
+ message << <<~MESSAGE
85
+ Detected multiple module specifications with name #{name}:
86
+ #{duplicate_specs.map(&:to_hash).to_yaml.lines.drop(1).join}
87
+ MESSAGE
88
+ end
89
+
90
+ raise Bolt::Error.new(message.chomp, "bolt/duplicate-spec-name-error")
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end