bolt 2.5.0 → 2.6.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 +2 -1
- data/lib/bolt/executor.rb +1 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +22 -14
- data/lib/bolt/pal/yaml_plan/step.rb +11 -4
- data/lib/bolt/pal/yaml_plan/step/command.rb +2 -2
- data/lib/bolt/pal/yaml_plan/step/resources.rb +3 -3
- data/lib/bolt/pal/yaml_plan/step/script.rb +2 -2
- data/lib/bolt/pal/yaml_plan/step/task.rb +2 -2
- data/lib/bolt/pal/yaml_plan/step/upload.rb +2 -2
- data/lib/bolt/plugin.rb +2 -1
- data/lib/bolt/shell.rb +1 -0
- data/lib/bolt/shell/powershell.rb +263 -0
- data/lib/bolt/shell/powershell/snippets.rb +273 -0
- data/lib/bolt/transport/local/connection.rb +11 -1
- data/lib/bolt/transport/winrm.rb +1 -118
- data/lib/bolt/transport/winrm/connection.rb +27 -78
- data/lib/bolt/version.rb +1 -1
- metadata +18 -4
- data/lib/bolt/transport/local_windows.rb +0 -189
- data/lib/bolt/transport/powershell.rb +0 -337
@@ -0,0 +1,273 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
class Shell
|
5
|
+
class Powershell < Shell
|
6
|
+
module Snippets
|
7
|
+
class << self
|
8
|
+
def execute_process(command)
|
9
|
+
<<~PS
|
10
|
+
if ([Console]::InputEncoding -eq [System.Text.Encoding]::UTF8) {
|
11
|
+
[Console]::InputEncoding = New-Object System.Text.UTF8Encoding $False
|
12
|
+
}
|
13
|
+
if ([Console]::OutputEncoding -eq [System.Text.Encoding]::UTF8) {
|
14
|
+
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding $False
|
15
|
+
}
|
16
|
+
$OutputEncoding = [Console]::OutputEncoding
|
17
|
+
#{command}
|
18
|
+
if (-not $? -and ($LASTEXITCODE -eq $null)) { exit 1 }
|
19
|
+
exit $LASTEXITCODE
|
20
|
+
PS
|
21
|
+
end
|
22
|
+
|
23
|
+
def make_tempdir(parent)
|
24
|
+
<<~PS
|
25
|
+
$parent = #{parent}
|
26
|
+
$name = [System.IO.Path]::GetRandomFileName()
|
27
|
+
$path = Join-Path $parent $name -ErrorAction Stop
|
28
|
+
New-Item -ItemType Directory -Path $path -ErrorAction Stop | Out-Null
|
29
|
+
$path
|
30
|
+
PS
|
31
|
+
end
|
32
|
+
|
33
|
+
def rmdir(dir)
|
34
|
+
<<~PS
|
35
|
+
Remove-Item -Force -Recurse -Path "#{dir}"
|
36
|
+
PS
|
37
|
+
end
|
38
|
+
|
39
|
+
def run_script(arguments, script_path)
|
40
|
+
build_arg_list = arguments.map do |a|
|
41
|
+
"$invokeArgs.ArgumentList += @'\n#{a}\n'@"
|
42
|
+
end.join("\n")
|
43
|
+
<<~PS
|
44
|
+
$invokeArgs = @{
|
45
|
+
ScriptBlock = (Get-Command "#{script_path}").ScriptBlock
|
46
|
+
ArgumentList = @()
|
47
|
+
}
|
48
|
+
#{build_arg_list}
|
49
|
+
|
50
|
+
try
|
51
|
+
{
|
52
|
+
Invoke-Command @invokeArgs
|
53
|
+
}
|
54
|
+
catch
|
55
|
+
{
|
56
|
+
Write-Error $_.Exception
|
57
|
+
exit 1
|
58
|
+
}
|
59
|
+
PS
|
60
|
+
end
|
61
|
+
|
62
|
+
def ps_task(path, arguments)
|
63
|
+
<<~PS
|
64
|
+
$private:tempArgs = Get-ContentAsJson (
|
65
|
+
$utf8.GetString([System.Convert]::FromBase64String('#{Base64.encode64(JSON.dump(arguments))}'))
|
66
|
+
)
|
67
|
+
$allowedArgs = (Get-Command "#{path}").Parameters.Keys
|
68
|
+
$private:taskArgs = @{}
|
69
|
+
$private:tempArgs.Keys | ? { $allowedArgs -contains $_ } | % { $private:taskArgs[$_] = $private:tempArgs[$_] }
|
70
|
+
try { & "#{path}" @taskArgs } catch { Write-Error $_.Exception; exit 1 }
|
71
|
+
PS
|
72
|
+
end
|
73
|
+
|
74
|
+
def try_catch(command)
|
75
|
+
%(try { & "#{command}" } catch { Write-Error $_.Exception; exit 1 })
|
76
|
+
end
|
77
|
+
|
78
|
+
def shell_init
|
79
|
+
<<~PS
|
80
|
+
$ENV:PATH += ";${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\bin\\;" +
|
81
|
+
"${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\puppet\\bin;" +
|
82
|
+
"${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\sys\\ruby\\bin\\"
|
83
|
+
$ENV:RUBYLIB = "${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\puppet\\lib;" +
|
84
|
+
"${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\facter\\lib;" +
|
85
|
+
"${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\hiera\\lib;" +
|
86
|
+
$ENV:RUBYLIB
|
87
|
+
|
88
|
+
Add-Type -AssemblyName System.ServiceModel.Web, System.Runtime.Serialization
|
89
|
+
$utf8 = [System.Text.Encoding]::UTF8
|
90
|
+
|
91
|
+
function Write-Stream {
|
92
|
+
PARAM(
|
93
|
+
[Parameter(Position=0)] $stream,
|
94
|
+
[Parameter(ValueFromPipeline=$true)] $string
|
95
|
+
)
|
96
|
+
PROCESS {
|
97
|
+
$bytes = $utf8.GetBytes($string)
|
98
|
+
$stream.Write( $bytes, 0, $bytes.Length )
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
function Convert-JsonToXml {
|
103
|
+
PARAM([Parameter(ValueFromPipeline=$true)] [string[]] $json)
|
104
|
+
BEGIN {
|
105
|
+
$mStream = New-Object System.IO.MemoryStream
|
106
|
+
}
|
107
|
+
PROCESS {
|
108
|
+
$json | Write-Stream -Stream $mStream
|
109
|
+
}
|
110
|
+
END {
|
111
|
+
$mStream.Position = 0
|
112
|
+
try {
|
113
|
+
$jsonReader = [System.Runtime.Serialization.Json.JsonReaderWriterFactory]::CreateJsonReader($mStream,[System.Xml.XmlDictionaryReaderQuotas]::Max)
|
114
|
+
$xml = New-Object Xml.XmlDocument
|
115
|
+
$xml.Load($jsonReader)
|
116
|
+
$xml
|
117
|
+
} finally {
|
118
|
+
$jsonReader.Close()
|
119
|
+
$mStream.Dispose()
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
Function ConvertFrom-Xml {
|
125
|
+
[CmdletBinding(DefaultParameterSetName="AutoType")]
|
126
|
+
PARAM(
|
127
|
+
[Parameter(ValueFromPipeline=$true,Mandatory=$true,Position=1)] [Xml.XmlNode] $xml,
|
128
|
+
[Parameter(Mandatory=$true,ParameterSetName="ManualType")] [Type] $Type,
|
129
|
+
[Switch] $ForceType
|
130
|
+
)
|
131
|
+
PROCESS{
|
132
|
+
if (Get-Member -InputObject $xml -Name root) {
|
133
|
+
return $xml.root.Objects | ConvertFrom-Xml
|
134
|
+
} elseif (Get-Member -InputObject $xml -Name Objects) {
|
135
|
+
return $xml.Objects | ConvertFrom-Xml
|
136
|
+
}
|
137
|
+
$propbag = @{}
|
138
|
+
foreach ($name in Get-Member -InputObject $xml -MemberType Properties | Where-Object{$_.Name -notmatch "^(__.*|type)$"} | Select-Object -ExpandProperty name) {
|
139
|
+
Write-Debug "$Name Type: $($xml.$Name.type)" -Debug:$false
|
140
|
+
$propbag."$Name" = Convert-Properties $xml."$name"
|
141
|
+
}
|
142
|
+
if (!$Type -and $xml.HasAttribute("__type")) { $Type = $xml.__Type }
|
143
|
+
if ($ForceType -and $Type) {
|
144
|
+
try {
|
145
|
+
$output = New-Object $Type -Property $propbag
|
146
|
+
} catch {
|
147
|
+
$output = New-Object PSObject -Property $propbag
|
148
|
+
$output.PsTypeNames.Insert(0, $xml.__type)
|
149
|
+
}
|
150
|
+
} elseif ($propbag.Count -ne 0) {
|
151
|
+
$output = New-Object PSObject -Property $propbag
|
152
|
+
if ($Type) {
|
153
|
+
$output.PsTypeNames.Insert(0, $Type)
|
154
|
+
}
|
155
|
+
}
|
156
|
+
return $output
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
Function Convert-Properties {
|
161
|
+
PARAM($InputObject)
|
162
|
+
switch ($InputObject.type) {
|
163
|
+
"object" {
|
164
|
+
return (ConvertFrom-Xml -Xml $InputObject)
|
165
|
+
}
|
166
|
+
"string" {
|
167
|
+
$MightBeADate = $InputObject.get_InnerText() -as [DateTime]
|
168
|
+
## Strings that are actually dates (*grumble* JSON is crap)
|
169
|
+
if ($MightBeADate -and $propbag."$Name" -eq $MightBeADate.ToString("G")) {
|
170
|
+
return $MightBeADate
|
171
|
+
} else {
|
172
|
+
return $InputObject.get_InnerText()
|
173
|
+
}
|
174
|
+
}
|
175
|
+
"number" {
|
176
|
+
$number = $InputObject.get_InnerText()
|
177
|
+
if ($number -eq ($number -as [int])) {
|
178
|
+
return $number -as [int]
|
179
|
+
} elseif ($number -eq ($number -as [double])) {
|
180
|
+
return $number -as [double]
|
181
|
+
} else {
|
182
|
+
return $number -as [decimal]
|
183
|
+
}
|
184
|
+
}
|
185
|
+
"boolean" {
|
186
|
+
return [bool]::parse($InputObject.get_InnerText())
|
187
|
+
}
|
188
|
+
"null" {
|
189
|
+
return $null
|
190
|
+
}
|
191
|
+
"array" {
|
192
|
+
[object[]]$Items = $(foreach( $item in $InputObject.GetEnumerator() ) {
|
193
|
+
Convert-Properties $item
|
194
|
+
})
|
195
|
+
return $Items
|
196
|
+
}
|
197
|
+
default {
|
198
|
+
return $InputObject
|
199
|
+
}
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
Function ConvertFrom-Json2 {
|
204
|
+
[CmdletBinding()]
|
205
|
+
PARAM(
|
206
|
+
[Parameter(ValueFromPipeline=$true,Mandatory=$true,Position=1)] [string] $InputObject,
|
207
|
+
[Parameter(Mandatory=$true)] [Type] $Type,
|
208
|
+
[Switch] $ForceType
|
209
|
+
)
|
210
|
+
PROCESS {
|
211
|
+
$null = $PSBoundParameters.Remove("InputObject")
|
212
|
+
[Xml.XmlElement]$xml = (Convert-JsonToXml $InputObject).Root
|
213
|
+
if ($xml) {
|
214
|
+
if ($xml.Objects) {
|
215
|
+
$xml.Objects.Item.GetEnumerator() | ConvertFrom-Xml @PSBoundParameters
|
216
|
+
} elseif ($xml.Item -and $xml.Item -isnot [System.Management.Automation.PSParameterizedProperty]) {
|
217
|
+
$xml.Item | ConvertFrom-Xml @PSBoundParameters
|
218
|
+
} else {
|
219
|
+
$xml | ConvertFrom-Xml @PSBoundParameters
|
220
|
+
}
|
221
|
+
} else {
|
222
|
+
Write-Error "Failed to parse JSON with JsonReader" -Debug:$false
|
223
|
+
}
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
function ConvertFrom-PSCustomObject
|
228
|
+
{
|
229
|
+
PARAM([Parameter(ValueFromPipeline = $true)] $InputObject)
|
230
|
+
PROCESS {
|
231
|
+
if ($null -eq $InputObject) { return $null }
|
232
|
+
|
233
|
+
if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {
|
234
|
+
$collection = @(
|
235
|
+
foreach ($object in $InputObject) { ConvertFrom-PSCustomObject $object }
|
236
|
+
)
|
237
|
+
|
238
|
+
$collection
|
239
|
+
} elseif ($InputObject -is [System.Management.Automation.PSCustomObject]) {
|
240
|
+
$hash = @{}
|
241
|
+
foreach ($property in $InputObject.PSObject.Properties) {
|
242
|
+
$hash[$property.Name] = ConvertFrom-PSCustomObject $property.Value
|
243
|
+
}
|
244
|
+
|
245
|
+
$hash
|
246
|
+
} else {
|
247
|
+
$InputObject
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
function Get-ContentAsJson
|
253
|
+
{
|
254
|
+
[CmdletBinding()]
|
255
|
+
PARAM(
|
256
|
+
[Parameter(Mandatory = $true)] $Text,
|
257
|
+
[Parameter(Mandatory = $false)] [Text.Encoding] $Encoding = [Text.Encoding]::UTF8
|
258
|
+
)
|
259
|
+
|
260
|
+
# using polyfill cmdlet on PS2, so pass type info
|
261
|
+
if ($PSVersionTable.PSVersion -lt [Version]'3.0') {
|
262
|
+
$Text | ConvertFrom-Json2 -Type PSObject | ConvertFrom-PSCustomObject
|
263
|
+
} else {
|
264
|
+
$Text | ConvertFrom-Json | ConvertFrom-PSCustomObject
|
265
|
+
}
|
266
|
+
}
|
267
|
+
PS
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'open3'
|
4
4
|
require 'fileutils'
|
5
|
+
require 'tempfile'
|
5
6
|
require 'bolt/node/output'
|
6
7
|
require 'bolt/util'
|
7
8
|
|
@@ -44,7 +45,16 @@ module Bolt
|
|
44
45
|
end
|
45
46
|
|
46
47
|
def execute(command)
|
47
|
-
|
48
|
+
if Bolt::Util.windows?
|
49
|
+
command += "\r\nif (!$?) { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }"
|
50
|
+
script_file = Tempfile.new(['wrapper', '.ps1'], target.options['tmpdir'])
|
51
|
+
File.write(script_file, command)
|
52
|
+
script_file.close
|
53
|
+
|
54
|
+
command = ['powershell.exe', *Bolt::Shell::Powershell::PS_ARGS, script_file.path]
|
55
|
+
end
|
56
|
+
|
57
|
+
Open3.popen3(*command)
|
48
58
|
end
|
49
59
|
|
50
60
|
# This is used by the Bash shell to decide whether to `cd` before
|
data/lib/bolt/transport/winrm.rb
CHANGED
@@ -2,20 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'bolt/node/errors'
|
4
4
|
require 'bolt/transport/base'
|
5
|
-
require 'bolt/transport/powershell'
|
6
5
|
|
7
6
|
module Bolt
|
8
7
|
module Transport
|
9
|
-
class WinRM <
|
10
|
-
def provided_features
|
11
|
-
['powershell']
|
12
|
-
end
|
13
|
-
|
14
|
-
def default_input_method(executable)
|
15
|
-
input_method ||= Powershell.powershell_file?(executable) ? 'powershell' : 'both'
|
16
|
-
input_method
|
17
|
-
end
|
18
|
-
|
8
|
+
class WinRM < Simple
|
19
9
|
def initialize
|
20
10
|
super
|
21
11
|
require 'winrm'
|
@@ -36,113 +26,6 @@ module Bolt
|
|
36
26
|
logger.info("Failed to close connection to #{target.safe_name} : #{e.message}")
|
37
27
|
end
|
38
28
|
end
|
39
|
-
|
40
|
-
def upload(target, source, destination, _options = {})
|
41
|
-
with_connection(target) do |conn|
|
42
|
-
conn.write_remote_file(source, destination)
|
43
|
-
Bolt::Result.for_upload(target, source, destination)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def run_command(target, command, _options = {})
|
48
|
-
with_connection(target) do |conn|
|
49
|
-
output = conn.execute(command)
|
50
|
-
Bolt::Result.for_command(target,
|
51
|
-
output.stdout.string,
|
52
|
-
output.stderr.string,
|
53
|
-
output.exit_code,
|
54
|
-
'command', command)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def run_script(target, script, arguments, _options = {})
|
59
|
-
# unpack any Sensitive data
|
60
|
-
arguments = unwrap_sensitive_args(arguments)
|
61
|
-
with_connection(target) do |conn|
|
62
|
-
conn.with_remote_tempdir do |dir|
|
63
|
-
remote_path = conn.write_remote_executable(dir, script)
|
64
|
-
if Powershell.powershell_file?(remote_path)
|
65
|
-
output = conn.execute(Powershell.run_script(arguments, remote_path))
|
66
|
-
else
|
67
|
-
path, args = *Powershell.process_from_extension(remote_path)
|
68
|
-
args += Powershell.escape_arguments(arguments)
|
69
|
-
output = conn.execute_process(path, args)
|
70
|
-
end
|
71
|
-
Bolt::Result.for_command(target,
|
72
|
-
output.stdout.string,
|
73
|
-
output.stderr.string,
|
74
|
-
output.exit_code,
|
75
|
-
'script', script)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def run_task(target, task, arguments, _options = {})
|
81
|
-
implementation = select_implementation(target, task)
|
82
|
-
executable = implementation['path']
|
83
|
-
input_method = implementation['input_method']
|
84
|
-
extra_files = implementation['files']
|
85
|
-
input_method ||= Powershell.powershell_file?(executable) ? 'powershell' : 'both'
|
86
|
-
|
87
|
-
# unpack any Sensitive data
|
88
|
-
arguments = unwrap_sensitive_args(arguments)
|
89
|
-
with_connection(target) do |conn|
|
90
|
-
conn.with_remote_tempdir do |dir|
|
91
|
-
if extra_files.empty?
|
92
|
-
task_dir = dir
|
93
|
-
else
|
94
|
-
# TODO: optimize upload of directories
|
95
|
-
arguments['_installdir'] = dir
|
96
|
-
task_dir = File.join(dir, task.tasks_dir)
|
97
|
-
conn.mkdirs([task_dir] + extra_files.map { |file| File.join(dir, File.dirname(file['name'])) })
|
98
|
-
extra_files.each do |file|
|
99
|
-
conn.write_remote_file(file['path'], File.join(dir, file['name']))
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
remote_task_path = conn.write_remote_executable(task_dir, executable)
|
104
|
-
|
105
|
-
if Bolt::Task::STDIN_METHODS.include?(input_method)
|
106
|
-
stdin = JSON.dump(arguments)
|
107
|
-
end
|
108
|
-
|
109
|
-
if Bolt::Task::ENVIRONMENT_METHODS.include?(input_method)
|
110
|
-
envify_params(arguments).each do |(arg, val)|
|
111
|
-
cmd = Powershell.set_env(arg, val)
|
112
|
-
result = conn.execute(cmd)
|
113
|
-
if result.exit_code != 0
|
114
|
-
raise Bolt::Node::EnvironmentVarError.new(arg, val)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
conn.shell_init
|
120
|
-
output =
|
121
|
-
if Powershell.powershell_file?(remote_task_path) && stdin.nil?
|
122
|
-
conn.execute(Powershell.run_ps_task(arguments, remote_task_path, input_method))
|
123
|
-
else
|
124
|
-
if (interpreter = select_interpreter(remote_task_path, target.options['interpreters']))
|
125
|
-
path = interpreter
|
126
|
-
args = [remote_task_path]
|
127
|
-
else
|
128
|
-
path, args = *Powershell.process_from_extension(remote_task_path)
|
129
|
-
end
|
130
|
-
conn.execute_process(path, args, stdin)
|
131
|
-
end
|
132
|
-
|
133
|
-
Bolt::Result.for_task(target, output.stdout.string,
|
134
|
-
output.stderr.string,
|
135
|
-
output.exit_code,
|
136
|
-
task.name)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def connected?(target)
|
142
|
-
with_connection(target) { true }
|
143
|
-
rescue Bolt::Node::ConnectError
|
144
|
-
false
|
145
|
-
end
|
146
29
|
end
|
147
30
|
end
|
148
31
|
end
|
@@ -5,12 +5,10 @@ require 'bolt/node/output'
|
|
5
5
|
|
6
6
|
module Bolt
|
7
7
|
module Transport
|
8
|
-
class WinRM <
|
8
|
+
class WinRM < Simple
|
9
9
|
class Connection
|
10
10
|
attr_reader :logger, :target
|
11
11
|
|
12
|
-
DEFAULT_EXTENSIONS = ['.ps1', '.rb', '.pp'].freeze
|
13
|
-
|
14
12
|
def initialize(target, transport_logger)
|
15
13
|
raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
|
16
14
|
@target = target
|
@@ -19,9 +17,6 @@ module Bolt
|
|
19
17
|
@port = @target.port || default_port
|
20
18
|
@user = @target.user
|
21
19
|
# Build set of extensions from extensions config as well as interpreters
|
22
|
-
extensions = [target.options['extensions'] || []].flatten.map { |ext| ext[0] != '.' ? '.' + ext : ext }
|
23
|
-
extensions += target.options['interpreters'].keys if target.options['interpreters']
|
24
|
-
@extensions = DEFAULT_EXTENSIONS.to_set.merge(extensions)
|
25
20
|
|
26
21
|
@logger = Logging.logger[@target.safe_name]
|
27
22
|
logger.debug("Initializing winrm connection to #{@target.safe_name}")
|
@@ -105,67 +100,48 @@ module Bolt
|
|
105
100
|
@logger.debug { "Closed session" }
|
106
101
|
end
|
107
102
|
|
108
|
-
def shell_init
|
109
|
-
return nil if @shell_initialized
|
110
|
-
result = execute(Powershell.shell_init)
|
111
|
-
if result.exit_code != 0
|
112
|
-
raise BaseError.new("Could not initialize shell: #{result.stderr.string}", "SHELL_INIT_ERROR")
|
113
|
-
end
|
114
|
-
@shell_initialized = true
|
115
|
-
end
|
116
|
-
|
117
103
|
def execute(command)
|
118
|
-
result_output = Bolt::Node::Output.new
|
119
|
-
|
120
104
|
@logger.debug { "Executing command: #{command}" }
|
121
105
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
@
|
131
|
-
|
132
|
-
|
106
|
+
inp = StringIO.new
|
107
|
+
# This transport doesn't accept stdin, so close the stream to ensure
|
108
|
+
# it will fail if the shell attempts to provide stdin
|
109
|
+
inp.close
|
110
|
+
|
111
|
+
out_rd, out_wr = IO.pipe
|
112
|
+
err_rd, err_wr = IO.pipe
|
113
|
+
th = Thread.new do
|
114
|
+
result = @session.run(command)
|
115
|
+
out_wr << result.stdout
|
116
|
+
err_wr << result.stderr
|
117
|
+
out_wr.close
|
118
|
+
err_wr.close
|
119
|
+
result.exitcode
|
133
120
|
end
|
134
|
-
|
121
|
+
|
122
|
+
[inp, out_rd, err_rd, th]
|
135
123
|
rescue StandardError
|
136
124
|
@logger.debug { "Command aborted" }
|
137
125
|
raise
|
138
126
|
end
|
139
127
|
|
140
|
-
def
|
141
|
-
execute(Powershell.execute_process(path, arguments, stdin))
|
142
|
-
end
|
143
|
-
|
144
|
-
def mkdirs(dirs)
|
145
|
-
result = execute(Powershell.mkdirs(dirs))
|
146
|
-
if result.exit_code != 0
|
147
|
-
message = "Could not create directories: #{result.stderr}"
|
148
|
-
raise Bolt::Node::FileError.new(message, 'MKDIR_ERROR')
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def write_remote_file(source, destination)
|
128
|
+
def copy_file(source, destination)
|
153
129
|
@logger.debug { "Uploading #{source}, to #{destination}" }
|
154
130
|
if target.options['file-protocol'] == 'smb'
|
155
|
-
|
131
|
+
copy_file_smb(source, destination)
|
156
132
|
else
|
157
|
-
|
133
|
+
copy_file_winrm(source, destination)
|
158
134
|
end
|
159
135
|
end
|
160
136
|
|
161
|
-
def
|
137
|
+
def copy_file_winrm(source, destination)
|
162
138
|
fs = ::WinRM::FS::FileManager.new(@connection)
|
163
139
|
fs.upload(source, destination)
|
164
140
|
rescue StandardError => e
|
165
141
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
166
142
|
end
|
167
143
|
|
168
|
-
def
|
144
|
+
def copy_file_smb(source, destination)
|
169
145
|
# lazy-load expensive gem code
|
170
146
|
require 'ruby_smb'
|
171
147
|
|
@@ -185,7 +161,7 @@ module Bolt
|
|
185
161
|
client = smb_client_login
|
186
162
|
tree = client.tree_connect(path)
|
187
163
|
begin
|
188
|
-
|
164
|
+
copy_file_smb_recursive(tree, source, dest)
|
189
165
|
ensure
|
190
166
|
tree.disconnect!
|
191
167
|
end
|
@@ -195,35 +171,8 @@ module Bolt
|
|
195
171
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
196
172
|
end
|
197
173
|
|
198
|
-
def
|
199
|
-
|
200
|
-
result = execute(Powershell.make_tempdir(find_parent))
|
201
|
-
if result.exit_code != 0
|
202
|
-
raise Bolt::Node::FileError.new("Could not make tempdir: #{result.stderr}", 'TEMPDIR_ERROR')
|
203
|
-
end
|
204
|
-
result.stdout.string.chomp
|
205
|
-
end
|
206
|
-
|
207
|
-
def with_remote_tempdir
|
208
|
-
dir = make_tempdir
|
209
|
-
yield dir
|
210
|
-
ensure
|
211
|
-
execute(Powershell.rmdir(dir))
|
212
|
-
end
|
213
|
-
|
214
|
-
def validate_extensions(ext)
|
215
|
-
unless @extensions.include?(ext)
|
216
|
-
raise Bolt::Node::FileError.new("File extension #{ext} is not enabled, "\
|
217
|
-
"to run it please add to 'winrm: extensions'", 'FILETYPE_ERROR')
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def write_remote_executable(dir, file, filename = nil)
|
222
|
-
filename ||= File.basename(file)
|
223
|
-
validate_extensions(File.extname(filename))
|
224
|
-
remote_path = "#{dir}\\#{filename}"
|
225
|
-
write_remote_file(file, remote_path)
|
226
|
-
remote_path
|
174
|
+
def shell
|
175
|
+
@shell ||= Bolt::Shell::Powershell.new(target, self)
|
227
176
|
end
|
228
177
|
|
229
178
|
private
|
@@ -273,13 +222,13 @@ module Bolt
|
|
273
222
|
)
|
274
223
|
end
|
275
224
|
|
276
|
-
def
|
225
|
+
def copy_file_smb_recursive(tree, source, dest)
|
277
226
|
if Dir.exist?(source)
|
278
227
|
tree.open_directory(directory: dest, write: true, disposition: ::RubySMB::Dispositions::FILE_OPEN_IF)
|
279
228
|
|
280
229
|
Dir.children(source).each do |child|
|
281
230
|
child_dest = dest + '\\' + child
|
282
|
-
|
231
|
+
copy_file_smb_recursive(tree, File.join(source, child), child_dest)
|
283
232
|
end
|
284
233
|
return
|
285
234
|
end
|