X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/19e913d3b754429be49c38505c249b7b2151a70f..afdab87268e4ee2f19f92e02b219696081959378:/sdk/cli/bin/arv diff --git a/sdk/cli/bin/arv b/sdk/cli/bin/arv index 337d9abdef..533ea39eb7 100755 --- a/sdk/cli/bin/arv +++ b/sdk/cli/bin/arv @@ -2,7 +2,7 @@ # Arvados cli client # -# Ward Vandewege +# Ward Vandewege require 'fileutils' @@ -23,6 +23,7 @@ begin require 'oj' require 'active_support/inflector' require 'yaml' + require 'tempfile' rescue LoadError abort <<-EOS @@ -112,10 +113,17 @@ def init_config end end -subcommands = %w(keep pipeline tag ws edit) + +subcommands = %w(copy create edit keep pipeline run tag ws) def check_subcommands client, arvados, subcommand, global_opts, remaining_opts case subcommand + when 'create' + arv_create client, arvados, global_opts, remaining_opts + when 'edit' + arv_edit client, arvados, global_opts, remaining_opts + when 'copy', 'tag', 'ws', 'run' + exec `which arv-#{subcommand}`.strip, *remaining_opts when 'keep' @sub = remaining_opts.shift if ['get', 'put', 'ls', 'normalize'].index @sub then @@ -142,12 +150,6 @@ def check_subcommands client, arvados, subcommand, global_opts, remaining_opts puts "Available methods: run" end abort - when 'tag' - exec `which arv-tag`.strip, *remaining_opts - when 'ws' - exec `which arv-ws`.strip, *remaining_opts - when 'edit' - arv_edit client, arvados, global_opts, remaining_opts end end @@ -156,6 +158,67 @@ def arv_edit_save_tmp tmp puts "Saved contents to " + tmp.path + ".saved" end +def command_exists?(command) + ENV['PATH'].split(':').each {|folder| File.executable?(File.join(folder, command))} +end + +def run_editor tmp_file, global_opts + need_edit = true + while need_edit + pid = Process::fork + if pid.nil? + editor = nil + [ENV["VISUAL"], ENV["EDITOR"], "nano", "vi"].each do |e| + editor ||= e if e and command_exists? e + end + if editor.nil? + puts "Could not find any editor to use, please set $VISUAL or $EDITOR to your desired editor." + exit 1 + end + exec editor, tmp_file.path + else + Process.wait pid + end + + if $?.exitstatus == 0 + tmp_file.open + newcontent = tmp_file.read() + + 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 + n = 1 + newcontent.each_line do |line| + puts "#{n.to_s.rjust 4} #{line}" + n += 1 + end + puts "Parse error! " + e.to_s + 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_file + abort + end + end + else + puts "Editor exited with status #{$?.exitstatus}" + exit $?.exitstatus + end + end + + newobj +end + def arv_edit client, arvados, global_opts, remaining_opts uuid = remaining_opts.shift if uuid.nil? or uuid == "-h" or uuid == "--help" @@ -224,60 +287,11 @@ def arv_edit client, arvados, global_opts, remaining_opts content = results.to_yaml end - require 'tempfile' + tmp_file = Tempfile.new([uuid, "." + global_opts[:format]]) + tmp_file.write(content) + tmp_file.close - tmp = Tempfile.new([uuid, "." + global_opts[:format]]) - tmp.write(content) - tmp.close - - 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() - - 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 + newobj = run_editor tmp_file, global_opts begin if newobj != results @@ -287,7 +301,7 @@ def arv_edit client, arvados, global_opts, remaining_opts begin result = client.execute(:api_method => eval(api_method), :parameters => {"uuid" => uuid}, - :body => { rsc.singularize => dumped }, + :body_object => { rsc.singularize => dumped }, :authenticated => false, :headers => { authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN'] @@ -296,13 +310,14 @@ def arv_edit client, arvados, global_opts, remaining_opts puts "Error communicating with server, error was #{e}" puts "Update body was:" puts dumped - arv_edit_save_tmp tmp + arv_edit_save_tmp tmp_file abort end begin results = JSON.parse result.body rescue JSON::ParserError => e + arv_edit_save_tmp tmp_file abort "Failed to parse server response:\n" + e.to_s end @@ -310,14 +325,114 @@ def arv_edit client, arvados, global_opts, remaining_opts puts "Update failed. Server responded #{result.response.status}: #{results['errors']} " puts "Update body was:" puts dumped - arv_edit_save_tmp tmp + arv_edit_save_tmp tmp_file abort end else puts "Object is unchanged, did not update." end ensure - tmp.close(true) + tmp_file.close(true) + end + + exit 0 +end + +def arv_create client, arvados, global_opts, remaining_opts + types = resource_types(arvados.discovery_document) + create_opts = Trollop::options do + opt :project_uuid, "Project uuid in which to create the object", :type => :string + stop_on resource_types(arvados.discovery_document) + end + + object_type = remaining_opts.shift + if object_type.nil? + abort "Missing resource type, must be one of #{types.join ', '}" + end + + rsc = arvados.discovery_document["resources"].keys.select { |k| object_type == k.singularize } + if rsc.empty? + abort "Could not determine resource type #{object_type}" + end + rsc = rsc.first + + discovered_params = arvados.discovery_document["resources"][rsc]["methods"]["create"]["parameters"] + method_opts = Trollop::options do + banner head_banner + banner "Usage: arv create [--project-uuid] #{object_type} [create parameters]" + banner "" + banner "This method supports the following parameters:" + banner "" + discovered_params.each do |k,v| + opts = Hash.new() + opts[:type] = v["type"].to_sym if v.include?("type") + if [:datetime, :text, :object, :array].index opts[:type] + opts[:type] = :string # else trollop bork + end + opts[:default] = v["default"] if v.include?("default") + opts[:default] = v["default"].to_i if opts[:type] == :integer + opts[:default] = to_boolean(v["default"]) if opts[:type] == :boolean + opts[:required] = true if v.include?("required") and v["required"] + description = '' + description = ' ' + v["description"] if v.include?("description") + opt k.to_sym, description, opts + end + end + + + newobj = {} + if create_opts[:project_uuid] + newobj["owner_uuid"] = create_opts[:project_uuid] + end + + case global_opts[:format] + when 'json' + content = Oj.dump(newobj, :indent => 1) + when 'yaml' + content = newobj.to_yaml + end + + tmp_file = Tempfile.new(["", ".#{global_opts[:format]}"]) + tmp_file.write(content) + tmp_file.close + + newobj = run_editor tmp_file, global_opts + + begin + api_method = 'arvados.' + rsc + '.create' + dumped = Oj.dump(newobj) + + result = client.execute(:api_method => eval(api_method), + :parameters => method_opts, + :body_object => {object_type => newobj}, + :authenticated => false, + :headers => { + authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN'] + }) + + begin + results = JSON.parse result.body + rescue JSON::ParserError => e + arv_edit_save_tmp tmp_file + abort "Failed to parse server response:\n" + e.to_s + end + + if result.response.status != 200 + puts "Create failed. Server responded #{result.response.status}: #{results['errors']} " + puts "Create body was:" + puts dumped + arv_edit_save_tmp tmp_file + abort + end + + begin + puts "Created object #{results['uuid']}" + rescue + arv_edit_save_tmp tmp_file + abort "Unexpected response:\n#{results}" + end + ensure + tmp_file.close(true) end exit 0 @@ -358,20 +473,19 @@ 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 -def parse_arguments(discovery_document, subcommands) +def resource_types discovery_document resource_types = Array.new() discovery_document["resources"].each do |k,v| resource_types << k.singularize end + resource_types +end - resource_types += subcommands +def parse_arguments(discovery_document, subcommands) + resources_and_subcommands = resource_types(discovery_document) + subcommands option_parser = Trollop::Parser.new do version __FILE__ @@ -400,7 +514,7 @@ def parse_arguments(discovery_document, subcommands) banner "Additional options:" conflicts :short, :format - stop_on resource_types + stop_on resources_and_subcommands end global_opts = Trollop::with_standard_exception_handling option_parser do @@ -420,7 +534,7 @@ def parse_arguments(discovery_document, subcommands) resource = ARGV.shift if not subcommands.include? resource - if not resource_types.include?(resource) + if not resources_and_subcommands.include?(resource) puts "Resource or subcommand '#{resource}' is not recognized.\n\n" if !resource.nil? help_resources(option_parser, discovery_document, resource) end @@ -558,7 +672,7 @@ when else result = client.execute(:api_method => eval(api_method), :parameters => request_parameters, - :body => request_body, + :body_object => request_body, :authenticated => false, :headers => { authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN']