Bug fixes (API requires request to be delivered in request_body)
[arvados.git] / sdk / cli / bin / arv-tag
1 #! /usr/bin/env ruby
2
3 # arv tag usage:
4 #   arv tag add tag1 [tag2 ...] --object obj_uuid1 [--object obj_uuid2 ...]
5 #   arv tag remove tag1 [tag2 ...] --object obj_uuid1 [--object obj_uuid2 ...]
6 #   arv tag remove tag1 [tag2 ...] --all
7
8 def usage
9   abort "Usage:\n" +
10     "arv tag add tag1 [tag2 ...] --objects object_uuid1 [object_uuid2...]\n" +
11     "arv tag remove tag1 [tag2 ...] --objects object_uuid1 [object_uuid2...]\n" +
12     "arv tag remove --all\n"
13 end
14
15 def tag_add(tag, obj_uuid)
16   request_body = {
17     :api_token => ENV['ARVADOS_API_TOKEN'],
18     :link => {
19       :name       => tag,
20       :link_class => :tag,
21       :head_uuid  => obj_uuid,
22     }
23   }
24
25   result = $client.execute(:api_method => $arvados.links.create,
26                            :body => request_body,
27                            :authenticated => false)
28
29   begin
30     results = JSON.parse result.body
31   rescue JSON::ParserError => e
32     $stderr.puts "Failed to parse server response:\n" + e.to_s
33     return nil
34   end
35
36   if results["errors"] then
37     $stderr.puts "Error: #{results["errors"][0]}"
38     return nil
39   end
40
41   return results
42 end
43
44 def tag_remove(tag, obj_uuid=nil)
45   request_body = {
46     :api_token => ENV['ARVADOS_API_TOKEN'],
47     :link => {
48       :name       => tag,
49       :link_class => :tag,
50       :head_uuid  => obj_uuid,
51     }
52   }
53
54   return $client.execute(:api_method => $arvados.links.destroy,
55                          :parameters => params,
56                          :body => request_body,
57                          :authenticated => false)
58 end
59
60 if RUBY_VERSION < '1.9.3' then
61   abort <<-EOS
62 #{$0.gsub(/^\.\//,'')} requires Ruby version 1.9.3 or higher.
63 EOS
64 end
65
66 $arvados_api_version = ENV['ARVADOS_API_VERSION'] || 'v1'
67 $arvados_api_host = ENV['ARVADOS_API_HOST'] or
68   abort "#{$0}: fatal: ARVADOS_API_HOST environment variable not set."
69 $arvados_api_token = ENV['ARVADOS_API_TOKEN'] or
70   abort "#{$0}: fatal: ARVADOS_API_TOKEN environment variable not set."
71 $arvados_api_host_insecure = ENV['ARVADOS_API_HOST_INSECURE'] == 'yes'
72
73 begin
74   require 'rubygems'
75   require 'google/api_client'
76   require 'json'
77   require 'pp'
78   require 'trollop'
79 rescue LoadError
80   abort <<-EOS
81 #{$0}: fatal: some runtime dependencies are missing.
82 Try: gem install pp google-api-client json trollop
83   EOS
84 end
85
86 def debuglog(message, verbosity=1)
87   $stderr.puts "#{File.split($0).last} #{$$}: #{message}" if $debuglevel >= verbosity
88 end
89
90 module Kernel
91   def suppress_warnings
92     original_verbosity = $VERBOSE
93     $VERBOSE = nil
94     result = yield
95     $VERBOSE = original_verbosity
96     return result
97   end
98 end
99
100 if $arvados_api_host_insecure or $arvados_api_host.match /local/
101   # You probably don't care about SSL certificate checks if you're
102   # testing with a dev server.
103   suppress_warnings { OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE }
104 end
105
106 class Google::APIClient
107   def discovery_document(api, version)
108     api = api.to_s
109     return @discovery_documents["#{api}:#{version}"] ||=
110       begin
111         response = self.execute!(
112                                  :http_method => :get,
113                                  :uri => self.discovery_uri(api, version),
114                                  :authenticated => false
115                                  )
116         response.body.class == String ? JSON.parse(response.body) : response.body
117       end
118   end
119 end
120
121 global_opts = Trollop::options do
122   banner "arvados cli client"
123   opt :dry_run, "Don't actually do anything", :short => "-n"
124   opt :verbose, "Print some things on stderr", :short => "-v"
125   opt :uuid, "Return the UUIDs of the objects in the response, one per line (default)", :short => nil
126   opt :json, "Return the entire response received from the API server, as a JSON object", :short => "-j"
127   opt :human, "Return the response received from the API server, as a JSON object with whitespace added for human consumption", :short => "-h"
128   opt :pretty, "Synonym of --human", :short => nil
129   opt :yaml, "Return the response received from the API server, in YAML format", :short => "-y"
130   stop_on ['add', 'remove']
131 end
132   
133 p = Trollop::Parser.new do
134   opt(:all,
135       "Remove this tag from all objects under your ownership. Only valid with `tag remove'.",
136       :short => :none)
137   opt(:object,
138       "The UUID of an object to which this tag operation should be applied.",
139       :type => :string,
140       :multi => true,
141       :short => :o)
142 end
143
144 $options = Trollop::with_standard_exception_handling p do
145   p.parse ARGV
146 end
147
148 if $options[:all] and ARGV[0] != 'remove'
149   usage
150 end
151
152 # Set up the API client.
153
154 $client ||= Google::APIClient.
155   new(:host => $arvados_api_host,
156       :application_name => File.split($0).last,
157       :application_version => $application_version.to_s)
158 $arvados = $client.discovered_api('arvados', $arvados_api_version)
159
160 results = []
161 cmd = ARGV.shift
162 case cmd
163 when 'add'
164   ARGV.each do |tag|
165     $options[:object].each do |obj|
166       results.push(tag_add(tag, obj))
167     end
168   end
169 when 'remove'
170   ARGV.each do |tag|
171     if $options[:all] then
172       results.push(tag_remove(tag))
173     else
174       $options[:object].each do |obj|
175         results.push(tag_remove(tag, obj))
176       end
177     end
178   end
179 else
180   usage
181 end
182
183 if global_opts[:human] or global_opts[:pretty] then
184   puts Oj.dump(results, :indent => 1)
185 elsif global_opts[:yaml] then
186   puts results.to_yaml
187 elsif global_opts[:json] then
188   puts Oj.dump(results)
189 else
190   results.each do |r|
191     next if r == nil
192     if r["items"] and r["kind"].match /list$/i
193       r['items'].each do |i| puts i['uuid'] end
194     elsif r['uuid'].nil?
195       abort("Response did not include a uuid:\n" +
196             Oj.dump(r, :indent => 1) +
197             "\n")
198     else
199       puts r['uuid']
200     end
201   end
202 end