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 +4 -4
- data/lib/kitchen-sync/version.rb +2 -1
- data/lib/kitchen/transport/sftp.rb +218 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5195fe4fdfca7efd26e6cb39af8faa9886d8efd
|
4
|
+
data.tar.gz: 86330af015cee42ae0075b628194982aa7f22a6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b519464648300eb50bcfbc2cc00edb2a3bd334dfe8149b870e5767c9ed1fb217c90ec87e10b94f0844bff15211570f8c4f3760d9f3bcc276eedeb989e48280b9
|
7
|
+
data.tar.gz: fbae6448d755ce2de522fc172b8db7d6355622c9e9b85075d4d003ceaf240304736583e3678180c13b4892d9a82f0c102a70ae688c9c967980916d953a8180ce
|
data/lib/kitchen-sync/version.rb
CHANGED
@@ -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
|
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:
|
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.
|
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:
|