circus-deployment 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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