class SyncWrap::Space
Serves as the container for hosts and roles and provides the top level execute.
Attributes
Default options, for use including SyncWrap::Component#rput, SyncWrap::Component#sh, and execute (see Options details). The CLI uses this, for example, to set :verbose => true (from –verbose) and :shell_verbose => :x (from –expand-shell). In limited cases it may be appropriate to set default overrides in a sync.rb.
Public Class Methods
Return the current space, as setup within a #with block, or raise something fierce.
# File lib/syncwrap.rb, line 59 def self.current Thread.current[:syncwrap_current_space] or raise "Space.current called outside of Space#with/thread" end
# File lib/syncwrap.rb, line 73 def initialize @provider = nil @roles = Hash.new { |h,k| h[k] = [] } @hosts = {} @default_options = { coalesce: true, sh_verbose: :v, sync_paths: [ File.join( SyncWrap::GEM_ROOT, 'sync' ) ] } @formatter = Formatter.new @composed = 0 end
Public Instance Methods
Return an ordered, unique set of component classes, direct or via roles, currently contained by the specified hosts or all hosts.
# File lib/syncwrap.rb, line 192 def component_classes( hs = hosts ) hs. map( &:components ). flatten. map( &:class ). uniq end
Creates a new component class with &block as implementation of its :install method. Convenient for quick one-off glue but will you move to a real component class once it gets complex?
# File lib/syncwrap.rb, line 203 def compose( &block ) cc = Class.new(Component) do define_method( :install, &block) end @composed += 1 self.class.const_set( "Composed#{@composed}", cc ) cc.new end
Execute components based on a host_list (default all), a component_plan (default :install on all components), and with any additional options (merged with #default_options).
Options¶ ↑
The following options are specifically handled by execute:
- :colorize
-
If false, don't color diagnostic output to stdout (default: true)
- :threads
-
The number of threads on which to execute. Each host is executed with a single thread. (Default: the number of hosts, maximum concurrency)
# File lib/syncwrap.rb, line 251 def execute( host_list = hosts, component_plan = [], opts = {} ) opts = default_options.merge( opts ) @formatter.colorize = ( opts[ :colorize ] != false ) component_plan = resolve_component_plan( component_plan ) if opts[ :threads ] && host_list.length > opts[ :threads ] queue = Queue.new host_list.each { |host| queue.push( host ) } threads = opts[ :threads ].times.map do Thread.new( queue, component_plan, opts ) do |q, cp, o| success = true begin while host = q.pop( true ) # non-block r = execute_host( host, cp, o ) success &&= r end rescue ThreadError #exit, from queue being empty end success end end else threads = host_list.map do |host| Thread.new( host, component_plan, opts ) do |h, cp, o| execute_host( h, cp, o ) end end end threads.inject(true) { |s,t| t.value && s } # Note: Unhandled (i.e. non-SyncError) exceptions will be # propigated and re-raised on call to value above, resulting in # standard ruby stack trace and immediate exit. end
Return host by name, or nil if not defined.
# File lib/syncwrap.rb, line 181 def get_host( name ) @hosts[ name ] end
Define/access a Host by name.
If first arg is a String, it is interpreted as the name property. The name property is also used for lookup and thus must be Space unique. Additional args are interpreted as role symbols or (direct) Components to add to this Host. Each role will only be added once. A final Hash argument is interpreted as properties to add or merge with the host.
# File lib/syncwrap.rb, line 169 def host( *args ) props = args.last.is_a?( Hash ) && args.pop || {} name = args.first.is_a?( String ) && args.shift props = props.merge( name: name ) if name raise "Missing required name parameter" unless props[ :name ] host = @hosts[ props[ :name ] ] ||= Host.new( self ) host.merge_props( props ) host.add( *args ) host end
All Host instances, in order added.
# File lib/syncwrap.rb, line 186 def hosts @hosts.values end
Load the specified filename as per a sync.rb, into this Space. This uses a with block internally.
# File lib/syncwrap.rb, line 94 def load_sync_file( filename ) require 'syncwrap/main' with do load( filename, wrap_sync_load? ) # Should wrap to avoid pollution of sync namespace, but there # are jruby bugs to workaround. This is particularly important # given the dynamic binding scheme of components. If not done, # top-level methods/vars in sync.rb would have precidents over # component methods. end end
Load the specified file path as per a sync.rb, into this Space. If relative, path is assumed to be relative to the caller (i.e. Rakefile, etc.) as with the conventional 'sync.rb'.
# File lib/syncwrap.rb, line 88 def load_sync_file_relative( fpath = './sync.rb' ) load_sync_file( path_relative_to_caller( fpath, caller ) ) end
Merge the specified options to #default_options
# File lib/syncwrap.rb, line 125 def merge_default_options( opts ) @default_options.merge!( opts ) nil end
Prepend the given directory path to front of the :sync_paths list. If relative, path is assumed to be relative to the caller (i.e. sync.rb) as with the conventional 'sync' directory. Returns a copy of the resultant sync_paths list.
# File lib/syncwrap.rb, line 134 def prepend_sync_path( rpath = 'sync' ) rpath = path_relative_to_caller( rpath, caller ) roots = default_options[ :sync_paths ] roots.delete( rpath ) # don't duplicate but move to front roots.unshift( rpath ) roots.dup #return a copy end
A hosting/cloud provider for creating/removing hosts from this space. See use_provider
# File lib/syncwrap.rb, line 120 def provider @provider or raise "No provider set via space.use_provider" end
Returns a new component_plan from plan, looking up any Class string names and using :install for any missing methods:
[ [ Class | String, Symbol? ] … ] -> [ [ Class, Symbol ] … ]
Class name lookup is by unqualified matching against component_classes (already added to hosts of this space.) If such String match is ambiguous or not found, a RuntimeError is raised.
# File lib/syncwrap.rb, line 221 def resolve_component_plan( plan ) classes = component_classes plan.map do |clz,mth| if clz.is_a?( String ) found = classes.select { |cc| cc.name =~ /(^|::)#{clz}$/ } if found.length == 1 clz = found.first else raise "Class \"#{clz}\" ambiguous or not found: #{found.inspect}" end end [ clz, mth && mth.to_sym || :install ] end end
Define/access a Role by symbol. Additional args are interpreted as Components to add to this role.
# File lib/syncwrap.rb, line 153 def role( symbol, *args ) if args.empty? @roles[ symbol.to_sym ] else @roles[ symbol.to_sym ] += args.flatten.compact end end
Given a Host, determine the address to use for ssh (incl. rsync) access. The following properties are used in order of decreasing preference: internet_name, internet_ip and host.name.
# File lib/syncwrap.rb, line 289 def ssh_host_name( host ) # This is included here for expected Space-wide policy settings. host[ :internet_name ] || host[ :internet_ip ] || host.name end
Create a new instance of the specified provider class for use, passing self and the given options.
# File lib/syncwrap.rb, line 145 def use_provider( provider_class, opts = {} ) opts = opts.merge( sync_file_path: caller_path( caller ) ) @provider = provider_class.new( self, opts ) end
Make self the ::current inside of block.
# File lib/syncwrap.rb, line 107 def with prior = Thread.current[:syncwrap_current_space] raise "Invalid Space#with nesting!" if prior && prior != self begin Thread.current[:syncwrap_current_space] = self yield self ensure Thread.current[:syncwrap_current_space] = prior end end