Merge branch 'master' into 3055-advanced-tab-explanations
[arvados.git] / sdk / cli / bin / arv
index 4ce0940f6e06a58f36fd237423f247849f461aa0..e84150a35d9b4064264393cdbab6e2e5312bc8f7 100755 (executable)
@@ -56,14 +56,20 @@ class Google::APIClient
    return @discovery_documents["#{api}:#{version}"] ||=
      begin
        # fetch new API discovery doc if stale
    return @discovery_documents["#{api}:#{version}"] ||=
      begin
        # fetch new API discovery doc if stale
-       cached_doc = File.expand_path '~/.cache/arvados/discovery_uri.json'
-       if not File.exist?(cached_doc) or (Time.now - File.mtime(cached_doc)) > 86400
+       cached_doc = File.expand_path '~/.cache/arvados/discovery_uri.json' rescue nil
+
+       if cached_doc.nil? or not File.exist?(cached_doc) or (Time.now - File.mtime(cached_doc)) > 86400
          response = self.execute!(:http_method => :get,
                                   :uri => self.discovery_uri(api, version),
                                   :authenticated => false)
          response = self.execute!(:http_method => :get,
                                   :uri => self.discovery_uri(api, version),
                                   :authenticated => false)
-         FileUtils.makedirs(File.dirname cached_doc)
-         File.open(cached_doc, 'w') do |f|
-           f.puts response.body
+
+         begin
+           FileUtils.makedirs(File.dirname cached_doc)
+           File.open(cached_doc, 'w') do |f|
+             f.puts response.body
+           end
+         rescue
+           return JSON.load response.body
          end
        end
 
          end
        end
 
@@ -85,8 +91,8 @@ end
 def init_config
   # read authentication data from arvados configuration file if present
   lineno = 0
 def init_config
   # read authentication data from arvados configuration file if present
   lineno = 0
-  config_file = File.expand_path('~/.config/arvados/settings.conf')
-  if File.exist? config_file then
+  config_file = File.expand_path('~/.config/arvados/settings.conf') rescue nil
+  if not config_file.nil? and File.exist? config_file then
     File.open(config_file, 'r').each do |line|
       lineno = lineno + 1
       # skip comments
     File.open(config_file, 'r').each do |line|
       lineno = lineno + 1
       # skip comments
@@ -125,7 +131,15 @@ def check_subcommands client, arvados, subcommand, global_opts, remaining_opts
     end
     abort
   when 'pipeline'
     end
     abort
   when 'pipeline'
-    exec `which arv-run-pipeline-instance`.strip, *remaining_opts
+    sub = remaining_opts.shift
+    if sub == 'run'
+      exec `which arv-run-pipeline-instance`.strip, *remaining_opts
+    else
+      puts "Usage: arv pipeline [method] [--parameters]\n"
+      puts "Use 'arv pipeline [method] --help' to get more information about specific methods.\n\n"
+      puts "Available methods: run"
+    end
+    abort
   when 'tag'
     exec `which arv-tag`.strip, *remaining_opts
   when 'ws'
   when 'tag'
     exec `which arv-tag`.strip, *remaining_opts
   when 'ws'
@@ -135,14 +149,19 @@ def check_subcommands client, arvados, subcommand, global_opts, remaining_opts
   end
 end
 
   end
 end
 
+def arv_edit_save_tmp tmp
+  FileUtils::cp tmp.path, tmp.path + ".saved"
+  puts "Saved contents to " + tmp.path + ".saved"
+end
+
 def arv_edit client, arvados, global_opts, remaining_opts
 def arv_edit client, arvados, global_opts, remaining_opts
-  n = remaining_opts.shift
-  if n.nil? or n == "-h" or n == "--help"
+  uuid = remaining_opts.shift
+  if uuid.nil? or uuid == "-h" or uuid == "--help"
     puts head_banner
     puts "Usage: arv edit [uuid] [fields...]\n\n"
     puts head_banner
     puts "Usage: arv edit [uuid] [fields...]\n\n"
-    puts "Fetchs the specified Arvados object, select the specified fields, and\n"
+    puts "Fetch the specified Arvados object, select the specified fields, \n"
     puts "open an interactive text editor on a text representation (json or\n"
     puts "open an interactive text editor on a text representation (json or\n"
-    puts "yaml, use --format) and then updates the object.  Will use 'nano'\n"
+    puts "yaml, use --format) and then update the object.  Will use 'nano'\n"
     puts "by default, customize with the EDITOR or VISUAL environment variable.\n"
     exit 255
   end
     puts "by default, customize with the EDITOR or VISUAL environment variable.\n"
     exit 255
   end
@@ -154,9 +173,13 @@ def arv_edit client, arvados, global_opts, remaining_opts
 
   # determine controller
 
 
   # determine controller
 
-  m = /([a-z0-9]{5})-([a-z0-9]{5})-([a-z0-9]{15})/.match n
+  m = /([a-z0-9]{5})-([a-z0-9]{5})-([a-z0-9]{15})/.match uuid
   if !m
   if !m
-    abort puts "#{n} does not appear to be an arvados uuid"
+    if /^[a-f0-9]{32}/.match uuid
+      abort "Arvados collections are not editable."
+    else
+      abort "#{n} does not appear to be an Arvados uuid"
+    end
   end
 
   rsc = nil
   end
 
   rsc = nil
@@ -175,7 +198,7 @@ def arv_edit client, arvados, global_opts, remaining_opts
   api_method = 'arvados.' + rsc + '.get'
 
   result = client.execute(:api_method => eval(api_method),
   api_method = 'arvados.' + rsc + '.get'
 
   result = client.execute(:api_method => eval(api_method),
-                          :parameters => {"uuid" => n},
+                          :parameters => {"uuid" => uuid},
                           :authenticated => false,
                           :headers => {
                             authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN']
                           :authenticated => false,
                           :headers => {
                             authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN']
@@ -201,42 +224,79 @@ def arv_edit client, arvados, global_opts, remaining_opts
 
   require 'tempfile'
 
 
   require 'tempfile'
 
-  tmp = Tempfile.new(n)
+  tmp = Tempfile.new([uuid, "." + global_opts[:format]])
   tmp.write(content)
   tmp.close
 
   tmp.write(content)
   tmp.close
 
-  pid = Process::fork
-  if pid.nil?
-    editor ||= ENV["VISUAL"]
-    editor ||= ENV["EDITOR"]
-    editor ||= "nano"
-    exec editor, tmp.path
-  else
-    Process.wait pid
-  end
+  need_edit = true
+
+  while need_edit
+    pid = Process::fork
+    if pid.nil?
+      editor ||= ENV["VISUAL"]
+      editor ||= ENV["EDITOR"]
+      editor ||= "nano"
+      exec editor, tmp.path
+    else
+      Process.wait pid
+    end
 
 
-  if $?.exitstatus == 0
-    tmp.open
-    newcontent = tmp.read()
+    if $?.exitstatus == 0
+      tmp.open
+      newcontent = tmp.read()
 
 
-    newobj = {}
-    case global_opts[:format]
-    when 'json'
-      newobj = Oj.load(newcontent)
-    when 'yaml'
-      newobj = YAML.load(newcontent)
+      newobj = {}
+      begin
+        case global_opts[:format]
+        when 'json'
+          newobj = Oj.load(newcontent)
+        when 'yaml'
+          newobj = YAML.load(newcontent)
+        end
+        need_edit = false
+      rescue Exception => e
+        puts "Parse error! " + e.to_s
+        n = 1
+        newcontent.each_line do |line|
+          puts "#{n.to_s.rjust 4}  #{line}"
+          n += 1
+        end
+        puts "\nTry again (y/n)? "
+        yn = "X"
+        while not ["y", "Y", "n", "N"].include?(yn)
+          yn = $stdin.read 1
+        end
+        if yn == 'n' or yn == 'N'
+          arv_edit_save_tmp tmp
+          abort
+        end
+      end
+    else
+      puts "Editor exited with status #{$?.exitstatus}"
+      exit $?.exitstatus
     end
     end
-    tmp.close
-    tmp.unlink
+  end
 
 
+  begin
     if newobj != results
       api_method = 'arvados.' + rsc + '.update'
     if newobj != results
       api_method = 'arvados.' + rsc + '.update'
-      result = client.execute(:api_method => eval(api_method),
-                              :parameters => {"uuid" => n, rsc.singularize => Oj.dump(newobj)},
-                              :authenticated => false,
-                              :headers => {
-                                authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN']
-                              })
+      dumped = Oj.dump(newobj)
+
+      begin
+        result = client.execute(:api_method => eval(api_method),
+                                :parameters => {"uuid" => uuid},
+                                :body => { rsc.singularize => dumped },
+                                :authenticated => false,
+                                :headers => {
+                                  authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN']
+                                })
+      rescue Exception => e
+        puts "Error communicating with server, error was #{e}"
+        puts "Update body was:"
+        puts dumped
+        arv_edit_save_tmp tmp
+        abort
+      end
 
       begin
         results = JSON.parse result.body
 
       begin
         results = JSON.parse result.body
@@ -246,10 +306,16 @@ def arv_edit client, arvados, global_opts, remaining_opts
 
       if result.response.status != 200
         puts "Update failed.  Server responded #{result.response.status}: #{results['errors']} "
 
       if result.response.status != 200
         puts "Update failed.  Server responded #{result.response.status}: #{results['errors']} "
+        puts "Update body was:"
+        puts dumped
+        arv_edit_save_tmp tmp
+        abort
       end
     else
       puts "Object is unchanged, did not update."
     end
       end
     else
       puts "Object is unchanged, did not update."
     end
+  ensure
+    tmp.close(true)
   end
 
   exit 0
   end
 
   exit 0
@@ -352,7 +418,8 @@ def parse_arguments(discovery_document, subcommands)
   resource = ARGV.shift
 
   if not subcommands.include? resource
   resource = ARGV.shift
 
   if not subcommands.include? resource
-    if global_opts[:resources] or not resource_types.include?(resource)
+    if not resource_types.include?(resource)
+      puts "Resource or subcommand '#{resource}' is not recognized.\n\n" if !resource.nil?
       help_resources(option_parser, discovery_document, resource)
     end
 
       help_resources(option_parser, discovery_document, resource)
     end