bolt 2.9.0 → 2.10.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +27 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +2 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +122 -0
  6. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  7. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +3 -1
  8. data/bolt-modules/file/lib/puppet/functions/file/join.rb +1 -1
  9. data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -1
  10. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
  11. data/bolt-modules/file/lib/puppet/functions/file/write.rb +3 -1
  12. data/lib/bolt/applicator.rb +1 -1
  13. data/lib/bolt/apply_target.rb +3 -2
  14. data/lib/bolt/bolt_option_parser.rb +13 -4
  15. data/lib/bolt/cli.rb +4 -5
  16. data/lib/bolt/config.rb +2 -2
  17. data/lib/bolt/config/transport/ssh.rb +47 -1
  18. data/lib/bolt/inventory.rb +2 -1
  19. data/lib/bolt/inventory/inventory.rb +5 -0
  20. data/lib/bolt/inventory/target.rb +17 -1
  21. data/lib/bolt/node/output.rb +1 -1
  22. data/lib/bolt/pal/yaml_plan.rb +1 -0
  23. data/lib/bolt/plugin.rb +13 -7
  24. data/lib/bolt/plugin/puppetdb.rb +5 -2
  25. data/lib/bolt/project.rb +13 -6
  26. data/lib/bolt/puppetdb/config.rb +14 -26
  27. data/lib/bolt/resource_instance.rb +126 -0
  28. data/lib/bolt/target.rb +12 -1
  29. data/lib/bolt/transport/ssh.rb +6 -2
  30. data/lib/bolt/transport/ssh/exec_connection.rb +110 -0
  31. data/lib/bolt/version.rb +1 -1
  32. data/lib/bolt_server/pe/pal.rb +1 -38
  33. data/lib/bolt_spec/bolt_context.rb +1 -4
  34. data/lib/bolt_spec/plans/mock_executor.rb +1 -0
  35. data/lib/bolt_spec/run.rb +2 -5
  36. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed1409405ce8a0e0367aea1cb1b7e2d194546c594b96932288c9ae81cef73209
4
- data.tar.gz: 0cfb52ce0dbeffb427988da5e3e338649d2f65defbeef133f01c745802661ac6
3
+ metadata.gz: 4f0e265eb792ba38e627651fad94478a4acbcbf7576912b95f6eaa0b234e6957
4
+ data.tar.gz: ad28f917ac186069e61300142dad31373b4a09dd484a3d131962d383b803d43a
5
5
  SHA512:
6
- metadata.gz: 986a57cd4c6b101ce69f11dec9b6f95212129c2930aded3d0b0c0d8243bcf98ef66b981af0398ecb79cea291603483057c3df7f82e56fa6ab40f7981f4f7ae0d
7
- data.tar.gz: 9ff02c10dd26709f6027c2d9ab45b051d67c0de62c87abe974faee9db28cf2ddde9e086f8cf83e2765bf43e1ff64690d849ad7e032fdc3cbce9990680f5f55f9
6
+ metadata.gz: 9b915748121d6081207a1afac60768008fc752f545ca722c403b58efbb96303417d3add08d8aaf1fd299770fd1202461f2759224b7bdf19592a9f6e5c277705c
7
+ data.tar.gz: 5c25c74ee7e7f7c39e1eb125e5b39fb0fcbefceb6ede50b8c364cacb19bf1eb20cd70dc80d24422e9590d8d8720b7fc25b3b98e147c4c4dee646ffc441d7154b
data/Puppetfile CHANGED
@@ -6,7 +6,7 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '1.2.0'
9
- mod 'puppetlabs-puppet_agent', '3.0.2'
9
+ mod 'puppetlabs-puppet_agent', '3.2.0'
10
10
  mod 'puppetlabs-facts', '1.0.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ Puppet::DataTypes.create_type('ResourceInstance') do
4
+ interface <<-PUPPET
5
+ attributes => {
6
+ 'target' => Target,
7
+ 'type' => Variant[String[1], Type[Resource]],
8
+ 'title' => String[1],
9
+ 'state' => Optional[Hash[String[1], Data]],
10
+ 'desired_state' => Optional[Hash[String[1], Data]],
11
+ 'events' => Optional[Array[Hash[String[1], Data]]]
12
+ },
13
+ functions => {
14
+ add_event => Callable[[Hash[String[1], Data]], [Hash[String[1], Data]]],
15
+ set_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
16
+ overwrite_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
17
+ set_desired_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
18
+ overwrite_desired_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
19
+ reference => Callable[[], String]
20
+ }
21
+ PUPPET
22
+
23
+ load_file('bolt/resource_instance')
24
+ # Needed for Puppet to recognize Bolt::ResourceInstance as a Puppet object when deserializing
25
+ Bolt::ResourceInstance.include(Puppet::Pops::Types::PuppetObject)
26
+ implementation_class Bolt::ResourceInstance
27
+ end
@@ -20,7 +20,8 @@ Puppet::DataTypes.create_type('Target') do
20
20
  vars => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
21
21
  facts => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
22
22
  features => { type => Optional[Array[String[1]]], kind => given_or_derived },
23
- plugin_hooks => { type => Optional[Hash[String[1], Data]], kind => given_or_derived }
23
+ plugin_hooks => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
24
+ resources => { type => Optional[Hash[String[1], ResourceInstance]], kind => given_or_derived }
24
25
  },
25
26
  functions => {
26
27
  host => Callable[[], Optional[String]],
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Sets one or more ResourceInstances on a Target. This function does not apply or modify
6
+ # resources on a target.
7
+ #
8
+ # For more information about resources see [the
9
+ # documentation](https://puppet.com/docs/puppet/latest/lang_resources.html).
10
+ #
11
+ # > **Note:** The `ResourceInstance` data type is under active development and is subject to
12
+ # change. You can read more about the data type in the [experimental features
13
+ # documentation](experimental_features.md#resourceinstance-data-type).
14
+ #
15
+ # > **Note:** Not available in apply block
16
+ Puppet::Functions.create_function(:set_resources) do
17
+ # Set multiple resources
18
+ # @param target The `Target` object to add resources to. See {get_targets}.
19
+ # @param resources The resources to set on the target.
20
+ # @return The added `ResourceInstance` objects.
21
+ # @example Add multiple resources to a target with an array of `ResourceInstance` objects.
22
+ # $resource1 = ResourceInstance.new(
23
+ # 'target' => $target,
24
+ # 'type' => 'file',
25
+ # 'title' => '/etc/puppetlabs',
26
+ # 'state' => { 'ensure' => 'present' }
27
+ # )
28
+ # $resource2 = ResourceInstance.new(
29
+ # 'target' => $target,
30
+ # 'type' => 'package',
31
+ # 'title' => 'openssl',
32
+ # 'state' => { 'ensure' => 'installed' }
33
+ # )
34
+ # $target.set_resources([$resource1, $resource2])
35
+ # @example Add resources retrieved with [`get_resources`](#get_resources) to a target.
36
+ # $target.apply_prep
37
+ # $resources = $target.get_resources(Package).first['resources']
38
+ # $target.set_resources($resources)
39
+ dispatch :set_resources do
40
+ param 'Target', :target
41
+ param 'Array[Variant[Hash, ResourceInstance]]', :resources
42
+ return_type 'Array[ResourceInstance]'
43
+ end
44
+
45
+ # Set a single resource
46
+ # @param target The `Target` object to add resources to. See {get_targets}.
47
+ # @param resource The resource to set on the target.
48
+ # @return The added `ResourceInstance` object.
49
+ # @example Add a single resource to a target with a resource data hash.
50
+ # $resource = {
51
+ # 'type' => 'file',
52
+ # 'title' => '/etc/puppetlabs',
53
+ # 'state' => { 'ensure' => 'present' }
54
+ # }
55
+ # $target.set_resources($resource)
56
+ dispatch :set_resource do
57
+ param 'Target', :target
58
+ param 'Variant[Hash, ResourceInstance]', :resource
59
+ return_type 'Array[ResourceInstance]'
60
+ end
61
+
62
+ def set_resources(target, resources)
63
+ unless Puppet[:tasks]
64
+ raise Puppet::ParseErrorWithIssue
65
+ .from_issue_and_stack(
66
+ Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
67
+ action: 'set_resources'
68
+ )
69
+ end
70
+
71
+ inventory = Puppet.lookup(:bolt_inventory)
72
+ executor = Puppet.lookup(:bolt_executor)
73
+ executor.report_function_call(self.class.name)
74
+
75
+ inventory_target = inventory.get_target(target)
76
+
77
+ resources.uniq.map do |resource|
78
+ if resource.is_a?(Hash)
79
+ # ResourceInstance expects a Target object, so either get a specified target from
80
+ # the inventory or use the target this function was called on.
81
+ resource_target = if resource.key?('target')
82
+ inventory.get_target(resource['target'])
83
+ else
84
+ inventory_target
85
+ end
86
+
87
+ # Observed state from get_resources() is under the 'parameters' key
88
+ resource_state = resource['state'] || resource['parameters']
89
+
90
+ init_hash = {
91
+ 'target' => resource_target,
92
+ 'type' => resource['type'],
93
+ 'title' => resource['title'],
94
+ 'state' => resource_state,
95
+ 'desired_state' => resource['desired_state'],
96
+ 'events' => resource['events']
97
+ }
98
+
99
+ # Calling Bolt::ResourceInstance.new or Bolt::ResourceInstance.from_asserted_hash
100
+ # will not perform any validation on the parameters. Instead, we need to use the
101
+ # Puppet constructor to initialize the object, which will first validate the parameters
102
+ # and then call Bolt::ResourceInstance.from_asserted_hash internally. To do this we
103
+ # first need to get the Puppet datatype and then call the new function on that type.
104
+ type = Puppet::Pops::Types::TypeParser.singleton.parse('ResourceInstance')
105
+ resource = call_function('new', type, init_hash)
106
+ end
107
+
108
+ unless resource.target == inventory_target
109
+ file, line = Puppet::Pops::PuppetStack.top_of_stack
110
+ raise Bolt::ValidationError, "Cannot set resource #{resource.reference} for target "\
111
+ "#{resource.target} on target #{inventory_target}. "\
112
+ "#{Puppet::Util::Errors.error_location(file, line)}"
113
+ end
114
+
115
+ inventory_target.set_resource(resource)
116
+ end
117
+ end
118
+
119
+ def set_resource(target, resource)
120
+ set_resources(target, [resource])
121
+ end
122
+ end
@@ -11,5 +11,6 @@ type Boltlib::PlanResult = Variant[Boolean,
11
11
  ApplyResult,
12
12
  ResultSet,
13
13
  Target,
14
+ ResourceInstance,
14
15
  Array[Boltlib::PlanResult],
15
16
  Hash[String, Boltlib::PlanResult]]
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Check if a file exists.
3
+ # Check if a local file exists using Puppet's
4
+ # `Puppet::Parser::Files.find_file()` function. This will only check files that
5
+ # are on the machine Bolt is run on.
4
6
  Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFunction) do
5
7
  # @param filename Absolute path or Puppet file path.
6
8
  # @return Whether the file exists.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Join file paths.
3
+ # Join file paths using ruby's `File.join()` function.
4
4
  Puppet::Functions.create_function(:'file::join') do
5
5
  # @param paths The paths to join.
6
6
  # @return The joined file path.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Read a file and return its contents.
3
+ # Read a file on localhost and return its contents using ruby's `File.read`. This will
4
+ # only read files on the machine you run Bolt on.
4
5
  Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunction) do
5
6
  # @param filename Absolute path or Puppet file path.
6
7
  # @return The file's contents.
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Check if a file is readable.
3
+ # Check if a local file is readable using Puppet's
4
+ # `Puppet::Parser::Files.find_file()` function. This will only check files on the
5
+ # machine you run Bolt on.
4
6
  Puppet::Functions.create_function(:'file::readable', Puppet::Functions::InternalFunction) do
5
7
  # @param filename Absolute path or Puppet file path.
6
8
  # @return Whether the file is readable.
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Write a string to a file.
3
+ # Write a string to a file on localhost using ruby's `File.write`. This will
4
+ # only write files to the machine you run Bolt on. Use `write_file()` to write
5
+ # to remote targets.
4
6
  Puppet::Functions.create_function(:'file::write') do
5
7
  # @param filename Absolute path.
6
8
  # @param content File content to write.
@@ -181,7 +181,7 @@ module Bolt
181
181
  rich_data: true,
182
182
  symbol_as_string: true,
183
183
  type_by_reference: true,
184
- local_reference: false)
184
+ local_reference: true)
185
185
 
186
186
  scope = {
187
187
  code_ast: ast,
@@ -3,7 +3,7 @@
3
3
  module Bolt
4
4
  class ApplyTarget
5
5
  ATTRIBUTES = %i[uri name target_alias config vars facts features
6
- plugin_hooks safe_name].freeze
6
+ plugin_hooks resources safe_name].freeze
7
7
  COMPUTED = %i[host password port protocol user].freeze
8
8
 
9
9
  attr_reader(*ATTRIBUTES)
@@ -24,7 +24,8 @@ module Bolt
24
24
  facts = nil,
25
25
  vars = nil,
26
26
  features = nil,
27
- plugin_hooks = nil)
27
+ plugin_hooks = nil,
28
+ resources = nil)
28
29
  raise Bolt::Error.new("Target objects cannot be instantiated inside apply blocks", 'bolt/apply-error')
29
30
  end
30
31
  # rubocop:enable Lint/UnusedMethodArgument
@@ -11,7 +11,7 @@ module Bolt
11
11
  escalation: %w[run-as sudo-password sudo-password-prompt sudo-executable],
12
12
  run_context: %w[concurrency inventoryfile save-rerun cleanup],
13
13
  global_config_setters: %w[modulepath boltdir configfile],
14
- transports: %w[transport connect-timeout tty],
14
+ transports: %w[transport connect-timeout tty ssh-command copy-command],
15
15
  display: %w[format color verbose trace],
16
16
  global: %w[help version debug] }.freeze
17
17
 
@@ -594,6 +594,7 @@ module Bolt
594
594
  HELP
595
595
 
596
596
  attr_reader :warnings
597
+
597
598
  def initialize(options)
598
599
  super()
599
600
 
@@ -655,8 +656,8 @@ module Bolt
655
656
  @options[:password] = STDIN.noecho(&:gets).chomp
656
657
  STDERR.puts
657
658
  end
658
- define('--private-key KEY', 'Private ssh key to authenticate with') do |key|
659
- @options[:'private-key'] = key
659
+ define('--private-key KEY', 'Path to private ssh key to authenticate with') do |key|
660
+ @options[:'private-key'] = File.expand_path(key)
660
661
  end
661
662
  define('--[no-]host-key-check', 'Check host keys with SSH') do |host_key_check|
662
663
  @options[:'host-key-check'] = host_key_check
@@ -718,7 +719,7 @@ module Bolt
718
719
  end
719
720
  define('--hiera-config FILEPATH',
720
721
  'Specify where to load Hiera config from (default: ~/.puppetlabs/bolt/hiera.yaml)') do |path|
721
- @options[:'hiera-config'] = path
722
+ @options[:'hiera-config'] = File.expand_path(path)
722
723
  end
723
724
  define('-i', '--inventoryfile FILEPATH',
724
725
  'Specify where to load inventory from (default: ~/.puppetlabs/bolt/inventory.yaml)') do |path|
@@ -741,6 +742,14 @@ module Bolt
741
742
  "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
742
743
  @options[:transport] = t
743
744
  end
745
+ define('--ssh-command EXEC', "Executable to use instead of the net-ssh ruby library. ",
746
+ "This option is experimental.") do |exec|
747
+ @options[:'ssh-command'] = exec
748
+ end
749
+ define('--copy-command EXEC', "Command to copy files to remote hosts if using external SSH. ",
750
+ "This option is experimental.") do |exec|
751
+ @options[:'copy-command'] = exec
752
+ end
744
753
  define('--connect-timeout TIMEOUT', Integer, 'Connection timeout (defaults vary)') do |timeout|
745
754
  @options[:'connect-timeout'] = timeout
746
755
  end
@@ -124,8 +124,9 @@ module Bolt
124
124
 
125
125
  Bolt::Logger.configure(config.log, config.color)
126
126
 
127
- # Logger must be configured before checking path case, otherwise warnings will not display
127
+ # Logger must be configured before checking path case and project file, otherwise warnings will not display
128
128
  @config.check_path_case('modulepath', @config.modulepath)
129
+ @config.project.check_deprecated_file
129
130
 
130
131
  # Log the file paths for loaded config files
131
132
  config_loaded
@@ -257,13 +258,11 @@ module Bolt
257
258
  end
258
259
 
259
260
  def puppetdb_client
260
- return @puppetdb_client if @puppetdb_client
261
- puppetdb_config = Bolt::PuppetDB::Config.load_config(nil, config.puppetdb, config.project.path)
262
- @puppetdb_client = Bolt::PuppetDB::Client.new(puppetdb_config)
261
+ plugins.puppetdb_client
263
262
  end
264
263
 
265
264
  def plugins
266
- @plugins ||= Bolt::Plugin.setup(config, pal, puppetdb_client, analytics)
265
+ @plugins ||= Bolt::Plugin.setup(config, pal, analytics)
267
266
  end
268
267
 
269
268
  def query_puppetdb_nodes(query)
@@ -312,10 +312,10 @@ module Bolt
312
312
  @warnings << { option: 'future', msg: msg }
313
313
  end
314
314
 
315
- keys = OPTIONS.keys - %w[plugins plugin_hooks]
315
+ keys = OPTIONS.keys - %w[plugins plugin_hooks puppetdb]
316
316
  keys.each do |key|
317
317
  next unless Bolt::Util.references?(@data[key])
318
- valid_keys = TRANSPORT_CONFIG.keys + %w[plugins plugin_hooks]
318
+ valid_keys = TRANSPORT_CONFIG.keys + %w[plugins plugin_hooks puppetdb]
319
319
  raise Bolt::ValidationError,
320
320
  "Found unsupported key _plugin in config setting #{key}. Plugins are only available in "\
321
321
  "#{valid_keys.join(', ')}."
@@ -13,14 +13,20 @@ module Bolt
13
13
  # in schemas/bolt-transport-definitions.json
14
14
  OPTIONS = {
15
15
  "cleanup" => { type: TrueClass,
16
+ external: true,
16
17
  desc: "Whether to clean up temporary files created on targets." },
17
18
  "connect-timeout" => { type: Integer,
18
19
  desc: "How long to wait when establishing connections." },
20
+ "copy-command" => { external: true,
21
+ desc: "Command to use when copying files using ssh-command. "\
22
+ "Bolt runs `<copy-command> <src> <dest>`. **This option is experimental.**" },
19
23
  "disconnect-timeout" => { type: Integer,
20
24
  desc: "How long to wait before force-closing a connection." },
21
25
  "host" => { type: String,
26
+ external: true,
22
27
  desc: "Host name." },
23
28
  "host-key-check" => { type: TrueClass,
29
+ external: true,
24
30
  desc: "Whether to perform host key validation when connecting." },
25
31
  "extensions" => { type: Array,
26
32
  desc: "List of file extensions that are accepted for scripts or tasks on Windows. "\
@@ -29,6 +35,7 @@ module Bolt
29
35
  "a `.py` script runs with `python.exe`. The extensions `.ps1`, `.rb`, and "\
30
36
  "`.pp` are always allowed and run via hard-coded executables." },
31
37
  "interpreters" => { type: Hash,
38
+ external: true,
32
39
  desc: "A map of an extension name to the absolute path of an executable, "\
33
40
  "enabling you to override the shebang defined in a task executable. The "\
34
41
  "extension can optionally be specified with the `.` character (`.py` and "\
@@ -44,26 +51,36 @@ module Bolt
44
51
  "password" => { type: String,
45
52
  desc: "Login password." },
46
53
  "port" => { type: Integer,
54
+ external: true,
47
55
  desc: "Connection port." },
48
- "private-key" => { desc: "Either the path to the private key file to use for authentication, or a "\
56
+ "private-key" => { external: true,
57
+ desc: "Either the path to the private key file to use for authentication, or a "\
49
58
  "hash with the key `key-data` and the contents of the private key." },
50
59
  "proxyjump" => { type: String,
51
60
  desc: "A jump host to proxy connections through, and an optional user to "\
52
61
  "connect with." },
53
62
  "run-as" => { type: String,
63
+ external: true,
54
64
  desc: "A different user to run commands as after login." },
55
65
  "run-as-command" => { type: Array,
66
+ external: true,
56
67
  desc: "The command to elevate permissions. Bolt appends the user and command "\
57
68
  "strings to the configured `run-as-command` before running it on the "\
58
69
  "target. This command must not require an interactive password prompt, "\
59
70
  "and the `sudo-password` option is ignored when `run-as-command` is "\
60
71
  "specified. The `run-as-command` must be specified as an array." },
61
72
  "script-dir" => { type: String,
73
+ external: true,
62
74
  desc: "The subdirectory of the tmpdir to use in place of a randomized "\
63
75
  "subdirectory for uploading and executing temporary files on the "\
64
76
  "target. It's expected that this directory already exists as a subdir "\
65
77
  "of tmpdir, which is either configured or defaults to `/tmp`." },
78
+ "ssh-command" => { external: true,
79
+ desc: "Command and flags to use when SSHing. This enables the external "\
80
+ "SSH transport which shells out to the specified command. "\
81
+ "**This option is experimental.**" },
66
82
  "sudo-executable" => { type: String,
83
+ external: true,
67
84
  desc: "The executable to use when escalating to the configured `run-as` "\
68
85
  "user. This is useful when you want to escalate using the configured "\
69
86
  "`sudo-password`, since `run-as-command` does not use `sudo-password` "\
@@ -71,14 +88,17 @@ module Bolt
71
88
  "`<sudo-executable> -S -u <user> -p custom_bolt_prompt <command>`. "\
72
89
  "**This option is experimental.**" },
73
90
  "sudo-password" => { type: String,
91
+ external: true,
74
92
  desc: "Password to use when changing users via `run-as`." },
75
93
  "tmpdir" => { type: String,
94
+ external: true,
76
95
  desc: "The directory to upload and execute temporary files on the target." },
77
96
  "tty" => { type: TrueClass,
78
97
  desc: "Request a pseudo tty for the session. This option is generally "\
79
98
  "only used in conjunction with the `run-as` option when the sudoers "\
80
99
  "policy requires a `tty`." },
81
100
  "user" => { type: String,
101
+ external: true,
82
102
  desc: "Login user." }
83
103
  }.freeze
84
104
 
@@ -103,6 +123,13 @@ module Bolt
103
123
 
104
124
  if key_opt.instance_of?(String)
105
125
  @config['private-key'] = File.expand_path(key_opt, @project)
126
+
127
+ # We have an explicit test for this to only warn if using net-ssh transport
128
+ Bolt::Util.validate_file('ssh key', @config['private-key']) if @config['ssh-command']
129
+ end
130
+
131
+ if key_opt.instance_of?(Hash) && @config['ssh-command']
132
+ raise Bolt::ValidationError, 'private-key must be a filepath when using ssh-command'
106
133
  end
107
134
  end
108
135
 
@@ -130,6 +157,25 @@ module Bolt
130
157
  end
131
158
  end
132
159
  end
160
+
161
+ if @config['ssh-command'] && !@config['load-config']
162
+ msg = 'Cannot use external SSH transport with load-config set to false'
163
+ raise Bolt::ValidationError, msg
164
+ end
165
+
166
+ if (ssh_cmd = @config['ssh-command'])
167
+ unless ssh_cmd.is_a?(String) || ssh_cmd.is_a?(Array)
168
+ raise Bolt::ValidationError,
169
+ "ssh-command must be a String or Array, received #{ssh_cmd.class} #{ssh_cmd.inspect}"
170
+ end
171
+ end
172
+
173
+ if (copy_cmd = @config['copy-command'])
174
+ unless copy_cmd.is_a?(String) || copy_cmd.is_a?(Array)
175
+ raise Bolt::ValidationError,
176
+ "copy-command must be a String or Array, received #{copy_cmd.class} #{copy_cmd.inspect}"
177
+ end
178
+ end
133
179
  end
134
180
  end
135
181
  end
@@ -15,6 +15,7 @@ module Bolt
15
15
 
16
16
  class ValidationError < Bolt::Error
17
17
  attr_accessor :path
18
+
18
19
  def initialize(message, offending_group)
19
20
  super(message, 'bolt.inventory/validation-error')
20
21
  @_message = message
@@ -83,7 +84,7 @@ module Bolt
83
84
 
84
85
  def self.empty
85
86
  config = Bolt::Config.default
86
- plugins = Bolt::Plugin.setup(config, nil, nil, Bolt::Analytics::NoopClient)
87
+ plugins = Bolt::Plugin.setup(config, nil)
87
88
 
88
89
  create_version({}, config.transport, config.transports, plugins)
89
90
  end
@@ -7,6 +7,7 @@ module Bolt
7
7
  class Inventory
8
8
  class Inventory
9
9
  attr_reader :targets, :plugins, :config, :transport
10
+
10
11
  class WildcardError < Bolt::Error
11
12
  def initialize(target)
12
13
  super("Found 0 targets matching wildcard pattern #{target}", 'bolt.inventory/wildcard-error')
@@ -318,6 +319,10 @@ module Bolt
318
319
  def target_config(target)
319
320
  @targets[target.name].config
320
321
  end
322
+
323
+ def resources(target)
324
+ @targets[target.name].resources
325
+ end
321
326
  end
322
327
  end
323
328
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt/resource_instance'
4
+
3
5
  module Bolt
4
6
  class Inventory
5
7
  # This class represents the active state of a target within the inventory.
6
8
  class Target
7
- attr_reader :name, :uri, :safe_name, :target_alias
9
+ attr_reader :name, :uri, :safe_name, :target_alias, :resources
8
10
 
9
11
  def initialize(target_data, inventory)
10
12
  unless target_data['name'] || target_data['uri']
@@ -36,12 +38,26 @@ module Bolt
36
38
  # When alias is specified in a plan, the key will be `target_alias`, when
37
39
  # alias is specified in inventory the key will be `alias`.
38
40
  @target_alias = target_data['target_alias'] || target_data['alias'] || []
41
+ @resources = {}
39
42
 
40
43
  @inventory = inventory
41
44
 
42
45
  validate
43
46
  end
44
47
 
48
+ # rubocop:disable Naming/AccessorMethodName
49
+ def set_resource(resource)
50
+ if (existing_resource = resources[resource.reference])
51
+ existing_resource.overwrite_state(resource.state)
52
+ existing_resource.overwrite_desired_state(resource.desired_state)
53
+ existing_resource.events = existing_resource.events + resource.events
54
+ existing_resource
55
+ else
56
+ @resources[resource.reference] = resource
57
+ end
58
+ end
59
+ # rubocop:enable Naming/AccessorMethodName
60
+
45
61
  def vars
46
62
  group_cache['vars'].merge(@vars)
47
63
  end
@@ -12,7 +12,7 @@ module Bolt
12
12
  def initialize
13
13
  @stdout = StringIO.new
14
14
  @stderr = StringIO.new
15
- @exit_code = 'unkown'
15
+ @exit_code = 'unknown'
16
16
  end
17
17
  end
18
18
  end
@@ -99,6 +99,7 @@ module Bolt
99
99
  # logic.
100
100
  class EvaluableString
101
101
  attr_reader :value
102
+
102
103
  def initialize(value)
103
104
  @value = value
104
105
  end
@@ -119,13 +119,9 @@ module Bolt
119
119
  end
120
120
  end
121
121
 
122
- def self.setup(config, pal, pdb_client, analytics)
122
+ def self.setup(config, pal, analytics = Bolt::Analytics::NoopClient.new)
123
123
  plugins = new(config, pal, analytics)
124
124
 
125
- # PDB is special because it needs the PDB client. Since it has no config,
126
- # we can just add it first.
127
- plugins.add_plugin(Bolt::Plugin::Puppetdb.new(pdb_client))
128
-
129
125
  # Initialize any plugins referenced in plugin config. This will also indirectly
130
126
  # initialize any plugins they depend on.
131
127
  if plugins.reference?(config.plugins)
@@ -142,7 +138,7 @@ module Bolt
142
138
  plugins
143
139
  end
144
140
 
145
- RUBY_PLUGINS = %w[task prompt env_var].freeze
141
+ RUBY_PLUGINS = %w[task prompt env_var puppetdb].freeze
146
142
  BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory
147
143
  yaml env_var gcloud_inventory].freeze
148
144
  DEFAULT_PLUGIN_HOOKS = { 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }.freeze
@@ -161,6 +157,13 @@ module Bolt
161
157
  @unknown = Set.new
162
158
  @resolution_stack = []
163
159
  @unresolved_plugin_configs = config.plugins.dup
160
+ # The puppetdb plugin config comes from the puppetdb section, not from
161
+ # the plugins section
162
+ if @unresolved_plugin_configs.key?('puppetdb')
163
+ msg = "Configuration for the PuppetDB plugin must be in the 'puppetdb' config section, not 'plugins'"
164
+ raise Bolt::Error.new(msg, 'bolt/plugin-error')
165
+ end
166
+ @unresolved_plugin_configs['puppetdb'] = config.puppetdb if config.puppetdb
164
167
  @plugin_hooks = DEFAULT_PLUGIN_HOOKS.dup
165
168
  end
166
169
 
@@ -168,7 +171,6 @@ module Bolt
168
171
  @modules ||= Bolt::Module.discover(@pal.modulepath)
169
172
  end
170
173
 
171
- # Generally this is private. Puppetdb is special though
172
174
  def add_plugin(plugin)
173
175
  @plugins[plugin.name] = plugin
174
176
  end
@@ -235,6 +237,10 @@ module Bolt
235
237
  end
236
238
  end
237
239
 
240
+ def puppetdb_client
241
+ by_name('puppetdb').puppetdb_client
242
+ end
243
+
238
244
  # Evaluate all _plugin references in a data structure. Leaves are
239
245
  # evaluated and then their parents are evaluated with references replaced
240
246
  # by their values. If the result of a reference contains more references,
@@ -14,8 +14,11 @@ module Bolt
14
14
  TEMPLATE_OPTS = %w[alias config facts features name uri vars].freeze
15
15
  PLUGIN_OPTS = %w[_plugin query target_mapping].freeze
16
16
 
17
- def initialize(pdb_client)
18
- @puppetdb_client = pdb_client
17
+ attr_reader :puppetdb_client
18
+
19
+ def initialize(config:, context:)
20
+ pdb_config = Bolt::PuppetDB::Config.load_config(config, context.boltdir)
21
+ @puppetdb_client = Bolt::PuppetDB::Client.new(pdb_config)
19
22
  @logger = Logging.logger[self]
20
23
  end
21
24
 
@@ -27,7 +27,7 @@ module Bolt
27
27
  dir = Pathname.new(dir)
28
28
  if (dir + BOLTDIR_NAME).directory?
29
29
  new(dir + BOLTDIR_NAME, 'embedded')
30
- elsif (dir + 'bolt.yaml').file?
30
+ elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
31
31
  new(dir, 'local')
32
32
  elsif dir.root?
33
33
  default_project
@@ -47,7 +47,7 @@ module Bolt
47
47
  @resource_types = @path + '.resource_types'
48
48
  @type = type
49
49
 
50
- @project_file = @path + 'project.yaml'
50
+ @project_file = @path + 'bolt-project.yaml'
51
51
  @data = Bolt::Util.read_optional_yaml_hash(File.expand_path(@project_file), 'project') || {}
52
52
  validate if load_as_module?
53
53
  end
@@ -72,7 +72,7 @@ module Bolt
72
72
  end
73
73
 
74
74
  def name
75
- # If the project is in mymod/Boltdir/project.yaml, use mymod as the project name
75
+ # If the project is in mymod/Boltdir/bolt-project.yaml, use mymod as the project name
76
76
  dirname = @path.basename.to_s == 'Boltdir' ? @path.parent.basename.to_s : @path.basename.to_s
77
77
  pname = @data['name'] || dirname
78
78
  pname.include?('-') ? pname.split('-', 2)[1] : pname
@@ -100,7 +100,7 @@ module Bolt
100
100
  n = @data['name']
101
101
  if n && !project_directory_name?(n) && !project_namespaced_name?(n)
102
102
  raise Bolt::ValidationError, <<~ERROR_STRING
103
- Invalid project name '#{n}' in project.yaml; project names must match either:
103
+ Invalid project name '#{n}' in bolt-project.yaml; project names must match either:
104
104
  An installed project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
105
105
  A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
106
106
  ERROR_STRING
@@ -110,7 +110,7 @@ module Bolt
110
110
  A project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
111
111
  A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
112
112
 
113
- Configure project name in <project_dir>/project.yaml
113
+ Configure project name in <project_dir>/bolt-project.yaml
114
114
  ERROR_STRING
115
115
  # If the project name is the same as one of the built-in modules raise a warning
116
116
  elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
@@ -120,9 +120,16 @@ module Bolt
120
120
 
121
121
  %w[tasks plans].each do |conf|
122
122
  unless @data.fetch(conf, []).is_a?(Array)
123
- raise Bolt::ValidationError, "'#{conf}' in project.yaml must be an array"
123
+ raise Bolt::ValidationError, "'#{conf}' in bolt-project.yaml must be an array"
124
124
  end
125
125
  end
126
126
  end
127
+
128
+ def check_deprecated_file
129
+ if (@path + 'project.yaml').file?
130
+ logger = Logging.logger[self]
131
+ logger.warn "Project configuration file 'project.yaml' is deprecated; use 'bolt-project.yaml' instead."
132
+ end
133
+ end
127
134
  end
128
135
  end
@@ -22,27 +22,20 @@ module Bolt
22
22
  File.expand_path(File.join(Dir::COMMON_APPDATA, 'PuppetLabs/client-tools/puppetdb.conf'))
23
23
  end
24
24
 
25
- def self.load_config(filename, options, project_path = nil)
25
+ def self.load_config(options, project_path = nil)
26
26
  config = {}
27
27
  global_path = Bolt::Util.windows? ? default_windows_config : DEFAULT_CONFIG[:global]
28
- if filename
29
- if File.exist?(filename)
30
- config = JSON.parse(File.read(filename))
31
- else
32
- raise Bolt::PuppetDBError, "config file #{filename} does not exist"
33
- end
34
- else
35
- if File.exist?(DEFAULT_CONFIG[:user])
36
- filepath = DEFAULT_CONFIG[:user]
37
- elsif File.exist?(global_path)
38
- filepath = global_path
39
- end
40
28
 
41
- begin
42
- config = JSON.parse(File.read(filepath)) if filepath
43
- rescue StandardError => e
44
- Logging.logger[self].error("Could not load puppetdb.conf from #{filepath}: #{e.message}")
45
- end
29
+ if File.exist?(DEFAULT_CONFIG[:user])
30
+ filepath = DEFAULT_CONFIG[:user]
31
+ elsif File.exist?(global_path)
32
+ filepath = global_path
33
+ end
34
+
35
+ begin
36
+ config = JSON.parse(File.read(filepath)) if filepath
37
+ rescue StandardError => e
38
+ Logging.logger[self].error("Could not load puppetdb.conf from #{filepath}: #{e.message}")
46
39
  end
47
40
 
48
41
  config = config.fetch('puppetdb', {})
@@ -51,8 +44,7 @@ module Bolt
51
44
 
52
45
  def initialize(settings, project_path = nil)
53
46
  @settings = settings
54
- @project_path = project_path
55
- expand_paths
47
+ expand_paths(project_path)
56
48
  end
57
49
 
58
50
  def token
@@ -68,14 +60,10 @@ module Bolt
68
60
  @token = @token.strip if @token
69
61
  end
70
62
 
71
- def expand_paths
63
+ def expand_paths(project_path)
72
64
  %w[cacert cert key token].each do |file|
73
65
  next unless @settings[file]
74
- @settings[file] = if @project_path
75
- File.expand_path(@settings[file], @project_path)
76
- else
77
- File.expand_path(@settings[file])
78
- end
66
+ @settings[file] = File.expand_path(@settings[file], project_path)
79
67
  end
80
68
  end
81
69
 
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Bolt
6
+ class ResourceInstance
7
+ attr_reader :target, :type, :title, :state, :desired_state
8
+ attr_accessor :events
9
+
10
+ # Needed by Puppet to recognize Bolt::ResourceInstance as a Puppet object when deserializing
11
+ def self._pcore_type
12
+ ResourceInstance
13
+ end
14
+
15
+ # Needed by Puppet to serialize with _pcore_init_hash instead of the object's attributes
16
+ def self._pcore_init_from_hash(_init_hash)
17
+ raise "ResourceInstance shouldn't be instantiated from a pcore_init class method. "\
18
+ "How did this get called?"
19
+ end
20
+
21
+ def _pcore_init_from_hash(init_hash)
22
+ initialize(init_hash)
23
+ end
24
+
25
+ # Parameters will already be validated when calling ResourceInstance.new or
26
+ # set_resources() from a plan. We don't perform any validation in the class
27
+ # itself since Puppet will pass an empty hash to the initializer as part of
28
+ # the deserialization process before passing the _pcore_init_hash.
29
+ def initialize(resource_hash)
30
+ @target = resource_hash['target']
31
+ @type = resource_hash['type'].to_s.capitalize
32
+ @title = resource_hash['title']
33
+ # get_resources() returns observed state under the 'parameters' key
34
+ @state = resource_hash['state'] || resource_hash['parameters'] || {}
35
+ @desired_state = resource_hash['desired_state'] || {}
36
+ @events = resource_hash['events'] || []
37
+ end
38
+
39
+ # Creates a ResourceInstance from a data hash in a plan when calling
40
+ # ResourceInstance.new($resource_hash) or $target.set_resources($resource_hash)
41
+ def self.from_asserted_hash(resource_hash)
42
+ new(resource_hash)
43
+ end
44
+
45
+ # Creates a ResourceInstance from positional arguments in a plan when
46
+ # calling ResourceInstance.new(target, type, title, ...)
47
+ def self.from_asserted_args(target,
48
+ type,
49
+ title,
50
+ state = nil,
51
+ desired_state = nil,
52
+ events = nil)
53
+ new(
54
+ 'target' => target,
55
+ 'type' => type,
56
+ 'title' => title,
57
+ 'state' => state,
58
+ 'desired_state' => desired_state,
59
+ 'events' => events
60
+ )
61
+ end
62
+
63
+ def eql?(other)
64
+ self.class.equal?(other.class) &&
65
+ target == other.target &&
66
+ type == other.type &&
67
+ title == other.title
68
+ end
69
+ alias == eql?
70
+
71
+ def to_hash
72
+ {
73
+ 'target' => target,
74
+ 'type' => type,
75
+ 'title' => title,
76
+ 'state' => state,
77
+ 'desired_state' => desired_state,
78
+ 'events' => events
79
+ }
80
+ end
81
+ alias _pcore_init_hash to_hash
82
+
83
+ def to_json(opts = nil)
84
+ to_hash.to_json(opts)
85
+ end
86
+
87
+ def reference
88
+ "#{type}[#{title}]"
89
+ end
90
+ alias to_s reference
91
+
92
+ def add_event(event)
93
+ @events << event
94
+ end
95
+
96
+ # rubocop:disable Naming/AccessorMethodName
97
+ def set_state(state)
98
+ assert_hash('state', state)
99
+ @state.merge!(state)
100
+ end
101
+ # rubocop:enable Naming/AccessorMethodName
102
+
103
+ def overwrite_state(state)
104
+ assert_hash('state', state)
105
+ @state = state
106
+ end
107
+
108
+ # rubocop:disable Naming/AccessorMethodName
109
+ def set_desired_state(desired_state)
110
+ assert_hash('desired_state', desired_state)
111
+ @desired_state.merge!(desired_state)
112
+ end
113
+ # rubocop:enable Naming/AccessorMethodName
114
+
115
+ def overwrite_desired_state(desired_state)
116
+ assert_hash('desired_state', desired_state)
117
+ @desired_state = desired_state
118
+ end
119
+
120
+ def assert_hash(loc, value)
121
+ unless value.is_a?(Hash)
122
+ raise Bolt::ValidationError, "#{loc} must be of type Hash; got #{value.class}"
123
+ end
124
+ end
125
+ end
126
+ end
@@ -31,7 +31,8 @@ module Bolt
31
31
  facts = nil,
32
32
  vars = nil,
33
33
  features = nil,
34
- plugin_hooks = nil)
34
+ plugin_hooks = nil,
35
+ resources = nil)
35
36
  from_asserted_hash('uri' => uri)
36
37
  end
37
38
  # rubocop:enable Lint/UnusedMethodArgument
@@ -75,6 +76,16 @@ module Bolt
75
76
  inventory_target.target_alias
76
77
  end
77
78
 
79
+ def resources
80
+ inventory_target.resources
81
+ end
82
+
83
+ # rubocop:disable Naming/AccessorMethodName
84
+ def set_resource(resource)
85
+ inventory_target.set_resource(resource)
86
+ end
87
+ # rubocop:enable Naming/AccessorMethodName
88
+
78
89
  def to_h
79
90
  options.to_h.merge(
80
91
  'name' => name,
@@ -16,13 +16,16 @@ module Bolt
16
16
  rescue LoadError
17
17
  logger.debug("Authentication method 'gssapi-with-mic' (Kerberos) is not available.")
18
18
  end
19
-
20
19
  @transport_logger = Logging.logger[Net::SSH]
21
20
  @transport_logger.level = :warn
22
21
  end
23
22
 
24
23
  def with_connection(target)
25
- conn = Connection.new(target, @transport_logger)
24
+ conn = if target.transport_config['ssh-command']
25
+ ExecConnection.new(target)
26
+ else
27
+ Connection.new(target, @transport_logger)
28
+ end
26
29
  conn.connect
27
30
  yield conn
28
31
  ensure
@@ -37,3 +40,4 @@ module Bolt
37
40
  end
38
41
 
39
42
  require 'bolt/transport/ssh/connection'
43
+ require 'bolt/transport/ssh/exec_connection'
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module Bolt
6
+ module Transport
7
+ class SSH < Simple
8
+ class ExecConnection
9
+ attr_reader :user, :target
10
+
11
+ def initialize(target)
12
+ raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
13
+
14
+ @target = target
15
+ ssh_config = Net::SSH::Config.for(target.host)
16
+ @user = @target.user || ssh_config[:user] || Etc.getlogin
17
+ @logger = Logging.logger[self]
18
+ end
19
+
20
+ # This is used to verify we can connect to targets with `connected?`
21
+ def connect
22
+ cmd = build_ssh_command('exit')
23
+ _, err, stat = Open3.capture3(*cmd)
24
+ unless stat.success?
25
+ raise Bolt::Node::ConnectError.new(
26
+ "Failed to connect to #{@target.safe_name}: #{err}",
27
+ 'CONNECT_ERROR'
28
+ )
29
+ end
30
+ end
31
+
32
+ def disconnect; end
33
+
34
+ def shell
35
+ Bolt::Shell::Bash.new(@target, self)
36
+ end
37
+
38
+ def userhost
39
+ "#{@user}@#{@target.host}"
40
+ end
41
+
42
+ def ssh_opts
43
+ cmd = []
44
+ # BatchMode is SSH's noninteractive option: if key authentication
45
+ # fails it will error out instead of falling back to password prompt
46
+ cmd += %w[-o BatchMode=yes]
47
+ cmd += %W[-o Port=#{@target.port}] if @target.port
48
+
49
+ if @target.transport_config.key?('host-key-check')
50
+ hkc = @target.transport_config['host-key-check'] ? 'yes' : 'no'
51
+ cmd += %W[-o StrictHostKeyChecking=#{hkc}]
52
+ end
53
+
54
+ if (key = target.transport_config['private-key'])
55
+ cmd += ['-i', key]
56
+ end
57
+ cmd
58
+ end
59
+
60
+ def build_ssh_command(command)
61
+ ssh_conf = @target.transport_config['ssh-command']
62
+ ssh_cmd = Array(ssh_conf)
63
+ ssh_cmd += ssh_opts
64
+ ssh_cmd << userhost
65
+ ssh_cmd << command
66
+ end
67
+
68
+ def copy_file(source, dest)
69
+ @logger.debug { "Uploading #{source}, to #{userhost}:#{dest}" } unless source.is_a?(StringIO)
70
+
71
+ cp_conf = @target.transport_config['copy-command'] || ["scp", "-r"]
72
+ cp_cmd = Array(cp_conf)
73
+ cp_cmd += ssh_opts
74
+
75
+ _, err, stat = if source.is_a?(StringIO)
76
+ Tempfile.create(File.basename(dest)) do |f|
77
+ f.write(source.read)
78
+ f.close
79
+ cp_cmd << f.path
80
+ cp_cmd << "#{userhost}:#{Shellwords.escape(dest)}"
81
+ Open3.capture3(*cp_cmd)
82
+ end
83
+ else
84
+ cp_cmd << source
85
+ cp_cmd << "#{userhost}:#{Shellwords.escape(dest)}"
86
+ Open3.capture3(*cp_cmd)
87
+ end
88
+
89
+ if stat.success?
90
+ @logger.debug "Successfully uploaded #{source} to #{dest}"
91
+ else
92
+ message = "Could not copy file to #{dest}: #{err}"
93
+ raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
94
+ end
95
+ end
96
+
97
+ def execute(command)
98
+ cmd_array = build_ssh_command(command)
99
+ Open3.popen3(*cmd_array)
100
+ end
101
+
102
+ # This is used by the Bash shell to decide whether to `cd` before
103
+ # executing commands as a run-as user
104
+ def reset_cwd?
105
+ true
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.9.0'
4
+ VERSION = '2.10.0'
5
5
  end
@@ -51,50 +51,13 @@ module BoltServer
51
51
  basemodulepath = plan_executor_config['basemodulepath'] || "#{codedir}/modules:/opt/puppetlabs/puppet/modules"
52
52
 
53
53
  with_pe_pal_init_settings(codedir, environmentpath, basemodulepath) do
54
- modulepath_dirs = []
55
- modulepath_setting_from_bolt = nil
56
54
  environment = Puppet.lookup(:environments).get!(environment_name)
57
- path_to_env = environment.configuration.path_to_env
58
-
59
- # In the instance where the environment is "production" but no production dir
60
- # exists, the lookup will succeed, but the configuration will be mostly empty.
61
- # For other environments the lookup will fail, but for production we don't
62
- # want cryptic messages sent to the user about combining `nil` with a string.
63
- # Thus if we do get here and `path_to_env` is empty, just assume it's the
64
- # default production environment and continue.
65
- #
66
- # This should hopefully match puppet's behavior for the default 'production'
67
- # environment: _technically_ that environment always exists, but if the dir
68
- # isn't there it won't find the module and fail with "plan not found" rather
69
- # than "environment doesn't exist"
70
- if path_to_env
71
- bolt_yaml = File.join(environment.configuration.path_to_env, 'bolt.yaml')
72
- modulepath_setting_from_bolt = Bolt::Util.read_optional_yaml_hash(bolt_yaml, 'config')['modulepath']
73
- end
74
-
75
- # If we loaded a bolt.yaml in the environment root and it contained a modulepath setting:
76
- # we will use that modulepath rather than the one loaded through puppet. modulepath will
77
- # be the _only_ setting that will work from bolt.yaml in plans in PE.
78
- if modulepath_setting_from_bolt
79
- modulepath_setting_from_bolt.split(File::PATH_SEPARATOR).each do |path|
80
- if Pathname.new(path).absolute? && File.exist?(path)
81
- modulepath_dirs << path
82
- elsif File.exist?(File.join(path_to_env, path))
83
- modulepath_dirs << File.join(path_to_env, path)
84
- end
85
- end
86
-
87
- # Append the basemodulepath to include "built-in" modules.
88
- modulepath_dirs.concat(basemodulepath.split(File::PATH_SEPARATOR))
89
- else
90
- modulepath_dirs = environment.modulepath
91
- end
92
-
93
55
  # A new modulepath is created from scratch (rather than using super's @modulepath)
94
56
  # so that we can have full control over all the entries in modulepath. In the future
95
57
  # it's likely we will need to preceed _both_ Bolt::PAL::BOLTLIB_PATH _and_
96
58
  # Bolt::PAL::MODULES_PATH which would be more complex if we tried to use @modulepath since
97
59
  # we need to append our modulepaths and exclude modules shiped in bolt gem code
60
+ modulepath_dirs = environment.modulepath
98
61
  @original_modulepath = modulepath_dirs
99
62
  @modulepath = [PE_BOLTLIB_PATH, Bolt::PAL::BOLTLIB_PATH, *modulepath_dirs]
100
63
  end
@@ -145,10 +145,7 @@ module BoltSpec
145
145
  end
146
146
 
147
147
  def plugins
148
- @plugins ||= Bolt::Plugin.setup(config,
149
- pal,
150
- nil,
151
- Bolt::Analytics::NoopClient.new)
148
+ @plugins ||= Bolt::Plugin.setup(config, pal)
152
149
  end
153
150
 
154
151
  def pal
@@ -230,6 +230,7 @@ module BoltSpec
230
230
  def transport(_protocol)
231
231
  Class.new do
232
232
  attr_reader :provided_features
233
+
233
234
  def initialize(features)
234
235
  @provided_features = features
235
236
  end
@@ -152,14 +152,11 @@ module BoltSpec
152
152
  end
153
153
 
154
154
  def plugins
155
- @plugins ||= Bolt::Plugin.setup(config, pal, puppetdb_client, @analytics)
155
+ @plugins ||= Bolt::Plugin.setup(config, pal)
156
156
  end
157
157
 
158
158
  def puppetdb_client
159
- @puppetdb_client ||= begin
160
- puppetdb_config = Bolt::PuppetDB::Config.load_config(nil, config.puppetdb)
161
- Bolt::PuppetDB::Client.new(puppetdb_config)
162
- end
159
+ plugins.puppetdb_client
163
160
  end
164
161
 
165
162
  def pal
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.0
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-11 00:00:00.000000000 Z
11
+ date: 2020-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -390,6 +390,7 @@ extra_rdoc_files: []
390
390
  files:
391
391
  - Puppetfile
392
392
  - bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb
393
+ - bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb
393
394
  - bolt-modules/boltlib/lib/puppet/datatypes/result.rb
394
395
  - bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb
395
396
  - bolt-modules/boltlib/lib/puppet/datatypes/target.rb
@@ -413,6 +414,7 @@ files:
413
414
  - bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb
414
415
  - bolt-modules/boltlib/lib/puppet/functions/set_config.rb
415
416
  - bolt-modules/boltlib/lib/puppet/functions/set_feature.rb
417
+ - bolt-modules/boltlib/lib/puppet/functions/set_resources.rb
416
418
  - bolt-modules/boltlib/lib/puppet/functions/set_var.rb
417
419
  - bolt-modules/boltlib/lib/puppet/functions/upload_file.rb
418
420
  - bolt-modules/boltlib/lib/puppet/functions/vars.rb
@@ -493,6 +495,7 @@ files:
493
495
  - lib/bolt/puppetdb/config.rb
494
496
  - lib/bolt/r10k_log_proxy.rb
495
497
  - lib/bolt/rerun.rb
498
+ - lib/bolt/resource_instance.rb
496
499
  - lib/bolt/result.rb
497
500
  - lib/bolt/result_set.rb
498
501
  - lib/bolt/secret.rb
@@ -516,6 +519,7 @@ files:
516
519
  - lib/bolt/transport/simple.rb
517
520
  - lib/bolt/transport/ssh.rb
518
521
  - lib/bolt/transport/ssh/connection.rb
522
+ - lib/bolt/transport/ssh/exec_connection.rb
519
523
  - lib/bolt/transport/winrm.rb
520
524
  - lib/bolt/transport/winrm/connection.rb
521
525
  - lib/bolt/util.rb