Initial work adding provenance query to API server
[arvados.git] / services / api / app / controllers / arvados / v1 / collections_controller.rb
1 class Arvados::V1::CollectionsController < ApplicationController
2   def create
3     # Collections are owned by system_user. Creating a collection has
4     # two effects: The collection is added if it doesn't already
5     # exist, and a "permission" Link is added (if one doesn't already
6     # exist) giving the current user (or specified owner_uuid)
7     # permission to read it.
8     owner_uuid = resource_attrs.delete(:owner_uuid) || current_user.uuid
9     owner_kind = if owner_uuid.match(/-(\w+)-/)[1] == User.uuid_prefix
10                    'arvados#user'
11                  else
12                    'arvados#group'
13                  end
14     unless current_user.can? write: owner_uuid
15       raise ArvadosModel::PermissionDeniedError
16     end
17     act_as_system_user do
18       @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
19       begin
20         @object.save!
21       rescue ActiveRecord::RecordNotUnique
22         logger.debug resource_attrs.inspect
23         if resource_attrs[:manifest_text] and resource_attrs[:uuid]
24           @existing_object = model_class.
25             where('uuid=? and manifest_text=?',
26                   resource_attrs[:uuid],
27                   resource_attrs[:manifest_text]).
28             first
29           @object = @existing_object || @object
30         end
31       end
32
33       if @object
34         link_attrs = {
35           owner_uuid: owner_uuid,
36           link_class: 'permission',
37           name: 'can_read',
38           head_kind: 'arvados#collection',
39           head_uuid: @object.uuid,
40           tail_kind: owner_kind,
41           tail_uuid: owner_uuid
42         }
43         ActiveRecord::Base.transaction do
44           if Link.where(link_attrs).empty?
45             Link.create! link_attrs
46           end
47         end
48       end
49     end
50     show
51   end
52
53   def collection_uuid(uuid)
54     m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
55     if m
56       m[1]
57     else
58       nil
59     end
60   end
61
62   def script_param_edges(visited, sp)
63     if sp and not sp.empty?
64       case sp
65       when Hash
66         sp.each do |k, v|
67           script_param_edges(visited, v)
68         end
69       when Array
70         sp.each do |v|
71           script_param_edges(visited, v)
72         end
73       else
74         m = collection_uuid(sp)
75         if m
76           generate_provenance_edges(visited, m)
77         end
78       end
79     end
80     gr
81   end
82
83   def generate_provenance_edges(visited, uuid)
84     m = collection_uuid(uuid)
85
86     if not uuid or uuid.empty? or visited[uuid] or visited[m]
87       return ""
88     end
89
90     #puts "visiting #{uuid}"
91
92     if m  
93       # uuid is a collection
94       uuid = m
95       Collection.where(uuid:"uuid").each do |c|
96         visited[uuid] = c
97       end
98
99       Job.where(output: uuid).each do |job|
100         generate_provenance_edges(visited, job.uuid)
101       end
102
103       Job.where(log: uuid).each do |job|
104         generate_provenance_edges(visited, job.uuid)
105       end
106       
107     else
108       visited[uuid] = true
109
110       # uuid is something else
111       rsc = ArvadosBase::resource_class_for_uuid uuid
112
113       if rsc == Job
114         Job.where(uuid: uuid).each do |job|
115           visited[uuid] = job
116           script_param_edges(visited, job, "", job.script_parameters)
117         end
118     end
119
120     Link.where(head_uuid: uuid, link_class: "provenance").each do |link|
121       generate_provenance_edges(visited, link.tail_uuid)
122     end
123
124     #puts "finished #{uuid}"
125
126     gr
127   end
128
129   def provenance 
130     visited = {}
131     generate_provenance_edges(visited, @object.uuid)
132     visited
133   end
134
135   protected
136
137   def find_object_by_uuid
138     super
139     if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
140       # Normalize the given uuid and search again.
141       hash_part = params[:uuid].match(/^([0-9a-f]*)/)[1]
142       collection = Collection.where('uuid like ?', hash_part + '+%').first
143       if collection
144         # We know the collection exists, and what its real uuid is in
145         # the database. Now, throw out @objects and repeat the usual
146         # lookup procedure. (Returning the collection at this point
147         # would bypass permission checks.)
148         @objects = nil
149         @where = { uuid: collection.uuid }
150         find_objects_for_index
151         @object = @objects.first
152       end
153     end
154   end
155 end