class SyncWrap::Space

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

Attributes

default_options[R]

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"
end
new() click to toggle source
# 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

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 )
  hs.
    map( &:components ).
    flatten.
    map( &:class ).
    uniq
end
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 = Class.new(Component) do
    define_method( :install, &block)
  end
  @composed += 1
  self.class.const_set( "Composed#{@composed}", cc )
  cc.new
end
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).

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

All Host instances, in order added.

# File lib/syncwrap.rb, line 186
def hosts
  @hosts.values
end
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.
  end
end
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 ) )
end
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 )
  nil
end
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
end
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"
end
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
  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
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 ]
  else
    @roles[ symbol.to_sym ] += args.flatten.compact
  end
end
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 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
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 = provider_class.new( self, opts )
end
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
  begin
    Thread.current[:syncwrap_current_space] = self
    yield self
  ensure
    Thread.current[:syncwrap_current_space] = prior
  end
end