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,110 @@
|
|
|
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
|
+
module SyncWrap
|
|
18
|
+
|
|
19
|
+
# A Support module for Ruby VM components, also providing gem
|
|
20
|
+
# handling utilties which are largely common to all Ruby VMs.
|
|
21
|
+
module RubySupport
|
|
22
|
+
|
|
23
|
+
# The name of the gem command to be installed/used (default: gem)
|
|
24
|
+
attr_accessor :gem_command
|
|
25
|
+
|
|
26
|
+
# Default gem install arguments (default: --no-rdoc, --no-ri)
|
|
27
|
+
attr_accessor :gem_install_args
|
|
28
|
+
|
|
29
|
+
def initialize( *args )
|
|
30
|
+
@gem_command = 'gem'
|
|
31
|
+
@gem_install_args = %w[ --no-rdoc --no-ri ]
|
|
32
|
+
|
|
33
|
+
super( *args )
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def gemrc_path
|
|
37
|
+
"/etc/gemrc"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Install gemrc file to gemrc_path
|
|
41
|
+
def install_gemrc
|
|
42
|
+
rput( 'etc/gemrc', gemrc_path, user: :root )
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Install the specified gem.
|
|
46
|
+
#
|
|
47
|
+
# === Options
|
|
48
|
+
#
|
|
49
|
+
# :version:: Version specifier array or single value, like in a
|
|
50
|
+
# gemspec. (Default: nil -> latest) Examples:
|
|
51
|
+
#
|
|
52
|
+
# '1.1.0'
|
|
53
|
+
# '~> 1.1'
|
|
54
|
+
# ['>=1.0', '<1.2']
|
|
55
|
+
#
|
|
56
|
+
# :user_install:: If true, perform a --user-install as the current
|
|
57
|
+
# user. Otherwise system install with sudo (the
|
|
58
|
+
# default, false).
|
|
59
|
+
#
|
|
60
|
+
# :check:: If true, capture output and return the number of gems
|
|
61
|
+
# actually installed. Combine with :minimize to only
|
|
62
|
+
# install what is required, and short circuit when zero
|
|
63
|
+
# gems installed. (Default: false)
|
|
64
|
+
#
|
|
65
|
+
# :minimize:: Use --conservative and --minimal-deps (rubygems
|
|
66
|
+
# 2.1.5+, #min_deps_supported?) flags to reduce
|
|
67
|
+
# installs to the minimum required to satisfy the
|
|
68
|
+
# version requirements. (Default: true)
|
|
69
|
+
#
|
|
70
|
+
def gem_install( gem, opts = {} )
|
|
71
|
+
cmd = [ gem_command, 'install',
|
|
72
|
+
gem_install_args,
|
|
73
|
+
( '--user-install' if opts[ :user_install ] ),
|
|
74
|
+
( '--conservative' if opts[ :minimize] != false ),
|
|
75
|
+
( '--minimal-deps' if opts[ :minimize] != false &&
|
|
76
|
+
min_deps_supported? ),
|
|
77
|
+
gem_version_flags( opts[ :version ] ),
|
|
78
|
+
gem ].flatten.compact.join( ' ' )
|
|
79
|
+
|
|
80
|
+
shopts = opts[ :user_install ] ? {} : {user: :root}
|
|
81
|
+
|
|
82
|
+
if opts[ :check ]
|
|
83
|
+
_,out = capture( cmd, shopts.merge!( accept: 0 ) )
|
|
84
|
+
|
|
85
|
+
count = 0
|
|
86
|
+
out.split( "\n" ).each do |oline|
|
|
87
|
+
if oline =~ /^\s+(\d+)\s+gem(s)?\s+installed/
|
|
88
|
+
count = $1.to_i
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
count
|
|
92
|
+
else
|
|
93
|
+
sh( cmd, shopts )
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
def min_deps_supported?
|
|
101
|
+
true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def gem_version_flags( reqs )
|
|
105
|
+
Array( reqs ).flatten.compact.map { |req| "-v'#{req}'" }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
@@ -0,0 +1,207 @@
|
|
|
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
|
+
# The non-blocking aspects of Shell::capture3 below were partially
|
|
17
|
+
# derived from rake-remote_task, released under MIT License:
|
|
18
|
+
# Copyright (c) Ryan Davis, RubyHitSquad
|
|
19
|
+
#++
|
|
20
|
+
|
|
21
|
+
require 'open3'
|
|
22
|
+
|
|
23
|
+
module SyncWrap
|
|
24
|
+
|
|
25
|
+
# Low level command construction and process output capture.
|
|
26
|
+
#
|
|
27
|
+
# == Supported Commands
|
|
28
|
+
#
|
|
29
|
+
# Local:
|
|
30
|
+
# bash [-v|-x -e -n] -c COMMANDS
|
|
31
|
+
# sudo :sudo_flags [-u user] bash [-v|-x -e -n] -c COMMANDS
|
|
32
|
+
#
|
|
33
|
+
# Remote:
|
|
34
|
+
# ssh :ssh_flags :host bash [-v|-x -e -n] -c "COMMANDS"
|
|
35
|
+
# ssh :ssh_flags :host sudo :sudo_flags [-u user] bash [-v|-x -e -n] -c "COMMANDS"
|
|
36
|
+
#
|
|
37
|
+
# Example ssh_flags: -i ./key.pem -l ec2-user
|
|
38
|
+
#
|
|
39
|
+
# Example sudo_flags: -H
|
|
40
|
+
module Shell
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# When host is not 'localhost' return ssh command, flags,
|
|
45
|
+
# arguments on top of #sudo_args. Otherwise pass through to
|
|
46
|
+
# #sudo_args.
|
|
47
|
+
def ssh_args( host, command, opts = {} ) # :doc:
|
|
48
|
+
args = []
|
|
49
|
+
if host != 'localhost'
|
|
50
|
+
opts = opts.dup
|
|
51
|
+
coalesce = opts.delete( :coalesce )
|
|
52
|
+
args = [ 'ssh' ]
|
|
53
|
+
args += opts[ :ssh_flags ] if opts[ :ssh_flags ]
|
|
54
|
+
if opts[ :ssh_options ]
|
|
55
|
+
args += opts[ :ssh_options ].map { |o| [ '-o', o.join('=') ] }.flatten
|
|
56
|
+
end
|
|
57
|
+
if opts[ :ssh_user ]
|
|
58
|
+
args += [ '-l', opts[ :ssh_user ] ]
|
|
59
|
+
args += [ '-i', opts[ :ssh_user_pem ] ] if opts[ :ssh_user_pem ]
|
|
60
|
+
end
|
|
61
|
+
args << host.to_s
|
|
62
|
+
sargs = sudo_args( command, opts )
|
|
63
|
+
cmd = sargs.pop
|
|
64
|
+
args += sargs
|
|
65
|
+
args << ( '"' + shell_escape_cmd( cmd ) + '"' )
|
|
66
|
+
args << '1>&2' if coalesce
|
|
67
|
+
args
|
|
68
|
+
else
|
|
69
|
+
sudo_args( command, opts )
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Return sudo command, flags, arguments on top of #sh_args if the
|
|
74
|
+
# :user option is specified. Otherwise pass through to #sh_args.
|
|
75
|
+
def sudo_args( command, opts = {} ) # :doc:
|
|
76
|
+
args = []
|
|
77
|
+
if opts[ :user ]
|
|
78
|
+
args = [ 'sudo' ]
|
|
79
|
+
args += opts[ :sudo_flags ] if opts[ :sudo_flags ]
|
|
80
|
+
# FIXME: Replace with :sudo_home support for '-H'?
|
|
81
|
+
args += [ '-u', opts[ :user ] ] unless opts[ :user ] == :root
|
|
82
|
+
end
|
|
83
|
+
args + sh_args( command, opts )
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Return bash command, flags, arguments for the given command(s)
|
|
87
|
+
# passed to #commmand_lines_cleanup.
|
|
88
|
+
def sh_args( command, opts = {} ) # :doc:
|
|
89
|
+
args = [ 'bash' ]
|
|
90
|
+
args << '-e' if opts[ :error ].nil? || opts[ :error ] == :exit
|
|
91
|
+
args << '-n' if opts[ :dryrun ]
|
|
92
|
+
|
|
93
|
+
if opts[ :coalesce ]
|
|
94
|
+
args << '-c'
|
|
95
|
+
cmd = "exec 1>&2\n"
|
|
96
|
+
if opts[ :sh_verbose ]
|
|
97
|
+
cmd << "set " << ( opts[ :sh_verbose ] == :x ? '-x' : '-v' ) << "\n"
|
|
98
|
+
end
|
|
99
|
+
cmd << command_lines_cleanup( command )
|
|
100
|
+
args << cmd
|
|
101
|
+
else
|
|
102
|
+
if opts[ :sh_verbose ]
|
|
103
|
+
args << ( opts[ :sh_verbose ] == :x ? '-x' : '-v' )
|
|
104
|
+
end
|
|
105
|
+
args << '-c'
|
|
106
|
+
args << command_lines_cleanup( command )
|
|
107
|
+
end
|
|
108
|
+
args
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Escape the provided cmd string for inclusion in a bash quoted
|
|
112
|
+
# string command. This is only needed when using ssh.
|
|
113
|
+
def shell_escape_cmd( cmd ) # :doc:
|
|
114
|
+
cmd.gsub( /["$`\\]/ ) { |c| '\\' + c }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Given one or an Array of commands, apply #block_trim_padding to
|
|
118
|
+
# each and join all with newlines.
|
|
119
|
+
def command_lines_cleanup( commands ) # :doc:
|
|
120
|
+
Array( commands )
|
|
121
|
+
.map { |cmd| block_trim_padding( cmd.split( $/ ) ) }
|
|
122
|
+
.flatten
|
|
123
|
+
.join( "\n" )
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Left strip lines, but preserve increased indentation in
|
|
127
|
+
# subsequent lines. Also right strip and drop blank lines
|
|
128
|
+
def block_trim_padding( lines ) # :doc:
|
|
129
|
+
pad = nil
|
|
130
|
+
lines
|
|
131
|
+
.reject { |l| l =~ /^\s*$/ } #blank lines
|
|
132
|
+
.map do |line|
|
|
133
|
+
line = line.dup
|
|
134
|
+
unless pad && line.gsub!(/^(\s{,#{pad}})/, '')
|
|
135
|
+
prior = line.length
|
|
136
|
+
line.gsub!(/^(\s*)/, '')
|
|
137
|
+
pad = prior - line.length
|
|
138
|
+
end
|
|
139
|
+
line.rstrip!
|
|
140
|
+
line
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Captures out and err from a command expressed by args
|
|
145
|
+
# array. Returns [ exit_status, [outputs] ] where [outputs] is an
|
|
146
|
+
# array of [:err|:out, buffer] elements. Uses select, non-blocking
|
|
147
|
+
# I/O to receive buffers in the order they become available. This
|
|
148
|
+
# is often the same order you would see them in a real interactive
|
|
149
|
+
# terminal, but not always, as buffering or timing issues in the
|
|
150
|
+
# underlying implementation may cause some out of order results.
|
|
151
|
+
def capture3( args ) # :doc:
|
|
152
|
+
status = nil
|
|
153
|
+
outputs = []
|
|
154
|
+
Open3::popen3( *args ) do |inp, out, err, wait_thread|
|
|
155
|
+
inp.close rescue nil
|
|
156
|
+
|
|
157
|
+
streams = [ err, out ]
|
|
158
|
+
|
|
159
|
+
until streams.empty? do
|
|
160
|
+
selected, = select( streams, nil, nil, 0.1 )
|
|
161
|
+
next if selected.nil? || selected.empty?
|
|
162
|
+
|
|
163
|
+
selected.each do |stream|
|
|
164
|
+
if stream.eof?
|
|
165
|
+
streams.delete( stream )
|
|
166
|
+
next
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
chunk = stream.readpartial( 8192 )
|
|
170
|
+
marker = (stream == out) ? :out : :err
|
|
171
|
+
|
|
172
|
+
yield( marker, chunk ) if block_given?
|
|
173
|
+
|
|
174
|
+
# Merge chunks from the same stream
|
|
175
|
+
l = outputs.last
|
|
176
|
+
if l && l[0] == marker
|
|
177
|
+
l[1] += chunk
|
|
178
|
+
else
|
|
179
|
+
outputs << [ marker, chunk ]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Older jruby (even in 1.9+ mode) doesn't provide wait_thread but
|
|
186
|
+
# does return the status in $? instead (see workaround below)
|
|
187
|
+
status = wait_thread.value if wait_thread
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
#FIXME: Only if jruby?
|
|
191
|
+
status ||= $?
|
|
192
|
+
|
|
193
|
+
[ status && status.exitstatus, outputs ]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Select and merge the output buffers of the specific stream from
|
|
197
|
+
# outputs (as returned by #capture3)
|
|
198
|
+
def collect_stream( stream, outputs ) # :doc:
|
|
199
|
+
outputs.
|
|
200
|
+
select { |o| o[0] == stream }.
|
|
201
|
+
map { |o| o[1] }. #the buffers
|
|
202
|
+
inject( "", :+ )
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
data/lib/syncwrap.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#--
|
|
2
|
-
# Copyright (c) 2011-
|
|
2
|
+
# Copyright (c) 2011-2014 David Kellum
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
|
5
5
|
# may not use this file except in compliance with the License. You may
|
|
@@ -14,4 +14,370 @@
|
|
|
14
14
|
# permissions and limitations under the License.
|
|
15
15
|
#++
|
|
16
16
|
|
|
17
|
+
require 'thread'
|
|
18
|
+
|
|
17
19
|
require 'syncwrap/base'
|
|
20
|
+
require 'syncwrap/component'
|
|
21
|
+
require 'syncwrap/context'
|
|
22
|
+
require 'syncwrap/host'
|
|
23
|
+
require 'syncwrap/formatter'
|
|
24
|
+
require 'syncwrap/path_util'
|
|
25
|
+
|
|
26
|
+
module SyncWrap
|
|
27
|
+
|
|
28
|
+
# Base class for all SyncWrap exception types
|
|
29
|
+
class SyncError < RuntimeError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Error in the context in which a component is used
|
|
33
|
+
class ContextError < SyncError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Error in use of the Context#sh block form (:close option) with
|
|
37
|
+
# a nested flush or change in options.
|
|
38
|
+
class NestingError < SyncError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Source specified in rput can not be found in :sync_paths
|
|
42
|
+
class SourceNotFound < SyncError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Context#sh or derivatives failed with non-accepted exit code.
|
|
46
|
+
# Note the error may be delayed until the next Context#flush.
|
|
47
|
+
class CommandFailure < SyncError
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Serves as the container for #hosts and roles and provides the top
|
|
51
|
+
# level #execute.
|
|
52
|
+
class Space
|
|
53
|
+
include PathUtil
|
|
54
|
+
|
|
55
|
+
# Return the current space, as setup within a Space#with block, or
|
|
56
|
+
# raise something fierce.
|
|
57
|
+
def self.current
|
|
58
|
+
Thread.current[:syncwrap_current_space] or
|
|
59
|
+
raise "Space.current called outside of Space#with/thread"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Default options, for use including Component#rput, Component#sh,
|
|
63
|
+
# and #execute (see Options details). The CLI uses this, for
|
|
64
|
+
# example, to set :verbose => true (from --verbose) and
|
|
65
|
+
# :shell_verbose => :x (from --expand-shell). In limited cases it
|
|
66
|
+
# may be appropriate to set default overrides in a sync.rb.
|
|
67
|
+
attr_reader :default_options
|
|
68
|
+
|
|
69
|
+
attr_reader :formatter #:nodoc:
|
|
70
|
+
|
|
71
|
+
def initialize
|
|
72
|
+
@provider = nil
|
|
73
|
+
@roles = Hash.new { |h,k| h[k] = [] }
|
|
74
|
+
@hosts = {}
|
|
75
|
+
@default_options = {
|
|
76
|
+
coalesce: true,
|
|
77
|
+
sh_verbose: :v,
|
|
78
|
+
sync_paths: [ File.join( SyncWrap::GEM_ROOT, 'sync' ) ] }
|
|
79
|
+
@formatter = Formatter.new
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Load the specified file path as per a sync.rb, into this
|
|
83
|
+
# Space. If relative, path is assumed to be relative to the caller
|
|
84
|
+
# (i.e. Rakefile, etc.) as with the conventional 'sync' directory.
|
|
85
|
+
def load_sync_file_relative( fpath = './sync.rb' )
|
|
86
|
+
load_sync_file( path_relative_to_caller( fpath, caller ) )
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Load the specified filename as per a sync.rb, into this Space.
|
|
90
|
+
# This uses a #with block internally.
|
|
91
|
+
def load_sync_file( filename )
|
|
92
|
+
require 'syncwrap/main'
|
|
93
|
+
with do
|
|
94
|
+
load( filename, true )
|
|
95
|
+
# This is true -> wrapped to avoid pollution of sync
|
|
96
|
+
# namespace. This is particularly important given the dynamic
|
|
97
|
+
# binding scheme of components. If not done, top-level
|
|
98
|
+
# methods/vars in sync.rb would have precidents over
|
|
99
|
+
# component methods.
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Make self the Space.current inside of block.
|
|
104
|
+
def with
|
|
105
|
+
prior = Thread.current[:syncwrap_current_space]
|
|
106
|
+
raise "Invalid Space#with nesting!" if prior && prior != self
|
|
107
|
+
begin
|
|
108
|
+
Thread.current[:syncwrap_current_space] = self
|
|
109
|
+
yield self
|
|
110
|
+
ensure
|
|
111
|
+
Thread.current[:syncwrap_current_space] = prior
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A hosting/cloud provider for creating/removing hosts from this
|
|
116
|
+
# space. See #use_provider
|
|
117
|
+
def provider
|
|
118
|
+
@provider or raise "No provider set via space.use_provider"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Merge the specified options to default_options
|
|
122
|
+
def merge_default_options( opts )
|
|
123
|
+
@default_options.merge!( opts )
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Prepend the given directory path to front of the :sync_paths
|
|
128
|
+
# list. If relative, path is assumed to be relative to the caller
|
|
129
|
+
# (i.e. sync.rb) as with the conventional 'sync'
|
|
130
|
+
# directory. Returns a copy of the resultant sync_paths list.
|
|
131
|
+
def prepend_sync_path( rpath = 'sync' )
|
|
132
|
+
rpath = path_relative_to_caller( rpath, caller )
|
|
133
|
+
|
|
134
|
+
roots = default_options[ :sync_paths ]
|
|
135
|
+
roots.delete( rpath ) # don't duplicate but move to front
|
|
136
|
+
roots.unshift( rpath )
|
|
137
|
+
roots.dup #return a copy
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Create a new instance of the specified provider class for use,
|
|
141
|
+
# passing self and the given options.
|
|
142
|
+
def use_provider( provider_class, opts = {} )
|
|
143
|
+
opts = opts.merge( sync_file_path: caller_path( caller ) )
|
|
144
|
+
@provider = provider_class.new( self, opts )
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Define/access a Role by symbol.
|
|
148
|
+
# Additional args are interpreted as Components to add to this
|
|
149
|
+
# role.
|
|
150
|
+
def role( symbol, *args )
|
|
151
|
+
if args.empty?
|
|
152
|
+
@roles[ symbol.to_sym ]
|
|
153
|
+
else
|
|
154
|
+
@roles[ symbol.to_sym ] += args.flatten.compact
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Define/access a Host by name.
|
|
159
|
+
#
|
|
160
|
+
# If first arg is a String, it is interpreted as the name
|
|
161
|
+
# property. The name property is also used for lookup and thus
|
|
162
|
+
# must be Space unique. Additional args are interpreted as role
|
|
163
|
+
# symbols or (direct) Components to add to this Host. Each role
|
|
164
|
+
# will only be added once. A final Hash argument is interpreted as
|
|
165
|
+
# properties to add or merge with the host.
|
|
166
|
+
def host( *args )
|
|
167
|
+
props = args.last.is_a?( Hash ) && args.pop || {}
|
|
168
|
+
name = args.first.is_a?( String ) && args.shift
|
|
169
|
+
props = props.merge( name: name ) if name
|
|
170
|
+
raise "Missing required name parameter" unless props[ :name ]
|
|
171
|
+
host = @hosts[ props[ :name ] ] ||= Host.new( self )
|
|
172
|
+
host.merge_props( props )
|
|
173
|
+
host.add( *args )
|
|
174
|
+
host
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# All Host instances, in order added.
|
|
178
|
+
def hosts
|
|
179
|
+
@hosts.values
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def host_names
|
|
183
|
+
@hosts.keys
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Return an ordered, unique set of component classes, direct or via
|
|
187
|
+
# roles, currently contained by the specified hosts or all hosts.
|
|
188
|
+
def component_classes( hs = hosts )
|
|
189
|
+
hs.
|
|
190
|
+
map { |h| h.components }.
|
|
191
|
+
flatten.
|
|
192
|
+
map { |comp| comp.class }.
|
|
193
|
+
uniq
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Returns a new component_plan from plan, looking up any Class
|
|
197
|
+
# string names and using :install for any missing methods:
|
|
198
|
+
#
|
|
199
|
+
# \[ [ Class | String, Symbol? ] ... ] -> [ [ Class, Symbol ] ... ]
|
|
200
|
+
#
|
|
201
|
+
# Class name lookup is by unqualified matching against
|
|
202
|
+
# #component_classes (already added to hosts of this space.) If
|
|
203
|
+
# such String match is ambiguous or not found, a RuntimeError is
|
|
204
|
+
# raised.
|
|
205
|
+
def resolve_component_plan( plan )
|
|
206
|
+
classes = component_classes
|
|
207
|
+
plan.map do |clz,mth|
|
|
208
|
+
if clz.is_a?( String )
|
|
209
|
+
found = classes.select { |cc| cc.name =~ /(^|::)#{clz}$/ }
|
|
210
|
+
if found.length == 1
|
|
211
|
+
clz = found.first
|
|
212
|
+
else
|
|
213
|
+
raise "Class \"#{clz}\" ambiguous or not found: #{found.inspect}"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
[ clz, mth && mth.to_sym || :install ]
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Execute components based on a host_list (default all), a
|
|
221
|
+
# component_plan (default :install on all components), and with
|
|
222
|
+
# any additional options (merged with default_options).
|
|
223
|
+
#
|
|
224
|
+
# === Options
|
|
225
|
+
#
|
|
226
|
+
# The following options are specifically handled by execute:
|
|
227
|
+
#
|
|
228
|
+
# :colorize:: If false, don't color diagnostic output to stdout
|
|
229
|
+
# (default: true)
|
|
230
|
+
#
|
|
231
|
+
# :threads:: The number of threads on which to execute. Each host is
|
|
232
|
+
# executed with a single thread.
|
|
233
|
+
# (Default: the number of hosts, maximum concurrency)
|
|
234
|
+
#
|
|
235
|
+
def execute( host_list = hosts, component_plan = [], opts = {} )
|
|
236
|
+
opts = default_options.merge( opts )
|
|
237
|
+
@formatter.colorize = ( opts[ :colorize ] != false )
|
|
238
|
+
component_plan = resolve_component_plan( component_plan )
|
|
239
|
+
|
|
240
|
+
if opts[ :threads ] && host_list.length > opts[ :threads ]
|
|
241
|
+
queue = Queue.new
|
|
242
|
+
host_list.each { |host| queue.push( host ) }
|
|
243
|
+
threads = opts[ :threads ].times.map do
|
|
244
|
+
Thread.new( queue, component_plan, opts ) do |q, cp, o|
|
|
245
|
+
success = true
|
|
246
|
+
begin
|
|
247
|
+
while host = q.pop( true ) # non-block
|
|
248
|
+
r = execute_host( host, cp, o )
|
|
249
|
+
success &&= r
|
|
250
|
+
end
|
|
251
|
+
rescue ThreadError
|
|
252
|
+
#exit, from queue being empty
|
|
253
|
+
end
|
|
254
|
+
success
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
else
|
|
258
|
+
threads = host_list.map do |host|
|
|
259
|
+
Thread.new( host, component_plan, opts ) do |h, cp, o|
|
|
260
|
+
execute_host( h, cp, o )
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
threads.inject(true) { |s,t| t.value && s }
|
|
265
|
+
# Note: Unhandled (i.e. non-SyncError) exceptions will be
|
|
266
|
+
# propigated and re-raised on call to value above, resulting in
|
|
267
|
+
# standard ruby stack trace and immediate exit.
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Given a Host, determine the address to use for ssh (incl. rsync)
|
|
271
|
+
# access. The following properties are used in order of decreasing
|
|
272
|
+
# preference: internet_name, internet_ip and host.name.
|
|
273
|
+
def ssh_host_name( host )
|
|
274
|
+
# This is included here for expected Space-wide policy settings.
|
|
275
|
+
host[ :internet_name ] || host[ :internet_ip ] || host.name
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
def execute_host( host, component_plan = [], opts = {} )
|
|
281
|
+
# Important: resolve outside of context
|
|
282
|
+
comp_methods = resolve_component_methods(host.components, component_plan)
|
|
283
|
+
ctx = Context.new( host, opts )
|
|
284
|
+
ctx.with do
|
|
285
|
+
comp_methods.each do |comp, mths|
|
|
286
|
+
success = mths.inject(true) do |s, mth|
|
|
287
|
+
# short-circuit after first non-success
|
|
288
|
+
s && execute_component( ctx, host, comp, mth, opts )
|
|
289
|
+
end
|
|
290
|
+
return false unless success
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
true
|
|
294
|
+
rescue SyncError => e
|
|
295
|
+
formatter.sync do
|
|
296
|
+
formatter.write_error( host, e )
|
|
297
|
+
end
|
|
298
|
+
false
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Given components and plan, return an ordered Array of
|
|
302
|
+
# \[component, [method,...]] to execute. An empty/default plan is
|
|
303
|
+
# interpreted as :install on all components which implement it. If
|
|
304
|
+
# :install is explicitly part of the plan, then it trumps any
|
|
305
|
+
# other methods listed for the same component.
|
|
306
|
+
#
|
|
307
|
+
# Note this must be run out-of-Context to avoid unintended dynamic
|
|
308
|
+
# binding of :install or other methods.
|
|
309
|
+
def resolve_component_methods( components, component_plan = [] )
|
|
310
|
+
components.map do |comp|
|
|
311
|
+
mths = []
|
|
312
|
+
if component_plan.empty?
|
|
313
|
+
mths = [ :install ] if comp.respond_to?( :install )
|
|
314
|
+
else
|
|
315
|
+
found = component_plan.select { |cls,_| comp.kind_of?( cls ) }
|
|
316
|
+
mths = found.map { |_,mth| mth }
|
|
317
|
+
mths = [ :install ] if mths.include?( :install ) #trumps
|
|
318
|
+
end
|
|
319
|
+
[ comp, mths ] unless mths.empty?
|
|
320
|
+
end.compact
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def execute_component( ctx, host, comp, mth, opts )
|
|
324
|
+
|
|
325
|
+
formatter.sync do
|
|
326
|
+
formatter.write_component( host, comp, mth,
|
|
327
|
+
opts[ :flush_component ] ? "start" : "enqueue" )
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
comp.send( mth )
|
|
331
|
+
|
|
332
|
+
ctx.flush if opts[ :flush_component ]
|
|
333
|
+
|
|
334
|
+
if opts[ :flush_component ]
|
|
335
|
+
formatter.sync do
|
|
336
|
+
formatter.write_component( host, comp, mth, "complete" )
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
true
|
|
341
|
+
rescue SyncError => e
|
|
342
|
+
formatter.sync do
|
|
343
|
+
if e.is_a?( CommandFailure ) && ! opts[ :flush_component ]
|
|
344
|
+
# We don't know if its really from this component/method
|
|
345
|
+
formatter.write_error( host, e )
|
|
346
|
+
else
|
|
347
|
+
formatter.write_error( host, e, comp, mth )
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
false
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Setup autoloads for all included components. This should not be
|
|
356
|
+
# risky in SyncWrap given that all of these should be loaded before
|
|
357
|
+
# any threads are in play.
|
|
358
|
+
|
|
359
|
+
autoload :CommercialJDK, 'syncwrap/components/commercial_jdk'
|
|
360
|
+
autoload :CRubyVM, 'syncwrap/components/cruby_vm'
|
|
361
|
+
autoload :EtcHosts, 'syncwrap/components/etc_hosts'
|
|
362
|
+
autoload :Geminabox, 'syncwrap/components/geminabox'
|
|
363
|
+
autoload :Hashdot, 'syncwrap/components/hashdot'
|
|
364
|
+
autoload :Iyyov, 'syncwrap/components/iyyov'
|
|
365
|
+
autoload :IyyovDaemon, 'syncwrap/components/iyyov_daemon'
|
|
366
|
+
autoload :JRubyVM, 'syncwrap/components/jruby_vm'
|
|
367
|
+
autoload :MDRaid, 'syncwrap/components/mdraid'
|
|
368
|
+
autoload :Network, 'syncwrap/components/network'
|
|
369
|
+
autoload :OpenJDK, 'syncwrap/components/open_jdk'
|
|
370
|
+
autoload :PostgreSQL, 'syncwrap/components/postgresql'
|
|
371
|
+
autoload :Qpid, 'syncwrap/components/qpid'
|
|
372
|
+
autoload :QpidRepo, 'syncwrap/components/qpid'
|
|
373
|
+
autoload :RHEL, 'syncwrap/components/rhel'
|
|
374
|
+
autoload :RunUser, 'syncwrap/components/run_user'
|
|
375
|
+
autoload :Ubuntu, 'syncwrap/components/ubuntu'
|
|
376
|
+
autoload :Users, 'syncwrap/components/users'
|
|
377
|
+
|
|
378
|
+
# (Alpha sort class name)
|
|
379
|
+
|
|
380
|
+
# Additional autoloads (optional support)
|
|
381
|
+
autoload :AmazonEC2, 'syncwrap/amazon_ec2'
|
|
382
|
+
|
|
383
|
+
end
|
data/{etc → sync/etc}/gemrc
RENAMED