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