Remove broken require
[arvados.git] / bin / google-api
1 #!/usr/bin/env ruby
2
3 bin_dir = File.expand_path("..", __FILE__)
4 lib_dir = File.expand_path("../lib", bin_dir)
5
6 $LOAD_PATH.unshift(lib_dir)
7 $LOAD_PATH.uniq!
8
9 OAUTH_SERVER_PORT = 12736
10
11 require 'rubygems'
12 require 'optparse'
13 require 'faraday'
14 require 'faraday/utils'
15 require 'webrick'
16 require 'google/api_client/version'
17 require 'google/api_client'
18 require 'google/api_client/auth/installed_app'
19 require 'irb'
20
21 ARGV.unshift('--help') if ARGV.empty?
22
23 module Google
24   class APIClient
25     class CLI
26
27       # Initialize with default parameter values
28       def initialize(argv)
29         @options = {
30           :command => 'execute',
31           :rpcname => nil,
32           :verbose => false
33         }
34         @argv = argv.clone
35         if @argv.first =~ /^[a-z0-9][a-z0-9_-]*$/i
36           self.options[:command] = @argv.shift
37         end
38         if @argv.first =~ /^[a-z0-9_-]+\.[a-z0-9_\.-]+$/i
39           self.options[:rpcname] = @argv.shift
40         end
41       end
42
43       attr_reader :options
44       attr_reader :argv
45
46       def command
47         return self.options[:command]
48       end
49
50       def rpcname
51         return self.options[:rpcname]
52       end
53
54       def parser
55         @parser ||= OptionParser.new do |opts|
56           opts.banner = "Usage: google-api " +
57             "(execute <rpcname> | [command]) [options] [-- <parameters>]"
58
59           opts.separator "\nAvailable options:"
60
61           opts.on(
62               "--scope <scope>", String, "Set the OAuth scope") do |s|
63             options[:scope] = s
64           end
65           opts.on(
66               "--client-id <key>", String,
67                 "Set the OAuth client id or key") do |k|
68             options[:client_credential_key] = k
69           end
70           opts.on(
71               "--client-secret <secret>", String,
72                 "Set the OAuth client secret") do |s|
73             options[:client_credential_secret] = s
74           end
75           opts.on(
76               "--api <name>", String,
77               "Perform discovery on API") do |s|
78             options[:api] = s
79           end
80           opts.on(
81               "--api-version <id>", String,
82               "Select api version") do |id|
83             options[:version] = id
84           end
85           opts.on(
86               "--content-type <format>", String,
87               "Content-Type for request") do |f|
88             # Resolve content type shortcuts
89             case f
90             when 'json'
91               f = 'application/json'
92             when 'xml'
93               f = 'application/xml'
94             when 'atom'
95               f = 'application/atom+xml'
96             when 'rss'
97               f = 'application/rss+xml'
98             end
99             options[:content_type] = f
100           end
101           opts.on(
102               "-u", "--uri <uri>", String,
103               "Sets the URI to perform a request against") do |u|
104             options[:uri] = u
105           end
106           opts.on(
107               "--discovery-uri <uri>", String,
108               "Sets the URI to perform discovery") do |u|
109             options[:discovery_uri] = u
110           end
111           opts.on(
112               "-m", "--method <method>", String,
113               "Sets the HTTP method to use for the request") do |m|
114             options[:http_method] = m
115           end
116           opts.on(
117               "--requestor-id <email>", String,
118               "Sets the email address of the requestor") do |e|
119             options[:requestor_id] = e
120           end
121
122           opts.on("-v", "--verbose", "Run verbosely") do |v|
123             options[:verbose] = v
124           end
125           opts.on("-h", "--help", "Show this message") do
126             puts opts
127             exit
128           end
129           opts.on("--version", "Show version") do
130             puts "google-api-client (#{Google::APIClient::VERSION::STRING})"
131             exit
132           end
133
134           opts.separator(
135             "\nAvailable commands:\n" +
136             "    oauth-2-login   Log a user into an API with OAuth 2.0\n" +
137             "    list            List the methods available for an API\n" +
138             "    execute         Execute a method on the API\n" +
139             "    irb             Start an interactive client session"
140           )
141         end
142       end
143
144       def parse!
145         self.parser.parse!(self.argv)
146         symbol = self.command.gsub(/-/, "_").to_sym
147         if !COMMANDS.include?(symbol)
148           STDERR.puts("Invalid command: #{self.command}")
149           exit(1)
150         end
151         self.send(symbol)
152       end
153
154       def client
155         require 'yaml'
156         config_file = File.expand_path('~/.google-api.yaml')
157         authorization = nil
158         if File.exist?(config_file)
159           config = open(config_file, 'r') { |file| YAML.load(file.read) }
160         else
161           config = {}
162         end
163         if config["mechanism"]
164           authorization = config["mechanism"].to_sym
165         end
166
167         client = Google::APIClient.new(
168           :application_name => 'Ruby CLI', 
169           :application_version => Google::APIClient::VERSION::STRING,
170           :authorization => authorization)
171
172         case authorization
173         when :oauth_1
174           STDERR.puts('OAuth 1 is deprecated. Please reauthorize with OAuth 2.')
175           client.authorization.client_credential_key =
176             config["client_credential_key"]
177           client.authorization.client_credential_secret =
178             config["client_credential_secret"]
179           client.authorization.token_credential_key =
180             config["token_credential_key"]
181           client.authorization.token_credential_secret =
182             config["token_credential_secret"]
183         when :oauth_2
184           client.authorization.scope = options[:scope]
185           client.authorization.client_id = config["client_id"]
186           client.authorization.client_secret = config["client_secret"]
187           client.authorization.access_token = config["access_token"]
188           client.authorization.refresh_token = config["refresh_token"]
189         else
190           # Dunno?
191         end
192
193         if options[:discovery_uri]
194           if options[:api] && options[:version]
195             client.register_discovery_uri(
196               options[:api], options[:version], options[:discovery_uri]
197             )
198           else
199             STDERR.puts(
200               'Cannot register a discovery URI without ' +
201               'specifying an API and version.'
202             )
203             exit(1)
204           end
205         end
206
207         return client
208       end
209
210       def api_version(api_name, version)
211         v = version
212         if !version
213           if client.preferred_version(api_name)
214             v = client.preferred_version(api_name).version
215           else
216             v = 'v1'
217           end
218         end
219         return v
220       end
221
222       COMMANDS = [
223         :oauth_2_login,
224         :list,
225         :execute,
226         :irb,
227       ]
228
229       def oauth_2_login
230         require 'signet/oauth_2/client'
231         require 'yaml'
232         if !options[:client_credential_key] ||
233             !options[:client_credential_secret]
234           STDERR.puts('No client ID and secret supplied.')
235           exit(1)
236         end
237         if options[:access_token]
238           config = {
239             "mechanism" => "oauth_2",
240             "scope" => options[:scope],
241             "client_id" => options[:client_credential_key],
242             "client_secret" => options[:client_credential_secret],
243             "access_token" => options[:access_token],
244             "refresh_token" => options[:refresh_token]
245           }
246           config_file = File.expand_path('~/.google-api.yaml')
247           open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
248           exit(0)
249         else
250           flow = Google::APIClient::InstalledAppFlow.new(
251             :port => OAUTH_SERVER_PORT,
252             :client_id => options[:client_credential_key],
253             :client_secret => options[:client_credential_secret],
254             :scope => options[:scope]
255           )
256           
257           oauth_client = flow.authorize
258           if oauth_client
259             config = {
260               "mechanism" => "oauth_2",
261               "scope" => options[:scope],
262               "client_id" => oauth_client.client_id,
263               "client_secret" => oauth_client.client_secret,
264               "access_token" => oauth_client.access_token,
265               "refresh_token" => oauth_client.refresh_token
266             }
267             config_file = File.expand_path('~/.google-api.yaml')
268             open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
269           end
270           exit(0)
271         end
272       end
273
274       def list
275         api_name = options[:api]
276         unless api_name
277           STDERR.puts('No API name supplied.')
278           exit(1)
279         end
280         #client = Google::APIClient.new(:authorization => nil)
281         if options[:discovery_uri]
282           if options[:api] && options[:version]
283             client.register_discovery_uri(
284               options[:api], options[:version], options[:discovery_uri]
285             )
286           else
287             STDERR.puts(
288               'Cannot register a discovery URI without ' +
289               'specifying an API and version.'
290             )
291             exit(1)
292           end
293         end
294         version = api_version(api_name, options[:version])
295         api = client.discovered_api(api_name, version)
296         rpcnames = api.to_h.keys
297         puts rpcnames.sort.join("\n")
298         exit(0)
299       end
300
301       def execute
302         client = self.client
303
304         # Setup HTTP request data
305         request_body = ''
306         input_streams, _, _ = IO.select([STDIN], [], [], 0)
307         request_body = STDIN.read || '' if input_streams
308         headers = []
309         if options[:content_type]
310           headers << ['Content-Type', options[:content_type]]
311         elsif request_body
312           # Default to JSON
313           headers << ['Content-Type', 'application/json']
314         end
315
316         if options[:uri]
317           # Make request with URI manually specified
318           uri = Addressable::URI.parse(options[:uri])
319           if uri.relative?
320             STDERR.puts('URI may not be relative.')
321             exit(1)
322           end
323           if options[:requestor_id]
324             uri.query_values = uri.query_values.merge(
325               'xoauth_requestor_id' => options[:requestor_id]
326             )
327           end
328           method = options[:http_method]
329           method ||= request_body == '' ? 'GET' : 'POST'
330           method.upcase!
331           response = client.execute(:http_method => method, :uri => uri.to_str, 
332             :headers => headers, :body => request_body)
333           puts response.body
334           exit(0)
335         else
336           # Make request with URI generated from template and parameters
337           if !self.rpcname
338             STDERR.puts('No rpcname supplied.')
339             exit(1)
340           end
341           api_name = options[:api] || self.rpcname[/^([^\.]+)\./, 1]
342           version = api_version(api_name, options[:version])
343           api = client.discovered_api(api_name, version)
344           method = api.to_h[self.rpcname]
345           if !method
346             STDERR.puts(
347               "Method #{self.rpcname} does not exist for " +
348               "#{api_name}-#{version}."
349             )
350             exit(1)
351           end
352           parameters = self.argv.inject({}) do |accu, pair|
353             name, value = pair.split('=', 2)
354             accu[name] = value
355             accu
356           end
357           if options[:requestor_id]
358             parameters['xoauth_requestor_id'] = options[:requestor_id]
359           end
360           begin
361             result = client.execute(
362               :api_method => method,
363               :parameters => parameters,
364               :merged_body => request_body,
365               :headers => headers
366             )
367             puts result.response.body
368             exit(0)
369           rescue ArgumentError => e
370             puts e.message
371             exit(1)
372           end
373         end
374       end
375
376       def irb
377         $client = self.client
378         # Otherwise IRB will misinterpret command-line options
379         ARGV.clear
380         IRB.start(__FILE__)
381       end
382
383       def help
384         puts self.parser
385         exit(0)
386       end
387     end
388   end
389 end
390
391 Google::APIClient::CLI.new(ARGV).parse!