xpflow 0.1b
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.
- data/bin/xpflow +96 -0
- data/lib/colorado.rb +198 -0
- data/lib/json/add/core.rb +243 -0
- data/lib/json/add/rails.rb +8 -0
- data/lib/json/common.rb +423 -0
- data/lib/json/editor.rb +1369 -0
- data/lib/json/ext.rb +28 -0
- data/lib/json/pure/generator.rb +442 -0
- data/lib/json/pure/parser.rb +320 -0
- data/lib/json/pure.rb +15 -0
- data/lib/json/version.rb +8 -0
- data/lib/json.rb +62 -0
- data/lib/mime/types.rb +881 -0
- data/lib/mime-types.rb +3 -0
- data/lib/restclient/abstract_response.rb +106 -0
- data/lib/restclient/exceptions.rb +193 -0
- data/lib/restclient/net_http_ext.rb +55 -0
- data/lib/restclient/payload.rb +235 -0
- data/lib/restclient/raw_response.rb +34 -0
- data/lib/restclient/request.rb +316 -0
- data/lib/restclient/resource.rb +169 -0
- data/lib/restclient/response.rb +24 -0
- data/lib/restclient.rb +174 -0
- data/lib/xpflow/bash.rb +341 -0
- data/lib/xpflow/bundle.rb +113 -0
- data/lib/xpflow/cmdline.rb +249 -0
- data/lib/xpflow/collection.rb +122 -0
- data/lib/xpflow/concurrency.rb +79 -0
- data/lib/xpflow/data.rb +393 -0
- data/lib/xpflow/dsl.rb +816 -0
- data/lib/xpflow/engine.rb +574 -0
- data/lib/xpflow/ensemble.rb +135 -0
- data/lib/xpflow/events.rb +56 -0
- data/lib/xpflow/experiment.rb +65 -0
- data/lib/xpflow/exts/facter.rb +30 -0
- data/lib/xpflow/exts/g5k.rb +931 -0
- data/lib/xpflow/exts/g5k_use.rb +50 -0
- data/lib/xpflow/exts/gui.rb +140 -0
- data/lib/xpflow/exts/model.rb +155 -0
- data/lib/xpflow/graph.rb +1603 -0
- data/lib/xpflow/graph_xpflow.rb +251 -0
- data/lib/xpflow/import.rb +196 -0
- data/lib/xpflow/library.rb +349 -0
- data/lib/xpflow/logging.rb +153 -0
- data/lib/xpflow/manager.rb +147 -0
- data/lib/xpflow/nodes.rb +1250 -0
- data/lib/xpflow/runs.rb +773 -0
- data/lib/xpflow/runtime.rb +125 -0
- data/lib/xpflow/scope.rb +168 -0
- data/lib/xpflow/ssh.rb +186 -0
- data/lib/xpflow/stat.rb +50 -0
- data/lib/xpflow/stdlib.rb +381 -0
- data/lib/xpflow/structs.rb +369 -0
- data/lib/xpflow/taktuk.rb +193 -0
- data/lib/xpflow/templates/ssh-config.basic +14 -0
- data/lib/xpflow/templates/ssh-config.inria +18 -0
- data/lib/xpflow/templates/ssh-config.proxy +13 -0
- data/lib/xpflow/templates/taktuk +6590 -0
- data/lib/xpflow/templates/utils/batch +4 -0
- data/lib/xpflow/templates/utils/bootstrap +12 -0
- data/lib/xpflow/templates/utils/hostname +3 -0
- data/lib/xpflow/templates/utils/ping +3 -0
- data/lib/xpflow/templates/utils/rsync +12 -0
- data/lib/xpflow/templates/utils/scp +17 -0
- data/lib/xpflow/templates/utils/scp_many +8 -0
- data/lib/xpflow/templates/utils/ssh +3 -0
- data/lib/xpflow/templates/utils/ssh-interactive +4 -0
- data/lib/xpflow/templates/utils/taktuk +19 -0
- data/lib/xpflow/threads.rb +187 -0
- data/lib/xpflow/utils.rb +569 -0
- data/lib/xpflow/visual.rb +230 -0
- data/lib/xpflow/with_g5k.rb +7 -0
- data/lib/xpflow.rb +349 -0
- metadata +135 -0
@@ -0,0 +1,349 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
#
|
4
|
+
# Implementation of libraries, i.e., sets of activities
|
5
|
+
# that can be imported or injected into a namespace.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'monitor'
|
9
|
+
|
10
|
+
module XPFlow
|
11
|
+
|
12
|
+
class ResolutionError < StandardError
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class Library
|
17
|
+
# implements .activity and .process methods
|
18
|
+
# + some kind of activity management
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@names = { }
|
22
|
+
@lock = Mutex.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def synchronize
|
26
|
+
@lock.synchronize do
|
27
|
+
yield
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](key)
|
32
|
+
return @names[key]
|
33
|
+
end
|
34
|
+
|
35
|
+
def []=(key, value)
|
36
|
+
key = stringize(key)
|
37
|
+
raise "'#{key}' already exists here" if @names.key?(key)
|
38
|
+
raise "'#{key}' contains a dot" if key.include?(".")
|
39
|
+
@names[key] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_force(key, value)
|
43
|
+
@names[key] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
def into_parts(name)
|
47
|
+
array = name.split(".")
|
48
|
+
return [ array[0...-1], array.last ]
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_name(name)
|
52
|
+
name = stringize(name)
|
53
|
+
return resolve_parts(*into_parts(name))
|
54
|
+
end
|
55
|
+
|
56
|
+
def resolve_libs(libs)
|
57
|
+
libs = libs + [] # copy
|
58
|
+
path = libs.join(".")
|
59
|
+
current = self
|
60
|
+
while libs.length > 0
|
61
|
+
name = libs.shift
|
62
|
+
current = current[name]
|
63
|
+
if !current.is_a?(Library)
|
64
|
+
raise ResolutionError.new("No such library '#{path}'")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
return current
|
68
|
+
end
|
69
|
+
|
70
|
+
def resolve_parts(libs, name)
|
71
|
+
library = resolve_libs(libs)
|
72
|
+
object = library[name]
|
73
|
+
if object.nil?
|
74
|
+
path = libs.join(".")
|
75
|
+
raise ResolutionError.new("No such object '#{name}' in '#{path}'")
|
76
|
+
end
|
77
|
+
return object
|
78
|
+
end
|
79
|
+
|
80
|
+
def resolve_activity(libs, name)
|
81
|
+
activity = resolve_parts(libs, name)
|
82
|
+
if activity.is_a?(Library)
|
83
|
+
path = (libs + [ name ]).join(".")
|
84
|
+
raise ResolutionError.new("'#{path}' is not an activity")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_libraries(libs)
|
89
|
+
# creates all intermediate libraries if they don't exist
|
90
|
+
current = self
|
91
|
+
nesting = []
|
92
|
+
while libs.length > 0
|
93
|
+
name = libs.shift
|
94
|
+
nesting.push(name)
|
95
|
+
object = current[name]
|
96
|
+
if object.nil?
|
97
|
+
current[name] = BasicLibrary.new
|
98
|
+
elsif !object.is_a?(Library)
|
99
|
+
activity = nesting.join(".")
|
100
|
+
raise ResolutionError.new("Overwriting an activity '#{activity}'")
|
101
|
+
end
|
102
|
+
current = current[name]
|
103
|
+
end
|
104
|
+
return current
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_library_or_nil(name)
|
108
|
+
object = @names[name]
|
109
|
+
return object
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_activity_or_error(name)
|
113
|
+
object = @names[name]
|
114
|
+
if object.is_a?(Library)
|
115
|
+
raise ResolutionError.new("No such activity '#{name}'")
|
116
|
+
end
|
117
|
+
return object
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_activity_or_nil(name)
|
121
|
+
previous = nil
|
122
|
+
begin
|
123
|
+
previous = get_activity_or_error(name)
|
124
|
+
rescue ResolutionError => e
|
125
|
+
previous = nil # there is no previous activity
|
126
|
+
end
|
127
|
+
return previous
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def activity_alias(full_name, old_name)
|
132
|
+
# creates an alias between full_name and another_name
|
133
|
+
# please don't make it cyclic! :D
|
134
|
+
object = get_object(old_name)
|
135
|
+
raise "Object '#{old_name}' does not exist" if object.nil?
|
136
|
+
libs, name = into_parts(full_name)
|
137
|
+
library = resolve_libs(libs)
|
138
|
+
library[name] = object
|
139
|
+
end
|
140
|
+
|
141
|
+
def traversal_graph
|
142
|
+
g = {}
|
143
|
+
@names.each do |k, v|
|
144
|
+
r = case
|
145
|
+
when v.is_a?(Library) then v.traversal_graph
|
146
|
+
when v.is_a?(XPFlow::BlockActivity) then "block"
|
147
|
+
when v.is_a?(XPFlow::ProcessActivity) then "process"
|
148
|
+
else
|
149
|
+
raise "Error: #{k} #{v} (#{v.class})"
|
150
|
+
end
|
151
|
+
g[k] = r
|
152
|
+
end
|
153
|
+
return g
|
154
|
+
end
|
155
|
+
|
156
|
+
def stringize(o)
|
157
|
+
raise "Wrong name type: #{o.class}" if !(o.is_a?(String) || o.is_a?(Symbol))
|
158
|
+
return o.to_s
|
159
|
+
end
|
160
|
+
|
161
|
+
# ===PUBLIC API=== #
|
162
|
+
|
163
|
+
def activity(full_name, opts = {}, &block)
|
164
|
+
name, library = _create_entry(full_name)
|
165
|
+
|
166
|
+
opts[:__parent__] = library.get_activity_or_nil(name)
|
167
|
+
|
168
|
+
block_activity = XPFlow::BlockActivity.new(name, opts, block)
|
169
|
+
block_activity.doc = opts[:doc]
|
170
|
+
|
171
|
+
library.set_force(name, block_activity)
|
172
|
+
|
173
|
+
return block_activity
|
174
|
+
end
|
175
|
+
|
176
|
+
def constant(full_name, value)
|
177
|
+
activity full_name do
|
178
|
+
value
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def _create_entry(full_name)
|
183
|
+
full_name = stringize(full_name)
|
184
|
+
libs, name = into_parts(full_name)
|
185
|
+
library = create_libraries(libs)
|
186
|
+
return [ name, library ]
|
187
|
+
end
|
188
|
+
|
189
|
+
def process(full_name, opts = {}, &block)
|
190
|
+
name, library = _create_entry(full_name)
|
191
|
+
|
192
|
+
parent = opts[:__parent__] = library.get_activity_or_nil(name)
|
193
|
+
|
194
|
+
process_activity = XPFlow::ProcessDSL.new(name, self, &block).as_process
|
195
|
+
process_activity.doc = parent.doc unless parent.nil?
|
196
|
+
process_activity.doc = opts[:doc] if opts[:doc]
|
197
|
+
process_activity.opts = opts
|
198
|
+
process_activity.collect_meta(2)
|
199
|
+
|
200
|
+
library.set_force(name, process_activity)
|
201
|
+
|
202
|
+
return process_activity
|
203
|
+
end
|
204
|
+
|
205
|
+
def macro(full_name, opts = {}, &block)
|
206
|
+
name, library = _create_entry(full_name)
|
207
|
+
|
208
|
+
m = XPFlow::MacroDSL.new(name, self, block)
|
209
|
+
library.set_force(name, m)
|
210
|
+
return m
|
211
|
+
end
|
212
|
+
|
213
|
+
def get_object(name)
|
214
|
+
begin
|
215
|
+
return resolve_name(name)
|
216
|
+
rescue ResolutionError => e
|
217
|
+
return nil
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def get_activity_object(name)
|
222
|
+
object = get_object(name)
|
223
|
+
if object.is_a?(Library)
|
224
|
+
raise "'#{name}' is a library"
|
225
|
+
end
|
226
|
+
return object
|
227
|
+
end
|
228
|
+
|
229
|
+
def get_names
|
230
|
+
return @names.keys
|
231
|
+
end
|
232
|
+
|
233
|
+
def get_libraries
|
234
|
+
h = @names.select { |k, v| v.is_a?(Library) }
|
235
|
+
return h
|
236
|
+
end
|
237
|
+
|
238
|
+
def import_library(name, library)
|
239
|
+
self[name] = library
|
240
|
+
end
|
241
|
+
|
242
|
+
def inject_library(name, library)
|
243
|
+
# this will inject all activities from 'library' into this library
|
244
|
+
# this may overwrite some of them
|
245
|
+
import_library(name, library)
|
246
|
+
library.get_names.each do |object|
|
247
|
+
activity_alias(object, "#{name}.#{object}")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def setup
|
252
|
+
# empty
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
if __FILE__ == $0
|
258
|
+
require 'pp'
|
259
|
+
require 'xpflow'
|
260
|
+
l = Library.new
|
261
|
+
l.constant :a1, 1
|
262
|
+
l.process :"czesc.siema" do; end
|
263
|
+
g = Library.new
|
264
|
+
g.activity :cze
|
265
|
+
l.activity_alias("alejazda", "czesc")
|
266
|
+
pp l.traversal_graph
|
267
|
+
end
|
268
|
+
|
269
|
+
# a library that does some nice tricks
|
270
|
+
|
271
|
+
class ActivityLibrary < Library
|
272
|
+
|
273
|
+
def initialize
|
274
|
+
super
|
275
|
+
setup()
|
276
|
+
this = self
|
277
|
+
__activities__.each_pair do |name, realname|
|
278
|
+
activity(name, get_option(name)) do |*args|
|
279
|
+
this.invoke(realname, args, self, &self.__block__)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def invoke(method, args, proxy = nil, &block)
|
285
|
+
Thread.current[:__proxy__] = proxy
|
286
|
+
return self.send(method, *args, &block)
|
287
|
+
end
|
288
|
+
|
289
|
+
def proxy
|
290
|
+
return Thread.current[:__proxy__]
|
291
|
+
end
|
292
|
+
|
293
|
+
def get_option(name)
|
294
|
+
return { }
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
class HiddenActivityLibrary < ActivityLibrary
|
300
|
+
|
301
|
+
def get_option(name)
|
302
|
+
return { :log_level => :none }
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
class SyncedActivityLibrary < ActivityLibrary
|
308
|
+
|
309
|
+
def invoke(method, args, proxy = nil, &block)
|
310
|
+
synchronize do
|
311
|
+
super
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
class MonitoredActivityLibrary < ActivityLibrary
|
318
|
+
|
319
|
+
# with reentrant lock
|
320
|
+
def initialize
|
321
|
+
super
|
322
|
+
@lock = Monitor.new
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
IGNORED_LIBRARY_VARIABLES = Library.new.instance_variables
|
328
|
+
|
329
|
+
module SerializableLibrary
|
330
|
+
|
331
|
+
def checkpoint
|
332
|
+
vars = self.instance_variables - IGNORED_LIBRARY_VARIABLES
|
333
|
+
state = {}
|
334
|
+
vars.each do |v|
|
335
|
+
value = self.instance_variable_get(v)
|
336
|
+
state[v] = value
|
337
|
+
end
|
338
|
+
return state
|
339
|
+
end
|
340
|
+
|
341
|
+
def restore(state)
|
342
|
+
state.each_pair do |k, v|
|
343
|
+
self.instance_variable_set(k, v)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
end # Serializable
|
348
|
+
|
349
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module XPFlow
|
5
|
+
|
6
|
+
class AbstractLog
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mutex = Mutex.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def open(*args)
|
13
|
+
raise "Not implemented!"
|
14
|
+
end
|
15
|
+
|
16
|
+
def log(*args)
|
17
|
+
raise "Not implemented!"
|
18
|
+
end
|
19
|
+
|
20
|
+
def close(*args)
|
21
|
+
raise "Not implemented!"
|
22
|
+
end
|
23
|
+
|
24
|
+
def synchronize(&block)
|
25
|
+
return @mutex.synchronize(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class ConsoleLog < AbstractLog
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def log(*msgs)
|
37
|
+
synchronize do
|
38
|
+
msgs.each do |x|
|
39
|
+
x = x.plain unless STDOUT.tty?
|
40
|
+
puts x
|
41
|
+
end
|
42
|
+
STDOUT.flush
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def open; end
|
47
|
+
def close; end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
class FileLog < AbstractLog
|
52
|
+
|
53
|
+
def initialize(fname)
|
54
|
+
super()
|
55
|
+
@fname = fname
|
56
|
+
@f = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def open
|
60
|
+
@f = File.open(@fname, "w")
|
61
|
+
return self
|
62
|
+
end
|
63
|
+
|
64
|
+
def log(*msgs)
|
65
|
+
synchronize do
|
66
|
+
msgs.each do |x|
|
67
|
+
@f.write("#{x.plain}\n")
|
68
|
+
end
|
69
|
+
@f.flush
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def close
|
74
|
+
@f.close
|
75
|
+
@f = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
class Logging
|
81
|
+
|
82
|
+
def initialize
|
83
|
+
@loggers = {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def add(logger, label)
|
87
|
+
@loggers[label] = logger
|
88
|
+
end
|
89
|
+
|
90
|
+
def prefix
|
91
|
+
t = Time.now
|
92
|
+
s = t.strftime('%Y-%m-%d %H:%M:%S.%L')
|
93
|
+
ms = t.usec / 1000
|
94
|
+
return s.gsub('%L', "%03d" % ms) # Ruby 1.8 compat
|
95
|
+
end
|
96
|
+
|
97
|
+
def colorize(s, label)
|
98
|
+
colors = {
|
99
|
+
:normal => :green,
|
100
|
+
:verbose => :yellow,
|
101
|
+
:paranoic => :red,
|
102
|
+
:default => :red!
|
103
|
+
}
|
104
|
+
c = colors[:default]
|
105
|
+
c = colors[label] if colors.key?(label)
|
106
|
+
return s.send(c)
|
107
|
+
end
|
108
|
+
|
109
|
+
def log(msg, label = :normal)
|
110
|
+
pre = colorize(prefix, label)
|
111
|
+
msg = "[ %s ] %s" % [ pre, msg ]
|
112
|
+
@loggers.each_pair do |k, v|
|
113
|
+
v.log(msg)
|
114
|
+
end
|
115
|
+
# TODO: kind of ugly
|
116
|
+
Scope.current[:__experiment__].log(msg) # log to the experiment
|
117
|
+
end
|
118
|
+
|
119
|
+
def get(label)
|
120
|
+
return @loggers[label]
|
121
|
+
end
|
122
|
+
|
123
|
+
def using(&block)
|
124
|
+
ls = @loggers.values
|
125
|
+
opened = []
|
126
|
+
x = nil?
|
127
|
+
begin
|
128
|
+
ls.each { |x| x.open(); opened.push(x) }
|
129
|
+
x = block.call
|
130
|
+
ensure
|
131
|
+
opened.each { |x| x.close } # TODO
|
132
|
+
end
|
133
|
+
return x
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
$console = ConsoleLog.new # globally accessible, should be used by everybody
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
if __FILE__ == $0
|
143
|
+
require 'colorado'
|
144
|
+
s = "cze".green + "yo".red
|
145
|
+
x = XPFlow::Logging.new
|
146
|
+
x.add(XPFlow::ConsoleLog.new, :console)
|
147
|
+
x.add(XPFlow::FileLog.new("test.log"), :file)
|
148
|
+
|
149
|
+
x.using do
|
150
|
+
x.log(s)
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
# this is a temporary directory that contains
|
3
|
+
# files generated on-the-fly to simplify things
|
4
|
+
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'thread'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'monitor'
|
9
|
+
require 'erb'
|
10
|
+
require 'ostruct'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
module XPFlow
|
14
|
+
|
15
|
+
class ExecutionError < StandardError
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class LocalExecutionResult
|
20
|
+
|
21
|
+
def initialize(cmd, stdout, stderr)
|
22
|
+
@cmd = cmd
|
23
|
+
@stdout = stdout
|
24
|
+
@stderr = stderr
|
25
|
+
end
|
26
|
+
|
27
|
+
def stdout
|
28
|
+
return IO.read(@stdout)
|
29
|
+
end
|
30
|
+
|
31
|
+
def stderr
|
32
|
+
return IO.read(@stderr)
|
33
|
+
end
|
34
|
+
|
35
|
+
def stdout_file
|
36
|
+
return @stdout
|
37
|
+
end
|
38
|
+
|
39
|
+
def stderr_file
|
40
|
+
return @stderr
|
41
|
+
end
|
42
|
+
|
43
|
+
def command
|
44
|
+
return @cmd
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
return "LocalExecutionResult(#{@cmd}, out => #{@stdout}, err => #{@stderr})"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
class DirectoryManager
|
54
|
+
|
55
|
+
attr_reader :path
|
56
|
+
|
57
|
+
def initialize(path)
|
58
|
+
@mutex = Monitor.new
|
59
|
+
if path.nil?
|
60
|
+
@path = Dir.mktmpdir() # TODO: remove while exiting
|
61
|
+
# use remove_entry_secure
|
62
|
+
else
|
63
|
+
if File.directory?(path)
|
64
|
+
FileUtils.remove_entry_secure(path)
|
65
|
+
end
|
66
|
+
Dir.mkdir(path)
|
67
|
+
@path = path
|
68
|
+
end
|
69
|
+
@counter = 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def synchronize(&block)
|
73
|
+
return @mutex.synchronize(&block)
|
74
|
+
end
|
75
|
+
|
76
|
+
def mktemp(&block)
|
77
|
+
fname = synchronize do
|
78
|
+
@counter += 1
|
79
|
+
File.join(@path, "tmpfile-#{@counter}")
|
80
|
+
end
|
81
|
+
if block_given?
|
82
|
+
File.open(fname, "wb", &block)
|
83
|
+
end
|
84
|
+
return fname
|
85
|
+
end
|
86
|
+
|
87
|
+
def join(name)
|
88
|
+
return File.join(@path, name)
|
89
|
+
end
|
90
|
+
|
91
|
+
def open(fname, flags, &block)
|
92
|
+
path = File.join(@path, fname)
|
93
|
+
return synchronize { File.open(path, flags, &block) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def run_with_files(name, stdout, stderr, opts = {})
|
97
|
+
# assumes that name is properly escaped!!!
|
98
|
+
name = join(name)
|
99
|
+
out = `#{name} 2> #{stderr} > #{stdout}`
|
100
|
+
raise ExecutionError.new("Command #{name} returned error (see #{stdout} and #{stderr})!") if $?.exitstatus != 0
|
101
|
+
return LocalExecutionResult.new(name, stdout, stderr)
|
102
|
+
end
|
103
|
+
|
104
|
+
def run(name, opts = {})
|
105
|
+
return run_with_files(name, self.mktemp(), self.mktemp(), opts)
|
106
|
+
end
|
107
|
+
|
108
|
+
def run_ssh(cmd, opts = {})
|
109
|
+
# runs a command remotely
|
110
|
+
# the form is ".../ssh 'ENV cmd args' "
|
111
|
+
# TODO: Escaping yo!
|
112
|
+
|
113
|
+
stdout = (opts[:out].nil?) ? self.mktemp() : opts[:out]
|
114
|
+
stderr = (opts[:err].nil?) ? self.mktemp() : opts[:err]
|
115
|
+
stdin = opts.fetch(:in, "/dev/null")
|
116
|
+
|
117
|
+
if opts[:env] and opts[:env] != {}
|
118
|
+
prefix = opts[:env].each_pair.map { |k, v| "#{k}=#{v}" }.join(" ")
|
119
|
+
cmd = "#{prefix} #{cmd}"
|
120
|
+
end
|
121
|
+
|
122
|
+
if opts[:wd]
|
123
|
+
cmd = "cd #{opts[:wd]}; #{cmd}"
|
124
|
+
end
|
125
|
+
|
126
|
+
cmd = Shellwords.escape(cmd)
|
127
|
+
cmd = "ssh #{cmd}"
|
128
|
+
real_cmd = join(cmd)
|
129
|
+
out = `#{real_cmd} < #{stdin} 2> #{stderr} 1> #{stdout}`
|
130
|
+
|
131
|
+
raise ExecutionError.new("Command #{real_cmd} failed (see #{stdout} and #{stderr})") if $?.exitstatus != 0
|
132
|
+
return LocalExecutionResult.new(cmd, stdout, stderr)
|
133
|
+
end
|
134
|
+
|
135
|
+
def _subdir(name)
|
136
|
+
path = join(name)
|
137
|
+
Dir.mkdir(path) # TODO: what if exists?
|
138
|
+
return DirectoryManager.new(path)
|
139
|
+
end
|
140
|
+
|
141
|
+
def subdir(name)
|
142
|
+
synchronize { _subdir(name) }
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end # module
|