circus-deployment 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|