cult 0.1.1.pre → 0.1.2.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +48 -28
- data/cult +1 -1
- data/cult.gemspec +4 -4
- data/doc/images/masthead@0.5x.png +0 -0
- data/exe/cult +2 -5
- data/lib/cult/artifact.rb +2 -1
- data/lib/cult/bundle.rb +26 -0
- data/lib/cult/cli/common.rb +2 -0
- data/lib/cult/cli/console_cmd.rb +11 -11
- data/lib/cult/cli/cri_extensions.rb +1 -2
- data/lib/cult/cli/fleet_cmd.rb +37 -0
- data/lib/cult/cli/init_cmd.rb +0 -2
- data/lib/cult/cli/node_cmd.rb +54 -22
- data/lib/cult/cli/task_cmd.rb +25 -9
- data/lib/cult/commander.rb +78 -52
- data/lib/cult/definition.rb +4 -7
- data/lib/cult/driver.rb +1 -1
- data/lib/cult/drivers/common.rb +8 -21
- data/lib/cult/drivers/digital_ocean_driver.rb +41 -48
- data/lib/cult/drivers/linode_driver.rb +12 -19
- data/lib/cult/drivers/load.rb +0 -3
- data/lib/cult/drivers/vultr_driver.rb +33 -44
- data/lib/cult/named_array.rb +62 -14
- data/lib/cult/node.rb +43 -8
- data/lib/cult/project.rb +0 -8
- data/lib/cult/project_context.rb +23 -0
- data/lib/cult/provider.rb +0 -3
- data/lib/cult/role.rb +30 -50
- data/lib/cult/singleton_instances.rb +43 -0
- data/lib/cult/skel.rb +2 -2
- data/lib/cult/task.rb +30 -8
- data/lib/cult/template.rb +18 -70
- data/lib/cult/transaction.rb +44 -0
- data/lib/cult/transferable.rb +2 -5
- data/lib/cult/user_refinements.rb +65 -0
- data/lib/cult/version.rb +1 -1
- data/lib/cult.rb +26 -0
- data/skel/roles/all/tasks/sync +24 -0
- data/skel/roles/bootstrap/files/cult-motd +1 -1
- metadata +19 -14
- data/lib/cult/config.rb +0 -22
- data/lib/cult/drivers/script_driver.rb +0 -27
- data/skel/keys/.keep +0 -0
data/lib/cult/named_array.rb
CHANGED
@@ -73,27 +73,36 @@ module Cult
|
|
73
73
|
private :extract_regexp_options
|
74
74
|
|
75
75
|
|
76
|
-
#
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# Fallback to default behavior
|
82
|
-
return super
|
76
|
+
# Most of the named-array predicates are meant to be useful for user input
|
77
|
+
# or an interactive session. We give special behavior to certain strings
|
78
|
+
# the user might enter to convert them to a regexp, etc.
|
79
|
+
def expand_predicate(predicate)
|
80
|
+
case predicate
|
83
81
|
when String
|
84
|
-
|
82
|
+
predicate[0] == '/' ? build_regexp_from_string(predicate) : predicate
|
85
83
|
when Regexp, Proc, Range
|
86
|
-
|
87
|
-
when Symbol
|
88
|
-
|
84
|
+
predicate
|
85
|
+
when Symbol, Integer
|
86
|
+
->(v) { predicate.to_s == v.to_s }
|
89
87
|
when NilClass
|
90
|
-
|
88
|
+
nil
|
91
89
|
else
|
92
|
-
fail KeyError, "#{
|
90
|
+
fail KeyError, "Invalid predicate: #{predicate.inspect}"
|
93
91
|
end
|
92
|
+
end
|
93
|
+
private :expand_predicate
|
94
|
+
|
95
|
+
|
96
|
+
# Returns all keys that match if method == :select, the first if
|
97
|
+
# method == :find
|
98
|
+
def all(key, method = :select)
|
99
|
+
return super if key.is_a?(Integer)
|
100
|
+
return nil if key.nil?
|
101
|
+
|
102
|
+
predicate = expand_predicate(key)
|
94
103
|
|
95
104
|
send(method) do |v|
|
96
|
-
|
105
|
+
predicate === v.named_array_identifier
|
97
106
|
end
|
98
107
|
end
|
99
108
|
|
@@ -125,5 +134,44 @@ module Cult
|
|
125
134
|
def values
|
126
135
|
self
|
127
136
|
end
|
137
|
+
|
138
|
+
# Takes a predicate in the form of:
|
139
|
+
# key: value
|
140
|
+
# And returns all items that both respond_to?(key), and
|
141
|
+
# predicate === the result of sending key.
|
142
|
+
#
|
143
|
+
# Instances can override what predicates mean by defining "names_for_*" to
|
144
|
+
# override what is tested.
|
145
|
+
#
|
146
|
+
# For example, if you have an Object that contains a list of "Foos", but
|
147
|
+
# you want to select them by name, you'd do something like:
|
148
|
+
#
|
149
|
+
# class Object
|
150
|
+
# attr_reader :foos # Instances of Foo class
|
151
|
+
#
|
152
|
+
# def names_for_foos # Now we can select by name
|
153
|
+
# foos.map(&:name)
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
def with(**kw)
|
158
|
+
fail ArgumentError, "with requires exactly one predicate" if kw.size != 1
|
159
|
+
|
160
|
+
method, predicate = kw.first
|
161
|
+
predicate = expand_predicate(predicate)
|
162
|
+
|
163
|
+
select do |candidate|
|
164
|
+
method = ["names_for_#{method}", method].find do |m|
|
165
|
+
candidate.respond_to?(m)
|
166
|
+
end
|
167
|
+
|
168
|
+
if method
|
169
|
+
result = Array(candidate.send(method))
|
170
|
+
result.any? {|r| predicate === r }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
alias_method :where, :with
|
128
176
|
end
|
129
177
|
end
|
data/lib/cult/node.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
require 'cult/role'
|
2
1
|
require 'fileutils'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
require 'cult/role'
|
3
5
|
|
4
6
|
module Cult
|
5
7
|
class Node < Role
|
@@ -10,16 +12,17 @@ module Cult
|
|
10
12
|
FileUtils.mkdir_p(node.path)
|
11
13
|
File.write(project.dump_name(node.node_path),
|
12
14
|
project.dump_object(data))
|
15
|
+
|
16
|
+
node.generate_ssh_keys!
|
17
|
+
|
13
18
|
return by_name(project, data[:name])
|
14
19
|
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
21
|
+
delegate_to_definition :host
|
22
|
+
delegate_to_definition :ipv4_public
|
23
|
+
delegate_to_definition :ipv4_private
|
24
|
+
delegate_to_definition :ipv6_public
|
25
|
+
delegate_to_definition :ipv6_private
|
23
26
|
|
24
27
|
|
25
28
|
def self.path(project)
|
@@ -56,7 +59,39 @@ module Cult
|
|
56
59
|
definition.direct('roles') || super
|
57
60
|
end
|
58
61
|
|
62
|
+
def provider
|
63
|
+
project.providers[definition['provider']]
|
64
|
+
end
|
65
|
+
|
66
|
+
def names_for_provider
|
67
|
+
[ provider&.name ]
|
68
|
+
end
|
59
69
|
|
60
70
|
alias_method :roles, :parent_roles
|
71
|
+
|
72
|
+
|
73
|
+
def ssh_public_key_file
|
74
|
+
File.join(path, 'ssh.pub')
|
75
|
+
end
|
76
|
+
|
77
|
+
def ssh_private_key_file
|
78
|
+
File.join(path, 'ssh.key')
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate_ssh_keys!
|
82
|
+
esc = ->(s) { Shellwords.escape(s) }
|
83
|
+
tmp_public = ssh_private_key_file + '.pub'
|
84
|
+
|
85
|
+
# Wanted to use -o and -t ecdsa, but Net::SSH still has some
|
86
|
+
# issues with ECDSA, and only 4.0 beta supports -o style new keys
|
87
|
+
cmd = "ssh-keygen -N '' -t rsa -b 4096 -C #{esc.(name)} " +
|
88
|
+
"-f #{esc.(ssh_private_key_file)} && " +
|
89
|
+
"mv #{esc.(tmp_public)} #{esc.(ssh_public_key_file)}"
|
90
|
+
%x(#{cmd})
|
91
|
+
unless $?.success?
|
92
|
+
fail "Couldn't generate SSH key, command: #{cmd}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
61
96
|
end
|
62
97
|
end
|
data/lib/cult/project.rb
CHANGED
@@ -3,9 +3,6 @@ require 'shellwords'
|
|
3
3
|
require 'json'
|
4
4
|
require 'yaml'
|
5
5
|
|
6
|
-
require 'cult/config'
|
7
|
-
require 'cult/role'
|
8
|
-
require 'cult/provider'
|
9
6
|
|
10
7
|
module Cult
|
11
8
|
class Project
|
@@ -16,11 +13,6 @@ module Cult
|
|
16
13
|
|
17
14
|
def initialize(path)
|
18
15
|
@path = path
|
19
|
-
|
20
|
-
if Cult.immutable?
|
21
|
-
self.provider
|
22
|
-
self.freeze
|
23
|
-
end
|
24
16
|
end
|
25
17
|
|
26
18
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Cult
|
4
|
+
class ProjectContext
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :project, :methods, :respond_to?, :to_s, :inspect
|
7
|
+
|
8
|
+
attr_reader :project
|
9
|
+
|
10
|
+
def initialize(project, **extra)
|
11
|
+
@project = project
|
12
|
+
|
13
|
+
extra.each do |k, v|
|
14
|
+
define_singleton_method(k) { v }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(*args)
|
19
|
+
project.send(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/cult/provider.rb
CHANGED
data/lib/cult/role.rb
CHANGED
@@ -1,25 +1,24 @@
|
|
1
1
|
require 'tsort'
|
2
2
|
|
3
|
-
require 'cult/task'
|
4
|
-
require 'cult/artifact'
|
5
|
-
require 'cult/config'
|
6
|
-
require 'cult/definition'
|
7
|
-
require 'cult/named_array'
|
8
|
-
|
9
3
|
module Cult
|
10
4
|
class Role
|
5
|
+
include SingletonInstances
|
6
|
+
|
7
|
+
def self.delegate_to_definition(method_name, definition_key = nil)
|
8
|
+
definition_key ||= method_name
|
9
|
+
define_method(method_name) do
|
10
|
+
definition[definition_key.to_s]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
delegate_to_definition :user
|
15
|
+
|
11
16
|
attr_accessor :project
|
12
17
|
attr_accessor :path
|
13
18
|
|
14
19
|
def initialize(project, path)
|
15
20
|
@project = project
|
16
21
|
@path = path
|
17
|
-
|
18
|
-
if Cult.immutable?
|
19
|
-
definition
|
20
|
-
parent_roles
|
21
|
-
self.freeze
|
22
|
-
end
|
23
22
|
end
|
24
23
|
|
25
24
|
|
@@ -51,11 +50,7 @@ module Cult
|
|
51
50
|
|
52
51
|
|
53
52
|
def inspect
|
54
|
-
|
55
|
-
"\#<#{self.class.name} id:#{object_id.to_s(36)} #{name.inspect}>"
|
56
|
-
else
|
57
|
-
"\#<#{self.class.name} #{name.inspect}>"
|
58
|
-
end
|
53
|
+
"\#<#{self.class.name} id:#{object_id.to_s(36)} #{name.inspect}>"
|
59
54
|
end
|
60
55
|
alias_method :to_s, :inspect
|
61
56
|
|
@@ -76,6 +71,16 @@ module Cult
|
|
76
71
|
end
|
77
72
|
|
78
73
|
|
74
|
+
def build_tasks
|
75
|
+
tasks.select(&:build_task?)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def event_tasks
|
80
|
+
tasks.select(&:event_task?)
|
81
|
+
end
|
82
|
+
|
83
|
+
|
79
84
|
def artifacts
|
80
85
|
Artifact.all_for_role(project, self)
|
81
86
|
end
|
@@ -148,39 +153,6 @@ module Cult
|
|
148
153
|
end
|
149
154
|
|
150
155
|
|
151
|
-
if Cult.immutable?
|
152
|
-
def self.cache_get(cls, *args)
|
153
|
-
@singletons ||= {}
|
154
|
-
key = [cls, *args]
|
155
|
-
|
156
|
-
if (rval = @singletons[key])
|
157
|
-
return rval
|
158
|
-
end
|
159
|
-
|
160
|
-
return nil
|
161
|
-
end
|
162
|
-
|
163
|
-
|
164
|
-
def self.cache_put(obj, *args)
|
165
|
-
@singletons ||= {}
|
166
|
-
key = [obj.class, *args]
|
167
|
-
@singletons[key] = obj
|
168
|
-
obj
|
169
|
-
end
|
170
|
-
|
171
|
-
|
172
|
-
def self.new(*args)
|
173
|
-
if (result = cache_get(self, *args))
|
174
|
-
return result
|
175
|
-
else
|
176
|
-
result = super
|
177
|
-
cache_put(result, *args)
|
178
|
-
return result
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
|
184
156
|
def self.all(project)
|
185
157
|
all_files(project).map do |filename|
|
186
158
|
new(project, filename).tap do |new_role|
|
@@ -209,5 +181,13 @@ module Cult
|
|
209
181
|
! tree[role].nil?
|
210
182
|
end
|
211
183
|
|
184
|
+
def names_for_role(*a)
|
185
|
+
build_order.map(&:name)
|
186
|
+
end
|
187
|
+
|
188
|
+
def names_for_task
|
189
|
+
tasks.map(&:name)
|
190
|
+
end
|
191
|
+
|
212
192
|
end
|
213
193
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Cult
|
2
|
+
module SingletonInstances
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
private
|
7
|
+
def singletons
|
8
|
+
@singletons ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def cache_get(cls, *args)
|
13
|
+
singletons[[cls, *args]]
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def cache_put(obj, *args)
|
18
|
+
singletons[[obj.class, *args]] = obj
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
public
|
23
|
+
def new(*args)
|
24
|
+
return super unless Cult.singletons?
|
25
|
+
|
26
|
+
if (result = cache_get(self, *args))
|
27
|
+
return result
|
28
|
+
end
|
29
|
+
|
30
|
+
super.tap do |result|
|
31
|
+
cache_put(result, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.included(cls)
|
38
|
+
class << cls
|
39
|
+
prepend(ClassMethods)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/cult/skel.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'fileutils'
|
2
|
-
require 'cult/template'
|
3
2
|
|
4
3
|
module Cult
|
5
4
|
class Skel
|
@@ -51,7 +50,8 @@ module Cult
|
|
51
50
|
|
52
51
|
dst, data = case src
|
53
52
|
when /\.erb\z/
|
54
|
-
[ dst.sub(/\.erb\z/, ''),
|
53
|
+
[ dst.sub(/\.erb\z/, ''),
|
54
|
+
template.process(File.read(src), filename: src)]
|
55
55
|
else
|
56
56
|
[ dst, File.read(src) ]
|
57
57
|
end
|
data/lib/cult/task.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
require 'cult/transferable'
|
2
|
-
require 'cult/named_array'
|
3
|
-
|
4
1
|
module Cult
|
5
2
|
class Task
|
6
3
|
include Transferable
|
4
|
+
include SingletonInstances
|
7
5
|
|
8
6
|
attr_reader :path
|
9
7
|
attr_reader :role
|
10
8
|
attr_reader :serial
|
11
9
|
attr_reader :name
|
10
|
+
attr_reader :type
|
12
11
|
|
13
|
-
LEADING_ZEROS =
|
12
|
+
LEADING_ZEROS = 3
|
14
13
|
BASENAME_RE = /\A(\d{#{LEADING_ZEROS},})-([\w-]+)(\..+)?\z/i
|
14
|
+
EVENTS = [:sync]
|
15
15
|
|
16
16
|
|
17
17
|
def initialize(role, path)
|
@@ -19,11 +19,20 @@ module Cult
|
|
19
19
|
@path = path
|
20
20
|
@basename = File.basename(path)
|
21
21
|
|
22
|
+
unless self.class.valid_task_name?(@basename)
|
23
|
+
fail ArgumentError, "invalid task name: #{path}"
|
24
|
+
end
|
25
|
+
|
22
26
|
if (m = @basename.match(BASENAME_RE))
|
27
|
+
@type = :build
|
23
28
|
@serial = m[1].to_i
|
24
29
|
@name = m[2]
|
30
|
+
elsif EVENTS.map(&:to_s).include?(@basename)
|
31
|
+
@type = :event
|
32
|
+
@serial = nil
|
33
|
+
@name = @basename
|
25
34
|
else
|
26
|
-
fail
|
35
|
+
fail "WTF"
|
27
36
|
end
|
28
37
|
end
|
29
38
|
|
@@ -39,8 +48,18 @@ module Cult
|
|
39
48
|
end
|
40
49
|
|
41
50
|
|
51
|
+
def build_task?
|
52
|
+
type == :build
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def event_task?
|
57
|
+
type != :build
|
58
|
+
end
|
59
|
+
|
60
|
+
|
42
61
|
def inspect
|
43
|
-
"\#<#{self.class.name} role:#{role&.name.inspect} " +
|
62
|
+
"\#<#{self.class.name} type: #{type} role:#{role&.name.inspect} " +
|
44
63
|
"serial:#{serial} name:#{name.inspect}>"
|
45
64
|
end
|
46
65
|
alias_method :to_s, :inspect
|
@@ -50,15 +69,18 @@ module Cult
|
|
50
69
|
super | 0100
|
51
70
|
end
|
52
71
|
|
72
|
+
def self.valid_task_name?(basename)
|
73
|
+
EVENTS.map(&:to_s).include?(basename) || basename.match(BASENAME_RE)
|
74
|
+
end
|
75
|
+
|
53
76
|
|
54
77
|
def self.all_for_role(project, role)
|
55
78
|
Dir.glob(File.join(role.path, "tasks", "*")).map do |filename|
|
56
|
-
next unless File.basename(filename)
|
79
|
+
next unless valid_task_name?(File.basename(filename))
|
57
80
|
new(role, filename).tap do |new_task|
|
58
81
|
yield new_task if block_given?
|
59
82
|
end
|
60
83
|
end.compact.to_named_array
|
61
84
|
end
|
62
|
-
|
63
85
|
end
|
64
86
|
end
|
data/lib/cult/template.rb
CHANGED
@@ -1,91 +1,39 @@
|
|
1
1
|
require 'erb'
|
2
2
|
require 'json'
|
3
|
+
require 'cult/user_refinements'
|
3
4
|
|
4
5
|
module Cult
|
5
6
|
class Template
|
7
|
+
class Context < ProjectContext
|
8
|
+
using ::Cult::UserRefinements
|
6
9
|
|
7
|
-
|
8
|
-
module Refinements
|
9
|
-
module Util
|
10
|
-
module_function
|
11
|
-
def squote(s)
|
12
|
-
"'" + s.gsub("'", "\\\\\'") + "'"
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
def quote(s)
|
17
|
-
s.to_json
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
|
-
def slash(s)
|
22
|
-
Shellwords.escape(s)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
refine String do
|
27
|
-
def quote
|
28
|
-
Util.quote(self)
|
29
|
-
end
|
30
|
-
alias_method :q, :quote
|
31
|
-
|
32
|
-
|
33
|
-
def squote
|
34
|
-
Util.squote(self)
|
35
|
-
end
|
36
|
-
alias_method :sq, :squote
|
37
|
-
|
38
|
-
|
39
|
-
def slash
|
40
|
-
Util.slash(self)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
refine Array do
|
45
|
-
def quote(sep = ' ')
|
46
|
-
map {|v| Util.quote(v) }.join(sep)
|
47
|
-
end
|
48
|
-
alias_method :q, :quote
|
49
|
-
|
50
|
-
|
51
|
-
def squote(sep = ' ')
|
52
|
-
map {|v| Util.squote(v) }.join(sep)
|
53
|
-
end
|
54
|
-
alias_method :sq, :squote
|
55
|
-
|
56
|
-
|
57
|
-
def slash
|
58
|
-
map {|v| Util.slash(v) }.join(' ')
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
class Context
|
64
|
-
using Refinements
|
65
|
-
|
66
|
-
def initialize(pwd: nil, **kw)
|
10
|
+
def initialize(project, pwd: nil, **kw)
|
67
11
|
@pwd = pwd
|
68
|
-
kw
|
69
|
-
define_singleton_method(k) { v }
|
70
|
-
end
|
12
|
+
super(project, **kw)
|
71
13
|
end
|
72
14
|
|
73
|
-
|
74
|
-
def _process(template)
|
15
|
+
def _process(template, filename: nil)
|
75
16
|
Dir.chdir(@pwd || Dir.pwd) do
|
76
|
-
::ERB.new(template)
|
17
|
+
erb = ::ERB.new(template)
|
18
|
+
erb.filename = filename
|
19
|
+
erb.result(binding)
|
77
20
|
end
|
78
21
|
end
|
22
|
+
|
23
|
+
def binding
|
24
|
+
super
|
25
|
+
end
|
79
26
|
end
|
80
27
|
|
28
|
+
attr_reader :context
|
81
29
|
|
82
|
-
def initialize(pwd: nil, **kw)
|
83
|
-
@context = Context.new(pwd: pwd, **kw)
|
30
|
+
def initialize(project:, pwd: nil, **kw)
|
31
|
+
@context = Context.new(project, pwd: pwd, **kw)
|
84
32
|
end
|
85
33
|
|
86
34
|
|
87
|
-
def process(text)
|
88
|
-
|
35
|
+
def process(text, filename: nil)
|
36
|
+
context._process(text, filename: filename)
|
89
37
|
end
|
90
38
|
|
91
39
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Cult
|
2
|
+
module Transaction
|
3
|
+
class Log
|
4
|
+
attr_reader :steps
|
5
|
+
def initialize
|
6
|
+
@steps = []
|
7
|
+
yield self if block_given?
|
8
|
+
end
|
9
|
+
|
10
|
+
def unwind
|
11
|
+
begin
|
12
|
+
while (step = steps.pop)
|
13
|
+
step.call
|
14
|
+
end
|
15
|
+
rescue Exception => e
|
16
|
+
puts "Error raised while rolling back: #{e.inspect}\n#{e.backtrace}"
|
17
|
+
retry
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def protect(&block)
|
22
|
+
begin
|
23
|
+
yield
|
24
|
+
rescue Exception
|
25
|
+
$stderr.puts "Rolling back actions"
|
26
|
+
unwind
|
27
|
+
raise
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def rollback(&block)
|
32
|
+
steps.push(block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def transaction
|
37
|
+
Log.new do |list|
|
38
|
+
list.protect do
|
39
|
+
yield list
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/cult/transferable.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
|
-
require 'cult/template'
|
2
|
-
|
3
1
|
module Cult
|
4
2
|
module Transferable
|
5
|
-
|
6
3
|
module ClassMethods
|
7
4
|
def collection_name
|
8
5
|
name.split('::')[-1].downcase + 's'
|
@@ -39,8 +36,8 @@ module Cult
|
|
39
36
|
if binary?
|
40
37
|
File.read(path)
|
41
38
|
else
|
42
|
-
erb = Template.new(
|
43
|
-
erb.process
|
39
|
+
erb = Template.new(project: project, pwd: pwd, role: role, node: node)
|
40
|
+
erb.process(File.read(path), filename: path)
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|