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