class SyncWrap::Space

Serves as the container for hosts and roles and provides the top level execute.



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

current() click to toggle source

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"
new() click to toggle source
# File lib/syncwrap.rb, line 73
def initialize
  @provider = nil
  @roles = { |h,k| h[k] = [] }
  @hosts = {}
  @default_options = {
    coalesce: true,
    sh_verbose: :v,
    sync_paths: [ File.join( SyncWrap::GEM_ROOT, 'sync' ) ] }
  @formatter =
  @composed = 0

Public Instance Methods

component_classes( hs = hosts ) click to toggle source

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 )
    map( &:components ).
    map( &:class ).
compose( &block ) click to toggle source

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 = do
    define_method( :install, &block)
  @composed += 1
  self.class.const_set( "Composed#{@composed}", cc )
execute( host_list = hosts, component_plan = [], opts = {} ) click to toggle source

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


The following options are specifically handled by execute:


If false, don't color diagnostic output to stdout (default: true)


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 =
    host_list.each { |host| queue.push( host ) }
    threads = opts[ :threads ] do queue, component_plan, opts ) do |q, cp, o|
        success = true
          while host = q.pop( true ) # non-block
            r = execute_host( host, cp, o )
            success &&= r
        rescue ThreadError
          #exit, from queue being empty
    threads = do |host| host, component_plan, opts ) do |h, cp, o|
        execute_host( h, cp, o )
  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.
get_host( name ) click to toggle source

Return host by name, or nil if not defined.

# File lib/syncwrap.rb, line 181
def get_host( name )
  @hosts[ name ]
host( *args ) click to toggle source

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 ] ] ||= self )
  host.merge_props( props )
  host.add( *args )
hosts() click to toggle source

All Host instances, in order added.

# File lib/syncwrap.rb, line 186
def hosts
load_sync_file( filename ) click to toggle source

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.
load_sync_file_relative( fpath = './sync.rb' ) click to toggle source

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 ) )
merge_default_options( opts ) click to toggle source

Merge the specified options to #default_options

# File lib/syncwrap.rb, line 125
def merge_default_options( opts )
  @default_options.merge!( opts )
prepend_sync_path( rpath = 'sync' ) click to toggle source

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
provider() click to toggle source

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"
resolve_component_plan( plan ) click to toggle source

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 do |clz,mth|
    if clz.is_a?( String )
      found = { |cc| =~ /(^|::)#{clz}$/ }
      if found.length == 1
        clz = found.first
        raise "Class \"#{clz}\" ambiguous or not found: #{found.inspect}"
    [ clz, mth && mth.to_sym || :install ]
role( symbol, *args ) click to toggle source

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 ]
    @roles[ symbol.to_sym ] += args.flatten.compact
ssh_host_name( host ) click to toggle source

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

# 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 ] ||
use_provider( provider_class, opts = {} ) click to toggle source

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 = self, opts )
with() { |self| ... } click to toggle source

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
    Thread.current[:syncwrap_current_space] = self
    yield self
    Thread.current[:syncwrap_current_space] = prior