winrm-transport 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,47 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module WinRM
20
+
21
+ module Transport
22
+
23
+ # Mixin to use an optionally provided logger for logging.
24
+ #
25
+ # @author Fletcher Nichol <fnichol@nichol.ca>
26
+ module Logging
27
+
28
+ # Logs a message on the logger at the debug level, if a logger is
29
+ # present.
30
+ #
31
+ # @param msg [String] a message to log
32
+ # @yield evaluates and uses return value as message to log. If msg
33
+ # parameter is set, it will take precedence over the block.
34
+ def debug(msg = nil, &block)
35
+ return if logger.nil? || !logger.debug?
36
+ logger.debug("[#{log_subject}] " << (msg || block.call))
37
+ end
38
+
39
+ # The subject for log messages.
40
+ #
41
+ # @return [String] log subject
42
+ def log_subject
43
+ @log_subject ||= self.class.to_s.split("::").last
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,71 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module WinRM
20
+
21
+ module Transport
22
+
23
+ # An object that can close a remote shell session over WinRM.
24
+ #
25
+ # @author Fletcher Nichol <fnichol@nichol.ca>
26
+ class ShellCloser
27
+
28
+ # @return [String,nil] the identifier for the current open remote
29
+ # shell session
30
+ attr_accessor :shell_id
31
+
32
+ # Constructs a new ShellCloser.
33
+ #
34
+ # @param info [String] a string representation of the connection
35
+ # @param debug [true,false] whether or not debug messages should be
36
+ # output
37
+ # @param args [Array] arguments to construct a `WinRM::WinRMWebService`
38
+ def initialize(info, debug, args)
39
+ @info = info
40
+ @debug = debug
41
+ @args = args
42
+ end
43
+
44
+ # Closes the remote shell session.
45
+ def call(*)
46
+ debug("[CommandExecutor] closing remote shell #{@shell_id} on #{@info}")
47
+ ::WinRM::WinRMWebService.new(*@args).close_shell(@shell_id)
48
+ debug("[CommandExecutor] remote shell #{@shell_id} closed")
49
+ rescue => e
50
+ debug("Exception: #{e.inspect}")
51
+ end
52
+
53
+ # @param shell_id [String] a remote shell ID
54
+ # @return [ShellCloser] a new ShellCloser with a copy of this object's
55
+ # state and the shell_id set to the given parameter value
56
+ def for(shell_id)
57
+ self.class.new(@info, @debug, @args).tap { |c| c.shell_id = shell_id }
58
+ end
59
+
60
+ private
61
+
62
+ # Writes a debug message, if debug mode is enabled.
63
+ #
64
+ # @param message [String] a message
65
+ # @api private
66
+ def debug(message)
67
+ $stdout.puts "D #{message}" if @debug
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,184 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require "delegate"
20
+ require "pathname"
21
+ require "tempfile"
22
+ require "zip"
23
+
24
+ require "winrm/transport/logging"
25
+
26
+ module WinRM
27
+
28
+ module Transport
29
+
30
+ # A temporary Zip file for a given directory.
31
+ #
32
+ # @author Fletcher Nichol <fnichol@nichol.ca>
33
+ class TmpZip
34
+
35
+ include Logging
36
+
37
+ # Contructs a new Zip file for the given directory.
38
+ #
39
+ # There are 2 ways to interpret the directory path:
40
+ #
41
+ # * If the directory has no path separator terminator, then the
42
+ # directory basename will be used as the base directory in the
43
+ # resulting zip file.
44
+ # * If the directory has a path separator terminator (such as `/` or
45
+ # `\\`), then the entries under the directory will be added to the
46
+ # resulting zip file.
47
+ #
48
+ # The following emaples assume a directory tree structure of:
49
+ #
50
+ # src
51
+ # |-- alpha.txt
52
+ # |-- beta.txt
53
+ # \-- sub
54
+ # \-- charlie.txt
55
+ #
56
+ # @example Including the base directory in the zip file
57
+ #
58
+ # TmpZip.new("/path/to/src")
59
+ # # produces a zip file with entries:
60
+ # # - src/alpha.txt
61
+ # # - src/beta.txt
62
+ # # - src/sub/charlie.txt
63
+ #
64
+ # @example Excluding the base directory in the zip file
65
+ #
66
+ # TmpZip.new("/path/to/src/")
67
+ # # produces a zip file with entries:
68
+ # # - alpha.txt
69
+ # # - beta.txt
70
+ # # - sub/charlie.txt
71
+ #
72
+ # @param dir [String,Pathname,#to_s] path to the directory
73
+ # @param logger [#debug,#debug?] an optional logger/ui object that
74
+ # responds to `#debug` and `#debug?` (default `nil`)
75
+ def initialize(dir, logger = nil)
76
+ @logger = logger
77
+ @dir = Pathname.new(dir)
78
+ @method = ::Zip::Entry::DEFLATED
79
+ @compression = Zlib::BEST_COMPRESSION
80
+ @zip_io = Tempfile.open(["tmpzip-", ".zip"], :binmode => true)
81
+ write_zip
82
+ @zip_io.close
83
+ end
84
+
85
+ # @return [Pathname] path to zip file
86
+ def path
87
+ Pathname.new(zip_io.path) if zip_io.path
88
+ end
89
+
90
+ # Unlinks (deletes) the zip file from the filesystem.
91
+ def unlink
92
+ zip_io.unlink
93
+ end
94
+
95
+ private
96
+
97
+ # @return [Integer] the compression used for Zip entries. Possible
98
+ # values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION`,
99
+ # and `Zlib::NO_COMPRESSION`.
100
+ # @api private
101
+ attr_reader :compression
102
+
103
+ # @return [Pathname] the directory used to create the Zip file
104
+ # @api private
105
+ attr_reader :dir
106
+
107
+ # @return [#debug] the logger
108
+ # @api private
109
+ attr_reader :logger
110
+
111
+ # @return [Integer] compression method used for Zip entries. Possible
112
+ # values are `Zip::Entry::DEFLATED` and `Zip::Entry::STORED`.
113
+ # @api private
114
+ attr_reader :method
115
+
116
+ # @return [IO] the Zip file IO
117
+ # @api private
118
+ attr_reader :zip_io
119
+
120
+ # @return [Pathname] the path segement to be stripped off Zip entries
121
+ # @api private
122
+ def dir_strip
123
+ @dir_strip ||= if dir.to_s.end_with?("/", "\\")
124
+ Pathname.new(dir.to_s.chop)
125
+ else
126
+ dir.dirname
127
+ end
128
+ end
129
+
130
+ # @return [Array<Pathname] all recursive files under the base
131
+ # directory, excluding directories
132
+ # @api private
133
+ def entries
134
+ Pathname.glob(dir.join("**/*")).delete_if(&:directory?).sort
135
+ end
136
+
137
+ # (see Logging.log_subject)
138
+ # @api private
139
+ def log_subject
140
+ @log_subject ||= [self.class.to_s.split("::").last, path].join("::")
141
+ end
142
+
143
+ # Adds all file entries to the Zip output stream.
144
+ #
145
+ # @param zos [Zip::OutputStream] zip output stream
146
+ # @api private
147
+ def produce_zip_entries(zos)
148
+ entries.each do |entry|
149
+ entry_path = entry.sub("#{dir_strip}/", "")
150
+ debug { "+++ Adding #{entry_path}" }
151
+ zos.put_next_entry(entry_path, nil, nil, method, compression)
152
+ entry.open("rb") { |src| IO.copy_stream(src, zos) }
153
+ end
154
+ debug { "=== All files added." }
155
+ end
156
+
157
+ # Writes out a temporary Zip file.
158
+ #
159
+ # @api private
160
+ def write_zip
161
+ debug { "Populating files" }
162
+ Zip::OutputStream.write_buffer(NoDupIO.new(zip_io)) do |zos|
163
+ produce_zip_entries(zos)
164
+ end
165
+ end
166
+
167
+ # Simple delegate wrapper to prevent `#dup` calls being made on IO
168
+ # objects. This is used to bypass an issue in the `Zip::Outputstream`
169
+ # constructor where an incoming IO is duplicated, leading to races
170
+ # on flushing the final stream to disk.
171
+ #
172
+ # @author Fletcher Nichol <fnichol@nichol.ca>
173
+ # @api private
174
+ class NoDupIO < SimpleDelegator
175
+
176
+ # @return [self] returns self and does *not* return a duplicate
177
+ # object
178
+ def dup
179
+ self
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module WinRM
20
+
21
+ module Transport
22
+
23
+ VERSION = "1.0.0"
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ Function Cleanup($o) { if (($o -ne $null) -and ($o.GetType().GetMethod("Dispose") -ne $null)) { $o.Dispose() } }
2
+
3
+ Function Decode-Base64File($src, $dst) {
4
+ Try {
5
+ $in = (Get-Item $src).OpenRead()
6
+ $b64 = New-Object -TypeName System.Security.Cryptography.FromBase64Transform
7
+ $m = [System.Security.Cryptography.CryptoStreamMode]::Read
8
+ $d = New-Object -TypeName System.Security.Cryptography.CryptoStream $in,$b64,$m
9
+ Copy-Stream $d ($out = [System.IO.File]::OpenWrite($dst))
10
+ } Finally { Cleanup $in; Cleanup $out; Cleanup $d }
11
+ }
12
+
13
+ Function Copy-Stream($src, $dst) { $b = New-Object Byte[] 4096; while (($i = $src.Read($b, 0, $b.Length)) -ne 0) { $dst.Write($b, 0, $i) } }
14
+
15
+ Function Check-Files($h) {
16
+ return $h.GetEnumerator() | ForEach-Object {
17
+ $dst = Unresolve-Path $_.Key
18
+ New-Object psobject -Property @{
19
+ chk_exists = ($exists = Test-Path $dst -PathType Leaf)
20
+ src_md5 = ($sMd5 = $_.Value)
21
+ dst_md5 = ($dMd5 = if ($exists) { Get-MD5Sum $dst } else { $null })
22
+ chk_dirty = ($dirty = if ($sMd5 -ne $dMd5) { $true } else { $false })
23
+ verifies = if ($dirty -eq $false) { $true } else { $false }
24
+ }
25
+ } | Select-Object -Property chk_exists,src_md5,dst_md5,chk_dirty,verifies
26
+ }
27
+
28
+ Function Get-MD5Sum($src) {
29
+ Try {
30
+ $c = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
31
+ $bytes = $c.ComputeHash(($in = (Get-Item $src).OpenRead()))
32
+ return ([System.BitConverter]::ToString($bytes)).Replace("-", "").ToLower()
33
+ } Finally { Cleanup $c; Cleanup $in }
34
+ }
35
+
36
+ Function Invoke-Input($in) {
37
+ $in = Unresolve-Path $in
38
+ Decode-Base64File $in ($decoded = "$($in).ps1")
39
+ $expr = Get-Content $decoded | Out-String
40
+ Remove-Item $in,$decoded -Force
41
+ return Invoke-Expression "$expr"
42
+ }
43
+
44
+ Function Unresolve-Path($p) { if ($p -eq $null) { return $null } else { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p) } }
45
+
46
+ Check-Files (Invoke-Input $hash_file) | ConvertTo-Csv -NoTypeInformation
@@ -0,0 +1,60 @@
1
+ Function Cleanup($o) { if (($o -ne $null) -and ($o.GetType().GetMethod("Dispose") -ne $null)) { $o.Dispose() } }
2
+
3
+ Function Decode-Base64File($src, $dst) {
4
+ Try {
5
+ $in = (Get-Item $src).OpenRead()
6
+ $b64 = New-Object -TypeName System.Security.Cryptography.FromBase64Transform
7
+ $m = [System.Security.Cryptography.CryptoStreamMode]::Read
8
+ $d = New-Object -TypeName System.Security.Cryptography.CryptoStream $in,$b64,$m
9
+ echo $null > $dst
10
+ Copy-Stream $d ($out = [System.IO.File]::OpenWrite($dst))
11
+ } Finally { Cleanup $in; Cleanup $out; Cleanup $d }
12
+ }
13
+
14
+ Function Copy-Stream($src, $dst) { $b = New-Object Byte[] 4096; while (($i = $src.Read($b, 0, $b.Length)) -ne 0) { $dst.Write($b, 0, $i) } }
15
+
16
+ Function Decode-Files($hash) {
17
+ $hash.GetEnumerator() | ForEach-Object {
18
+ $tmp = Unresolve-Path $_.Key
19
+ $sMd5 = (Get-Item $tmp).BaseName.Replace("b64-", "")
20
+ $tzip, $dst = (Unresolve-Path $_.Value["tmpzip"]), (Unresolve-Path $_.Value["dst"])
21
+ $decoded = if ($tzip -ne $null) { $tzip } else { $dst }
22
+ Decode-Base64File $tmp $decoded
23
+ Remove-Item $tmp -Force
24
+ $dMd5 = Get-MD5Sum $decoded
25
+ $verifies = if ($sMd5 -eq $dMd5) { $true } else { $false }
26
+ if ($tzip) { Unzip-File $tzip $dst; Remove-Item $tzip -Force }
27
+ New-Object psobject -Property @{ dst = $dst; verifies = $verifies; src_md5 = $sMd5; dst_md5 = $dMd5; tmpfile = $tmp; tmpzip = $tzip }
28
+ } | Select-Object -Property dst,verifies,src_md5,dst_md5,tmpfile,tmpzip
29
+ }
30
+
31
+ Function Get-MD5Sum($src) {
32
+ Try {
33
+ $c = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
34
+ $bytes = $c.ComputeHash(($in = (Get-Item $src).OpenRead()))
35
+ return ([System.BitConverter]::ToString($bytes)).Replace("-", "").ToLower()
36
+ } Finally { Cleanup $c; Cleanup $in }
37
+ }
38
+
39
+ Function Invoke-Input($in) {
40
+ $in = Unresolve-Path $in
41
+ Decode-Base64File $in ($decoded = "$($in).ps1")
42
+ $expr = Get-Content $decoded | Out-String
43
+ Remove-Item $in,$decoded -Force
44
+ return Invoke-Expression "$expr"
45
+ }
46
+
47
+ Function Release-COM($o) { if ($o -ne $null) { [void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($o) } }
48
+
49
+ Function Unresolve-Path($p) { if ($p -eq $null) { return $null } else { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p) } }
50
+
51
+ Function Unzip-File($src, $dst) {
52
+ $r = "HKLM:\Software\Microsoft\NET Framework Setup\NDP\v4"
53
+ if (($PSVersionTable.PSVersion.Major -ge 3) -and ((gp "$r\Full").Version -like "4.5*" -or (gp "$r\Client").Version -like "4.5*")) {
54
+ [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null; [System.IO.Compression.ZipFile]::ExtractToDirectory("$src", "$dst")
55
+ } else {
56
+ Try { $s = New-Object -ComObject Shell.Application; ($dp = $s.NameSpace($dst)).CopyHere(($z = $s.NameSpace($src)).Items(), 0x610) } Finally { Release-Com $s; Release-Com $z; Release-COM $dp }
57
+ }
58
+ }
59
+
60
+ Decode-Files (Invoke-Input $hash_file) | ConvertTo-Csv -NoTypeInformation
@@ -0,0 +1,52 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "winrm/transport/version"
5
+ require "English"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "winrm-transport"
9
+ spec.version = WinRM::Transport::VERSION
10
+ spec.authors = ["Fletcher Nichol"]
11
+ spec.email = ["fnichol@nichol.ca"]
12
+
13
+ spec.summary = "WinRM transport logic for re-using remote shells " \
14
+ "and uploading files. The original code was extracted " \
15
+ "from the Test Kitchen project and remains the " \
16
+ "primary reference use case."
17
+
18
+ spec.description = spec.summary
19
+ spec.homepage = "https://github.com/test-kitchen/winrm-transport"
20
+ spec.license = "Apache 2.0"
21
+
22
+ spec.files = `git ls-files -z`.split("\x0").
23
+ reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+
25
+ spec.bindir = "exe"
26
+ spec.executables = []
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.required_ruby_version = ">= 1.9.1"
30
+
31
+ spec.add_dependency "winrm", "~> 1.3"
32
+ spec.add_dependency "rubyzip", ">= 1.1.7", "~> 1.1"
33
+
34
+ spec.add_development_dependency "pry"
35
+ spec.add_development_dependency "bundler", "~> 1.9"
36
+ spec.add_development_dependency "rake", "~> 10.0"
37
+
38
+ spec.add_development_dependency "fakefs", "~> 0.4"
39
+ spec.add_development_dependency "minitest"
40
+ spec.add_development_dependency "mocha", "~> 1.1"
41
+
42
+ spec.add_development_dependency "countloc", "~> 0.4"
43
+ spec.add_development_dependency "maruku", "~> 0.6"
44
+ spec.add_development_dependency "simplecov", "~> 0.7"
45
+ spec.add_development_dependency "yard", "~> 0.8"
46
+
47
+ # style and complexity libraries are tightly version pinned as newer releases
48
+ # may introduce new and undesireable style choices which would be immediately
49
+ # enforced in CI
50
+ spec.add_development_dependency "finstyle", "1.4.0"
51
+ spec.add_development_dependency "cane", "2.6.2"
52
+ end