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.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +27 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +122 -0
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +3 -1
- data/bolt-modules/file/lib/puppet/functions/file/join.rb +1 -1
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -1
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
- data/bolt-modules/file/lib/puppet/functions/file/write.rb +3 -1
- data/lib/bolt/applicator.rb +1 -1
- data/lib/bolt/apply_target.rb +3 -2
- data/lib/bolt/bolt_option_parser.rb +13 -4
- data/lib/bolt/cli.rb +4 -5
- data/lib/bolt/config.rb +2 -2
- data/lib/bolt/config/transport/ssh.rb +47 -1
- data/lib/bolt/inventory.rb +2 -1
- data/lib/bolt/inventory/inventory.rb +5 -0
- data/lib/bolt/inventory/target.rb +17 -1
- data/lib/bolt/node/output.rb +1 -1
- data/lib/bolt/pal/yaml_plan.rb +1 -0
- data/lib/bolt/plugin.rb +13 -7
- data/lib/bolt/plugin/puppetdb.rb +5 -2
- data/lib/bolt/project.rb +13 -6
- data/lib/bolt/puppetdb/config.rb +14 -26
- data/lib/bolt/resource_instance.rb +126 -0
- data/lib/bolt/target.rb +12 -1
- data/lib/bolt/transport/ssh.rb +6 -2
- data/lib/bolt/transport/ssh/exec_connection.rb +110 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/pe/pal.rb +1 -38
- data/lib/bolt_spec/bolt_context.rb +1 -4
- data/lib/bolt_spec/plans/mock_executor.rb +1 -0
- data/lib/bolt_spec/run.rb +2 -5
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f0e265eb792ba38e627651fad94478a4acbcbf7576912b95f6eaa0b234e6957
|
4
|
+
data.tar.gz: ad28f917ac186069e61300142dad31373b4a09dd484a3d131962d383b803d43a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
@@ -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,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.
|
data/lib/bolt/applicator.rb
CHANGED
data/lib/bolt/apply_target.rb
CHANGED
@@ -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', '
|
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
|
data/lib/bolt/cli.rb
CHANGED
@@ -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
|
-
|
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,
|
265
|
+
@plugins ||= Bolt::Plugin.setup(config, pal, analytics)
|
267
266
|
end
|
268
267
|
|
269
268
|
def query_puppetdb_nodes(query)
|
data/lib/bolt/config.rb
CHANGED
@@ -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" => {
|
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
|
data/lib/bolt/inventory.rb
CHANGED
@@ -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
|
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
|
data/lib/bolt/node/output.rb
CHANGED
data/lib/bolt/pal/yaml_plan.rb
CHANGED
data/lib/bolt/plugin.rb
CHANGED
@@ -119,13 +119,9 @@ module Bolt
|
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
|
-
def self.setup(config, pal,
|
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,
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
@@ -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
|
-
|
18
|
-
|
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
|
|
data/lib/bolt/project.rb
CHANGED
@@ -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
|
data/lib/bolt/puppetdb/config.rb
CHANGED
@@ -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(
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
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] =
|
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
|
data/lib/bolt/target.rb
CHANGED
@@ -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,
|
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -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 =
|
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
|
data/lib/bolt/version.rb
CHANGED
data/lib/bolt_server/pe/pal.rb
CHANGED
@@ -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
|
data/lib/bolt_spec/run.rb
CHANGED
@@ -152,14 +152,11 @@ module BoltSpec
|
|
152
152
|
end
|
153
153
|
|
154
154
|
def plugins
|
155
|
-
@plugins ||= Bolt::Plugin.setup(config, pal
|
155
|
+
@plugins ||= Bolt::Plugin.setup(config, pal)
|
156
156
|
end
|
157
157
|
|
158
158
|
def puppetdb_client
|
159
|
-
|
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.
|
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
|
+
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
|