syncwrap 1.5.2 → 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 +7 -0
- data/History.rdoc +19 -0
- data/Manifest.txt +82 -34
- data/README.rdoc +96 -48
- data/Rakefile +0 -65
- data/bin/syncwrap +27 -0
- data/examples/LAYOUT.rdoc +70 -0
- data/examples/Rakefile +16 -0
- data/examples/ec2.rb +44 -0
- data/examples/hello.rb +14 -0
- data/examples/hello_binding.rb +27 -0
- data/examples/jruby.rb +11 -0
- data/examples/private/aws.json +4 -0
- data/examples/rput.rb +24 -0
- data/examples/sync/home/bob/.ssh/authorized_keys +1 -0
- data/examples/sync/tmp/sample.erb +3 -0
- data/lib/syncwrap/amazon_ec2.rb +236 -0
- data/lib/syncwrap/amazon_ws.rb +308 -0
- data/lib/syncwrap/base.rb +4 -2
- data/lib/syncwrap/cli.rb +328 -0
- data/lib/syncwrap/component.rb +443 -0
- data/lib/syncwrap/components/commercial_jdk.rb +76 -0
- data/lib/syncwrap/components/cruby_vm.rb +144 -0
- data/lib/syncwrap/components/etc_hosts.rb +44 -0
- data/lib/syncwrap/{geminabox.rb → components/geminabox.rb} +12 -17
- data/lib/syncwrap/components/hashdot.rb +97 -0
- data/lib/syncwrap/components/iyyov.rb +144 -0
- data/lib/syncwrap/components/iyyov_daemon.rb +125 -0
- data/lib/syncwrap/components/jruby_vm.rb +122 -0
- data/lib/syncwrap/components/mdraid.rb +204 -0
- data/lib/syncwrap/components/network.rb +99 -0
- data/lib/syncwrap/components/open_jdk.rb +70 -0
- data/lib/syncwrap/components/postgresql.rb +159 -0
- data/lib/syncwrap/components/qpid.rb +303 -0
- data/lib/syncwrap/components/rhel.rb +71 -0
- data/lib/syncwrap/components/run_user.rb +99 -0
- data/lib/syncwrap/components/ubuntu.rb +85 -0
- data/lib/syncwrap/components/users.rb +200 -0
- data/lib/syncwrap/context.rb +260 -0
- data/lib/syncwrap/distro.rb +53 -60
- data/lib/syncwrap/formatter.rb +149 -0
- data/lib/syncwrap/host.rb +134 -0
- data/lib/syncwrap/main.rb +62 -0
- data/lib/syncwrap/path_util.rb +55 -0
- data/lib/syncwrap/rsync.rb +227 -0
- data/lib/syncwrap/ruby_support.rb +110 -0
- data/lib/syncwrap/shell.rb +207 -0
- data/lib/syncwrap.rb +367 -1
- data/{etc → sync/etc}/gemrc +1 -3
- data/sync/etc/hosts.erb +8 -0
- data/{etc/init.d/iyyov → sync/etc/init.d/iyyov.erb} +35 -7
- data/sync/etc/sysconfig/pgsql/postgresql.erb +2 -0
- data/sync/src/hashdot/Makefile.erb +98 -0
- data/sync/src/hashdot/profiles/default.hdp.erb +25 -0
- data/sync/src/hashdot/profiles/jruby-common.hdp +28 -0
- data/sync/src/hashdot/profiles/jruby-shortlived.hdp +9 -0
- data/sync/src/hashdot/profiles/jruby.hdp.erb +13 -0
- data/sync/src/hashdot/profiles/shortlived.hdp +6 -0
- data/sync/var/iyyov/default/config.rb +1 -0
- data/sync/var/iyyov/default/daemon.rb.erb +15 -0
- data/sync/var/iyyov/jobs.rb.erb +4 -0
- data/test/muddled_sync.rb +13 -0
- data/test/setup.rb +39 -0
- data/test/sync/d1/bar +1 -0
- data/test/sync/d1/foo.erb +1 -0
- data/test/sync/d3/d2/bar +1 -0
- data/test/sync/d3/d2/foo.erb +1 -0
- data/test/test_components.rb +108 -0
- data/test/test_context.rb +107 -0
- data/test/test_context_rput.rb +289 -0
- data/test/test_rsync.rb +138 -0
- data/test/test_shell.rb +233 -0
- data/test/test_space.rb +218 -0
- data/test/test_space_main.rb +40 -0
- data/test/zfile +1 -0
- metadata +204 -71
- data/etc/sysconfig/pgsql/postgresql +0 -2
- data/lib/syncwrap/aws.rb +0 -448
- data/lib/syncwrap/common.rb +0 -161
- data/lib/syncwrap/ec2.rb +0 -59
- data/lib/syncwrap/hashdot.rb +0 -70
- data/lib/syncwrap/iyyov.rb +0 -139
- data/lib/syncwrap/java.rb +0 -61
- data/lib/syncwrap/jruby.rb +0 -118
- data/lib/syncwrap/postgresql.rb +0 -135
- data/lib/syncwrap/qpid.rb +0 -251
- data/lib/syncwrap/remote_task.rb +0 -199
- data/lib/syncwrap/rhel.rb +0 -67
- data/lib/syncwrap/ubuntu.rb +0 -78
- data/lib/syncwrap/user_run.rb +0 -102
- data/test/test_syncwrap.rb +0 -202
- data/var/iyyov/jobs.rb +0 -11
- /data/{etc → sync/etc}/corosync/corosync.conf +0 -0
- /data/{etc → sync/etc}/corosync/uidgid.d/qpid +0 -0
- /data/{etc → sync/etc}/init.d/qpidd +0 -0
- /data/{etc → sync/etc}/sysctl.d/61-postgresql-shm.conf +0 -0
- /data/{usr/local → sync/jruby}/bin/jgem +0 -0
- /data/{postgresql → sync/postgresql}/rhel/pg_hba.conf +0 -0
- /data/{postgresql → sync/postgresql}/rhel/pg_ident.conf +0 -0
- /data/{postgresql → sync/postgresql}/rhel/postgresql.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/environment +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/pg_ctl.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/pg_hba.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/pg_ident.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/postgresql.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/start.conf +0 -0
- /data/{usr → sync/usr}/local/etc/qpidd.conf +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright (c) 2011-2014 David Kellum
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
|
5
|
+
# may not use this file except in compliance with the License. You
|
|
6
|
+
# 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
|
|
13
|
+
# implied. See the License for the specific language governing
|
|
14
|
+
# permissions and limitations under the License.
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
require 'thread'
|
|
18
|
+
require 'term/ansicolor'
|
|
19
|
+
|
|
20
|
+
module SyncWrap
|
|
21
|
+
|
|
22
|
+
class Formatter
|
|
23
|
+
include Term::ANSIColor
|
|
24
|
+
|
|
25
|
+
attr_reader :io
|
|
26
|
+
attr_reader :lock
|
|
27
|
+
attr_accessor :colorize
|
|
28
|
+
|
|
29
|
+
def initialize( io = $stdout )
|
|
30
|
+
@io = io
|
|
31
|
+
@lock = Mutex.new
|
|
32
|
+
@colorize = true
|
|
33
|
+
@newlined = true
|
|
34
|
+
@backtraces = {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def sync( &block )
|
|
38
|
+
@lock.synchronize( &block )
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def write_component( host, comp, mth, state )
|
|
42
|
+
io << yellow if colorize
|
|
43
|
+
io << '== ' << host.name << ' ' << comp.class << '#' << mth
|
|
44
|
+
io << ': ' << state
|
|
45
|
+
io << clear if colorize
|
|
46
|
+
io << "\n"
|
|
47
|
+
flush
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def write_header( host, mode, opts, live = false )
|
|
51
|
+
olist = []
|
|
52
|
+
olist << "-#{opts[:sh_verbose]}" if opts[:sh_verbose] && mode != :rsync
|
|
53
|
+
olist << 'coalesce' if opts[:coalesce]
|
|
54
|
+
olist << 'dryrun' if opts[:dryrun]
|
|
55
|
+
olist << "accept:#{opts[:accept].join ','}" if opts[:accept]
|
|
56
|
+
olist << "user:#{opts[:user]}" if opts[:user]
|
|
57
|
+
olist << 'live' if live
|
|
58
|
+
|
|
59
|
+
io << yellow if colorize
|
|
60
|
+
io << '<-- ' << mode << ' ' << host.name
|
|
61
|
+
first = true
|
|
62
|
+
olist.each do |li|
|
|
63
|
+
if first
|
|
64
|
+
io << ' ('
|
|
65
|
+
first = false
|
|
66
|
+
else
|
|
67
|
+
io << ' '
|
|
68
|
+
end
|
|
69
|
+
io << li
|
|
70
|
+
end
|
|
71
|
+
io << ')' unless olist.empty?
|
|
72
|
+
io << clear if colorize
|
|
73
|
+
io << "\n"
|
|
74
|
+
flush
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def write_result( result )
|
|
78
|
+
io << yellow if colorize
|
|
79
|
+
io << '--> ' << result
|
|
80
|
+
io << clear if colorize
|
|
81
|
+
io << "\n"
|
|
82
|
+
flush
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def write_command_outputs( outputs, color = true )
|
|
86
|
+
outputs.each do |stream, buff|
|
|
87
|
+
write_command_output( stream, buff, color )
|
|
88
|
+
end
|
|
89
|
+
output_terminate
|
|
90
|
+
flush
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def output_terminate
|
|
94
|
+
unless @newlined
|
|
95
|
+
io.puts
|
|
96
|
+
@newlined = true
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def write_command_output( stream, buff, color = true )
|
|
101
|
+
unless buff.empty?
|
|
102
|
+
if stream == :err && colorize && color
|
|
103
|
+
io << red << buff << clear
|
|
104
|
+
else
|
|
105
|
+
io << buff
|
|
106
|
+
end
|
|
107
|
+
@newlined = ( buff[-1] == "\n" )
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def write_error( host, error, comp = nil, mth = nil )
|
|
112
|
+
bt = error.backtrace
|
|
113
|
+
bt_num = @backtraces[ bt ]
|
|
114
|
+
if bt_num
|
|
115
|
+
bt = nil
|
|
116
|
+
else
|
|
117
|
+
@backtraces[ bt ] = bt_num = @backtraces.length + 1
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
io << yellow if colorize
|
|
121
|
+
io << '== ' << host.name << ' '
|
|
122
|
+
io << comp.class << '#' << mth << ' ' if comp && mth
|
|
123
|
+
io << "error"
|
|
124
|
+
io << ", same stack as" unless bt
|
|
125
|
+
io << " [" << bt_num << "]:\n"
|
|
126
|
+
|
|
127
|
+
io << red if colorize
|
|
128
|
+
io << short_cn( error.class ) << ': ' << error.message << "\n"
|
|
129
|
+
if bt
|
|
130
|
+
bt.each do |line|
|
|
131
|
+
break if line =~ /execute_component'$/
|
|
132
|
+
io.puts line
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
io << clear if colorize
|
|
136
|
+
flush
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def flush
|
|
140
|
+
io.flush
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def short_cn( cls )
|
|
144
|
+
cls.name.sub(/^SyncWrap::/,'')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright (c) 2011-2014 David Kellum
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
|
5
|
+
# may not use this file except in compliance with the License. You
|
|
6
|
+
# 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
|
|
13
|
+
# implied. See the License for the specific language governing
|
|
14
|
+
# permissions and limitations under the License.
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
module SyncWrap
|
|
18
|
+
|
|
19
|
+
# Represents various host (server, machine instance) metadata and
|
|
20
|
+
# serves as a container for #roles and #components.
|
|
21
|
+
class Host
|
|
22
|
+
|
|
23
|
+
# The space in which this host was constructed.
|
|
24
|
+
attr_reader :space
|
|
25
|
+
|
|
26
|
+
# Array of role Symbols or (direct) Component instances in the
|
|
27
|
+
# order added.
|
|
28
|
+
attr_reader :contents
|
|
29
|
+
|
|
30
|
+
attr_reader :props
|
|
31
|
+
|
|
32
|
+
def initialize( space, props = {} )
|
|
33
|
+
@space = space
|
|
34
|
+
@props = {}
|
|
35
|
+
merge_props( props )
|
|
36
|
+
@contents = [ :all ]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Return the :name property.
|
|
40
|
+
def name
|
|
41
|
+
self[ :name ]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Return the property by (Symbol) key
|
|
45
|
+
def []( key )
|
|
46
|
+
@props[ key ]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Set property by (Symbol) key to value. Note that the :roles
|
|
50
|
+
# property key is supported here, but is effectively the same as
|
|
51
|
+
# #add( val ). Roles are only added, never removed.
|
|
52
|
+
def []=( key, val )
|
|
53
|
+
key = key.to_sym
|
|
54
|
+
if key == :roles
|
|
55
|
+
add( *val )
|
|
56
|
+
else
|
|
57
|
+
@props[ key.to_sym ] = val
|
|
58
|
+
end
|
|
59
|
+
val
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Merge properties. Note that the :roles property key is
|
|
63
|
+
# supported here, but is affectively the same as #add( val ).
|
|
64
|
+
def merge_props( opts )
|
|
65
|
+
opts.each do |key,val|
|
|
66
|
+
self[ key ] = val
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_h
|
|
71
|
+
@props.merge( roles: roles )
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Add any number of roles (by Symbol) or (direct) Component
|
|
75
|
+
# instances.
|
|
76
|
+
def add( *args )
|
|
77
|
+
args.each do |arg|
|
|
78
|
+
case( arg )
|
|
79
|
+
when Symbol
|
|
80
|
+
@contents << arg unless @contents.include?( arg )
|
|
81
|
+
when Component
|
|
82
|
+
@contents << arg
|
|
83
|
+
else
|
|
84
|
+
raise "Invalid host #{name} addition: #{arg.inspect}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Return an Array of previously added role symbols.
|
|
90
|
+
def roles
|
|
91
|
+
@contents.select { |c| c.is_a?( Symbol ) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Return a flat Array of Component instances by traversing
|
|
95
|
+
# previously added roles and any direct components in order.
|
|
96
|
+
def components
|
|
97
|
+
@contents.inject([]) do |m,c|
|
|
98
|
+
if c.is_a?( Symbol )
|
|
99
|
+
m += space.role( c )
|
|
100
|
+
else
|
|
101
|
+
m << c
|
|
102
|
+
end
|
|
103
|
+
m
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Return the last component added to this Host prior to the given
|
|
108
|
+
# component (either directly or via a role), or nil if there is no
|
|
109
|
+
# such component.
|
|
110
|
+
def prior_component( component )
|
|
111
|
+
last = nil
|
|
112
|
+
@contents.each do |c|
|
|
113
|
+
if c.is_a?( Symbol )
|
|
114
|
+
space.role( c ).each do |rc|
|
|
115
|
+
return last if rc.equal?( component ) #identity
|
|
116
|
+
last = rc
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
return last if c.equal?( component ) #identity
|
|
120
|
+
last = c
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Return the _last_ component which is a kind of the specified
|
|
127
|
+
# Class or Module clz, or nil if not found.
|
|
128
|
+
def component( clz )
|
|
129
|
+
components.reverse.find { |c| c.kind_of?( clz ) }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright (c) 2011-2014 David Kellum
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
|
5
|
+
# may not use this file except in compliance with the License. You may
|
|
6
|
+
# 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
|
|
13
|
+
# implied. See the License for the specific language governing
|
|
14
|
+
# permissions and limitations under the License.
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
require 'syncwrap'
|
|
18
|
+
|
|
19
|
+
module SyncWrap
|
|
20
|
+
|
|
21
|
+
# A limited set of (private) methods for use at the top-level in
|
|
22
|
+
# a sync.rb. All of these methods delegate to the _current_
|
|
23
|
+
# Space.
|
|
24
|
+
module Main
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
# The current Space
|
|
29
|
+
def space # :doc:
|
|
30
|
+
Space.current
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Shorthand for space.role
|
|
34
|
+
def role( *args ) # :doc:
|
|
35
|
+
space.role( *args )
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Shorthand for space.role
|
|
39
|
+
def host( *args ) # :doc:
|
|
40
|
+
space.host( *args )
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Merge options given, or (without opts) return space.default_options
|
|
44
|
+
def options( opts = nil ) # :doc:
|
|
45
|
+
if opts
|
|
46
|
+
space.merge_default_options( opts )
|
|
47
|
+
else
|
|
48
|
+
space.default_options
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Shorthand for space.provider.profile
|
|
53
|
+
def profile( *args ) # :doc:
|
|
54
|
+
space.provider.profile( *args )
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Extend the top level Object with the Main module
|
|
62
|
+
self.extend SyncWrap::Main
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright (c) 2011-2014 David Kellum
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
|
5
|
+
# may not use this file except in compliance with the License. You may
|
|
6
|
+
# 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
|
|
13
|
+
# implied. See the License for the specific language governing
|
|
14
|
+
# permissions and limitations under the License.
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
require 'pathname'
|
|
18
|
+
|
|
19
|
+
module SyncWrap
|
|
20
|
+
|
|
21
|
+
# Utility methods for handling paths.
|
|
22
|
+
module PathUtil
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def caller_path( clr )
|
|
27
|
+
clr.first =~ /^([^:]+):/ && File.dirname( $1 )
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Unless rpath is already absolute, expand it relative to the
|
|
31
|
+
# calling file, as computed from passed in clr (use your
|
|
32
|
+
# Kernel#caller)
|
|
33
|
+
def path_relative_to_caller( rpath, clr ) # :doc:
|
|
34
|
+
unless rpath =~ %r{^/}
|
|
35
|
+
from = caller_path( clr )
|
|
36
|
+
rpath = File.expand_path( rpath, from ) if from
|
|
37
|
+
end
|
|
38
|
+
rpath
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Return path relative to PWD if the result is shorter, otherwise
|
|
42
|
+
# return input path. Preserves any trailing '/'.
|
|
43
|
+
def relativize( path ) # :doc:
|
|
44
|
+
p = Pathname.new( path )
|
|
45
|
+
unless p.relative?
|
|
46
|
+
p = p.relative_path_from( Pathname.pwd ).to_s
|
|
47
|
+
p += '/' if path[-1] == '/'
|
|
48
|
+
path = p if p.length < path.length
|
|
49
|
+
end
|
|
50
|
+
path
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright (c) 2011-2014 David Kellum
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
|
5
|
+
# may not use this file except in compliance with the License. You may
|
|
6
|
+
# 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
|
|
13
|
+
# implied. See the License for the specific language governing
|
|
14
|
+
# permissions and limitations under the License.
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
require 'tmpdir'
|
|
18
|
+
require 'erb'
|
|
19
|
+
require 'fileutils'
|
|
20
|
+
|
|
21
|
+
require 'syncwrap/path_util'
|
|
22
|
+
|
|
23
|
+
module SyncWrap
|
|
24
|
+
|
|
25
|
+
# Low level support for rsync command construction and template
|
|
26
|
+
# processing.
|
|
27
|
+
module Rsync
|
|
28
|
+
include PathUtil
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def rsync_args( host, srcs, target, opts = {} ) # :doc:
|
|
33
|
+
|
|
34
|
+
# -i --itemize-changes, used for counting changed files
|
|
35
|
+
flags = %w[ -i ]
|
|
36
|
+
|
|
37
|
+
# -r --recursive
|
|
38
|
+
flags << '-r' unless opts[:recursive] == false
|
|
39
|
+
|
|
40
|
+
# -l --links (recreate symlinks on the destination)
|
|
41
|
+
flags << '-l' unless opts[:links] == false
|
|
42
|
+
|
|
43
|
+
# -p --perms (set destination to source permissions)
|
|
44
|
+
# -E --executability (perserve execute only, default)
|
|
45
|
+
if opts[:perms] != false
|
|
46
|
+
if opts[:perms] == :p || opts[:perms].is_a?( String )
|
|
47
|
+
flags << '-p'
|
|
48
|
+
if opts[ :perms ].is_a?( String )
|
|
49
|
+
flags << "--chmod=#{opts[ :perms ]}"
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
flags << '-E'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# -c --checksum (to determine changes; not just size,time)
|
|
57
|
+
flags << '-c' unless opts[:checksum] == false
|
|
58
|
+
|
|
59
|
+
# -b --backup (make backups)
|
|
60
|
+
flags << '-b' unless opts[:backup] == false
|
|
61
|
+
|
|
62
|
+
# Pass ssh options via -e (--rsh) flag
|
|
63
|
+
ssh_flags = []
|
|
64
|
+
ssh_flags += opts[ :ssh_flags ] if opts[ :ssh_flags ]
|
|
65
|
+
if opts[ :ssh_options ]
|
|
66
|
+
ssh_flags += opts[:ssh_options].map { |o| ['-o', o.join('=')] }.flatten
|
|
67
|
+
end
|
|
68
|
+
if opts[ :ssh_user ]
|
|
69
|
+
ssh_flags += [ '-l', opts[ :ssh_user ] ]
|
|
70
|
+
ssh_flags += [ '-i', opts[ :ssh_user_pem ] ] if opts[ :ssh_user_pem ]
|
|
71
|
+
end
|
|
72
|
+
flags += [ '-e', "ssh #{ssh_flags.join ' '}" ] unless ssh_flags.empty?
|
|
73
|
+
|
|
74
|
+
if opts[ :user ]
|
|
75
|
+
# Use sudo to place files at remote.
|
|
76
|
+
user = opts[ :user ].to_s
|
|
77
|
+
flags << ( if user != 'root'
|
|
78
|
+
"--rsync-path=sudo -u #{user} rsync"
|
|
79
|
+
else
|
|
80
|
+
"--rsync-path=sudo rsync"
|
|
81
|
+
end )
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
excludes = Array( opts[ :excludes ] )
|
|
85
|
+
flags += excludes.map do |e|
|
|
86
|
+
if e == :dev
|
|
87
|
+
'--cvs-exclude'
|
|
88
|
+
else
|
|
89
|
+
"--exclude=#{e}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
flags << '-n' if opts[ :dryrun ]
|
|
94
|
+
|
|
95
|
+
target = [ host, target ].join(':') unless host == 'localhost'
|
|
96
|
+
|
|
97
|
+
[ 'rsync', flags, srcs, target ].flatten.compact
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def expand_implied_target( srcs ) # :doc:
|
|
101
|
+
#FIXME: Honor absolute arg paths?
|
|
102
|
+
if srcs.length == 1
|
|
103
|
+
target = "/" + srcs.first
|
|
104
|
+
target = File.dirname( target ) + '/' unless target =~ %r{/$}
|
|
105
|
+
else
|
|
106
|
+
target = srcs.pop
|
|
107
|
+
end
|
|
108
|
+
[ srcs, target ]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Resolves each srcs path via #resolve_source! Raises SourceNotFound
|
|
112
|
+
# if any not found.
|
|
113
|
+
def resolve_sources( srcs, sync_paths ) # :doc:
|
|
114
|
+
srcs.map { |path| resolve_source!( path, sync_paths ) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Resolve the specified source path via #resolve_source. Raises
|
|
118
|
+
# SourceNotFound if not found.
|
|
119
|
+
def resolve_source!( path, sync_paths ) # :doc:
|
|
120
|
+
resolve_source( path, sync_paths ) or
|
|
121
|
+
raise( SourceNotFound,
|
|
122
|
+
"#{path.inspect} not found in :sync_paths #{sync_paths.inspect}" )
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Resolve the specified source path to the first existing
|
|
126
|
+
# file/directory in sync_paths roots and returns a #relativize
|
|
127
|
+
# path. Also tries with an .erb suffix if a src does not have a
|
|
128
|
+
# trailing '/'. Preserves any trailing '/'. Returns nil if not
|
|
129
|
+
# found.
|
|
130
|
+
def resolve_source( path, sync_paths ) # :doc:
|
|
131
|
+
#FIXME: Honor absolute arg paths?
|
|
132
|
+
found = nil
|
|
133
|
+
sync_paths.each do |r|
|
|
134
|
+
candidate = File.join( r, path )
|
|
135
|
+
if File.exist?( candidate )
|
|
136
|
+
found = candidate
|
|
137
|
+
elsif candidate !~ %r{(\.erb|/)$}
|
|
138
|
+
candidate += '.erb'
|
|
139
|
+
if File.exist?( candidate )
|
|
140
|
+
found = candidate
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
break if found
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
found && relativize( found )
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Given file path within src, return any sub-directory path needed
|
|
150
|
+
# to reach file, or the empty string. This is also src trailing
|
|
151
|
+
# '/' aware.
|
|
152
|
+
def subpath( src, file ) # :doc:
|
|
153
|
+
src = src.sub( %r{/[^/]*$}, '' ) #remove trail slash or last element
|
|
154
|
+
File.dirname( file ).sub( /^#{src}\/?/, '' )
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Process templates in tmpdir and yield post-processed sources to
|
|
158
|
+
# block, cleaning up on exit.
|
|
159
|
+
def process_templates( srcs, opts ) # :doc:
|
|
160
|
+
bnd = opts[ :erb_binding ] or raise "required :erb_binding param missing"
|
|
161
|
+
erb_mode = opts[ :erb_mode ] || '<>' #Trim new line on "<% ... %>\n"
|
|
162
|
+
mktmpdir( 'syncwrap' ) do |tmp_dir|
|
|
163
|
+
processed_sources = []
|
|
164
|
+
out_dir = File.join( tmp_dir, 'd' ) #for default perms
|
|
165
|
+
srcs.each do |src|
|
|
166
|
+
erbs = find_source_erbs( src )
|
|
167
|
+
outname = nil
|
|
168
|
+
erbs.each do |erb|
|
|
169
|
+
spath = subpath( src, erb )
|
|
170
|
+
outname = File.join( out_dir, spath, File.basename( erb, '.erb' ) )
|
|
171
|
+
FileUtils.mkdir_p( File.dirname( outname ) )
|
|
172
|
+
perm = File.stat( erb ).mode
|
|
173
|
+
File.open( outname, "w", perm ) do |fout|
|
|
174
|
+
template = ERB.new( IO.read( erb ), nil, erb_mode )
|
|
175
|
+
template.filename = erb
|
|
176
|
+
fout.puts( template.result( bnd ) )
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
if erbs.length == 1 && src == erbs.first
|
|
180
|
+
processed_sources << outname
|
|
181
|
+
elsif !erbs.empty?
|
|
182
|
+
processed_sources << ( out_dir + '/' )
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
yield processed_sources
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Just like Dir.mktmpdir but with an attempt to workaround a JRuby
|
|
190
|
+
# 1.6.x bug. See https://jira.codehaus.org/browse/JRUBY-5678
|
|
191
|
+
def mktmpdir( prefix ) # :doc:
|
|
192
|
+
old_env_tmpdir = nil
|
|
193
|
+
newdir = nil
|
|
194
|
+
if defined?( JRUBY_VERSION ) && JRUBY_VERSION =~ /^1.6/
|
|
195
|
+
old_env_tmpdir = ENV['TMPDIR']
|
|
196
|
+
newdir = "/tmp/syncwrap.#{ENV['USER']}"
|
|
197
|
+
FileUtils.mkdir_p( newdir, mode: 0700 )
|
|
198
|
+
ENV['TMPDIR'] = newdir
|
|
199
|
+
end
|
|
200
|
+
Dir.mktmpdir( 'syncwrap' ) do |tmp_dir|
|
|
201
|
+
yield tmp_dir
|
|
202
|
+
end
|
|
203
|
+
ensure
|
|
204
|
+
FileUtils.rmdir( newdir ) if newdir
|
|
205
|
+
ENV['TMPDIR'] = old_env_tmpdir if old_env_tmpdir
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def find_source_erbs( sources ) # :doc:
|
|
209
|
+
Array( sources ).inject([]) do |list, src|
|
|
210
|
+
if File.directory?( src )
|
|
211
|
+
list += find_source_erbs( expand_entries( src ) )
|
|
212
|
+
elsif src =~ /\.erb$/
|
|
213
|
+
list << src
|
|
214
|
+
end
|
|
215
|
+
list
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def expand_entries( src ) # :doc:
|
|
220
|
+
Dir.entries( src ).
|
|
221
|
+
reject { |e| e =~ /^\.+$/ }.
|
|
222
|
+
map { |e| File.join( src, e ) }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
end
|