cnvrg 1.9.9.9.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/cnvrg +9 -0
- data/cnvrg.gemspec +47 -0
- data/lib/cnvrg.rb +7 -0
- data/lib/cnvrg/Images.rb +351 -0
- data/lib/cnvrg/api.rb +247 -0
- data/lib/cnvrg/api_v2.rb +14 -0
- data/lib/cnvrg/auth.rb +79 -0
- data/lib/cnvrg/cli.rb +5715 -0
- data/lib/cnvrg/cli/flow.rb +166 -0
- data/lib/cnvrg/cli/library_cli.rb +33 -0
- data/lib/cnvrg/cli/subcommand.rb +28 -0
- data/lib/cnvrg/cli/task.rb +116 -0
- data/lib/cnvrg/colors.rb +8 -0
- data/lib/cnvrg/connect_job_ssh.rb +31 -0
- data/lib/cnvrg/data.rb +335 -0
- data/lib/cnvrg/datafiles.rb +1325 -0
- data/lib/cnvrg/dataset.rb +892 -0
- data/lib/cnvrg/downloader/client.rb +101 -0
- data/lib/cnvrg/downloader/clients/azure_client.rb +45 -0
- data/lib/cnvrg/downloader/clients/gcp_client.rb +50 -0
- data/lib/cnvrg/downloader/clients/s3_client.rb +78 -0
- data/lib/cnvrg/experiment.rb +209 -0
- data/lib/cnvrg/files.rb +1047 -0
- data/lib/cnvrg/flow.rb +137 -0
- data/lib/cnvrg/helpers.rb +422 -0
- data/lib/cnvrg/helpers/agent.rb +188 -0
- data/lib/cnvrg/helpers/executer.rb +213 -0
- data/lib/cnvrg/hyper.rb +21 -0
- data/lib/cnvrg/image.rb +113 -0
- data/lib/cnvrg/image_cli.rb +25 -0
- data/lib/cnvrg/job_cli.rb +73 -0
- data/lib/cnvrg/job_ssh.rb +48 -0
- data/lib/cnvrg/logger.rb +111 -0
- data/lib/cnvrg/org_helpers.rb +5 -0
- data/lib/cnvrg/project.rb +822 -0
- data/lib/cnvrg/result.rb +29 -0
- data/lib/cnvrg/runner.rb +49 -0
- data/lib/cnvrg/ssh.rb +94 -0
- data/lib/cnvrg/storage.rb +128 -0
- data/lib/cnvrg/task.rb +165 -0
- data/lib/cnvrg/version.rb +3 -0
- metadata +460 -0
data/lib/cnvrg/result.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
module Cnvrg
|
3
|
+
class Result
|
4
|
+
attr_reader :msg, :msg_color, :e_msg, :e_msg_backtrace
|
5
|
+
|
6
|
+
def initialize(success, msg, e_msg = "", e_backtrace = "")
|
7
|
+
begin
|
8
|
+
@success = success
|
9
|
+
@msg = msg
|
10
|
+
@e_msg = e_msg
|
11
|
+
@e_msg_backtrace = e_backtrace
|
12
|
+
if !@success
|
13
|
+
@msg_color = Thor::Shell::Color::RED
|
14
|
+
else
|
15
|
+
@msg_color = Thor::Shell::Color::GREEN
|
16
|
+
|
17
|
+
end
|
18
|
+
rescue => e
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
def is_success?
|
23
|
+
return @success
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
end
|
data/lib/cnvrg/runner.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'cnvrg'
|
2
|
+
|
3
|
+
module Cnvrg
|
4
|
+
class Runner
|
5
|
+
# Allow everything fun to be injected from the outside while defaulting to normal implementations.
|
6
|
+
def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
|
7
|
+
@argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute!
|
11
|
+
exit_code = begin
|
12
|
+
# Thor accesses these streams directly rather than letting them be injected, so we replace them...
|
13
|
+
$stderr = @stderr
|
14
|
+
$stdin = @stdin
|
15
|
+
$stdout = @stdout
|
16
|
+
|
17
|
+
# Run our normal Thor app the way we know and love.
|
18
|
+
Cnvrg::CLI.start(@argv)
|
19
|
+
|
20
|
+
# Thor::Base#start does not have a return value, assume success if no exception is raised.
|
21
|
+
0
|
22
|
+
rescue StandardError => e
|
23
|
+
# The ruby interpreter would pipe this to STDERR and exit 1 in the case of an unhandled exception
|
24
|
+
b = e.backtrace
|
25
|
+
@stderr.puts("#{b.shift}: #{e.message} (#{e.class})")
|
26
|
+
@stderr.puts(b.map{|s| "\tfrom #{s}"}.join("\n"))
|
27
|
+
1
|
28
|
+
rescue SystemExit => e
|
29
|
+
e.status
|
30
|
+
ensure
|
31
|
+
# TODO: reset your app here, free up resources, etc.
|
32
|
+
# Examples:
|
33
|
+
# MyApp.logger.flush
|
34
|
+
# MyApp.logger.close
|
35
|
+
# MyApp.logger = nil
|
36
|
+
#
|
37
|
+
# MyApp.reset_singleton_instance_variables
|
38
|
+
|
39
|
+
# ...then we put the streams back.
|
40
|
+
$stderr = STDERR
|
41
|
+
$stdin = STDIN
|
42
|
+
$stdout = STDOUT
|
43
|
+
end
|
44
|
+
|
45
|
+
# Proxy our exit code back to the injected kernel.
|
46
|
+
@kernel.exit(exit_code)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/cnvrg/ssh.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'cnvrg/files'
|
3
|
+
require 'net/ssh'
|
4
|
+
|
5
|
+
|
6
|
+
module Cnvrg
|
7
|
+
class Ssh
|
8
|
+
attr_reader :is_ssh
|
9
|
+
|
10
|
+
|
11
|
+
def initialize(resp)
|
12
|
+
begin
|
13
|
+
@is_ssh = false
|
14
|
+
sts_path = resp["result"]["sts_path"]
|
15
|
+
|
16
|
+
uri = URI.parse(sts_path)
|
17
|
+
|
18
|
+
http_object = Net::HTTP.new(uri.host, uri.port)
|
19
|
+
http_object.use_ssl = true if uri.scheme == 'https'
|
20
|
+
request = Net::HTTP::Get.new(sts_path)
|
21
|
+
|
22
|
+
body = ""
|
23
|
+
http_object.start do |http|
|
24
|
+
response = http.request request
|
25
|
+
body = response.read_body
|
26
|
+
end
|
27
|
+
|
28
|
+
URLcrypt::key = [body].pack('H*')
|
29
|
+
ip = URLcrypt.decrypt(resp["result"]["machine_i"])
|
30
|
+
|
31
|
+
@user = URLcrypt.decrypt(resp["result"]["machine_u"])
|
32
|
+
key = URLcrypt.decrypt(resp["result"]["machine_k"])
|
33
|
+
@container = URLcrypt.decrypt(resp["result"]["machine_c"])
|
34
|
+
|
35
|
+
tempssh = Tempfile.new "sshkey"
|
36
|
+
tempssh.write open(key).read
|
37
|
+
tempssh.rewind
|
38
|
+
key_path = tempssh.path
|
39
|
+
count = 0
|
40
|
+
while count < 5
|
41
|
+
|
42
|
+
begin
|
43
|
+
@ssh = Net::SSH.start(ip, user=@user, :keys => key_path, :timeout => 10)
|
44
|
+
if !@ssh.nil?
|
45
|
+
@is_ssh = true
|
46
|
+
return
|
47
|
+
else
|
48
|
+
count+=1
|
49
|
+
sleep(2)
|
50
|
+
|
51
|
+
end
|
52
|
+
rescue
|
53
|
+
count+=1
|
54
|
+
sleep(2)
|
55
|
+
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
if tempssh
|
60
|
+
tempssh.close
|
61
|
+
tempssh.unlink
|
62
|
+
end
|
63
|
+
return false
|
64
|
+
rescue => e
|
65
|
+
|
66
|
+
puts e
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
def exec_command(command)
|
73
|
+
exec_command = "sudo -i -u #{@user} cnvrg exec_container #{@container} \"#{command}\" "
|
74
|
+
return @ssh.exec!(exec_command)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def close_ssh()
|
79
|
+
|
80
|
+
|
81
|
+
begin
|
82
|
+
|
83
|
+
@ssh.close
|
84
|
+
rescue => e
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Cnvrg
|
2
|
+
class Storage
|
3
|
+
def initialize(dataset: nil, project: nil, root_path: nil)
|
4
|
+
@element = dataset || project
|
5
|
+
@root_path = root_path
|
6
|
+
@client = @element.get_storage_client
|
7
|
+
end
|
8
|
+
|
9
|
+
def log_error(action: nil, error: '')
|
10
|
+
"[#{Time.now}] (#{action || 'default'}) #{error}"
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def get_chunks_size
|
15
|
+
(ENV['CNVRG_STORAGE_CHUNK_SIZE'] || 10).to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def init_progress_bar(size: nil, title: "Download Progress")
|
19
|
+
@progressbar = ProgressBar.create(:title => title,
|
20
|
+
:progress_mark => '=',
|
21
|
+
:format => "%b>>%i| %p%% %t",
|
22
|
+
:starting_at => 0,
|
23
|
+
:total => size,
|
24
|
+
:autofinish => true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_progress(size: 1)
|
28
|
+
@progressbar.progress += size
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def clone(commit: nil)
|
33
|
+
files_generator = Proc.new do |params|
|
34
|
+
@element.get_clone_chunk(commit: commit, chunk_size: params[:limit], offset: params[:offset])
|
35
|
+
end
|
36
|
+
action = Proc.new do |storage, local|
|
37
|
+
@client.download(storage, local)
|
38
|
+
end
|
39
|
+
|
40
|
+
@stats = @element.get_stats
|
41
|
+
progress = {size: @stats['commit_size'], title: "Clone Progress"}
|
42
|
+
|
43
|
+
storage_action(files_generator: files_generator, action: action, progress: progress)
|
44
|
+
end
|
45
|
+
|
46
|
+
def upload_files(commit: nil)
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def upload_files(files_generator, progress: {size: 0, title: ''})
|
51
|
+
init_progress_bar(progress)
|
52
|
+
@storage_errors = []
|
53
|
+
@finished = false
|
54
|
+
@files = Queue.new
|
55
|
+
t = Thread.new{file_gen_thread(files_generator)}
|
56
|
+
do_parallel{|file| self.upload_files_thread(file); self.make_progress(size: file['size'])}
|
57
|
+
t.join
|
58
|
+
handle_errors
|
59
|
+
end
|
60
|
+
|
61
|
+
def file_gen_upload_thread(files_generator)
|
62
|
+
while true
|
63
|
+
files = files_generator
|
64
|
+
files.each{|f| @files.push(f)}
|
65
|
+
break if files.blank?
|
66
|
+
end
|
67
|
+
@finished = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def storage_action(files_generator: nil, action: nil, progress: {size: 0, title: ''})
|
71
|
+
### the generator files should have {path (encrypted), name, size}
|
72
|
+
init_progress_bar(progress)
|
73
|
+
@storage_errors = []
|
74
|
+
@finished = false
|
75
|
+
@files = Queue.new
|
76
|
+
t = Thread.new{file_gen_thread(files_generator)}
|
77
|
+
do_parallel do |file|
|
78
|
+
self.download_file_thread(file) do |local, storage|
|
79
|
+
action.call(local, storage)
|
80
|
+
end
|
81
|
+
self.make_progress(size: file['size'])
|
82
|
+
end
|
83
|
+
t.join
|
84
|
+
handle_errors
|
85
|
+
end
|
86
|
+
|
87
|
+
def file_gen_thread(file_gen)
|
88
|
+
offset = 0
|
89
|
+
chunk_size = get_chunks_size
|
90
|
+
while true
|
91
|
+
files = file_gen.call(limit: chunk_size, offset: offset)
|
92
|
+
break if files.blank?
|
93
|
+
files.each{|f| @files.push(f)}
|
94
|
+
offset += files.size
|
95
|
+
end
|
96
|
+
@finished = true
|
97
|
+
end
|
98
|
+
|
99
|
+
def handle_errors
|
100
|
+
if @storage_errors.present?
|
101
|
+
File.open(@element.working_dir + "/.cnvrg/errors.yml", "w+"){|f| f.write @storage_errors.to_yaml}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def do_parallel
|
106
|
+
Parallel.each( -> { @files.empty? ? (@finished ? Parallel::Stop : sleep(1)) : @files.pop }, in_threads: get_chunks_size) do |file|
|
107
|
+
if file == 1
|
108
|
+
next
|
109
|
+
end
|
110
|
+
yield(file)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def download_file_thread(file)
|
115
|
+
return if file.blank?
|
116
|
+
local_path = file['name']
|
117
|
+
storage_path = file['path']
|
118
|
+
(0..5).each do
|
119
|
+
begin
|
120
|
+
# @client.download(storage_path, "#{@root_path}/#{local_path}")
|
121
|
+
break
|
122
|
+
rescue => e
|
123
|
+
log_error(action: "download #{local_path}", error: e.message)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/cnvrg/task.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
module Cnvrg
|
2
|
+
class Task
|
3
|
+
attr_accessor :title, :path, :source
|
4
|
+
def initialize(project_path, path: nil, content: {})
|
5
|
+
@path = path
|
6
|
+
@content = content
|
7
|
+
@source = 'flow' if @content.present?
|
8
|
+
@source = 'file' if @path.present?
|
9
|
+
@project = Cnvrg::Project.new(project_path)
|
10
|
+
@project_path = project_path
|
11
|
+
#generic
|
12
|
+
@type = nil #type can be exec, data, library, deploy
|
13
|
+
@compute = 'medium'
|
14
|
+
@title = nil
|
15
|
+
@uid = nil
|
16
|
+
@params = {}
|
17
|
+
|
18
|
+
#exec
|
19
|
+
@cmd = nil
|
20
|
+
@params_path = nil
|
21
|
+
|
22
|
+
#library
|
23
|
+
@library = nil
|
24
|
+
|
25
|
+
#dataset
|
26
|
+
@dataset = nil
|
27
|
+
@query = nil
|
28
|
+
|
29
|
+
#deploy
|
30
|
+
@function = nil
|
31
|
+
|
32
|
+
|
33
|
+
@base_resource = @project.base_resource + "/tasks"
|
34
|
+
|
35
|
+
self.reload_task
|
36
|
+
end
|
37
|
+
|
38
|
+
def save
|
39
|
+
path = @path || gen_path
|
40
|
+
File.open(path, 'w'){|f| f.write(get_content.to_yaml)}
|
41
|
+
end
|
42
|
+
|
43
|
+
def reload_task
|
44
|
+
task_raw = get_content
|
45
|
+
@title = task_raw[:title]
|
46
|
+
@type = task_raw[:type]
|
47
|
+
@uid = task_raw[:uid]
|
48
|
+
@title = task_raw[:title] || @uid
|
49
|
+
@compute = task_raw[:compute] || @compute
|
50
|
+
|
51
|
+
case @type
|
52
|
+
when 'exec'
|
53
|
+
@cmd = task_raw[:cmd]
|
54
|
+
init_params(task_raw)
|
55
|
+
when 'library'
|
56
|
+
@library = task_raw[:library]
|
57
|
+
init_params(task_raw)
|
58
|
+
when 'data'
|
59
|
+
@dataset = task_raw[:dataset]
|
60
|
+
@query = task_raw[:query]
|
61
|
+
when 'deploy'
|
62
|
+
@cmd = task_raw[:cmd]
|
63
|
+
@function = task_raw[:function]
|
64
|
+
else
|
65
|
+
error("Cant parse task of type #{@type}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def verify_task
|
70
|
+
case @type
|
71
|
+
when 'exec'
|
72
|
+
verify_exec
|
73
|
+
when 'data'
|
74
|
+
verify_data
|
75
|
+
when 'deploy'
|
76
|
+
verify_deploy
|
77
|
+
when 'library'
|
78
|
+
verify_library
|
79
|
+
else
|
80
|
+
error("Cant parse task of type #{@type}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def to_api
|
86
|
+
get_content.merge(params: @params)
|
87
|
+
end
|
88
|
+
|
89
|
+
def run
|
90
|
+
verify_task
|
91
|
+
if @type == 'data'
|
92
|
+
raise StandardError.new("Data Tasks are not runnable")
|
93
|
+
end
|
94
|
+
resp = Cnvrg::API.request(@base_resource, "POST", {task: to_api})
|
95
|
+
Cnvrg::CLI.is_response_success(resp, true)
|
96
|
+
Cnvrg::Helpers.remote_url + resp['result']['url']
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def verify_compute
|
101
|
+
unless @project.check_machine(@compute)
|
102
|
+
raise StandardError.new("Cant find #{@compute} machine in project.")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def init_params(task_raw)
|
107
|
+
@params_path = task_raw[:params_path].presence
|
108
|
+
@params = task_raw[:params] || @params
|
109
|
+
if @params_path.present?
|
110
|
+
@hyper = Cnvrg::Hyper.new(@project_path, @params_path)
|
111
|
+
@params = @hyper.resolve_params
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def verify_exec
|
116
|
+
if @cmd.blank?
|
117
|
+
error("Cant find command")
|
118
|
+
end
|
119
|
+
verify_compute
|
120
|
+
end
|
121
|
+
|
122
|
+
def verify_data
|
123
|
+
if @dataset.blank?
|
124
|
+
error("Cant find dataset slug")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def verify_deploy
|
129
|
+
error("Cant find command") if @cmd.blank?
|
130
|
+
error("Cant find function") if @function.blank?
|
131
|
+
verify_compute
|
132
|
+
end
|
133
|
+
|
134
|
+
def verify_library
|
135
|
+
error("Cant find library") if @library.blank?
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_content
|
139
|
+
return @content if @source == 'flow'
|
140
|
+
unless File.exists? @path
|
141
|
+
raise StandardError.new("Cant find task in #{@path}")
|
142
|
+
end
|
143
|
+
YAML.load_file(@path)
|
144
|
+
end
|
145
|
+
|
146
|
+
def gen_path
|
147
|
+
@title ||= "#{@type.capitalize}Task"
|
148
|
+
unless File.exists? @title
|
149
|
+
@path = "#{@title}.task.yaml"
|
150
|
+
return @path
|
151
|
+
end
|
152
|
+
i = 0
|
153
|
+
while File.exists? "#{@title}_#{i}.task.yaml"
|
154
|
+
i += 1
|
155
|
+
end
|
156
|
+
@path = "#{@title}.task.yaml"
|
157
|
+
return @path
|
158
|
+
end
|
159
|
+
|
160
|
+
def error(msg)
|
161
|
+
raise StandardError.new("task: #{@uid} - #{msg}")
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|