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