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.
Files changed (94) hide show
  1. data/LICENSE +23 -0
  2. data/README.md +0 -0
  3. data/bin/circus +24 -0
  4. data/lib/bundler/circus_bundler.rb +24 -0
  5. data/lib/bundler/circus_util.rb +43 -0
  6. data/lib/bundler/patches.rb +18 -0
  7. data/lib/circus/act.rb +74 -0
  8. data/lib/circus/actstore_client.rb +30 -0
  9. data/lib/circus/agents/agent.rb +59 -0
  10. data/lib/circus/agents/client.rb +77 -0
  11. data/lib/circus/agents/connection.rb +76 -0
  12. data/lib/circus/agents/conversation.rb +17 -0
  13. data/lib/circus/agents/dbus_connection.rb +85 -0
  14. data/lib/circus/agents/dbus_logger.rb +34 -0
  15. data/lib/circus/agents/encoding.rb +32 -0
  16. data/lib/circus/agents/logger.rb +52 -0
  17. data/lib/circus/agents/params.rb +47 -0
  18. data/lib/circus/agents/ssh_connection.rb +120 -0
  19. data/lib/circus/application.rb +99 -0
  20. data/lib/circus/booth_client.rb +25 -0
  21. data/lib/circus/booth_tool.rb +98 -0
  22. data/lib/circus/cli.rb +147 -0
  23. data/lib/circus/clown_client.rb +27 -0
  24. data/lib/circus/connection_builder.rb +32 -0
  25. data/lib/circus/external_util.rb +14 -0
  26. data/lib/circus/local_config.rb +65 -0
  27. data/lib/circus/profiles/base.rb +115 -0
  28. data/lib/circus/profiles/django.rb +90 -0
  29. data/lib/circus/profiles/jekyll.rb +90 -0
  30. data/lib/circus/profiles/make_base.rb +17 -0
  31. data/lib/circus/profiles/pure_py.rb +46 -0
  32. data/lib/circus/profiles/pure_rb.rb +48 -0
  33. data/lib/circus/profiles/python_base.rb +39 -0
  34. data/lib/circus/profiles/rack.rb +59 -0
  35. data/lib/circus/profiles/ruby_base.rb +52 -0
  36. data/lib/circus/profiles/shell.rb +46 -0
  37. data/lib/circus/profiles.rb +10 -0
  38. data/lib/circus/repos/git.rb +42 -0
  39. data/lib/circus/repos/mercurial.rb +42 -0
  40. data/lib/circus/repos.rb +16 -0
  41. data/lib/circus/resource_allocator_client.rb +19 -0
  42. data/lib/circus/stdout_logger.rb +11 -0
  43. data/lib/circus/version.rb +3 -0
  44. data/lib/circus.rb +9 -0
  45. data/vendor/ruby-dbus/COPYING +504 -0
  46. data/vendor/ruby-dbus/ChangeLog +782 -0
  47. data/vendor/ruby-dbus/HOWTO-RELEASE +14 -0
  48. data/vendor/ruby-dbus/NEWS +104 -0
  49. data/vendor/ruby-dbus/README +53 -0
  50. data/vendor/ruby-dbus/Rakefile +47 -0
  51. data/vendor/ruby-dbus/doc/tutorial/src/00.index.page +12 -0
  52. data/vendor/ruby-dbus/doc/tutorial/src/10.intro.page +127 -0
  53. data/vendor/ruby-dbus/doc/tutorial/src/20.basic_client.page +174 -0
  54. data/vendor/ruby-dbus/doc/tutorial/src/30.service.page +121 -0
  55. data/vendor/ruby-dbus/doc/tutorial/src/default.css +129 -0
  56. data/vendor/ruby-dbus/doc/tutorial/src/default.template +46 -0
  57. data/vendor/ruby-dbus/examples/gdbus/gdbus +255 -0
  58. data/vendor/ruby-dbus/examples/gdbus/gdbus.glade +184 -0
  59. data/vendor/ruby-dbus/examples/gdbus/launch.sh +4 -0
  60. data/vendor/ruby-dbus/examples/no-introspect/nm-test.rb +21 -0
  61. data/vendor/ruby-dbus/examples/no-introspect/tracker-test.rb +16 -0
  62. data/vendor/ruby-dbus/examples/rhythmbox/playpause.rb +25 -0
  63. data/vendor/ruby-dbus/examples/service/call_service.rb +25 -0
  64. data/vendor/ruby-dbus/examples/service/service_newapi.rb +51 -0
  65. data/vendor/ruby-dbus/examples/simple/call_introspect.rb +34 -0
  66. data/vendor/ruby-dbus/examples/utils/listnames.rb +11 -0
  67. data/vendor/ruby-dbus/examples/utils/notify.rb +19 -0
  68. data/vendor/ruby-dbus/lib/dbus/auth.rb +156 -0
  69. data/vendor/ruby-dbus/lib/dbus/bus.rb +750 -0
  70. data/vendor/ruby-dbus/lib/dbus/export.rb +133 -0
  71. data/vendor/ruby-dbus/lib/dbus/introspect.rb +544 -0
  72. data/vendor/ruby-dbus/lib/dbus/marshall.rb +443 -0
  73. data/vendor/ruby-dbus/lib/dbus/matchrule.rb +100 -0
  74. data/vendor/ruby-dbus/lib/dbus/message.rb +293 -0
  75. data/vendor/ruby-dbus/lib/dbus/type.rb +222 -0
  76. data/vendor/ruby-dbus/lib/dbus.rb +89 -0
  77. data/vendor/ruby-dbus/ruby-dbus.gemspec +28 -0
  78. data/vendor/ruby-dbus/setup.rb +1585 -0
  79. data/vendor/ruby-dbus/test/Makefile +4 -0
  80. data/vendor/ruby-dbus/test/bus_driver_test.rb +21 -0
  81. data/vendor/ruby-dbus/test/server_robustness_test.rb +41 -0
  82. data/vendor/ruby-dbus/test/server_test.rb +44 -0
  83. data/vendor/ruby-dbus/test/service_newapi.rb +99 -0
  84. data/vendor/ruby-dbus/test/session_bus_test_manual.rb +20 -0
  85. data/vendor/ruby-dbus/test/signal_test.rb +57 -0
  86. data/vendor/ruby-dbus/test/t1 +4 -0
  87. data/vendor/ruby-dbus/test/t2.rb +66 -0
  88. data/vendor/ruby-dbus/test/t3-ticket27.rb +18 -0
  89. data/vendor/ruby-dbus/test/t5-report-dbus-interface.rb +58 -0
  90. data/vendor/ruby-dbus/test/t6-loop.rb +85 -0
  91. data/vendor/ruby-dbus/test/test_all +26 -0
  92. data/vendor/ruby-dbus/test/test_server +74 -0
  93. data/vendor/ruby-dbus/test/variant_test.rb +66 -0
  94. 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,14 @@
1
+ module Circus
2
+ class ExternalUtil
3
+ def self.run_external(logger, desc, cmd)
4
+ res = `#{cmd} 2>&1`
5
+ if $? != 0
6
+ logger.error "#{desc} failed:"
7
+ logger.error res
8
+ false
9
+ else
10
+ true
11
+ end
12
+ end
13
+ end
14
+ 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