scout-gear 8.0.0 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +26 -9
  3. data/Rakefile +6 -1
  4. data/VERSION +1 -1
  5. data/bin/scout +15 -4
  6. data/doc/lib/scout/path.md +35 -0
  7. data/doc/lib/scout/workflow/task.md +13 -0
  8. data/lib/scout/cmd.rb +23 -24
  9. data/lib/scout/concurrent_stream.rb +36 -19
  10. data/lib/scout/exceptions.rb +10 -0
  11. data/lib/scout/log/color.rb +11 -11
  12. data/lib/scout/log/progress/report.rb +7 -5
  13. data/lib/scout/log/progress/util.rb +3 -0
  14. data/lib/scout/log/trap.rb +3 -3
  15. data/lib/scout/log.rb +64 -36
  16. data/lib/scout/meta_extension.rb +34 -0
  17. data/lib/scout/misc/digest.rb +11 -2
  18. data/lib/scout/misc/format.rb +12 -7
  19. data/lib/scout/misc/monitor.rb +11 -0
  20. data/lib/scout/misc/system.rb +48 -0
  21. data/lib/scout/named_array.rb +8 -0
  22. data/lib/scout/offsite/ssh.rb +171 -0
  23. data/lib/scout/offsite/step.rb +83 -0
  24. data/lib/scout/offsite/sync.rb +55 -0
  25. data/lib/scout/offsite.rb +3 -0
  26. data/lib/scout/open/lock.rb +5 -24
  27. data/lib/scout/open/remote.rb +12 -1
  28. data/lib/scout/open/stream.rb +110 -122
  29. data/lib/scout/open/util.rb +9 -0
  30. data/lib/scout/open.rb +5 -4
  31. data/lib/scout/path/find.rb +15 -10
  32. data/lib/scout/path/util.rb +5 -0
  33. data/lib/scout/persist/serialize.rb +3 -3
  34. data/lib/scout/persist.rb +1 -1
  35. data/lib/scout/resource/path.rb +4 -0
  36. data/lib/scout/resource/util.rb +10 -4
  37. data/lib/scout/tsv/dumper.rb +2 -0
  38. data/lib/scout/tsv/index.rb +28 -86
  39. data/lib/scout/tsv/open.rb +35 -14
  40. data/lib/scout/tsv/parser.rb +9 -2
  41. data/lib/scout/tsv/persist/tokyocabinet.rb +2 -0
  42. data/lib/scout/tsv/stream.rb +204 -0
  43. data/lib/scout/tsv/transformer.rb +11 -0
  44. data/lib/scout/tsv.rb +9 -2
  45. data/lib/scout/work_queue/worker.rb +2 -2
  46. data/lib/scout/work_queue.rb +36 -12
  47. data/lib/scout/workflow/definition.rb +2 -1
  48. data/lib/scout/workflow/deployment/orchestrator.rb +245 -0
  49. data/lib/scout/workflow/deployment.rb +1 -0
  50. data/lib/scout/workflow/step/dependencies.rb +37 -11
  51. data/lib/scout/workflow/step/file.rb +5 -0
  52. data/lib/scout/workflow/step/info.rb +5 -3
  53. data/lib/scout/workflow/step/load.rb +1 -1
  54. data/lib/scout/workflow/step/provenance.rb +1 -0
  55. data/lib/scout/workflow/step/status.rb +6 -8
  56. data/lib/scout/workflow/step.rb +75 -30
  57. data/lib/scout/workflow/task/dependencies.rb +114 -0
  58. data/lib/scout/workflow/task/inputs.rb +27 -13
  59. data/lib/scout/workflow/task.rb +9 -108
  60. data/lib/scout/workflow/usage.rb +40 -12
  61. data/lib/scout/workflow.rb +4 -2
  62. data/lib/scout-gear.rb +2 -0
  63. data/lib/scout.rb +6 -0
  64. data/scout-gear.gemspec +32 -7
  65. data/scout_commands/doc +37 -0
  66. data/scout_commands/find +1 -0
  67. data/scout_commands/offsite +30 -0
  68. data/scout_commands/update +29 -0
  69. data/scout_commands/workflow/info +15 -3
  70. data/scout_commands/workflow/install +102 -0
  71. data/scout_commands/workflow/task +26 -5
  72. data/test/scout/offsite/test_ssh.rb +15 -0
  73. data/test/scout/offsite/test_step.rb +33 -0
  74. data/test/scout/offsite/test_sync.rb +36 -0
  75. data/test/scout/offsite/test_task.rb +0 -0
  76. data/test/scout/resource/test_path.rb +6 -0
  77. data/test/scout/test_named_array.rb +6 -0
  78. data/test/scout/test_persist.rb +3 -2
  79. data/test/scout/test_tsv.rb +17 -0
  80. data/test/scout/test_work_queue.rb +63 -41
  81. data/test/scout/tsv/persist/test_adapter.rb +1 -1
  82. data/test/scout/tsv/test_index.rb +14 -0
  83. data/test/scout/tsv/test_parser.rb +14 -0
  84. data/test/scout/tsv/test_stream.rb +200 -0
  85. data/test/scout/tsv/test_transformer.rb +12 -0
  86. data/test/scout/workflow/deployment/test_orchestrator.rb +272 -0
  87. data/test/scout/workflow/step/test_dependencies.rb +68 -0
  88. data/test/scout/workflow/step/test_info.rb +18 -0
  89. data/test/scout/workflow/step/test_status.rb +0 -1
  90. data/test/scout/workflow/task/test_dependencies.rb +355 -0
  91. data/test/scout/workflow/task/test_inputs.rb +53 -0
  92. data/test/scout/workflow/test_definition.rb +18 -0
  93. data/test/scout/workflow/test_documentation.rb +24 -0
  94. data/test/scout/workflow/test_step.rb +109 -0
  95. data/test/scout/workflow/test_task.rb +0 -287
  96. data/test/test_scout.rb +9 -0
  97. metadata +83 -5
  98. data/scout_commands/workflow/task_old +0 -706
data/lib/scout/log.rb CHANGED
@@ -12,9 +12,9 @@ module Log
12
12
  end
13
13
 
14
14
  SEVERITY_NAMES ||= begin
15
- names = %w(DEBUG LOW MEDIUM HIGH INFO WARN ERROR NONE )
15
+ names = %w(DEBUG LOW MEDIUM HIGH INFO WARN ERROR NONE )
16
16
  names.each_with_index do |name,i|
17
- eval "#{ name } = #{ i }"
17
+ eval "#{ name } = #{ i }"
18
18
  end
19
19
  names
20
20
  end
@@ -32,14 +32,14 @@ module Log
32
32
  @@default_severity
33
33
  end
34
34
 
35
- case ENV['SCOUT_LOG']
36
- when 'DEBUG'
35
+ case ENV['SCOUT_LOG']
36
+ when 'DEBUG'
37
37
  self.severity = DEBUG
38
- when 'LOW'
38
+ when 'LOW'
39
39
  self.severity = LOW
40
- when 'MEDIUM'
40
+ when 'MEDIUM'
41
41
  self.severity = MEDIUM
42
- when 'HIGH'
42
+ when 'HIGH'
43
43
  self.severity = HIGH
44
44
  when nil
45
45
  self.severity = default_severity
@@ -50,23 +50,27 @@ module Log
50
50
 
51
51
 
52
52
  def self.tty_size
53
- @tty_size = begin
54
- IO.console.winsize.last
55
- rescue
56
- begin
57
- `tput li`
58
- rescue
59
- 80
60
- end
61
- end
53
+ @@tty_size ||= Log.ignore_stderr do
54
+ begin
55
+ IO.console.winsize.last
56
+ rescue Exception
57
+ begin
58
+ res = `tput li`
59
+ res = nil if res == ""
60
+ res || ENV["TTY_SIZE"] || 80
61
+ rescue Exception
62
+ ENV["TTY_SIZE"] || 80
63
+ end
64
+ end
65
+ end
62
66
  end
63
67
 
64
68
 
65
69
  def self.last_caller(stack)
66
70
  line = nil
67
71
  pos ||= 0
68
- while line.nil? or line =~ /scout\/log\.rb/ and stack.any?
69
- line = stack.shift
72
+ while line.nil? or line =~ /scout\/log\.rb/ and stack.any?
73
+ line = stack.shift
70
74
  end
71
75
  line ||= caller.first
72
76
  line.gsub('`', "'")
@@ -129,9 +133,36 @@ module Log
129
133
  out.puts Log.return_line << " " * (Log.tty_size || 80) << Log.return_line unless nocolor
130
134
  end
131
135
 
136
+ MUTEX = Mutex.new
137
+ def self.log_write(str)
138
+ MUTEX.synchronize do
139
+ if logfile.nil?
140
+ begin
141
+ STDERR.write str
142
+ rescue
143
+ end
144
+ else
145
+ logfile.write str
146
+ end
147
+ end
148
+ end
149
+
150
+ def self.log_puts(str)
151
+ MUTEX.synchronize do
152
+ if logfile.nil?
153
+ begin
154
+ STDERR.puts str
155
+ rescue
156
+ end
157
+ else
158
+ logfile.puts str
159
+ end
160
+ end
161
+ end
162
+
132
163
  LAST = "log"
133
164
  def self.logn(message = nil, severity = MEDIUM, &block)
134
- return if severity < self.severity
165
+ return if severity < self.severity
135
166
  message ||= block.call if block_given?
136
167
  return if message.nil?
137
168
 
@@ -147,17 +178,14 @@ module Log
147
178
  message = "" << highlight << message << color(0) if severity >= INFO
148
179
  str = prefix << " " << message.to_s
149
180
 
150
- if logfile.nil?
151
- STDERR.write str
152
- else
153
- logfile.write str
154
- end
181
+ log_write str
182
+
155
183
  Log::LAST.replace "log"
156
184
  nil
157
185
  end
158
186
 
159
187
  def self.log(message = nil, severity = MEDIUM, &block)
160
- return if severity < self.severity
188
+ return if severity < self.severity
161
189
  message ||= block.call if block_given?
162
190
  return if message.nil?
163
191
  message = message + "\n" unless message[-1] == "\n"
@@ -250,9 +278,9 @@ module Log
250
278
  end
251
279
 
252
280
  def self.tsv(tsv, example = false)
253
- STDERR.puts Log.color :magenta, "TSV log: " << Log.last_caller(caller).gsub('`',"'")
254
- STDERR.puts Log.color(:blue, "=> "<< Log.fingerprint(tsv), true)
255
- STDERR.puts Log.color(:cyan, "=> " << tsv.summary)
281
+ log_puts Log.color :magenta, "TSV log: " << Log.last_caller(caller).gsub('`',"'")
282
+ log_puts Log.color(:blue, "=> "<< Log.fingerprint(tsv), true)
283
+ log_puts Log.color(:cyan, "=> " << tsv.summary)
256
284
  if example && ! tsv.empty?
257
285
  key = case example
258
286
  when TrueClass, :first, "first"
@@ -266,11 +294,11 @@ module Log
266
294
  values = tsv[key]
267
295
  values = [values] if tsv.type == :flat || tsv.type == :single
268
296
  if values.nil?
269
- STDERR.puts Log.color(:blue, "Key (#{tsv.key_field}) not present: ") + key
297
+ log_puts Log.color(:blue, "Key (#{tsv.key_field}) not present: ") + key
270
298
  else
271
- STDERR.puts Log.color(:blue, "Key (#{tsv.key_field}): ") + key
299
+ log_puts Log.color(:blue, "Key (#{tsv.key_field}): ") + key
272
300
  tsv.fields.zip(values).each do |field,value|
273
- STDERR.puts Log.color(:magenta, field + ": ") + (Array === value ? value * ", " : value.to_s)
301
+ log_puts Log.color(:magenta, field + ": ") + (Array === value ? value * ", " : value.to_s)
274
302
  end
275
303
  end
276
304
  end
@@ -278,14 +306,14 @@ module Log
278
306
 
279
307
  def self.stack(stack)
280
308
  if ENV["SCOUT_ORIGINAL_STACK"] == 'true'
281
- STDERR.puts Log.color :magenta, "Stack trace [#{Process.pid}]: " << Log.last_caller(caller)
309
+ log_puts Log.color :magenta, "Stack trace [#{Process.pid}]: " << Log.last_caller(caller)
282
310
  color_stack(stack).each do |line|
283
- STDERR.puts line
311
+ log_puts line
284
312
  end
285
313
  else
286
- STDERR.puts Log.color :magenta, "Stack trace [#{Process.pid}]: " << Log.last_caller(caller)
314
+ log_puts Log.color :magenta, "Stack trace [#{Process.pid}]: " << Log.last_caller(caller)
287
315
  color_stack(stack.reverse).each do |line|
288
- STDERR.puts line
316
+ log_puts line
289
317
  end
290
318
  end
291
319
  end
@@ -293,7 +321,7 @@ module Log
293
321
  def self.count_stack
294
322
  if ! $count_stacks
295
323
  Log.debug "Counting stacks at: " << caller.first
296
- return
324
+ return
297
325
  end
298
326
  $stack_counts ||= {}
299
327
  head = $count_stacks_head
@@ -62,5 +62,39 @@ module MetaExtension
62
62
  end
63
63
  base.setup(other, *attr_values)
64
64
  end
65
+
66
+ base.define_method(:purge) do
67
+ new = self.dup
68
+
69
+ if new.instance_variables.include?(:@extension_attrs)
70
+ new.instance_variable_get(:@extension_attrs).each do |a|
71
+ new.remove_instance_variable("@#{a}")
72
+ end
73
+ new.remove_instance_variable("@extension_attrs")
74
+ end
75
+
76
+ new
77
+ end
78
+ end
79
+
80
+ def self.is_extended?(obj)
81
+ obj.respond_to?(:extension_attr_hash)
82
+ end
83
+
84
+ def self.purge(obj)
85
+ case obj
86
+ when nil
87
+ nil
88
+ when Array
89
+ obj.collect{|e| purge(e) }
90
+ when Hash
91
+ new = {}
92
+ obj.each do |k,v|
93
+ new[k] = purge(v)
94
+ end
95
+ new
96
+ else
97
+ is_extended?(obj) ? obj.purge : obj
98
+ end
65
99
  end
66
100
  end
@@ -1,4 +1,5 @@
1
1
  module Misc
2
+ MAX_ARRAY_DIGEST_LENGTH = 100_000
2
3
  def self.digest_str(obj)
3
4
  if obj.respond_to?(:digest_str)
4
5
  obj.digest_str
@@ -9,12 +10,19 @@ module Misc
9
10
  if Path === obj || ! Open.exists?(obj)
10
11
  '\'' << obj << '\''
11
12
  else
12
- Misc.file_md5(obj)
13
+ "File MD5: #{Misc.file_md5(obj)}"
13
14
  end
14
15
  when Integer, Symbol
15
16
  obj.to_s
16
17
  when Array
17
- '[' << obj.inject(""){|acc,o| acc.empty? ? Misc.digest_str(o) : acc << ', ' << Misc.digest_str(o) } << ']'
18
+ if obj.length > MAX_ARRAY_DIGEST_LENGTH
19
+ length = obj.length
20
+ mid = length/2
21
+ sample_pos = [1, 2, mid, length-2, length-1]
22
+ "[#{length}:" << obj.values_at(*sample_pos).inject(""){|acc,o| acc.empty? ? Misc.digest_str(o) : acc << ', ' << Misc.digest_str(o) } << ']'
23
+ else
24
+ '[' << obj.inject(""){|acc,o| acc.empty? ? Misc.digest_str(o) : acc << ', ' << Misc.digest_str(o) } << ']'
25
+ end
18
26
  when Hash
19
27
  '{' << obj.inject(""){|acc,p| s = Misc.digest_str(p.first) << "=" << Misc.digest_str(p.last); acc.empty? ? s : acc << ', ' << s } << '}'
20
28
  when Integer
@@ -46,6 +54,7 @@ module Misc
46
54
 
47
55
  def self.file_md5(file)
48
56
  file = file.find if Path === file
57
+ file = File.expand_path(file)
49
58
  #md5file = file + '.md5'
50
59
  Persist.persist("MD5:#{file}", :string) do
51
60
  Digest::MD5.file(file).hexdigest
@@ -38,10 +38,10 @@ module Misc
38
38
 
39
39
 
40
40
 
41
- MAX_WIDTH = 100
41
+ MAX_TTY_LINE_WIDTH = 100
42
42
  def self.format_paragraph(text, size = nil, indent = nil, offset = nil)
43
- size ||= Log.tty_size || MAX_WIDTH
44
- size = MAX_WIDTH if size > MAX_WIDTH
43
+ size ||= Log.tty_size || MAX_TTY_LINE_WIDTH
44
+ size = MAX_TTY_LINE_WIDTH if size > MAX_TTY_LINE_WIDTH
45
45
  indent ||= 0
46
46
  offset ||= 0
47
47
 
@@ -75,9 +75,14 @@ module Misc
75
75
  end
76
76
 
77
77
  def self.format_definition_list_item(dt, dd, indent = nil, size = nil, color = :yellow)
78
- size ||= Log.tty_size || MAX_WIDTH
79
- size = MAX_WIDTH if size > MAX_WIDTH
80
- indent ||= size / 3
78
+ if size.nil?
79
+ base_size = MAX_TTY_LINE_WIDTH
80
+ base_indent = indent || (base_size / 3)
81
+ size = base_size - base_indent
82
+ end
83
+
84
+ indent ||= base_indent || size / 3
85
+
81
86
  dd = "" if dd.nil?
82
87
  dt = Log.color color, dt if color
83
88
  dt = dt.to_s unless dd.empty?
@@ -96,8 +101,8 @@ module Misc
96
101
  end
97
102
 
98
103
  def self.format_definition_list(defs, indent = nil, size = nil, color = :yellow, sep = "\n\n")
99
- size ||= Log.tty_size || MAX_WIDTH
100
104
  indent ||= 30
105
+ size ||= (Log.tty_size || MAX_TTY_LINE_WIDTH) - indent
101
106
  entries = []
102
107
  defs.each do |dt,dd|
103
108
  text = format_definition_list_item(dt,dd,indent, size,color)
@@ -2,6 +2,7 @@ module Misc
2
2
  def self.pid_alive?(pid)
3
3
  !! Process.kill(0, pid) rescue false
4
4
  end
5
+
5
6
  def self.benchmark(repeats = 1, message = nil)
6
7
  require 'benchmark'
7
8
  res = nil
@@ -52,4 +53,14 @@ module Misc
52
53
  end
53
54
  eend - start
54
55
  end
56
+
57
+ def self.wait_for_interrupt
58
+ while true
59
+ begin
60
+ sleep 1
61
+ rescue Interrupt
62
+ break
63
+ end
64
+ end
65
+ end
55
66
  end
@@ -1,4 +1,18 @@
1
+ require 'sys/proctable'
2
+
1
3
  module Misc
4
+
5
+ def self.hostname
6
+ @@hostname ||= begin
7
+ `hostname`.strip
8
+ end
9
+ end
10
+
11
+ def self.children(ppid = nil)
12
+ ppid ||= Process.pid
13
+ Sys::ProcTable.ps.select{ |pe| pe.ppid == ppid }
14
+ end
15
+
2
16
  def self.env_add(var, value, sep = ":", prepend = true)
3
17
  if ENV[var].nil?
4
18
  ENV[var] = value
@@ -22,4 +36,38 @@ module Misc
22
36
  ENV[var] = old_value
23
37
  end
24
38
  end
39
+
40
+ def self.update_git(gem_name = 'scout-gear')
41
+ gem_name = 'scout-gear' if gem_name.nil?
42
+ dir = File.join(__dir__, '../../../../', gem_name)
43
+ return unless Open.exist?(dir)
44
+ Misc.in_dir dir do
45
+ begin
46
+ begin
47
+ CMD.cmd_log('git pull')
48
+ rescue
49
+ raise "Could not update #{gem_name}"
50
+ end
51
+
52
+ begin
53
+ CMD.cmd_log('git submodule update')
54
+ rescue
55
+ raise "Could not update #{gem_name} submodules"
56
+ end
57
+
58
+
59
+ begin
60
+ CMD.cmd_log('rake install')
61
+ rescue
62
+ raise "Could not install updated #{gem_name}"
63
+ end
64
+ rescue
65
+ Log.warn $!.message
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.processors
71
+ Etc.nprocessors
72
+ end
25
73
  end
@@ -127,4 +127,12 @@ module NamedArray
127
127
  end
128
128
  source
129
129
  end
130
+
131
+ def method_missing(name, *args)
132
+ if identify_name(name)
133
+ return self[name]
134
+ else
135
+ return super(name, *args)
136
+ end
137
+ end
130
138
  end
@@ -0,0 +1,171 @@
1
+ require 'net/ssh'
2
+
3
+ class SSHLine
4
+ class << self
5
+ attr_accessor :default_server
6
+ def default_server
7
+ @@default_server ||= begin
8
+ ENV["SCOUT_OFFSITE"] || ENV["SCOUT_SERVER"] || 'localhost'
9
+ end
10
+ end
11
+ end
12
+
13
+ def initialize(host = :default, user = nil)
14
+ host = SSHLine.default_server if host.nil? || host == :default
15
+ @host = host
16
+ @user = user
17
+
18
+ @ssh = Net::SSH.start(@host, @user)
19
+
20
+ @ch = @ssh.open_channel do |ch|
21
+ ch.exec 'bash -l'
22
+ end
23
+
24
+ @ch.on_data do |_,data|
25
+ if m = data.match(/DONECMD: (\d+)\n/)
26
+ @exit_status = m[1].to_i
27
+ @output << data.sub(m[0],'')
28
+ serve_output
29
+ else
30
+ @output << data
31
+ end
32
+ end
33
+
34
+ @ch.on_extended_data do |_,c,err|
35
+ STDERR.write err
36
+ end
37
+ end
38
+
39
+
40
+ def self.reach?(server = SSHLine.default_server)
41
+ Persist.memory(server, :key => "Reach server") do
42
+ begin
43
+ CMD.cmd("ssh #{server} bash -l -c \"scout\"")
44
+ true
45
+ rescue Exception
46
+ false
47
+ end
48
+ end
49
+ end
50
+
51
+ def send_cmd(command)
52
+ @output = ""
53
+ @complete_output = false
54
+ @ch.send_data(command+"\necho DONECMD: $?\n")
55
+ end
56
+
57
+ def serve_output
58
+ @complete_output = true
59
+ end
60
+
61
+ def run(command)
62
+ send_cmd(command)
63
+ @ssh.loop{ ! @complete_output}
64
+ if @exit_status.to_i == 0
65
+ return @output
66
+ else
67
+ raise SSHProcessFailed.new @host, command
68
+ end
69
+ end
70
+
71
+ def ruby(script)
72
+ @output = ""
73
+ @complete_output = false
74
+ cmd = "ruby -e \"#{script.gsub('"','\\"')}\"\n"
75
+ Log.debug "Running ruby on #{@host}:\n#{ script }"
76
+ @ch.send_data(cmd)
77
+ @ch.send_data("echo DONECMD: $?\n")
78
+ @ssh.loop{ !@complete_output }
79
+ if @exit_status.to_i == 0
80
+ return @output
81
+ else
82
+ raise SSHProcessFailed.new @host, "Ruby script:\n#{script}"
83
+ end
84
+ end
85
+
86
+ def scout(script)
87
+ scout_script =<<-EOF
88
+ require 'scout'
89
+ SSHLine.run_local do
90
+ #{script.strip}
91
+ end
92
+ EOF
93
+
94
+ m = ruby(scout_script)
95
+ Marshal.load m
96
+ end
97
+
98
+ def workflow(workflow, script)
99
+ preamble =<<-EOF
100
+ wf = Workflow.require_workflow('#{workflow}')
101
+ EOF
102
+
103
+ scout(preamble + "\n" + script)
104
+ end
105
+
106
+ class Mock < SSHLine
107
+ def initialize
108
+ end
109
+
110
+ def run(command)
111
+ CMD.cmd(command)
112
+ end
113
+
114
+ def ruby(script)
115
+ cmd = "ruby -e \"#{script.gsub('"','\\"')}\"\n"
116
+ CMD.cmd(cmd)
117
+ end
118
+ end
119
+
120
+ @connections = {}
121
+ def self.open(host, user = nil)
122
+ @connections[[host, user]] ||=
123
+ begin
124
+ if host == 'localhost'
125
+ SSHLine::Mock.new
126
+ else
127
+ SSHLine.new host, user
128
+ end
129
+ end
130
+ end
131
+
132
+ def self.run(server, cmd, options = nil)
133
+ cmd = cmd * " " if Array === cmd
134
+ cmd += " " + CMD.process_cmd_options(options) if options
135
+ open(server).run(cmd)
136
+ end
137
+
138
+ def self.ruby(server, script)
139
+ open(server).ruby(script)
140
+ end
141
+
142
+ def self.scout(server, script)
143
+ open(server).scout(script)
144
+ end
145
+
146
+ def self.workflow(server, workflow, script)
147
+ open(server).workflow(workflow, script)
148
+ end
149
+
150
+ def self.command(server, command, argv = [], options = nil)
151
+ command = "scout #{command}" unless command && command.include?('scout')
152
+ argv_str = (argv - ["--"]).collect{|v| '"' + v.to_s + '"' } * " "
153
+ command = "#{command} #{argv_str}"
154
+ Log.debug "Offsite #{server} running: #{command}"
155
+ run(server, command, options)
156
+ end
157
+
158
+ def self.mkdir(server, path)
159
+ self.run server, "mkdir -p '#{path}'"
160
+ end
161
+
162
+ def self.run_local(&block)
163
+ res = begin
164
+ old_stdout = STDOUT.dup; STDOUT.reopen(STDERR)
165
+ block.call
166
+ ensure
167
+ STDOUT.reopen(old_stdout)
168
+ end
169
+ puts Marshal.dump(res)
170
+ end
171
+ end
@@ -0,0 +1,83 @@
1
+ require_relative '../workflow/step'
2
+ require_relative 'ssh'
3
+ require_relative 'sync'
4
+
5
+ module OffsiteStep
6
+
7
+ extend MetaExtension
8
+ extension_attr :server, :workflow_name, :clean_id, :provided_inputs
9
+
10
+ def inputs_directory
11
+ @inputs_directory ||= begin
12
+ if provided_inputs && provided_inputs.any?
13
+ file = ".scout/tmp/step_inputs/#{workflow}/#{task_name}/#{name}"
14
+ TmpFile.with_path do |inputs_dir|
15
+ task.save_inputs(inputs_dir, provided_inputs)
16
+ SSHLine.rsync(inputs_dir, file, target: server, directory: true)
17
+ end
18
+ file
19
+ end
20
+ end
21
+ end
22
+
23
+ def workflow_name
24
+ @workflow_name || workflow.to_s
25
+ end
26
+
27
+ def offsite_job_ssh(script)
28
+ parts = []
29
+ parts << <<~EOF.strip
30
+ wf = Workflow.require_workflow "#{workflow_name}";
31
+ EOF
32
+
33
+ if inputs_directory
34
+ parts << <<~EOF.strip
35
+ job = wf.job(:#{task_name}, "#{clean_name}", :load_inputs => "#{inputs_directory}");
36
+ EOF
37
+ else
38
+ parts << <<~EOF.strip
39
+ job = wf.job(:#{task_name}, "#{clean_name}");
40
+ EOF
41
+ end
42
+
43
+ parts << script
44
+
45
+
46
+ SSHLine.scout server, parts * "\n"
47
+ end
48
+
49
+ def offsite_path
50
+ @path = offsite_job_ssh <<~EOF
51
+ job.path.identify
52
+ EOF
53
+ end
54
+
55
+ def info
56
+ info = @info ||= offsite_job_ssh <<~EOF
57
+ info = Open.exists?(job.info_file) ? job.info : {}
58
+ info[:running] = true if job.running?
59
+ info
60
+ EOF
61
+
62
+ @info = nil unless %w(done aborted error).include?(info[:status].to_s)
63
+
64
+ info
65
+ end
66
+
67
+ def done?
68
+ status == :done
69
+ end
70
+
71
+ def exec
72
+ bundle_files = offsite_job_ssh <<~EOF
73
+ job.run
74
+ job.bundle_files
75
+ EOF
76
+ SSHLine.sync(bundle_files, source: server)
77
+ self.load
78
+ end
79
+
80
+ def run
81
+ exec
82
+ end
83
+ end
@@ -0,0 +1,55 @@
1
+ class SSHLine
2
+ def self.locate(server, paths, map: :user)
3
+ SSHLine.scout server, <<-EOF
4
+ map = :#{map}
5
+ paths = [#{paths.collect{|p| "'" + p + "'" } * ", " }]
6
+ located = paths.collect{|p| Path.setup(p).find(map) }
7
+ identified = paths.collect{|p| Resource.identify(p) }
8
+ [located, identified]
9
+ EOF
10
+ end
11
+
12
+ def self.rsync(source_path, target_path, directory: false, source: nil, target: nil, dry_run: false, hard_link: false)
13
+ rsync_args = "-avztHP --copy-unsafe-links --omit-dir-times "
14
+
15
+ rsync_args << "--link-dest '#{source_path}' " if hard_link && ! source
16
+
17
+ source_path = source_path + "/" if directory && ! source_path.end_with?("/")
18
+ target_path = target_path + "/" if directory && ! target_path.end_with?("/")
19
+ if target
20
+ SSHLine.mkdir target, File.dirname(target_path)
21
+ else
22
+ Open.mkdir(File.dirname(target_path))
23
+ end
24
+
25
+ cmd = 'rsync '
26
+ cmd << rsync_args
27
+ cmd << '-nv ' if dry_run
28
+ cmd << (source ? [source, source_path] * ":" : source_path) << " "
29
+ cmd << (target ? [target, target_path] * ":" : target_path) << " "
30
+
31
+ CMD.cmd_log(cmd, :log => Log::HIGH)
32
+ end
33
+
34
+ def self.sync(paths, source: nil, target: nil, map: :user, **kwargs)
35
+ source = nil if source == 'localhost'
36
+ target = nil if target == 'localhost'
37
+
38
+ if source
39
+ source_paths, identified_paths = SSHLine.locate(source, paths)
40
+ else
41
+ source_paths = paths.collect{|p| Path === p ? p.find : p }
42
+ identified_paths = paths.collect{|p| Resource.identify(p) }
43
+ end
44
+
45
+ if target
46
+ target_paths = SSHLine.locate(target, identified_paths, map: map)
47
+ else
48
+ target_paths = identified_paths.collect{|p| p.find(map) }
49
+ end
50
+
51
+ source_paths.zip(target_paths).each do |source_path,target_path|
52
+ rsync(source_path, target_path, source: source, target: target, **kwargs)
53
+ end
54
+ end
55
+ end