bolt 0.20.5 → 0.20.6
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/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +1 -1
- data/lib/bolt/analytics.rb +160 -0
- data/lib/bolt/cli.rb +14 -4
- data/lib/bolt/executor.rb +15 -2
- data/lib/bolt/pal.rb +3 -1
- data/lib/bolt/puppetdb/config.rb +9 -4
- data/lib/bolt/transport/local.rb +3 -2
- data/lib/bolt/transport/orch.rb +1 -1
- data/lib/bolt/transport/orch/connection.rb +1 -0
- data/lib/bolt/transport/ssh.rb +7 -2
- data/lib/bolt/transport/ssh/connection.rb +39 -19
- data/lib/bolt/transport/winrm.rb +1 -0
- data/lib/bolt/transport/winrm/connection.rb +3 -0
- data/lib/bolt/util.rb +5 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_ext/puppetdb_inventory.rb +2 -1
- data/vendored/puppet/lib/puppet/defaults.rb +15 -1
- data/vendored/puppet/lib/puppet/pops/loader/static_loader.rb +0 -14
- data/vendored/puppet/lib/puppet/pops/pcore.rb +1 -1
- data/vendored/puppet/lib/puppet/type/file/mode.rb +1 -1
- data/vendored/require_vendored.rb +0 -2
- metadata +3 -21
- data/vendored/puppet/lib/puppet/external/nagios.rb +0 -46
- data/vendored/puppet/lib/puppet/external/nagios/base.rb +0 -472
- data/vendored/puppet/lib/puppet/external/nagios/parser.rb +0 -400
- data/vendored/puppet/lib/puppet/provider/naginator.rb +0 -63
- data/vendored/puppet/lib/puppet/type/nagios_command.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_contact.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_contactgroup.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_host.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_hostdependency.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_hostescalation.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_hostextinfo.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_hostgroup.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_service.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_servicedependency.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_serviceescalation.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_serviceextinfo.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_servicegroup.rb +0 -3
- data/vendored/puppet/lib/puppet/type/nagios_timeperiod.rb +0 -3
- data/vendored/puppet/lib/puppet/util/nagios_maker.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecd8364f409304d8286adae0544a3f2b34038403
|
4
|
+
data.tar.gz: e13c9abe1b098794394947baec4da9d349a922fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d98430251616d064eb98830532ce8c7de35c4f60b7b55768b9d82a9fbbb9c183363888da3e98d2d915df9637ff488893a495f6a5fe0a6254ed329ecfa2077908
|
7
|
+
data.tar.gz: 99827f1b25b5b83849c9900551168f3377b2f3b99da600c32f9284173247988c6fbf550ac737f81875d32ea78cf2885d2b9395879e314f6d64d4eb53ae2ff835
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/version'
|
4
|
+
require 'httpclient'
|
5
|
+
require 'json'
|
6
|
+
require 'locale'
|
7
|
+
require 'logging'
|
8
|
+
require 'securerandom'
|
9
|
+
|
10
|
+
module Bolt
|
11
|
+
module Analytics
|
12
|
+
PROTOCOL_VERSION = 1
|
13
|
+
APPLICATION_NAME = 'bolt'
|
14
|
+
TRACKING_ID = 'UA-120367942-1'
|
15
|
+
TRACKING_URL = 'https://google-analytics.com/collect'
|
16
|
+
|
17
|
+
def self.build_client
|
18
|
+
logger = Logging.logger[self]
|
19
|
+
|
20
|
+
config_file = File.expand_path('~/.puppetlabs/bolt/analytics.yaml')
|
21
|
+
config = load_config(config_file)
|
22
|
+
|
23
|
+
if config['disabled'] || ENV['BOLT_DISABLE_ANALYTICS']
|
24
|
+
logger.debug "Analytics opt-out is set, analytics will be disabled"
|
25
|
+
NoopClient.new
|
26
|
+
else
|
27
|
+
unless config.key?('user-id')
|
28
|
+
config['user-id'] = SecureRandom.uuid
|
29
|
+
write_config(config_file, config)
|
30
|
+
end
|
31
|
+
|
32
|
+
Client.new(config['user-id'])
|
33
|
+
end
|
34
|
+
rescue StandardError => e
|
35
|
+
logger.debug "Failed to initialize analytics client, analytics will be disabled: #{e}"
|
36
|
+
NoopClient.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.load_config(filename)
|
40
|
+
if File.exist?(filename)
|
41
|
+
YAML.load_file(filename)
|
42
|
+
else
|
43
|
+
{}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.write_config(filename, config)
|
48
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
49
|
+
File.write(filename, config.to_yaml)
|
50
|
+
end
|
51
|
+
|
52
|
+
class Client
|
53
|
+
attr_reader :user_id
|
54
|
+
|
55
|
+
def initialize(user_id)
|
56
|
+
@logger = Logging.logger[self]
|
57
|
+
@http = HTTPClient.new
|
58
|
+
@user_id = user_id
|
59
|
+
@executor = Concurrent.global_io_executor
|
60
|
+
@os = compute_os
|
61
|
+
end
|
62
|
+
|
63
|
+
def screen_view(screen)
|
64
|
+
screen_view_params = {
|
65
|
+
# Type
|
66
|
+
t: 'screenview',
|
67
|
+
# Screen Name
|
68
|
+
cd: screen
|
69
|
+
}
|
70
|
+
|
71
|
+
submit(base_params.merge(screen_view_params))
|
72
|
+
end
|
73
|
+
|
74
|
+
def event(category, action, label = nil, value = nil)
|
75
|
+
event_params = {
|
76
|
+
# Type
|
77
|
+
t: 'event',
|
78
|
+
# Event Category
|
79
|
+
ec: category,
|
80
|
+
# Event Action
|
81
|
+
ea: action
|
82
|
+
}
|
83
|
+
|
84
|
+
# Event Label
|
85
|
+
event_params[:el] = label if label
|
86
|
+
# Event Value
|
87
|
+
event_params[:ev] = value if value
|
88
|
+
|
89
|
+
submit(base_params.merge(event_params))
|
90
|
+
end
|
91
|
+
|
92
|
+
def submit(params)
|
93
|
+
# Handle analytics submission in the background to avoid blocking the
|
94
|
+
# app or polluting the log with errors
|
95
|
+
Concurrent::Future.execute(executor: @executor) do
|
96
|
+
@logger.debug "Submitting analytics: #{JSON.pretty_generate(params)}"
|
97
|
+
@http.post(TRACKING_URL, params)
|
98
|
+
@logger.debug "Completed analytics submission"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# These parameters have terrible names. See this page for complete documentation:
|
103
|
+
# https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
|
104
|
+
def base_params
|
105
|
+
{
|
106
|
+
v: PROTOCOL_VERSION,
|
107
|
+
# Client ID
|
108
|
+
cid: @user_id,
|
109
|
+
# Tracking ID
|
110
|
+
tid: TRACKING_ID,
|
111
|
+
# Application Name
|
112
|
+
an: APPLICATION_NAME,
|
113
|
+
# Application Version
|
114
|
+
av: Bolt::VERSION,
|
115
|
+
# Anonymize IPs
|
116
|
+
aip: true,
|
117
|
+
# User locale
|
118
|
+
ul: Locale.current.to_rfc,
|
119
|
+
# Custom Dimension 1 (Operating System)
|
120
|
+
cd1: @os.value
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def compute_os
|
125
|
+
Concurrent::Future.execute(executor: @executor) do
|
126
|
+
require_relative '../../vendored/require_vendored'
|
127
|
+
require 'facter'
|
128
|
+
os = Facter.value('os')
|
129
|
+
"#{os['name']} #{os.dig('release', 'major')}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# If the user is running a very fast command, there may not be time for
|
134
|
+
# analytics submission to complete before the command is finished. In
|
135
|
+
# that case, we give a little buffer for any stragglers to finish up.
|
136
|
+
# 250ms strikes a balance between accomodating slower networks while not
|
137
|
+
# introducing a noticeable "hang".
|
138
|
+
def finish
|
139
|
+
@executor.shutdown
|
140
|
+
@executor.wait_for_termination(0.25)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class NoopClient
|
145
|
+
def initialize
|
146
|
+
@logger = Logging.logger[self]
|
147
|
+
end
|
148
|
+
|
149
|
+
def screen_view(screen)
|
150
|
+
@logger.debug "Skipping submission of '#{screen}' screenview because analytics is disabled"
|
151
|
+
end
|
152
|
+
|
153
|
+
def event(category, action, _label = nil, _value = nil)
|
154
|
+
@logger.debug "Skipping submission of '#{category} #{action}' event because analytics is disabled"
|
155
|
+
end
|
156
|
+
|
157
|
+
def finish; end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/bolt/cli.rb
CHANGED
@@ -6,6 +6,7 @@ require 'json'
|
|
6
6
|
require 'io/console'
|
7
7
|
require 'logging'
|
8
8
|
require 'optparse'
|
9
|
+
require 'bolt/analytics'
|
9
10
|
require 'bolt/config'
|
10
11
|
require 'bolt/error'
|
11
12
|
require 'bolt/executor'
|
@@ -493,6 +494,15 @@ Available options are:
|
|
493
494
|
exit!
|
494
495
|
end
|
495
496
|
|
497
|
+
@analytics = Bolt::Analytics.build_client
|
498
|
+
|
499
|
+
screen = "#{options[:mode]}_#{options[:action]}"
|
500
|
+
# submit a different screen for `bolt task show` and `bolt task show foo`
|
501
|
+
if options[:action] == 'show' && options[:object]
|
502
|
+
screen += '_object'
|
503
|
+
end
|
504
|
+
@analytics.screen_view(screen)
|
505
|
+
|
496
506
|
if options[:mode] == 'plan' || options[:mode] == 'task'
|
497
507
|
pal = Bolt::PAL.new(config)
|
498
508
|
end
|
@@ -540,17 +550,16 @@ Available options are:
|
|
540
550
|
params: params }
|
541
551
|
plan_context[:description] = options[:description] if options[:description]
|
542
552
|
|
543
|
-
executor = Bolt::Executor.new(config, options[:noop])
|
553
|
+
executor = Bolt::Executor.new(config, @analytics, options[:noop])
|
544
554
|
executor.start_plan(plan_context)
|
545
555
|
result = pal.run_plan(options[:object], options[:task_options], executor, inventory, puppetdb_client)
|
546
556
|
|
547
557
|
# If a non-bolt exeception bubbles up the plan won't get finished
|
548
|
-
|
549
|
-
# executor.finish_plan(result)
|
558
|
+
executor.finish_plan(result)
|
550
559
|
outputter.print_plan_result(result)
|
551
560
|
code = result.ok? ? 0 : 1
|
552
561
|
else
|
553
|
-
executor = Bolt::Executor.new(config, options[:noop])
|
562
|
+
executor = Bolt::Executor.new(config, @analytics, options[:noop])
|
554
563
|
targets = options[:targets]
|
555
564
|
|
556
565
|
results = nil
|
@@ -606,6 +615,7 @@ Available options are:
|
|
606
615
|
ensure
|
607
616
|
# restore original signal handler
|
608
617
|
Signal.trap :INT, handler if handler
|
618
|
+
@analytics&.finish
|
609
619
|
end
|
610
620
|
|
611
621
|
def validate_file(type, path)
|
data/lib/bolt/executor.rb
CHANGED
@@ -5,6 +5,8 @@ require 'English'
|
|
5
5
|
require 'json'
|
6
6
|
require 'concurrent'
|
7
7
|
require 'logging'
|
8
|
+
require 'set'
|
9
|
+
require 'bolt/analytics'
|
8
10
|
require 'bolt/result'
|
9
11
|
require 'bolt/config'
|
10
12
|
require 'bolt/notifier'
|
@@ -16,14 +18,18 @@ module Bolt
|
|
16
18
|
attr_reader :noop, :transports
|
17
19
|
attr_accessor :run_as, :plan_logging
|
18
20
|
|
19
|
-
def initialize(config = Bolt::Config.new, noop = nil)
|
21
|
+
def initialize(config = Bolt::Config.new, analytics = Bolt::Analytics::NoopClient.new, noop = nil)
|
20
22
|
@config = config
|
23
|
+
@analytics = analytics
|
21
24
|
@logger = Logging.logger[self]
|
22
25
|
@plan_logging = false
|
23
26
|
|
24
27
|
@transports = Bolt::TRANSPORTS.each_with_object({}) do |(key, val), coll|
|
25
|
-
coll[key.to_s] = Concurrent::Delay.new
|
28
|
+
coll[key.to_s] = Concurrent::Delay.new do
|
29
|
+
val.new
|
30
|
+
end
|
26
31
|
end
|
32
|
+
@reported_transports = Set.new
|
27
33
|
|
28
34
|
@noop = noop
|
29
35
|
@run_as = nil
|
@@ -50,6 +56,7 @@ module Bolt
|
|
50
56
|
def batch_execute(targets)
|
51
57
|
promises = targets.group_by(&:protocol).flat_map do |protocol, protocol_targets|
|
52
58
|
transport = transport(protocol)
|
59
|
+
report_transport(transport, protocol_targets.count)
|
53
60
|
transport.batches(protocol_targets).flat_map do |batch|
|
54
61
|
batch_promises = Array(batch).each_with_object({}) do |target, h|
|
55
62
|
h[target] = Concurrent::Promise.new(executor: :immediate)
|
@@ -110,6 +117,12 @@ module Bolt
|
|
110
117
|
end
|
111
118
|
private :log_action
|
112
119
|
|
120
|
+
def report_transport(transport, count)
|
121
|
+
name = transport.class.name.split('::').last.downcase
|
122
|
+
@analytics&.event('Transport', 'initialize', name, count) unless @reported_transports.include?(name)
|
123
|
+
@reported_transports.add(name)
|
124
|
+
end
|
125
|
+
|
113
126
|
def with_node_logging(description, batch)
|
114
127
|
@logger.info("#{description} on #{batch.map(&:uri)}")
|
115
128
|
result = yield
|
data/lib/bolt/pal.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'bolt/executor'
|
4
4
|
require 'bolt/error'
|
5
5
|
require 'bolt/plan_result'
|
6
|
+
require 'bolt/util'
|
6
7
|
|
7
8
|
module Bolt
|
8
9
|
class PAL
|
@@ -54,7 +55,7 @@ module Bolt
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def self.load_puppet
|
57
|
-
if
|
58
|
+
if Bolt::Util.windows?
|
58
59
|
# Windows 'fix' for openssl behaving strangely. Prevents very slow operation
|
59
60
|
# of random_bytes later when establishing winrm connections from a Windows host.
|
60
61
|
# See https://github.com/rails/rails/issues/25805 for background.
|
@@ -64,6 +65,7 @@ module Bolt
|
|
64
65
|
|
65
66
|
begin
|
66
67
|
require_relative '../../vendored/require_vendored'
|
68
|
+
require 'puppet_pal'
|
67
69
|
rescue LoadError
|
68
70
|
raise Bolt::Error.new("Puppet must be installed to execute tasks", "bolt/puppet-missing")
|
69
71
|
end
|
data/lib/bolt/puppetdb/config.rb
CHANGED
@@ -1,30 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'bolt/util'
|
4
5
|
|
5
6
|
module Bolt
|
6
7
|
module PuppetDB
|
7
8
|
class Config
|
8
9
|
DEFAULT_TOKEN = File.expand_path('~/.puppetlabs/token')
|
9
|
-
DEFAULT_CONFIG = File.expand_path('~/.puppetlabs/client-tools/puppetdb.conf')
|
10
|
+
DEFAULT_CONFIG = { user: File.expand_path('~/.puppetlabs/client-tools/puppetdb.conf'),
|
11
|
+
global: '/etc/puppetlabs/client-tools/puppetdb.conf',
|
12
|
+
win_global: 'C:/ProgramData/PuppetLabs/client-tools/puppetdb.conf' }.freeze
|
10
13
|
|
11
14
|
def initialize(config_file, options)
|
12
15
|
@settings = load_config(config_file)
|
13
16
|
@settings.merge!(options)
|
14
|
-
|
15
17
|
expand_paths
|
16
18
|
validate
|
17
19
|
end
|
18
20
|
|
19
21
|
def load_config(filename)
|
22
|
+
global_path = Bolt::Util.windows? ? DEFAULT_CONFIG[:win_global] : DEFAULT_CONFIG[:global]
|
20
23
|
if filename
|
21
24
|
if File.exist?(filename)
|
22
25
|
config = JSON.parse(File.read(filename))
|
23
26
|
else
|
24
27
|
raise Bolt::PuppetDBError, "config file #{filename} does not exist"
|
25
28
|
end
|
26
|
-
elsif File.exist?(DEFAULT_CONFIG)
|
27
|
-
config = JSON.parse(File.read(DEFAULT_CONFIG))
|
29
|
+
elsif File.exist?(DEFAULT_CONFIG[:user])
|
30
|
+
config = JSON.parse(File.read(DEFAULT_CONFIG[:user]))
|
31
|
+
elsif File.exist?(global_path)
|
32
|
+
config = JSON.parse(File.read(global_path))
|
28
33
|
else
|
29
34
|
config = {}
|
30
35
|
end
|
data/lib/bolt/transport/local.rb
CHANGED
@@ -5,6 +5,7 @@ require 'fileutils'
|
|
5
5
|
require 'tmpdir'
|
6
6
|
require 'bolt/transport/base'
|
7
7
|
require 'bolt/result'
|
8
|
+
require 'bolt/util'
|
8
9
|
|
9
10
|
module Bolt
|
10
11
|
module Transport
|
@@ -20,7 +21,7 @@ module Bolt
|
|
20
21
|
def initialize
|
21
22
|
super
|
22
23
|
|
23
|
-
if
|
24
|
+
if Bolt::Util.windows?
|
24
25
|
raise NotImplementedError, "The local transport is not yet implemented on Windows"
|
25
26
|
else
|
26
27
|
@conn = Shell.new
|
@@ -83,7 +84,7 @@ module Bolt
|
|
83
84
|
executable = target.select_impl(task, PROVIDED_FEATURES)
|
84
85
|
raise "No suitable implementation of #{task.name} for #{target.name}" unless executable
|
85
86
|
|
86
|
-
input_method = task.input_method
|
87
|
+
input_method = task.input_method || "both"
|
87
88
|
stdin = STDIN_METHODS.include?(input_method) ? JSON.dump(arguments) : nil
|
88
89
|
env = ENVIRONMENT_METHODS.include?(input_method) ? arguments : nil
|
89
90
|
|
data/lib/bolt/transport/orch.rb
CHANGED
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -9,7 +9,7 @@ module Bolt
|
|
9
9
|
module Transport
|
10
10
|
class SSH < Base
|
11
11
|
def self.options
|
12
|
-
%w[port user password sudo-password private-key host-key-check connect-timeout tmpdir run-as tty]
|
12
|
+
%w[port user password sudo-password private-key host-key-check connect-timeout tmpdir run-as tty run-as-command]
|
13
13
|
end
|
14
14
|
|
15
15
|
PROVIDED_FEATURES = ['shell'].freeze
|
@@ -39,6 +39,11 @@ module Bolt
|
|
39
39
|
error_msg = "connect-timeout value must be an Integer, received #{timeout_value}:#{timeout_value.class}"
|
40
40
|
raise Bolt::ValidationError, error_msg
|
41
41
|
end
|
42
|
+
|
43
|
+
run_as_cmd = options['run-as-command']
|
44
|
+
if run_as_cmd && (!run_as_cmd.is_a?(Array) || run_as_cmd.any? { |n| !n.is_a?(String) })
|
45
|
+
raise Bolt::ValidationError, "run-as-command must be an Array of Strings, received #{run_as_cmd}"
|
46
|
+
end
|
42
47
|
end
|
43
48
|
|
44
49
|
def initialize
|
@@ -113,7 +118,7 @@ module Bolt
|
|
113
118
|
executable = target.select_impl(task, PROVIDED_FEATURES)
|
114
119
|
raise "No suitable implementation of #{task.name} for #{target.name}" unless executable
|
115
120
|
|
116
|
-
input_method = task.input_method
|
121
|
+
input_method = task.input_method || "both"
|
117
122
|
with_connection(target) do |conn|
|
118
123
|
conn.running_as(options['_run_as']) do
|
119
124
|
stdin, output = nil
|
@@ -4,6 +4,7 @@ require 'logging'
|
|
4
4
|
require 'shellwords'
|
5
5
|
require 'bolt/node/errors'
|
6
6
|
require 'bolt/node/output'
|
7
|
+
require 'bolt/util'
|
7
8
|
|
8
9
|
module Bolt
|
9
10
|
module Transport
|
@@ -24,12 +25,22 @@ module Bolt
|
|
24
25
|
def chown(owner)
|
25
26
|
return if owner.nil? || owner == @owner
|
26
27
|
|
27
|
-
|
28
|
-
result
|
28
|
+
result = @node.execute(['id', '-g', owner])
|
29
|
+
if result.exit_code != 0
|
30
|
+
message = "Could not identify group of user #{owner}: #{result.stderr.string}"
|
31
|
+
raise Bolt::Node::FileError.new(message, 'ID_ERROR')
|
32
|
+
end
|
33
|
+
group = result.stdout.string.chomp
|
34
|
+
|
35
|
+
# Chown can only be run by root.
|
36
|
+
result = @node.execute(['chown', '-R', "#{owner}:#{group}", @path], sudoable: true, run_as: 'root')
|
29
37
|
if result.exit_code != 0
|
30
|
-
message = "Could not change owner of '#{@path}' to #{
|
38
|
+
message = "Could not change owner of '#{@path}' to #{owner}: #{result.stderr.string}"
|
31
39
|
raise Bolt::Node::FileError.new(message, 'CHOWN_ERROR')
|
32
40
|
end
|
41
|
+
|
42
|
+
# File ownership successfully changed, record the new owner.
|
43
|
+
@owner = owner
|
33
44
|
end
|
34
45
|
|
35
46
|
def delete
|
@@ -52,7 +63,7 @@ module Bolt
|
|
52
63
|
@logger = Logging.logger[@target.host]
|
53
64
|
end
|
54
65
|
|
55
|
-
if
|
66
|
+
if Bolt::Util.windows?
|
56
67
|
require 'ffi'
|
57
68
|
module Win
|
58
69
|
extend FFI::Library
|
@@ -95,7 +106,7 @@ module Bolt
|
|
95
106
|
@logger.debug { "Disabling use_agent in net-ssh: ssh-agent is not available" }
|
96
107
|
options[:use_agent] = false
|
97
108
|
end
|
98
|
-
elsif
|
109
|
+
elsif Bolt::Util.windows?
|
99
110
|
pageant_wide = 'Pageant'.encode('UTF-16LE')
|
100
111
|
if Win.FindWindow(pageant_wide, pageant_wide).to_i == 0
|
101
112
|
@logger.debug { "Disabling use_agent in net-ssh: pageant process not running" }
|
@@ -160,6 +171,8 @@ module Bolt
|
|
160
171
|
channel.wait
|
161
172
|
return true
|
162
173
|
else
|
174
|
+
# Cancel the sudo prompt to prevent later commands getting stuck
|
175
|
+
channel.close
|
163
176
|
raise Bolt::Node::EscalateError.new(
|
164
177
|
"Sudo password for user #{@user} was not provided for #{target.uri}",
|
165
178
|
'NO_PASSWORD'
|
@@ -184,14 +197,20 @@ module Bolt
|
|
184
197
|
def execute(command, sudoable: false, **options)
|
185
198
|
result_output = Bolt::Node::Output.new
|
186
199
|
run_as = options[:run_as] || self.run_as
|
187
|
-
|
200
|
+
escalate = sudoable && run_as && @user != run_as
|
201
|
+
use_sudo = escalate && @target.options['run-as-command'].nil?
|
188
202
|
|
189
203
|
command_str = command.is_a?(String) ? command : Shellwords.shelljoin(command)
|
190
|
-
if
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
204
|
+
if escalate
|
205
|
+
if use_sudo
|
206
|
+
sudo_flags = ["sudo", "-S", "-u", run_as, "-p", sudo_prompt]
|
207
|
+
sudo_flags += ["-E"] if options[:environment]
|
208
|
+
sudo_str = Shellwords.shelljoin(sudo_flags)
|
209
|
+
command_str = "#{sudo_str} #{command_str}"
|
210
|
+
else
|
211
|
+
run_as_str = Shellwords.shelljoin(@target.options['run-as-command'] + [run_as])
|
212
|
+
command_str = "#{run_as_str} #{command_str}"
|
213
|
+
end
|
195
214
|
end
|
196
215
|
|
197
216
|
# Including the environment declarations in the shelljoin will escape
|
@@ -221,14 +240,14 @@ module Bolt
|
|
221
240
|
unless use_sudo && handled_sudo(channel, data)
|
222
241
|
result_output.stdout << data
|
223
242
|
end
|
224
|
-
@logger.debug { "stdout: #{data}" }
|
243
|
+
@logger.debug { "stdout: #{data.strip}" }
|
225
244
|
end
|
226
245
|
|
227
246
|
channel.on_extended_data do |_, _, data|
|
228
247
|
unless use_sudo && handled_sudo(channel, data)
|
229
248
|
result_output.stderr << data
|
230
249
|
end
|
231
|
-
@logger.debug { "stderr: #{data}" }
|
250
|
+
@logger.debug { "stderr: #{data.strip}" }
|
232
251
|
end
|
233
252
|
|
234
253
|
channel.on_request("exit-status") do |_, data|
|
@@ -249,6 +268,9 @@ module Bolt
|
|
249
268
|
@logger.info { "Command failed with exit code #{result_output.exit_code}" }
|
250
269
|
end
|
251
270
|
result_output
|
271
|
+
rescue StandardError
|
272
|
+
@logger.debug { "Command aborted" }
|
273
|
+
raise
|
252
274
|
end
|
253
275
|
|
254
276
|
def write_remote_file(source, destination)
|
@@ -258,12 +280,10 @@ module Bolt
|
|
258
280
|
end
|
259
281
|
|
260
282
|
def make_tempdir
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
command = ['mktemp', '-d']
|
266
|
-
end
|
283
|
+
tmpdir = target.options.fetch('tmpdir', '/tmp')
|
284
|
+
tmppath = "#{tmpdir}/#{SecureRandom.uuid}"
|
285
|
+
command = ['mkdir', '-m', 700, tmppath]
|
286
|
+
|
267
287
|
result = execute(command)
|
268
288
|
if result.exit_code != 0
|
269
289
|
raise Bolt::Node::FileError.new("Could not make tempdir: #{result.stderr.string}", 'TEMPDIR_ERROR')
|