syncwrap 1.5.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/History.rdoc +19 -0
  3. data/Manifest.txt +82 -34
  4. data/README.rdoc +96 -48
  5. data/Rakefile +0 -65
  6. data/bin/syncwrap +27 -0
  7. data/examples/LAYOUT.rdoc +70 -0
  8. data/examples/Rakefile +16 -0
  9. data/examples/ec2.rb +44 -0
  10. data/examples/hello.rb +14 -0
  11. data/examples/hello_binding.rb +27 -0
  12. data/examples/jruby.rb +11 -0
  13. data/examples/private/aws.json +4 -0
  14. data/examples/rput.rb +24 -0
  15. data/examples/sync/home/bob/.ssh/authorized_keys +1 -0
  16. data/examples/sync/tmp/sample.erb +3 -0
  17. data/lib/syncwrap/amazon_ec2.rb +236 -0
  18. data/lib/syncwrap/amazon_ws.rb +308 -0
  19. data/lib/syncwrap/base.rb +4 -2
  20. data/lib/syncwrap/cli.rb +328 -0
  21. data/lib/syncwrap/component.rb +443 -0
  22. data/lib/syncwrap/components/commercial_jdk.rb +76 -0
  23. data/lib/syncwrap/components/cruby_vm.rb +144 -0
  24. data/lib/syncwrap/components/etc_hosts.rb +44 -0
  25. data/lib/syncwrap/{geminabox.rb → components/geminabox.rb} +12 -17
  26. data/lib/syncwrap/components/hashdot.rb +97 -0
  27. data/lib/syncwrap/components/iyyov.rb +144 -0
  28. data/lib/syncwrap/components/iyyov_daemon.rb +125 -0
  29. data/lib/syncwrap/components/jruby_vm.rb +122 -0
  30. data/lib/syncwrap/components/mdraid.rb +204 -0
  31. data/lib/syncwrap/components/network.rb +99 -0
  32. data/lib/syncwrap/components/open_jdk.rb +70 -0
  33. data/lib/syncwrap/components/postgresql.rb +159 -0
  34. data/lib/syncwrap/components/qpid.rb +303 -0
  35. data/lib/syncwrap/components/rhel.rb +71 -0
  36. data/lib/syncwrap/components/run_user.rb +99 -0
  37. data/lib/syncwrap/components/ubuntu.rb +85 -0
  38. data/lib/syncwrap/components/users.rb +200 -0
  39. data/lib/syncwrap/context.rb +260 -0
  40. data/lib/syncwrap/distro.rb +53 -60
  41. data/lib/syncwrap/formatter.rb +149 -0
  42. data/lib/syncwrap/host.rb +134 -0
  43. data/lib/syncwrap/main.rb +62 -0
  44. data/lib/syncwrap/path_util.rb +55 -0
  45. data/lib/syncwrap/rsync.rb +227 -0
  46. data/lib/syncwrap/ruby_support.rb +110 -0
  47. data/lib/syncwrap/shell.rb +207 -0
  48. data/lib/syncwrap.rb +367 -1
  49. data/{etc → sync/etc}/gemrc +1 -3
  50. data/sync/etc/hosts.erb +8 -0
  51. data/{etc/init.d/iyyov → sync/etc/init.d/iyyov.erb} +35 -7
  52. data/sync/etc/sysconfig/pgsql/postgresql.erb +2 -0
  53. data/sync/src/hashdot/Makefile.erb +98 -0
  54. data/sync/src/hashdot/profiles/default.hdp.erb +25 -0
  55. data/sync/src/hashdot/profiles/jruby-common.hdp +28 -0
  56. data/sync/src/hashdot/profiles/jruby-shortlived.hdp +9 -0
  57. data/sync/src/hashdot/profiles/jruby.hdp.erb +13 -0
  58. data/sync/src/hashdot/profiles/shortlived.hdp +6 -0
  59. data/sync/var/iyyov/default/config.rb +1 -0
  60. data/sync/var/iyyov/default/daemon.rb.erb +15 -0
  61. data/sync/var/iyyov/jobs.rb.erb +4 -0
  62. data/test/muddled_sync.rb +13 -0
  63. data/test/setup.rb +39 -0
  64. data/test/sync/d1/bar +1 -0
  65. data/test/sync/d1/foo.erb +1 -0
  66. data/test/sync/d3/d2/bar +1 -0
  67. data/test/sync/d3/d2/foo.erb +1 -0
  68. data/test/test_components.rb +108 -0
  69. data/test/test_context.rb +107 -0
  70. data/test/test_context_rput.rb +289 -0
  71. data/test/test_rsync.rb +138 -0
  72. data/test/test_shell.rb +233 -0
  73. data/test/test_space.rb +218 -0
  74. data/test/test_space_main.rb +40 -0
  75. data/test/zfile +1 -0
  76. metadata +204 -71
  77. data/etc/sysconfig/pgsql/postgresql +0 -2
  78. data/lib/syncwrap/aws.rb +0 -448
  79. data/lib/syncwrap/common.rb +0 -161
  80. data/lib/syncwrap/ec2.rb +0 -59
  81. data/lib/syncwrap/hashdot.rb +0 -70
  82. data/lib/syncwrap/iyyov.rb +0 -139
  83. data/lib/syncwrap/java.rb +0 -61
  84. data/lib/syncwrap/jruby.rb +0 -118
  85. data/lib/syncwrap/postgresql.rb +0 -135
  86. data/lib/syncwrap/qpid.rb +0 -251
  87. data/lib/syncwrap/remote_task.rb +0 -199
  88. data/lib/syncwrap/rhel.rb +0 -67
  89. data/lib/syncwrap/ubuntu.rb +0 -78
  90. data/lib/syncwrap/user_run.rb +0 -102
  91. data/test/test_syncwrap.rb +0 -202
  92. data/var/iyyov/jobs.rb +0 -11
  93. /data/{etc → sync/etc}/corosync/corosync.conf +0 -0
  94. /data/{etc → sync/etc}/corosync/uidgid.d/qpid +0 -0
  95. /data/{etc → sync/etc}/init.d/qpidd +0 -0
  96. /data/{etc → sync/etc}/sysctl.d/61-postgresql-shm.conf +0 -0
  97. /data/{usr/local → sync/jruby}/bin/jgem +0 -0
  98. /data/{postgresql → sync/postgresql}/rhel/pg_hba.conf +0 -0
  99. /data/{postgresql → sync/postgresql}/rhel/pg_ident.conf +0 -0
  100. /data/{postgresql → sync/postgresql}/rhel/postgresql.conf +0 -0
  101. /data/{postgresql → sync/postgresql}/ubuntu/environment +0 -0
  102. /data/{postgresql → sync/postgresql}/ubuntu/pg_ctl.conf +0 -0
  103. /data/{postgresql → sync/postgresql}/ubuntu/pg_hba.conf +0 -0
  104. /data/{postgresql → sync/postgresql}/ubuntu/pg_ident.conf +0 -0
  105. /data/{postgresql → sync/postgresql}/ubuntu/postgresql.conf +0 -0
  106. /data/{postgresql → sync/postgresql}/ubuntu/start.conf +0 -0
  107. /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-2013 David Kellum
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
@@ -2,10 +2,8 @@
2
2
  install: --no-ri --no-rdoc
3
3
  update: --no-ri --no-rdoc
4
4
  :backtrace: false
5
- :benchmark: false
6
- :bulk_threshold: 1000
7
5
  :verbose: true
8
6
  :update_sources: true
9
7
  :sources:
10
- - http://rubygems.org/
8
+ - https://rubygems.org/
11
9
  ...