2411: Add copyright notices to everything.
[arvados.git] / sdk / cli / bin / arv-tag
index 5860c2e1f2e6d99fee0d9a681c3666891095b38b..b1783bccf3f277cd705b5012b382182cb3e6b3ad 100755 (executable)
 #! /usr/bin/env ruby
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
 
 # arv tag usage:
 #   arv tag add tag1 [tag2 ...] --object obj_uuid1 [--object obj_uuid2 ...]
 #   arv tag remove tag1 [tag2 ...] --object obj_uuid1 [--object obj_uuid2 ...]
 #   arv tag remove tag1 [tag2 ...] --all
 
-def tag_add(tag, obj_uuid)
-  request_body = {}
-  request_body[:api_token] = ENV['ARVADOS_API_TOKEN']
+def usage_string
+  return "\nUsage:\n" +
+    "arv tag add tag1 [tag2 ...] --object object_uuid1 [object_uuid2...]\n" +
+    "arv tag remove tag1 [tag2 ...] --object object_uuid1 [object_uuid2...]\n" +
+    "arv tag remove --all\n"
+end
 
-  return client.execute(:api_method => 'arvados.links.create',
-                        :parameters => {
-                          :name       => tag
-                          :link_class => :tag,
-                          :head_uuid  => obj_uuid,
-                        },
-                        :body => request_body,
-                        :authenticated => false)
+def usage
+  abort usage_string
 end
 
-def tag_remove(tag, obj_uuid=nil)
-  request_body = {}
-  request_body[:api_token] = ENV['ARVADOS_API_TOKEN']
+def api_call(method, parameters:{}, request_body:{})
+  result = $client.execute(:api_method => method,
+                           :parameters => parameters,
+                           :body_object => request_body,
+                           :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
+
+  if results["errors"]
+    abort "Error: #{results["errors"][0]}"
+  end
+
+  return results
+end
+
+def tag_add(tag, obj_uuid)
+  return api_call($arvados.links.create,
+                  request_body: {
+                    :link => {
+                      :name       => tag,
+                      :link_class => :tag,
+                      :head_uuid  => obj_uuid,
+                    }
+                  })
+end
+
+def tag_remove(tag, obj_uuids=nil)
+  # If we got a list of objects to untag, look up the uuids for the
+  # links that need to be deleted.
+  link_uuids = []
+  if obj_uuids
+    obj_uuids.each do |uuid|
+      link = api_call($arvados.links.list,
+                      request_body: {
+                        :where => {
+                          :link_class => :tag,
+                          :name => tag,
+                          :head_uuid => uuid,
+                        }
+                      })
+      if link['items_available'] > 0
+        link_uuids.push link['items'][0]['uuid']
+      end
+    end
+  else
+    all_tag_links = api_call($arvados.links.list,
+                             request_body: {
+                               :where => {
+                                 :link_class => :tag,
+                                 :name => tag,
+                               }
+                             })
+    link_uuids = all_tag_links['items'].map { |obj| obj['uuid'] }
+  end
 
-  params = { :name => tag, :link_class => :tag }
-  if obj_uuid then
-    params[:head_uuid] = obj_uuid
+  results = []
+  if link_uuids
+    link_uuids.each do |uuid|
+      results.push api_call($arvados.links.delete, parameters:{ :uuid => uuid })
+    end
+  else
+    $stderr.puts "no tags found to remove"
   end
 
-  return client.execute(:api_method => 'arvados.links.destroy',
-                        :parameters => params,
-                        :body => request_body,
-                        :authenticated => false)
+  return results
 end
 
 if RUBY_VERSION < '1.9.3' then
   abort <<-EOS
 #{$0.gsub(/^\.\//,'')} requires Ruby version 1.9.3 or higher.
-  EOS
+EOS
 end
 
 $arvados_api_version = ENV['ARVADOS_API_VERSION'] || 'v1'
@@ -45,12 +104,15 @@ $arvados_api_host = ENV['ARVADOS_API_HOST'] or
   abort "#{$0}: fatal: ARVADOS_API_HOST environment variable not set."
 $arvados_api_token = ENV['ARVADOS_API_TOKEN'] or
   abort "#{$0}: fatal: ARVADOS_API_TOKEN environment variable not set."
+$arvados_api_host_insecure = %w(1 true yes).
+  include?((ENV['ARVADOS_API_HOST_INSECURE'] || "").downcase)
 
 begin
   require 'rubygems'
   require 'google/api_client'
   require 'json'
   require 'pp'
+  require 'oj'
   require 'trollop'
 rescue LoadError
   abort <<-EOS
@@ -73,7 +135,7 @@ module Kernel
   end
 end
 
-if $arvados_api_host.match /local/
+if $arvados_api_host_insecure or $arvados_api_host.match /local/
   # You probably don't care about SSL certificate checks if you're
   # testing with a dev server.
   suppress_warnings { OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE }
@@ -94,6 +156,19 @@ class Google::APIClient
   end
 end
 
+global_opts = Trollop::options do
+  banner usage_string
+  banner ""
+  opt :dry_run, "Don't actually do anything", :short => "-n"
+  opt :verbose, "Print some things on stderr", :short => "-v"
+  opt :uuid, "Return the UUIDs of the objects in the response, one per line (default)", :short => nil
+  opt :json, "Return the entire response received from the API server, as a JSON object", :short => "-j"
+  opt :human, "Return the response received from the API server, as a JSON object with whitespace added for human consumption", :short => "-h"
+  opt :pretty, "Synonym of --human", :short => nil
+  opt :yaml, "Return the response received from the API server, in YAML format", :short => "-y"
+  stop_on ['add', 'remove']
+end
+
 p = Trollop::Parser.new do
   opt(:all,
       "Remove this tag from all objects under your ownership. Only valid with `tag remove'.",
@@ -110,7 +185,7 @@ $options = Trollop::with_standard_exception_handling p do
 end
 
 if $options[:all] and ARGV[0] != 'remove'
-  abort "#{$0}: the --all option is only valid with the tag 'remove' command"
+  usage
 end
 
 # Set up the API client.
@@ -123,6 +198,11 @@ $arvados = $client.discovered_api('arvados', $arvados_api_version)
 
 results = []
 cmd = ARGV.shift
+
+if ARGV.empty?
+  usage
+end
+
 case cmd
 when 'add'
   ARGV.each do |tag|
@@ -133,15 +213,13 @@ when 'add'
 when 'remove'
   ARGV.each do |tag|
     if $options[:all] then
-      results.push(tag_remove(tag))
+      results.concat tag_remove(tag)
     else
-      $options[:object].each do |obj|
-        results.push(tag_remove(tag, obj))
-      end
+      results.concat tag_remove(tag, $options[:object])
     end
   end
 else
-  abort "unknown tag command #{cmd}"
+  usage
 end
 
 if global_opts[:human] or global_opts[:pretty] then
@@ -150,14 +228,14 @@ elsif global_opts[:yaml] then
   puts results.to_yaml
 elsif global_opts[:json] then
   puts Oj.dump(results)
-elsif results["items"] and results["kind"].match /list$/i
-  results['items'].each do |i| puts i['uuid'] end
-elsif results['uuid'].nil?
-  abort("Response did not include a uuid:\n" +
-        Oj.dump(results, :indent => 1) +
-        "\n")
 else
-  results.each do |result|
-    puts result['uuid']
+  results.each do |r|
+    if r['uuid'].nil?
+      abort("Response did not include a uuid:\n" +
+            Oj.dump(r, :indent => 1) +
+            "\n")
+    else
+      puts r['uuid']
+    end
   end
 end