rubble 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.
- 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
|