rubble 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/LICENSE.txt +674 -0
- data/README.md +29 -0
- data/bin/rubble +5 -0
- data/demo/applique/qed_test.rb +82 -0
- data/demo/applique/rubble_test.rb +33 -0
- data/demo/environment.md +32 -0
- data/demo/file_set.md +65 -0
- data/demo/logging.md +11 -0
- data/demo/server.md +53 -0
- data/demo/snapshot.md +32 -0
- data/demo/tool.md +36 -0
- data/lib/rubble.rb +15 -0
- data/lib/rubble/application.rb +82 -0
- data/lib/rubble/command/activate.rb +13 -0
- data/lib/rubble/command/base.rb +57 -0
- data/lib/rubble/command/upload.rb +28 -0
- data/lib/rubble/context.rb +33 -0
- data/lib/rubble/dsl.rb +31 -0
- data/lib/rubble/environment.rb +38 -0
- data/lib/rubble/executor/base.rb +134 -0
- data/lib/rubble/executor/local.rb +113 -0
- data/lib/rubble/executor/remote.rb +81 -0
- data/lib/rubble/file_set.rb +41 -0
- data/lib/rubble/logging.rb +23 -0
- data/lib/rubble/plan/base.rb +21 -0
- data/lib/rubble/plan/deploy.rb +11 -0
- data/lib/rubble/resource/base.rb +43 -0
- data/lib/rubble/resource/database.rb +8 -0
- data/lib/rubble/resource/fileset.rb +93 -0
- data/lib/rubble/resource/webapp.rb +22 -0
- data/lib/rubble/scope.rb +44 -0
- data/lib/rubble/server.rb +61 -0
- data/lib/rubble/snapshot.rb +21 -0
- data/lib/rubble/target/base.rb +24 -0
- data/lib/rubble/target/directory.rb +11 -0
- data/lib/rubble/target/mysql.rb +8 -0
- data/lib/rubble/target/tomcat.rb +20 -0
- data/lib/rubble/tool.rb +138 -0
- data/lib/rubble/version.rb +3 -0
- metadata +244 -0
@@ -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
|
data/lib/rubble/dsl.rb
ADDED
@@ -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
|