]> git.arvados.org - arvados.git/blob - services/api/app/controllers/arvados/v1/schema_controller.rb
Merge branch '22394-project-tab-preference' into main. Closes #22394
[arvados.git] / services / api / app / controllers / arvados / v1 / schema_controller.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 class Arvados::V1::SchemaController < ApplicationController
6   skip_before_action :catch_redirect_hint
7   skip_before_action :find_objects_for_index
8   skip_before_action :find_object_by_uuid
9   skip_before_action :load_filters_param
10   skip_before_action :load_limit_offset_order_params
11   skip_before_action :load_select_param
12   skip_before_action :load_read_auths
13   skip_before_action :load_where_param
14   skip_before_action :render_404_if_no_object
15   skip_before_action :require_auth_scope
16
17   include DbCurrentTime
18
19   def index
20     expires_in 24.hours, public: true
21     send_json discovery_doc
22   end
23
24   protected
25
26   ActionNameMap = {
27     'destroy' => 'delete',
28     'index' => 'list',
29     'show' => 'get',
30   }
31
32   HttpMethodDescriptionMap = {
33     "DELETE" => "delete",
34     "GET" => "query",
35     "POST" => "update",
36     "PUT" => "create",
37   }
38
39   ModelHumanNameMap = {
40     # The discovery document has code to humanize most model names.
41     # These are exceptions that require some capitalization.
42     "ApiClientAuthorization" => "API client authorization",
43     "KeepService" => "Keep service",
44   }
45
46   SchemaDescriptionMap = {
47     # This hash contains descriptions for everything in the schema.
48     # Schemas are looked up by their model name.
49     # Schema properties are looked up by "{model_name}.{property_name}"
50     # and fall back to just the property name if that doesn't exist.
51     "ApiClientAuthorization" => "Arvados API client authorization token
52
53 This resource represents an API token a user may use to authenticate an
54 Arvados API request.",
55     "AuthorizedKey" => "Arvados authorized public key
56
57 This resource represents a public key a user may use to authenticate themselves
58 to services on the cluster. Its primary use today is to store SSH keys for
59 virtual machines (\"shell nodes\"). It may be extended to store other keys in
60 the future.",
61     "Collection" => "Arvados data collection
62
63 A collection describes how a set of files is stored in data blocks in Keep,
64 along with associated metadata.",
65     "ComputedPermission" => "Arvados computed permission
66
67 Computed permissions do not correspond directly to any Arvados resource, but
68 provide a simple way to query the entire graph of permissions granted to
69 users and groups.",
70     "ContainerRequest" => "Arvados container request
71
72 A container request represents a user's request that Arvados do some compute
73 work, along with full details about what work should be done. Arvados will
74 attempt to fulfill the request by mapping it to a matching container record,
75 running the work on demand if necessary.",
76     "Container" => "Arvados container record
77
78 A container represents compute work that has been or should be dispatched,
79 along with its results. A container can satisfy one or more container requests.",
80     "Group" => "Arvados group
81
82 Groups provide a way to organize users or data together, depending on their
83 `group_class`.",
84     "KeepService" => "Arvados Keep service
85
86 This resource stores information about a single Keep service in this Arvados
87 cluster that clients can contact to retrieve and store data.",
88     "Link" => "Arvados object link
89
90 A link provides a way to define relationships between Arvados objects,
91 depending on their `link_class`.",
92     "Log" => "Arvados log record
93
94 This resource represents a single log record about an event in this Arvados
95 cluster. Some individual Arvados services create log records. Users can also
96 create custom logs.",
97     "UserAgreement" => "Arvados user agreement
98
99 A user agreement is a collection with terms that users must agree to before
100 they can use this Arvados cluster.",
101     "User" => "Arvados user
102
103 A user represents a single individual or role who may be authorized to access
104 this Arvados cluster.",
105     "VirtualMachine" => "Arvados virtual machine (\"shell node\")
106
107 This resource stores information about a virtual machine or \"shell node\"
108 hosted on this Arvados cluster where users can log in and use preconfigured
109 Arvados client tools.",
110     "Workflow" => "Arvados workflow
111
112 A workflow contains workflow definition source code that Arvados can execute
113 along with associated metadata for users.",
114
115     # This section contains:
116     # * attributes shared across most resources
117     # * attributes shared across Collections and UserAgreements
118     # * attributes shared across Containers and ContainerRequests
119     "command" =>
120     "An array of strings that defines the command that the dispatcher should
121 execute inside this container.",
122     "container_image" =>
123     "The portable data hash of the Arvados collection that contains the image
124 to use for this container.",
125     "created_at" => "The time this %s was created.",
126     "current_version_uuid" => "The UUID of the current version of this %s.",
127     "cwd" =>
128     "A string that the defines the working directory that the dispatcher should
129 use when it executes the command inside this container.",
130     "delete_at" => "The time this %s will be permanently deleted.",
131     "description" =>
132     "A longer HTML description of this %s assigned by a user.
133 Allowed HTML tags are `a`, `b`, `blockquote`, `br`, `code`,
134 `del`, `dd`, `dl`, `dt`, `em`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `hr`,
135 `i`, `img`, `kbd`, `li`, `ol`, `p`, `pre`,
136 `s`, `section`, `span`, `strong`, `sub`, `sup`, and `ul`.",
137     "environment" =>
138     "A hash of string keys and values that defines the environment variables
139 for the dispatcher to set when it executes this container.",
140     "file_count" =>
141     "The number of files represented in this %s's `manifest_text`.
142 This attribute is read-only.",
143     "file_size_total" =>
144     "The total size in bytes of files represented in this %s's `manifest_text`.
145 This attribute is read-only.",
146     "is_trashed" => "A boolean flag to indicate whether or not this %s is trashed.",
147     "manifest_text" =>
148     "The manifest text that describes how files are constructed from data blocks
149 in this %s. Refer to the [manifest format][] reference for details.
150
151 [manifest format]: https://doc.arvados.org/architecture/manifest-format.html
152
153 ",
154     "modified_at" => "The time this %s was last updated.",
155     "modified_by_user_uuid" => "The UUID of the user that last updated this %s.",
156     "mounts" =>
157     "A hash where each key names a directory inside this container, and its
158 value is an object that defines the mount source for that directory. Refer
159 to the [mount types reference][] for details.
160
161 [mount types reference]: https://doc.arvados.org/api/methods/containers.html#mount_types
162
163 ",
164     "name" => "The name of this %s assigned by a user.",
165     "output_glob" =>
166     "An array of strings of shell-style glob patterns that define which file(s)
167 and subdirectory(ies) under the `output_path` directory should be recorded in
168 the container's final output. Refer to the [glob patterns reference][] for details.
169
170 [glob patterns reference]: https://doc.arvados.org/api/methods/containers.html#glob_patterns
171
172 ",
173     "output_path" =>
174     "A string that defines the file or directory path where the command
175 writes output that should be saved from this container.",
176     "output_properties" =>
177 "A hash of arbitrary metadata to set on the output collection of this %s.
178 Some keys may be reserved by Arvados or defined by a configured vocabulary.
179 Refer to the [metadata properties reference][] for details.
180
181 [metadata properties reference]: https://doc.arvados.org/api/properties.html
182
183 ",
184     "output_storage_classes" =>
185     "An array of strings identifying the storage class(es) that should be set
186 on the output collection of this %s. Storage classes are configured by
187 the cluster administrator.",
188     "owner_uuid" => "The UUID of the user or group that owns this %s.",
189     "portable_data_hash" =>
190     "The portable data hash of this %s. This string provides a unique
191 and stable reference to these contents.",
192     "preserve_version" =>
193     "A boolean flag to indicate whether this specific version of this %s
194 should be persisted in cluster storage.",
195     "priority" =>
196     "An integer between 0 and 1000 (inclusive) that represents this %s's
197 scheduling priority. 0 represents a request to be cancelled. Higher
198 values represent higher priority. Refer to the [priority reference][] for details.
199
200 [priority reference]: https://doc.arvados.org/api/methods/container_requests.html#priority
201
202 ",
203     "properties" =>
204     "A hash of arbitrary metadata for this %s.
205 Some keys may be reserved by Arvados or defined by a configured vocabulary.
206 Refer to the [metadata properties reference][] for details.
207
208 [metadata properties reference]: https://doc.arvados.org/api/properties.html
209
210 ",
211     "replication_confirmed" =>
212     "The number of copies of data in this %s that the cluster has confirmed
213 exist in storage.",
214     "replication_confirmed_at" =>
215     "The last time the cluster confirmed that it met `replication_confirmed`
216 for this %s.",
217     "replication_desired" =>
218     "The number of copies that should be made for data in this %s.",
219     "runtime_auth_scopes" =>
220     "The `scopes` from the API client authorization token used to run this %s.",
221     "runtime_constraints" =>
222     "A hash that identifies compute resources this container requires to run
223 successfully. See the [runtime constraints reference][] for details.
224
225 [runtime constraints reference]: https://doc.arvados.org/api/methods/containers.html#runtime_constraints
226
227 ",
228     "runtime_token" =>
229     "The `api_token` from an Arvados API client authorization token that a
230 dispatcher should use to set up this container.",
231     "runtime_user_uuid" =>
232     "The UUID of the Arvados user associated with the API client authorization
233 token used to run this container.",
234     "secret_mounts" =>
235     "A hash like `mounts`, but this attribute is only available through a
236 dedicated API before the container is run.",
237     "scheduling_parameters" =>
238     "A hash of scheduling parameters that should be passed to the underlying
239 dispatcher when this container is run.
240 See the [scheduling parameters reference][] for details.
241
242 [scheduling parameters reference]: https://doc.arvados.org/api/methods/containers.html#scheduling_parameters
243
244 ",
245     "storage_classes_desired" =>
246     "An array of strings identifying the storage class(es) that should be used
247 for data in this %s. Storage classes are configured by the cluster administrator.",
248     "storage_classes_confirmed" =>
249     "An array of strings identifying the storage class(es) the cluster has
250 confirmed have a copy of this %s's data.",
251     "storage_classes_confirmed_at" =>
252     "The last time the cluster confirmed that data was stored on the storage
253 class(es) in `storage_classes_confirmed`.",
254     "trash_at" => "The time this %s will be trashed.",
255
256     "ApiClientAuthorization.api_token" =>
257     "The secret token that can be used to authorize Arvados API requests.",
258     "ApiClientAuthorization.created_by_ip_address" =>
259     "The IP address of the client that created this token.",
260     "ApiClientAuthorization.expires_at" =>
261     "The time after which this token is no longer valid for authorization.",
262     "ApiClientAuthorization.last_used_at" =>
263     "The last time this token was used to authorize a request.",
264     "ApiClientAuthorization.last_used_by_ip_address" =>
265     "The IP address of the client that last used this token.",
266     "ApiClientAuthorization.refreshes_at" =>
267     "The time at which the token will be revalidated if it is a cached token issued by a remote cluster, otherise null.",
268     "ApiClientAuthorization.scopes" =>
269     "An array of strings identifying HTTP methods and API paths this token is
270 authorized to use. Refer to the [scopes reference][] for details.
271
272 [scopes reference]: https://doc.arvados.org/api/tokens.html#scopes
273
274 ",
275     "version" =>
276     "An integer that counts which version of a %s this record
277 represents. Refer to [collection versioning][] for details. This attribute is
278 read-only.
279
280 [collection versioning]: https://doc.arvados.org/user/topics/collection-versioning.html
281
282 ",
283
284     "AuthorizedKey.authorized_user_uuid" =>
285     "The UUID of the Arvados user that is authorized by this key.",
286     "AuthorizedKey.expires_at" =>
287     "The time after which this key is no longer valid for authorization.",
288     "AuthorizedKey.key_type" =>
289     "A string identifying what type of service uses this key. Supported values are:
290
291   * `\"SSH\"`
292
293 ",
294     "AuthorizedKey.public_key" =>
295     "The full public key, in the format referenced by `key_type`.",
296
297     "ComputedPermission.user_uuid" =>
298     "The UUID of the Arvados user who has this permission.",
299     "ComputedPermission.target_uuid" =>
300     "The UUID of the Arvados object the user has access to.",
301     "ComputedPermission.perm_level" =>
302     "A string representing the user's level of access to the target object.
303 Possible values are:
304
305   * `\"can_read\"`
306   * `\"can_write\"`
307   * `\"can_manage\"`
308
309 ",
310
311     "Container.auth_uuid" =>
312     "The UUID of the Arvados API client authorization token that a dispatcher
313 should use to set up this container. This token is automatically created by
314 Arvados and this attribute automatically assigned unless a container is
315 created with `runtime_token`.",
316     "Container.cost" =>
317     "A float with the estimated cost of the cloud instance used to run this
318 container. The value is `0` if cost estimation is not available on this cluster.",
319     "Container.exit_code" =>
320     "An integer that records the Unix exit code of the `command` from a
321 finished container.",
322     "Container.gateway_address" =>
323     "A string with the address of the Arvados gateway server, in `HOST:PORT`
324 format. This is for internal use only.",
325     "Container.interactive_session_started" =>
326     "This flag is set true if any user starts an interactive shell inside the
327 running container.",
328     "Container.lock_count" =>
329     "The number of times this container has been locked by a dispatcher. This
330 may be greater than 1 if a dispatcher locks a container but then execution is
331 interrupted for any reason.",
332     "Container.locked_by_uuid" =>
333     "The UUID of the Arvados API client authorization token that successfully
334 locked this container in preparation to execute it.",
335     "Container.log" =>
336     "The portable data hash of the Arvados collection that contains this
337 container's logs.",
338     "Container.output" =>
339     "The portable data hash of the Arvados collection that contains this
340 container's output file(s).",
341     "Container.progress" =>
342     "A float between 0.0 and 1.0 (inclusive) that represents the container's
343 execution progress. This attribute is not implemented yet.",
344     "Container.runtime_status" =>
345     "A hash with status updates from a running container.
346 Refer to the [runtime status reference][] for details.
347
348 [runtime status reference]: https://doc.arvados.org/api/methods/containers.html#runtime_status
349
350 ",
351     "Container.subrequests_cost" =>
352     "A float with the estimated cost of all cloud instances used to run this
353 container and all its subrequests. The value is `0` if cost estimation is not
354 available on this cluster.",
355     "Container.state" =>
356     "A string representing the container's current execution status. Possible
357 values are:
358
359   * `\"Queued\"` --- This container has not been dispatched yet.
360   * `\"Locked\"` --- A dispatcher has claimed this container in preparation to run it.
361   * `\"Running\"` --- A dispatcher is running this container.
362   * `\"Cancelled\"` --- Container execution has been cancelled by user request.
363   * `\"Complete\"` --- A dispatcher ran this container to completion and recorded the results.
364
365 ",
366     "Container.service" =>
367     "A boolean flag. If set, it informs the system that this is a long-running container
368 that functions as a system service or web app, rather than a once-through batch operation.",
369     "Container.published_ports" =>
370     "A hash where keys are numeric TCP ports on the container which expose HTTP services.  Arvados
371 will proxy HTTP requests to these ports.  Values are hashes with the following keys:
372
373   * `\"access\"` --- One of 'private' or 'public' indicating if an Arvados API token is required to access the endpoint.
374   * `\"label\"` --- A human readable label describing the service, for display in Workbench.
375   * `\"initial_path\"` --- The relative path that should be included when constructing the URL that will be presented to the user in Workbench.",
376
377     "ContainerRequest.auth_uuid" =>
378     "The UUID of the Arvados API client authorization token that a
379 dispatcher should use to set up a corresponding container. This token is
380 automatically created by Arvados and this attribute automatically assigned
381 unless a container request is created with `runtime_token`.",
382     "ContainerRequest.container_count" =>
383     "An integer that records how many times Arvados has attempted to dispatch
384 a container to fulfill this container request.",
385     "ContainerRequest.container_count_max" =>
386     "An integer that defines the maximum number of times Arvados should attempt
387 to dispatch a container to fulfill this container request.",
388     "ContainerRequest.container_uuid" =>
389     "The UUID of the container that fulfills this container request, if any.",
390     "ContainerRequest.cumulative_cost" =>
391     "A float with the estimated cost of all cloud instances used to run
392 container(s) to fulfill this container request and their subrequests.
393 The value is `0` if cost estimation is not available on this cluster.",
394     "ContainerRequest.expires_at" =>
395     "The time after which this %s will no longer be fulfilled.",
396     "ContainerRequest.filters" =>
397     "Filters that limit which existing containers are eligible to satisfy this
398 container request. This attribute is not implemented yet and should be null.",
399     "ContainerRequest.log_uuid" =>
400     "The UUID of the Arvados collection that contains logs for all the
401 container(s) that were dispatched to fulfill this container request.",
402     "ContainerRequest.output_name" =>
403     "The name to set on the output collection of this container request.",
404     "ContainerRequest.output_ttl" =>
405     "An integer in seconds. If greater than zero, when an output collection is
406 created for this container request, its `expires_at` attribute will be set this
407 far in the future.",
408     "ContainerRequest.output_uuid" =>
409     "The UUID of the Arvados collection that contains output for all the
410 container(s) that were dispatched to fulfill this container request.",
411     "ContainerRequest.requesting_container_uuid" =>
412     "The UUID of the container that created this container request, if any.",
413     "ContainerRequest.state" =>
414     "A string indicating where this container request is in its lifecycle.
415 Possible values are:
416
417   * `\"Uncommitted\"` --- The container request has not been finalized and can still be edited.
418   * `\"Committed\"` --- The container request is ready to be fulfilled.
419   * `\"Final\"` --- The container request has been fulfilled or cancelled.
420
421 ",
422     "ContainerRequest.use_existing" =>
423     "A boolean flag. If set, Arvados may choose to satisfy this container
424 request with an eligible container that already exists. Otherwise, Arvados will
425 satisfy this container request with a newer container, which will usually result
426 in the container running again.",
427     "ContainerRequest.service" =>
428     "A boolean flag. If set, it informs the system that this request is for a long-running container
429 that functions as a system service or web app, rather than a once-through batch operation.",
430     "ContainerRequest.published_ports" =>
431     "A hash where keys are numeric TCP ports on the container which expose HTTP services.  Arvados
432 will proxy HTTP requests to these ports.  Values are hashes with the following keys:
433
434   * `\"access\"` --- One of 'private' or 'public' indicating if an Arvados API token is required to access the endpoint.
435   * `\"label\"` --- A human readable label describing the service, for display in Workbench.
436   * `\"initial_path\"` --- The relative path that should be included when constructing the URL that will be presented to the user in Workbench.",
437
438     "Group.group_class" =>
439     "A string representing which type of group this is. One of:
440
441   * `\"filter\"` --- A virtual project whose contents are selected dynamically by filters.
442   * `\"project\"` --- An Arvados project that can contain collections,
443     container records, workflows, and subprojects.
444   * `\"role\"` --- A group of users that can be granted permissions in Arvados.
445
446 ",
447     "Group.frozen_by_uuid" =>
448     "The UUID of the user that has frozen this group, if any. Frozen projects
449 cannot have their contents or metadata changed, even by admins.",
450
451     "KeepService.service_host" => "The DNS hostname of this %s.",
452     "KeepService.service_port" => "The TCP port where this %s listens.",
453     "KeepService.service_ssl_flag" =>
454     "A boolean flag that indicates whether or not this %s uses TLS/SSL.",
455     "KeepService.service_type" =>
456     "A string that describes which type of %s this is. One of:
457
458   * `\"disk\"` --- A service that stores blocks on a local filesystem.
459   * `\"blob\"` --- A service that stores blocks in a cloud object store.
460   * `\"proxy\"` --- A keepproxy service.
461
462 ",
463     "KeepService.read_only" =>
464     "A boolean flag. If set, this %s does not accept requests to write data
465 blocks; it only serves blocks it already has.",
466
467     "Link.head_uuid" =>
468     "The UUID of the Arvados object that is the originator or actor in this
469 relationship. May be null.",
470     "Link.link_class" =>
471     "A string that defines which kind of link this is. One of:
472
473   * `\"permission\"` --- This link grants a permission to the user or group
474     referenced by `head_uuid` to the object referenced by `tail_uuid`. The
475     access level is set by `name`.
476   * `\"star\"` --- This link represents a \"favorite.\" The user referenced
477     by `head_uuid` wants quick access to the object referenced by `tail_uuid`.
478   * `\"tag\"` --- This link represents an unstructured metadata tag. The object
479     referenced by `tail_uuid` has the tag defined by `name`.
480
481 ",
482     "Link.name" =>
483     "The primary value of this link. For `\"permission\"` links, this is one of
484 `\"can_read\"`, `\"can_write\"`, or `\"can_manage\"`.",
485     "Link.tail_uuid" =>
486     "The UUID of the Arvados object that is the target of this relationship.",
487
488     "Log.id" =>
489     "The serial number of this log. You can use this in filters to query logs
490 that were created before/after another.",
491     "Log.event_type" =>
492     "An arbitrary short string that classifies what type of log this is.",
493     "Log.object_owner_uuid" =>
494     "The `owner_uuid` of the object referenced by `object_uuid` at the time
495 this log was created.",
496     "Log.object_uuid" =>
497     "The UUID of the Arvados object that this log pertains to, such as a user
498 or container.",
499     "Log.summary" =>
500     "A text string that describes the logged event. This is the primary
501 attribute for simple logs.",
502
503     "User.email" => "This user's email address.",
504     "User.first_name" => "This user's first name.",
505     "User.identity_url" =>
506     "A URL that represents this user with the cluster's identity provider.",
507     "User.is_active" =>
508     "A boolean flag. If unset, this user is not permitted to make any Arvados
509 API requests.",
510     "User.is_admin" =>
511     "A boolean flag. If set, this user is an administrator of the Arvados
512 cluster, and automatically passes most permissions checks.",
513     "User.last_name" => "This user's last name.",
514     "User.prefs" => "A hash that stores cluster-wide user preferences.",
515     "User.username" => "This user's Unix username on virtual machines.",
516
517     "VirtualMachine.hostname" =>
518     "The DNS hostname where users should access this %s.",
519
520     "Workflow.definition" => "A string with the CWL source of this %s.",
521     "Workflow.collection_uuid" => "The collection this workflow is linked to, containing the definition of the workflow.",
522   }
523
524   def discovery_doc
525     Rails.application.eager_load!
526     remoteHosts = {}
527     Rails.configuration.RemoteClusters.each {|k,v| if k != :"*" then remoteHosts[k] = v["Host"] end }
528     discovery = {
529       kind: "discovery#restDescription",
530       discoveryVersion: "v1",
531       id: "arvados:v1",
532       name: "arvados",
533       version: "v1",
534       # format is YYYYMMDD, must be fixed width (needs to be lexically
535       # sortable), updated manually, may be used by clients to
536       # determine availability of API server features.
537       revision: "20250402",
538       source_version: AppVersion.hash,
539       sourceVersion: AppVersion.hash, # source_version should be deprecated in the future
540       packageVersion: AppVersion.package_version,
541       generatedAt: db_current_time.iso8601,
542       title: "Arvados API",
543       description: "The API to interact with Arvados.",
544       documentationLink: "http://doc.arvados.org/api/index.html",
545       defaultCollectionReplication: Rails.configuration.Collections.DefaultReplication,
546       protocol: "rest",
547       baseUrl: root_url + "arvados/v1/",
548       basePath: "/arvados/v1/",
549       rootUrl: root_url,
550       servicePath: "arvados/v1/",
551       batchPath: "batch",
552       uuidPrefix: Rails.configuration.ClusterID,
553       defaultTrashLifetime: Rails.configuration.Collections.DefaultTrashLifetime,
554       blobSignatureTtl: Rails.configuration.Collections.BlobSigningTTL,
555       maxRequestSize: Rails.configuration.API.MaxRequestSize,
556       maxItemsPerResponse: Rails.configuration.API.MaxItemsPerResponse,
557       dockerImageFormats: Rails.configuration.Containers.SupportedDockerImageFormats.keys,
558       crunchLogUpdatePeriod: Rails.configuration.Containers.Logging.LogUpdatePeriod,
559       crunchLogUpdateSize: Rails.configuration.Containers.Logging.LogUpdateSize,
560       remoteHosts: remoteHosts,
561       remoteHostsViaDNS: Rails.configuration.RemoteClusters["*"].Proxy,
562       websocketUrl: Rails.configuration.Services.Websocket.ExternalURL.to_s,
563       workbenchUrl: Rails.configuration.Services.Workbench1.ExternalURL.to_s,
564       workbench2Url: Rails.configuration.Services.Workbench2.ExternalURL.to_s,
565       keepWebServiceUrl: Rails.configuration.Services.WebDAV.ExternalURL.to_s,
566       parameters: {
567         alt: {
568           type: "string",
569           description: "Data format for the response.",
570           default: "json",
571           enum: [
572             "json"
573           ],
574           enumDescriptions: [
575             "Responses with Content-Type of application/json"
576           ],
577           location: "query"
578         },
579         fields: {
580           type: "string",
581           description: "Selector specifying which fields to include in a partial response.",
582           location: "query"
583         },
584         key: {
585           type: "string",
586           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.",
587           location: "query"
588         },
589         oauth_token: {
590           type: "string",
591           description: "OAuth 2.0 token for the current user.",
592           location: "query"
593         }
594       },
595       auth: {
596         oauth2: {
597           scopes: {
598             "https://api.arvados.org/auth/arvados" => {
599               description: "View and manage objects"
600             },
601             "https://api.arvados.org/auth/arvados.readonly" => {
602               description: "View objects"
603             }
604           }
605         }
606       },
607       schemas: {},
608       resources: {}
609     }
610
611     ActiveRecord::Base.descendants.reject(&:abstract_class?).sort_by(&:to_s).each do |k|
612       begin
613         ctl_class = "Arvados::V1::#{k.to_s.pluralize}Controller".constantize
614       rescue
615         # No controller -> no discovery.
616         next
617       end
618       human_name = ModelHumanNameMap[k.to_s] || k.to_s.underscore.humanize.downcase
619       object_properties = {}
620       k.columns.
621         select { |col| k.selectable_attributes.include? col.name }.
622         collect do |col|
623         if k.serialized_attributes.has_key? col.name
624           col_type = k.serialized_attributes[col.name].object_class.to_s
625         elsif k.attribute_types[col.name].is_a? JsonbType::Hash
626           col_type = Hash.to_s
627         elsif k.attribute_types[col.name].is_a? JsonbType::Array
628           col_type = Array.to_s
629         else
630           col_type = col.type
631         end
632         desc_fmt =
633           SchemaDescriptionMap["#{k}.#{col.name}"] ||
634           SchemaDescriptionMap[col.name] ||
635           ""
636         if k.attribute_types[col.name].type == :datetime
637           desc_fmt += " The string encodes a UTC date and time in ISO 8601 format."
638         end
639         object_properties[col.name] = {
640           description: desc_fmt % human_name,
641           type: col_type,
642         }
643       end
644       discovery[:schemas][k.to_s + 'List'] = {
645         id: k.to_s + 'List',
646         description: "A list of #{k} objects.",
647         type: "object",
648         properties: {
649           kind: {
650             type: "string",
651             description: "Object type. Always arvados##{k.to_s.camelcase(:lower)}List.",
652             default: "arvados##{k.to_s.camelcase(:lower)}List"
653           },
654           etag: {
655             type: "string",
656             description: "List cache version."
657           },
658           items: {
659             type: "array",
660             description: "An array of matching #{k} objects.",
661             items: {
662               "$ref" => k.to_s
663             }
664           },
665         }
666       }
667       discovery[:schemas][k.to_s] = {
668         id: k.to_s,
669         description: SchemaDescriptionMap[k.to_s] || "Arvados #{human_name}.",
670         type: "object",
671         uuidPrefix: nil,
672         properties: {
673           etag: {
674             type: "string",
675             description: "Object cache version."
676           }
677         }.merge(object_properties)
678       }
679       if k.respond_to? :uuid_prefix
680         discovery[:schemas][k.to_s][:uuidPrefix] ||= k.uuid_prefix
681         discovery[:schemas][k.to_s][:properties][:uuid] ||= {
682           type: "string",
683           description: "This #{human_name}'s Arvados UUID, like `zzzzz-#{k.uuid_prefix}-12345abcde67890`."
684         }
685       end
686       discovery[:resources][k.to_s.underscore.pluralize] = {
687         methods: {
688           get: {
689             id: "arvados.#{k.to_s.underscore.pluralize}.get",
690             path: "#{k.to_s.underscore.pluralize}/{uuid}",
691             httpMethod: "GET",
692             description: "Get a #{k.to_s} record by UUID.",
693             parameters: {
694               uuid: {
695                 type: "string",
696                 description: "The UUID of the #{k.to_s} to return.",
697                 required: true,
698                 location: "path"
699               }
700             },
701             parameterOrder: [
702               "uuid"
703             ],
704             response: {
705               "$ref" => k.to_s
706             },
707             scopes: [
708               "https://api.arvados.org/auth/arvados",
709               "https://api.arvados.org/auth/arvados.readonly"
710             ]
711           },
712           list: {
713             id: "arvados.#{k.to_s.underscore.pluralize}.list",
714             path: k.to_s.underscore.pluralize,
715             httpMethod: "GET",
716             description: "Retrieve a #{k.to_s}List.",
717             parameters: {
718             },
719             response: {
720               "$ref" => "#{k.to_s}List"
721             },
722             scopes: [
723               "https://api.arvados.org/auth/arvados",
724               "https://api.arvados.org/auth/arvados.readonly"
725             ]
726           },
727           create: {
728             id: "arvados.#{k.to_s.underscore.pluralize}.create",
729             path: "#{k.to_s.underscore.pluralize}",
730             httpMethod: "POST",
731             description: "Create a new #{k.to_s}.",
732             parameters: {},
733             request: {
734               required: true,
735               properties: {
736                 k.to_s.underscore => {
737                   "$ref" => k.to_s
738                 }
739               }
740             },
741             response: {
742               "$ref" => k.to_s
743             },
744             scopes: [
745               "https://api.arvados.org/auth/arvados"
746             ]
747           },
748           update: {
749             id: "arvados.#{k.to_s.underscore.pluralize}.update",
750             path: "#{k.to_s.underscore.pluralize}/{uuid}",
751             httpMethod: "PUT",
752             description: "Update attributes of an existing #{k.to_s}.",
753             parameters: {
754               uuid: {
755                 type: "string",
756                 description: "The UUID of the #{k.to_s} to update.",
757                 required: true,
758                 location: "path"
759               }
760             },
761             request: {
762               required: true,
763               properties: {
764                 k.to_s.underscore => {
765                   "$ref" => k.to_s
766                 }
767               }
768             },
769             response: {
770               "$ref" => k.to_s
771             },
772             scopes: [
773               "https://api.arvados.org/auth/arvados"
774             ]
775           },
776           delete: {
777             id: "arvados.#{k.to_s.underscore.pluralize}.delete",
778             path: "#{k.to_s.underscore.pluralize}/{uuid}",
779             httpMethod: "DELETE",
780             description: "Delete an existing #{k.to_s}.",
781             parameters: {
782               uuid: {
783                 type: "string",
784                 description: "The UUID of the #{k.to_s} to delete.",
785                 required: true,
786                 location: "path"
787               }
788             },
789             response: {
790               "$ref" => k.to_s
791             },
792             scopes: [
793               "https://api.arvados.org/auth/arvados"
794             ]
795           }
796         }
797       }
798       # Check for Rails routes that don't match the usual actions
799       # listed above
800       d_methods = discovery[:resources][k.to_s.underscore.pluralize][:methods]
801       Rails.application.routes.routes.each do |route|
802         action = route.defaults[:action]
803         httpMethod = ['GET', 'POST', 'PUT', 'DELETE'].map { |method|
804           method if route.verb.match(method)
805         }.compact.first
806         if httpMethod &&
807           route.defaults[:controller] == 'arvados/v1/' + k.to_s.underscore.pluralize &&
808           ctl_class.action_methods.include?(action)
809           method_name = ActionNameMap[action] || action
810           method_key = method_name.to_sym
811           if !d_methods[method_key]
812             method = {
813               id: "arvados.#{k.to_s.underscore.pluralize}.#{method_name}",
814               path: route.path.spec.to_s.sub('/arvados/v1/','').sub('(.:format)','').sub(/:(uu)?id/,'{uuid}'),
815               httpMethod: httpMethod,
816               description: ctl_class.send("_#{method_name}_method_description".to_sym),
817               parameters: {},
818               response: {
819                 "$ref" => (method_name == 'list' ? "#{k.to_s}List" : k.to_s)
820               },
821               scopes: [
822                 "https://api.arvados.org/auth/arvados"
823               ]
824             }
825             route.segment_keys.each do |key|
826               case key
827               when :format
828                 next
829               when :id, :uuid
830                 key = :uuid
831                 description = "The UUID of the #{k} to #{HttpMethodDescriptionMap[httpMethod]}."
832               else
833                 description = ""
834               end
835               method[:parameters][key] = {
836                 type: "string",
837                 description: description,
838                 required: true,
839                 location: "path",
840               }
841             end
842           else
843             # We already built a generic method description, but we
844             # might find some more required parameters through
845             # introspection.
846             method = d_methods[method_key]
847           end
848           if ctl_class.respond_to? "_#{action}_requires_parameters".to_sym
849             ctl_class.send("_#{action}_requires_parameters".to_sym).each do |l, v|
850               if v.is_a? Hash
851                 method[:parameters][l] = v
852               else
853                 method[:parameters][l] = {}
854               end
855               if !method[:parameters][l][:default].nil?
856                 # The JAVA SDK is sensitive to all values being strings
857                 method[:parameters][l][:default] = method[:parameters][l][:default].to_s
858               end
859               method[:parameters][l][:type] ||= 'string'
860               method[:parameters][l][:description] ||= ''
861               method[:parameters][l][:location] = (route.segment_keys.include?(l) ? 'path' : 'query')
862               if method[:parameters][l][:required].nil?
863                 method[:parameters][l][:required] = v != false
864               end
865             end
866           end
867           d_methods[method_key] = method
868         end
869       end
870     end
871
872     # The computed_permissions controller does not offer all of the
873     # usual methods and attributes.  Modify discovery doc accordingly.
874     discovery[:resources]['computed_permissions'][:methods].select! do |method|
875       method == :list
876     end
877     discovery[:resources]['computed_permissions'][:methods][:list][:parameters].reject! do |param|
878       [:cluster_id, :bypass_federation, :offset].include?(param)
879     end
880     discovery[:schemas]['ComputedPermission'].delete(:uuidPrefix)
881     discovery[:schemas]['ComputedPermission'][:properties].reject! do |prop|
882       [:uuid, :etag].include?(prop)
883     end
884     discovery[:schemas]['ComputedPermission'][:properties]['perm_level'][:type] = 'string'
885
886     # The 'replace_files' and 'replace_segments' options are
887     # implemented in lib/controller, not Rails -- we just need to add
888     # them here so discovery-aware clients know how to validate them.
889     [:create, :update].each do |action|
890       discovery[:resources]['collections'][:methods][action][:parameters]['replace_files'] = {
891         type: 'object',
892         description:
893           "Add, delete, and replace files and directories with new content
894 and/or content from other collections. Refer to the
895 [replace_files reference][] for details.
896
897 [replace_files reference]: https://doc.arvados.org/api/methods/collections.html#replace_files
898
899 ",
900         required: false,
901         location: 'query',
902         properties: {},
903         additionalProperties: {type: 'string'},
904       }
905       discovery[:resources]['collections'][:methods][action][:parameters]['replace_segments'] = {
906         type: 'object',
907         description:
908           "Replace existing block segments in the collection with new segments.
909 Refer to the [replace_segments reference][] for details.
910
911 [replace_segments reference]: https://doc.arvados.org/api/methods/collections.html#replace_segments
912
913 ",
914         required: false,
915         location: 'query',
916         properties: {},
917         additionalProperties: {type: 'string'},
918       }
919     end
920
921     discovery[:resources]['configs'] = {
922       methods: {
923         get: {
924           id: "arvados.configs.get",
925           path: "config",
926           httpMethod: "GET",
927           description: "Get this cluster's public configuration settings.",
928           parameters: {
929           },
930           parameterOrder: [
931           ],
932           response: {
933           },
934           scopes: [
935             "https://api.arvados.org/auth/arvados",
936             "https://api.arvados.org/auth/arvados.readonly"
937           ]
938         },
939       }
940     }
941
942     discovery[:resources]['vocabularies'] = {
943       methods: {
944         get: {
945           id: "arvados.vocabularies.get",
946           path: "vocabulary",
947           httpMethod: "GET",
948           description: "Get this cluster's configured vocabulary definition.
949
950 Refer to [metadata vocabulary documentation][] for details.
951
952 [metadata vocabulary documentation]: https://doc.aravdos.org/admin/metadata-vocabulary.html
953
954 ",
955           parameters: {
956           },
957           parameterOrder: [
958           ],
959           response: {
960           },
961           scopes: [
962             "https://api.arvados.org/auth/arvados",
963             "https://api.arvados.org/auth/arvados.readonly"
964           ]
965         },
966       }
967     }
968
969     discovery[:resources]['sys'] = {
970       methods: {
971         get: {
972           id: "arvados.sys.trash_sweep",
973           path: "sys/trash_sweep",
974           httpMethod: "POST",
975           description:
976             "Run scheduled data trash and sweep operations across this cluster's Keep services.",
977           parameters: {
978           },
979           parameterOrder: [
980           ],
981           response: {
982           },
983           scopes: [
984             "https://api.arvados.org/auth/arvados",
985             "https://api.arvados.org/auth/arvados.readonly"
986           ]
987         },
988       }
989     }
990
991     Rails.configuration.API.DisabledAPIs.each do |method, _|
992       ctrl, action = method.to_s.split('.', 2)
993       next if ctrl.in?(['api_clients', 'job_tasks', 'jobs', 'keep_disks', 'nodes', 'pipeline_instances', 'pipeline_templates', 'repositories'])
994       discovery[:resources][ctrl][:methods].delete(action.to_sym)
995     end
996     discovery
997   end
998 end