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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/bin/cnvrg +9 -0
  3. data/cnvrg.gemspec +47 -0
  4. data/lib/cnvrg.rb +7 -0
  5. data/lib/cnvrg/Images.rb +351 -0
  6. data/lib/cnvrg/api.rb +247 -0
  7. data/lib/cnvrg/api_v2.rb +14 -0
  8. data/lib/cnvrg/auth.rb +79 -0
  9. data/lib/cnvrg/cli.rb +5715 -0
  10. data/lib/cnvrg/cli/flow.rb +166 -0
  11. data/lib/cnvrg/cli/library_cli.rb +33 -0
  12. data/lib/cnvrg/cli/subcommand.rb +28 -0
  13. data/lib/cnvrg/cli/task.rb +116 -0
  14. data/lib/cnvrg/colors.rb +8 -0
  15. data/lib/cnvrg/connect_job_ssh.rb +31 -0
  16. data/lib/cnvrg/data.rb +335 -0
  17. data/lib/cnvrg/datafiles.rb +1325 -0
  18. data/lib/cnvrg/dataset.rb +892 -0
  19. data/lib/cnvrg/downloader/client.rb +101 -0
  20. data/lib/cnvrg/downloader/clients/azure_client.rb +45 -0
  21. data/lib/cnvrg/downloader/clients/gcp_client.rb +50 -0
  22. data/lib/cnvrg/downloader/clients/s3_client.rb +78 -0
  23. data/lib/cnvrg/experiment.rb +209 -0
  24. data/lib/cnvrg/files.rb +1047 -0
  25. data/lib/cnvrg/flow.rb +137 -0
  26. data/lib/cnvrg/helpers.rb +422 -0
  27. data/lib/cnvrg/helpers/agent.rb +188 -0
  28. data/lib/cnvrg/helpers/executer.rb +213 -0
  29. data/lib/cnvrg/hyper.rb +21 -0
  30. data/lib/cnvrg/image.rb +113 -0
  31. data/lib/cnvrg/image_cli.rb +25 -0
  32. data/lib/cnvrg/job_cli.rb +73 -0
  33. data/lib/cnvrg/job_ssh.rb +48 -0
  34. data/lib/cnvrg/logger.rb +111 -0
  35. data/lib/cnvrg/org_helpers.rb +5 -0
  36. data/lib/cnvrg/project.rb +822 -0
  37. data/lib/cnvrg/result.rb +29 -0
  38. data/lib/cnvrg/runner.rb +49 -0
  39. data/lib/cnvrg/ssh.rb +94 -0
  40. data/lib/cnvrg/storage.rb +128 -0
  41. data/lib/cnvrg/task.rb +165 -0
  42. data/lib/cnvrg/version.rb +3 -0
  43. metadata +460 -0
@@ -0,0 +1,25 @@
1
+
2
+ module Cnvrg
3
+ class ImageCli < SubCommandBase
4
+
5
+ desc 'image build --image=IMAGE_ID', 'Build image job', :hide => true
6
+ method_option :image_id, :type => :string, :aliases => ["--image"]
7
+ def build_image_job()
8
+ begin
9
+ @cli = Cnvrg::CLI.new
10
+ @cli.verify_logged_in(false)
11
+ @cli.log_start(__method__, args, options)
12
+ @cli.log_message("build image started", Thor::Shell::Color::BLUE)
13
+ image = Image.new(options["image_id"])
14
+ image.build
15
+ @cli.log_message("Image build completed successfully", Thor::Shell::Color::BLUE)
16
+ rescue => e
17
+ @cli.log_message("Image build completed with an error", Thor::Shell::Color::RED)
18
+ @cli.log_error(e)
19
+ exit(1)
20
+ end
21
+ end
22
+
23
+
24
+ end
25
+ end
@@ -0,0 +1,73 @@
1
+ module Cnvrg
2
+ class JobCli < SubCommandBase
3
+
4
+ desc 'log', '', :hide => true
5
+ method_option :level, :type => :string, :aliases => ["-l", "--level"], :default => 'info'
6
+ method_option :step, :type => :string, :aliases => ["-s", "--step"], :default => nil
7
+ method_option :restart, :type => :boolean, :aliases => ["-r", "--restart"], :default => false
8
+ def log(*logs)
9
+ Cnvrg::CLI.new.log_start(__method__, args, options)
10
+ @project = Project.new(owner: ENV['CNVRG_OWNER'], slug: ENV['CNVRG_PROJECT'])
11
+ if options['restart'] == @project.check_job_pod_restart[0] or options['step'] == "ready"
12
+ @project.job_log(logs, level: options['level'], step: options['step'])
13
+ else
14
+ @project.job_log(nil, level: options['level'], step: options['step'])
15
+ end
16
+ end
17
+
18
+ desc 'requirements', '', :hide => true
19
+ def requirements
20
+ cli = Cnvrg::CLI.new
21
+ cli.log_start(__method__, args, options)
22
+ project_dir = cli.get_project_home
23
+ @project = Project.new(project_dir, owner: ENV['CNVRG_OWNER'], slug: ENV['CNVRG_PROJECT'])
24
+ end
25
+
26
+ desc 'install_reqs', 'Install requirements', :hide => true
27
+ def install_reqs
28
+ cli = Cnvrg::CLI.new
29
+ cli.log_start(__method__, args, options)
30
+ @project = Project.new(nil, owner: ENV['CNVRG_OWNER'], slug: ENV['CNVRG_PROJECT'])
31
+ @executer = Helpers::Executer.new(project: @project, job_type: ENV['CNVRG_JOB_TYPE'], job_id: ENV['CNVRG_JOB_ID'])
32
+ commands = @executer.get_requirements_commands
33
+ @executer.execute_cmds(commands)
34
+ end
35
+
36
+ desc 'start', 'Job Start!', :hide => true
37
+ def start
38
+ cli = Cnvrg::CLI.new
39
+ cli.log_start(__method__, args, options)
40
+ cli.auth
41
+ poll_every = (ENV["CNVRG_AGENT_POLL_EVERY"] || '30').to_i
42
+ owner = ENV["CNVRG_OWNER"]
43
+ machine_activity_slug = ENV["CNVRG_MACHINE_ACTIVITY"]
44
+ job_id = ENV["CNVRG_JOB_ID"]
45
+ @executer = Cnvrg::Helpers::Executer.new(machine_activity: machine_activity_slug, poll_every: poll_every, owner: owner, job_id: job_id)
46
+ @executer.main_thread
47
+ end
48
+
49
+ desc 'stats', 'stats of agent and slave', :hide => true
50
+ def stats
51
+ cli = Cnvrg::CLI.new
52
+ cli.log_start(__method__, args, options)
53
+ poll_every = (ENV["CNVRG_AGENT_POLL_EVERY"] || '30').to_i
54
+ owner = ENV["CNVRG_OWNER"]
55
+ machine_activity_slug = ENV["CNVRG_MACHINE_ACTIVITY"]
56
+ job_id = ENV["CNVRG_JOB_ID"]
57
+ @executer = Cnvrg::Helpers::Executer.new(machine_activity: machine_activity_slug, poll_every: poll_every, owner: owner, job_id: job_id)
58
+ @executer.executer_stats
59
+ end
60
+
61
+ desc 'pre_pod_stop', '', :hide => true
62
+ def pre_pod_stop
63
+ cli = Cnvrg::CLI.new
64
+ cli.log_start(__method__, args, options)
65
+ owner = ENV["CNVRG_OWNER"]
66
+ machine_activity = YAML.load_file("/conf/.machine_activity.yml") rescue {:slug => ENV["CNVRG_MACHINE_ACTIVITY"]}
67
+ machine_activity_slug = machine_activity[:slug]
68
+ job_id = ENV["CNVRG_JOB_ID"]
69
+ @executer = Cnvrg::Helpers::Executer.new(machine_activity: machine_activity_slug, owner: owner, job_id: job_id)
70
+ @executer.pre_pod_stop
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,48 @@
1
+ module Cnvrg
2
+ class JobSsh < SubCommandBase
3
+
4
+ desc 'ssh start', 'stats of agent and slave'
5
+ method_option :port, :type => :numeric, :aliases => ["-p", "--port"], :desc => "Port to bind into", :default => 2222
6
+ method_option :username, :type => :string, :aliases => ["-u", "--username"], :desc => "Job container user name", :default => nil
7
+ method_option :password, :type => :string, :aliases => ["--password"], :desc =>"Job Conatainer user name, will be set by cnvrg", :default => nil
8
+ method_option :kubeconfig, :type => :string, :aliases => ["--kubeconfig"], :desc => "Path to kubeconfig, if blank default config will be used", :default => nil
9
+ def start(job_id)
10
+ Cnvrg::CLI.new.log_start(__method__, args, options)
11
+ @job_ssh = ConnectJobSsh.new(job_id)
12
+ @job_ssh.start(options['username'], options['password'])
13
+ pod_name = nil
14
+ namespace = "cnvrg"
15
+ ssh_ready = false
16
+ while not ssh_ready
17
+ resp = @job_ssh.status()
18
+ status = resp["ssh_status"]
19
+
20
+ if status == "in_progress"
21
+ puts("Waiting for ssh to start ...")
22
+ sleep(3)
23
+ next
24
+ elsif status == "finished"
25
+ password = resp["password"]
26
+ username = resp["username"]
27
+ pod_name = resp["pod_name"]
28
+ namespace = resp["namespace"]
29
+ ssh_ready = true
30
+ else
31
+ puts("Failed to start ssh")
32
+ break
33
+ end
34
+ end
35
+ if pod_name.blank? or password.blank? or username.blank?
36
+ puts("Failed to get required params")
37
+ return
38
+ end
39
+
40
+ puts("In order to connect to your job, define your ssh connection with the following params:")
41
+ puts("host: 127.0.0.1")
42
+ puts("port: #{options["port"]}")
43
+ puts("username: #{username}")
44
+ puts("password: #{password}")
45
+ @job_ssh.run_portforward_command(pod_name, options["port"], options["kubeconfig"], namespace)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,111 @@
1
+ module Cnvrg
2
+ module Logger
3
+ extend self
4
+ def log_handler
5
+ begin
6
+ date = DateTime.now.strftime("%m_%d_%Y")
7
+ home_dir = File.expand_path('~')
8
+
9
+ if !File.directory? home_dir + "/.cnvrg"
10
+ FileUtils.mkdir_p([home_dir + "/.cnvrg", home_dir + "/.cnvrg/tmp"])
11
+ end
12
+ if !File.exist?(home_dir + "/.cnvrg/config.yml")
13
+ FileUtils.touch [home_dir + "/.cnvrg/config.yml"]
14
+ end
15
+ logfile = File.expand_path('~') + "/.cnvrg/log_#{date}.log"
16
+ if !File.exist? logfile
17
+ FileUtils.touch([logfile])
18
+ yesterday = get_start_day - 86399
19
+ date = yesterday.strftime("%m_%d_%Y")
20
+
21
+ logfile_old = File.expand_path('~') + "/.cnvrg/log_#{date}.log"
22
+ count = 0
23
+ while not File.exist? logfile_old and count < 60
24
+ yesterday = yesterday - 86399
25
+ date = yesterday.strftime("%m_%d_%Y")
26
+ logfile_old = File.expand_path('~') + "/.cnvrg/log_#{date}.log"
27
+ count += 1
28
+ end
29
+ if File.exist? logfile_old
30
+ @files = Cnvrg::Files.new(Cnvrg::CLI.get_owner, "")
31
+ @files.upload_log_file(logfile_old, "log_#{date}.log", yesterday)
32
+ FileUtils.remove logfile_old
33
+ end
34
+
35
+ end
36
+ config = LogStashLogger.configure do |config|
37
+ config.customize_event do |event|
38
+ event.remove('@version')
39
+ event.remove('severity')
40
+ end
41
+ end
42
+ $log = LogStashLogger.new(type: :file, path: logfile, sync: true, config: config)
43
+ self.remove_old_log_files
44
+ rescue
45
+ end
46
+ end
47
+
48
+ def remove_old_log_files()
49
+ begin
50
+ last_week = (Time.now - (7 * 24 * 60 * 60)).strftime("%Y-%m-%d")
51
+ home = File.expand_path('~')
52
+ log_files = Dir["#{home}/.cnvrg/tmp/*.log"]
53
+ log_files.each do |l|
54
+ if File.mtime(l).strftime("%Y-%m-%d") < last_week
55
+ FileUtils.rm_rf(l)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def info(msg)
62
+ self.log_info(msg)
63
+ end
64
+
65
+ def log_method(bind: nil)
66
+ return if bind.blank?
67
+ Cnvrg::Logger.log_handler if $log.blank?
68
+ arg = {}
69
+ bind.local_variables.map do |name|
70
+ arg[name] = bind.local_variable_get(name).try(:to_s)
71
+ end
72
+ method = bind.eval('__method__')
73
+ $log.info method: method, args: arg
74
+ end
75
+
76
+ def log_error(e)
77
+ Cnvrg::Logger.log_handler if $log.blank?
78
+ return if $log.blank?
79
+ bc = ActiveSupport::BacktraceCleaner.new
80
+ bc.add_silencer{|line| line =~ /thor/}
81
+ $log.error message: "An exception #{e.class} was logged during running", type: "error"
82
+ backtrace = bc.clean(e.backtrace).slice(0,12).map.with_index{|backtrace,idx| "(#{idx}) - #{backtrace}"}.join("; ")
83
+ $log.error message: e.message, type: "error", backtrace: backtrace
84
+ end
85
+
86
+ def log_error_message(msg)
87
+ Cnvrg::Logger.log_handler if $log.blank?
88
+ return if $log.blank?
89
+ $log.error message: msg, type: "error"
90
+ end
91
+
92
+ def log_info(msg)
93
+ Cnvrg::Logger.log_handler if $log.blank?
94
+ return if $log.blank?
95
+ $log.info message: msg, type: "info"
96
+ end
97
+
98
+ def log_json(json, msg: '')
99
+ Cnvrg::Logger.log_handler if $log.blank?
100
+ return if $log.blank?
101
+ $log.info message: msg, type: "info", data: json
102
+ end
103
+
104
+ def jsonify_message(msg: '', success: true)
105
+ puts JSON[{
106
+ "msg": msg,
107
+ "success": success.to_s
108
+ }]
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,5 @@
1
+ module Cnvrg
2
+ class OrgHelpers
3
+
4
+ end
5
+ end
@@ -0,0 +1,822 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ module Cnvrg
5
+ class Project
6
+ attr_reader :slug, :owner, :title, :local_path, :working_dir, :is_git, :is_branch, :machines
7
+
8
+ RemoteURL ||= "https://cnvrg.io"
9
+ IDXParallelThreads ||= Cnvrg::Helpers.parallel_threads
10
+
11
+
12
+ def initialize(project_home=nil, slug: nil, owner: nil)
13
+ begin
14
+ @local_path = project_home
15
+ @working_dir = project_home
16
+ config = YAML.load_file(project_home + "/.cnvrg/config.yml") rescue {owner: owner, project_slug: slug}
17
+ @title = config[:project_name]
18
+ @slug = config[:project_slug]
19
+ @owner = config[:owner]
20
+ @is_branch = config[:is_branch]
21
+ @is_git = config[:git] || false
22
+ @base_resource = "users/#{@owner}/projects/#{@slug}/"
23
+ @machines = nil
24
+ rescue => e
25
+ end
26
+ end
27
+
28
+ def base_resource
29
+ "users/#{@owner}/projects/#{@slug}/"
30
+ end
31
+
32
+
33
+ def last_local_commit
34
+ YAML.load_file(@local_path + "/.cnvrg/idx.yml")[:commit] rescue nil
35
+ end
36
+
37
+
38
+ def url
39
+ url = Cnvrg::Helpers.remote_url
40
+ "#{url}/#{self.owner}/projects/#{self.slug}"
41
+ end
42
+
43
+ def update_ignore_list(new_ignore)
44
+ if new_ignore.nil? or new_ignore.empty?
45
+ return true
46
+ end
47
+ list = new_ignore.split(",")
48
+ begin
49
+ File.open(self.local_path + "/.cnvrgignore", "a+") do |f|
50
+ f.puts("\n")
51
+ list.each do |i|
52
+ f.puts("#{i}\n")
53
+ end
54
+ end
55
+ return true
56
+ rescue
57
+ return false
58
+ end
59
+ end
60
+
61
+ def get_ignore_list
62
+ ignore_list = []
63
+ if !File.exist? self.local_path + "/.cnvrgignore"
64
+ return ignore_list
65
+ end
66
+ File.open(self.local_path + "/.cnvrgignore", "r").each_line do |line|
67
+ line = line.strip
68
+ if line.start_with? "#" or ignore_list.include? line or line.empty?
69
+ next
70
+ end
71
+ if line.end_with? "*"
72
+ list_regex = Dir.glob("**/#{line}", File::FNM_DOTMATCH).flatten
73
+ list_regex.each do |l|
74
+ ignore_list << l
75
+ if File.directory?(l)
76
+ all_sub = Dir.glob("#{line}/**/*", File::FNM_DOTMATCH).flatten
77
+
78
+ ignore_list << all_sub.flatten
79
+ end
80
+
81
+ end
82
+ elsif line.end_with? "/*"
83
+ line = line.gsub("/*", "")
84
+ regex_list = Dir.glob("**/#{line}/**/*", File::FNM_DOTMATCH).flatten
85
+ ignore_list << regex_list
86
+ elsif line.include? "*"
87
+ regex_list = Dir.glob("**/#{line}").flatten
88
+ ignore_list << regex_list
89
+ elsif line.end_with? "/" or File.directory?(line)
90
+ ignore_list << line
91
+ all_sub = Dir.glob("#{line}/**/*", File::FNM_DOTMATCH).flatten
92
+
93
+ ignore_list << all_sub.flatten
94
+
95
+ else
96
+ ignore_list << line
97
+ end
98
+ end
99
+ return ignore_list.flatten
100
+
101
+ end
102
+
103
+ def send_ignore_list()
104
+ begin
105
+ ignore_list = []
106
+ File.open(self.local_path + "/.cnvrgignore", "r").each_line do |line|
107
+ line = line.strip
108
+ if line.start_with? "#" or ignore_list.include? line or line.empty?
109
+ next
110
+ end
111
+ if line.end_with? "/"
112
+ ignore_list << line.gsub("/", "")
113
+ ignore_list << line + "."
114
+ elsif line.include? "*"
115
+ line = line.gsub("*", ".*")
116
+ ignore_list << line
117
+ else
118
+ ignore_list << line
119
+ end
120
+ end
121
+ return ignore_list.flatten
122
+ rescue
123
+ return []
124
+ end
125
+
126
+ end
127
+
128
+ # Create project
129
+
130
+ def self.create(project_name, clean, with_docker = false, bucket: nil)
131
+ if clean
132
+ list_dirs = [project_name, project_name + "/.cnvrg"]
133
+ else
134
+
135
+ list_dirs = [project_name,
136
+ project_name + "/models",
137
+ project_name + "/notebooks",
138
+ project_name + "/src",
139
+ project_name + "/src/visualizations",
140
+ project_name + "/src/features",
141
+ project_name + "/.cnvrg"
142
+ ]
143
+ end
144
+
145
+
146
+ list_files = [
147
+ project_name + "/README.md",
148
+ project_name + "/.cnvrgignore",
149
+ project_name + "/.cnvrg/config.yml"
150
+ ]
151
+ cnvrgreadme = Helpers.readme_content
152
+ cnvrgignore = Helpers.cnvrgignore_content
153
+ cnvrghyper = Helpers.hyper_content
154
+
155
+ begin
156
+
157
+ owner = Cnvrg::CLI.get_owner()
158
+ response = Cnvrg::API.request("cli/create_project", 'POST', {title: project_name, owner: owner, is_docker: with_docker, bucket: bucket})
159
+ Cnvrg::CLI.is_response_success(response)
160
+ response = JSON.parse response["result"]
161
+ project_slug = response["slug"]
162
+
163
+ config = {project_name: project_name,
164
+ project_slug: project_slug,
165
+ owner: owner,
166
+ docker: with_docker}
167
+ FileUtils.mkdir_p list_dirs
168
+ FileUtils.touch list_files
169
+
170
+ File.open(project_name + "/.cnvrg/config.yml", "w+") {|f| f.write config.to_yaml}
171
+ File.open(project_name + "/.cnvrgignore", "w+") {|f| f.write cnvrgignore}
172
+ File.open(project_name + "/README.md", "w+") {|f| f.write cnvrgreadme}
173
+ File.open(project_name + "/src/hyper.yaml", "w+") {|f| f.write cnvrghyper}
174
+
175
+ rescue
176
+ return false
177
+ end
178
+ return true
179
+ end
180
+
181
+ def self.link(owner, project_name, docker = false, git = false, bucket: nil)
182
+ ignore_exits = File.exist? ".cnvrgignore"
183
+ list_dirs = [".cnvrg"
184
+ ]
185
+ list_files = [
186
+ ".cnvrg/config.yml"
187
+ ]
188
+ if !ignore_exits
189
+ list_files <<
190
+ ".cnvrgignore"
191
+ end
192
+
193
+ cnvrgreadme = Helpers.readme_content
194
+ cnvrgignore = Helpers.cnvrgignore_content
195
+ begin
196
+ response = Cnvrg::API.request("cli/create_project", 'POST', {title: project_name, owner: owner, is_docker: docker, bucket: bucket})
197
+ Cnvrg::CLI.is_response_success(response)
198
+ response = JSON.parse response["result"]
199
+ project_slug = response["slug"]
200
+
201
+ config = {project_name: project_name,
202
+ project_slug: project_slug,
203
+ owner: owner,
204
+ git: git}
205
+ FileUtils.mkdir_p list_dirs
206
+ FileUtils.touch list_files
207
+ File.open(".cnvrg/config.yml", "w+") {|f| f.write config.to_yaml}
208
+ File.open(".cnvrgignore", "w+") {|f| f.write cnvrgignore} unless ignore_exits
209
+ if !File.exist? "README" and !File.exist? "README.md"
210
+ FileUtils.touch ["README.md"]
211
+ File.open("README.md", "w+") {|f| f.write cnvrgreadme}
212
+ end
213
+
214
+ rescue => e
215
+ puts e
216
+ return false
217
+ end
218
+ return true
219
+ end
220
+
221
+ def self.clone_dir(project_slug, project_owner, project_name, is_git = false)
222
+ list_dirs = [project_name,
223
+ project_name + "/.cnvrg"
224
+ ]
225
+
226
+
227
+ list_files = [
228
+ project_name + "/.cnvrg/config.yml",
229
+ project_name + "/.cnvrgignore",
230
+ ]
231
+ begin
232
+ config = {project_name: project_name,
233
+ project_slug: project_slug,
234
+ owner: project_owner,
235
+ git: is_git}
236
+ FileUtils.mkdir_p list_dirs
237
+ FileUtils.touch list_files
238
+ cnvrgignore = Helpers.cnvrgignore_content
239
+
240
+
241
+ File.open(project_name + "/.cnvrg/config.yml", "w+") {|f| f.write config.to_yaml}
242
+ File.open(project_name + "/.cnvrgignore", "w+") {|f| f.write cnvrgignore}
243
+
244
+ rescue
245
+ return false
246
+ end
247
+ return true
248
+ end
249
+
250
+ def self.verify_cnvrgignore_exist(project_name, remote)
251
+ path = ".cnvrgignore"
252
+ if !File.exist? path
253
+ path = "#{project_name}/.cnvrgignore"
254
+ end
255
+ ignore_exits = File.exist? path
256
+ if !ignore_exits
257
+ begin
258
+ list_files = [
259
+ path
260
+ ]
261
+ FileUtils.touch list_files
262
+ cnvrgignore = Helpers.cnvrgignore_content
263
+ File.open(path, "w+") {|f| f.write cnvrgignore}
264
+ rescue => e
265
+ return false
266
+ end
267
+
268
+ end
269
+ end
270
+
271
+ def self.clone_dir_remote(project_slug, project_owner, project_name, is_git = false)
272
+ cli = Cnvrg::CLI.new()
273
+ begin
274
+ list_dirs = [
275
+ ".cnvrg"
276
+ ]
277
+
278
+
279
+ list_files = [
280
+ ".cnvrg/config.yml",
281
+
282
+ ]
283
+
284
+
285
+ config = {project_name: project_name,
286
+ project_slug: project_slug,
287
+ owner: project_owner,
288
+ git: is_git
289
+ }
290
+ FileUtils.mkdir_p list_dirs
291
+ FileUtils.touch list_files
292
+
293
+
294
+ File.open(".cnvrg/config.yml", "w+") {|f| f.write config.to_yaml}
295
+ if !File.exist? ".cnvrgignore"
296
+ FileUtils.touch ".cnvrgignore"
297
+ list_files << ".cnvrgignore"
298
+ cnvrgignore = Helpers.cnvrgignore_content
299
+ File.open(".cnvrgignore", "w+") {|f| f.write cnvrgignore}
300
+
301
+
302
+ end
303
+ true
304
+ rescue => e
305
+ cli.log_message(e.message)
306
+ cli.log_error(e)
307
+ false
308
+ end
309
+ end
310
+
311
+ def update_is_new_branch(new_branch)
312
+ config = YAML.load_file(@working_dir + "/.cnvrg/config.yml")
313
+ config[:new_branch] = new_branch
314
+ File.open(@working_dir + "/.cnvrg/config.yml", "w+") {|f| f.write config.to_yaml}
315
+ end
316
+
317
+ def get_new_branch
318
+ begin
319
+ config = YAML.load_file(@working_dir + "/.cnvrg/config.yml")
320
+ return config[:new_branch]
321
+ rescue => e
322
+ return false
323
+ end
324
+ end
325
+
326
+ def get_config
327
+ YAML.load_file(@working_dir + "/.cnvrg/config.yml") rescue {}
328
+ end
329
+
330
+ def get_storage_client
331
+ response = Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/client", 'GET')
332
+ if Cnvrg::CLI.is_response_success(response, false)
333
+
334
+ client_params = response['client']
335
+ else
336
+
337
+ client_params = get_storage_client_fallback
338
+ end
339
+
340
+ Cnvrg::Downloader::Client.factory(client_params)
341
+ end
342
+
343
+ def get_storage_client_fallback
344
+ response = Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/download_files", "POST", {files: [], commit: get_latest_commit})
345
+ raise StandardError.new("Can't find project credentials") unless Cnvrg::CLI.is_response_success(response, false)
346
+ files = response['result']
347
+ storage = files['is_s3'] ? 's3' : 'minio'
348
+ files['storage'] = storage
349
+ files
350
+ end
351
+
352
+ def get_latest_commit
353
+ resp = clone(0, '')
354
+
355
+ resp['result']['commit']
356
+ end
357
+
358
+ def set_config(config)
359
+ slug = config[:project_slug] rescue nil
360
+ owner = config[:owner] rescue nil
361
+ name = config[:project_name] rescue nil
362
+ if slug.blank? or owner.blank? or name.blank?
363
+ return
364
+ end
365
+ File.open(@working_dir + "/.cnvrg/config.yml", "w+") {|f| f.write config.to_yaml}
366
+ end
367
+
368
+ def remove_new_branch
369
+ config = YAML.load_file(@working_dir + "/.cnvrg/config.yml")
370
+ new_config = config.except(:new_branch)
371
+ File.open(@working_dir + "/.cnvrg/config.yml", "w+") {|f| f.write new_config.to_yaml}
372
+ end
373
+
374
+ def generate_git_diff
375
+ git_diff = `git diff --name-only`
376
+ git_diff.split("\n")
377
+ rescue
378
+ []
379
+ end
380
+
381
+ def generate_output_dir(output_dir)
382
+ Cnvrg::Logger.log_info("Generating output dir for #{output_dir}")
383
+ upload_list = []
384
+ list = Dir.glob("#{output_dir}/**/*", File::FNM_DOTMATCH)
385
+ Parallel.map(list, in_threads: IDXParallelThreads) do |e|
386
+ next if e.end_with? "/."
387
+ if File.directory? e
388
+
389
+ upload_list << e + "/"
390
+ else
391
+ upload_list << e
392
+ end
393
+ end
394
+ if Dir.exists? output_dir
395
+ upload_list << output_dir + "/"
396
+ end
397
+ Cnvrg::Logger.log_info("Uploading: #{upload_list.join(", ")}")
398
+ upload_list
399
+ end
400
+
401
+ def generate_output_dir_tmp(output_dir)
402
+ upload_list = []
403
+ list = Dir.glob("#{output_dir}/**/*", File::FNM_DOTMATCH)
404
+ Parallel.map(list, in_threads: IDXParallelThreads) do |e|
405
+ next if e.end_with? "/."
406
+ if File.directory? e
407
+
408
+ next
409
+ else
410
+ upload_list << e
411
+ end
412
+ end
413
+ # if Dir.exists? output_dir
414
+ # upload_list << output_dir + "/"
415
+ # end
416
+
417
+
418
+ return upload_list
419
+
420
+ end
421
+
422
+ def generate_idx(deploy: false, files: [])
423
+ if File.exists? "#{self.local_path}/.cnvrg/idx.yml"
424
+ old_idx = YAML.load_file("#{self.local_path}/.cnvrg/idx.yml")
425
+ else
426
+ old_idx = {:tree => {}, :commit => nil}
427
+ end
428
+
429
+ tree_idx = Hash.new(0)
430
+
431
+ ### if file specified, just take them, dont calculate everything from scratch
432
+ list_ignore = self.get_ignore_list()
433
+ if files.blank?
434
+ list = Dir.glob("#{self.local_path}/**/*", File::FNM_DOTMATCH).reject {|x| (x =~ /\/\.{1,2}$/) or (x =~ /^#{self.local_path}\/\.cnvrg\/*/) or (x =~ /^#{self.local_path}\/\.git\/*/) or (x =~ /^#{self.local_path}\/\.cnvrgignore.conflict*/) and not (x =~ /^#{self.local_path}\/\.cnvrgignore/)}
435
+ else
436
+ list = files
437
+ end
438
+ if deploy
439
+ list_ignore += ["main.py", "main.pyc", "__init__.py", "uwsgi.ini"]
440
+ list_ignore.flatten!
441
+ end
442
+ list_ignore_new = list_ignore.map{|x| x.gsub("//","/")} rescue []
443
+ # list.each do |e|
444
+ Parallel.map(list, in_threads: IDXParallelThreads) do |e|
445
+ label = e.sub(self.local_path + "/", "")
446
+
447
+ if list_ignore_new.include? label
448
+ next
449
+ end
450
+ if File.directory? e
451
+
452
+ tree_idx[label + "/"] = nil
453
+ else
454
+ file_in_idx = old_idx[:tree][label] rescue nil
455
+ last_modified = File.mtime(e).to_f
456
+ if file_in_idx.present? and last_modified == file_in_idx[:last_modified]
457
+ sha1 = file_in_idx[:sha1]
458
+ else
459
+ sha1 = OpenSSL::Digest::SHA1.file(e).hexdigest
460
+ end
461
+
462
+ if old_idx.nil? or old_idx.to_h[:tree].nil?
463
+ tree_idx[label] = {sha1: sha1, commit_time: nil, last_modified: last_modified}
464
+ elsif file_in_idx.nil? or file_in_idx[:sha1] != sha1 or file_in_idx[:last_modified].blank? or file_in_idx[:last_modified] != last_modified
465
+ tree_idx[label] = {sha1: sha1, commit_time: nil, last_modified: last_modified}
466
+ else
467
+ tree_idx[label] = old_idx[:tree][label]
468
+ end
469
+ end
470
+ end
471
+ old_idx[:tree] = tree_idx
472
+ File.open("#{self.local_path}/.cnvrg/idx.yml", 'w') {|f| f.write old_idx.to_yaml}
473
+ return YAML.load_file("#{self.local_path}/.cnvrg/idx.yml")
474
+ end
475
+
476
+ def get_idx
477
+ unless File.exists? "#{self.local_path}/.cnvrg/idx.yml"
478
+ empty_idx = {:commit => nil, :tree => {}}
479
+ File.open("#{self.local_path}/.cnvrg/idx.yml", 'w') {|f| f.write empty_idx.to_yaml}
480
+ return empty_idx
481
+ end
482
+ YAML.load_file("#{self.local_path}/.cnvrg/idx.yml")
483
+ end
484
+
485
+ def set_idx(idx)
486
+ FileUtils.mkdir_p("#{self.local_path}/.cnvrg")
487
+ File.open("#{self.local_path}/.cnvrg/idx.yml", 'w') {|f| f.write idx.to_yaml}
488
+ end
489
+
490
+ def clone(remote = 0, commit)
491
+ response = Cnvrg::API.request("users/#{self.owner}/projects/#{self.slug}/clone", 'POST', {project_slug: self.slug, remote: remote, commit: commit})
492
+ return response
493
+ end
494
+
495
+ def git_download_commit(commit)
496
+ response = Cnvrg::API.request("users/#{self.owner}/projects/#{self.slug}/git_download_commit", 'POST', {commit_sha1: commit})
497
+ CLI.is_response_success(response, true)
498
+ return response
499
+ end
500
+
501
+ def get_job_last_commit(job_type, job_id)
502
+ base_url = "users/#{self.owner}/projects/#{self.slug}/jobs/#{job_type.underscore}/#{job_id}"
503
+ resp = Cnvrg::API.request("#{base_url}/last_commit", "GET")
504
+ commit = resp["commit"]
505
+ return commit
506
+ end
507
+
508
+ def compare_idx(new_branch, force:false, deploy: false, in_exp:false, specific_files: [], download: false)
509
+ is_download = download
510
+ if is_download
511
+ local_idx = self.get_idx
512
+ else
513
+ #upload
514
+ local_idx = self.generate_idx(deploy: deploy, files: specific_files)
515
+ end
516
+ commit = local_idx[:commit]
517
+ tree = local_idx[:tree]
518
+ ignore_list = self.send_ignore_list()
519
+ if force or specific_files.present?
520
+ added = []
521
+ if tree.present?
522
+ added += local_idx[:tree].keys
523
+ end
524
+ response = {"result" => {"commit" => nil, "tree" => {"added" => added,
525
+ "updated_on_server" => [],
526
+ "updated_on_local" => [],
527
+ "update_local" => [],
528
+ "deleted" => [],
529
+ "conflicts" => []}}}
530
+ return response
531
+ end
532
+ #we dont want to send it on download - we only compare between commits sha1 in download.
533
+ if is_download
534
+ #the new server doesnt need the tree, but the old probably needs :X
535
+ local_idx[:tree] = {} if Cnvrg::Helpers.server_version > 0
536
+ end
537
+ response = Cnvrg::API.request(@base_resource + "status", 'POST', {idx: local_idx, new_branch: new_branch,
538
+ current_commit: commit, ignore: ignore_list, force: force, in_exp: in_exp, download: download})
539
+
540
+ CLI.is_response_success(response, true)
541
+ if is_download
542
+ if Cnvrg::Helpers.server_version > 0
543
+ #trying to optimize the download using resolver
544
+ resolve = response['result']['tree']['resolver'] || tree #tree of file -> sha1 from current commit to check conflicts
545
+ destination_files = response['result']['tree']['destination'] || {} #tree of file -> sha1 from last commit to check files that already downloaded
546
+ else
547
+ resolve = tree
548
+ destination_files = {}
549
+ end
550
+ @files = self.get_files
551
+ local_tree = @files.calculate_sha1(resolve.keys)
552
+ changed_files = resolve.keys.select {|file| local_tree[file] != resolve[file]}
553
+
554
+ # means that the user changed the file locally
555
+ response['result']['tree']['update_local'] = changed_files
556
+
557
+ # means that we already downloaded this file and we dont need it anymore
558
+ downloaded_files = destination_files.keys.select {|file| local_tree[file] == destination_files[file]}
559
+ response['result']['tree']['added'] -= downloaded_files
560
+ response['result']['tree']['updated_on_server'] -= downloaded_files
561
+ end
562
+ Cnvrg::Logger.log_json(response, msg: "Comparing IDX response(tree)")
563
+ return response
564
+ end
565
+
566
+ def get_files
567
+ Cnvrg::Files.new(self.owner, self.slug, project_home: @local_path)
568
+ end
569
+
570
+ def jump_idx(destination: self.last_local_commit)
571
+ local_idx = self.generate_idx
572
+ ignore_list = self.send_ignore_list
573
+ current_commit = local_idx[:commit]
574
+ tree = local_idx[:tree]
575
+ response = Cnvrg::API.request("users/#{self.owner}/projects/#{self.slug}/jump_to",
576
+ 'POST',
577
+ {tree: tree, ignore: ignore_list,
578
+ dest_commit: destination, current_commit: current_commit})
579
+ CLI.is_response_success(response, false)
580
+ response
581
+ end
582
+
583
+ def set_on_branch(is_latest)
584
+ config = YAML.load_file(@working_dir + "/.cnvrg/config.yml")
585
+ @is_branch = !is_latest
586
+ config[:is_branch] = @is_branch
587
+
588
+ File.open(@working_dir + "/.cnvrg/config.yml", 'w') {|f| f.write config.to_yaml}
589
+
590
+ end
591
+
592
+ def compare_commit(commit)
593
+ if commit.nil? or commit.empty?
594
+ commit = last_local_commit
595
+ end
596
+ response = Cnvrg::API.request("users/#{self.owner}/projects/#{self.slug}/commit/compare", 'POST', {current_commit: commit})
597
+ CLI.is_response_success(response, false)
598
+ update_is_new_branch(response["result"]["new_branch"])
599
+ return response["result"]["new_branch"]
600
+ end
601
+
602
+ def update_idx_with_files_commits!(files, commit_time)
603
+
604
+ idx_hash = YAML.load_file("#{self.local_path}/.cnvrg/idx.yml")
605
+ files.each do |path|
606
+ idx_hash[:tree].to_h[path].to_h[:commit_time] = commit_time
607
+ end
608
+ File.open("#{self.local_path}/.cnvrg/idx.yml", 'w') {|f| f.write idx_hash.to_yaml}
609
+
610
+ return true
611
+ end
612
+
613
+ def deploy(file_to_run, function, input_params, commit_to_run, instance_type, image_slug, scheduling_query, local_timestamp, workers, file_input, title)
614
+ response = Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/deploy", 'POST', {file_to_run: file_to_run, function: function,
615
+ image_slug: image_slug, input_params: input_params,
616
+ commit_sha1: commit_to_run,
617
+ instance_type: instance_type,
618
+ scheduling_query: scheduling_query,
619
+ local_timestamp: local_timestamp,
620
+ workers: workers, file_input: file_input,
621
+ title: title})
622
+ return response
623
+ end
624
+
625
+ def list_commits
626
+ response = Cnvrg::API.request("users/#{self.owner}/projects/#{self.slug}/commits/list", 'GET')
627
+ CLI.is_response_success(response)
628
+ return response
629
+ end
630
+
631
+
632
+ def get_experiments
633
+ response = Cnvrg::API.request("users/#{self.owner}/projects/#{self.slug}/experiments/list", 'GET')
634
+ CLI.is_response_success(response)
635
+ return response
636
+ end
637
+
638
+
639
+ def get_experiment(slug)
640
+ response = Cnvrg::API.request("users/#{self.owner}/projects/#{self.slug}/experiments/#{slug}", 'GET')
641
+ response['status']=200
642
+ CLI.is_response_success(response)
643
+ return response
644
+ end
645
+
646
+ def fetch_webapp_slugs(webapp_slug, slugs: nil)
647
+ response = Cnvrg::API_V2.request("#{self.owner}/projects/#{self.slug}/webapps/#{webapp_slug}" , 'GET')
648
+ return response["experiments"]
649
+ rescue
650
+ slugs
651
+ end
652
+
653
+ def update_idx_with_commit!(commit, latest: nil)
654
+ idx_hash = self.get_idx
655
+ idx_hash[:commit] = commit
656
+ self.set_on_branch(latest) unless latest.nil?
657
+
658
+ File.open("#{self.local_path}/.cnvrg/idx.yml", 'w') {|f| f.write idx_hash.to_yaml}
659
+ return true
660
+ end
661
+
662
+ def revert(working_dir)
663
+ FileUtils.rm_rf working_dir
664
+ end
665
+
666
+ def init_machines
667
+ Cnvrg::Logger.log_info("Init machines.")
668
+ resp = Cnvrg::API.request("users/#{@owner}/machines", "GET")
669
+ return unless Cnvrg::CLI.is_response_success(resp, false)
670
+ @machines = resp['result']['machines']
671
+ end
672
+
673
+ def get_machines
674
+ init_machines if @machines.nil?
675
+ @machines = false if @machines.nil?
676
+ @machines || []
677
+ end
678
+
679
+
680
+ def update_job_jupyter_token(job_type, job_id, token)
681
+ owner = self.owner || ENV['CNVRG_OWNER']
682
+ slug = self.slug || ENV['CNVRG_PROJECT']
683
+ base_url = "users/#{owner}/projects/#{slug}/jobs/#{job_type.underscore}/#{job_id}"
684
+ Cnvrg::API.request("#{base_url}/update_jupyter_token", "POST", {token: token})
685
+ end
686
+
687
+ def check_machine(machine)
688
+ Cnvrg::Logger.log_info("Check if #{machine} machine exists")
689
+ machines = get_machines
690
+ return true if machines.blank?
691
+ machines.include? machine
692
+ end
693
+
694
+
695
+ def fetch_project
696
+ resp = Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/get_project", "GET")
697
+ res = JSON.parse(resp['result']) rescue nil
698
+ return if res.blank?
699
+ config = self.get_config
700
+ config[:is_git] = res['git']
701
+ config[:project_name] = res['title']
702
+ self.set_config(config)
703
+ end
704
+
705
+ def job_log(logs, level: 'info', step: nil, job_type: nil, job_id: nil)
706
+ job_type ||= ENV['CNVRG_JOB_TYPE']
707
+ job_id ||= ENV['CNVRG_JOB_ID']
708
+ logs = [logs].flatten
709
+ if job_type.blank? or job_id.blank?
710
+ raise StandardError.new("Cant find job env variables")
711
+ end
712
+ logs.each_slice(10).each do |temp_logs|
713
+ Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/jobs/#{job_type.underscore}/#{job_id}/log", "POST", {job_type: job_type, job_id: job_id, logs: temp_logs, log_level: level, step: step, timestamp: Time.now})
714
+ sleep(1)
715
+ end
716
+ end
717
+
718
+
719
+ def job_commands
720
+ job_type, job_id = ENV['CNVRG_JOB_TYPE'], ENV['CNVRG_JOB_ID']
721
+ resp = Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/jobs/#{job_type.underscore}/#{job_id}/commands", "GET")
722
+ end
723
+
724
+ def spot_will_terminate
725
+ restart = false
726
+ config = self.get_config
727
+ # logic that will prevent multiple restart calls
728
+ if config[:spot_taken]
729
+ return false
730
+ end
731
+ begin
732
+ url = URI.parse('http://169.254.169.254/latest/meta-data/spot/termination-time')
733
+ req = Net::HTTP::Get.new(url.to_s)
734
+ res = Net::HTTP.start(url.host, url.port) {|http|
735
+ http.request(req)
736
+ }
737
+ unless res.body.include? "404"
738
+ restart = true
739
+ end
740
+ if res.body.include? "Empty reply from server"
741
+ restart = false
742
+ end
743
+ rescue
744
+ restart = false
745
+ end
746
+
747
+ if restart
748
+ config[:spot_taken] = true
749
+ self.set_config(config)
750
+ end
751
+
752
+ return restart
753
+ end
754
+
755
+ def send_restart_request(job_id: nil, job_type: nil, ma_id: nil)
756
+ Cnvrg::API.request("#{base_resource}/spot_restart", 'POST', {job_type: job_type, job_id: job_id, machine_activity: ma_id})
757
+ end
758
+
759
+ def get_machine_activity
760
+ begin
761
+ machine_activity = File.open("#{@local_path}/.cnvrg/machine_activity", "rb").read
762
+ machine_activity = machine_activity.to_s.strip
763
+ ma_id = machine_activity.to_i
764
+ return ma_id
765
+ rescue
766
+ return nil
767
+ end
768
+ end
769
+
770
+ def set_job_pod_restart(job_type: nil, job_id: nil)
771
+ job_type ||= ENV['CNVRG_JOB_TYPE']
772
+ job_id ||= ENV['CNVRG_JOB_ID']
773
+ if job_type.blank? or job_id.blank?
774
+ raise StandardError.new("Cant find job env variables")
775
+ end
776
+ Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/jobs/#{job_type.underscore}/#{job_id}/set_pod_restart", "POST", {job_type: job_type, job_id: job_id})
777
+ end
778
+
779
+ def check_job_pod_restart(job_type: nil, job_id: nil)
780
+ job_type ||= ENV['CNVRG_JOB_TYPE']
781
+ job_id ||= ENV['CNVRG_JOB_ID']
782
+ if job_type.blank? or job_id.blank?
783
+ raise StandardError.new("Cant find job env variables")
784
+ end
785
+ resp = Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/jobs/#{job_type.underscore}/#{job_id}/check_pod_restart", "GET", {job_type: job_type, job_id: job_id})
786
+ return [false, false] if resp.blank?
787
+ Cnvrg::Logger.log_info("Checked for pod restart got response #{resp}")
788
+ [resp['project_downloaded'], resp['dataset_downloaded']]
789
+ end
790
+
791
+ def pre_job_pod_restart
792
+ job_type ||= ENV['CNVRG_JOB_TYPE']
793
+ job_id ||= ENV['CNVRG_JOB_ID']
794
+ if job_type.blank? or job_id.blank?
795
+ raise StandardError.new("Cant find job env variables")
796
+ end
797
+ Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/jobs/#{job_type.underscore}/#{job_id}/pre_pod_restart", "POST", {job_type: job_type, job_id: job_id})
798
+ end
799
+
800
+ def set_job_started
801
+ job_type ||= ENV['CNVRG_JOB_TYPE']
802
+ job_id ||= ENV['CNVRG_JOB_ID']
803
+ if job_type.blank? or job_id.blank?
804
+ raise StandardError.new("Cant find job env variables")
805
+ end
806
+ Cnvrg::API.request("users/#{@owner}/projects/#{@slug}/jobs/#{job_type.underscore}/#{job_id}/set_started", "POST", {job_type: job_type, job_id: job_id})
807
+ end
808
+
809
+ def self.stop_if_project_present(project_home, project_name)
810
+ cli = Cnvrg::CLI.new()
811
+ config = YAML.load_file(project_home + "/.cnvrg/config.yml")
812
+ local_commit = YAML.load_file(project_home + "/.cnvrg/idx.yml")[:commit] rescue nil
813
+ return if local_commit.blank?
814
+ if config[:project_name] == project_name
815
+ cli.log_message("Project already present, clone aborted")
816
+ exit(0)
817
+ end
818
+ rescue => e
819
+ nil
820
+ end
821
+ end
822
+ end