kitchen-sync 1.0.1 → 1.1.0

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