cnvrg 0.0.142561 → 0.0.142562

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d2a92149e9b565661704f3f91438b28c89976475
4
- data.tar.gz: 9f5c2dc4a71823c9b5657fd941361e33305d06f6
3
+ metadata.gz: 965ed0f54a906aa040983dd7857810a934e6ddf8
4
+ data.tar.gz: 6ec1122320418d0f0f45d21be77607d3751eb9f5
5
5
  SHA512:
6
- metadata.gz: 0aace663fb6460ade819c6676fb96b11cbbb286340e0b2599b6be6bb6d1ae5f3e6098270b9f37976cae7ec743b6381f46dd1e1054cb4f56e71d249cbb875f43b
7
- data.tar.gz: 47dd9900f83b208196690862ceba2511643fbf84fc2d1daa6a23b66c51c63279aa9766bcaee9811b72af9b4f44e605e7fa34e5b0e450bcbe67f4e62a7480c173
6
+ metadata.gz: 55aa453ff5c88e47c2b189927c6aa99da7c6864e3acafd0d319db8e7cff88db4e3411eead1c26e3cbbcf7b0765d1a82c0ed8a46cf9fb2ea674b2e7acb071a663
7
+ data.tar.gz: 39cc6c68eca8f1eefe319595bc0bca4bd3d9bd77f6c3a1034bf6c065e20b097e8f5ae8cc194a5f8d85fb56fca1dfa90946f8c2c7b00a38253843cdc09db326f5
@@ -40,6 +40,8 @@ Gem::Specification.new do |spec|
40
40
  spec.add_runtime_dependency 'rubyzip', '~> 1.2'
41
41
  spec.add_runtime_dependency 'activesupport', '~> 5.0'
42
42
  spec.add_runtime_dependency 'ruby-progressbar'
43
+ spec.add_runtime_dependency 'net-ssh'
44
+
43
45
 
44
46
 
45
47
 
@@ -3,6 +3,5 @@ require 'cnvrg/version'
3
3
  require 'cnvrg/cli'
4
4
  require 'thor'
5
5
 
6
-
7
6
  module Cnvrg
8
7
  end
@@ -1,6 +1,8 @@
1
1
  require 'fileutils'
2
2
  require 'cnvrg/files'
3
3
  require 'docker'
4
+ require 'net/ssh'
5
+
4
6
 
5
7
  module Cnvrg
6
8
  class Images
@@ -93,6 +95,73 @@ module Cnvrg
93
95
 
94
96
 
95
97
  end
98
+ def self.create_new_custom_image(type,owner,image_name,is_public,is_base,image_extend,python3)
99
+ response = Cnvrg::API.request("users/#{owner}/images/custom", 'POST', {instance_type:type,image_name:image_name,is_public:is_public,
100
+ is_base:is_base,image_extend:image_extend,
101
+ python3:python3})
102
+ return response
103
+ end
104
+ def self.revoke_custom_new_image(owner,slug)
105
+ response = Cnvrg::API.request("users/#{owner}/images/#{slug}/revoke_image", 'GET')
106
+ return response
107
+ end
108
+ def self.commit_custom_image(owner,slug)
109
+ response = Cnvrg::API.request("users/#{owner}/images/#{slug}/commit_custom_image", 'GET')
110
+ return response
111
+ end
112
+ def self.ssh_to_machine(resp)
113
+
114
+ sts_path = resp["result"]["sts_path"]
115
+
116
+ uri = URI.parse(sts_path)
117
+
118
+ http_object = Net::HTTP.new(uri.host, uri.port)
119
+ http_object.use_ssl = true if uri.scheme == 'https'
120
+ request = Net::HTTP::Get.new(sts_path)
121
+
122
+ body = ""
123
+ http_object.start do |http|
124
+ response = http.request request
125
+ body = response.read_body
126
+ end
127
+
128
+ URLcrypt::key = [body].pack('H*')
129
+
130
+ ip = URLcrypt.decrypt(resp["result"]["machine_i"])
131
+
132
+ user = URLcrypt.decrypt(resp["result"]["machine_u"])
133
+ key = URLcrypt.decrypt(resp["result"]["machine_k"])
134
+ tempssh = Tempfile.new "sshkey"
135
+ tempssh.write open(key).read
136
+ tempssh.rewind
137
+ key_path = tempssh.path
138
+ count = 0
139
+ while count < 5
140
+
141
+ begin
142
+ ssh = Net::SSH.start(ip, user=user, :keys => key_path, :timeout => 10)
143
+ if !ssh.nil?
144
+ return ssh
145
+ else
146
+ count+=1
147
+ sleep(2)
148
+
149
+ end
150
+ rescue
151
+ count+=1
152
+ sleep(2)
153
+
154
+
155
+ end
156
+ end
157
+ if tempssh
158
+ tempssh.close
159
+ tempssh.unlink
160
+ end
161
+ return false
162
+ end
163
+
164
+
96
165
 
97
166
  def create_custom_image(new_image_name,working_dir,stored_commands)
98
167
 
@@ -57,7 +57,10 @@ module Cnvrg
57
57
  else
58
58
  response
59
59
  end
60
- when 'POST'
60
+ when 'POST'
61
+ conn.options.timeout = 420
62
+ conn.options.open_timeout =420
63
+
61
64
  response = conn.post "#{endpoint_uri}/#{resource}", data
62
65
 
63
66
  if parse_request == true
@@ -70,12 +73,12 @@ module Cnvrg
70
73
  fr.headers['Auth-Token'] = @pass
71
74
  fr.headers['User-Agent'] = "#{Cnvrg::API::USER_AGENT}"
72
75
  fr.headers["Content-Type"] = "multipart/form-data"
73
-
76
+
74
77
  fr.request :multipart
75
78
  fr.request :url_encoded
76
79
  fr.adapter :net_http
77
80
  end
78
-
81
+
79
82
 
80
83
  # what if windows?
81
84
  # data[:file] = Faraday::UploadIO.new(data[:absolute_path], content_type)
@@ -104,6 +107,7 @@ module Cnvrg
104
107
  else
105
108
  end
106
109
  rescue => e
110
+ puts e
107
111
  return nil
108
112
  end
109
113
 
@@ -21,6 +21,7 @@ require 'cnvrg/Images'
21
21
  require 'cnvrg/dataset'
22
22
  require 'cnvrg/datafiles'
23
23
  require 'cnvrg/data'
24
+ require 'cnvrg/ssh'
24
25
  require 'etc'
25
26
  require 'logstash-logger'
26
27
  require 'cnvrg/job'
@@ -1154,8 +1155,9 @@ module Cnvrg
1154
1155
  method_option :ignore, :type => :array, :aliases => ["-i", "--i"], :desc => "ignore following files"
1155
1156
  method_option :verbose, :type => :boolean, :aliases => ["-v"], :default => false
1156
1157
  method_option :sync, :type => :boolean, :aliases => ["-s"], :default => false
1158
+ method_option :no_compression, :type => :boolean, :aliases => ["-nc", "--no_compression"], :default => false
1157
1159
 
1158
- def upload_data_tar(ignore, verbose, sync)
1160
+ def upload_data_tar(ignore, verbose, sync,no_compression)
1159
1161
 
1160
1162
  begin
1161
1163
  verify_logged_in(true)
@@ -1225,7 +1227,7 @@ module Cnvrg
1225
1227
  tar_files_path = "#{home_dir}/.cnvrg/tmp/#{@dataset.slug}_#{commit_sha1}.txt"
1226
1228
  tar_files = (result["added"] + result["updated_on_local"]).join("\n")
1227
1229
  File.open(tar_files_path, 'w') { |f| f.write tar_files }
1228
- is_tar = create_tar(dataset_dir, tar_path, tar_files_path)
1230
+ is_tar = create_tar(dataset_dir, tar_path, tar_files_path,no_compression)
1229
1231
  if !is_tar
1230
1232
  say "ERROR: Couldn't compress data", Thor::Shell::Color::RED
1231
1233
  FileUtils.rm_rf([tar_path]) if File.exist? tar_path
@@ -3541,7 +3543,6 @@ module Cnvrg
3541
3543
  container.stop()
3542
3544
  end
3543
3545
  rescue SignalException
3544
- log_End(-1)
3545
3546
  if container
3546
3547
  container.stop()
3547
3548
  end
@@ -3551,6 +3552,98 @@ module Cnvrg
3551
3552
 
3552
3553
  end
3553
3554
 
3555
+ method_option :small, :type => :boolean, :aliases => ["-sm", "--small"], :default => false
3556
+ method_option :medium, :type => :boolean, :aliases => ["-md", "--medium"], :default => false
3557
+ method_option :large, :type => :boolean, :aliases => ["-lg", "--large"], :default => false
3558
+ method_option :gpu, :type => :boolean, :aliases => ["--gpu"], :default => false
3559
+ method_option :gpuxl, :type => :boolean, :aliases => ["--gpuxl"], :default => false
3560
+ method_option :gpuxxl, :type => :boolean, :aliases => ["--gpuxxl"], :default => false
3561
+ method_option :image, :type => :string, :aliases => ["-i","--image"], :default => ""
3562
+ method_option :public, :type => :boolean, :aliases => ["-p","--public"], :default => false
3563
+ method_option :base, :type => :boolean, :aliases => ["-b","--base"], :default => false
3564
+ method_option :python3, :type => :boolean, :aliases => ["--python3"], :default => false
3565
+
3566
+ desc 'create_custom_image', 'run commands inside containers', :hide=>true
3567
+
3568
+ def create_custom_image(image_name)
3569
+ begin
3570
+ verify_logged_in(false)
3571
+ log_start(__method__, args, options)
3572
+ instances = {"small" => options["small"], "medium" => options["medium"], "large" => options["large"],
3573
+ "gpu" => options["gpu"], "gpuxl" => options["gpuxl"], "gpuxxl" => options["gpuxxl"]}
3574
+ instance_type = get_instance_type(instances)
3575
+ image_extend = options["image"]
3576
+ public = options["public"]
3577
+ base = options["base"]
3578
+ python3 = options["python3"]
3579
+ owner = CLI.get_owner
3580
+ checks = Helpers.checkmark()
3581
+
3582
+ say "Creating machine for custom image, this may take a few moments...", Thor::Shell::Color::BLUE
3583
+
3584
+ resp = Images.create_new_custom_image(instance_type,owner,image_name,public,base,image_extend,python3)
3585
+ if Cnvrg::CLI.is_response_success(resp,false)
3586
+ image_slug = resp["result"]["slug"]
3587
+ container = resp["result"]["machine_c"]
3588
+ say "#{checks} Created image and machine successfully", Thor::Shell::Color::GREEN
3589
+ say "Connecting to machine", Thor::Shell::Color::BLUE
3590
+ ssh = Ssh.new(resp)
3591
+ if !ssh.is_ssh
3592
+ say "Couldn't connect to machine,aborting", Thor::Shell::Color::RED
3593
+ Images.revoke_custom_new_image(owner, image_slug)
3594
+ log_end(-1,"Couldn't connect to machine,aborting")
3595
+ end
3596
+ say "run command until ctrl + c initiates", Thor::Shell::Color::BLUE
3597
+ begin
3598
+
3599
+ while true
3600
+ command = ask("$>")
3601
+ puts ssh.exec_command(command)
3602
+ end
3603
+
3604
+ rescue SignalException
3605
+ say "Commiting Image..", Thor::Shell::Color::BLUE
3606
+
3607
+ end
3608
+ resp = Images.commit_custom_image(owner,image_slug)
3609
+ if Cnvrg::CLI.is_response_success(resp,false)
3610
+ say "#{checks} Image commited successfuly, email will be sent when image is ready", Thor::Shell::Color::GREEN
3611
+ end
3612
+ if ssh
3613
+ ssh.close_ssh()
3614
+ end
3615
+
3616
+
3617
+
3618
+
3619
+
3620
+
3621
+
3622
+ end
3623
+ rescue =>e
3624
+ puts e
3625
+ if image_slug
3626
+ Images.revoke_custom_new_image(owner, image_slug)
3627
+ end
3628
+ if ssh
3629
+ ssh.close_ssh()
3630
+ end
3631
+
3632
+
3633
+ rescue SignalException
3634
+ if image_slug
3635
+ Images.revoke_custom_new_image(owner, image_slug)
3636
+ end
3637
+ if ssh
3638
+ ssh.close_ssh
3639
+ end
3640
+ say "\nAborting"
3641
+ exit(1)
3642
+ end
3643
+
3644
+ end
3645
+
3646
+
3554
3647
  desc 'build', 'run commands inside containers'
3555
3648
  method_option :install, :type => :string, :aliases => ["--i"], :default => nil, :desc => "Install from the given instructions file"
3556
3649
 
@@ -4952,10 +5045,14 @@ module Cnvrg
4952
5045
  end
4953
5046
  end
4954
5047
 
4955
- def create_tar(path_in, path_out, tar_files)
5048
+ def create_tar(path_in, path_out, tar_files,no_compression=false)
4956
5049
  #The cd is meant for cases when running cnvrg data uplaod not in the main folder
4957
5050
  begin
4958
- `cd #{path_in} && tar -czf #{path_out} -T #{tar_files}`
5051
+ if no_compression
5052
+ `cd #{path_in} && tar -cf #{path_out} -T #{tar_files}`
5053
+ else
5054
+ `cd #{path_in} && tar -czf #{path_out} -T #{tar_files}`
5055
+ end
4959
5056
  rescue => e
4960
5057
  puts "Exception while compressing data: #{e.message}"
4961
5058
  end
@@ -11,7 +11,9 @@ class SubCommandBase < Thor
11
11
  end
12
12
 
13
13
  module Cnvrg
14
+
14
15
  class Data < SubCommandBase
16
+ class_option :no_compression, :type => :boolean, :aliases => ["-nc", "--no_compression"], :default => false
15
17
  desc "data init", "init data folder"
16
18
  method_option :public, :type => :boolean, :aliases => ["-p", "--public"], :default => false
17
19
  def init
@@ -23,13 +25,15 @@ module Cnvrg
23
25
  method_option :ignore, :type => :array, :aliases => ["-i", "--i"], :desc => "ignore following files"
24
26
  method_option :verbose, :type => :boolean, :aliases => ["-v"], :default => false
25
27
  method_option :sync, :type => :boolean, :aliases => ["-s"], :default => false
28
+ method_option :no_compression, :type => :boolean, :aliases => ["-nc", "--no_compression"], :default => false
29
+
26
30
  def upload
27
31
  cli = Cnvrg::CLI.new()
28
32
  ignore = options["ignore"]
29
33
  verbose = options["verbose"]
30
34
  sync = options["sync"]
31
-
32
- cli.upload_data_tar(ignore, verbose,sync)
35
+ no_compression = options["no_compression"]
36
+ cli.upload_data_tar(ignore, verbose,sync,no_compression)
33
37
  end
34
38
  desc 'data download', 'pull data'
35
39
  method_option :verbose, :type => :boolean, :aliases => ["-v"], :default => false
@@ -234,7 +234,7 @@ module Cnvrg
234
234
  def upload_large_files_s3(upload_resp, file_path)
235
235
  begin
236
236
  sts_path = upload_resp["result"]["path_sts"]
237
- s4cmd_path = upload_resp["result"]["path_s4cmd"]
237
+ # s4cmd_path = upload_resp["result"]["path_s4cmd"]
238
238
 
239
239
  uri = URI.parse(sts_path)
240
240
  http_object = Net::HTTP.new(uri.host, uri.port)
@@ -266,46 +266,45 @@ module Cnvrg
266
266
  s3 = Aws::S3::Resource.new(
267
267
  :access_key_id => URLcrypt.decrypt(upload_resp["result"]["sts_a"]),
268
268
  :secret_access_key => URLcrypt.decrypt(upload_resp["result"]["sts_s"]),
269
- :session_token => URLcrypt.decrypt(upload_resp["result"]["sts_st"]),
270
269
  :region => URLcrypt.decrypt(upload_resp["result"]["region"]))
271
270
  resp = s3.bucket(URLcrypt.decrypt(upload_resp["result"]["bucket"])).
272
271
  object(upload_resp["result"]["path"]+"/"+File.basename(file_path)).
273
272
  upload_file(file_path,{:use_accelerate_endpoint=>true})
274
273
 
275
- else
276
-
277
- s4cmd_uri = URI.parse(s4cmd_path)
278
- s4cmd_http_object = Net::HTTP.new(s4cmd_uri.host, s4cmd_uri.port)
279
- s4cmd_http_object.use_ssl = true if s4cmd_uri.scheme == 'https'
280
- s4cmd_request = Net::HTTP::Get.new(s4cmd_path)
281
-
282
- s4cmd_body = ""
283
- s4cmd_http_object.start do |http|
284
- response = http.request s4cmd_request
285
- s4cmd_body = response.read_body
286
- end
287
-
288
- s4cmd_new_body = s4cmd_body.gsub(" self.client = self.boto3.client('s3',
289
- aws_access_key_id=aws_access_key_id,
290
- aws_secret_access_key=aws_secret_access_key)"," self.client = self.boto3.client('s3',
291
- aws_access_key_id='#{ URLcrypt.decrypt(upload_resp["result"]["sts_a"])}',
292
- aws_secret_access_key='#{URLcrypt.decrypt(upload_resp["result"]["sts_s"])}',
293
- aws_session_token='#{URLcrypt.decrypt(upload_resp["result"]["sts_st"])}')")
294
-
295
- tmp = Tempfile.new('s4cmd.py')
296
- tmp << s4cmd_new_body
297
- tmp.flush
298
- tmp.close
299
-
300
- is_success = false
301
- count = 0
302
- while !is_success and count <3
303
- resp = `python #{tmp.path} --num-threads=8 --max-singlepart-upload-size=#{MULTIPART_SPLIT} put -f #{file_path} s3://#{URLcrypt.decrypt(upload_resp["result"]["bucket"])}/#{upload_resp["result"]["path"]+"/"+File.basename(file_path)} > /dev/null 2>&1`
304
- is_success =$?.success?
305
- count +=1
306
-
307
- end
308
- resp= is_success
274
+ # else
275
+
276
+ # s4cmd_uri = URI.parse(s4cmd_path)
277
+ # s4cmd_http_object = Net::HTTP.new(s4cmd_uri.host, s4cmd_uri.port)
278
+ # s4cmd_http_object.use_ssl = true if s4cmd_uri.scheme == 'https'
279
+ # s4cmd_request = Net::HTTP::Get.new(s4cmd_path)
280
+ #
281
+ # s4cmd_body = ""
282
+ # s4cmd_http_object.start do |http|
283
+ # response = http.request s4cmd_request
284
+ # s4cmd_body = response.read_body
285
+ # end
286
+ #
287
+ # s4cmd_new_body = s4cmd_body.gsub(" self.client = self.boto3.client('s3',
288
+ # aws_access_key_id=aws_access_key_id,
289
+ # aws_secret_access_key=aws_secret_access_key)"," self.client = self.boto3.client('s3',
290
+ # aws_access_key_id='#{ URLcrypt.decrypt(upload_resp["result"]["sts_a"])}',
291
+ # aws_secret_access_key='#{URLcrypt.decrypt(upload_resp["result"]["sts_s"])}',
292
+ # aws_session_token='#{URLcrypt.decrypt(upload_resp["result"]["sts_st"])}')")
293
+ #
294
+ # tmp = Tempfile.new('s4cmd.py')
295
+ # tmp << s4cmd_new_body
296
+ # tmp.flush
297
+ # tmp.close
298
+ #
299
+ # is_success = false
300
+ # count = 0
301
+ # while !is_success and count <3
302
+ # resp = `python #{tmp.path} --num-threads=8 --max-singlepart-upload-size=#{MULTIPART_SPLIT} put -f #{file_path} s3://#{URLcrypt.decrypt(upload_resp["result"]["bucket"])}/#{upload_resp["result"]["path"]+"/"+File.basename(file_path)} > /dev/null 2>&1`
303
+ # is_success =$?.success?
304
+ # count +=1
305
+ #
306
+ # end
307
+ # resp= is_success
309
308
 
310
309
  end
311
310
 
@@ -0,0 +1,95 @@
1
+ require 'fileutils'
2
+ require 'cnvrg/files'
3
+ require 'docker'
4
+ require 'net/ssh'
5
+
6
+
7
+ module Cnvrg
8
+ class Ssh
9
+ attr_reader :is_ssh
10
+
11
+
12
+ def initialize(resp)
13
+ begin
14
+ @is_ssh = false
15
+ sts_path = resp["result"]["sts_path"]
16
+
17
+ uri = URI.parse(sts_path)
18
+
19
+ http_object = Net::HTTP.new(uri.host, uri.port)
20
+ http_object.use_ssl = true if uri.scheme == 'https'
21
+ request = Net::HTTP::Get.new(sts_path)
22
+
23
+ body = ""
24
+ http_object.start do |http|
25
+ response = http.request request
26
+ body = response.read_body
27
+ end
28
+
29
+ URLcrypt::key = [body].pack('H*')
30
+ ip = URLcrypt.decrypt(resp["result"]["machine_i"])
31
+
32
+ @user = URLcrypt.decrypt(resp["result"]["machine_u"])
33
+ key = URLcrypt.decrypt(resp["result"]["machine_k"])
34
+ @container = URLcrypt.decrypt(resp["result"]["machine_c"])
35
+
36
+ tempssh = Tempfile.new "sshkey"
37
+ tempssh.write open(key).read
38
+ tempssh.rewind
39
+ key_path = tempssh.path
40
+ count = 0
41
+ while count < 5
42
+
43
+ begin
44
+ @ssh = Net::SSH.start(ip, user=@user, :keys => key_path, :timeout => 10)
45
+ if !@ssh.nil?
46
+ @is_ssh = true
47
+ return
48
+ else
49
+ count+=1
50
+ sleep(2)
51
+
52
+ end
53
+ rescue
54
+ count+=1
55
+ sleep(2)
56
+
57
+
58
+ end
59
+ end
60
+ if tempssh
61
+ tempssh.close
62
+ tempssh.unlink
63
+ end
64
+ return false
65
+ rescue => e
66
+
67
+ puts e
68
+ end
69
+ end
70
+
71
+
72
+
73
+ def exec_command(command)
74
+ exec_command = "sudo -i -u #{@user} cnvrg exec_container #{@container} \"#{command}\" "
75
+ return @ssh.exec!(exec_command)
76
+ end
77
+
78
+
79
+ def close_ssh()
80
+
81
+
82
+ begin
83
+
84
+ @ssh.close
85
+ rescue => e
86
+
87
+ end
88
+
89
+
90
+ end
91
+ end
92
+
93
+
94
+
95
+ end
@@ -1,5 +1,5 @@
1
1
  module Cnvrg
2
- VERSION = '0.0.142561'
2
+ VERSION = '0.0.142562'
3
3
 
4
4
  end
5
5
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cnvrg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.142561
4
+ version: 0.0.142562
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yochay Ettun
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-07-09 00:00:00.000000000 Z
12
+ date: 2017-07-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -329,6 +329,20 @@ dependencies:
329
329
  - - ">="
330
330
  - !ruby/object:Gem::Version
331
331
  version: '0'
332
+ - !ruby/object:Gem::Dependency
333
+ name: net-ssh
334
+ requirement: !ruby/object:Gem::Requirement
335
+ requirements:
336
+ - - ">="
337
+ - !ruby/object:Gem::Version
338
+ version: '0'
339
+ type: :runtime
340
+ prerelease: false
341
+ version_requirements: !ruby/object:Gem::Requirement
342
+ requirements:
343
+ - - ">="
344
+ - !ruby/object:Gem::Version
345
+ version: '0'
332
346
  description: A CLI tool for interacting with cnvrg.io.
333
347
  email:
334
348
  - info@cnvrg.io
@@ -353,6 +367,7 @@ files:
353
367
  - lib/cnvrg/job.rb
354
368
  - lib/cnvrg/project.rb
355
369
  - lib/cnvrg/runner.rb
370
+ - lib/cnvrg/ssh.rb
356
371
  - lib/cnvrg/version.rb
357
372
  homepage: https://cnvrg.io
358
373
  licenses: []