circus-deployment 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +23 -0
- data/README.md +0 -0
- data/bin/circus +24 -0
- data/lib/bundler/circus_bundler.rb +24 -0
- data/lib/bundler/circus_util.rb +43 -0
- data/lib/bundler/patches.rb +18 -0
- data/lib/circus/act.rb +74 -0
- data/lib/circus/actstore_client.rb +30 -0
- data/lib/circus/agents/agent.rb +59 -0
- data/lib/circus/agents/client.rb +77 -0
- data/lib/circus/agents/connection.rb +76 -0
- data/lib/circus/agents/conversation.rb +17 -0
- data/lib/circus/agents/dbus_connection.rb +85 -0
- data/lib/circus/agents/dbus_logger.rb +34 -0
- data/lib/circus/agents/encoding.rb +32 -0
- data/lib/circus/agents/logger.rb +52 -0
- data/lib/circus/agents/params.rb +47 -0
- data/lib/circus/agents/ssh_connection.rb +120 -0
- data/lib/circus/application.rb +99 -0
- data/lib/circus/booth_client.rb +25 -0
- data/lib/circus/booth_tool.rb +98 -0
- data/lib/circus/cli.rb +147 -0
- data/lib/circus/clown_client.rb +27 -0
- data/lib/circus/connection_builder.rb +32 -0
- data/lib/circus/external_util.rb +14 -0
- data/lib/circus/local_config.rb +65 -0
- data/lib/circus/profiles/base.rb +115 -0
- data/lib/circus/profiles/django.rb +90 -0
- data/lib/circus/profiles/jekyll.rb +90 -0
- data/lib/circus/profiles/make_base.rb +17 -0
- data/lib/circus/profiles/pure_py.rb +46 -0
- data/lib/circus/profiles/pure_rb.rb +48 -0
- data/lib/circus/profiles/python_base.rb +39 -0
- data/lib/circus/profiles/rack.rb +59 -0
- data/lib/circus/profiles/ruby_base.rb +52 -0
- data/lib/circus/profiles/shell.rb +46 -0
- data/lib/circus/profiles.rb +10 -0
- data/lib/circus/repos/git.rb +42 -0
- data/lib/circus/repos/mercurial.rb +42 -0
- data/lib/circus/repos.rb +16 -0
- data/lib/circus/resource_allocator_client.rb +19 -0
- data/lib/circus/stdout_logger.rb +11 -0
- data/lib/circus/version.rb +3 -0
- data/lib/circus.rb +9 -0
- data/vendor/ruby-dbus/COPYING +504 -0
- data/vendor/ruby-dbus/ChangeLog +782 -0
- data/vendor/ruby-dbus/HOWTO-RELEASE +14 -0
- data/vendor/ruby-dbus/NEWS +104 -0
- data/vendor/ruby-dbus/README +53 -0
- data/vendor/ruby-dbus/Rakefile +47 -0
- data/vendor/ruby-dbus/doc/tutorial/src/00.index.page +12 -0
- data/vendor/ruby-dbus/doc/tutorial/src/10.intro.page +127 -0
- data/vendor/ruby-dbus/doc/tutorial/src/20.basic_client.page +174 -0
- data/vendor/ruby-dbus/doc/tutorial/src/30.service.page +121 -0
- data/vendor/ruby-dbus/doc/tutorial/src/default.css +129 -0
- data/vendor/ruby-dbus/doc/tutorial/src/default.template +46 -0
- data/vendor/ruby-dbus/examples/gdbus/gdbus +255 -0
- data/vendor/ruby-dbus/examples/gdbus/gdbus.glade +184 -0
- data/vendor/ruby-dbus/examples/gdbus/launch.sh +4 -0
- data/vendor/ruby-dbus/examples/no-introspect/nm-test.rb +21 -0
- data/vendor/ruby-dbus/examples/no-introspect/tracker-test.rb +16 -0
- data/vendor/ruby-dbus/examples/rhythmbox/playpause.rb +25 -0
- data/vendor/ruby-dbus/examples/service/call_service.rb +25 -0
- data/vendor/ruby-dbus/examples/service/service_newapi.rb +51 -0
- data/vendor/ruby-dbus/examples/simple/call_introspect.rb +34 -0
- data/vendor/ruby-dbus/examples/utils/listnames.rb +11 -0
- data/vendor/ruby-dbus/examples/utils/notify.rb +19 -0
- data/vendor/ruby-dbus/lib/dbus/auth.rb +156 -0
- data/vendor/ruby-dbus/lib/dbus/bus.rb +750 -0
- data/vendor/ruby-dbus/lib/dbus/export.rb +133 -0
- data/vendor/ruby-dbus/lib/dbus/introspect.rb +544 -0
- data/vendor/ruby-dbus/lib/dbus/marshall.rb +443 -0
- data/vendor/ruby-dbus/lib/dbus/matchrule.rb +100 -0
- data/vendor/ruby-dbus/lib/dbus/message.rb +293 -0
- data/vendor/ruby-dbus/lib/dbus/type.rb +222 -0
- data/vendor/ruby-dbus/lib/dbus.rb +89 -0
- data/vendor/ruby-dbus/ruby-dbus.gemspec +28 -0
- data/vendor/ruby-dbus/setup.rb +1585 -0
- data/vendor/ruby-dbus/test/Makefile +4 -0
- data/vendor/ruby-dbus/test/bus_driver_test.rb +21 -0
- data/vendor/ruby-dbus/test/server_robustness_test.rb +41 -0
- data/vendor/ruby-dbus/test/server_test.rb +44 -0
- data/vendor/ruby-dbus/test/service_newapi.rb +99 -0
- data/vendor/ruby-dbus/test/session_bus_test_manual.rb +20 -0
- data/vendor/ruby-dbus/test/signal_test.rb +57 -0
- data/vendor/ruby-dbus/test/t1 +4 -0
- data/vendor/ruby-dbus/test/t2.rb +66 -0
- data/vendor/ruby-dbus/test/t3-ticket27.rb +18 -0
- data/vendor/ruby-dbus/test/t5-report-dbus-interface.rb +58 -0
- data/vendor/ruby-dbus/test/t6-loop.rb +85 -0
- data/vendor/ruby-dbus/test/test_all +26 -0
- data/vendor/ruby-dbus/test/test_server +74 -0
- data/vendor/ruby-dbus/test/variant_test.rb +66 -0
- metadata +225 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'circus/agents/encoding'
|
2
|
+
|
3
|
+
module Circus
|
4
|
+
module Agents
|
5
|
+
class CommandParams
|
6
|
+
def initialize(str)
|
7
|
+
@params = if str
|
8
|
+
Encoding.decode(str)
|
9
|
+
else
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def required(*names)
|
15
|
+
res = names.map do |n|
|
16
|
+
raise MissingParameterException.new(n) unless @params.has_key? n
|
17
|
+
|
18
|
+
@params[n]
|
19
|
+
end
|
20
|
+
format_result(res)
|
21
|
+
end
|
22
|
+
|
23
|
+
def optional(*names)
|
24
|
+
res = names.map do |n|
|
25
|
+
if @params.has_key? n
|
26
|
+
@params[n]
|
27
|
+
else
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
format_result(res)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def format_result(result)
|
36
|
+
return result.first if result.length == 1
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class MissingParameterException < ArgumentError
|
42
|
+
def initialize(name)
|
43
|
+
super("missing parameter #{name}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'dbus'
|
2
|
+
require 'uuid'
|
3
|
+
require 'net/ssh'
|
4
|
+
require 'net/scp'
|
5
|
+
require 'uri'
|
6
|
+
require 'circus/agents/dbus_connection'
|
7
|
+
|
8
|
+
module Circus
|
9
|
+
module Agents
|
10
|
+
class SSHConnection < DBusConnection
|
11
|
+
def initialize(target)
|
12
|
+
addr = "/tmp/circus-#{UUID.generate}"
|
13
|
+
@uri = URI.parse(target)
|
14
|
+
@ssh = open_ssh
|
15
|
+
# Determine our remote uid so we can use that in authentication requests
|
16
|
+
remote_uid = @ssh.exec!('id -u').strip
|
17
|
+
|
18
|
+
@acceptor = Socket.new(Socket::Constants::PF_UNIX, Socket::Constants::SOCK_STREAM, 0)
|
19
|
+
@acceptor.bind(Socket.pack_sockaddr_un(addr))
|
20
|
+
@acceptor.listen(5)
|
21
|
+
|
22
|
+
Thread.new do
|
23
|
+
begin
|
24
|
+
socket, info = @acceptor.accept
|
25
|
+
@ssh.open_channel do |channel|
|
26
|
+
channel.exec('nc -U /var/run/dbus/system_bus_socket') do |ch, success|
|
27
|
+
abort "Couldn't open DBus connection" unless success
|
28
|
+
|
29
|
+
socket.extend(Net::SSH::BufferedIo)
|
30
|
+
@ssh.listen_to(socket)
|
31
|
+
|
32
|
+
ch.on_process do
|
33
|
+
if socket.available > 0
|
34
|
+
ch.send_data(socket.read_available)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ch.on_data do |ch, data|
|
39
|
+
socket.write(data)
|
40
|
+
end
|
41
|
+
|
42
|
+
ch.on_close do
|
43
|
+
@ssh.stop_listening_to(socket)
|
44
|
+
socket.close
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@ssh.loop
|
49
|
+
rescue
|
50
|
+
puts "SSH connector thread died:"
|
51
|
+
puts $!, $@
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
bus = RemoteDBusConnection.new("unix:path=#{addr}", remote_uid)
|
56
|
+
|
57
|
+
super(bus)
|
58
|
+
end
|
59
|
+
|
60
|
+
def send_file(fn)
|
61
|
+
# Perform an SCP upload of the file into the /tmp of the target
|
62
|
+
target_fn = File.join('/tmp', File.basename(fn))
|
63
|
+
|
64
|
+
scp_ssh = open_ssh
|
65
|
+
scp = Net::SCP.new(scp_ssh)
|
66
|
+
scp.upload!(fn, target_fn)
|
67
|
+
scp_ssh.exec!("chmod ugo+r #{target_fn}")
|
68
|
+
scp_ssh.close
|
69
|
+
target_fn
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def open_ssh
|
74
|
+
Net::SSH.start(@uri.host, @uri.user || ENV['USER'], :port => @uri.port || 22)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class RemoteDBusConnection < DBus::Connection
|
79
|
+
def initialize(path, remote_uid)
|
80
|
+
super(path)
|
81
|
+
|
82
|
+
@remote_uid = remote_uid
|
83
|
+
|
84
|
+
connect
|
85
|
+
send_hello
|
86
|
+
end
|
87
|
+
|
88
|
+
def init_connection
|
89
|
+
@client = DBus::Client.new(@socket)
|
90
|
+
@client.auth_list = [RemoteExternal.new(@remote_uid)]
|
91
|
+
@client.authenticate
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class RemoteExternal
|
96
|
+
def initialize(uid)
|
97
|
+
@uid = uid
|
98
|
+
end
|
99
|
+
|
100
|
+
# Make this look like a class that can be instantiated
|
101
|
+
def new
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def name
|
106
|
+
'EXTERNAL'
|
107
|
+
end
|
108
|
+
|
109
|
+
def authenticate
|
110
|
+
@uid.to_s.split(//).collect { |a| "%x" % a[0].ord }.join
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module DBus
|
117
|
+
class Client
|
118
|
+
attr_accessor :auth_list
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Circus
|
5
|
+
class Application
|
6
|
+
attr_reader :acts
|
7
|
+
|
8
|
+
def initialize(dir, name = nil)
|
9
|
+
@dir = dir
|
10
|
+
@name = name || File.basename(dir)
|
11
|
+
@acts = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def load!
|
15
|
+
@acts = if File.exists?(acts_file)
|
16
|
+
acts_cfg = YAML.load(File.read(acts_file))
|
17
|
+
acts_cfg.map do |name, props|
|
18
|
+
# If no props are specified in the YAML, this could be nil. Default it.
|
19
|
+
props ||= {}
|
20
|
+
|
21
|
+
act_dir = File.join(@dir, props['dir'] || name)
|
22
|
+
Act.new(name, act_dir, props)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
[Act.new(@name, @dir)]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Instructs the application to startup in place and activate all of its associated
|
30
|
+
# acts.
|
31
|
+
def go!(logger)
|
32
|
+
load! unless @acts
|
33
|
+
|
34
|
+
FileUtils.rm_rf(private_run_root)
|
35
|
+
acts.each do |act|
|
36
|
+
act.package_for_dev(logger, private_run_root)
|
37
|
+
end
|
38
|
+
acts.each do |act|
|
39
|
+
logger.info "Starting act #{act.name} at #{act.dir} using profile #{act.profile.name}"
|
40
|
+
end
|
41
|
+
logger.info "---------------------"
|
42
|
+
|
43
|
+
system("svscan #{private_run_root}")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Instructs the application to assemble it's components for deployment and generate
|
47
|
+
# act output files.
|
48
|
+
def assemble!(output_dir, logger, dev = false, only_acts = nil)
|
49
|
+
load! unless @acts
|
50
|
+
|
51
|
+
if dev
|
52
|
+
acts.each do |act|
|
53
|
+
logger.info "Assembling development act #{act.name} at #{act.dir}"
|
54
|
+
act.package_for_dev(logger, private_run_root)
|
55
|
+
end
|
56
|
+
return true
|
57
|
+
end
|
58
|
+
|
59
|
+
assembly_acts = acts.select {|a| only_acts.nil? or only_acts.include? a.name }
|
60
|
+
|
61
|
+
FileUtils.mkdir_p(output_dir)
|
62
|
+
assembly_acts.each do |act|
|
63
|
+
act.detect!
|
64
|
+
if act.should_package?
|
65
|
+
logger.info "Assembling act #{act.name} at #{act.dir} using profile #{act.profile.name}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
logger.info "---------------------"
|
69
|
+
assembly_acts.each do |act|
|
70
|
+
if act.should_package?
|
71
|
+
return false unless act.assemble(logger, output_dir, private_overlay_root)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def upload(output_dir, act_store, only_acts = nil)
|
79
|
+
load! unless @acts
|
80
|
+
upload_acts = acts.select {|a| only_acts.nil? or only_acts.include? a.name }
|
81
|
+
|
82
|
+
upload_acts.each do |act|
|
83
|
+
act.upload(output_dir, act_store)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def private_run_root
|
88
|
+
File.join(@dir, '.circus', 'run-dev')
|
89
|
+
end
|
90
|
+
|
91
|
+
def private_overlay_root
|
92
|
+
File.join(@dir, '.circus', 'overlays')
|
93
|
+
end
|
94
|
+
|
95
|
+
def acts_file
|
96
|
+
File.join(@dir, 'acts.yaml')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'circus/agents/client'
|
2
|
+
require 'circus/agents/encoding'
|
3
|
+
|
4
|
+
module Circus
|
5
|
+
class BoothClient < Circus::Agents::Client
|
6
|
+
def initialize(connection, logger)
|
7
|
+
super(connection)
|
8
|
+
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_booth(booth, name, scm_type, scm_url)
|
13
|
+
@connection.call(booth, 'Booth', 'Booth', 'create', {'name' => name, 'scm' => scm_type, 'scm_url' => scm_url}, @logger)
|
14
|
+
end
|
15
|
+
|
16
|
+
def admit(booth, booth_id, commit_id, patch_fn, acts)
|
17
|
+
@connection.call(booth, 'Booth', 'Booth', 'admit',
|
18
|
+
{'app_id' => booth_id, 'commit_id' => commit_id, 'patch_fn' => patch_fn || '', 'acts' => acts.join(',')}, @logger)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_ssh_key(booth)
|
22
|
+
@connection.call(booth, 'Booth', 'Booth', 'get_ssh_key', {}, @logger)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'circus/booth_client'
|
2
|
+
require 'circus/clown_client'
|
3
|
+
require 'circus/agents/encoding'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module Circus
|
7
|
+
class BoothTool
|
8
|
+
def initialize(logger, config)
|
9
|
+
@logger = logger
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect(name, booth, options = {})
|
14
|
+
# Detect the facets of the repository
|
15
|
+
repo_type_clazz = if options[:repo_type]
|
16
|
+
Repos.find_repo_by_id(options[:repo_type])
|
17
|
+
else
|
18
|
+
Repos.find_repo_from_dir(File.expand_path('.'))
|
19
|
+
end
|
20
|
+
repo_type = repo_type_clazz.type_id
|
21
|
+
repo_url = if options[:repo_url]
|
22
|
+
options[:repo_url]
|
23
|
+
else
|
24
|
+
repo_helper = repo_type_clazz.new(File.expand_path('.'))
|
25
|
+
repo_helper.repo_url
|
26
|
+
end
|
27
|
+
|
28
|
+
app_name = options[:app_name] || File.basename(File.expand_path('.'))
|
29
|
+
|
30
|
+
@logger.info("Creating booth connection #{name} on #{booth} for #{repo_url} as #{app_name}")
|
31
|
+
connection = ConnectionBuilder.new(options).build(booth)
|
32
|
+
|
33
|
+
client = BoothClient.new(connection, @logger)
|
34
|
+
booth_id = client.create_booth(booth, app_name, repo_type, repo_url).result
|
35
|
+
|
36
|
+
@config.booths[name] = {
|
37
|
+
:booth => booth, :booth_id => booth_id,
|
38
|
+
:repo_type => repo_type,
|
39
|
+
:target => options[:deploy_target] || booth
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def admit(name, apps, options)
|
44
|
+
booth = get_booth_or_default(name)
|
45
|
+
return unless booth
|
46
|
+
|
47
|
+
repo_helper = Repos.find_repo_by_id(booth[:repo_type]).new(File.expand_path('.'))
|
48
|
+
current_rev = repo_helper.current_revision
|
49
|
+
|
50
|
+
@logger.info("Starting admission into #{name} of version #{current_rev}")
|
51
|
+
connection = ConnectionBuilder.new(booth).build(booth[:booth])
|
52
|
+
# connection.configure_bg!(OpenStruct.new(booth))
|
53
|
+
client = BoothClient.new(connection, @logger)
|
54
|
+
apply_patch_fn = if options[:uncommitted]
|
55
|
+
patch_fn = Tempfile.new('booth').path
|
56
|
+
repo_helper.write_patch(patch_fn)
|
57
|
+
|
58
|
+
connection.send_file(patch_fn)
|
59
|
+
else
|
60
|
+
''
|
61
|
+
end
|
62
|
+
admitted = Circus::Agents::Encoding.decode(client.admit(booth[:booth], booth[:booth_id], current_rev, apply_patch_fn, apps).result)
|
63
|
+
|
64
|
+
clown_connection = ConnectionBuilder.new(options).build(booth[:target])
|
65
|
+
clown_client = ClownClient.new(clown_connection, @logger)
|
66
|
+
admitted.each do |name, url|
|
67
|
+
@logger.info("Executing deployment of #{name} from #{url} to #{booth[:target]}")
|
68
|
+
clown_client.deploy(booth[:target], name, url).result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def get_booth_or_default(name)
|
74
|
+
unless name
|
75
|
+
if @config.booths.count == 0
|
76
|
+
@logger.error("No booths configured. Please configure a booth with 'connect' first.")
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
if @config.booths.count > 1
|
81
|
+
@logger.error("A booth name needs to be provided when multiple booths have been configured. " +
|
82
|
+
"One of #{local_config.booths.keys.join(', ')} should be specified")
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
|
86
|
+
name = @config.booths.keys.first
|
87
|
+
end
|
88
|
+
|
89
|
+
booth = @config.booths[name]
|
90
|
+
unless booth
|
91
|
+
@logger.error("No booth #{name} is configured. Configure it first with 'connect'.")
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
|
95
|
+
booth
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/circus/cli.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'thor'
|
3
|
+
#require 'circus/agents/connection'
|
4
|
+
require 'circus/clown_client'
|
5
|
+
require 'circus/booth_client'
|
6
|
+
require 'circus/booth_tool'
|
7
|
+
require 'circus/version'
|
8
|
+
|
9
|
+
module Circus
|
10
|
+
class CLI < Thor
|
11
|
+
LOGGER = StdoutLogger.new
|
12
|
+
|
13
|
+
#
|
14
|
+
# Global Options
|
15
|
+
#
|
16
|
+
|
17
|
+
class_option :source, :default => '.', :desc => 'Source directory containing the application'
|
18
|
+
|
19
|
+
|
20
|
+
#
|
21
|
+
# Actions
|
22
|
+
#
|
23
|
+
|
24
|
+
desc "go", "Run the current application in place for development"
|
25
|
+
def go
|
26
|
+
load!
|
27
|
+
@app.go!(LOGGER)
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "assemble", "Assemble the application's acts for deployment"
|
31
|
+
method_option :output, :default => '.circus/acts/', :desc => 'Destination directory for generated acts'
|
32
|
+
method_option :actstore, :desc => 'URL of the act store to upload the built acts to'
|
33
|
+
method_option :dev, :type => :boolean, :default => false, :desc => 'Make assembly for local dev use only'
|
34
|
+
def assemble
|
35
|
+
load!
|
36
|
+
|
37
|
+
output_path = File.expand_path(options[:output])
|
38
|
+
dev = options[:dev]
|
39
|
+
|
40
|
+
@app.assemble!(output_path, LOGGER, dev)
|
41
|
+
if options[:actstore]
|
42
|
+
store = ActStoreClient.new(options[:actstore], LOGGER)
|
43
|
+
@app.upload(output_path, store)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "upload FILENAME", "Uploads the given set of act files to act store"
|
48
|
+
method_option :actstore, :required => true, :desc => 'URL of the act store to upload builds to'
|
49
|
+
def upload(fn)
|
50
|
+
store = ActStoreClient.new(options[:actstore], LOGGER)
|
51
|
+
store.upload_act(fn)
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "get-booth-key BOOTH", "Retrieves the public key that a booth will use when connecting to SSH resources"
|
55
|
+
def get_booth_key(booth)
|
56
|
+
connection = ConnectionBuilder.new(options).build(booth)
|
57
|
+
|
58
|
+
client = BoothClient.new(connection, LOGGER)
|
59
|
+
key = client.get_ssh_key(booth).result
|
60
|
+
puts "SSH key for: #{booth}"
|
61
|
+
puts " #{key}"
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "connect NAME BOOTH", "Associates the local application with the provided booth to allow for deployment"
|
65
|
+
method_option :deploy_target, :desc => 'The target for application deployment. Uses same host as booth if not specified.'
|
66
|
+
method_option :app_name, :desc => 'The name of the application being configured. Defaults to application directory name.'
|
67
|
+
method_option :repo_type, :desc => 'The type of the repository being deployed. Defaults to type of current working directory.'
|
68
|
+
method_option :repo_url, :desc => 'The URL of the repository being deployed. Defaults to the primary url of current working directory.'
|
69
|
+
|
70
|
+
# method_option :jid, :required => true, :desc => 'The deployer jid to connect as'
|
71
|
+
# method_option :host, :required => true, :desc => 'The XMPP host to connect to'
|
72
|
+
# method_option :port, :type => :numeric, :default => 5222, :desc => 'The XMPP port to connect to'
|
73
|
+
# method_option :password, :required => true, :desc => 'The XMPP user password'
|
74
|
+
def connect(name, booth)
|
75
|
+
config = LocalConfig.new
|
76
|
+
tool = BoothTool.new(LOGGER, config)
|
77
|
+
tool.connect(name, booth, options)
|
78
|
+
config.save!
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "admit [NAME] [ACT1 ACT2]", "Admits and deploys the application with the given booth reference"
|
82
|
+
method_option :uncommitted, :desc => 'Indicates that the current uncommitted changes should be sent to the booth and included in the app'
|
83
|
+
def admit(name = nil, *apps)
|
84
|
+
tool = BoothTool.new(LOGGER, LocalConfig.new)
|
85
|
+
tool.admit(name, apps, options)
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "exec TARGET [ACT] [CMD]", "Executes the given command in the deployed context of the given act"
|
89
|
+
# method_option :jid, :required => true, :desc => 'The deployer jid to connect as'
|
90
|
+
# method_option :host, :required => true, :desc => 'The XMPP host to connect to'
|
91
|
+
# method_option :port, :type => :numeric, :default => 5222, :desc => 'The XMPP port to connect to'
|
92
|
+
# method_option :password, :required => true, :desc => 'The XMPP user password'
|
93
|
+
def exec(target, act_name, *cmd_parts)
|
94
|
+
cmd = cmd_parts.join(' ')
|
95
|
+
|
96
|
+
connection = ConnectionBuilder.new(options).build(target)
|
97
|
+
client = ClownClient.new(connection, LOGGER)
|
98
|
+
client.exec(target, act_name, cmd).result
|
99
|
+
end
|
100
|
+
|
101
|
+
desc "deploy TARGET NAME ACT", "Deploy the named object using the given act onto the given target server"
|
102
|
+
method_option :config, :desc => 'URL of configuration object to use at deployment'
|
103
|
+
# method_option :actstore, :required => true, :desc => 'The store to retrieve the act from'
|
104
|
+
# method_option :jid, :required => true, :desc => 'The deployer jid to connect as'
|
105
|
+
# method_option :host, :required => true, :desc => 'The XMPP host to connect to'
|
106
|
+
# method_option :port, :type => :numeric, :default => 5222, :desc => 'The XMPP port to connect to'
|
107
|
+
# method_option :password, :required => true, :desc => 'The XMPP user password'
|
108
|
+
def deploy(target, name, act)
|
109
|
+
connection = ConnectionBuilder.new(options).build(target)
|
110
|
+
|
111
|
+
client = ClownClient.new(connection, LOGGER)
|
112
|
+
client.deploy(target, name, act, options[:config]).result
|
113
|
+
end
|
114
|
+
|
115
|
+
desc "undeploy TARGET NAME", "Undeploys the named act from the given target server"
|
116
|
+
# method_option :jid, :required => true, :desc => 'The deployer jid to connect as'
|
117
|
+
# method_option :host, :required => true, :desc => 'The XMPP host to connect to'
|
118
|
+
# method_option :port, :type => :numeric, :default => 5222, :desc => 'The XMPP port to connect to'
|
119
|
+
# method_option :password, :required => true, :desc => 'The XMPP user password'
|
120
|
+
def undeploy(target, name)
|
121
|
+
connection = ConnectionBuilder.new(options).build(target)
|
122
|
+
|
123
|
+
client = ClownClient.new(connection, LOGGER)
|
124
|
+
client.undeploy(target, name).result
|
125
|
+
end
|
126
|
+
|
127
|
+
desc "configure TARGET NAME CONFIG", "(Re)configures the given act on the given target using the provided config details"
|
128
|
+
def configure(target, name, config)
|
129
|
+
connection = ConnectionBuilder.new(options).build(target)
|
130
|
+
|
131
|
+
client = ClownClient.new(connection, LOGGER)
|
132
|
+
client.configure(target, name, config).result
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "alias NAME TARGET", "Adds an alias so that NAME can be used when target TARGET is desired"
|
136
|
+
def alias(name, target)
|
137
|
+
local_config = LocalConfig.new
|
138
|
+
local_config.aliases[name] = target
|
139
|
+
local_config.save!
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
def load!
|
144
|
+
@app = Circus::Application.new(File.expand_path(options[:source]))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'circus/agents/client'
|
2
|
+
|
3
|
+
module Circus
|
4
|
+
class ClownClient < Circus::Agents::Client
|
5
|
+
def initialize(connection, logger)
|
6
|
+
super(connection)
|
7
|
+
|
8
|
+
@logger = logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def deploy(node, name, act_url, config_url = nil)
|
12
|
+
@connection.call(node, 'Clown', 'Clown', 'deploy', {'name' => name, 'url' => act_url, 'config_url' => config_url || ''}, @logger)
|
13
|
+
end
|
14
|
+
|
15
|
+
def configure(node, name, config_url)
|
16
|
+
@connection.call(node, 'Clown', 'Clown', 'configure', {'name' => name, 'config_url' => config_url}, @logger)
|
17
|
+
end
|
18
|
+
|
19
|
+
def undeploy(node, name)
|
20
|
+
@connection.call(node, 'Clown', 'Clown', 'undeploy', {'name' => name}, @logger)
|
21
|
+
end
|
22
|
+
|
23
|
+
def exec(node, name, cmd)
|
24
|
+
@connection.call(node, 'Clown', 'Clown', 'exec', {'name' => name, 'command' => cmd}, @logger)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'circus/agents/dbus_connection'
|
2
|
+
require 'circus/agents/ssh_connection'
|
3
|
+
|
4
|
+
module Circus
|
5
|
+
class ConnectionBuilder
|
6
|
+
def initialize(options)
|
7
|
+
@options = options
|
8
|
+
@local_config = LocalConfig.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def build(target)
|
12
|
+
if not target.include? ':'
|
13
|
+
if @local_config.aliases.include? target
|
14
|
+
build(@local_config.aliases[target])
|
15
|
+
else
|
16
|
+
raise ArgumentError.new("Invalid target #{target} - looks like an alias, but none configured to match")
|
17
|
+
end
|
18
|
+
elsif target.start_with? 'local:'
|
19
|
+
# Local connection
|
20
|
+
client = Agents::DBusConnection.new
|
21
|
+
# client.configure_bg!
|
22
|
+
client
|
23
|
+
elsif target.start_with? 'ssh://'
|
24
|
+
# SSH connection
|
25
|
+
client = Agents::SSHConnection.new(target)
|
26
|
+
client
|
27
|
+
elsif
|
28
|
+
raise ArgumentError.new("Ivalid target #{target} - unknown protocol")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Circus
|
4
|
+
class LocalConfig
|
5
|
+
def initialize(dir = '.')
|
6
|
+
@dir = File.expand_path(dir)
|
7
|
+
@is_new = true
|
8
|
+
|
9
|
+
@store_fn = find_store_fn
|
10
|
+
load!
|
11
|
+
end
|
12
|
+
|
13
|
+
def booths
|
14
|
+
@state[:booths] ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def aliases
|
18
|
+
@state[:aliases] ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def new?
|
22
|
+
@is_new
|
23
|
+
end
|
24
|
+
|
25
|
+
def load!
|
26
|
+
if File.exists? @store_fn
|
27
|
+
@is_new = false
|
28
|
+
@state = YAML::load(File.read(@store_fn))
|
29
|
+
else
|
30
|
+
@state = {}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def save!
|
35
|
+
FileUtils.mkdir_p(File.dirname(@store_fn))
|
36
|
+
File.open(@store_fn, 'w') do |f|
|
37
|
+
YAML::dump(@state, f)
|
38
|
+
end
|
39
|
+
@is_new = false
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def find_store_fn
|
44
|
+
path = @dir
|
45
|
+
fn = store_fn_for_dir(path)
|
46
|
+
|
47
|
+
until File.exists? fn
|
48
|
+
next_path = File.dirname(path)
|
49
|
+
|
50
|
+
# If we've hit the root, then the filename will keep repeating.
|
51
|
+
return store_fn_for_dir(@dir) if path == next_path
|
52
|
+
|
53
|
+
# Build for the next loop
|
54
|
+
path = next_path
|
55
|
+
fn = store_fn_for_dir(path)
|
56
|
+
end
|
57
|
+
|
58
|
+
fn
|
59
|
+
end
|
60
|
+
|
61
|
+
def store_fn_for_dir(dir)
|
62
|
+
File.join(dir, '.circus/config')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|