kitchen-syncgz 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []