3609: Added documentation page. Added to "arv" frontend command. Bug fix to
[arvados.git] / sdk / cli / bin / arv
index cf3fffa1311febc928ba0d466c0e93092ef8f8b2..59bdfae81ae59024ba174c03600703f5e92ade99 100755 (executable)
@@ -2,7 +2,7 @@
 
 # Arvados cli client
 #
-# Ward Vandewege <ward@clinicalfuture.com>
+# Ward Vandewege <ward@curoverse.com>
 
 require 'fileutils'
 
@@ -53,17 +53,25 @@ end
 class Google::APIClient
  def discovery_document(api, version)
    api = api.to_s
-   return @discovery_documents["#{api}:#{version}"] ||=
+   discovery_uri = self.discovery_uri(api, version)
+   discovery_uri_hash = Digest::MD5.hexdigest(discovery_uri)
+   return @discovery_documents[discovery_uri_hash] ||=
      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-#{discovery_uri_hash}.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),
+                                  :uri => discovery_uri,
                                   :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
 
@@ -85,8 +93,8 @@ end
 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
@@ -104,7 +112,7 @@ def init_config
   end
 end
 
-subcommands = %w(keep pipeline tag ws edit)
+subcommands = %w(keep pipeline run tag ws edit)
 
 def check_subcommands client, arvados, subcommand, global_opts, remaining_opts
   case subcommand
@@ -134,6 +142,8 @@ def check_subcommands client, arvados, subcommand, global_opts, remaining_opts
       puts "Available methods: run"
     end
     abort
+  when 'run'
+    exec `which arv-run`.strip, *remaining_opts
   when 'tag'
     exec `which arv-tag`.strip, *remaining_opts
   when 'ws'
@@ -143,9 +153,14 @@ def check_subcommands client, arvados, subcommand, global_opts, remaining_opts
   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
-  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 "Fetch the specified Arvados object, select the specified fields, \n"
@@ -162,9 +177,9 @@ def arv_edit client, arvados, global_opts, remaining_opts
 
   # 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 /^[a-f0-9]{32}/.match n
+    if /^[a-f0-9]{32}/.match uuid
       abort "Arvados collections are not editable."
     else
       abort "#{n} does not appear to be an Arvados uuid"
@@ -187,7 +202,7 @@ def arv_edit client, arvados, global_opts, remaining_opts
   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']
@@ -213,7 +228,7 @@ def arv_edit client, arvados, global_opts, remaining_opts
 
   require 'tempfile'
 
-  tmp = Tempfile.new([n, "." + global_opts[:format]])
+  tmp = Tempfile.new([uuid, "." + global_opts[:format]])
   tmp.write(content)
   tmp.close
 
@@ -251,9 +266,13 @@ def arv_edit client, arvados, global_opts, remaining_opts
           n += 1
         end
         puts "\nTry again (y/n)? "
-        yn = $stdin.read 1
+        yn = "X"
+        while not ["y", "Y", "n", "N"].include?(yn)
+          yn = $stdin.read 1
+        end
         if yn == 'n' or yn == 'N'
-          exit 1
+          arv_edit_save_tmp tmp
+          abort
         end
       end
     else
@@ -262,31 +281,45 @@ def arv_edit client, arvados, global_opts, remaining_opts
     end
   end
 
-  tmp.close(true)
-
-  if newobj != results
-    api_method = 'arvados.' + rsc + '.update'
-    dumped = Oj.dump(newobj)
-    result = client.execute(:api_method => eval(api_method),
-                            :parameters => {"uuid" => n, rsc.singularize => dumped},
-                            :authenticated => false,
-                            :headers => {
-                              authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN']
-                            })
-
-    begin
-      results = JSON.parse result.body
-    rescue JSON::ParserError => e
-      abort "Failed to parse server response:\n" + e.to_s
-    end
+  begin
+    if newobj != results
+      api_method = 'arvados.' + rsc + '.update'
+      dumped = Oj.dump(newobj)
 
-    if result.response.status != 200
-      puts "Update failed.  Server responded #{result.response.status}: #{results['errors']} "
-      puts "Update body was:"
-      puts dumped
+      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
+      rescue JSON::ParserError => e
+        abort "Failed to parse server response:\n" + e.to_s
+      end
+
+      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
-  else
-    puts "Object is unchanged, did not update."
+  ensure
+    tmp.close(true)
   end
 
   exit 0
@@ -327,10 +360,6 @@ end
 
 def help_resources(option_parser, discovery_document, resource)
   option_parser.educate
-
-  if not resource.nil? and resource != '--help' then
-    Trollop::die "Unknown resource type #{resource.inspect}"
-  end
   exit 255
 end
 
@@ -390,7 +419,7 @@ def parse_arguments(discovery_document, subcommands)
 
   if not subcommands.include? resource
     if not resource_types.include?(resource)
-      puts "Resource or subcommand '#{resource}' is not recognized.\n\n"
+      puts "Resource or subcommand '#{resource}' is not recognized.\n\n" if !resource.nil?
       help_resources(option_parser, discovery_document, resource)
     end