Checkpoint commit
M .ruby-version +1 -1
@@ 1,1 1,1 @@ 
-2.5
+2.6

          
M Gemfile.devel +2 -0
@@ 3,3 3,5 @@ gemspec
 
 gem 'hglib', path: '../hglib'
 gem 'cztop-reactor', path: '../CZTop-Reactor'
+gem 'pry'
+

          
M README.md +12 -13
@@ 127,29 127,28 @@ Now you can start the worker, which will
 Now we need our repositories to notify the assembly server when events occur.
 We'll hook up a Mercurial repo for a Ruby library so that it runs unit tests
 whenever there's a new commit. First we'll install assemblage and the Mercurial
-client library on the repo server and create a run directory for repo
-operations:
+client library on the repo server and create a publisher for repo operations:
 
     user@example-repo $ sudo gem install assemblage hglib
     user@example-repo $ mkdir /usr/local/hg/repos/.assemblage
     user@example-repo $ cd /usr/local/hg/repos/.assemblage
-    user@example-repo $ assemblage repo setup project1
-    Setting up a new assemblage repo config directory in
+    user@example-repo $ assemblage publisher setup
+    Setting up a new assemblage publisher config directory in
       /usr/local/hg/repos/.assemblage...
-    Set up with repo name: project1
-    Client public key is:
+    Publisher public key is:
       bq9VheQbLtcu]LGK4I&xzK3^UW0Iyak/6<YS=^$w
     done.
 
-Now we'll need to register the repo on the server like we did before for the
-worker:
+Now we'll need to register the publisher on the server like we did before for
+the worker:
+
+    user@example $ assemblage publisher add \
+      'bq9VheQbLtcu]LGK4I&xzK3^UW0Iyak/6<YS=^$w'
+    Accepting publisher events from pub-example-com-0a44fe...
+    done.
 
     user@example $ assemblage repo add --type=hg project1 \
-        'bq9VheQbLtcu]LGK4I&xzK3^UW0Iyak/6<YS=^$w' \
-        http://repo.example.com/project1
-    Looking for repo registration... found.
-    Approving repo events from http://repo.example.com/project1...
-    done.
+      http://repo.example.com/project1
 
 We'll add a hook to the repository's .hg/hgrc that looks like:
 

          
M lib/assemblage.rb +15 -0
@@ 1,6 1,8 @@ 
 # -*- ruby -*-
 #encoding: utf-8
 
+require 'securerandom'
+require 'socket'
 require 'set'
 require 'loggability'
 require 'configurability'

          
@@ 46,9 48,11 @@ module Assemblage
 	autoload :CLI, 'assemblage/cli'
 	autoload :DbObject, 'assemblage/db_object'
 	autoload :Protocol, 'assemblage/protocol'
+	autoload :Publisher, 'assemblage/publisher'
 	autoload :Repository, 'assemblage/repository'
 	autoload :Server, 'assemblage/server'
 	autoload :Worker, 'assemblage/worker'
+	autoload :VCSStrategy, 'assemblage/vcs_strategy'
 
 
 	require 'assemblage/mixins'

          
@@ 140,5 144,16 @@ module Assemblage
 	end
 
 
+	### Generate a unique name prefixed with +noun+ to use as an ID for a new
+	### publisher, worker, etc.
+	def self::make_unique_id( noun )
+		raw = [
+			noun,
+			Socket.gethostname.split('.').first.downcase,
+			SecureRandom.hex( 8 )
+		].join( '-' )
+		return raw.gsub( /[^\w\-]+/, '-' ).squeeze( '-' )
+	end
+
 end # module Assemblage
 

          
A => lib/assemblage/command/publisher.rb +103 -0
@@ 0,0 1,103 @@ 
+# -*- ruby -*-
+# frozen_string_literal: true
+
+require 'pathname'
+require 'assemblage/cli' unless defined?( Assemblage::CLI )
+
+
+# Assemblage Publisher commands
+module Assemblage::CLI::PublisherCommand
+	extend Assemblage::CLI::Subcommand
+
+
+	SETUP_ADVICE = %{
+		Now you can tell a server to accept repository events published from this host
+		like so:
+		  assemblage server add-publisher %{name} '%{public_key}' <server_directory>
+
+		You can use this publisher to publish events from a Mercurial repository
+		by adding a hook:
+
+		[hooks]
+		incoming.myrepo = assemblage -c %{directory}/config.yml publish commit %{name} $HG_NODE
+
+	}.gsub( /^\t+/, '' )
+
+
+	desc "Publisher commands"
+	command :publisher do |publisher|
+
+		publisher.desc 'Set up a new repo event publisher run directory'
+		publisher.long_desc <<-END_DESC
+		Set up a new run directory for hooks that run in one or more repositories.
+		This is how the commit (or other) hook authenticates to the server when
+		it runs.
+		END_DESC
+		publisher.arg :NAME
+		publisher.arg :DIRECTORY
+		publisher.command :setup do |setup|
+
+			setup.action do |globals, options, args|
+				name = args.shift or help_now!( "No name specified.", 64 )
+				directory = Pathname( args.shift || '.' ).expand_path
+				name = options.name
+
+				prompt.say "Creating a publisher run directory in %s..." % [ directory ]
+				Assemblage::Publisher.setup_run_directory( directory )
+
+				prompt.say "Generating a publisher key..."
+				Assemblage::Publisher.generate_cert
+
+				msg = SETUP_ADVICE % {
+					public_key: Assemblage::Publisher.public_key,
+					directory: directory,
+					name: name
+				}
+				prompt.say( msg )
+			end
+		end
+
+
+		publisher.desc "Output the publisher's public key"
+		publisher.long_desc <<-END_DESC
+		Output the publisher's public key to STDOUT.
+		END_DESC
+		publisher.arg :DIRECTORY, :optional
+		publisher.command 'show-key' do |show_key|
+			show_key.action do |globals, options, args|
+				directory = Pathname( args.shift || '.' ).expand_path
+
+				Assemblage.use_run_directory( directory )
+				prompt.say "Worker's public key:"
+				puts Assemblage::Publisher.public_key
+			end
+		end
+
+
+		publisher.desc "Add a server key to a publisher config"
+		publisher.long_desc <<-END_DESC
+		Add the specified PUBLIC_KEY for a server named NAME to the server config
+		in SERVER_DIRECTORY.
+		END_DESC
+		publisher.arg :ENDPOINT
+		publisher.arg :PUBLIC_KEY
+		publisher.arg :DIRECTORY, :optional
+		publisher.command 'add-server' do |add|
+
+			add.action do |globals, options, args|
+				endpoint = args.shift or help_now!( "Missing the endpoint." )
+				public_key = args.shift or help_now!( "Missing the server public key." )
+
+				Assemblage.use_run_directory( args.shift )
+
+				prompt.say "Adding server key for connecting to %s..." % [ endpoint ]
+				Assemblage::Publisher.add_server( endpoint, public_key )
+				prompt.say "done."
+			end
+
+		end
+
+	end
+
+end # module Assemblage::CLI::PublisherCommand
+

          
M lib/assemblage/command/repo.rb +1 -90
@@ 1,7 1,6 @@ 
 # -*- ruby -*-
 # frozen_string_literal: true
 
-require 'pathname'
 require 'assemblage/cli' unless defined?( Assemblage::CLI )
 
 

          
@@ 10,94 9,6 @@ module Assemblage::CLI::RepoCommand
 	extend Assemblage::CLI::Subcommand
 
 
-	SETUP_ADVICE = %{
-		Now you can tell a server to accept commits from a repository on this host
-		like so:
-		  assemblage repo add %{name} '%{public_key}' <uri>
-
-		Once it is registered, you can add :
-		  assemblage commit %{directory}
-
-	}.gsub( /^\t+/, '' )
-
-
-	desc "Repository commands"
-	command :repo do |repo|
-
-		repo.desc "Specify a name that will identify the repository server"
-		repo.flag [:N, :name], type: String,
-			must_match: Assemblage::Auth::CLIENT_NAME_PATTERN,
-			required: true
-
-		repo.desc 'Set up a new run directory for repository hooks'
-		repo.long_desc <<-END_DESC
-		Set up a new run directory for hooks that run in one or more repositories.
-		This is how the commit (or other) hook authenticates to the server when
-		it runs.
-		END_DESC
-		repo.arg :DIRECTORY
-		repo.command :setup do |setup|
-
-			setup.action do |globals, options, args|
-				directory = Pathname( args.shift || '.' ).expand_path
-				name = options.name
-
-				prompt.say "Creating a repo run directory in %s..." % [ directory ]
-				Assemblage::Repository.setup_run_directory( directory )
-
-				prompt.say "Generating a repo key..."
-				Assemblage::Repository.generate_cert
 
-				msg = SETUP_ADVICE % {
-					public_key: Assemblage::Repository.public_key,
-					directory: directory,
-					name: name
-				}
-				prompt.say( msg )
-			end
-		end
-
-
-		repo.desc "Output the repo's public key"
-		repo.long_desc <<-END_DESC
-		Output the repo's public key to STDOUT.
-		END_DESC
-		repo.arg :DIRECTORY, :optional
-		repo.command 'show-key' do |show_key|
-			show_key.action do |globals, options, args|
-				directory = Pathname( args.shift || '.' ).expand_path
-
-				Assemblage.use_run_directory( directory )
-				prompt.say "Worker's public key:"
-				puts Assemblage::Repository.public_key
-			end
-		end
-
+end # module Assemblage::CLI::RepoCommand
 
-		repo.desc "Add a new repository key to a server"
-		repo.long_desc <<-END_DESC
-		Allow connections from a repository named NAME to the server config
-		in SERVER_DIRECTORY.
-		END_DESC
-		repo.arg :NAME
-		repo.arg :PUBLIC_KEY
-		repo.arg :SERVER_DIRECTORY, :optional
-		repo.command :add do |add|
-
-			add.action do |globals, options, args|
-				name = args.shift or help_now!( "Missing the repo name." )
-				public_key = args.shift or help_now!( "Missing the repo public key." )
-
-				Assemblage.use_run_directory( args.shift )
-
-				prompt.say "Approving connections from %s..." % [ name ]
-				Assemblage::Server.add_repo( name, public_key )
-				prompt.say "done."
-			end
-
-		end
-
-	end
-
-end # module Assemblage::CLI::StartServer
-

          
M lib/assemblage/command/server.rb +130 -18
@@ 2,6 2,7 @@ 
 # frozen_string_literal: true
 
 require 'assemblage/cli' unless defined?( Assemblage::CLI )
+require 'assemblage/server'
 
 
 # Assemblage server commands

          
@@ 10,45 11,63 @@ module Assemblage::CLI::ServerCommand
 
 	CREATE_ADVICE = %{
 		You can start the assembly server like so:
-		  assemblage start server %{directory}
+		  assemblage server start %{directory}
 
 		Server public key is:
 		  %{public_key}
 
 	}.gsub( /^\t+/, '' )
 
+	# The range of ports to choose from if an endpoint isn't specified when creating
+	# a new server.
+	ENDPOINT_PORT_RANGE = 14000 .. 16000
 
 
 	desc "Server commands"
 	command :server do |server|
 
-		server.desc "Specify the URI to a database to use."
-		server.flag [:D, :database], type: String
-
 		server.desc 'Set up a new assembly server'
 		server.long_desc <<-END_DESC
-		Set up a new assembly server in the given DIRECTORY. If the DIRECTORY is
+		Set up a new assembly server in the given DIRECTORY and DATABASE. If the DIRECTORY is
 		not specified, the current directory will be used. If the target directory
-		is not empty, this command will abort.
+		is not empty, this command will abort. If no DATABASE is specified, a Sqlite database
+		will be created in the target directory. If an ENDPOINT is specified, the server
+		will accept connections using that specification; otherwise a random port on
+		`localhost` will be chosen.
 		END_DESC
 		server.arg :DIRECTORY
+		server.arg :DATABASE, :optional
+		server.arg :ENDPOINT, :optional
 		server.command :create do |create|
 
 			create.action do |globals, options, args|
 				directory = Pathname( args.shift || '.' ).expand_path
+				database = args.shift
+				endpoint = args.shift
 
-				if options[:database]
-					Configurability.loaded_config ||= Configurability.default_config
-					Assemblage.config.assemblage.db.uri = options[:database]
+				Configurability.loaded_config ||= Configurability.default_config
+
+				if database && database != ''
+					prompt.say "Setting the database to: %p" % [ database ]
+					Assemblage.config.assemblage.db.uri = database
 				end
 
+				unless endpoint
+					port = rand( ENDPOINT_PORT_RANGE )
+					endpoint = "tcp://%s:%d"
+				end
+
+				prompt.say "Setting the endpoint to: %p" % [ endpoint ]
+				Assemblage.config.assemblage.server.endpoint = endpoint
+
 				prompt.say "Creating a server run directory in %s..." % [ directory ]
 				Assemblage::Server.setup_run_directory( directory )
 
 				prompt.say "Generating a server key..."
 				Assemblage::Server.generate_cert
 
-				prompt.say "Creating the assemblies database..."
+				prompt.say "Creating the assemblies database (%s)..." %
+					[ Assemblage.config.assemblage.db.uri ]
 				Assemblage::Server.create_database
 
 				msg = CREATE_ADVICE % {

          
@@ 60,24 79,117 @@ module Assemblage::CLI::ServerCommand
 		end
 
 
-		server.desc "Add a new server to a worker"
+		server.desc "Output the server's public key"
 		server.long_desc <<-END_DESC
-		Add a cert and configuration for a new server to a worker run directory.
+		Output the server's public key to STDOUT.
+		END_DESC
+		server.arg :DIRECTORY, :optional
+		server.command 'show-key' do |show_key|
+			show_key.action do |globals, options, args|
+				directory = Pathname( args.shift || '.' ).expand_path
+
+				Assemblage.use_run_directory( directory )
+				prompt.say "Server's public key:"
+				puts Assemblage::Server.public_key
+			end
+		end
+
+
+		server.desc "Output the server's endpoint"
+		server.long_desc <<-END_DESC
+		Output the server's endpoint to STDOUT.
 		END_DESC
-		server.arg :URL
+		server.arg :DIRECTORY, :optional
+		server.command 'show-endpoint' do |show_key|
+			show_key.action do |globals, options, args|
+				directory = Pathname( args.shift || '.' ).expand_path
+
+				Assemblage.use_run_directory( directory )
+				prompt.say "Server's endpoint:"
+				puts Assemblage::Server.endpoint
+			end
+		end
+
+
+		server.desc "Add a new worker to a server"
+		server.long_desc <<-END_DESC
+		Add a cert and configuration for a new worker to a server run directory.
+		END_DESC
+		server.arg :NAME
 		server.arg :PUBLIC_KEY
-		server.arg :WORKER_DIRECTORY, :optional
-		server.command :add do |add|
+		server.arg :SERVER_DIRECTORY, :optional
+		server.command 'add-worker' do |add|
 
 			add.action do |globals, options, args|
-				url = args.shift or help_now!( "Missing the server url." )
-				public_key = args.shift or help_now!( "Missing the server public key." )
+				name = args.shift or help_now!( "Missing the worker name." )
+				public_key = args.shift or help_now!( "Missing the worker public key." )
+
+				Assemblage.use_run_directory( args.shift )
+
+				prompt.say "Approving connections from %s..." % [ name ]
+				Assemblage::Server.add_worker( name, public_key )
+				prompt.say "done."
+			end
+
+		end
+
+
+		server.desc "Add a new publisher to a server"
+		server.long_desc <<-END_DESC
+		Add a cert and configuration for a new publisher to a server run directory.
+		END_DESC
+		server.arg :NAME
+		server.arg :PUBLIC_KEY
+		server.arg :SERVER_DIRECTORY, :optional
+		server.command 'add-publisher' do |add|
+
+			add.action do |globals, options, args|
+				name = args.shift or help_now!( "Missing the publisher name." )
+				public_key = args.shift or help_now!( "Missing the publisher public key." )
 
 				Assemblage.use_run_directory( args.shift )
-				Assemblage::Worker.add_server( url, public_key )
 
+				prompt.say "Approving connections from %s..." % [ name ]
+				Assemblage::Server.add_publisher( name, public_key )
 				prompt.say "done."
 			end
+
+		end
+
+
+		server.desc "Add a new repository to a server"
+		server.long_desc <<-END_DESC
+		Add a new repository named NAME and type TYPE to the server config in SERVER_DIRECTORY.
+		Workers will check out source from the specified URL in response to events
+		published from it.  
+	  
+		Valid types are:  
+		  #{Assemblage::VCSStrategy.available.map(&:to_s).join(', ')}
+		END_DESC
+		server.arg :NAME
+		server.arg :TYPE
+		server.arg :URL
+		server.arg :SERVER_DIRECTORY, :optional
+		server.command 'add-repo' do |add|
+
+			add.action do |globals, options, args|
+				name = args.shift or help_now!( "Missing the repo name." )
+				type = args.shift or help_now!( "Missing the repo type." )
+				url = args.shift or help_now!( "Missing the repo URL." )
+				directory = args.shift || '.'
+
+				unless Assemblage::VCSStrategy.available.include?( type )
+					help_now! "Invalid repo type %p; supported types are: %s" %
+						[ type, Assemblage::VCSStrategy.available.join(', ') ]
+				end
+
+				Assemblage.use_run_directory( args.shift )
+
+				prompt.say "Approving connections from %s..." % [ name ]
+				Assemblage::Server.add_repo( name, public_key )
+				prompt.say "done."
+			end
+
 		end
 
 

          
M lib/assemblage/command/worker.rb +10 -12
@@ 11,10 11,10 @@ module Assemblage::CLI::WorkerCommand
 
 	CREATE_ADVICE = %{
 		Now you can register this worker with a server like so:
-		  assemblage add worker %{name} '%{public_key}' <server directory>
+		  assemblage worker add %{name} '%{public_key}' <server directory>
 
 		Once it is registered, you can start the assembly worker like so:
-		  assemblage start worker %{directory}
+		  assemblage worker start %{directory}
 
 	}.gsub( /^\t+/, '' )
 

          
@@ 80,26 80,24 @@ module Assemblage::CLI::WorkerCommand
 		end
 
 
-		worker.desc "Add a new worker to a server"
+		worker.desc "Add a new server to a worker"
 		worker.long_desc <<-END_DESC
-		Add a cert and configuration for a new worker to a server run directory.
+		Add a cert and configuration for a new server to a worker run directory.
 		END_DESC
-		worker.arg :NAME
+		worker.arg :URL
 		worker.arg :PUBLIC_KEY
-		worker.arg :SERVER_DIRECTORY, :optional
-		worker.command :add do |add|
+		worker.arg :WORKER_DIRECTORY, :optional
+		worker.command 'add-server' do |add|
 
 			add.action do |globals, options, args|
-				name = args.shift or help_now!( "Missing the worker name." )
-				public_key = args.shift or help_now!( "Missing the worker public key." )
+				url = args.shift or help_now!( "Missing the server url." )
+				public_key = args.shift or help_now!( "Missing the server public key." )
 
 				Assemblage.use_run_directory( args.shift )
+				Assemblage::Worker.add_server( url, public_key )
 
-				prompt.say "Approving connections from %s..." % [ name ]
-				Assemblage::Server.add_worker( name, public_key )
 				prompt.say "done."
 			end
-
 		end
 
 

          
A => lib/assemblage/publisher.rb +69 -0
@@ 0,0 1,69 @@ 
+# -*- ruby -*-
+# frozen_string_literal: true
+
+require 'loggability'
+
+require 'assemblage' unless defined?( Assemblage )
+
+
+# A client that pushes events for one or more repositories to the server.
+class Assemblage::Publisher
+	extend Loggability,
+		Configurability
+
+	# Loggability API -- log to the assemblage logger
+	log_to :assemblage
+
+
+	configurability( 'assemblage.publisher' ) do
+
+		##
+		# The ZeroMQ endpoint URL of the server to publish to
+		setting :server_endpoint
+
+	end
+
+
+	### Generate a client CZTop::Certificate for the publisher.
+	def self::generate_cert
+		Assemblage::Auth.generate_local_cert unless Assemblage::Auth.has_local_cert?
+	end
+
+
+	### Return the publisher's public key as a Z85-encoded ASCII string.
+	def self::public_key
+		return Assemblage::Auth.local_cert.public_key
+	end
+
+
+	### Add a server with the specified +endpoint+ and +public_key+ to the current run
+	### directory.
+	def self::add_server( endpoint, public_key )
+		self.log.info "Adding server at %p with public key: %s" % [ endpoint, public_key ]
+		Assemblage::Auth.save_remote_cert( 'server', public_key )
+
+		Assemblage.config.assemblage.publisher.server_endpoint = endpoint
+		Assemblage.config.write
+	end
+
+
+	### Set up the +directory+ as a publisher run directory. Raises an
+	### exception if the directory already exists and is not empty.
+	def self::setup_run_directory( directory='.' )
+		directory = Pathname( directory || '.' )
+		raise "Directory not empty" if directory.exist? && !directory.empty?
+
+		self.log.debug "Attempting to set up %s as a run directory." % [ directory ]
+		directory.mkpath
+		directory.chmod( 0755 )
+
+		config = Assemblage.config || Configurability.default_config
+		config.assemblage.auth.cert_store_dir ||= (directory + 'certs').to_s
+
+		Loggability.with_level( :fatal ) do
+			config.install
+		end
+		config.write( directory + Assemblage::DEFAULT_CONFIG_FILE )
+	end
+
+end # class Assemblage::Publisher

          
M lib/assemblage/repository.rb +0 -35
@@ 20,40 20,5 @@ class Assemblage::Repository < Assemblag
 	# Assemblies for this repository
 	one_to_many :assemblies, class: 'Assemblage::Assembly'
 
-	##
-	# 
-
-	### Generate a client CZTop::Certificate for the repo.
-	def self::generate_cert
-		Assemblage::Auth.generate_local_cert unless Assemblage::Auth.has_local_cert?
-	end
-
-
-	### Return the repo's public key as a Z85-encoded ASCII string.
-	def self::public_key
-		return Assemblage::Auth.local_cert.public_key
-	end
-
-
-	### Set up the +directory+ as a repo run directory. Raises an
-	### exception if the directory already exists and is not empty.
-	def self::setup_run_directory( directory='.' )
-		directory = Pathname( directory || '.' )
-		raise "Directory not empty" if directory.exist? && !directory.empty?
-
-		self.log.debug "Attempting to set up %s as a run directory." % [ directory ]
-		directory.mkpath
-		directory.chmod( 0755 )
-
-		config = Assemblage.config || Configurability.default_config
-		config.assemblage.auth.cert_store_dir ||= (directory + 'certs').to_s
-
-		Loggability.with_level( :fatal ) do
-			config.install
-		end
-		config.write( directory + Assemblage::DEFAULT_CONFIG_FILE )
-	end
-
-
 end # class Assemblage::Repository
 

          
M lib/assemblage/server.rb +9 -5
@@ 116,15 116,15 @@ class Assemblage::Server
 	### directory.
 	def self::add_worker( name, public_key )
 		self.log.info "Adding worker %p with public key: %s" % [ name, public_key ]
-		cert = Assemblage::Auth.save_remote_cert( name, public_key )
+		Assemblage::Auth.save_remote_cert( name, public_key )
 	end
 
 
-	### Add a repository with the specified +name+ and +public_key+ to the current run
+	### Add a publisher with the specified +name+ and +public_key+ to the current run
 	### directory.
-	def self::add_repo( name, public_key )
-		self.log.info "Adding repo %p with public key: %s" % [ name, public_key ]
-		cert = Assemblage::Auth.save_remote_cert( name, public_key )
+	def self::add_publisher( name, public_key )
+		self.log.info "Adding publisher %p with public key: %s" % [ name, public_key ]
+		Assemblage::Auth.save_remote_cert( name, public_key )
 	end
 
 

          
@@ 370,10 370,14 @@ class Assemblage::Server
 	def client_for_sender( frame )
 		rid = frame.routing_id
 		return self.clients.fetch( rid ) do
+			require 'pry'; binding.pry
 			clientname = frame.meta( Assemblage::Auth::CLIENT_NAME_KEY )
+
 			if clientname
+				self.log.debug "Looking up client %p" % [ clientname ]
 				self.connect_client( clientname, rid )
 			else
+				self.log.error "No client name associated with %p" % [ frame ]
 				nil
 			end
 		end

          
M lib/assemblage/vcs_strategy.rb +11 -1
@@ 12,7 12,17 @@ require 'assemblage' unless defined?( As
 class Assemblage::VCSStrategy
 	extend Pluggability
 
-	plugin_paths 'assemblage/vcs_strategy'
+	plugin_prefixes 'assemblage/vcs_strategy'
+
+
+	### Return the list of available strategies as an Array of Strings.
+	def self::available
+		self.load_all
+		return self.derivatives.keys.
+			reject {|obj| obj.is_a?(Class) }.
+			map( &:downcase ).
+			uniq
+	end
 
 
 	### Clone the repository at the given +url+ into the specified +directory+ at

          
M spec/assemblage/server_spec.rb +1 -0
@@ 89,6 89,7 @@ describe Assemblage::Server, db: true do
 
 				Assemblage.use_run_directory( @assemblage_dir )
 				Loggability.level = :debug
+				Loggability.format_with( :color )
 
 				described_class.add_worker( 'testworker1', @worker_cert.public_key )
 

          
A => spec/assemblage/vcs_strategy_spec.rb +46 -0
@@ 0,0 1,46 @@ 
+#!/usr/bin/env rspec -cfd
+
+require_relative '../spec_helper'
+
+require 'assemblage/vcs_strategy'
+
+
+describe Assemblage::VCSStrategy do
+
+	before( :each ) do
+		@original_derivatives = described_class.derivatives.dup
+	end
+
+	after( :each ) do
+		described_class.derivatives.replace( @original_derivatives )
+	end
+
+
+	let( :type1_class ) do
+		Class.new( described_class ) do
+			def self::name; "#{described_class}::Mercurial"; end
+		end
+	end
+	let( :type2_class ) do
+		Class.new( described_class ) do
+			def self::name; "#{described_class}::Git"; end
+		end
+	end
+
+
+	it "can return a list of the names of available types" do
+		allow( described_class ).to receive( :load_all )
+		described_class.derivatives.replace({
+			'mercurial' => type1_class,
+			'Mercurial' => type1_class,
+			type1_class => type1_class,
+			'git' => type2_class,
+			'Git' => type2_class,
+			type2_class => type2_class,
+		})
+
+		expect( described_class.available ).to contain_exactly( 'git', 'mercurial' )
+	end
+
+end
+

          
M spec/assemblage_spec.rb +8 -0
@@ 14,6 14,13 @@ describe Assemblage do
 	end
 
 
+	it "can generate a unique string ID for an object" do
+		expect( Socket ).to receive( :gethostname ).and_return( "Netrehesh.local" )
+		expect( described_class.make_unique_id( 'doot' ) ).
+			to match( /^doot-netrehesh-[[:xdigit:]]{16}$/ )
+	end
+
+
 	describe "configurability" do
 
 		before( :all ) do

          
@@ 123,6 130,7 @@ describe Assemblage do
 			Assemblage.load_config( 'a/different/configfile.yml', database: {dbname: 'test'} )
 		end
 
+
 	end
 
 end