scout-gear 8.0.0 → 8.1.0

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