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.
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
  ...