scout-gear 7.3.0 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +44 -16
  3. data/Rakefile +6 -1
  4. data/VERSION +1 -1
  5. data/bin/scout +21 -7
  6. data/doc/lib/scout/path.md +35 -0
  7. data/doc/lib/scout/workflow/task.md +13 -0
  8. data/lib/rbbt-scout.rb +1 -0
  9. data/lib/scout/cmd.rb +24 -25
  10. data/lib/scout/concurrent_stream.rb +59 -39
  11. data/lib/scout/config.rb +1 -1
  12. data/lib/scout/exceptions.rb +10 -0
  13. data/lib/scout/log/color.rb +15 -12
  14. data/lib/scout/log/progress/report.rb +8 -6
  15. data/lib/scout/log/progress/util.rb +61 -54
  16. data/lib/scout/log/progress.rb +1 -1
  17. data/lib/scout/log/trap.rb +107 -0
  18. data/lib/scout/log.rb +115 -52
  19. data/lib/scout/meta_extension.rb +47 -6
  20. data/lib/scout/misc/digest.rb +12 -3
  21. data/lib/scout/misc/format.rb +24 -7
  22. data/lib/scout/misc/insist.rb +1 -1
  23. data/lib/scout/misc/monitor.rb +22 -0
  24. data/lib/scout/misc/system.rb +58 -0
  25. data/lib/scout/named_array.rb +73 -3
  26. data/lib/scout/offsite/ssh.rb +171 -0
  27. data/lib/scout/offsite/step.rb +83 -0
  28. data/lib/scout/offsite/sync.rb +55 -0
  29. data/lib/scout/offsite.rb +3 -0
  30. data/lib/scout/open/lock/lockfile.rb +587 -0
  31. data/lib/scout/open/lock.rb +9 -2
  32. data/lib/scout/open/remote.rb +16 -1
  33. data/lib/scout/open/stream.rb +146 -83
  34. data/lib/scout/open/util.rb +22 -3
  35. data/lib/scout/open.rb +5 -4
  36. data/lib/scout/path/find.rb +24 -11
  37. data/lib/scout/path/util.rb +40 -0
  38. data/lib/scout/persist/serialize.rb +19 -6
  39. data/lib/scout/persist.rb +29 -13
  40. data/lib/scout/resource/path.rb +57 -0
  41. data/lib/scout/resource/produce.rb +0 -8
  42. data/lib/scout/resource/util.rb +12 -5
  43. data/lib/scout/tmpfile.rb +7 -8
  44. data/lib/scout/tsv/attach.rb +177 -0
  45. data/lib/scout/tsv/change_id.rb +40 -0
  46. data/lib/scout/tsv/dumper.rb +74 -46
  47. data/lib/scout/tsv/index.rb +85 -87
  48. data/lib/scout/tsv/open.rb +160 -85
  49. data/lib/scout/tsv/parser.rb +142 -80
  50. data/lib/scout/tsv/path.rb +1 -2
  51. data/lib/scout/tsv/persist/adapter.rb +15 -45
  52. data/lib/scout/tsv/persist/fix_width_table.rb +3 -0
  53. data/lib/scout/tsv/persist/tokyocabinet.rb +6 -1
  54. data/lib/scout/tsv/persist.rb +4 -0
  55. data/lib/scout/tsv/stream.rb +204 -0
  56. data/lib/scout/tsv/transformer.rb +152 -0
  57. data/lib/scout/tsv/traverse.rb +96 -92
  58. data/lib/scout/tsv/util/filter.rb +9 -0
  59. data/lib/scout/tsv/util/reorder.rb +81 -0
  60. data/lib/scout/tsv/util/select.rb +78 -33
  61. data/lib/scout/tsv/util/unzip.rb +86 -0
  62. data/lib/scout/tsv/util.rb +60 -11
  63. data/lib/scout/tsv.rb +34 -4
  64. data/lib/scout/work_queue/socket.rb +6 -1
  65. data/lib/scout/work_queue/worker.rb +5 -2
  66. data/lib/scout/work_queue.rb +51 -20
  67. data/lib/scout/workflow/definition.rb +23 -3
  68. data/lib/scout/workflow/deployment/orchestrator.rb +245 -0
  69. data/lib/scout/workflow/deployment.rb +1 -0
  70. data/lib/scout/workflow/step/dependencies.rb +56 -10
  71. data/lib/scout/workflow/step/file.rb +5 -0
  72. data/lib/scout/workflow/step/info.rb +40 -7
  73. data/lib/scout/workflow/step/load.rb +1 -1
  74. data/lib/scout/workflow/step/provenance.rb +9 -7
  75. data/lib/scout/workflow/step/status.rb +43 -0
  76. data/lib/scout/workflow/step.rb +160 -49
  77. data/lib/scout/workflow/task/dependencies.rb +114 -0
  78. data/lib/scout/workflow/task/inputs.rb +40 -32
  79. data/lib/scout/workflow/task.rb +38 -102
  80. data/lib/scout/workflow/usage.rb +48 -18
  81. data/lib/scout/workflow.rb +4 -2
  82. data/lib/scout-gear.rb +2 -0
  83. data/lib/scout.rb +6 -0
  84. data/scout-gear.gemspec +52 -23
  85. data/scout_commands/doc +37 -0
  86. data/scout_commands/find +1 -0
  87. data/scout_commands/offsite +30 -0
  88. data/scout_commands/update +29 -0
  89. data/scout_commands/workflow/info +15 -3
  90. data/scout_commands/workflow/install +102 -0
  91. data/scout_commands/workflow/task +57 -9
  92. data/test/scout/offsite/test_ssh.rb +15 -0
  93. data/test/scout/offsite/test_step.rb +33 -0
  94. data/test/scout/offsite/test_sync.rb +36 -0
  95. data/test/scout/offsite/test_task.rb +0 -0
  96. data/test/scout/open/test_stream.rb +60 -58
  97. data/test/scout/path/test_find.rb +10 -1
  98. data/test/scout/resource/test_path.rb +6 -0
  99. data/test/scout/resource/test_produce.rb +15 -0
  100. data/test/scout/test_meta_extension.rb +25 -0
  101. data/test/scout/test_named_array.rb +24 -0
  102. data/test/scout/test_persist.rb +9 -2
  103. data/test/scout/test_tsv.rb +229 -2
  104. data/test/scout/test_work_queue.rb +65 -41
  105. data/test/scout/tsv/persist/test_tokyocabinet.rb +29 -1
  106. data/test/scout/tsv/test_attach.rb +227 -0
  107. data/test/scout/tsv/test_change_id.rb +98 -0
  108. data/test/scout/tsv/test_dumper.rb +1 -1
  109. data/test/scout/tsv/test_index.rb +49 -3
  110. data/test/scout/tsv/test_open.rb +160 -2
  111. data/test/scout/tsv/test_parser.rb +33 -2
  112. data/test/scout/tsv/test_persist.rb +2 -0
  113. data/test/scout/tsv/test_stream.rb +200 -0
  114. data/test/scout/tsv/test_transformer.rb +120 -0
  115. data/test/scout/tsv/test_traverse.rb +88 -3
  116. data/test/scout/tsv/test_util.rb +1 -0
  117. data/test/scout/tsv/util/test_reorder.rb +94 -0
  118. data/test/scout/tsv/util/test_select.rb +25 -11
  119. data/test/scout/tsv/util/test_unzip.rb +112 -0
  120. data/test/scout/work_queue/test_socket.rb +0 -1
  121. data/test/scout/workflow/deployment/test_orchestrator.rb +272 -0
  122. data/test/scout/workflow/step/test_dependencies.rb +68 -0
  123. data/test/scout/workflow/step/test_info.rb +18 -0
  124. data/test/scout/workflow/step/test_status.rb +30 -0
  125. data/test/scout/workflow/task/test_dependencies.rb +355 -0
  126. data/test/scout/workflow/task/test_inputs.rb +67 -14
  127. data/test/scout/workflow/test_definition.rb +18 -0
  128. data/test/scout/workflow/test_documentation.rb +24 -0
  129. data/test/scout/workflow/test_step.rb +112 -3
  130. data/test/scout/workflow/test_task.rb +0 -151
  131. data/test/scout/workflow/test_usage.rb +33 -6
  132. data/test/test_scout.rb +9 -0
  133. metadata +100 -8
  134. data/scout_commands/workflow/task_old +0 -706
@@ -2,7 +2,7 @@ module MetaExtension
2
2
  def self.extended(base)
3
3
  meta = class << base; self; end
4
4
 
5
- base.class_variable_set("@@extension_attrs", [])
5
+ base.class_variable_set("@@extension_attrs", []) unless base.class_variables.include?("@@extension_attrs")
6
6
 
7
7
  meta.define_method(:extension_attr) do |*attrs|
8
8
  self.class_variable_get("@@extension_attrs").concat attrs
@@ -11,6 +11,14 @@ module MetaExtension
11
11
  end
12
12
  end
13
13
 
14
+ meta.define_method(:extended) do |obj|
15
+ attrs = self.class_variable_get("@@extension_attrs")
16
+
17
+ obj.instance_variable_set(:@extension_attrs, []) unless obj.instance_variables.include?(:@extension_attrs)
18
+ extension_attrs = obj.instance_variable_get(:@extension_attrs)
19
+ extension_attrs.concat attrs
20
+ end
21
+
14
22
  meta.define_method(:setup) do |*args,&block|
15
23
  if block_given?
16
24
  obj, rest = block, args
@@ -18,7 +26,8 @@ module MetaExtension
18
26
  obj, *rest = args
19
27
  end
20
28
  obj = block if obj.nil?
21
- obj.extend base
29
+ obj.extend base unless base === obj
30
+
22
31
  attrs = self.class_variable_get("@@extension_attrs")
23
32
 
24
33
  return if attrs.nil? || attrs.empty?
@@ -27,8 +36,6 @@ module MetaExtension
27
36
  ((! (rlkey = rlast.keys.first).nil? && attrs.include?(rlkey.to_sym)) ||
28
37
  (! attrs.length != 1 ))
29
38
 
30
-
31
-
32
39
  pairs = rlast
33
40
  else
34
41
  pairs = attrs.zip(rest)
@@ -43,17 +50,51 @@ module MetaExtension
43
50
 
44
51
  base.define_method(:extension_attr_hash) do
45
52
  attr_hash = {}
46
- meta.class_variable_get("@@extension_attrs").each do |name|
53
+ @extension_attrs.each do |name|
47
54
  attr_hash[name] = self.instance_variable_get("@#{name}")
48
55
  end
49
56
  attr_hash
50
57
  end
51
58
 
52
59
  base.define_method(:annotate) do |other|
53
- attr_values = meta.class_variable_get("@@extension_attrs").collect do |a|
60
+ attr_values = @extension_attrs.collect do |a|
54
61
  self.instance_variable_get("@#{a}")
55
62
  end
56
63
  base.setup(other, *attr_values)
57
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
58
99
  end
59
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
@@ -40,12 +48,13 @@ module Misc
40
48
  end
41
49
 
42
50
  def self.digest(obj)
43
- str = Misc.digest_str(obj)
51
+ str = String === obj ? obj : Misc.digest_str(obj)
44
52
  Digest::MD5.hexdigest(str)
45
53
  end
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
@@ -25,11 +25,23 @@ module Misc
25
25
  str
26
26
  end
27
27
 
28
+ CHAR_SENCONDS = ENV["SCOUT_NOCOLOR"] == "true" ? "sec" : "″"
29
+ def self.format_seconds_short(time)
30
+ if time < 0.0001
31
+ "%.5g" % time + CHAR_SENCONDS
32
+ elsif time < 60
33
+ "%.2g" % time + CHAR_SENCONDS
34
+ else
35
+ format_seconds(time)
36
+ end
37
+ end
38
+
39
+
28
40
 
29
- MAX_WIDTH = 100
41
+ MAX_TTY_LINE_WIDTH = 100
30
42
  def self.format_paragraph(text, size = nil, indent = nil, offset = nil)
31
- size ||= Log.tty_size || MAX_WIDTH
32
- 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
33
45
  indent ||= 0
34
46
  offset ||= 0
35
47
 
@@ -63,9 +75,14 @@ module Misc
63
75
  end
64
76
 
65
77
  def self.format_definition_list_item(dt, dd, indent = nil, size = nil, color = :yellow)
66
- size ||= Log.tty_size || MAX_WIDTH
67
- size = MAX_WIDTH if size > MAX_WIDTH
68
- 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
+
69
86
  dd = "" if dd.nil?
70
87
  dt = Log.color color, dt if color
71
88
  dt = dt.to_s unless dd.empty?
@@ -84,8 +101,8 @@ module Misc
84
101
  end
85
102
 
86
103
  def self.format_definition_list(defs, indent = nil, size = nil, color = :yellow, sep = "\n\n")
87
- size ||= Log.tty_size || MAX_WIDTH
88
104
  indent ||= 30
105
+ size ||= (Log.tty_size || MAX_TTY_LINE_WIDTH) - indent
89
106
  entries = []
90
107
  defs.each do |dt,dd|
91
108
  text = format_definition_list_item(dt,dd,indent, size,color)
@@ -32,7 +32,7 @@ module Misc
32
32
  end
33
33
  raise $!
34
34
  rescue Exception
35
- Log.exception $! if ENV["RBBT_LOG_INSIST"] == 'true'
35
+ Log.exception $! if ENV["SCOUT_LOG_INSIST"] == 'true'
36
36
  if msg
37
37
  Log.warn("Insisting after exception: #{$!.class} #{$!.message} -- #{msg}")
38
38
  elsif FalseClass === msg
@@ -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
@@ -41,4 +42,25 @@ module Misc
41
42
 
42
43
  res
43
44
  end
45
+
46
+ def self.exec_time(&block)
47
+ start = Time.now
48
+ eend = nil
49
+ begin
50
+ yield
51
+ ensure
52
+ eend = Time.now
53
+ end
54
+ eend - start
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
44
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
@@ -12,4 +26,48 @@ module Misc
12
26
  end
13
27
  end
14
28
  end
29
+
30
+ def self.with_env(var, value, &block)
31
+ old_value = ENV[var]
32
+ begin
33
+ ENV[var] = value
34
+ yield
35
+ ensure
36
+ ENV[var] = old_value
37
+ end
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
15
73
  end
@@ -1,13 +1,25 @@
1
1
  require_relative 'meta_extension'
2
2
  module NamedArray
3
3
  extend MetaExtension
4
- extension_attr :fields
4
+ extension_attr :fields, :key
5
5
 
6
- def self.identify_name(names, selected)
6
+ def self.field_match(field, name)
7
+ if (String === field) && (String === name)
8
+ field == name ||
9
+ field.start_with?(name) || field.include?("(" + name + ")") ||
10
+ name.start_with?(field) || name.include?("(" + field + ")")
11
+ else
12
+ field == name
13
+ end
14
+ end
15
+
16
+ def self.identify_name(names, selected, strict: false)
7
17
  res = (Array === selected ? selected : [selected]).collect do |field|
8
18
  case field
9
19
  when nil
10
20
  0
21
+ when Range
22
+ field
11
23
  when Integer
12
24
  field
13
25
  when Symbol
@@ -26,7 +38,8 @@ module NamedArray
26
38
  if field =~ /^\d+$/
27
39
  next identify_names(names, field.to_i)
28
40
  end
29
- pos = names.index{|name| name.start_with?(field) }
41
+ next pos if strict
42
+ pos = names.index{|name| field_match(field, name) }
30
43
  next pos if pos
31
44
  nil
32
45
  else
@@ -37,6 +50,10 @@ module NamedArray
37
50
  Array === selected ? res : res.first
38
51
  end
39
52
 
53
+ def identify_name(selected)
54
+ NamedArray.identify_name(fields, selected)
55
+ end
56
+
40
57
  def positions(fields)
41
58
  if Array == fields
42
59
  fields.collect{|field|
@@ -49,6 +66,7 @@ module NamedArray
49
66
 
50
67
  def [](key)
51
68
  pos = NamedArray.identify_name(@fields, key)
69
+ return nil if pos.nil?
52
70
  super(pos)
53
71
  end
54
72
 
@@ -65,4 +83,56 @@ module NamedArray
65
83
  end
66
84
  IndiferentHash.setup hash
67
85
  end
86
+
87
+ def values_at(*positions)
88
+ super(*identify_name(positions))
89
+ end
90
+
91
+ def self._zip_fields(array, max = nil)
92
+ return [] if array.nil? or array.empty? or (first = array.first).nil?
93
+
94
+ max = array.collect{|l| l.length }.max if max.nil?
95
+
96
+ rest = array[1..-1].collect{|v|
97
+ v.length == 1 & max > 1 ? v * max : v
98
+ }
99
+
100
+ first = first * max if first.length == 1 and max > 1
101
+
102
+ first.zip(*rest)
103
+ end
104
+
105
+ def self.zip_fields(array)
106
+ if array.length < 10000
107
+ _zip_fields(array)
108
+ else
109
+ zipped_slices = []
110
+ max = array.collect{|l| l.length}.max
111
+ array.each_slice(10000) do |slice|
112
+ zipped_slices << _zip_fields(slice, max)
113
+ end
114
+ new = zipped_slices.first
115
+ zipped_slices[1..-1].each do |rest|
116
+ rest.each_with_index do |list,i|
117
+ new[i].concat list
118
+ end
119
+ end
120
+ new
121
+ end
122
+ end
123
+
124
+ def self.add_zipped(source, new)
125
+ source.zip(new).each do |s,n|
126
+ s.concat(n)
127
+ end
128
+ source
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
68
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