5416: Add arv-git-httpd to install docs, api config, and discovery doc.
[arvados.git] / services / api / app / controllers / arvados / v1 / schema_controller.rb
1 class Arvados::V1::SchemaController < ApplicationController
2   skip_before_filter :catch_redirect_hint
3   skip_before_filter :find_objects_for_index
4   skip_before_filter :find_object_by_uuid
5   skip_before_filter :load_filters_param
6   skip_before_filter :load_limit_offset_order_params
7   skip_before_filter :load_read_auths
8   skip_before_filter :load_where_param
9   skip_before_filter :render_404_if_no_object
10   skip_before_filter :require_auth_scope
11
12   def index
13     expires_in 24.hours, public: true
14     discovery = Rails.cache.fetch 'arvados_v1_rest_discovery' do
15       Rails.application.eager_load!
16       discovery = {
17         kind: "discovery#restDescription",
18         discoveryVersion: "v1",
19         id: "arvados:v1",
20         name: "arvados",
21         version: "v1",
22         revision: "20131114",
23         source_version: (Rails.application.config.source_version ? Rails.application.config.source_version : "No version information available") + (Rails.application.config.local_modified ? Rails.application.config.local_modified.to_s : ''),
24         generatedAt: Time.now.iso8601,
25         title: "Arvados API",
26         description: "The API to interact with Arvados.",
27         documentationLink: "http://doc.arvados.org/api/index.html",
28         defaultCollectionReplication: Rails.configuration.default_collection_replication,
29         gitHttpBase: Rails.configuration.git_http_base,
30         protocol: "rest",
31         baseUrl: root_url + "arvados/v1/",
32         basePath: "/arvados/v1/",
33         rootUrl: root_url,
34         servicePath: "arvados/v1/",
35         batchPath: "batch",
36         defaultTrashLifetime: Rails.application.config.default_trash_lifetime,
37         maxRequestSize: Rails.application.config.max_request_size,
38         parameters: {
39           alt: {
40             type: "string",
41             description: "Data format for the response.",
42             default: "json",
43             enum: [
44                    "json"
45                   ],
46             enumDescriptions: [
47                                "Responses with Content-Type of application/json"
48                               ],
49             location: "query"
50           },
51           fields: {
52             type: "string",
53             description: "Selector specifying which fields to include in a partial response.",
54             location: "query"
55           },
56           key: {
57             type: "string",
58             description: "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
59             location: "query"
60           },
61           oauth_token: {
62             type: "string",
63             description: "OAuth 2.0 token for the current user.",
64             location: "query"
65           }
66         },
67         auth: {
68           oauth2: {
69             scopes: {
70               "https://api.curoverse.com/auth/arvados" => {
71                 description: "View and manage objects"
72               },
73               "https://api.curoverse.com/auth/arvados.readonly" => {
74                 description: "View objects"
75               }
76             }
77           }
78         },
79         schemas: {},
80         resources: {}
81       }
82
83       if Rails.application.config.websocket_address
84         discovery[:websocketUrl] = Rails.application.config.websocket_address
85       elsif ENV['ARVADOS_WEBSOCKETS']
86         discovery[:websocketUrl] = (root_url.sub /^http/, 'ws') + "websocket"
87       end
88
89       ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |k|
90         begin
91           ctl_class = "Arvados::V1::#{k.to_s.pluralize}Controller".constantize
92         rescue
93           # No controller -> no discovery.
94           next
95         end
96         object_properties = {}
97         k.columns.
98           select { |col| col.name != 'id' }.
99           collect do |col|
100           if k.serialized_attributes.has_key? col.name
101             object_properties[col.name] = {
102               type: k.serialized_attributes[col.name].object_class.to_s
103             }
104           else
105             object_properties[col.name] = {
106               type: col.type
107             }
108           end
109         end
110         discovery[:schemas][k.to_s + 'List'] = {
111           id: k.to_s + 'List',
112           description: k.to_s + ' list',
113           type: "object",
114           properties: {
115             kind: {
116               type: "string",
117               description: "Object type. Always arvados##{k.to_s.camelcase(:lower)}List.",
118               default: "arvados##{k.to_s.camelcase(:lower)}List"
119             },
120             etag: {
121               type: "string",
122               description: "List version."
123             },
124             items: {
125               type: "array",
126               description: "The list of #{k.to_s.pluralize}.",
127               items: {
128                 "$ref" => k.to_s
129               }
130             },
131             next_link: {
132               type: "string",
133               description: "A link to the next page of #{k.to_s.pluralize}."
134             },
135             next_page_token: {
136               type: "string",
137               description: "The page token for the next page of #{k.to_s.pluralize}."
138             },
139             selfLink: {
140               type: "string",
141               description: "A link back to this list."
142             }
143           }
144         }
145         discovery[:schemas][k.to_s] = {
146           id: k.to_s,
147           description: k.to_s,
148           type: "object",
149           uuidPrefix: (k.respond_to?(:uuid_prefix) ? k.uuid_prefix : nil),
150           properties: {
151             uuid: {
152               type: "string",
153               description: "Object ID."
154             },
155             etag: {
156               type: "string",
157               description: "Object version."
158             }
159           }.merge(object_properties)
160         }
161         discovery[:resources][k.to_s.underscore.pluralize] = {
162           methods: {
163             get: {
164               id: "arvados.#{k.to_s.underscore.pluralize}.get",
165               path: "#{k.to_s.underscore.pluralize}/{uuid}",
166               httpMethod: "GET",
167               description: "Gets a #{k.to_s}'s metadata by UUID.",
168               parameters: {
169                 uuid: {
170                   type: "string",
171                   description: "The UUID of the #{k.to_s} in question.",
172                   required: true,
173                   location: "path"
174                 }
175               },
176               parameterOrder: [
177                                "uuid"
178                               ],
179               response: {
180                 "$ref" => k.to_s
181               },
182               scopes: [
183                        "https://api.curoverse.com/auth/arvados",
184                        "https://api.curoverse.com/auth/arvados.readonly"
185                       ]
186             },
187             list: {
188               id: "arvados.#{k.to_s.underscore.pluralize}.list",
189               path: k.to_s.underscore.pluralize,
190               httpMethod: "GET",
191               description:
192                  %|List #{k.to_s.pluralize}.
193
194                    The <code>list</code> method returns a
195                    <a href="/api/resources.html">resource list</a> of
196                    matching #{k.to_s.pluralize}. For example:
197
198                    <pre>
199                    {
200                     "kind":"arvados##{k.to_s.camelcase(:lower)}List",
201                     "etag":"",
202                     "self_link":"",
203                     "next_page_token":"",
204                     "next_link":"",
205                     "items":[
206                        ...
207                     ],
208                     "items_available":745,
209                     "_profile":{
210                      "request_time":0.157236317
211                     }
212                     </pre>|,
213               parameters: {
214                 limit: {
215                   type: "integer",
216                   description: "Maximum number of #{k.to_s.underscore.pluralize} to return.",
217                   default: "100",
218                   format: "int32",
219                   minimum: "0",
220                   location: "query",
221                 },
222                 offset: {
223                   type: "integer",
224                   description: "Number of #{k.to_s.underscore.pluralize} to skip before first returned record.",
225                   default: "0",
226                   format: "int32",
227                   minimum: "0",
228                   location: "query",
229                   },
230                 filters: {
231                   type: "array",
232                   description: "Conditions for filtering #{k.to_s.underscore.pluralize}.",
233                   location: "query"
234                 },
235                 where: {
236                   type: "object",
237                   description: "Conditions for filtering #{k.to_s.underscore.pluralize}. (Deprecated. Use filters instead.)",
238                   location: "query"
239                 },
240                 order: {
241                   type: "string",
242                   description: "Order in which to return matching #{k.to_s.underscore.pluralize}.",
243                   location: "query"
244                 },
245                 select: {
246                   type: "array",
247                   description: "Select which fields to return",
248                   location: "query"
249                 },
250                 distinct: {
251                   type: "boolean",
252                   description: "Return each distinct object",
253                   location: "query"
254                 }
255               },
256               response: {
257                 "$ref" => "#{k.to_s}List"
258               },
259               scopes: [
260                        "https://api.curoverse.com/auth/arvados",
261                        "https://api.curoverse.com/auth/arvados.readonly"
262                       ]
263             },
264             create: {
265               id: "arvados.#{k.to_s.underscore.pluralize}.create",
266               path: "#{k.to_s.underscore.pluralize}",
267               httpMethod: "POST",
268               description: "Create a new #{k.to_s}.",
269               parameters: {},
270               request: {
271                 required: true,
272                 properties: {
273                   k.to_s.underscore => {
274                     "$ref" => k.to_s
275                   }
276                 }
277               },
278               response: {
279                 "$ref" => k.to_s
280               },
281               scopes: [
282                        "https://api.curoverse.com/auth/arvados"
283                       ]
284             },
285             update: {
286               id: "arvados.#{k.to_s.underscore.pluralize}.update",
287               path: "#{k.to_s.underscore.pluralize}/{uuid}",
288               httpMethod: "PUT",
289               description: "Update attributes of an existing #{k.to_s}.",
290               parameters: {
291                 uuid: {
292                   type: "string",
293                   description: "The UUID of the #{k.to_s} in question.",
294                   required: true,
295                   location: "path"
296                 }
297               },
298               request: {
299                 required: true,
300                 properties: {
301                   k.to_s.underscore => {
302                     "$ref" => k.to_s
303                   }
304                 }
305               },
306               response: {
307                 "$ref" => k.to_s
308               },
309               scopes: [
310                        "https://api.curoverse.com/auth/arvados"
311                       ]
312             },
313             delete: {
314               id: "arvados.#{k.to_s.underscore.pluralize}.delete",
315               path: "#{k.to_s.underscore.pluralize}/{uuid}",
316               httpMethod: "DELETE",
317               description: "Delete an existing #{k.to_s}.",
318               parameters: {
319                 uuid: {
320                   type: "string",
321                   description: "The UUID of the #{k.to_s} in question.",
322                   required: true,
323                   location: "path"
324                 }
325               },
326               response: {
327                 "$ref" => k.to_s
328               },
329               scopes: [
330                        "https://api.curoverse.com/auth/arvados"
331                       ]
332             }
333           }
334         }
335         # Check for Rails routes that don't match the usual actions
336         # listed above
337         d_methods = discovery[:resources][k.to_s.underscore.pluralize][:methods]
338         Rails.application.routes.routes.each do |route|
339           action = route.defaults[:action]
340           httpMethod = ['GET', 'POST', 'PUT', 'DELETE'].map { |method|
341             method if route.verb.match(method)
342           }.compact.first
343           if httpMethod and
344               route.defaults[:controller] == 'arvados/v1/' + k.to_s.underscore.pluralize and
345               ctl_class.action_methods.include? action
346             if !d_methods[action.to_sym]
347               method = {
348                 id: "arvados.#{k.to_s.underscore.pluralize}.#{action}",
349                 path: route.path.spec.to_s.sub('/arvados/v1/','').sub('(.:format)','').sub(/:(uu)?id/,'{uuid}'),
350                 httpMethod: httpMethod,
351                 description: "#{action} #{k.to_s.underscore.pluralize}",
352                 parameters: {},
353                 response: {
354                   "$ref" => (action == 'index' ? "#{k.to_s}List" : k.to_s)
355                 },
356                 scopes: [
357                          "https://api.curoverse.com/auth/arvados"
358                         ]
359               }
360               route.segment_keys.each do |key|
361                 if key != :format
362                   key = :uuid if key == :id
363                   method[:parameters][key] = {
364                     type: "string",
365                     description: "",
366                     required: true,
367                     location: "path"
368                   }
369                 end
370               end
371             else
372               # We already built a generic method description, but we
373               # might find some more required parameters through
374               # introspection.
375               method = d_methods[action.to_sym]
376             end
377             if ctl_class.respond_to? "_#{action}_requires_parameters".to_sym
378               ctl_class.send("_#{action}_requires_parameters".to_sym).each do |k, v|
379                 if v.is_a? Hash
380                   method[:parameters][k] = v
381                 else
382                   method[:parameters][k] = {}
383                 end
384                 if !method[:parameters][k][:default].nil?
385                   # The JAVA SDK is sensitive to all values being strings
386                   method[:parameters][k][:default] = method[:parameters][k][:default].to_s
387                 end
388                 method[:parameters][k][:type] ||= 'string'
389                 method[:parameters][k][:description] ||= ''
390                 method[:parameters][k][:location] = (route.segment_keys.include?(k) ? 'path' : 'query')
391                 if method[:parameters][k][:required].nil?
392                   method[:parameters][k][:required] = v != false
393                 end
394               end
395             end
396             d_methods[action.to_sym] = method
397           end
398         end
399       end
400       discovery
401     end
402     send_json discovery
403   end
404 end