bolt 2.31.0 → 2.32.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b9f29b79c8544029c9a69f6dc76b083857a87e178c9aae0359e49c59ce3cfdb
4
- data.tar.gz: 02166a6dda2dc381365b7556f506a419e2717267e88f6015af430f7f725d75d0
3
+ metadata.gz: 5b010e9146d3269d88005be58db6b788f57ea3046f96f28756641b0c7266eec2
4
+ data.tar.gz: ce8d15031143acabc664a7025ef004c69211c25316f9691783388ba74e61dec9
5
5
  SHA512:
6
- metadata.gz: 3a282194ca9ccf988e7282afc3a3a0b57b4edc16bb5bf61790fb0eb0ef234e0c7a76c0ea6cd44915047a99e6d423311be1307f435d8000c568d0552268f6afa6
7
- data.tar.gz: 957e6be1a72af03bd448fe070e6f9bb0b78aa1bb65e90a8bda251ef0f6c56e5ffaa8ff64aa0c5852963d9fecb87144b61ffd24494fe0aabcbdff43ecde9947a3
6
+ metadata.gz: b45657eb2b985f8e97c59ff4603ad049fd17ee6585292463a57bde3b5c7ca55451883b5e88cb0f61e55c05f1d5415cbde3874320ae446124d646a3d3e8ffa812
7
+ data.tar.gz: 673e3f3310bf4f22602153bf8300851fa3a832db8e73abc9352874bea7c0b1fd50a5baa42d18474885caea7ec9fad4aa50c25f44aa27974097e10ea03b31e07e
data/Puppetfile CHANGED
@@ -34,7 +34,7 @@ mod 'puppetlabs-stdlib', '6.5.0'
34
34
  mod 'puppetlabs-aws_inventory', '0.5.2'
35
35
  mod 'puppetlabs-azure_inventory', '0.4.1'
36
36
  mod 'puppetlabs-gcloud_inventory', '0.1.3'
37
- mod 'puppetlabs-http_request', '0.1.0'
37
+ mod 'puppetlabs-http_request', '0.2.0'
38
38
  mod 'puppetlabs-pkcs7', '0.1.1'
39
39
  mod 'puppetlabs-terraform', '0.5.0'
40
40
  mod 'puppetlabs-vault', '0.3.0'
@@ -26,8 +26,51 @@ Puppet::Functions.create_function(:'out::message') do
26
26
  # Send Analytics Report
27
27
  executor.report_function_call(self.class.name)
28
28
 
29
- executor.publish_event(type: :message, message: message)
29
+ executor.publish_event(type: :message, message: stringify(message))
30
30
 
31
31
  nil
32
32
  end
33
+
34
+ def stringify(message)
35
+ formatted = format_message(message)
36
+ if formatted.is_a?(Hash) || formatted.is_a?(Array)
37
+ ::JSON.pretty_generate(formatted)
38
+ else
39
+ formatted
40
+ end
41
+ end
42
+
43
+ def format_message(message)
44
+ case message
45
+ when Array
46
+ message.map { |item| format_message(item) }
47
+ when Bolt::ApplyResult
48
+ format_apply_result(message)
49
+ when Bolt::Result, Bolt::ResultSet
50
+ # This is equivalent to to_s, but formattable
51
+ message.to_data
52
+ when Bolt::RunFailure
53
+ formatted_resultset = message.result_set.to_data
54
+ message.to_h.merge('result_set' => formatted_resultset)
55
+ when Hash
56
+ message.each_with_object({}) do |(k, v), h|
57
+ h[format_message(k)] = format_message(v)
58
+ end
59
+ when Integer, Float, NilClass
60
+ message
61
+ else
62
+ message.to_s
63
+ end
64
+ end
65
+
66
+ def format_apply_result(result)
67
+ logs = result.resource_logs&.map do |log|
68
+ # Omit low-level info/debug messages
69
+ next if %w[info debug].include?(log['level'])
70
+ indent(2, format_log(log))
71
+ end
72
+ hash = result.to_data
73
+ hash['logs'] = logs unless logs.empty?
74
+ hash
75
+ end
33
76
  end
@@ -9,11 +9,14 @@ Puppet::Functions.create_function(:prompt) do
9
9
  # @param prompt The prompt to display.
10
10
  # @param options A hash of additional options.
11
11
  # @option options [Boolean] sensitive Disable echo back and mark the response as sensitive.
12
+ # The returned value will be wrapped by the `Sensitive` data type. To access the raw
13
+ # value, use the `unwrap` function (i.e. `$sensitive_value.unwrap`).
12
14
  # @return The response to the prompt.
13
15
  # @example Prompt the user if plan execution should continue
14
16
  # $response = prompt('Continue executing plan? [Y\N]')
15
17
  # @example Prompt the user for sensitive information
16
18
  # $password = prompt('Enter your password', 'sensitive' => true)
19
+ # out::message("Password is: ${password.unwrap}")
17
20
  dispatch :prompt do
18
21
  param 'String', :prompt
19
22
  optional_param 'Hash[String[1], Any]', :options
@@ -0,0 +1,19 @@
1
+ TOPIC
2
+ module
3
+
4
+ DESCRIPTION
5
+ Modules are shareable, reusable packages of Puppet content. They can include
6
+ tasks, plans, functions, and other types of content that you can use in your
7
+ project. You can download and install modules to your project from the
8
+ Puppet Forge or write your own modules. Bolt also ships with several helpful
9
+ modules pre-installed that are available to all of your projects.
10
+
11
+ Bolt makes it easy to manage the modules that your project depends on. You
12
+ can use Bolt commands to install a project's modules, add new modules to a
13
+ project, and view the modules that are available to the project.
14
+
15
+ To learn more about managing modules in a project, see the documentation.
16
+ To learn how modules are loaded by Bolt, see the 'modulepath' guide.
17
+
18
+ DOCUMENTATION
19
+ https://pup.pt/bolt-modules
@@ -0,0 +1,25 @@
1
+ TOPIC
2
+ modulepath
3
+
4
+ DESCRIPTION
5
+ The modulepath is an ordered list of directories that Bolt loads modules
6
+ from. When Bolt runs a command, it automatically loads modules from the
7
+ modulepath.
8
+
9
+ While Bolt has a default modulepath, you can also configure your own
10
+ modulepath, which can include directories within the project or directories
11
+ elsewhere on your system. Regardless of whether your project uses a default
12
+ or configured modulepath, Bolt automatically adds directories to the
13
+ modulepath. This includes modules containing core Bolt content, which is
14
+ added to the beginning of the modulepath, and bundled content, which is
15
+ added to the end of the modulepath.
16
+
17
+ Modules loaded from a directory listed earlier in the modulepath take
18
+ precedence over modules with the same name loaded from a directory later in
19
+ the modulepath. Bolt will not warn or error when two modules share a name
20
+ and instead will ignore modules with a lower precedence.
21
+
22
+ To learn more about modules, see the 'module' guide.
23
+
24
+ DOCUMENTATION
25
+ https://pup.pt/bolt-project-reference#modulepath
@@ -234,26 +234,44 @@ module Bolt
234
234
  type: Array,
235
235
  items: {
236
236
  type: [Hash, String],
237
- required: ["name"],
238
- properties: {
239
- "name" => {
240
- description: "The name of the module.",
241
- type: String
237
+ oneOf: [
238
+ {
239
+ required: ["name"],
240
+ properties: {
241
+ "name" => {
242
+ description: "The name of the module.",
243
+ type: String
244
+ },
245
+ "version_requirement" => {
246
+ description: "The version requirement for the module. Accepts a specific version (1.2.3), version "\
247
+ "shorthand (1.2.x), or a version range (>= 1.2.0).",
248
+ type: String
249
+ }
250
+ }
242
251
  },
243
- "version_requirement" => {
244
- description: "The version requirement for the module. Accepts a specific version (1.2.3), version "\
245
- "shorthand (1.2.x), or a version range (>= 1.2.0).",
246
- type: String
252
+ {
253
+ required: %w[git ref],
254
+ properties: {
255
+ "git" => {
256
+ description: "The URL to the public git repository.",
257
+ type: String
258
+ },
259
+ "ref" => {
260
+ description: "The git reference to check out. Can be either a branch, tag, or commit SHA.",
261
+ type: String
262
+ }
263
+ }
247
264
  }
248
- }
265
+ ]
249
266
  },
250
267
  _plugin: false,
251
268
  _example: [
252
- { "name" => "puppetlabs-mysql" },
253
269
  "puppetlabs-facts",
270
+ { "name" => "puppetlabs-mysql" },
254
271
  { "name" => "puppetlabs-apache", "version_requirement" => "5.5.0" },
255
272
  { "name" => "puppetlabs-puppetdb", "version_requirement" => "7.x" },
256
- { "name" => "puppetlabs-firewall", "version_requirement" => ">= 1.0.0 < 3.0.0" }
273
+ { "name" => "puppetlabs-firewall", "version_requirement" => ">= 1.0.0 < 3.0.0" },
274
+ { "git" => "https://github.com/puppetlabs/puppetlabs-apt", "ref" => "7.6.0" }
257
275
  ]
258
276
  },
259
277
  "name" => {
@@ -329,7 +347,7 @@ module Bolt
329
347
  "server_urls" => {
330
348
  description: "An array containing the PuppetDB host to connect to. Include the protocol `https` "\
331
349
  "and the port, which is usually `8081`. For example, "\
332
- "`https://my-master.example.com:8081`.",
350
+ "`https://my-puppetdb-server.com:8081`.",
333
351
  type: Array,
334
352
  _example: ["https://puppet.example.com:8081"]
335
353
  },
@@ -2,6 +2,10 @@
2
2
 
3
3
  require 'bolt/error'
4
4
  require 'bolt/logger'
5
+ require 'bolt/module_installer/installer'
6
+ require 'bolt/module_installer/puppetfile'
7
+ require 'bolt/module_installer/resolver'
8
+ require 'bolt/module_installer/specs'
5
9
 
6
10
  module Bolt
7
11
  class ModuleInstaller
@@ -13,45 +17,53 @@ module Bolt
13
17
 
14
18
  # Adds a single module to the project.
15
19
  #
16
- def add(name, modules, puppetfile_path, moduledir, config_path)
17
- require 'bolt/puppetfile'
18
-
19
- @outputter.print_message("Adding module #{name} to project\n\n")
20
-
21
- # If the project configuration file already includes this module,
22
- # exit early.
23
- puppetfile = Bolt::Puppetfile.new(modules)
24
- new_module = Bolt::Puppetfile::Module.from_hash('name' => name)
25
-
26
- if puppetfile.modules.include?(new_module)
27
- @outputter.print_action_step(
28
- "Project configuration file #{config_path} already includes module #{new_module}. Nothing to do."
20
+ def add(name, specs, puppetfile_path, moduledir, config_path)
21
+ project_specs = Specs.new(specs)
22
+
23
+ # Exit early if project config already includes a spec with this name.
24
+ if project_specs.include?(name)
25
+ @outputter.print_message(
26
+ "Project configuration file #{config_path} already includes specification with name "\
27
+ "#{name}. Nothing to do."
29
28
  )
30
29
  return true
31
30
  end
32
31
 
33
- # If the Puppetfile exists, make sure it's managed by Bolt.
34
- if puppetfile_path.exist?
35
- assert_managed_puppetfile(puppetfile, puppetfile_path)
36
- existing = Bolt::Puppetfile.parse(puppetfile_path)
37
- else
38
- existing = Bolt::Puppetfile.new
39
- end
32
+ @outputter.print_message("Adding module #{name} to project\n\n")
40
33
 
41
- # Create a Puppetfile object that includes the new module and its
42
- # dependencies. We error early here so we don't add the new module to the
43
- # project config or modify the Puppetfile.
44
- puppetfile = add_new_module_to_puppetfile(new_module, modules, puppetfile_path)
34
+ # Generate the specs to resolve from. If a Puppetfile exists, parse it and
35
+ # convert the modules to specs. Otherwise, use the project specs.
36
+ resolve_specs = if puppetfile_path.exist?
37
+ existing_puppetfile = Puppetfile.parse(puppetfile_path)
38
+ existing_puppetfile.assert_satisfies(project_specs)
39
+ Specs.from_puppetfile(existing_puppetfile)
40
+ else
41
+ project_specs
42
+ end
43
+
44
+ # Resolve module dependencies. Attempt to first resolve with resolve
45
+ # specss. If that fails, fall back to resolving from project specs.
46
+ # This prevents Bolt from modifying installed modules unless there is
47
+ # a version conflict.
48
+ @outputter.print_action_step("Resolving module dependencies, this may take a moment")
49
+
50
+ begin
51
+ resolve_specs.add_specs('name' => name)
52
+ puppetfile = Resolver.new.resolve(resolve_specs)
53
+ rescue Bolt::Error
54
+ project_specs.add_specs('name' => name)
55
+ puppetfile = Resolver.new.resolve(project_specs)
56
+ end
45
57
 
46
58
  # Display the diff between the existing Puppetfile and the new Puppetfile.
47
- print_puppetfile_diff(existing, puppetfile)
59
+ print_puppetfile_diff(existing_puppetfile, puppetfile)
48
60
 
49
61
  # Add the module to the project configuration.
50
62
  @outputter.print_action_step("Updating project configuration file at #{config_path}")
51
63
 
52
64
  data = Bolt::Util.read_yaml_hash(config_path, 'project')
53
65
  data['modules'] ||= []
54
- data['modules'] << { 'name' => new_module.title }
66
+ data['modules'] << name
55
67
 
56
68
  begin
57
69
  File.write(config_path, data.to_yaml)
@@ -70,130 +82,97 @@ module Bolt
70
82
  install_puppetfile(puppetfile_path, moduledir)
71
83
  end
72
84
 
73
- # Creates a new Puppetfile that includes the new module and its dependencies.
74
- #
75
- private def add_new_module_to_puppetfile(new_module, modules, path)
76
- @outputter.print_action_step("Resolving module dependencies, this may take a moment")
77
-
78
- # If there is an existing Puppetfile, add the new module and attempt
79
- # to resolve. This will not update the versions of any installed modules.
80
- if path.exist?
81
- puppetfile = Bolt::Puppetfile.parse(path)
82
- puppetfile.add_modules(new_module)
83
-
84
- begin
85
- puppetfile.resolve
86
- return puppetfile
87
- rescue Bolt::Error
88
- @logger.debug "Unable to find a version of #{new_module} compatible "\
89
- "with installed modules. Attempting to re-resolve modules "\
90
- "from project configuration; some versions of installed "\
91
- "modules may change."
92
- end
93
- end
94
-
95
- # If there is not an existing Puppetfile, or resolving with pinned
96
- # modules fails, resolve all of the module declarations with the new
97
- # module.
98
- puppetfile = Bolt::Puppetfile.new(modules)
99
- puppetfile.add_modules(new_module)
100
- puppetfile.resolve
101
- puppetfile
102
- end
103
-
104
85
  # Outputs a diff of an old Puppetfile and a new Puppetfile.
105
86
  #
106
87
  def print_puppetfile_diff(old, new)
107
- # Build hashes mapping the module title to the module object. This makes it
88
+ # Build hashes mapping the module name to the module object. This makes it
108
89
  # a little easier to determine which modules have been added, removed, or
109
90
  # modified.
110
- old = old.modules.each_with_object({}) do |mod, acc|
111
- acc[mod.title] = mod
91
+ old = (old&.modules || []).each_with_object({}) do |mod, acc|
92
+ next unless mod.type == :forge
93
+ acc[mod.full_name] = mod
112
94
  end
113
95
 
114
96
  new = new.modules.each_with_object({}) do |mod, acc|
115
- acc[mod.title] = mod
97
+ next unless mod.type == :forge
98
+ acc[mod.full_name] = mod
116
99
  end
117
100
 
118
101
  # New modules are those present in new but not in old.
119
- added = new.reject { |title, _mod| old.include?(title) }.values
102
+ added = new.reject { |full_name, _mod| old.include?(full_name) }.values
120
103
 
121
104
  if added.any?
122
105
  diff = "Adding the following modules:\n"
123
- added.each { |mod| diff += "#{mod.title} #{mod.version}\n" }
106
+ added.each { |mod| diff += "#{mod.full_name} #{mod.version}\n" }
124
107
  @outputter.print_action_step(diff)
125
108
  end
126
109
 
127
110
  # Upgraded modules are those that have a newer version in new than old.
128
- upgraded = new.select do |title, mod|
129
- if old.include?(title)
130
- SemanticPuppet::Version.parse(mod.version) > SemanticPuppet::Version.parse(old[title].version)
111
+ upgraded = new.select do |full_name, mod|
112
+ if old.include?(full_name)
113
+ mod.version > old[full_name].version
131
114
  end
132
115
  end.keys
133
116
 
134
117
  if upgraded.any?
135
118
  diff = "Upgrading the following modules:\n"
136
- upgraded.each { |title| diff += "#{title} #{old[title].version} to #{new[title].version}\n" }
119
+ upgraded.each { |full_name| diff += "#{full_name} #{old[full_name].version} to #{new[full_name].version}\n" }
137
120
  @outputter.print_action_step(diff)
138
121
  end
139
122
 
140
123
  # Downgraded modules are those that have an older version in new than old.
141
- downgraded = new.select do |title, mod|
142
- if old.include?(title)
143
- SemanticPuppet::Version.parse(mod.version) < SemanticPuppet::Version.parse(old[title].version)
124
+ downgraded = new.select do |full_name, mod|
125
+ if old.include?(full_name)
126
+ mod.version < old[full_name].version
144
127
  end
145
128
  end.keys
146
129
 
147
130
  if downgraded.any?
148
131
  diff = "Downgrading the following modules: \n"
149
- downgraded.each { |title| diff += "#{title} #{old[title].version} to #{new[title].version}\n" }
132
+ downgraded.each { |full_name| diff += "#{full_name} #{old[full_name].version} to #{new[full_name].version}\n" }
150
133
  @outputter.print_action_step(diff)
151
134
  end
152
135
 
153
136
  # Removed modules are those present in old but not in new.
154
- removed = old.reject { |title, _mod| new.include?(title) }.values
137
+ removed = old.reject { |full_name, _mod| new.include?(full_name) }.values
155
138
 
156
139
  if removed.any?
157
140
  diff = "Removing the following modules:\n"
158
- removed.each { |mod| diff += "#{mod.title} #{mod.version}\n" }
141
+ removed.each { |mod| diff += "#{mod.full_name} #{mod.version}\n" }
159
142
  @outputter.print_action_step(diff)
160
143
  end
161
144
  end
162
145
 
163
146
  # Installs a project's module dependencies.
164
147
  #
165
- def install(modules, path, moduledir, force: false, resolve: true)
166
- require 'bolt/puppetfile'
167
-
148
+ def install(specs, path, moduledir, force: false, resolve: true)
168
149
  @outputter.print_message("Installing project modules\n\n")
169
150
 
170
- puppetfile = Bolt::Puppetfile.new(modules)
171
-
172
- # If the Puppetfile exists, check if it includes specs for each declared
173
- # module, erroring if there are any missing. Otherwise, resolve the
174
- # module dependencies and write a new Puppetfile. Users can forcibly
175
- # overwrite an existing Puppetfile with the '--force' option, or opt to
176
- # install the Puppetfile as-is with --no-resolve.
177
- #
178
- # This is just if resolve is not false (nil should default to true)
179
151
  if resolve != false
180
- if path.exist? && !force
181
- assert_managed_puppetfile(puppetfile, path)
182
- else
152
+ specs = Specs.new(specs)
153
+
154
+ # If forcibly installing or if there is no Puppetfile, resolve
155
+ # and write a Puppetfile.
156
+ if force || !path.exist?
183
157
  @outputter.print_action_step("Resolving module dependencies, this may take a moment")
184
- puppetfile.resolve
158
+ puppetfile = Resolver.new.resolve(specs)
185
159
 
186
- @outputter.print_action_step("Writing Puppetfile at #{path}")
187
160
  # We get here either through 'bolt module install' which uses the
188
161
  # managed modulepath (which isn't configurable) or through bolt
189
162
  # project init --modules, which uses the default modulepath. This
190
163
  # should be safe to assume that if `.modules/` is the moduledir the
191
164
  # user is using the new workflow
165
+ @outputter.print_action_step("Writing Puppetfile at #{path}")
192
166
  if moduledir.basename.to_s == '.modules'
193
167
  puppetfile.write(path, moduledir)
194
168
  else
195
169
  puppetfile.write(path)
196
170
  end
171
+ # If not forcibly installing and there is a Puppetfile, assert
172
+ # that it satisfies the specs.
173
+ else
174
+ puppetfile = Puppetfile.parse(path)
175
+ puppetfile.assert_satisfies(specs)
197
176
  end
198
177
  end
199
178
 
@@ -204,10 +183,8 @@ module Bolt
204
183
  # Installs the Puppetfile and generates types.
205
184
  #
206
185
  def install_puppetfile(path, moduledir, config = {})
207
- require 'bolt/puppetfile/installer'
208
-
209
186
  @outputter.print_action_step("Syncing modules from #{path} to #{moduledir}")
210
- ok = Bolt::Puppetfile::Installer.new(config).install(path, moduledir)
187
+ ok = Installer.new(config).install(path, moduledir)
211
188
 
212
189
  # Automatically generate types after installing modules
213
190
  @pal.generate_types
@@ -216,27 +193,5 @@ module Bolt
216
193
 
217
194
  ok
218
195
  end
219
-
220
- # Asserts that an existing Puppetfile is managed by Bolt.
221
- #
222
- private def assert_managed_puppetfile(puppetfile, path)
223
- existing_puppetfile = Bolt::Puppetfile.parse(path)
224
-
225
- unless existing_puppetfile.modules.superset? puppetfile.modules
226
- missing_modules = puppetfile.modules - existing_puppetfile.modules
227
-
228
- message = <<~MESSAGE.chomp
229
- Puppetfile #{path} is missing specifications for the following
230
- module declarations:
231
-
232
- #{missing_modules.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
233
-
234
- This may not be a Puppetfile managed by Bolt. To forcibly overwrite the
235
- Puppetfile, run 'bolt module install --force'.
236
- MESSAGE
237
-
238
- raise Bolt::Error.new(message, 'bolt/missing-module-specs')
239
- end
240
- end
241
196
  end
242
197
  end