winrm-fs 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,166 +1,166 @@
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
- module WinRM
25
- module FS
26
- module Core
27
- # A temporary Zip file for a given directory.
28
- #
29
- # @author Fletcher Nichol <fnichol@nichol.ca>
30
- class TmpZip
31
- # Contructs a new Zip file for the given directory.
32
- #
33
- # There are 2 ways to interpret the directory path:
34
- #
35
- # * If the directory has no path separator terminator, then the
36
- # directory basename will be used as the base directory in the
37
- # resulting zip file.
38
- # * If the directory has a path separator terminator (such as `/` or
39
- # `\\`), then the entries under the directory will be added to the
40
- # resulting zip file.
41
- #
42
- # The following emaples assume a directory tree structure of:
43
- #
44
- # src
45
- # |-- alpha.txt
46
- # |-- beta.txt
47
- # \-- sub
48
- # \-- charlie.txt
49
- #
50
- # @example Including the base directory in the zip file
51
- #
52
- # TmpZip.new("/path/to/src")
53
- # # produces a zip file with entries:
54
- # # - src/alpha.txt
55
- # # - src/beta.txt
56
- # # - src/sub/charlie.txt
57
- #
58
- # @example Excluding the base directory in the zip file
59
- #
60
- # TmpZip.new("/path/to/src/")
61
- # # produces a zip file with entries:
62
- # # - alpha.txt
63
- # # - beta.txt
64
- # # - sub/charlie.txt
65
- #
66
- # @param dir [String,Pathname,#to_s] path to the directory
67
- # @param logger [#debug,#debug?] an optional logger/ui object that
68
- # responds to `#debug` and `#debug?` (default `nil`)
69
- def initialize(dir, logger = nil)
70
- @logger = logger || Logging.logger[self]
71
- @dir = Pathname.new(dir)
72
- @zip_io = Tempfile.open(['tmpzip-', '.zip'], binmode: true)
73
- write_zip
74
- @zip_io.close
75
- end
76
-
77
- # @return [Pathname] path to zip file
78
- def path
79
- Pathname.new(zip_io.path) if zip_io.path
80
- end
81
-
82
- # Unlinks (deletes) the zip file from the filesystem.
83
- def unlink
84
- zip_io.unlink
85
- end
86
-
87
- private
88
-
89
- # @return [Pathname] the directory used to create the Zip file
90
- # @api private
91
- attr_reader :dir
92
-
93
- # @return [#debug] the logger
94
- # @api private
95
- attr_reader :logger
96
-
97
- # @return [IO] the Zip file IO
98
- # @api private
99
- attr_reader :zip_io
100
-
101
- # @return [Array<Pathname] all recursive files under the base
102
- # directory, excluding directories
103
- # @api private
104
- def entries
105
- Pathname.glob(dir.join('**/*')).delete_if(&:directory?).sort
106
- end
107
-
108
- # (see Logging.log_subject)
109
- # @api private
110
- def log_subject
111
- @log_subject ||= [self.class.to_s.split('::').last, path].join('::')
112
- end
113
-
114
- # Adds all file entries to the Zip output stream.
115
- #
116
- # @param zos [Zip::OutputStream] zip output stream
117
- # @api private
118
- def produce_zip_entries(zos)
119
- entries.each do |entry|
120
- entry_path = entry.sub(/#{dir}\//i, '')
121
- logger.debug "+++ Adding #{entry_path}"
122
- zos.put_next_entry(
123
- zip_entry(entry_path),
124
- nil, nil, ::Zip::Entry::DEFLATED, Zlib::BEST_COMPRESSION)
125
- entry.open('rb') { |src| IO.copy_stream(src, zos) }
126
- end
127
- logger.debug '=== All files added.'
128
- end
129
-
130
- # Writes out a temporary Zip file.
131
- #
132
- # @api private
133
- def write_zip
134
- logger.debug 'Populating files'
135
- Zip::OutputStream.write_buffer(NoDupIO.new(zip_io)) do |zos|
136
- produce_zip_entries(zos)
137
- end
138
- end
139
-
140
- def zip_entry(entry_path)
141
- Zip::Entry.new(
142
- zip_io.path,
143
- entry_path.to_s,
144
- nil, nil, nil, nil, nil, nil,
145
- ::Zip::DOSTime.new(2000)
146
- )
147
- end
148
-
149
- # Simple delegate wrapper to prevent `#dup` calls being made on IO
150
- # objects. This is used to bypass an issue in the `Zip::Outputstream`
151
- # constructor where an incoming IO is duplicated, leading to races
152
- # on flushing the final stream to disk.
153
- #
154
- # @author Fletcher Nichol <fnichol@nichol.ca>
155
- # @api private
156
- class NoDupIO < SimpleDelegator
157
- # @return [self] returns self and does *not* return a duplicate
158
- # object
159
- def dup
160
- self
161
- end
162
- end
163
- end
164
- end
165
- end
166
- end
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
+ module WinRM
25
+ module FS
26
+ module Core
27
+ # A temporary Zip file for a given directory.
28
+ #
29
+ # @author Fletcher Nichol <fnichol@nichol.ca>
30
+ class TmpZip
31
+ # Contructs a new Zip file for the given directory.
32
+ #
33
+ # There are 2 ways to interpret the directory path:
34
+ #
35
+ # * If the directory has no path separator terminator, then the
36
+ # directory basename will be used as the base directory in the
37
+ # resulting zip file.
38
+ # * If the directory has a path separator terminator (such as `/` or
39
+ # `\\`), then the entries under the directory will be added to the
40
+ # resulting zip file.
41
+ #
42
+ # The following emaples assume a directory tree structure of:
43
+ #
44
+ # src
45
+ # |-- alpha.txt
46
+ # |-- beta.txt
47
+ # \-- sub
48
+ # \-- charlie.txt
49
+ #
50
+ # @example Including the base directory in the zip file
51
+ #
52
+ # TmpZip.new("/path/to/src")
53
+ # # produces a zip file with entries:
54
+ # # - src/alpha.txt
55
+ # # - src/beta.txt
56
+ # # - src/sub/charlie.txt
57
+ #
58
+ # @example Excluding the base directory in the zip file
59
+ #
60
+ # TmpZip.new("/path/to/src/")
61
+ # # produces a zip file with entries:
62
+ # # - alpha.txt
63
+ # # - beta.txt
64
+ # # - sub/charlie.txt
65
+ #
66
+ # @param dir [String,Pathname,#to_s] path to the directory
67
+ # @param logger [#debug,#debug?] an optional logger/ui object that
68
+ # responds to `#debug` and `#debug?` (default `nil`)
69
+ def initialize(dir, logger = nil)
70
+ @logger = logger || Logging.logger[self]
71
+ @dir = Pathname.new(dir)
72
+ @zip_io = Tempfile.open(['tmpzip-', '.zip'], binmode: true)
73
+ write_zip
74
+ @zip_io.close
75
+ end
76
+
77
+ # @return [Pathname] path to zip file
78
+ def path
79
+ Pathname.new(zip_io.path) if zip_io.path
80
+ end
81
+
82
+ # Unlinks (deletes) the zip file from the filesystem.
83
+ def unlink
84
+ zip_io.unlink
85
+ end
86
+
87
+ private
88
+
89
+ # @return [Pathname] the directory used to create the Zip file
90
+ # @api private
91
+ attr_reader :dir
92
+
93
+ # @return [#debug] the logger
94
+ # @api private
95
+ attr_reader :logger
96
+
97
+ # @return [IO] the Zip file IO
98
+ # @api private
99
+ attr_reader :zip_io
100
+
101
+ # @return [Array<Pathname] all recursive files under the base
102
+ # directory, excluding directories
103
+ # @api private
104
+ def entries
105
+ Pathname.glob(dir.join('**/*')).delete_if(&:directory?).sort
106
+ end
107
+
108
+ # (see Logging.log_subject)
109
+ # @api private
110
+ def log_subject
111
+ @log_subject ||= [self.class.to_s.split('::').last, path].join('::')
112
+ end
113
+
114
+ # Adds all file entries to the Zip output stream.
115
+ #
116
+ # @param zos [Zip::OutputStream] zip output stream
117
+ # @api private
118
+ def produce_zip_entries(zos)
119
+ entries.each do |entry|
120
+ entry_path = entry.sub(/#{dir}\//i, '')
121
+ logger.debug "+++ Adding #{entry_path}"
122
+ zos.put_next_entry(
123
+ zip_entry(entry_path),
124
+ nil, nil, ::Zip::Entry::DEFLATED, Zlib::BEST_COMPRESSION)
125
+ entry.open('rb') { |src| IO.copy_stream(src, zos) }
126
+ end
127
+ logger.debug '=== All files added.'
128
+ end
129
+
130
+ # Writes out a temporary Zip file.
131
+ #
132
+ # @api private
133
+ def write_zip
134
+ logger.debug 'Populating files'
135
+ Zip::OutputStream.write_buffer(NoDupIO.new(zip_io)) do |zos|
136
+ produce_zip_entries(zos)
137
+ end
138
+ end
139
+
140
+ def zip_entry(entry_path)
141
+ Zip::Entry.new(
142
+ zip_io.path,
143
+ entry_path.to_s,
144
+ nil, nil, nil, nil, nil, nil,
145
+ ::Zip::DOSTime.new(2000)
146
+ )
147
+ end
148
+
149
+ # Simple delegate wrapper to prevent `#dup` calls being made on IO
150
+ # objects. This is used to bypass an issue in the `Zip::Outputstream`
151
+ # constructor where an incoming IO is duplicated, leading to races
152
+ # on flushing the final stream to disk.
153
+ #
154
+ # @author Fletcher Nichol <fnichol@nichol.ca>
155
+ # @api private
156
+ class NoDupIO < SimpleDelegator
157
+ # @return [self] returns self and does *not* return a duplicate
158
+ # object
159
+ def dup
160
+ self
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -1,28 +1,28 @@
1
- # encoding: UTF-8
2
- #
3
- # Copyright 2015 Shawn Neal <sneal@sneal.net>
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- module WinRM
18
- module FS
19
- # WinRM-FS base class for errors
20
- class WinRMFSError < StandardError; end
21
-
22
- # Error that occurs when a file upload fails
23
- class WinRMUploadError < WinRMFSError; end
24
-
25
- # Error that occurs when a file download fails
26
- class WinRMDownloadError < WinRMFSError; end
27
- end
28
- end
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2015 Shawn Neal <sneal@sneal.net>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module WinRM
18
+ module FS
19
+ # WinRM-FS base class for errors
20
+ class WinRMFSError < StandardError; end
21
+
22
+ # Error that occurs when a file upload fails
23
+ class WinRMUploadError < WinRMFSError; end
24
+
25
+ # Error that occurs when a file download fails
26
+ class WinRMDownloadError < WinRMFSError; end
27
+ end
28
+ end
@@ -1,117 +1,117 @@
1
- # encoding: UTF-8
2
- #
3
- # Copyright 2015 Shawn Neal <sneal@sneal.net>
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- require 'winrm'
18
- require_relative 'scripts/scripts'
19
- require_relative 'core/file_transporter'
20
-
21
- module WinRM
22
- module FS
23
- # Perform file transfer operations between a local machine and winrm endpoint
24
- class FileManager
25
- # Creates a new FileManager instance
26
- # @param [WinRMWebService] WinRM web service client
27
- def initialize(service)
28
- @service = service
29
- @logger = service.logger
30
- end
31
-
32
- # Gets the MD5 checksum of the specified file if it exists,
33
- # otherwise ''
34
- # @param [String] The remote file path
35
- def checksum(path)
36
- @logger.debug("checksum: #{path}")
37
- script = WinRM::FS::Scripts.render('checksum', path: path)
38
- @service.create_executor { |e| e.run_powershell_script(script).stdout.chomp }
39
- end
40
-
41
- # Create the specifed directory recursively
42
- # @param [String] The remote dir to create
43
- # @return [Boolean] True if successful, otherwise false
44
- def create_dir(path)
45
- @logger.debug("create_dir: #{path}")
46
- script = WinRM::FS::Scripts.render('create_dir', path: path)
47
- @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
48
- end
49
-
50
- # Deletes the file or directory at the specified path
51
- # @param [String] The path to remove
52
- # @return [Boolean] True if successful, otherwise False
53
- def delete(path)
54
- @logger.debug("deleting: #{path}")
55
- script = WinRM::FS::Scripts.render('delete', path: path)
56
- @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
57
- end
58
-
59
- # Downloads the specified remote file to the specified local path
60
- # @param [String] The full path on the remote machine
61
- # @param [String] The full path to write the file to locally
62
- def download(remote_path, local_path)
63
- @logger.debug("downloading: #{remote_path} -> #{local_path}")
64
- script = WinRM::FS::Scripts.render('download', path: remote_path)
65
- output = @service.create_executor { |e| e.run_powershell_script(script) }
66
- return false if output[:exitcode] != 0
67
- contents = output.stdout.gsub('\n\r', '')
68
- out = Base64.decode64(contents)
69
- IO.binwrite(local_path, out)
70
- true
71
- end
72
-
73
- # Checks to see if the given path exists on the target file system.
74
- # @param [String] The full path to the directory or file
75
- # @return [Boolean] True if the file/dir exists, otherwise false.
76
- def exists?(path)
77
- @logger.debug("exists?: #{path}")
78
- script = WinRM::FS::Scripts.render('exists', path: path)
79
- @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
80
- end
81
-
82
- # Gets the current user's TEMP directory on the remote system, for example
83
- # 'C:/Windows/Temp'
84
- # @return [String] Full path to the temp directory
85
- def temp_dir
86
- @guest_temp ||= begin
87
- (@service.create_executor { |e| e.run_cmd('echo %TEMP%') }).stdout.chomp.gsub('\\', '/')
88
- end
89
- end
90
-
91
- # Upload one or more local files and directories to a remote directory
92
- # @example copy a single file to a winrm endpoint
93
- #
94
- # file_manager.upload('/Users/sneal/myfile.txt', 'c:/foo/myfile.txt')
95
- #
96
- # @example copy a single directory to a winrm endpoint
97
- #
98
- # file_manager.upload('c:/dev/my_dir', '$env:AppData')
99
- #
100
- # @param [String] A path to a local directory or file that will be copied
101
- # to the remote Windows box.
102
- # @param [String] The target directory or file
103
- # This path may contain powershell style environment variables
104
- # @yieldparam [Fixnum] Number of bytes copied in current payload sent to the winrm endpoint
105
- # @yieldparam [Fixnum] The total number of bytes to be copied
106
- # @yieldparam [String] Path of file being copied
107
- # @yieldparam [String] Target path on the winrm endpoint
108
- # @return [Fixnum] The total number of bytes copied
109
- def upload(local_path, remote_path, &block)
110
- @service.create_executor do |executor|
111
- file_transporter ||= WinRM::FS::Core::FileTransporter.new(executor)
112
- file_transporter.upload(local_path, remote_path, &block)[0]
113
- end
114
- end
115
- end
116
- end
117
- end
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2015 Shawn Neal <sneal@sneal.net>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'winrm'
18
+ require_relative 'scripts/scripts'
19
+ require_relative 'core/file_transporter'
20
+
21
+ module WinRM
22
+ module FS
23
+ # Perform file transfer operations between a local machine and winrm endpoint
24
+ class FileManager
25
+ # Creates a new FileManager instance
26
+ # @param [WinRMWebService] WinRM web service client
27
+ def initialize(service)
28
+ @service = service
29
+ @logger = service.logger
30
+ end
31
+
32
+ # Gets the MD5 checksum of the specified file if it exists,
33
+ # otherwise ''
34
+ # @param [String] The remote file path
35
+ def checksum(path)
36
+ @logger.debug("checksum: #{path}")
37
+ script = WinRM::FS::Scripts.render('checksum', path: path)
38
+ @service.create_executor { |e| e.run_powershell_script(script).stdout.chomp }
39
+ end
40
+
41
+ # Create the specifed directory recursively
42
+ # @param [String] The remote dir to create
43
+ # @return [Boolean] True if successful, otherwise false
44
+ def create_dir(path)
45
+ @logger.debug("create_dir: #{path}")
46
+ script = WinRM::FS::Scripts.render('create_dir', path: path)
47
+ @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
48
+ end
49
+
50
+ # Deletes the file or directory at the specified path
51
+ # @param [String] The path to remove
52
+ # @return [Boolean] True if successful, otherwise False
53
+ def delete(path)
54
+ @logger.debug("deleting: #{path}")
55
+ script = WinRM::FS::Scripts.render('delete', path: path)
56
+ @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
57
+ end
58
+
59
+ # Downloads the specified remote file to the specified local path
60
+ # @param [String] The full path on the remote machine
61
+ # @param [String] The full path to write the file to locally
62
+ def download(remote_path, local_path)
63
+ @logger.debug("downloading: #{remote_path} -> #{local_path}")
64
+ script = WinRM::FS::Scripts.render('download', path: remote_path)
65
+ output = @service.create_executor { |e| e.run_powershell_script(script) }
66
+ return false if output[:exitcode] != 0
67
+ contents = output.stdout.gsub('\n\r', '')
68
+ out = Base64.decode64(contents)
69
+ IO.binwrite(local_path, out)
70
+ true
71
+ end
72
+
73
+ # Checks to see if the given path exists on the target file system.
74
+ # @param [String] The full path to the directory or file
75
+ # @return [Boolean] True if the file/dir exists, otherwise false.
76
+ def exists?(path)
77
+ @logger.debug("exists?: #{path}")
78
+ script = WinRM::FS::Scripts.render('exists', path: path)
79
+ @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
80
+ end
81
+
82
+ # Gets the current user's TEMP directory on the remote system, for example
83
+ # 'C:/Windows/Temp'
84
+ # @return [String] Full path to the temp directory
85
+ def temp_dir
86
+ @guest_temp ||= begin
87
+ (@service.create_executor { |e| e.run_cmd('echo %TEMP%') }).stdout.chomp.gsub('\\', '/')
88
+ end
89
+ end
90
+
91
+ # Upload one or more local files and directories to a remote directory
92
+ # @example copy a single file to a winrm endpoint
93
+ #
94
+ # file_manager.upload('/Users/sneal/myfile.txt', 'c:/foo/myfile.txt')
95
+ #
96
+ # @example copy a single directory to a winrm endpoint
97
+ #
98
+ # file_manager.upload('c:/dev/my_dir', '$env:AppData')
99
+ #
100
+ # @param [String] A path to a local directory or file that will be copied
101
+ # to the remote Windows box.
102
+ # @param [String] The target directory or file
103
+ # This path may contain powershell style environment variables
104
+ # @yieldparam [Fixnum] Number of bytes copied in current payload sent to the winrm endpoint
105
+ # @yieldparam [Fixnum] The total number of bytes to be copied
106
+ # @yieldparam [String] Path of file being copied
107
+ # @yieldparam [String] Target path on the winrm endpoint
108
+ # @return [Fixnum] The total number of bytes copied
109
+ def upload(local_path, remote_path, &block)
110
+ @service.create_executor do |executor|
111
+ file_transporter ||= WinRM::FS::Core::FileTransporter.new(executor)
112
+ file_transporter.upload(local_path, remote_path, &block)[0]
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end