bolt 2.31.0 → 2.35.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +7 -7
  3. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -3
  4. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
  5. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
  6. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
  10. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
  12. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
  13. data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
  14. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
  15. data/guides/logging.txt +18 -0
  16. data/guides/module.txt +19 -0
  17. data/guides/modulepath.txt +25 -0
  18. data/lib/bolt/bolt_option_parser.rb +6 -1
  19. data/lib/bolt/cli.rb +70 -144
  20. data/lib/bolt/config/options.rb +35 -17
  21. data/lib/bolt/config/transport/options.rb +1 -1
  22. data/lib/bolt/error.rb +37 -3
  23. data/lib/bolt/executor.rb +111 -13
  24. data/lib/bolt/inventory/group.rb +2 -1
  25. data/lib/bolt/module_installer.rb +71 -115
  26. data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
  27. data/lib/bolt/module_installer/puppetfile.rb +117 -0
  28. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  29. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  30. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  31. data/lib/bolt/module_installer/resolver.rb +76 -0
  32. data/lib/bolt/module_installer/specs.rb +93 -0
  33. data/lib/bolt/module_installer/specs/forge_spec.rb +85 -0
  34. data/lib/bolt/module_installer/specs/git_spec.rb +179 -0
  35. data/lib/bolt/outputter.rb +0 -47
  36. data/lib/bolt/outputter/human.rb +23 -11
  37. data/lib/bolt/outputter/json.rb +1 -1
  38. data/lib/bolt/pal.rb +48 -30
  39. data/lib/bolt/pal/yaml_plan.rb +11 -2
  40. data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
  41. data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
  42. data/lib/bolt/plan_creator.rb +160 -0
  43. data/lib/bolt/plugin.rb +1 -1
  44. data/lib/bolt/project.rb +5 -10
  45. data/lib/bolt/project_migrator/config.rb +2 -1
  46. data/lib/bolt/project_migrator/inventory.rb +2 -2
  47. data/lib/bolt/project_migrator/modules.rb +10 -8
  48. data/lib/bolt/puppetdb/client.rb +3 -2
  49. data/lib/bolt/puppetdb/config.rb +8 -6
  50. data/lib/bolt/result.rb +23 -11
  51. data/lib/bolt/shell/bash.rb +11 -6
  52. data/lib/bolt/shell/powershell.rb +12 -7
  53. data/lib/bolt/task/run.rb +1 -1
  54. data/lib/bolt/transport/base.rb +18 -18
  55. data/lib/bolt/transport/docker.rb +23 -6
  56. data/lib/bolt/transport/orch.rb +23 -19
  57. data/lib/bolt/transport/orch/connection.rb +10 -3
  58. data/lib/bolt/transport/remote.rb +3 -3
  59. data/lib/bolt/transport/simple.rb +6 -6
  60. data/lib/bolt/util.rb +5 -0
  61. data/lib/bolt/version.rb +1 -1
  62. data/lib/bolt/yarn.rb +23 -0
  63. data/lib/bolt_server/file_cache.rb +2 -0
  64. data/lib/bolt_server/schemas/partials/task.json +17 -2
  65. data/lib/bolt_server/transport_app.rb +38 -7
  66. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
  67. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
  68. data/lib/bolt_spec/plans/mock_executor.rb +9 -6
  69. metadata +25 -8
  70. data/lib/bolt/puppetfile.rb +0 -149
  71. data/lib/bolt/puppetfile/module.rb +0 -93
  72. data/modules/secure_env_vars/plans/init.pp +0 -20
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'r10k/cli'
4
3
  require 'bolt/r10k_log_proxy'
5
4
  require 'bolt/error'
6
5
 
7
6
  # This class is used to install modules from a Puppetfile to a module directory.
8
7
  #
9
8
  module Bolt
10
- class Puppetfile
9
+ class ModuleInstaller
11
10
  class Installer
12
11
  def initialize(config = {})
13
12
  @config = config
14
13
  end
15
14
 
16
15
  def install(path, moduledir)
16
+ require 'r10k/cli'
17
+
17
18
  unless File.exist?(path)
18
19
  raise Bolt::FileError.new(
19
20
  "Could not find a Puppetfile at #{path}",
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+ require 'bolt/module_installer/puppetfile/forge_module'
5
+ require 'bolt/module_installer/puppetfile/git_module'
6
+
7
+ # This class manages the logical contents of a Puppetfile. It includes methods
8
+ # for parsing and generating a Puppetfile.
9
+ #
10
+ module Bolt
11
+ class ModuleInstaller
12
+ class Puppetfile
13
+ attr_reader :modules
14
+
15
+ def initialize(modules = [])
16
+ @modules = modules
17
+ end
18
+
19
+ # Loads a Puppetfile and parses its modules.
20
+ #
21
+ def self.parse(path, skip_unsupported_modules: false)
22
+ require 'puppetfile-resolver'
23
+
24
+ return new unless path.exist?
25
+
26
+ begin
27
+ parsed = ::PuppetfileResolver::Puppetfile::Parser::R10KEval.parse(File.read(path))
28
+ rescue StandardError => e
29
+ raise Bolt::Error.new(
30
+ "Unable to parse Puppetfile #{path}: #{e.message}",
31
+ 'bolt/puppetfile-parsing'
32
+ )
33
+ end
34
+
35
+ unless parsed.valid?
36
+ raise Bolt::ValidationError, <<~MSG
37
+ Unable to parse Puppetfile #{path}:
38
+ #{parsed.validation_errors.join("\n\n")}.
39
+ This may not be a Puppetfile managed by Bolt.
40
+ MSG
41
+ end
42
+
43
+ modules = parsed.modules.each_with_object([]) do |mod, acc|
44
+ case mod.module_type
45
+ when :forge
46
+ acc << ForgeModule.new(
47
+ mod.title,
48
+ mod.version.is_a?(String) ? mod.version[1..-1] : nil
49
+ )
50
+ when :git
51
+ acc << GitModule.new(
52
+ mod.name,
53
+ mod.remote,
54
+ mod.ref || mod.commit || mod.tag
55
+ )
56
+ else
57
+ unless skip_unsupported_modules
58
+ raise Bolt::ValidationError,
59
+ "Cannot parse Puppetfile at #{path}, module '#{mod.title}' is not a "\
60
+ "Puppet Forge or Git module."
61
+ end
62
+ end
63
+ end
64
+
65
+ new(modules)
66
+ end
67
+
68
+ # Writes a Puppetfile that includes specifications for each of the
69
+ # modules.
70
+ #
71
+ def write(path, moduledir = nil)
72
+ File.open(path, 'w') do |file|
73
+ if moduledir
74
+ file.puts "# This Puppetfile is managed by Bolt. Do not edit."
75
+ file.puts "# For more information, see https://pup.pt/bolt-modules"
76
+ file.puts
77
+ file.puts "# The following directive installs modules to the managed moduledir."
78
+ file.puts "moduledir '#{moduledir.basename}'"
79
+ file.puts
80
+ end
81
+
82
+ @modules.each { |mod| file.puts mod.to_spec }
83
+ end
84
+ rescue SystemCallError => e
85
+ raise Bolt::FileError.new(
86
+ "#{e.message}: unable to write Puppetfile.",
87
+ path
88
+ )
89
+ end
90
+
91
+ # Asserts that the Puppetfile satisfies the given specifications.
92
+ #
93
+ def assert_satisfies(specs)
94
+ unsatisfied_specs = specs.specs.reject do |spec|
95
+ @modules.any? do |mod|
96
+ spec.satisfied_by?(mod)
97
+ end
98
+ end
99
+
100
+ return if unsatisfied_specs.empty?
101
+
102
+ command = Bolt::Util.windows? ? 'Install-BoltModule -Force' : 'bolt module install --force'
103
+
104
+ message = <<~MESSAGE.chomp
105
+ Puppetfile does not include modules that satisfy the following specifications:
106
+
107
+ #{unsatisfied_specs.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
108
+
109
+ This may not be a Puppetfile managed by Bolt. To forcibly overwrite the
110
+ Puppetfile, run '#{command}'.
111
+ MESSAGE
112
+
113
+ raise Bolt::Error.new(message, 'bolt/missing-module-specs')
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'semantic_puppet'
4
+ require 'bolt/module_installer/puppetfile/module'
5
+
6
+ # This class represents a resolved Forge module.
7
+ #
8
+ module Bolt
9
+ class ModuleInstaller
10
+ class Puppetfile
11
+ class ForgeModule < Module
12
+ attr_reader :version
13
+
14
+ def initialize(name, version)
15
+ super(name)
16
+ @version = parse_version(version)
17
+ @type = :forge
18
+ end
19
+
20
+ # Parses the version into a Semantic Puppet version.
21
+ #
22
+ private def parse_version(version)
23
+ return unless version.is_a?(String)
24
+
25
+ unless SemanticPuppet::Version.valid?(version)
26
+ raise Bolt::ValidationError,
27
+ "Invalid version for Forge module #{@full_name}: #{version.inspect}"
28
+ end
29
+
30
+ SemanticPuppet::Version.parse(version)
31
+ end
32
+
33
+ # Returns a Puppetfile module specification.
34
+ #
35
+ def to_spec
36
+ if @version
37
+ "mod '#{@full_name}', '#{@version}'"
38
+ else
39
+ "mod '#{@full_name}'"
40
+ end
41
+ end
42
+
43
+ # Returns a hash that can be used to create a module specification.
44
+ #
45
+ def to_hash
46
+ {
47
+ 'name' => @full_name,
48
+ 'version_requirement' => @version ? @version.to_s : nil
49
+ }.compact
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/module_installer/puppetfile/module'
4
+
5
+ # This class represents a resolved Git module.
6
+ #
7
+ module Bolt
8
+ class ModuleInstaller
9
+ class Puppetfile
10
+ class GitModule < Module
11
+ attr_reader :git, :ref
12
+
13
+ def initialize(name, git, ref)
14
+ super(name)
15
+ @git = git
16
+ @ref = ref
17
+ @type = :git
18
+ end
19
+
20
+ # Returns a Puppetfile module specification.
21
+ #
22
+ def to_spec
23
+ "mod '#{@name}',\n git: '#{@git}',\n ref: '#{@ref}'"
24
+ end
25
+
26
+ # Returns a hash that can be used to create a module specification.
27
+ #
28
+ def to_hash
29
+ {
30
+ 'git' => @git,
31
+ 'ref' => @ref
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ module Bolt
6
+ class ModuleInstaller
7
+ class Puppetfile
8
+ class Module
9
+ attr_reader :full_name, :name, :type
10
+
11
+ def initialize(name)
12
+ @full_name, @name = parse_name(name)
13
+ end
14
+
15
+ # Formats the full name and extracts the module name.
16
+ #
17
+ protected def parse_name(name)
18
+ full_name = name.tr('-', '/')
19
+ first, second = full_name.split('/', 2)
20
+
21
+ [full_name, second || first]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+ require 'bolt/module_installer/puppetfile'
5
+ require 'bolt/module_installer/specs'
6
+
7
+ module Bolt
8
+ class ModuleInstaller
9
+ class Resolver
10
+ # Resolves module specs and returns a Puppetfile object.
11
+ #
12
+ def resolve(specs)
13
+ require 'puppetfile-resolver'
14
+
15
+ # Build the document model from the specs.
16
+ document = PuppetfileResolver::Puppetfile::Document.new('')
17
+
18
+ specs.specs.each do |spec|
19
+ document.add_module(spec.to_resolver_module)
20
+ end
21
+
22
+ # Make sure the document model is valid.
23
+ unless document.valid?
24
+ message = <<~MESSAGE.chomp
25
+ Unable to resolve module specifications:
26
+
27
+ #{document.validation_errors.map(&:message).join("\n")}
28
+ MESSAGE
29
+
30
+ raise Bolt::Error.new(message, 'bolt/module-resolver-error')
31
+ end
32
+
33
+ # Create the resolver using the Puppetfile model. nil disables Puppet
34
+ # version restrictions.
35
+ resolver = PuppetfileResolver::Resolver.new(document, nil)
36
+
37
+ # Configure and resolve the dependency graph, catching any errors
38
+ # raised by puppetfile-resolver and re-raising them as Bolt errors.
39
+ begin
40
+ result = resolver.resolve(
41
+ cache: nil,
42
+ ui: nil,
43
+ module_paths: [],
44
+ allow_missing_modules: false
45
+ )
46
+ rescue StandardError => e
47
+ raise Bolt::Error.new(e.message, 'bolt/module-resolver-error')
48
+ end
49
+
50
+ # Convert the specs returned from the resolver into Bolt module objects.
51
+ modules = result.specifications.values.each_with_object([]) do |mod, acc|
52
+ # Skip over anything that isn't a module spec, such as a Puppet spec.
53
+ next unless mod.is_a? PuppetfileResolver::Models::ModuleSpecification
54
+
55
+ case mod.origin
56
+ when :forge
57
+ acc << Bolt::ModuleInstaller::Puppetfile::ForgeModule.new(
58
+ "#{mod.owner}/#{mod.name}",
59
+ mod.version.to_s
60
+ )
61
+ when :git
62
+ spec = specs.specs.find { |s| s.name == mod.name }
63
+ acc << Bolt::ModuleInstaller::Puppetfile::GitModule.new(
64
+ spec.name,
65
+ spec.git,
66
+ spec.sha
67
+ )
68
+ end
69
+ end
70
+
71
+ # Create the Puppetfile object.
72
+ Bolt::ModuleInstaller::Puppetfile.new(modules)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+ require 'bolt/module_installer/specs/forge_spec'
5
+ require 'bolt/module_installer/specs/git_spec'
6
+
7
+ module Bolt
8
+ class ModuleInstaller
9
+ class Specs
10
+ def initialize(specs = [])
11
+ @specs = []
12
+ add_specs(specs)
13
+ assert_unique_names
14
+ end
15
+
16
+ # Creates a list of specs from the modules in a Puppetfile object.
17
+ #
18
+ def self.from_puppetfile(puppetfile)
19
+ new(puppetfile.modules.map(&:to_hash))
20
+ end
21
+
22
+ # Returns a list of specs.
23
+ #
24
+ def specs
25
+ @specs.uniq(&:name)
26
+ end
27
+
28
+ # Returns true if the specs includes the given name.
29
+ #
30
+ def include?(name)
31
+ _owner, name = name.tr('-', '/').split('/', 2)
32
+ @specs.any? { |spec| spec.name == name }
33
+ end
34
+
35
+ # Adds a spec.
36
+ #
37
+ def add_specs(*specs)
38
+ specs.flatten.map do |spec|
39
+ case spec
40
+ when Hash
41
+ @specs.unshift spec_from_hash(spec)
42
+ else
43
+ @specs.unshift spec
44
+ end
45
+ end
46
+ end
47
+
48
+ # Parses a spec hash into a spec object.
49
+ #
50
+ private def spec_from_hash(hash)
51
+ return ForgeSpec.new(hash) if ForgeSpec.implements?(hash)
52
+ return GitSpec.new(hash) if GitSpec.implements?(hash)
53
+
54
+ raise Bolt::ValidationError, <<~MESSAGE.chomp
55
+ Invalid module specification:
56
+ #{hash.to_yaml.lines.drop(1).join.chomp}
57
+
58
+ To read more about specifying modules, see https://pup.pt/bolt-modules
59
+ MESSAGE
60
+ end
61
+
62
+ # Returns true if all specs are satisfied by the modules in a Puppetfile.
63
+ #
64
+ def satisfied_by?(puppetfile)
65
+ @specs.all? do |spec|
66
+ puppetfile.modules.any? do |mod|
67
+ spec.satisfied_by?(mod)
68
+ end
69
+ end
70
+ end
71
+
72
+ # Asserts that all specs are unique by name. The puppetfile-resolver
73
+ # library also does this, but the error it raises isn't as helpful.
74
+ #
75
+ private def assert_unique_names
76
+ duplicates = @specs.group_by(&:name).select { |_name, specs| specs.count > 1 }
77
+
78
+ if duplicates.any?
79
+ message = String.new
80
+
81
+ duplicates.each do |name, duplicate_specs|
82
+ message << <<~MESSAGE
83
+ Detected multiple module specifications with name #{name}:
84
+ #{duplicate_specs.map(&:to_hash).to_yaml.lines.drop(1).join}
85
+ MESSAGE
86
+ end
87
+
88
+ raise Bolt::Error.new(message.chomp, "bolt/duplicate-spec-name-error")
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'semantic_puppet'
4
+ require 'set'
5
+
6
+ require 'bolt/error'
7
+
8
+ # This class represents a Forge module specification.
9
+ #
10
+ module Bolt
11
+ class ModuleInstaller
12
+ class Specs
13
+ class ForgeSpec
14
+ NAME_REGEX = %r{\A[a-zA-Z0-9]+[-/](?<name>[a-z][a-z0-9_]*)\z}.freeze
15
+ REQUIRED_KEYS = Set.new(%w[name]).freeze
16
+ KNOWN_KEYS = Set.new(%w[name version_requirement]).freeze
17
+
18
+ attr_reader :full_name, :name, :semantic_version, :type
19
+
20
+ def initialize(init_hash)
21
+ @full_name, @name = parse_name(init_hash['name'])
22
+ @version_requirement, @semantic_version = parse_version_requirement(init_hash['version_requirement'])
23
+ @type = :forge
24
+ end
25
+
26
+ def self.implements?(hash)
27
+ KNOWN_KEYS.superset?(hash.keys.to_set) && REQUIRED_KEYS.subset?(hash.keys.to_set)
28
+ end
29
+
30
+ # Formats the full name and extracts the module name.
31
+ #
32
+ private def parse_name(name)
33
+ unless (match = name.match(NAME_REGEX))
34
+ raise Bolt::ValidationError,
35
+ "Invalid name for Forge module specification: #{name}. Name must match "\
36
+ "'owner/name'. Owner segment may only include letters or digits. Name "\
37
+ "segment must start with a lowercase letter and may only include lowercase "\
38
+ "letters, digits, and underscores."
39
+ end
40
+
41
+ [name.tr('-', '/'), match[:name]]
42
+ end
43
+
44
+ # Parses the version into a Semantic Puppet version range.
45
+ #
46
+ private def parse_version_requirement(version_requirement)
47
+ [version_requirement, SemanticPuppet::VersionRange.parse(version_requirement || '>= 0')]
48
+ rescue StandardError
49
+ raise Bolt::ValidationError,
50
+ "Invalid version requirement for Forge module specification #{@full_name}: "\
51
+ "#{version_requirement.inspect}"
52
+ end
53
+
54
+ # Returns true if the specification is satisfied by the module.
55
+ #
56
+ def satisfied_by?(mod)
57
+ @type == mod.type &&
58
+ @full_name.downcase == mod.full_name.downcase &&
59
+ !mod.version.nil? &&
60
+ @semantic_version.cover?(mod.version)
61
+ end
62
+
63
+ # Returns a hash matching the module spec in bolt-project.yaml
64
+ #
65
+ def to_hash
66
+ {
67
+ 'name' => @full_name,
68
+ 'version_requirement' => @version_requirement
69
+ }.compact
70
+ end
71
+
72
+ # Creates a PuppetfileResolver::Puppetfile::ForgeModule object, which is
73
+ # used to generate a graph of resolved modules.
74
+ #
75
+ def to_resolver_module
76
+ require 'puppetfile-resolver'
77
+
78
+ PuppetfileResolver::Puppetfile::ForgeModule.new(@full_name).tap do |mod|
79
+ mod.version = @version_requirement
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end