vagrant-unbundled 2.2.6.1 → 2.2.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +11 -9
  3. data/vagrant.gemspec +1 -1
  4. data/vendor/bundle/ruby/2.6.0/bundler/gems/vagrant-spec-abfc34474d12/vagrant-spec.gemspec +1 -1
  5. data/vendor/bundle/ruby/2.6.0/gems/erubi-1.9.0/CHANGELOG +79 -0
  6. data/vendor/bundle/ruby/2.6.0/gems/erubi-1.9.0/MIT-LICENSE +21 -0
  7. data/vendor/bundle/ruby/2.6.0/gems/erubi-1.9.0/README.rdoc +109 -0
  8. data/vendor/bundle/ruby/2.6.0/gems/erubi-1.9.0/Rakefile +78 -0
  9. data/vendor/bundle/ruby/2.6.0/gems/erubi-1.9.0/lib/erubi/capture_end.rb +52 -0
  10. data/vendor/bundle/ruby/2.6.0/gems/erubi-1.9.0/lib/erubi.rb +211 -0
  11. data/vendor/bundle/ruby/2.6.0/gems/erubi-1.9.0/test/test.rb +780 -0
  12. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/README.md +354 -0
  13. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/Rakefile +18 -0
  14. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/TODO +15 -0
  15. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/central_directory.rb +208 -0
  16. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/compressor.rb +9 -0
  17. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/constants.rb +63 -0
  18. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/encryption.rb +11 -0
  19. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/null_encryption.rb +43 -0
  20. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/traditional_encryption.rb +99 -0
  21. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/decompressor.rb +13 -0
  22. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/deflater.rb +34 -0
  23. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/dos_time.rb +48 -0
  24. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/entry.rb +700 -0
  25. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/entry_set.rb +86 -0
  26. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/errors.rb +18 -0
  27. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/generic.rb +43 -0
  28. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/ntfs.rb +90 -0
  29. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/old_unix.rb +44 -0
  30. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/universal_time.rb +47 -0
  31. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/unix.rb +37 -0
  32. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/zip64.rb +68 -0
  33. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/zip64_placeholder.rb +15 -0
  34. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field.rb +101 -0
  35. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/file.rb +443 -0
  36. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/filesystem.rb +627 -0
  37. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/inflater.rb +66 -0
  38. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/input_stream.rb +173 -0
  39. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras/abstract_input_stream.rb +111 -0
  40. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras/abstract_output_stream.rb +43 -0
  41. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras.rb +36 -0
  42. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_compressor.rb +15 -0
  43. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_decompressor.rb +27 -0
  44. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_input_stream.rb +10 -0
  45. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/output_stream.rb +189 -0
  46. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/pass_thru_compressor.rb +23 -0
  47. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/pass_thru_decompressor.rb +40 -0
  48. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/streamable_directory.rb +15 -0
  49. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/streamable_stream.rb +56 -0
  50. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/version.rb +3 -0
  51. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip.rb +71 -0
  52. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example.rb +81 -0
  53. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example_filesystem.rb +31 -0
  54. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example_recursive.rb +54 -0
  55. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/samples/gtk_ruby_zip.rb +84 -0
  56. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/samples/qtzip.rb +92 -0
  57. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/samples/write_simple.rb +12 -0
  58. data/vendor/bundle/ruby/2.6.0/gems/rubyzip-2.0.0/samples/zipfind.rb +66 -0
  59. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/LICENSE +202 -0
  60. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/README.md +276 -0
  61. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/bin/rwinrm +90 -0
  62. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/connection.rb +84 -0
  63. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/connection_opts.rb +90 -0
  64. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/exceptions.rb +88 -0
  65. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/http/response_handler.rb +127 -0
  66. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/http/transport.rb +462 -0
  67. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/http/transport_factory.rb +64 -0
  68. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/output.rb +58 -0
  69. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/create_pipeline.xml.erb +167 -0
  70. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/fragment.rb +68 -0
  71. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/init_runspace_pool.xml.erb +224 -0
  72. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message.rb +128 -0
  73. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data/base.rb +47 -0
  74. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data/error_record.rb +66 -0
  75. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data/pipeline_host_call.rb +30 -0
  76. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data/pipeline_output.rb +48 -0
  77. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data/pipeline_state.rb +38 -0
  78. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data/runspacepool_host_call.rb +30 -0
  79. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data/runspacepool_state.rb +37 -0
  80. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data/session_capability.rb +34 -0
  81. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_data.rb +40 -0
  82. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_defragmenter.rb +62 -0
  83. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_factory.rb +86 -0
  84. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/message_fragmenter.rb +58 -0
  85. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/powershell_output_decoder.rb +142 -0
  86. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/receive_response_reader.rb +95 -0
  87. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/session_capability.xml.erb +7 -0
  88. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/psrp/uuid.rb +39 -0
  89. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/shells/base.rb +187 -0
  90. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/shells/cmd.rb +63 -0
  91. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/shells/power_shell.rb +206 -0
  92. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/shells/retryable.rb +44 -0
  93. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/shells/shell_factory.rb +56 -0
  94. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/version.rb +5 -0
  95. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/base.rb +57 -0
  96. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/cleanup_command.rb +60 -0
  97. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/close_shell.rb +49 -0
  98. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/command.rb +100 -0
  99. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/command_output.rb +75 -0
  100. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/command_output_decoder.rb +54 -0
  101. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/configuration.rb +44 -0
  102. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/create_pipeline.rb +64 -0
  103. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/create_shell.rb +115 -0
  104. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/header.rb +213 -0
  105. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/init_runspace_pool.rb +96 -0
  106. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/iso8601_duration.rb +58 -0
  107. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/keep_alive.rb +66 -0
  108. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/receive_response_reader.rb +128 -0
  109. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/send_data.rb +66 -0
  110. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/soap.rb +49 -0
  111. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/wql_pull.rb +54 -0
  112. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/wql_query.rb +98 -0
  113. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm/wsmv/write_stdin.rb +86 -0
  114. data/vendor/bundle/ruby/2.6.0/gems/winrm-2.3.3/lib/winrm.rb +37 -0
  115. data/vendor/bundle/ruby/2.6.0/gems/winrm-elevated-1.1.2/LICENSE +202 -0
  116. data/vendor/bundle/ruby/2.6.0/gems/winrm-elevated-1.1.2/README.md +99 -0
  117. data/vendor/bundle/ruby/2.6.0/gems/winrm-elevated-1.1.2/lib/winrm/shells/elevated.rb +108 -0
  118. data/vendor/bundle/ruby/2.6.0/gems/winrm-elevated-1.1.2/lib/winrm-elevated/scripts/elevated_shell.ps1 +116 -0
  119. data/vendor/bundle/ruby/2.6.0/gems/winrm-elevated-1.1.2/lib/winrm-elevated.rb +17 -0
  120. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/LICENSE +202 -0
  121. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/README.md +82 -0
  122. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/bin/rwinrmcp +87 -0
  123. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/core/file_transporter.rb +569 -0
  124. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/core/tmp_zip.rb +178 -0
  125. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/exceptions.rb +29 -0
  126. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/file_manager.rb +158 -0
  127. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/scripts/check_files.ps1.erb +49 -0
  128. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/scripts/checksum.ps1.erb +13 -0
  129. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/scripts/create_dir.ps1.erb +6 -0
  130. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/scripts/delete.ps1.erb +6 -0
  131. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/scripts/download.ps1.erb +17 -0
  132. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/scripts/exists.ps1.erb +10 -0
  133. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/scripts/extract_files.ps1.erb +52 -0
  134. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs/scripts/scripts.rb +47 -0
  135. data/vendor/bundle/ruby/2.6.0/gems/winrm-fs-1.3.4/lib/winrm-fs.rb +29 -0
  136. data/vendor/bundle/ruby/2.6.0/specifications/erubi-1.9.0.gemspec +38 -0
  137. data/vendor/bundle/ruby/2.6.0/specifications/rubyzip-2.0.0.gemspec +45 -0
  138. data/vendor/bundle/ruby/2.6.0/specifications/winrm-2.3.3.gemspec +73 -0
  139. data/vendor/bundle/ruby/2.6.0/specifications/winrm-elevated-1.1.2.gemspec +50 -0
  140. data/vendor/bundle/ruby/2.6.0/specifications/winrm-fs-1.3.4.gemspec +58 -0
  141. data/version.txt +1 -1
  142. metadata +142 -6
@@ -0,0 +1,569 @@
1
+ # frozen_string_literal: false
2
+
3
+ #
4
+ # Author:: Fletcher (<fnichol@nichol.ca>)
5
+ #
6
+ # Copyright (C) 2015, Fletcher Nichol
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ require 'benchmark'
21
+ require 'csv'
22
+ require 'digest'
23
+ require 'securerandom'
24
+ require 'stringio'
25
+
26
+ require 'winrm/exceptions'
27
+ require 'winrm-fs/core/tmp_zip'
28
+
29
+ module WinRM
30
+ module FS
31
+ module Core
32
+ # Wrapped exception for any internally raised WinRM-related errors.
33
+ #
34
+ # @author Fletcher Nichol <fnichol@nichol.ca>
35
+ class FileTransporterFailed < ::WinRM::WinRMError; end
36
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/ClassLength
37
+
38
+ # Exception for the case where upload source contains more than one
39
+ # StringIO object, or a combination of file/directory paths and StringIO object
40
+ class UploadSourceError < StandardError
41
+ def initialize(msg = 'Only a single StringIO object may be uploaded.')
42
+ super
43
+ end
44
+ end
45
+
46
+ # Object which can upload one or more files or directories to a remote
47
+ # host over WinRM using PowerShell scripts and CMD commands. Note that
48
+ # this form of file transfer is *not* ideal and extremely costly on both
49
+ # the local and remote sides. Great pains are made to minimize round
50
+ # trips to the remote host and to minimize the number of PowerShell
51
+ # sessions being invoked which can be 2 orders of magnitude more
52
+ # expensive than vanilla CMD commands.
53
+ #
54
+ # This object is supported by a `PowerShell` instance as it
55
+ # depends on the `#run` API contract.
56
+ #
57
+ # An optional logger can be supplied, assuming it can respond to the
58
+ # `#debug` and `#debug?` messages.
59
+ #
60
+ # @author Fletcher Nichol <fnichol@nichol.ca>
61
+ # @author Matt Wrock <matt@mattwrock.com>
62
+ class FileTransporter
63
+ # Creates a FileTransporter given a PowerShell object.
64
+ #
65
+ # @param shell [PowerShell] a winrm PowerShell object
66
+ def initialize(shell, opts = {})
67
+ @shell = shell
68
+ @logger = shell.logger
69
+ @id_generator = opts.fetch(:id_generator) { -> { SecureRandom.uuid } }
70
+ Zip.unicode_names = true
71
+ end
72
+
73
+ # Uploads a collection of files and/or directories to the remote host.
74
+ #
75
+ # **TODO Notes:**
76
+ # * options could specify zip mode, zip options, etc.
77
+ # * maybe option to set tmpfile base dir to override $env:PATH?
78
+ # * progress yields block like net-scp progress
79
+ # * final API: def upload(locals, remote, _options = {}, &_progress)
80
+ #
81
+ # @param locals [Array<String>,String,StringIO] one or more
82
+ # local file or directory paths, StringIO objects also accepted
83
+ # @param remote [String] the base destination path on the remote host
84
+ # @return [Hash] report hash, keyed by the local SHA1 digest
85
+ def upload(locals, remote)
86
+ files = nil
87
+ report = nil
88
+ remote = remote.to_s
89
+ elapsed1 = Benchmark.measure do
90
+ files = make_files_hash([locals].flatten, remote)
91
+ report = check_files(files)
92
+ merge_with_report!(files, report)
93
+ reconcile_destinations!(files)
94
+ end
95
+ total_size = total_base64_transfer_size(files)
96
+
97
+ elapsed2 = Benchmark.measure do
98
+ report = stream_upload_files(files) do |local_path, xfered|
99
+ yield xfered, total_size, local_path, remote if block_given?
100
+ end
101
+ merge_with_report!(files, report)
102
+ end
103
+
104
+ elapsed3 = Benchmark.measure do
105
+ report = extract_files(files)
106
+ merge_with_report!(files, report)
107
+ cleanup(files)
108
+ end
109
+
110
+ logger.debug(
111
+ "Uploaded #{files.keys.size} items " \
112
+ "dirty_check: #{duration(elapsed1.real)} " \
113
+ "stream_files: #{duration(elapsed2.real)} " \
114
+ "extract: #{duration(elapsed3.real)} " \
115
+ )
116
+
117
+ [total_size, files]
118
+ end
119
+
120
+ private
121
+
122
+ # @return [String] the Array pack template for Base64 encoding a stream
123
+ # of data
124
+ # @api private
125
+ BASE64_PACK = 'm0'.freeze
126
+
127
+ # @return [String] the directory where temporary upload artifacts are
128
+ # persisted
129
+ # @api private
130
+ TEMP_UPLOAD_DIRECTORY = '$env:TEMP\\winrm-upload'.freeze
131
+
132
+ # @return [#debug,#debug?] the logger
133
+ # @api private
134
+ attr_reader :logger
135
+
136
+ # @return [Winrm::Shells::Powershell] a WinRM Powershell shell
137
+ # @api private
138
+ attr_reader :shell
139
+
140
+ # @return [Integer] the maximum number of bytes to send per request
141
+ # when streaming a file. This is optimized to send as much data
142
+ # as allowed in a single PSRP fragment
143
+ # @api private
144
+ def max_encoded_write
145
+ @max_encoded_write ||= begin
146
+ empty_command = WinRM::PSRP::MessageFactory.create_pipeline_message(
147
+ '00000000-0000-0000-0000-000000000000',
148
+ '00000000-0000-0000-0000-000000000000',
149
+ stream_command('')
150
+ )
151
+ shell.max_fragment_blob_size - empty_command.bytes.length
152
+ end
153
+ end
154
+
155
+ # Examines the files and corrects the file destination if it is
156
+ # targeting an existing folder. In this case, the destination path
157
+ # will have the base name of the source file appended. This only
158
+ # applies to file uploads and not to folder uploads.
159
+ #
160
+ # @param files [Hash] files hash, keyed by the local SHA1 digest
161
+ # @return [Hash] a report hash, keyed by the local SHA1 digest
162
+ # @api private
163
+ def reconcile_destinations!(files)
164
+ files.each do |_, data|
165
+ data['dst'] = File.join(data['dst'], File.basename(data['src'])) if data['target_is_folder'] == 'True'
166
+ end
167
+ end
168
+
169
+ # Adds an entry to a files Hash (keyed by local SHA1 digest) for a
170
+ # directory. When a directory is added, a temporary Zip file is created
171
+ # containing the contents of the directory and any file-related data
172
+ # such as SHA1 digest, size, etc. will be referring to the Zip file.
173
+ #
174
+ # @param hash [Hash] hash to be mutated
175
+ # @param dir [String] directory path to be Zipped and added
176
+ # @param remote [String] path to destination on remote host
177
+ # @api private
178
+ def add_directory_hash!(hash, dir, remote)
179
+ logger.debug "creating hash for directory #{remote}"
180
+ zip_io = TmpZip.new(dir, logger)
181
+ zip_sha1 = sha1sum(zip_io.path)
182
+
183
+ hash[zip_sha1] = {
184
+ 'src' => dir,
185
+ 'src_zip' => zip_io.path.to_s,
186
+ 'zip_io' => zip_io,
187
+ 'tmpzip' => "#{TEMP_UPLOAD_DIRECTORY}\\tmpzip-#{zip_sha1}.zip",
188
+ 'dst' => "#{remote}\\#{File.basename(dir)}",
189
+ 'size' => File.size(zip_io.path)
190
+ }
191
+ end
192
+
193
+ # Adds an entry to a files Hash (keyed by local SHA1 digest) for a file.
194
+ #
195
+ # @param hash [Hash] hash to be mutated
196
+ # @param local [String, StringIO] file path or StringIO object
197
+ # @param remote [String] path to destination on remote host
198
+ # @api private
199
+ def add_file_hash!(hash, local, remote)
200
+ logger.debug "creating hash for file #{remote}"
201
+ hash[sha1sum(local)] = {
202
+ 'src' => local,
203
+ 'dst' => remote,
204
+ 'size' => local.is_a?(StringIO) ? local.size : File.size(local)
205
+ }
206
+ end
207
+
208
+ # Runs the check_files PowerShell script against a collection of
209
+ # destination path/SHA1 checksum pairs. The PowerShell script returns
210
+ # its results as a CSV-formatted report which is converted into a Ruby
211
+ # Hash.
212
+ #
213
+ # @param files [Hash] files hash, keyed by the local SHA1 digest
214
+ # @return [Hash] a report hash, keyed by the local SHA1 digest
215
+ # @api private
216
+ def check_files(files)
217
+ logger.debug 'Running check_files.ps1'
218
+ hash_file = check_files_ps_hash(files)
219
+ script = WinRM::FS::Scripts.render('check_files', hash_file: hash_file)
220
+ parse_response(shell.run(script))
221
+ end
222
+
223
+ # Constructs a collection of destination path/SHA1 checksum pairs as a
224
+ # String representation of the contents of a PowerShell Hash Table.
225
+ #
226
+ # @param files [Hash] files hash, keyed by the local SHA1 digest
227
+ # @return [String] the inner contents of a PowerShell Hash Table
228
+ # @api private
229
+ def check_files_ps_hash(files)
230
+ hash = files.map do |sha1, data|
231
+ [
232
+ sha1,
233
+ {
234
+ 'target' => data.fetch('tmpzip', data['dst']),
235
+ 'src_basename' => data['src'].is_a?(StringIO) ? data['dst'] : File.basename(data['src']),
236
+ 'dst' => data['dst']
237
+ }
238
+ ]
239
+ end
240
+ ps_hash(Hash[hash])
241
+ end
242
+
243
+ # Performs any final cleanup on the report Hash and removes any
244
+ # temporary files/resources used in the upload task.
245
+ #
246
+ # @param files [Hash] a files hash
247
+ # @api private
248
+ def cleanup(files)
249
+ files.select { |_, data| data.key?('zip_io') }.each do |sha1, data|
250
+ data.fetch('zip_io').unlink
251
+ files.fetch(sha1).delete('zip_io')
252
+ logger.debug "Cleaned up src_zip #{data['src_zip']}"
253
+ end
254
+ end
255
+
256
+ # Runs the extract_files PowerShell script against a collection of
257
+ # temporary file/destination path pairs. The PowerShell script returns
258
+ # its results as a CSV-formatted report which is converted into a Ruby
259
+ # Hash. The script will not be invoked if there are no zip files
260
+ # present in the incoming files Hash.
261
+ #
262
+ # @param files [Hash] files hash, keyed by the local SHA1 digest
263
+ # @return [Hash] a report hash, keyed by the local SHA1 digest
264
+ # @api private
265
+ def extract_files(files)
266
+ extracted_files = extract_files_ps_hash(files)
267
+
268
+ if extracted_files == ps_hash({})
269
+ logger.debug 'No remote files to extract, skipping'
270
+ {}
271
+ else
272
+ logger.debug 'Running extract_files.ps1'
273
+ script = WinRM::FS::Scripts.render('extract_files', hash_file: extracted_files)
274
+
275
+ parse_response(shell.run(script))
276
+ end
277
+ end
278
+
279
+ # Constructs a collection of temporary file/destination path pairs for
280
+ # all zipped folders as a String representation of the contents of a
281
+ # PowerShell Hash Table.
282
+ #
283
+ # @param files [Hash] files hash, keyed by the local SHA1 digest
284
+ # @return [String] the inner contents of a PowerShell Hash Table
285
+ # @api private
286
+ def extract_files_ps_hash(files)
287
+ file_data = files.select { |_, data| data.key?('tmpzip') }
288
+
289
+ result = file_data.map do |sha1, data|
290
+ val = { 'dst' => data['dst'] }
291
+ val['tmpzip'] = data['tmpzip'] if data['tmpzip']
292
+
293
+ [sha1, val]
294
+ end
295
+
296
+ ps_hash(Hash[result])
297
+ end
298
+
299
+ # Returns a formatted string representing a duration in seconds.
300
+ #
301
+ # @param total [Integer] the total number of seconds
302
+ # @return [String] a formatted string of the form (XmYY.00s)
303
+ def duration(total)
304
+ total = 0 if total.nil?
305
+ minutes = (total / 60).to_i
306
+ seconds = (total - (minutes * 60))
307
+ format('(%dm%.2fs)', minutes, seconds)
308
+ end
309
+
310
+ # Contructs a Hash of files or directories, keyed by the local SHA1
311
+ # digest. Each file entry has a source and destination set, at a
312
+ # minimum.
313
+ #
314
+ # @param locals [Array<String,StringIO>] a collection of local files,
315
+ # directories or StringIO objects
316
+ # @param remote [String] the base destination path on the remote host
317
+ # @return [Hash] files hash, keyed by the local SHA1 digest
318
+ # @api private
319
+ def make_files_hash(locals, remote)
320
+ hash = {}
321
+ check_locals_array(locals)
322
+ locals.each do |local|
323
+ if local.is_a?(StringIO)
324
+ add_file_hash!(hash, local, remote)
325
+ else
326
+ local = local.to_s
327
+ expanded = File.expand_path(local)
328
+ expanded += local[-1] if local.end_with?('/', '\\')
329
+ if File.file?(expanded)
330
+ add_file_hash!(hash, expanded, remote)
331
+ elsif File.directory?(expanded)
332
+ add_directory_hash!(hash, expanded, remote)
333
+ else
334
+ raise Errno::ENOENT, "No such file or directory #{expanded}"
335
+ end
336
+ end
337
+ end
338
+ hash
339
+ end
340
+
341
+ # Ensure that only a single StringIO object is uploaded at a time
342
+ # This is necessary because the contents of the buffer will be written
343
+ # to the destination.
344
+ # @param locals [Array<String,StringIO>] a collection of local files,
345
+ # directories or StringIO objects
346
+ # @api private
347
+ def check_locals_array(locals)
348
+ string_io = false
349
+ path = false
350
+ locals.each do |local|
351
+ raise UploadSourceError if string_io
352
+
353
+ if local.is_a?(StringIO)
354
+ string_io = true
355
+ else
356
+ path = true
357
+ end
358
+ raise UploadSourceError if string_io && path
359
+ end
360
+ end
361
+
362
+ # @return [String] the SHA1 digest of a local file or StringIO
363
+ # @api private
364
+ def sha1sum(local)
365
+ if local.is_a?(StringIO)
366
+ Digest::SHA1.hexdigest(local.string)
367
+ else
368
+ Digest::SHA1.file(local).hexdigest
369
+ end
370
+ end
371
+
372
+ # Destructively merges a report Hash into an existing files Hash.
373
+ # **Note:** this method mutates the files Hash.
374
+ #
375
+ # @param files [Hash] files hash, keyed by the local SHA1 digest
376
+ # @param report [Hash] report hash, keyed by the local SHA1 digest
377
+ # @api private
378
+ def merge_with_report!(files, report)
379
+ files.merge!(report) { |_, oldval, newval| oldval.merge(newval) }
380
+ end
381
+
382
+ # @param depth [Integer] number of padding characters (default: `0`)
383
+ # @return [String] a whitespace padded string of the given length
384
+ # @api private
385
+ def pad(depth = 0)
386
+ ' ' * depth
387
+ end
388
+
389
+ # Parses response of a PowerShell script or CMD command which contains
390
+ # a CSV-formatted document in the standard output stream.
391
+ #
392
+ # @param output [WinRM::Output] output object with stdout, stderr, and
393
+ # exit code
394
+ # @return [Hash] report hash, keyed by the local SHA1 digest
395
+ # @api private
396
+ def parse_response(output)
397
+ exitcode = output.exitcode
398
+ stderr = output.stderr
399
+
400
+ if exitcode != 0
401
+ raise FileTransporterFailed, "[#{self.class}] Upload failed " \
402
+ "(exitcode: #{exitcode})\n#{stderr}"
403
+ elsif stderr != '\r\n' && stderr != ''
404
+ raise FileTransporterFailed, "[#{self.class}] Upload failed " \
405
+ "(exitcode: 0), but stderr present\n#{stderr}"
406
+ end
407
+
408
+ logger.debug 'Parsing CSV Response'
409
+ logger.debug output.stdout
410
+
411
+ array = CSV.parse(output.stdout, headers: true).map(&:to_hash)
412
+ array.each { |h| h.each { |key, value| h[key] = nil if value == '' } }
413
+ Hash[array.map { |entry| [entry.fetch('src_sha1'), entry] }]
414
+ end
415
+
416
+ # Converts a Ruby hash into a PowerShell hash table, represented in a
417
+ # String.
418
+ #
419
+ # @param obj [Object] source Hash or object when used in recursive
420
+ # calls
421
+ # @param depth [Integer] padding depth, used in recursive calls
422
+ # (default: `0`)
423
+ # @return [String] a PowerShell hash table
424
+ # @api private
425
+ def ps_hash(obj, depth = 0)
426
+ if obj.is_a?(Hash)
427
+ obj.map do |k, v|
428
+ %(#{pad(depth + 2)}#{ps_hash(k)} = #{ps_hash(v, depth + 2)})
429
+ end.join(";\n").insert(0, "@{\n").insert(-1, "\n#{pad(depth)}}")
430
+ else
431
+ %("#{obj}")
432
+ end
433
+ end
434
+
435
+ # Uploads an IO stream to a Base64-encoded destination file.
436
+ #
437
+ # **Implementation Note:** Some of the code in this method may appear
438
+ # slightly too dense and while adding additional variables would help,
439
+ # the code is written very precisely to avoid unwanted allocations
440
+ # which will bloat the Ruby VM's object space (and memory footprint).
441
+ # The goal here is to stream potentially large files to a remote host
442
+ # while not loading the entire file into memory first, then Base64
443
+ # encoding it--duplicating the file in memory again.
444
+ #
445
+ # @param input_io [#read] a readable stream or object to be uploaded
446
+ # @param dest [String] path to the destination file on the remote host
447
+ # @return [Integer,Integer] the number of resulting upload chunks and
448
+ # the number of bytes transferred to the remote host
449
+ # @api private
450
+ def stream_upload(input_io, dest)
451
+ read_size = ((max_encoded_write - dest.length) / 4) * 3
452
+ chunk = 1
453
+ bytes = 0
454
+ # Do not freeze this string
455
+ buffer = ''
456
+ shell.run(<<-PS
457
+ $to = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("#{dest}")
458
+ $parent = Split-Path $to
459
+ if(!(Test-path $parent)) { mkdir $parent | Out-Null }
460
+ $fileStream = New-Object -TypeName System.IO.FileStream -ArgumentList @(
461
+ $to,
462
+ [system.io.filemode]::Create,
463
+ [System.io.FileAccess]::Write,
464
+ [System.IO.FileShare]::ReadWrite
465
+ )
466
+ # Powershell caches ScrpitBlocks in a dictionary
467
+ # keyed on the script block text. Thats just great
468
+ # unless the script is super large and called a gillion
469
+ # times like we might do. In such a case it will saturate the
470
+ # Large Object Heap and lead to Out Of Memory exceptions
471
+ # for large files or folders. So we call the internal method
472
+ # ClearScriptBlockCache to clear it.
473
+ $bindingFlags= [Reflection.BindingFlags] "NonPublic,Static"
474
+ $method = [scriptblock].GetMethod("ClearScriptBlockCache", $bindingFlags)
475
+ PS
476
+ )
477
+
478
+ while input_io.read(read_size, buffer)
479
+ bytes += (buffer.bytesize / 3 * 4)
480
+ shell.run(stream_command([buffer].pack(BASE64_PACK)))
481
+ logger.debug "Wrote chunk #{chunk} for #{dest}" if chunk % 25 == 0
482
+ chunk += 1
483
+ yield bytes if block_given?
484
+ end
485
+ shell.run('$fileStream.Dispose()')
486
+ buffer = nil # rubocop:disable Lint/UselessAssignment
487
+
488
+ [chunk - 1, bytes]
489
+ end
490
+
491
+ def stream_command(encoded_bytes)
492
+ <<-PS
493
+ if($method) { $method.Invoke($Null, $Null) }
494
+ $bytes=[Convert]::FromBase64String('#{encoded_bytes}')
495
+ $fileStream.Write($bytes, 0, $bytes.length)
496
+ PS
497
+ end
498
+
499
+ # Uploads a local file.
500
+ #
501
+ # @param src [String, StringIO] path to a local file or StringIO object
502
+ # @param dest [String] path to the file on the remote host
503
+ # @return [Integer,Integer] the number of resulting upload chunks and
504
+ # the number of bytes transferred to the remote host
505
+ # @api private
506
+ def stream_upload_file(src, dest, &block)
507
+ logger.debug "Uploading #{src} to #{dest}"
508
+ chunks = 0
509
+ bytes = 0
510
+ elapsed = Benchmark.measure do
511
+ if src.is_a?(StringIO)
512
+ chunks, bytes = stream_upload(src, dest, &block)
513
+ else
514
+ File.open(src, 'rb') do |io|
515
+ chunks, bytes = stream_upload(io, dest, &block)
516
+ end
517
+ end
518
+ end
519
+ logger.debug(
520
+ "Finished uploading #{src} to #{dest} " \
521
+ "(#{bytes.to_f / 1000} KB over #{chunks} chunks) " \
522
+ "in #{duration(elapsed.real)}"
523
+ )
524
+
525
+ [chunks, bytes]
526
+ end
527
+
528
+ # Uploads a collection of "dirty" files to the remote host as
529
+ # Base64-encoded temporary files. A "dirty" file is one which has the
530
+ # `"chk_dirty"` option set to `"True"` in the incoming files Hash.
531
+ #
532
+ # @param files [Hash] files hash, keyed by the local SHA1 digest
533
+ # @return [Hash] a report hash, keyed by the local SHA1 digest
534
+ # @api private
535
+ def stream_upload_files(files)
536
+ response = {}
537
+ files.each do |sha1, data|
538
+ src = data.fetch('src_zip', data['src'])
539
+ if data['chk_dirty'] == 'True'
540
+ response[sha1] = { 'dest' => data['tmpzip'] || data['dst'] }
541
+ chunks, bytes = stream_upload_file(src, data['tmpzip'] || data['dst']) do |xfered|
542
+ yield data['src'], xfered
543
+ end
544
+ response[sha1]['chunks'] = chunks
545
+ response[sha1]['xfered'] = bytes
546
+ else
547
+ logger.debug "File #{data['dst']} is up to date, skipping"
548
+ end
549
+ end
550
+ response
551
+ end
552
+
553
+ # Total by byte count to be transferred.
554
+ # Calculates count based on the sum of base64 encoded content size
555
+ # of all files base 64 that are dirty.
556
+ #
557
+ # @param files [Hash] files hash, keyed by the local SHA1 digest
558
+ # @return [Fixnum] total byte size
559
+ # @api private
560
+ def total_base64_transfer_size(files)
561
+ size = 0
562
+ files.values.each { |file| size += file['size'] if file['chk_dirty'] == 'True' }
563
+ size / 3 * 4
564
+ end
565
+ end
566
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/ClassLength
567
+ end
568
+ end
569
+ end