rubble 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ require 'rubble/plan/base'
2
+
3
+ module Rubble
4
+ module Plan
5
+ class Activate < Base
6
+ def execute
7
+ current_war = File.join(deploy_dir, 'current', target.war)
8
+ remote.cd(target.webapps_dir)
9
+ remote.symlink(current_war, File.basename(current_war))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ require 'rubble/dsl'
2
+
3
+ module Rubble
4
+ module Command
5
+ class Base
6
+ attr_reader :plan
7
+ attr_reader :context
8
+ attr_reader :local
9
+ attr_reader :remote
10
+
11
+ def initialize(plan, context)
12
+ @log = Logging.logger[self]
13
+ @plan = plan
14
+ @context = context
15
+ end
16
+
17
+ def server
18
+ @plan.server
19
+ end
20
+
21
+ def target
22
+ @plan.target
23
+ end
24
+
25
+ def resource
26
+ @plan.resource
27
+ end
28
+
29
+ def env
30
+ @plan.env
31
+ end
32
+
33
+ def local
34
+ if @local.nil? then
35
+ @local = context.local_executor
36
+ end
37
+ @local
38
+ end
39
+
40
+ def remote
41
+ if @remote.nil? then
42
+ @remote = context.remote_executor(server)
43
+ end
44
+ @remote
45
+ end
46
+
47
+ def resolve(string)
48
+ string = '"' << string.gsub('"', '\"') << '"'
49
+ eval(string, binding)
50
+ end
51
+
52
+ def deploy_dir
53
+ resolve(File.join(server.rubble_dir, server.deploy_dir))
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubble/command/base'
2
+ require 'docile'
3
+
4
+ module Rubble
5
+ module Command
6
+ class Upload < Base
7
+ def execute
8
+ snapshot = resource.snapshot
9
+ target_dir = File.join(deploy_dir, snapshot.version)
10
+
11
+ @log.debug("Uploading snapshot #{snapshot}")
12
+
13
+ remote.mkdir(target_dir)
14
+ remote.cd(deploy_dir, true)
15
+
16
+ if not snapshot.empty? then
17
+ if remote.file_exists?('current') then
18
+ # remote.rsync('current/', "#{snapshot.version}/")
19
+ end
20
+
21
+ remote.sync_up(snapshot.filesets, target_dir)
22
+ end
23
+
24
+ remote.symlink(snapshot.version, 'current')
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ require 'base32'
2
+ require 'rubble/executor/local'
3
+ require 'rubble/executor/remote'
4
+
5
+ module Rubble
6
+ class Context
7
+ attr_reader :uuid
8
+ attr_reader :tool
9
+ attr_reader :local_executor
10
+
11
+ def initialize(tool)
12
+ @tool = tool
13
+ uuid_str = SecureRandom.uuid.gsub(/-/, '')
14
+ uuid_bytes = [uuid_str].pack('H*')
15
+ @uuid = Base32.encode(uuid_bytes).gsub(/=+$/, '')
16
+ @remote_executors = {}
17
+ @local_executor = Executor::Local.new
18
+ end
19
+
20
+ def remote_executor(server)
21
+ @remote_executors.fetch(server.name) do |k|
22
+ @remote_executors[server.name] = Executor::Remote.new(server, local_executor)
23
+ end
24
+ end
25
+
26
+ def close
27
+ @remote_executors.each_value do |executor|
28
+ executor.close
29
+ end
30
+ @remote_executors = {}
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ class Module
2
+ # Create a DSL style accessor
3
+ #
4
+ # Works like :attr_accessor, with the difference that the getter has an optional parameter that sets the property
5
+ # when present.
6
+ #
7
+ # @example Create a mutable DLS style property named 'plan'
8
+ #
9
+ # class Processor
10
+ # dsl_accessor :plan
11
+ # end
12
+ #
13
+ # @param symbols [Array] names of the properties to create
14
+ def dsl_accessor(*symbols)
15
+ symbols.each do |symbol|
16
+ class_eval %{
17
+ def #{symbol}(value = nil)
18
+ if value.nil?
19
+ @#{symbol}
20
+ else
21
+ @#{symbol} = value
22
+ end
23
+ end
24
+
25
+ def #{symbol}=(value)
26
+ @#{symbol} = value
27
+ end
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubble/scope'
2
+ require 'docile'
3
+
4
+ module Rubble
5
+ class Environment
6
+ attr_reader :tool
7
+ attr_reader :name
8
+ attr_reader :plans
9
+
10
+ # Action statt plan
11
+
12
+ def initialize(tool, name)
13
+ @tool = tool
14
+ @name = name
15
+ @plans = []
16
+ end
17
+
18
+ def server(*names, &block)
19
+ names.each do |name|
20
+ server = @tool.provide_server(name)
21
+ scope = Scope.new(self, server)
22
+ if not block.nil? then
23
+ Docile.dsl_eval(scope, &block)
24
+ end
25
+ end
26
+ end
27
+
28
+ def add_plan(plan)
29
+ @plans << plan
30
+ end
31
+
32
+ def execute(context)
33
+ @plans.each do |plan|
34
+ yield(plan, context)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,134 @@
1
+ require 'logging'
2
+ require 'tempfile'
3
+ require 'pathname'
4
+ require 'shellwords'
5
+ require 'ostruct'
6
+
7
+ module Rubble
8
+ module Executor
9
+ SyncList = Struct.new(:dirs, :files)
10
+
11
+ class Base
12
+ def initialize
13
+ @log = Logging.logger[self]
14
+ end
15
+
16
+ def exec(*command)
17
+ run(*command)
18
+ end
19
+
20
+ def convert_options(options)
21
+ switches = []
22
+
23
+ options.each do |k, v|
24
+ if not v.nil? then
25
+ switches << "--#{k.to_s.gsub(/_/, '-')}"
26
+ if not v == true then
27
+ switches << v
28
+ end
29
+ end
30
+ end
31
+
32
+ switches
33
+ end
34
+
35
+ def rsync_includes(filesets)
36
+ dirs = []
37
+ files = Set.new
38
+
39
+ filesets.each do |fileset|
40
+ dirs << "#{fileset.dir.to_s}/"
41
+
42
+ fileset.files.each do |file|
43
+ until file.to_s == '.'
44
+ files << "+ #{file.to_s}"
45
+ file = file.parent
46
+ end
47
+ end
48
+ end
49
+
50
+ files = files.to_a.sort
51
+ files << '- *'
52
+
53
+ SyncList.new(dirs, files)
54
+ end
55
+
56
+ def rsync_remote_prefix
57
+ ''
58
+ end
59
+
60
+ def rsync(*parameters)
61
+ paths = []
62
+ options = {
63
+ :recursive => nil,
64
+ :dirs => nil,
65
+ :delete => nil,
66
+ :delete_excluded => nil,
67
+ :include_from => nil,
68
+ :rsh => 'ssh -o ClearAllForwardings=Yes',
69
+ :verbose => nil
70
+ }
71
+ includes = []
72
+
73
+ parameters.each do |parameter|
74
+ if parameter.is_a?(Hash) then
75
+ parameter.each do |name, value|
76
+ if name.to_s == 'includes' then
77
+ Array(value).each do |v|
78
+ includes << v
79
+ end
80
+ elsif options.include?(name) then
81
+ options[name] = value
82
+ else
83
+ raise "Unknown rsync parameter #{name}."
84
+ end
85
+ end
86
+ else
87
+ paths << parameter
88
+ end
89
+ end
90
+
91
+ begin
92
+ tempfile = nil
93
+
94
+ if not includes.empty? then
95
+ tempfile = Tempfile.new(['includes', '.rsync'])
96
+ begin
97
+ includes.each do |f|
98
+ tempfile.puts f
99
+ end
100
+ end
101
+ tempfile.close
102
+
103
+ options[:include_from] = tempfile.path
104
+ end
105
+
106
+ command = ['rsync'].concat(['-vv']).concat(convert_options(options)).concat(paths)
107
+
108
+ @log.info("Rsyncing #{paths[0..-2].join(', ')} to #{paths[-1]}.")
109
+ run(*command)
110
+ ensure
111
+ tempfile && tempfile.close!
112
+ end
113
+ end
114
+
115
+ # Redirect stdout and return output
116
+ #
117
+ # @yield Execute block with redirected stdout
118
+ # @return [String] Output
119
+ def redirect
120
+ begin
121
+ old_stdout = $stdout
122
+ old_stderr = $stderr
123
+ $stdout = StringIO.new('', 'w')
124
+ $stderr = $stdout
125
+ result = yield
126
+ [result, $stdout.string]
127
+ ensure
128
+ $stdout = old_stdout
129
+ $stderr = old_stderr
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,113 @@
1
+ require 'rubble/executor/base'
2
+ require 'open3'
3
+ module Rubble
4
+ module Executor
5
+ class Local < Base
6
+ class StreamInfo
7
+ attr_accessor :data
8
+ attr_accessor :level
9
+ attr_reader :autoflush
10
+ def initialize(level, autoflush)
11
+ @level = ::Logging::level_num(level)
12
+ @data = ""
13
+ @autoflush = autoflush
14
+ end
15
+ end
16
+
17
+ class Line
18
+ attr_reader :level
19
+ attr_reader :text
20
+ def initialize(level, text)
21
+ @level = level
22
+ @text = text.chomp
23
+ end
24
+ end
25
+
26
+ class Output
27
+ attr_reader :log
28
+ attr_reader :lines
29
+ def initialize(log)
30
+ @log = log
31
+ @lines = []
32
+ @flushed = false
33
+ end
34
+ def <<(line)
35
+ @lines << line
36
+ if @flushed then
37
+ @log.add(line.level, line.text)
38
+ end
39
+ end
40
+ def flush
41
+ @lines.each do |line|
42
+ @log.add(line.level, line.text)
43
+ end
44
+ @flushed = true
45
+ end
46
+ end
47
+
48
+ def mkdir(directory_name)
49
+ Pathname(directory_name).mkpath
50
+ end
51
+
52
+ def symlink(source, target)
53
+ Pathname(target).make_symlink(Pathname(source))
54
+ end
55
+
56
+ def file_exists?(file)
57
+ Pathname(file).exists?
58
+ end
59
+
60
+ def run(*command)
61
+ command_str = Shellwords.join(command)
62
+ @log.debug(command_str)
63
+
64
+ status = nil
65
+ output = Output.new(@log)
66
+
67
+ Open3.popen3(command_str) do |stdin, stdout, stderr, thread|
68
+ streams = {stdout => StreamInfo.new(:info, false), stderr => StreamInfo.new(:error, false)}
69
+
70
+ until streams.empty? do
71
+ selected, = IO::select(streams.keys, nil, nil, 0.1)
72
+
73
+ if not selected.nil? then
74
+ selected.each do |stream|
75
+ info = streams[stream]
76
+
77
+ if stream.eof? then
78
+ streams.delete(stream)
79
+
80
+ if not info.data.empty? then
81
+ output << Line.new(info.level, info.data)
82
+ end
83
+ else
84
+ data = info.data + stream.readpartial(1024)
85
+
86
+ data.each_line do |line|
87
+ if line.end_with?("\n") then
88
+ output << Line.new(info.level, line)
89
+ else
90
+ info.data = line
91
+ end
92
+ end
93
+ end
94
+
95
+ if info.autoflush then
96
+ output.flush
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ status = thread.value
103
+ end
104
+
105
+ if status.exitstatus != 0 then
106
+ output.flush
107
+ raise "Command failed with exit status #{status.exitstatus}."
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+ end