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