scout-camp 0.1.1

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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.vimproject +81 -0
  3. data/LICENSE +20 -0
  4. data/README.md +26 -0
  5. data/Rakefile +70 -0
  6. data/VERSION +1 -0
  7. data/bin/scout-camp +5 -0
  8. data/lib/scout/aws/s3.rb +157 -0
  9. data/lib/scout/offsite/exceptions.rb +9 -0
  10. data/lib/scout/offsite/ssh.rb +175 -0
  11. data/lib/scout/offsite/step.rb +100 -0
  12. data/lib/scout/offsite/sync.rb +55 -0
  13. data/lib/scout/offsite.rb +3 -0
  14. data/lib/scout/terraform_dsl/deployment.rb +285 -0
  15. data/lib/scout/terraform_dsl/util.rb +100 -0
  16. data/lib/scout/terraform_dsl.rb +317 -0
  17. data/lib/scout-camp.rb +6 -0
  18. data/scout_commands/offsite +30 -0
  19. data/scout_commands/terraform/add +78 -0
  20. data/scout_commands/terraform/apply +31 -0
  21. data/scout_commands/terraform/destroy +31 -0
  22. data/scout_commands/terraform/list +36 -0
  23. data/scout_commands/terraform/remove +39 -0
  24. data/scout_commands/terraform/status +33 -0
  25. data/share/terraform/aws/bucket/main.tf +8 -0
  26. data/share/terraform/aws/bucket/output.tf +3 -0
  27. data/share/terraform/aws/bucket/variables.tf +4 -0
  28. data/share/terraform/aws/cluster/main.tf +66 -0
  29. data/share/terraform/aws/cluster/output.tf +9 -0
  30. data/share/terraform/aws/cluster/variables.tf +49 -0
  31. data/share/terraform/aws/host/locals.tf +15 -0
  32. data/share/terraform/aws/host/main.tf +22 -0
  33. data/share/terraform/aws/host/output.tf +9 -0
  34. data/share/terraform/aws/host/variables.tf +67 -0
  35. data/share/terraform/aws/lambda/main.tf +40 -0
  36. data/share/terraform/aws/lambda/variables.tf +23 -0
  37. data/share/terraform/aws/provider/data.tf +35 -0
  38. data/share/terraform/aws/provider/output.tf +16 -0
  39. data/test/scout/aws/test_s3.rb +82 -0
  40. data/test/scout/offsite/test_ssh.rb +15 -0
  41. data/test/scout/offsite/test_step.rb +33 -0
  42. data/test/scout/offsite/test_sync.rb +36 -0
  43. data/test/scout/test_terraform_dsl.rb +519 -0
  44. data/test/test_helper.rb +19 -0
  45. metadata +99 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6f890271ab935a22aef0383dbb515abf6198f4e91d965055655c862066a09f5a
4
+ data.tar.gz: 04bf98b46eefc8e63a7b1ef158797f01ecb8aa3db24a92d42721eec2df2cefa5
5
+ SHA512:
6
+ metadata.gz: 6e8bb582fc33d1facaccb53f6e3d762f77a7dc41366a46bb032f65edb73bd976f9c0edf69fee313764e295417ad324c136366c49e5bcee0d0f927f4de7397dd2
7
+ data.tar.gz: 7245f12146cadce9940e2d59d0cef3b6843ef0139cada356995236c39bf75b104888f9192fca247c98775cd2b3235e51bc7c661f502f9914fdb7c0998752a6fd
data/.vimproject ADDED
@@ -0,0 +1,81 @@
1
+ scout-camp=/$PWD filter="*" {
2
+ bin=bin {
3
+ scout-camp
4
+ }
5
+ lib=lib {
6
+ scout-camp.rb
7
+ scout=scout {
8
+ aws=aws{
9
+ s3.rb
10
+ }
11
+ terraform_dsl.rb
12
+ terraform_dsl=terraform_dsl {
13
+ deployment.rb
14
+ util.rb
15
+ }
16
+ offsite.rb
17
+ offsite=offsite {
18
+ exceptions.rb
19
+ ssh.rb
20
+ step.rb
21
+ sync.rb
22
+ }
23
+ }
24
+ }
25
+ scout_commands=scout_commands {
26
+ offsite
27
+ terraform=terraform{
28
+ list
29
+ add
30
+ status
31
+ apply
32
+ destroy
33
+ remove
34
+ }
35
+ }
36
+ share=share {
37
+ terraform=terraform {
38
+ aws=aws {
39
+ bucket=bucket {
40
+ main.tf
41
+ output.tf
42
+ variables.tf
43
+ }
44
+ cluster=cluster {
45
+ main.tf
46
+ output.tf
47
+ variables.tf
48
+ }
49
+ host=host {
50
+ locals.tf
51
+ main.tf
52
+ output.tf
53
+ variables.tf
54
+ }
55
+ lambda=lambda {
56
+ main.tf
57
+ variables.tf
58
+ }
59
+ provider=provider {
60
+ data.tf
61
+ output.tf
62
+ }
63
+ }
64
+ }
65
+ }
66
+ test=test {
67
+ test_helper.rb
68
+ scout=scout {
69
+ test_terraform_dsl.rb
70
+ offsite=offsite {
71
+ test_ssh.rb
72
+ test_step.rb
73
+ test_sync.rb
74
+ }
75
+ }
76
+ }
77
+ LICENSE
78
+ README.md
79
+ Rakefile
80
+ VERSION
81
+ }
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2025 Miguel Vazquez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ scout-camp===================
2
+
3
+ Description goes here.
4
+
5
+ Contributing to scout-camp
6
+ ------------------------------------------
7
+
8
+ - Check out the latest master to make sure the feature hasn't been
9
+ implemented or the bug hasn't been fixed yet.
10
+ - Check out the issue tracker to make sure someone already hasn't
11
+ requested it and/or contributed it.
12
+ - Fork the project.
13
+ - Start a feature/bugfix branch.
14
+ - Commit and push until you are happy with your contribution.
15
+ - Make sure to add tests for it. This is important so I don't break it
16
+ in a future version unintentionally.
17
+ - Please try not to mess with the Rakefile, version, or history. If
18
+ you want to have your own version, or is otherwise necessary, that
19
+ is fine, but please isolate to its own commit so I can cherry-pick
20
+ around it.
21
+
22
+ Copyright
23
+ ---------
24
+
25
+ Copyright (c) 2025 Miguel Vazquez. See
26
+ LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+
3
+ ENV["BRANCH"] = 'main'
4
+
5
+ require 'rubygems'
6
+ require 'rake'
7
+ require 'juwelier'
8
+ Juwelier::Tasks.new do |gem|
9
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
10
+ gem.name = "scout-camp"
11
+ gem.homepage = "http://github.com/mikisvaz/scout-camp"
12
+ gem.license = "MIT"
13
+ gem.summary = %Q{Deploy you scouts}
14
+ gem.description = %Q{Functionalities to deploy and use scouts in remote servers like AWS}
15
+ gem.email = "mikisvaz@gmail.com"
16
+ gem.authors = ["Miguel Vazquez"]
17
+
18
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
19
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
20
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
21
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
22
+ #gem.add_development_dependency "shoulda", ">= 0"
23
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
24
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
25
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
26
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
27
+ #gem.add_development_dependency "rdoc", "~> 3.12"
28
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
29
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
30
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
31
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
32
+ #gem.add_development_dependency "bundler", "~> 1.0"
33
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
34
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
35
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
36
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
37
+ #gem.add_development_dependency "juwelier", "~> 2.1.0"
38
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
39
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
40
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
41
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
42
+ #gem.add_development_dependency "simplecov", ">= 0"
43
+
44
+ gem.add_runtime_dependency 'scout-essentials', '>= 0'
45
+ end
46
+ Juwelier::RubygemsDotOrgTasks.new
47
+ require 'rake/testtask'
48
+ Rake::TestTask.new(:test) do |test|
49
+ test.libs << 'lib' << 'test'
50
+ test.pattern = 'test/**/test_*.rb'
51
+ test.verbose = true
52
+ end
53
+
54
+ desc "Code coverage detail"
55
+ task :simplecov do
56
+ ENV['COVERAGE'] = "true"
57
+ Rake::Task['test'].execute
58
+ end
59
+
60
+ task :default => :test
61
+
62
+ require 'rdoc/task'
63
+ Rake::RDocTask.new do |rdoc|
64
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
65
+
66
+ rdoc.rdoc_dir = 'rdoc'
67
+ rdoc.title = "scout-camp #{version}"
68
+ rdoc.rdoc_files.include('README*')
69
+ rdoc.rdoc_files.include('lib/**/*.rb')
70
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
data/bin/scout-camp ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'scout-camp'
4
+
5
+ load Scout.bin.scout.find
@@ -0,0 +1,157 @@
1
+ require 'aws-sdk-s3'
2
+ require 'uri'
3
+
4
+ require 'scout/path'
5
+ require 'scout/open'
6
+ require 'scout/misc/hook'
7
+
8
+ module Open
9
+ module S3
10
+ extend Hook
11
+
12
+ def self.is_s3?(uri)
13
+ uri.start_with? 's3://'
14
+ end
15
+
16
+ def self.claim(uri, ...)
17
+ is_s3? uri
18
+ end
19
+
20
+ def self.parse_s3_uri(uri)
21
+ uri = uri.sub(%r{^s3://}, '')
22
+ bucket, *key_parts = uri.split('/', -1)
23
+ key = key_parts.join('/').sub(%r{^/}, '')
24
+ [bucket, key]
25
+ end
26
+
27
+ def self.get_stream(uri, *args)
28
+ bucket, key = parse_s3_uri(uri)
29
+ return nil if key.empty?
30
+
31
+ Open.open_pipe do |sin|
32
+ s3 = Aws::S3::Client.new
33
+ s3.get_object(bucket: bucket, key: key) do |block|
34
+ sin.write block
35
+ end
36
+ end
37
+ rescue Aws::S3::Errors::NoSuchKey
38
+ nil
39
+ end
40
+
41
+ def self.write(uri, content = nil, &block)
42
+ bucket, key = parse_s3_uri(uri)
43
+ s3 = Aws::S3::Client.new
44
+ content = Open.open_pipe(&block).read if block_given?
45
+ s3.put_object(bucket: bucket, key: key, body: content)
46
+ end
47
+
48
+ def self.glob(uri, pattern="**/*")
49
+ bucket, prefix = parse_s3_uri(uri)
50
+ s3 = Aws::S3::Client.new
51
+ matches = []
52
+ continuation_token = nil
53
+
54
+ loop do
55
+ resp = s3.list_objects_v2(
56
+ bucket: bucket,
57
+ prefix: prefix,
58
+ continuation_token: continuation_token
59
+ )
60
+
61
+ resp.contents.each do |object|
62
+ key = object.key
63
+ remaining = key[prefix.length..-1] || ''
64
+ remaining = remaining.sub(%r{^/}, '') if prefix.empty? # Handle root-level keys with leading slash
65
+
66
+ if File.fnmatch?(pattern, remaining, File::FNM_PATHNAME)
67
+ matches << "s3://#{bucket}/#{key}"
68
+ end
69
+ end
70
+
71
+ continuation_token = resp.next_continuation_token
72
+ break unless continuation_token
73
+ end
74
+
75
+ matches
76
+ end
77
+
78
+ def self.rm(uri)
79
+ bucket, key = parse_s3_uri(uri)
80
+ return false if key.empty? # Prevent accidental bucket deletion attempts
81
+
82
+ s3 = Aws::S3::Client.new
83
+ s3.delete_object(bucket: bucket, key: key)
84
+ true
85
+ rescue Aws::S3::Errors::NoSuchKey, Aws::S3::Errors::NoSuchBucket
86
+ false
87
+ end
88
+
89
+ def self.rm_rf(uri)
90
+ bucket, prefix = parse_s3_uri(uri)
91
+ s3 = Aws::S3::Client.new
92
+ continuation_token = nil
93
+
94
+ loop do
95
+ resp = s3.list_objects_v2(
96
+ bucket: bucket,
97
+ prefix: prefix,
98
+ continuation_token: continuation_token
99
+ )
100
+
101
+ if resp.contents.any?
102
+ # Delete objects in batches of 1000 (S3 limit per request)
103
+ objects = resp.contents.map { |obj| { key: obj.key } }
104
+ s3.delete_objects(
105
+ bucket: bucket,
106
+ delete: { objects: objects, quiet: true }
107
+ )
108
+ end
109
+
110
+ continuation_token = resp.next_continuation_token
111
+ break unless continuation_token
112
+ end
113
+
114
+ true
115
+ rescue Aws::S3::Errors::NoSuchBucket
116
+ false
117
+ end
118
+
119
+ def self.exists?(uri)
120
+ bucket, key = parse_s3_uri(uri)
121
+ return false if key.empty? # Can't check existence of bucket this way
122
+
123
+ s3 = Aws::S3::Client.new
124
+ s3.head_object(bucket: bucket, key: key)
125
+ true
126
+ rescue Aws::S3::Errors::NotFound, Aws::S3::Errors::NoSuchBucket
127
+ false
128
+ end
129
+
130
+ self.singleton_class.alias_method :exist?, :exists?
131
+
132
+ end
133
+ end
134
+
135
+ module Path
136
+ extend Hook
137
+
138
+ module S3
139
+ def located?
140
+ Open::S3.is_s3?(self) || orig_located?
141
+ end
142
+
143
+ def glob(*args)
144
+ if Open::S3.is_s3?(self)
145
+ Open::S3.glob(self, *args)
146
+ else
147
+ orig_glob(*args)
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ Hook.apply(Open::S3, Open)
154
+ Hook.apply(Path::S3, Path)
155
+
156
+
157
+ #$ ask -t code --file /home/miki/git/scout-camp/lib/scout/aws/s3.rb extend this file [[...]] to include a function called self.exists? that determines if a uri exists {{{
@@ -0,0 +1,9 @@
1
+ class SSHProcessFailed < StandardError
2
+ attr_accessor :host, :cmd
3
+ def initialize(host, cmd)
4
+ @host = host
5
+ @cmd = cmd
6
+ message = "SSH server #{host} failed cmd '#{cmd}'"
7
+ super(message)
8
+ end
9
+ end
@@ -0,0 +1,175 @@
1
+ require 'net/ssh'
2
+ require_relative 'exceptions'
3
+
4
+ class SSHLine
5
+ class << self
6
+ attr_accessor :default_server
7
+ def default_server
8
+ @@default_server ||= begin
9
+ ENV["SCOUT_OFFSITE"] || ENV["SCOUT_SERVER"] || 'localhost'
10
+ end
11
+ end
12
+ end
13
+
14
+ def initialize(host = :default, user = nil)
15
+ host = SSHLine.default_server if host.nil? || host == :default
16
+ @host = host
17
+ @user = user
18
+
19
+ @ssh = Net::SSH.start(@host, @user)
20
+
21
+ @ch = @ssh.open_channel do |ch|
22
+ ch.exec 'bash -l'
23
+ end
24
+
25
+ @ch.send_data("[[ -f ~/.scout/environment ]] && source ~/.scout/environment\n")
26
+ @ch.send_data("[[ -f ~/.rbbt/environment ]] && source ~/.rbbt/environment\n")
27
+
28
+ @ch.on_data do |_,data|
29
+ if m = data.match(/DONECMD: (\d+)\n/)
30
+ @exit_status = m[1].to_i
31
+ @output << data.sub(m[0],'')
32
+ serve_output
33
+ else
34
+ @output << data
35
+ end
36
+ end
37
+
38
+ @ch.on_extended_data do |_,c,err|
39
+ STDERR.write err
40
+ end
41
+ end
42
+
43
+
44
+ def self.reach?(server = SSHLine.default_server)
45
+ Persist.memory(server, :key => "Reach server") do
46
+ begin
47
+ CMD.cmd("ssh #{server} bash -l -c \"scout\"")
48
+ true
49
+ rescue Exception
50
+ false
51
+ end
52
+ end
53
+ end
54
+
55
+ def send_cmd(command)
56
+ @output = ""
57
+ @complete_output = false
58
+ @ch.send_data(command+"\necho DONECMD: $?\n")
59
+ end
60
+
61
+ def serve_output
62
+ @complete_output = true
63
+ end
64
+
65
+ def run(command)
66
+ send_cmd(command)
67
+ @ssh.loop{ ! @complete_output}
68
+ if @exit_status.to_i == 0
69
+ return @output
70
+ else
71
+ raise SSHProcessFailed.new @host, command
72
+ end
73
+ end
74
+
75
+ def ruby(script)
76
+ @output = ""
77
+ @complete_output = false
78
+ cmd = "ruby -e \"#{script.gsub('"','\\"')}\"\n"
79
+ Log.debug "Running ruby on #{@host}:\n#{ script }"
80
+ @ch.send_data(cmd)
81
+ @ch.send_data("echo DONECMD: $?\n")
82
+ @ssh.loop{ !@complete_output }
83
+ if @exit_status.to_i == 0
84
+ return @output
85
+ else
86
+ raise SSHProcessFailed.new @host, "Ruby script:\n#{script}"
87
+ end
88
+ end
89
+
90
+ def scout(script)
91
+ scout_script =<<-EOF
92
+ require 'scout'
93
+ SSHLine.run_local do
94
+ #{script.strip}
95
+ end
96
+ EOF
97
+
98
+ m = ruby(scout_script)
99
+ Marshal.load m
100
+ end
101
+
102
+ def workflow(workflow, script)
103
+ preamble =<<-EOF
104
+ wf = Workflow.require_workflow('#{workflow}')
105
+ EOF
106
+
107
+ scout(preamble + "\n" + script)
108
+ end
109
+
110
+ class Mock < SSHLine
111
+ def initialize
112
+ end
113
+
114
+ def run(command)
115
+ CMD.cmd(command)
116
+ end
117
+
118
+ def ruby(script)
119
+ cmd = "ruby -e \"#{script.gsub('"','\\"')}\"\n"
120
+ CMD.cmd(cmd)
121
+ end
122
+ end
123
+
124
+ @connections = {}
125
+ def self.open(host, user = nil)
126
+ @connections[[host, user]] ||=
127
+ begin
128
+ if host == 'localhost'
129
+ SSHLine::Mock.new
130
+ else
131
+ SSHLine.new host, user
132
+ end
133
+ end
134
+ end
135
+
136
+ def self.run(server, cmd, options = nil)
137
+ cmd = cmd * " " if Array === cmd
138
+ cmd += " " + CMD.process_cmd_options(options) if options
139
+ open(server).run(cmd)
140
+ end
141
+
142
+ def self.ruby(server, script)
143
+ open(server).ruby(script)
144
+ end
145
+
146
+ def self.scout(server, script)
147
+ open(server).scout(script)
148
+ end
149
+
150
+ def self.workflow(server, workflow, script)
151
+ open(server).workflow(workflow, script)
152
+ end
153
+
154
+ def self.command(server, command, argv = [], options = nil)
155
+ command = "scout #{command}" unless command && command.include?('scout')
156
+ argv_str = (argv - ["--"]).collect{|v| '"' + v.to_s + '"' } * " "
157
+ command = "#{command} #{argv_str}"
158
+ Log.debug "Offsite #{server} running: #{command}"
159
+ run(server, command, options)
160
+ end
161
+
162
+ def self.mkdir(server, path)
163
+ self.run server, "mkdir -p '#{path}'"
164
+ end
165
+
166
+ def self.run_local(&block)
167
+ res = begin
168
+ old_stdout = STDOUT.dup; STDOUT.reopen(STDERR)
169
+ block.call
170
+ ensure
171
+ STDOUT.reopen(old_stdout)
172
+ end
173
+ puts Marshal.dump(res)
174
+ end
175
+ end
@@ -0,0 +1,100 @@
1
+ require_relative 'ssh'
2
+ require_relative 'sync'
3
+ require 'scout/workflow/step'
4
+
5
+ module OffsiteStep
6
+
7
+ extend Annotation
8
+ annotation :server, :workflow_name, :clean_id, :slurm
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
+ save_inputs(inputs_dir)
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 orchestrate_slurm
72
+ bundle_files = offsite_job_ssh <<~EOF
73
+ require 'rbbt/hpc'
74
+ HPC::BATCH_MODULE = HPC.batch_system "SLURM"
75
+ HPC::BATCH_MODULE.orchestrate_job(job, {})
76
+ job.join
77
+ job.bundle_files
78
+ EOF
79
+ SSHLine.sync(bundle_files, source: server)
80
+ self.load
81
+ end
82
+
83
+
84
+ def exec
85
+ bundle_files = offsite_job_ssh <<~EOF
86
+ job.run
87
+ job.bundle_files
88
+ EOF
89
+ SSHLine.sync(bundle_files, source: server)
90
+ self.load
91
+ end
92
+
93
+ def run
94
+ if slurm
95
+ orchestrate_slurm
96
+ else
97
+ exec
98
+ end
99
+ end
100
+ end