kitchen-sync 1.0.1 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a7e74c8634b238460e2128e167a09da25bd5cd5f
4
- data.tar.gz: dd830cb7438202527397586e9aea0f1239dc86a8
3
+ metadata.gz: f5195fe4fdfca7efd26e6cb39af8faa9886d8efd
4
+ data.tar.gz: 86330af015cee42ae0075b628194982aa7f22a6e
5
5
  SHA512:
6
- metadata.gz: 0917e5d947d2d581170d17bded14a8ffb979334a9ac78b67e047609ab54e604df1d86dc576c19309459629579155d9e192a6aa4c71cd3f0e03564199e90ac5d1
7
- data.tar.gz: 1fb8e8de36b020d04e57fad7a2dc2a73202faf1cb5af983ce2ef4b68f7c907875b5716b4efe7000d6cae39f4db6e746e8aadc356b2bdb4ea97fac95a3dfb8eec
6
+ metadata.gz: b519464648300eb50bcfbc2cc00edb2a3bd334dfe8149b870e5767c9ed1fb217c90ec87e10b94f0844bff15211570f8c4f3760d9f3bcc276eedeb989e48280b9
7
+ data.tar.gz: fbae6448d755ce2de522fc172b8db7d6355622c9e9b85075d4d003ceaf240304736583e3678180c13b4892d9a82f0c102a70ae688c9c967980916d953a8180ce
@@ -16,6 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
+
19
20
  class KitchenSync
20
- VERSION = '1.0.1'
21
+ VERSION = '1.1.0'
21
22
  end
@@ -0,0 +1,218 @@
1
+ #
2
+ # Copyright 2014-2015, Noah Kantrowitz
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 'kitchen/transport/ssh'
18
+
19
+
20
+ module Kitchen
21
+ module Transport
22
+ class Sftp < Ssh
23
+ CHECKSUMS_PATH = File.expand_path('../../../kitchen-sync/checksums.rb', __FILE__)
24
+ CHECKSUMS_HASH = Digest::SHA1.file(CHECKSUMS_PATH)
25
+ CHECKSUMS_REMOTE_PATH = "/tmp/checksums-#{CHECKSUMS_HASH}.rb" # This won't work on Windows targets
26
+ MAX_TRANSFERS = 64
27
+
28
+ # Copy-pasta from Ssh#create_new_connection because I need the SFTP
29
+ # connection class.
30
+ # Tracked in https://github.com/test-kitchen/test-kitchen/pull/726
31
+ def create_new_connection(options, &block)
32
+ if @connection
33
+ logger.debug("[SSH] shutting previous connection #{@connection}")
34
+ @connection.close
35
+ end
36
+
37
+ @connection_options = options
38
+ @connection = self.class::Connection.new(options, &block)
39
+ end
40
+
41
+ class Connection < Ssh::Connection
42
+ # Wrap Ssh::Connection#close to also shut down the SFTP connection.
43
+ def close
44
+ if @sftp_session
45
+ logger.debug("[SFTP] closing connection to #{self}")
46
+ sftp_session.close_channel
47
+ end
48
+ ensure
49
+ @sftp_session = nil
50
+ super
51
+ end
52
+
53
+ def upload(locals, remote)
54
+ Array(locals).each do |local|
55
+ full_remote = File.join(remote, File.basename(local))
56
+ recursive = File.directory?(local)
57
+ time = Benchmark.realtime do
58
+ sftp_upload!(local, full_remote, recursive)
59
+ end
60
+ logger.info("[SFTP] Time taken to upload #{local} to #{self}:#{full_remote}: %.2f sec" % time)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def sftp_upload!(local, remote, recursive)
67
+ # Fast path check, if the remote path doesn't exist at all we just run a direct transfer
68
+ unless safe_stat(remote)
69
+ logger.debug("[SFTP] Fast path upload from #{local} to #{remote}")
70
+ sftp_session.mkdir!(remote) if recursive
71
+ sftp_session.upload!(local, remote, requests: MAX_TRANSFERS)
72
+ return
73
+ end
74
+ # Get checksums for existing files on the remote side.
75
+ logger.debug("[SFTP] Slow path upload from #{local} to #{remote}")
76
+ copy_checksums_script!
77
+ checksum_cmd = "/opt/chef/embedded/bin/ruby #{CHECKSUMS_REMOTE_PATH} #{remote}"
78
+ logger.debug("[SFTP] Running #{checksum_cmd}")
79
+ checksums = JSON.parse(session.exec!(checksum_cmd))
80
+ # Sync files that have changed.
81
+ files_to_upload(checksums, local, recursive).each do |rel_path|
82
+ upload_file(checksums, local, remote, rel_path)
83
+ end
84
+ purge_files(checksums, remote)
85
+ # Wait until all xfers are complete.
86
+ sftp_loop(0)
87
+ end
88
+
89
+ # Bug fix for session.loop never terminating if there is an SFTP conn active
90
+ # since as far as it is concerned there is still active stuff.
91
+ # This function is Copyright Fletcher Nichol
92
+ # Tracked in https://github.com/test-kitchen/test-kitchen/pull/724
93
+ def execute_with_exit_code(command)
94
+ exit_code = nil
95
+ session.open_channel do |channel|
96
+
97
+ channel.request_pty
98
+
99
+ channel.exec(command) do |_ch, _success|
100
+
101
+ channel.on_data do |_ch, data|
102
+ logger << data
103
+ end
104
+
105
+ channel.on_extended_data do |_ch, _type, data|
106
+ logger << data
107
+ end
108
+
109
+ channel.on_request("exit-status") do |_ch, data|
110
+ exit_code = data.read_long
111
+ end
112
+ end
113
+ end
114
+ session.loop { exit_code.nil? } # THERE IS A CHANGE ON THIS LINE, PAY ATTENTION!!!!!!
115
+ exit_code
116
+ end
117
+
118
+ # Create the SFTP session and block until it is ready.
119
+ #
120
+ # @return [Net::SFTP::Session]
121
+ def sftp_session
122
+ @sftp_session ||= session.sftp
123
+ end
124
+
125
+ # Return if the path exists (because net::sftp uses exceptions for that
126
+ # and it makes code gross) and also raise an exception if the path is a
127
+ # symlink.
128
+ #
129
+ # @param path [String] Remote path to check.
130
+ # @return [Boolean]
131
+ def safe_stat(path)
132
+ stat = sftp_session.lstat!(path)
133
+ raise "#{path} is a symlink, possible security threat, bailing out" if stat.symlink?
134
+ true
135
+ rescue Net::SFTP::StatusException
136
+ false
137
+ end
138
+
139
+ # Upload the checksum script if needed.
140
+ #
141
+ # @return [void]
142
+ def copy_checksums_script!
143
+ # Fast path because upload itself is called multiple times.
144
+ return if @checksums_copied
145
+ # Only try to transfer the script if it isn't present. a stat takes about
146
+ # 1/3rd the time of the transfer, so worst case here is still okay.
147
+ sftp_session.upload!(CHECKSUMS_PATH, CHECKSUMS_REMOTE_PATH) unless safe_stat(CHECKSUMS_REMOTE_PATH)
148
+ @checksums_copied = true
149
+ end
150
+
151
+ def files_to_upload(checksums, local, recursive)
152
+ glob_path = if recursive
153
+ File.join(local, '**', '*')
154
+ else
155
+ local
156
+ end
157
+ pending = []
158
+ Dir.glob(glob_path, File::FNM_PATHNAME | File::FNM_DOTMATCH).each do |path|
159
+ next unless File.file?(path)
160
+ rel_path = path[local.length..-1]
161
+ remote_hash = checksums.delete(rel_path)
162
+ pending << rel_path unless remote_hash && remote_hash == Digest::SHA1.file(path).hexdigest
163
+ end
164
+ pending
165
+ end
166
+
167
+ def upload_file(checksums, local, remote, rel_path)
168
+ parts = rel_path.split('/')
169
+ parts.pop # Drop the filename since we are only checking dirs
170
+ parts_to_check = []
171
+ until parts.empty?
172
+ parts_to_check << parts.shift
173
+ path_to_check = parts_to_check.join('/')
174
+ unless checksums[path_to_check]
175
+ logger.debug("[SFTP] Creating directory #{remote}#{path_to_check}")
176
+ add_xfer(sftp_session.mkdir("#{remote}#{path_to_check}"))
177
+ checksums[path_to_check] = true
178
+ end
179
+ end
180
+ logger.debug("[SFTP] Uploading #{local}#{rel_path} to #{remote}#{rel_path}")
181
+ add_xfer(sftp_session.upload("#{local}#{rel_path}", "#{remote}#{rel_path}"))
182
+ end
183
+
184
+ def purge_files(checksums, remote)
185
+ checksums.each do |key, value|
186
+ # Special case for /tmp/kitchen/cache upload not clearing cookbooks.
187
+ # Tracked in https://github.com/test-kitchen/test-kitchen/issues/725
188
+ next if remote == '/tmp/kitchen/cache' && key.start_with?('/cookbooks')
189
+ # Check if the file was uploaded in #upload_file.
190
+ if value != true
191
+ logger.debug("[SFTP] Removing #{remote}#{key}")
192
+ add_xfer(sftp_session.remove("#{remote}#{key}"))
193
+ end
194
+ end
195
+ end
196
+
197
+ def sftp_xfers
198
+ @sftp_xfers ||= []
199
+ end
200
+
201
+ def add_xfer(xfer)
202
+ sftp_xfers << xfer
203
+ sftp_loop
204
+ end
205
+
206
+ def sftp_loop(n_xfers=MAX_TRANSFERS)
207
+ sftp_session.loop do
208
+ 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
209
+ sftp_xfers.length > n_xfers # Run until we have fewer than max
210
+ end
211
+ end
212
+
213
+
214
+ end
215
+
216
+ end
217
+ end
218
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Kantrowitz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-01 00:00:00.000000000 Z
11
+ date: 2015-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-kitchen
@@ -87,6 +87,7 @@ files:
87
87
  - lib/kitchen-sync/scp.rb
88
88
  - lib/kitchen-sync/sftp.rb
89
89
  - lib/kitchen-sync/version.rb
90
+ - lib/kitchen/transport/sftp.rb
90
91
  homepage: https://github.com/coderanger/kitchen-sync
91
92
  licenses:
92
93
  - Apache 2.0
@@ -107,9 +108,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
108
  version: '0'
108
109
  requirements: []
109
110
  rubyforge_project:
110
- rubygems_version: 2.2.2
111
+ rubygems_version: 2.4.5
111
112
  signing_key:
112
113
  specification_version: 4
113
114
  summary: Improved file transfers for for test-kitchen
114
115
  test_files: []
115
- has_rdoc: