kitchen-sync 1.1.1 → 2.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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +23 -17
- data/lib/kitchen-sync.rb +2 -62
- data/lib/kitchen-sync/core_ext.rb +1 -52
- data/lib/kitchen-sync/version.rb +2 -4
- data/lib/kitchen/transport/rsync.rb +106 -0
- data/lib/kitchen/transport/sftp.rb +7 -4
- metadata +5 -7
- data/lib/kitchen-sync/base.rb +0 -35
- data/lib/kitchen-sync/rsync.rb +0 -70
- data/lib/kitchen-sync/scp.rb +0 -36
- data/lib/kitchen-sync/sftp.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de3176802b5d0d2d9e1f1596f6dea7c3b779a5e8
|
4
|
+
data.tar.gz: fb8c7e2f628f05a4b02f4412b3a17fc815d6f88c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4227cbf1e1a90875146c48ad09a508a550ef19adba081cde50b14449ff9a55b0b22791da4a4c85a5fc94c3f52a6def3874a2d2dc31093a5f75bcaf64774ca032
|
7
|
+
data.tar.gz: e75859c18388457ae8df42177cfa22a8fdfe6982dcedcca4afaf933133173cbf8a30fd6d87d7e3ca0f6b3330f2d77bc8946a22daa4a9fd7e25e6ea108edabb20
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Kitchen-Sync Changelog
|
2
|
+
|
3
|
+
## v2.0.0
|
4
|
+
|
5
|
+
Fully revamped at last for Test Kitchen's new modular transports.
|
6
|
+
|
7
|
+
## v1.1.1
|
8
|
+
|
9
|
+
Bugfix for the new SFTP transport.
|
10
|
+
|
11
|
+
## v1.1.0
|
12
|
+
|
13
|
+
First stab at a Test Kitchen 1.4 transport plugin.
|
14
|
+
|
15
|
+
## v1.0.1
|
16
|
+
|
17
|
+
Bugfix for the SFTP transport provider.
|
18
|
+
|
19
|
+
## v1.0.0
|
20
|
+
|
21
|
+
Initial release!
|
data/README.md
CHANGED
@@ -9,43 +9,49 @@ most of which are faster than the default, thus speeding up your test runs.
|
|
9
9
|
Quick Start
|
10
10
|
-----------
|
11
11
|
|
12
|
-
|
13
|
-
`.kitchen.yml`:
|
12
|
+
Run `chef gem install kitchen-sync` and then set your transport to `sftp`:
|
14
13
|
|
15
14
|
```
|
16
|
-
|
15
|
+
transport:
|
16
|
+
name: sftp
|
17
17
|
```
|
18
18
|
|
19
19
|
Available Transfer Methods
|
20
20
|
--------------------------
|
21
21
|
|
22
|
-
|
23
|
-
variable. If not present, it defaults to `sftp`.
|
24
|
-
|
25
|
-
### SFTP
|
22
|
+
### `sftp`
|
26
23
|
|
27
24
|
The default mode uses SFTP for file transfers, as well as a helper script to
|
28
25
|
avoid recopying files that are already present on the test host. If SFTP is
|
29
26
|
disabled, this will automatically fall back to the SCP mode.
|
30
27
|
|
31
|
-
###
|
32
|
-
|
33
|
-
The SCP mode is just a copy of the implementation from test-kitchen. It is
|
34
|
-
present as a fallback and for benchmark comparisons, and generally won't be
|
35
|
-
used directly.
|
28
|
+
### `rsync`
|
36
29
|
|
37
|
-
|
38
|
-
|
39
|
-
The rsync mode is based on the work done by [Mikhail Bautin](https://github.com/test-kitchen/test-kitchen/pull/359).
|
30
|
+
The Rsync mode is based on the work done by [Mikhail Bautin](https://github.com/test-kitchen/test-kitchen/pull/359).
|
40
31
|
This is the fastest mode, but it does have a few downsides. The biggest is that
|
41
32
|
you must be using `ssh-agent` and have an identity loaded for it to use. It also
|
42
33
|
requires that rsync be available on the remote side. Consider this implementation
|
43
|
-
more experimental than
|
34
|
+
more experimental than `sftp` at this time.
|
35
|
+
|
36
|
+
Windows Guests
|
37
|
+
--------------
|
38
|
+
|
39
|
+
Windows is not specifically supported at this time, though if you have an SSH
|
40
|
+
server it will probably work. There is no support for WinRM.
|
41
|
+
|
42
|
+
Upgrading from 1.x
|
43
|
+
------------------
|
44
|
+
|
45
|
+
As of version 2.0, kitchen-sync uses Test Kitchen's modular transport system
|
46
|
+
rather than monkey patch overrides. To upgrade, remove the `<% require 'kitchen-sync' %>`
|
47
|
+
from your `.kitchen.yml` and add the transport configuration mentioned above.
|
48
|
+
The `$KITCHEN_SYNC_MODE` environment variable is no longer needed as configuration
|
49
|
+
can happen in the normal Yaml file.
|
44
50
|
|
45
51
|
License
|
46
52
|
-------
|
47
53
|
|
48
|
-
Copyright 2014, Noah Kantrowitz
|
54
|
+
Copyright 2014-2016, Noah Kantrowitz
|
49
55
|
|
50
56
|
Licensed under the Apache License, Version 2.0 (the "License");
|
51
57
|
you may not use this file except in compliance with the License.
|
data/lib/kitchen-sync.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# Copyright 2014, Noah Kantrowitz
|
2
|
+
# Copyright 2014-2016, Noah Kantrowitz
|
5
3
|
#
|
6
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
5
|
# you may not use this file except in compliance with the License.
|
@@ -16,65 +14,7 @@
|
|
16
14
|
# limitations under the License.
|
17
15
|
#
|
18
16
|
|
19
|
-
require 'benchmark'
|
20
|
-
require 'digest/sha1'
|
21
|
-
require 'json'
|
22
|
-
|
23
|
-
require 'kitchen/ssh'
|
24
|
-
require 'kitchen/provisioner/chef_base'
|
25
|
-
require 'net/sftp'
|
26
|
-
|
27
|
-
require 'kitchen-sync/core_ext'
|
28
|
-
require 'kitchen-sync/rsync'
|
29
|
-
require 'kitchen-sync/scp'
|
30
|
-
require 'kitchen-sync/sftp'
|
31
|
-
require 'kitchen-sync/version'
|
32
|
-
|
33
17
|
|
34
18
|
class KitchenSync
|
35
|
-
|
36
|
-
'rsync' => Rsync,
|
37
|
-
'scp' => SCP,
|
38
|
-
'sftp' => SFTP,
|
39
|
-
}
|
40
|
-
|
41
|
-
def initialize(logger, session, options)
|
42
|
-
@logger = logger
|
43
|
-
@session = session
|
44
|
-
@options = options
|
45
|
-
@impl = load_implementation
|
46
|
-
end
|
47
|
-
|
48
|
-
def load_implementation(default_mode='sftp')
|
49
|
-
mode = (ENV['KITCHEN_SYNC_MODE'] || default_mode).downcase
|
50
|
-
@logger.debug("[sync] Using transfer mode #{mode}")
|
51
|
-
impl_class = IMPLEMENTATIONS[mode]
|
52
|
-
raise "Sync implementation for #{mode} not found" unless impl_class
|
53
|
-
# Create the instance, any error during init means we use SCP instead
|
54
|
-
begin
|
55
|
-
impl_class.new(@logger, @session, @options)
|
56
|
-
rescue Exception
|
57
|
-
if impl_class != SCP
|
58
|
-
@logger.debug("[sync] Falling back to SCP")
|
59
|
-
impl_class = SCP
|
60
|
-
retry
|
61
|
-
else
|
62
|
-
raise
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def upload(local, remote, options)
|
68
|
-
# This is set even for single files, so make it something that matters again
|
69
|
-
options[:recursive] = File.directory?(local)
|
70
|
-
time = Benchmark.realtime do
|
71
|
-
@impl.upload(local, remote, options[:recursive])
|
72
|
-
end
|
73
|
-
@logger.info("[sync] Time taken to upload #{local} to #{@session}:#{remote}: " +
|
74
|
-
"%.2f sec" % time)
|
75
|
-
end
|
76
|
-
|
77
|
-
def shutdown
|
78
|
-
@impl.shutdown
|
79
|
-
end
|
19
|
+
autoload :VERSION, 'kitchen-sync/version'
|
80
20
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# Copyright 2014, Noah Kantrowitz
|
2
|
+
# Copyright 2014-2016, Noah Kantrowitz
|
5
3
|
#
|
6
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,55 +16,6 @@
|
|
18
16
|
|
19
17
|
# ┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻
|
20
18
|
module Kitchen
|
21
|
-
class SSH
|
22
|
-
|
23
|
-
#old_upload = instance_method(:upload!)
|
24
|
-
define_method(:upload!) do |local, remote, options = {}, &progress|
|
25
|
-
@kitchen_sync ||= KitchenSync.new(logger, session, @options)
|
26
|
-
@kitchen_sync.upload(local, remote, options)
|
27
|
-
end
|
28
|
-
|
29
|
-
# Monkey patch the shutdown to tear down the SFTP connection too.
|
30
|
-
old_shutdown = instance_method(:shutdown)
|
31
|
-
define_method(:shutdown) do
|
32
|
-
begin
|
33
|
-
@kitchen_sync.shutdown if @kitchen_sync
|
34
|
-
ensure
|
35
|
-
old_shutdown.bind(self).call
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
# Bug fix for session.loop never terminating if there is an SFTP conn active
|
42
|
-
# since as far as it is concerned there is still active stuff.
|
43
|
-
# This function is Copyright Fletcher Nichol
|
44
|
-
def exec_with_exit(cmd)
|
45
|
-
exit_code = nil
|
46
|
-
session.open_channel do |channel|
|
47
|
-
|
48
|
-
channel.request_pty
|
49
|
-
|
50
|
-
channel.exec(cmd) do |ch, success|
|
51
|
-
|
52
|
-
channel.on_data do |ch, data|
|
53
|
-
logger << data
|
54
|
-
end
|
55
|
-
|
56
|
-
channel.on_extended_data do |ch, type, data|
|
57
|
-
logger << data
|
58
|
-
end
|
59
|
-
|
60
|
-
channel.on_request("exit-status") do |ch, data|
|
61
|
-
exit_code = data.read_long
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
session.loop { !exit_code } # THERE IS A CHANGE ON THIS LINE, PAY ATTENTION!!!!!!
|
66
|
-
exit_code
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
19
|
# Monkey patch to prevent the deletion of everything
|
71
20
|
module Provisioner
|
72
21
|
class ChefBase < Base
|
data/lib/kitchen-sync/version.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# Copyright 2014, Noah Kantrowitz
|
2
|
+
# Copyright 2014-2016, Noah Kantrowitz
|
5
3
|
#
|
6
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,5 +16,5 @@
|
|
18
16
|
|
19
17
|
|
20
18
|
class KitchenSync
|
21
|
-
VERSION = '
|
19
|
+
VERSION = '2.0.0'
|
22
20
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014-2016, 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 'base64'
|
18
|
+
|
19
|
+
require 'kitchen/transport/ssh'
|
20
|
+
require 'net/ssh'
|
21
|
+
|
22
|
+
require 'kitchen-sync/core_ext'
|
23
|
+
|
24
|
+
module Kitchen
|
25
|
+
module Transport
|
26
|
+
class Rsync < Ssh
|
27
|
+
# Copy-pasta from Ssh#create_new_connection because I need the Rsync
|
28
|
+
# connection class.
|
29
|
+
# Tracked in https://github.com/test-kitchen/test-kitchen/pull/726
|
30
|
+
def create_new_connection(options, &block)
|
31
|
+
if @connection
|
32
|
+
logger.debug("[SSH] shutting previous connection #{@connection}")
|
33
|
+
@connection.close
|
34
|
+
end
|
35
|
+
|
36
|
+
@connection_options = options
|
37
|
+
@connection = self.class::Connection.new(options, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
class Connection < Ssh::Connection
|
41
|
+
def upload(locals, remote)
|
42
|
+
if @rsync_failed || !File.exists?('/usr/bin/rsync')
|
43
|
+
logger.debug('[rsync] Rsync already failed or not installed, not trying it')
|
44
|
+
return super
|
45
|
+
end
|
46
|
+
|
47
|
+
locals = Array(locals)
|
48
|
+
# We only try to sync folders for now.
|
49
|
+
rsync_candidates = locals.select {|path| File.directory?(path) }
|
50
|
+
ssh_command = "ssh #{ssh_args.join(' ')}"
|
51
|
+
copy_identity
|
52
|
+
rsync_cmd = "/usr/bin/rsync -e '#{ssh_command}' -az#{logger.level == :debug ? 'vv' : ''} #{rsync_candidates.join(' ')} #{@session.options[:user]}@#{@session.host}:#{remote}"
|
53
|
+
logger.debug("[rsync] Running rsync command: #{rsync_cmd}")
|
54
|
+
ret = []
|
55
|
+
time = Benchmark.realtime do
|
56
|
+
ret << system(rsync_cmd)
|
57
|
+
end
|
58
|
+
logger.info("[rsync] Time taken to upload #{rsync_candidates.join(';')} to #{self}:#{remote}: %.2f sec" % time)
|
59
|
+
unless ret.first
|
60
|
+
logger.warn("[rsync] rsync exited with status #{$?.exitstatus}, using SCP instead")
|
61
|
+
@rsync_failed = true
|
62
|
+
end
|
63
|
+
|
64
|
+
# Fall back to SCP
|
65
|
+
remaining = if @rsync_failed
|
66
|
+
locals
|
67
|
+
else
|
68
|
+
locals - rsync_candidates
|
69
|
+
end
|
70
|
+
logger.debug("[rsync] Using fallback to upload #{remaining.join(';')}")
|
71
|
+
super(remaining, remote) unless remaining.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Copy your SSH identity, creating a new one if needed
|
75
|
+
def copy_identity
|
76
|
+
return if @copied_identity
|
77
|
+
identities = Net::SSH::Authentication::Agent.connect.identities
|
78
|
+
raise 'No SSH identities found. Please run ssh-add.' if identities.empty?
|
79
|
+
key = identities.first
|
80
|
+
enc_key = Base64.encode64(key.to_blob).gsub("\n", '')
|
81
|
+
identitiy = "ssh-rsa #{enc_key} #{key.comment}"
|
82
|
+
@session.exec! <<-EOT
|
83
|
+
test -e ~/.ssh || mkdir ~/.ssh
|
84
|
+
test -e ~/.ssh/authorized_keys || touch ~/.ssh/authorized_keys
|
85
|
+
if ! grep -q "#{identitiy}" ~/.ssh/authorized_keys ; then
|
86
|
+
chmod go-w ~ ~/.ssh ~/.ssh/authorized_keys ; \
|
87
|
+
echo "#{identitiy}" >> ~/.ssh/authorized_keys
|
88
|
+
fi
|
89
|
+
EOT
|
90
|
+
@copied_identity = true
|
91
|
+
end
|
92
|
+
|
93
|
+
def ssh_args
|
94
|
+
args = %W{ -o UserKnownHostsFile=/dev/null }
|
95
|
+
args += %W{ -o StrictHostKeyChecking=no }
|
96
|
+
args += %W{ -o IdentitiesOnly=yes } if @options[:keys]
|
97
|
+
args += %W{ -o LogLevel=#{@logger.debug? ? "VERBOSE" : "ERROR"} }
|
98
|
+
args += %W{ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} } if @options.key? :forward_agent
|
99
|
+
Array(@options[:keys]).each { |ssh_key| args += %W{ -i #{ssh_key}} }
|
100
|
+
args += %W{ -p #{@session.options[:port]}}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright 2014-
|
2
|
+
# Copyright 2014-2016, Noah Kantrowitz
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -14,9 +14,15 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
|
17
|
+
require 'benchmark'
|
18
|
+
require 'digest/sha1'
|
19
|
+
require 'json'
|
20
|
+
|
17
21
|
require 'kitchen/transport/ssh'
|
18
22
|
require 'net/sftp'
|
19
23
|
|
24
|
+
require 'kitchen-sync/core_ext'
|
25
|
+
|
20
26
|
|
21
27
|
module Kitchen
|
22
28
|
module Transport
|
@@ -184,9 +190,6 @@ module Kitchen
|
|
184
190
|
|
185
191
|
def purge_files(checksums, remote)
|
186
192
|
checksums.each do |key, value|
|
187
|
-
# Special case for /tmp/kitchen/cache upload not clearing cookbooks.
|
188
|
-
# Tracked in https://github.com/test-kitchen/test-kitchen/issues/725
|
189
|
-
next if remote == '/tmp/kitchen/cache' && key.start_with?('/cookbooks')
|
190
193
|
# Check if the file was uploaded in #upload_file.
|
191
194
|
if value != true
|
192
195
|
logger.debug("[SFTP] Removing #{remote}#{key}")
|
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:
|
4
|
+
version: 2.0.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: 2016-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: test-kitchen
|
@@ -74,19 +74,17 @@ extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
76
|
- ".gitignore"
|
77
|
+
- CHANGELOG.md
|
77
78
|
- Gemfile
|
78
79
|
- LICENSE
|
79
80
|
- README.md
|
80
81
|
- Rakefile
|
81
82
|
- kitchen-sync.gemspec
|
82
83
|
- lib/kitchen-sync.rb
|
83
|
-
- lib/kitchen-sync/base.rb
|
84
84
|
- lib/kitchen-sync/checksums.rb
|
85
85
|
- lib/kitchen-sync/core_ext.rb
|
86
|
-
- lib/kitchen-sync/rsync.rb
|
87
|
-
- lib/kitchen-sync/scp.rb
|
88
|
-
- lib/kitchen-sync/sftp.rb
|
89
86
|
- lib/kitchen-sync/version.rb
|
87
|
+
- lib/kitchen/transport/rsync.rb
|
90
88
|
- lib/kitchen/transport/sftp.rb
|
91
89
|
homepage: https://github.com/coderanger/kitchen-sync
|
92
90
|
licenses:
|
@@ -108,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
106
|
version: '0'
|
109
107
|
requirements: []
|
110
108
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.4.
|
109
|
+
rubygems_version: 2.4.8
|
112
110
|
signing_key:
|
113
111
|
specification_version: 4
|
114
112
|
summary: Improved file transfers for for test-kitchen
|
data/lib/kitchen-sync/base.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Noah Kantrowitz <noah@coderanger.net>
|
3
|
-
#
|
4
|
-
# Copyright 2014, Noah Kantrowitz
|
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
|
-
class KitchenSync
|
20
|
-
class Base
|
21
|
-
def initialize(logger, session, options)
|
22
|
-
@logger = logger
|
23
|
-
@session = session
|
24
|
-
@options = options
|
25
|
-
end
|
26
|
-
|
27
|
-
def upload(local, remote, recursive=true)
|
28
|
-
raise NotImplementedError
|
29
|
-
end
|
30
|
-
|
31
|
-
def shutdown
|
32
|
-
# This space left intentionally blank
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
data/lib/kitchen-sync/rsync.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Noah Kantrowitz <noah@coderanger.net>
|
3
|
-
#
|
4
|
-
# Copyright 2013-2014, Fletcher Nichol
|
5
|
-
# Copyright 2014, Noah Kantrowitz
|
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 'kitchen-sync/scp'
|
21
|
-
|
22
|
-
class KitchenSync
|
23
|
-
class Rsync < SCP
|
24
|
-
def upload(local, remote, recursive=true)
|
25
|
-
upload_done = false
|
26
|
-
if !@rsync_failed && recursive && File.exists?('/usr/bin/rsync')
|
27
|
-
ssh_command = "ssh #{ssh_args.join(' ')}"
|
28
|
-
copy_identity
|
29
|
-
rsync_cmd = "/usr/bin/rsync -e '#{ssh_command}' -az #{local} #{@session.options[:user]}@#{@session.host}:#{remote}"
|
30
|
-
@logger.info("[sync:rsync] Running rsync command: #{rsync_cmd}")
|
31
|
-
if system(rsync_cmd)
|
32
|
-
upload_done = true
|
33
|
-
else
|
34
|
-
@logger.warn("[sync:rsync] rsync exited with status #{$?.exitstatus}, using Net::SCP instead")
|
35
|
-
@rsync_failed = true
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Fall back to SCP
|
40
|
-
super unless upload_done
|
41
|
-
end
|
42
|
-
|
43
|
-
# Copy your SSH identity, creating a new one if needed
|
44
|
-
def copy_identity
|
45
|
-
return if @copied_identity
|
46
|
-
key = Net::SSH::Authentication::Agent.connect.identities.first
|
47
|
-
enc_key = Base64.encode64(key.to_blob).gsub("\n", '')
|
48
|
-
identitiy = "ssh-rsa #{enc_key} #{key.comment}"
|
49
|
-
@session.exec! <<-EOT
|
50
|
-
test -e ~/.ssh || mkdir ~/.ssh
|
51
|
-
test -e ~/.ssh/authorized_keys || touch ~/.ssh/authorized_keys
|
52
|
-
if ! grep -q "#{identitiy}" ~/.ssh/authorized_keys ; then
|
53
|
-
chmod go-w ~ ~/.ssh ~/.ssh/authorized_keys ; \
|
54
|
-
echo "#{identitiy}" >> ~/.ssh/authorized_keys
|
55
|
-
fi
|
56
|
-
EOT
|
57
|
-
@copied_identity = true
|
58
|
-
end
|
59
|
-
|
60
|
-
def ssh_args
|
61
|
-
args = %W{ -o UserKnownHostsFile=/dev/null }
|
62
|
-
args += %W{ -o StrictHostKeyChecking=no }
|
63
|
-
args += %W{ -o IdentitiesOnly=yes } if @options[:keys]
|
64
|
-
args += %W{ -o LogLevel=#{@logger.debug? ? "VERBOSE" : "ERROR"} }
|
65
|
-
args += %W{ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} } if @options.key? :forward_agent
|
66
|
-
Array(@options[:keys]).each { |ssh_key| args += %W{ -i #{ssh_key}} }
|
67
|
-
args += %W{ -p #{@session.options[:port]}}
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
data/lib/kitchen-sync/scp.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Noah Kantrowitz <noah@coderanger.net>
|
3
|
-
#
|
4
|
-
# Copyright 2013-2014, Fletcher Nichol
|
5
|
-
# Copyright 2014, Noah Kantrowitz
|
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 'kitchen-sync/base'
|
21
|
-
|
22
|
-
require 'net/scp'
|
23
|
-
|
24
|
-
class KitchenSync
|
25
|
-
class SCP < Base
|
26
|
-
def upload(local, remote, recursive=true)
|
27
|
-
true_remote = File.join(remote, File.basename(local))
|
28
|
-
@session.exec!("rm -rf #{true_remote}")
|
29
|
-
@session.scp.upload!(local, remote, recursive: recursive) do |ch, name, sent, total|
|
30
|
-
if sent == total
|
31
|
-
@logger.debug("Uploaded #{name} (#{total} bytes)")
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
data/lib/kitchen-sync/sftp.rb
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Noah Kantrowitz <noah@coderanger.net>
|
3
|
-
#
|
4
|
-
# Copyright 2014, Noah Kantrowitz
|
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 'kitchen-sync/base'
|
20
|
-
|
21
|
-
require 'net/sftp'
|
22
|
-
|
23
|
-
class KitchenSync
|
24
|
-
class SFTP < Base
|
25
|
-
CHECKSUMS_PATH = File.expand_path('../checksums.rb', __FILE__)
|
26
|
-
CHECKSUMS_HASH = Digest::SHA1.file(CHECKSUMS_PATH)
|
27
|
-
CHECKSUMS_REMOTE_PATH = "/tmp/checksums-#{CHECKSUMS_HASH}.rb" # This won't work on Windows targets
|
28
|
-
MAX_TRANSFERS = 64
|
29
|
-
|
30
|
-
def initialize(*args)
|
31
|
-
super
|
32
|
-
@sftp = @session.sftp
|
33
|
-
@xfers = []
|
34
|
-
end
|
35
|
-
|
36
|
-
def upload(local, remote, recursive=true)
|
37
|
-
remote = File.join(remote, File.basename(local))
|
38
|
-
# Fast path check, if the remote path doesn't exist at all we just run a direct transfer
|
39
|
-
unless safe_stat(remote)
|
40
|
-
@logger.debug("[sync:sftp] Fast path upload from #{local} to #{remote}")
|
41
|
-
@sftp.mkdir!(remote) if recursive
|
42
|
-
@sftp.upload!(local, remote, requests: MAX_TRANSFERS)
|
43
|
-
return
|
44
|
-
end
|
45
|
-
copy_checksums_script
|
46
|
-
# Get our checksums
|
47
|
-
checksum_cmd = "/opt/chef/embedded/bin/ruby #{CHECKSUMS_REMOTE_PATH} #{remote}"
|
48
|
-
@logger.info("[sync:sftp] Running #{checksum_cmd}")
|
49
|
-
checksums = JSON.parse(@session.exec!(checksum_cmd))
|
50
|
-
files_to_upload(checksums, local, recursive).each do |rel_path|
|
51
|
-
upload_file(checksums, local, remote, rel_path)
|
52
|
-
end
|
53
|
-
purge_files(checksums, remote)
|
54
|
-
sftp_loop(0) # Wait until all xfers are complete
|
55
|
-
end
|
56
|
-
|
57
|
-
def shutdown
|
58
|
-
@logger.debug("[sync:sftp] closing connection to #{@session}")
|
59
|
-
@sftp.close_channel
|
60
|
-
@sftp = nil
|
61
|
-
end
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
# Return if the path exists (because net::sftp uses exceptions for that and
|
66
|
-
# it makes code gross) and also raise an exception if the path is a symlink.
|
67
|
-
def safe_stat(path)
|
68
|
-
stat = @sftp.lstat!(path)
|
69
|
-
raise "#{path} is a symlink, possible security threat, bailing out" if stat.symlink?
|
70
|
-
true
|
71
|
-
rescue Net::SFTP::StatusException
|
72
|
-
false
|
73
|
-
end
|
74
|
-
|
75
|
-
def copy_checksums_script
|
76
|
-
return if @checksums_copied
|
77
|
-
# Only try to transfer the script if it isn't present. a stat takes about
|
78
|
-
# 1/3rd the time of the transfer, so worst case here is still okay.
|
79
|
-
@sftp.upload!(CHECKSUMS_PATH, CHECKSUMS_REMOTE_PATH) unless safe_stat(CHECKSUMS_REMOTE_PATH)
|
80
|
-
@checksums_copied = true
|
81
|
-
end
|
82
|
-
|
83
|
-
def files_to_upload(checksums, local, recursive)
|
84
|
-
glob_path = if recursive
|
85
|
-
File.join(local, '**', '*')
|
86
|
-
else
|
87
|
-
local
|
88
|
-
end
|
89
|
-
pending = []
|
90
|
-
Dir.glob(glob_path, File::FNM_PATHNAME | File::FNM_DOTMATCH).each do |path|
|
91
|
-
next unless File.file?(path)
|
92
|
-
rel_path = path[local.length..-1]
|
93
|
-
remote_hash = checksums.delete(rel_path)
|
94
|
-
pending << rel_path unless remote_hash && remote_hash == Digest::SHA1.file(path).hexdigest
|
95
|
-
end
|
96
|
-
pending
|
97
|
-
end
|
98
|
-
|
99
|
-
def upload_file(checksums, local, remote, rel_path)
|
100
|
-
parts = rel_path.split('/')
|
101
|
-
parts.pop # Drop the filename since we are only checking dirs
|
102
|
-
parts_to_check = []
|
103
|
-
until parts.empty?
|
104
|
-
parts_to_check << parts.shift
|
105
|
-
path_to_check = parts_to_check.join('/')
|
106
|
-
unless checksums[path_to_check]
|
107
|
-
@logger.debug("[sync:sftp] Creating directory #{remote}#{path_to_check}")
|
108
|
-
add_xfer(@sftp.mkdir("#{remote}#{path_to_check}"))
|
109
|
-
checksums[path_to_check] = true
|
110
|
-
end
|
111
|
-
end
|
112
|
-
@logger.debug("[sync:sftp] Uploading #{local}#{rel_path} to #{remote}#{rel_path}")
|
113
|
-
add_xfer(@sftp.upload("#{local}#{rel_path}", "#{remote}#{rel_path}"))
|
114
|
-
end
|
115
|
-
|
116
|
-
def purge_files(checksums, remote)
|
117
|
-
checksums.each do |key, value|
|
118
|
-
if value != true
|
119
|
-
@logger.debug("[sync:sftp] Removing #{remote}#{key}")
|
120
|
-
add_xfer(@sftp.remove("#{remote}#{key}"))
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def add_xfer(xfer)
|
126
|
-
@xfers << xfer
|
127
|
-
sftp_loop
|
128
|
-
end
|
129
|
-
|
130
|
-
def sftp_loop(n_xfers=MAX_TRANSFERS)
|
131
|
-
@sftp.loop do
|
132
|
-
@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
|
133
|
-
@xfers.length > n_xfers # Run until we have fewer than max
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
end
|
138
|
-
end
|