kitchen-syncgz 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c8f79965af43b32cde2ac34b80758aef7ecbfe5ceb54324957a7d0e13b6c6559
4
+ data.tar.gz: 47aafaabb8d30a8f1fb52737c1c0767f7017ae481b373965b684978eb051bced
5
+ SHA512:
6
+ metadata.gz: dc9c1f538cc5aba4d76e58df4432e1e351fd1fea8d4b40e2d741f04218b173ffe85ba69cf2cd22528d21b08dd72f162c0c5d8387146c3488d0dafe76e587872e
7
+ data.tar.gz: 55d9fb8b1555e55ca31a7554f7007975acefbdc7520d9cdd100af86e37e5e25ac5907df781d442dce0a560d415ffef67e54b028f93de6a910e1faaf34c9c8819
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ *.gem
3
+ .idea/
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v1.0.0
2
+
3
+ Initial release!
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ #
2
+ # Author:: Igal Shprincis <igal.shprincis@gmail.com>
3
+ #
4
+ # Copyright 2019, Igal Shprincis
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ source 'http://rubygems.org'
20
+
21
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ kitchen-syncgz
2
+ ============
3
+
4
+ kitchen-syncgz provides alternate file transfer implementations for test-kitchen,
5
+ most of which are faster than the default, thus speeding up your test runs.
6
+
7
+ This project is a fork of [kitchen-sync](https://github.com/coderanger/kitchen-sync), the key enhancement of kitchen-syncgz is that it archives directories before transferring files to the target machine, which helps to overcome latency related issues when transferring a large number of small files. The archiving enhancement is based on the amazing work done by [Roy Reznik](https://github.com/coderanger/kitchen-sync/pull/31).
8
+
9
+ Quick Start
10
+ -----------
11
+
12
+ Run `chef gem install kitchen-syncgz` and then set your transport to `sftpgz`:
13
+
14
+ ```
15
+ transport:
16
+ name: sftpgz
17
+ ```
18
+
19
+ Available Transfer Methods
20
+ --------------------------
21
+
22
+ ### `sftpgz`
23
+
24
+ The default mode uses SFTP for file transfers, as well as a helper script to
25
+ avoid recopying files that are already present on the test host. If SFTP is
26
+ disabled, this will automatically fall back to the SCP mode.
27
+
28
+ By default this will use the Chef omnibus Ruby, you can customize the path to
29
+ Ruby via `ruby_path`:
30
+
31
+ ```
32
+ transport:
33
+ name: sftpgz
34
+ ruby_path: /usr/bin/ruby
35
+ ```
36
+
37
+
38
+ License
39
+ -------
40
+
41
+ Copyright 2019, Igal Shprincis
42
+
43
+ Licensed under the Apache License, Version 2.0 (the "License");
44
+ you may not use this file except in compliance with the License.
45
+ You may obtain a copy of the License at
46
+
47
+ http://www.apache.org/licenses/LICENSE-2.0
48
+
49
+ Unless required by applicable law or agreed to in writing, software
50
+ distributed under the License is distributed on an "AS IS" BASIS,
51
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
52
+ See the License for the specific language governing permissions and
53
+ limitations under the License.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env rake
2
+ #
3
+ # Author:: Igal Shprincis <igal.shprincis@gmail.com>
4
+ #
5
+ # Copyright 2019, Igal Shprincis
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
+
20
+ require 'bundler/gem_tasks'
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kitchen-syncgz/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'kitchen-syncgz'
8
+ spec.version = KitchenSyncGZ::VERSION
9
+ spec.authors = ['Igal Shprincis']
10
+ spec.email = ['igal.shprincis@gmail.com']
11
+ spec.description = 'Improved file transfers for for test-kitchen'
12
+ spec.summary = spec.description
13
+ spec.homepage = 'https://github.com/igal-s/kitchen-syncgz'
14
+ spec.license = 'Apache 2.0'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = []
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'net-sftp'
22
+ spec.add_dependency 'test-kitchen', '>= 1.0.0'
23
+
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake'
26
+ end
@@ -0,0 +1,20 @@
1
+ #
2
+ # Copyright 2019, Igal Shprincis
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ class KitchenSyncGZ
19
+ autoload :VERSION, 'kitchen-syncgz/version'
20
+ end
@@ -0,0 +1,36 @@
1
+ #
2
+ # Author:: Igal Shprincis <igal.shprincis@gmail.com>
3
+ #
4
+ # Copyright 2019, Igal Shprincis
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'json'
20
+ require 'digest/sha1'
21
+
22
+ glob_path = base = ARGV.first
23
+ glob_path = File.join(glob_path, '**', '*') if File.directory?(glob_path)
24
+ d = Digest::SHA1.new
25
+ STDOUT.write(
26
+ Dir.glob(glob_path, File::FNM_PATHNAME | File::FNM_DOTMATCH).inject({}) do |memo, path|
27
+ rel_path = path[base.length..-1]
28
+ if File.file?(path) && File.readable?(path)
29
+ d.reset
30
+ memo[rel_path] = d.file(path).hexdigest
31
+ elsif File.directory?(path)
32
+ memo[rel_path] = true
33
+ end
34
+ memo
35
+ end.to_json
36
+ )
@@ -0,0 +1,34 @@
1
+ #
2
+ # Copyright 2019, Igal Shprincis
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module Kitchen
18
+ # Monkey patch to prevent the deletion of everything
19
+ module Provisioner
20
+ class ChefBase < Base
21
+
22
+ old_init_command = instance_method(:init_command)
23
+
24
+ define_method(:init_command) do
25
+ if defined?(Kitchen::Transport::Sftpgz) && instance.transport.is_a?(Kitchen::Transport::Sftpgz)
26
+ "mkdir -p #{config[:root_path]}"
27
+ else
28
+ old_init_command.bind(self).()
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ #
2
+ # Copyright 2019, Igal Shprincis
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ class KitchenSyncGZ
19
+ VERSION = '1.0.0'
20
+ end
@@ -0,0 +1,303 @@
1
+ #
2
+ # Copyright 2019, Igal Shprincis
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'benchmark'
18
+ require 'digest/sha1'
19
+ require 'json'
20
+
21
+ require 'kitchen/transport/ssh'
22
+ require 'net/sftp'
23
+
24
+ require 'kitchen-syncgz/core_ext'
25
+
26
+ require 'rubygems'
27
+ require 'rubygems/package'
28
+ require 'zlib'
29
+ require 'fileutils'
30
+
31
+ module Kitchen
32
+ module Transport
33
+ class Sftpgz < Ssh
34
+ CHECKSUMS_PATH = File.expand_path('../../../kitchen-syncgz/checksums.rb', __FILE__)
35
+ CHECKSUMS_HASH = Digest::SHA1.file(CHECKSUMS_PATH)
36
+ CHECKSUMS_REMOTE_PATH = "/tmp/checksums-#{CHECKSUMS_HASH}.rb" # This won't work on Windows targets
37
+ MAX_TRANSFERS = 64
38
+
39
+ default_config :ruby_path, '/opt/chef/embedded/bin/ruby'
40
+
41
+ # Copy-pasta from Ssh#create_new_connection because I need the SFTP
42
+ # connection class.
43
+ # Tracked in https://github.com/test-kitchen/test-kitchen/pull/726
44
+ def create_new_connection(options, &block)
45
+ if @connection
46
+ logger.debug("[SSH] shutting previous connection #{@connection}")
47
+ @connection.close
48
+ end
49
+
50
+ @connection_options = options
51
+ @connection = self.class::Connection.new(config, options, &block)
52
+ end
53
+
54
+ class Connection < Ssh::Connection
55
+ def initialize(config, options, &block)
56
+ @config = config
57
+ super(options, &block)
58
+ end
59
+
60
+ # Wrap Ssh::Connection#close to also shut down the SFTP connection.
61
+ def close
62
+ if @sftp_session
63
+ logger.debug("[SFTP] closing connection to #{self}")
64
+ begin
65
+ sftp_session.close_channel
66
+ rescue Net::SSH::Disconnect
67
+ # Welp, we tried.
68
+ rescue IOError
69
+ # Can happen with net-ssh 4.x, no idea why.
70
+ # See https://github.com/net-ssh/net-ssh/pull/493
71
+ end
72
+ end
73
+ ensure
74
+ @sftp_session = nil
75
+ # Make sure we can turn down the session even if closing the channels
76
+ # fails in the middle because of a remote disconnect.
77
+ saved_session = @session
78
+ begin
79
+ super
80
+ rescue Net::SSH::Disconnect
81
+ # Boooo zlib warnings.
82
+ saved_session.transport.close if saved_session
83
+ end
84
+ end
85
+
86
+ def upload(locals, remote)
87
+ Array(locals).each do |local|
88
+ full_remote = File.join(remote, File.basename(local))
89
+ options = {
90
+ recursive: File.directory?(local),
91
+ purge: File.basename(local) != 'cache',
92
+ }
93
+ recursive = File.directory?(local)
94
+ time = Benchmark.realtime do
95
+ sftp_upload!(local, full_remote, options)
96
+ end
97
+ logger.info("[SFTP] Time taken to upload #{local} to #{self}:#{full_remote}: %.2f sec" % time)
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def sftp_upload!(local, remote, recursive: true, purge: true)
104
+ # Fast path check, if the remote path doesn't exist at all we just run a direct transfer
105
+ unless safe_stat(remote)
106
+ logger.debug("[SFTP] Fast path upload from #{local} to #{remote}")
107
+ sftp_session.mkdir!(remote) if recursive
108
+ gzip_uploaded = false
109
+ if File.directory?(local)
110
+ logger.debug("[SFTP] Attempting to upload gzip of folder")
111
+ # tar.gz folder
112
+ temp_file_name = 'xfer_tmp.tar.gz'
113
+ gzipped_file_path = File.join(local, temp_file_name)
114
+ gzipped_data = gzip(tar(local), gzipped_file_path)
115
+ # Upload tar.gz to remote
116
+ remote_path = "#{remote}/#{temp_file_name}"
117
+ sftp_session.upload!(gzipped_file_path, remote_path, requests: MAX_TRANSFERS)
118
+ # Unzip tar.gz @ remote
119
+ exit_code = execute_with_exit_code("cd #{remote} && tar -zxvf #{temp_file_name}")
120
+ # Cleanup
121
+ sftp_session.remove(remote_path)
122
+ # Validate success
123
+ gzip_uploaded = exit_code == 0
124
+ end
125
+ if not gzip_uploaded
126
+ sftp_session.upload!(local, remote, requests: MAX_TRANSFERS)
127
+ end
128
+ return
129
+ end
130
+ # Get checksums for existing files on the remote side.
131
+ logger.debug("[SFTP] Slow path upload from #{local} to #{remote}")
132
+ copy_checksums_script!
133
+ checksum_cmd = "#{@config[:ruby_path]} #{CHECKSUMS_REMOTE_PATH} #{remote}"
134
+ logger.debug("[SFTP] Running #{checksum_cmd}")
135
+ checksums = JSON.parse(session.exec!(checksum_cmd))
136
+ # Sync files that have changed.
137
+ files_to_upload(checksums, local, recursive).each do |rel_path|
138
+ upload_file(checksums, local, remote, rel_path)
139
+ end
140
+ purge_files(checksums, remote) if purge
141
+ # Wait until all xfers are complete.
142
+ sftp_loop(0)
143
+ end
144
+
145
+ # Bug fix for session.loop never terminating if there is an SFTP conn active
146
+ # since as far as it is concerned there is still active stuff.
147
+ # This function is Copyright Fletcher Nichol
148
+ # Tracked in https://github.com/test-kitchen/test-kitchen/pull/724
149
+ def execute_with_exit_code(command)
150
+ exit_code = nil
151
+ closed = false
152
+ session.open_channel do |channel|
153
+
154
+ channel.request_pty
155
+
156
+ channel.exec(command) do |_ch, _success|
157
+
158
+ channel.on_data do |_ch, data|
159
+ logger << data
160
+ end
161
+
162
+ channel.on_extended_data do |_ch, _type, data|
163
+ logger << data
164
+ end
165
+
166
+ channel.on_request("exit-status") do |_ch, data|
167
+ exit_code = data.read_long
168
+ end
169
+
170
+ channel.on_close do |ch| # This block is new.
171
+ closed = true
172
+ end
173
+ end
174
+ end
175
+ session.loop { exit_code.nil? && !closed } # THERE IS A CHANGE ON THIS LINE, PAY ATTENTION!!!!!!
176
+ exit_code
177
+ end
178
+
179
+ # Create the SFTP session and block until it is ready.
180
+ #
181
+ # @return [Net::SFTP::Session]
182
+ def sftp_session
183
+ @sftp_session ||= session.sftp
184
+ end
185
+
186
+ # Return if the path exists (because net::sftp uses exceptions for that
187
+ # and it makes code gross) and also raise an exception if the path is a
188
+ # symlink.
189
+ #
190
+ # @param path [String] Remote path to check.
191
+ # @return [Boolean]
192
+ def safe_stat(path)
193
+ stat = sftp_session.lstat!(path)
194
+ raise "#{path} is a symlink, possible security threat, bailing out" if stat.symlink?
195
+ true
196
+ rescue Net::SFTP::StatusException
197
+ false
198
+ end
199
+
200
+ # Upload the checksum script if needed.
201
+ #
202
+ # @return [void]
203
+ def copy_checksums_script!
204
+ # Fast path because upload itself is called multiple times.
205
+ return if @checksums_copied
206
+ # Only try to transfer the script if it isn't present. a stat takes about
207
+ # 1/3rd the time of the transfer, so worst case here is still okay.
208
+ sftp_session.upload!(CHECKSUMS_PATH, CHECKSUMS_REMOTE_PATH) unless safe_stat(CHECKSUMS_REMOTE_PATH)
209
+ @checksums_copied = true
210
+ end
211
+
212
+ def files_to_upload(checksums, local, recursive)
213
+ glob_path = if recursive
214
+ File.join(local, '**', '*')
215
+ else
216
+ local
217
+ end
218
+ pending = []
219
+ Dir.glob(glob_path, File::FNM_PATHNAME | File::FNM_DOTMATCH).each do |path|
220
+ next unless File.file?(path)
221
+ rel_path = path[local.length..-1]
222
+ remote_hash = checksums.delete(rel_path)
223
+ pending << rel_path unless remote_hash && remote_hash == Digest::SHA1.file(path).hexdigest
224
+ end
225
+ pending
226
+ end
227
+
228
+ def upload_file(checksums, local, remote, rel_path)
229
+ parts = rel_path.split('/')
230
+ parts.pop # Drop the filename since we are only checking dirs
231
+ parts_to_check = []
232
+ until parts.empty?
233
+ parts_to_check << parts.shift
234
+ path_to_check = parts_to_check.join('/')
235
+ unless checksums[path_to_check]
236
+ logger.debug("[SFTP] Creating directory #{remote}#{path_to_check}")
237
+ add_xfer(sftp_session.mkdir("#{remote}#{path_to_check}"))
238
+ checksums[path_to_check] = true
239
+ end
240
+ end
241
+ logger.debug("[SFTP] Uploading #{local}#{rel_path} to #{remote}#{rel_path}")
242
+ add_xfer(sftp_session.upload("#{local}#{rel_path}", "#{remote}#{rel_path}"))
243
+ end
244
+
245
+ def purge_files(checksums, remote)
246
+ checksums.each do |key, value|
247
+ # Check if the file was uploaded in #upload_file.
248
+ if value != true
249
+ logger.debug("[SFTP] Removing #{remote}#{key}")
250
+ add_xfer(sftp_session.remove("#{remote}#{key}"))
251
+ end
252
+ end
253
+ end
254
+
255
+ def sftp_xfers
256
+ @sftp_xfers ||= []
257
+ end
258
+
259
+ def add_xfer(xfer)
260
+ sftp_xfers << xfer
261
+ sftp_loop
262
+ end
263
+
264
+ def sftp_loop(n_xfers=MAX_TRANSFERS)
265
+ sftp_session.loop do
266
+ sftp_xfers.delete_if {|x| !(x.is_a?(Net::SFTP::Request) ? x.pending? : x.active?) } # Purge any completed operations, which has two different APIs for some reason
267
+ sftp_xfers.length > n_xfers # Run until we have fewer than max
268
+ end
269
+ end
270
+
271
+ def tar(path)
272
+ tarfile = StringIO.new("")
273
+ Gem::Package::TarWriter.new(tarfile) do |tar|
274
+ Dir[File.join(path, "**/*")].each do |file|
275
+ mode = File.stat(file).mode
276
+ relative_file = file.sub /^#{Regexp::escape path}\/?/, ''
277
+
278
+ if File.directory?(file)
279
+ tar.mkdir relative_file, mode
280
+ else
281
+ tar.add_file relative_file, mode do |tf|
282
+ File.open(file, "rb") { |f| tf.write f.read }
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ tarfile.rewind
289
+ tarfile
290
+ end
291
+
292
+ # gzips the underlying string in the given StringIO,
293
+ # returning a new StringIO representing the
294
+ # compressed file.
295
+ def gzip(tarfile, output_file)
296
+ z = Zlib::GzipWriter.open(output_file)
297
+ z.write tarfile.read
298
+ z.close
299
+ end
300
+ end
301
+ end
302
+ end
303
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitchen-syncgz
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Igal Shprincis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-sftp
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-kitchen
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Improved file transfers for for test-kitchen
70
+ email:
71
+ - igal.shprincis@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - CHANGELOG.md
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - kitchen-syncgz.gemspec
83
+ - lib/kitchen-syncgz.rb
84
+ - lib/kitchen-syncgz/checksums.rb
85
+ - lib/kitchen-syncgz/core_ext.rb
86
+ - lib/kitchen-syncgz/version.rb
87
+ - lib/kitchen/transport/sftpgz.rb
88
+ homepage: https://github.com/igal-s/kitchen-syncgz
89
+ licenses:
90
+ - Apache 2.0
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.7.6
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Improved file transfers for for test-kitchen
112
+ test_files: []