* Added used_by query for collections
[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       logger.warn "User #{current_user.andand.uuid} tried to set collection owner_uuid to #{owner_uuid}"
16       raise ArvadosModel::PermissionDeniedError
17     end
18     act_as_system_user do
19       @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
20       begin
21         @object.save!
22       rescue ActiveRecord::RecordNotUnique
23         logger.debug resource_attrs.inspect
24         if resource_attrs[:manifest_text] and resource_attrs[:uuid]
25           @existing_object = model_class.
26             where('uuid=? and manifest_text=?',
27                   resource_attrs[:uuid],
28                   resource_attrs[:manifest_text]).
29             first
30           @object = @existing_object || @object
31         end
32       end
33
34       if @object
35         link_attrs = {
36           owner_uuid: owner_uuid,
37           link_class: 'permission',
38           name: 'can_read',
39           head_kind: 'arvados#collection',
40           head_uuid: @object.uuid,
41           tail_kind: owner_kind,
42           tail_uuid: owner_uuid
43         }
44         ActiveRecord::Base.transaction do
45           if Link.where(link_attrs).empty?
46             Link.create! link_attrs
47           end
48         end
49       end
50     end
51     show
52   end
53
54   def collection_uuid(uuid)
55     m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
56     if m
57       m[1]
58     else
59       nil
60     end
61   end
62
63   def script_param_edges(visited, sp)
64     if sp and not sp.empty?
65       case sp
66       when Hash
67         sp.each do |k, v|
68           script_param_edges(visited, v)
69         end
70       when Array
71         sp.each do |v|
72           script_param_edges(visited, v)
73         end
74       else
75         m = collection_uuid(sp)
76         if m
77           generate_provenance_edges(visited, m)
78         end
79       end
80     end
81   end
82
83   def generate_provenance_edges(visited, uuid)
84     m = collection_uuid(uuid)
85     uuid = m if m
86
87     if not uuid or uuid.empty? or visited[uuid]
88       return ""
89     end
90
91     logger.debug "visiting #{uuid}"
92
93     if m  
94       # uuid is a collection
95       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
96         visited[uuid] = c.as_api_response
97         visited[uuid][:files] = []
98         c.files.each do |f|
99           visited[uuid][:files] << f
100         end
101       end
102
103       Job.readable_by(current_user).where(output: uuid).each do |job|
104         generate_provenance_edges(visited, job.uuid)
105       end
106
107       Job.readable_by(current_user).where(log: uuid).each do |job|
108         generate_provenance_edges(visited, job.uuid)
109       end
110       
111     else
112       # uuid is something else
113       rsc = ArvadosModel::resource_class_for_uuid uuid
114       if rsc == Job
115         Job.readable_by(current_user).where(uuid: uuid).each do |job|
116           visited[uuid] = job.as_api_response
117           script_param_edges(visited, job.script_parameters)
118         end
119       elsif rsc != nil
120         rsc.where(uuid: uuid).each do |r|
121           visited[uuid] = r.as_api_response
122         end
123       end
124     end
125
126     Link.readable_by(current_user).
127       where(head_uuid: uuid, link_class: "provenance").
128       each do |link|
129       visited[link.uuid] = link.as_api_response
130       generate_provenance_edges(visited, link.tail_uuid)
131     end
132
133     #puts "finished #{uuid}"
134   end
135
136   def provenance
137     visited = {}
138     generate_provenance_edges(visited, @object[:uuid])
139     render json: visited
140   end
141
142   def generate_used_by_edges(visited, uuid)
143     m = collection_uuid(uuid)
144     uuid = m if m
145
146     if not uuid or uuid.empty? or visited[uuid]
147       return ""
148     end
149
150     logger.debug "visiting #{uuid}"
151
152     if m  
153       # uuid is a collection
154       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
155         visited[uuid] = c.as_api_response
156         visited[uuid][:files] = []
157         c.files.each do |f|
158           visited[uuid][:files] << f
159         end
160       end
161
162       if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
163         # special case for empty collection
164         return
165       end
166
167       Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
168         generate_used_by_edges(visited, job.uuid)
169       end
170       
171     else
172       # uuid is something else
173       rsc = ArvadosModel::resource_class_for_uuid uuid
174       if rsc == Job
175         Job.readable_by(current_user).where(uuid: uuid).each do |job|
176           visited[uuid] = job.as_api_response
177           generate_used_by_edges(visited, job.output)
178         end
179       elsif rsc != nil
180         rsc.where(uuid: uuid).each do |r|
181           visited[uuid] = r.as_api_response
182         end
183       end
184     end
185
186     Link.readable_by(current_user).
187       where(tail_uuid: uuid, link_class: "provenance").
188       each do |link|
189       visited[link.uuid] = link.as_api_response
190       generate_used_by_edges(visited, link.head_uuid)
191     end
192
193     #puts "finished #{uuid}"
194   end
195
196   def used_by
197     visited = {}
198     generate_used_by_edges(visited, @object[:uuid])
199     render json: visited
200   end
201
202   protected
203   def find_object_by_uuid
204     super
205     if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
206       # Normalize the given uuid and search again.
207       hash_part = params[:uuid].match(/^([0-9a-f]*)/)[1]
208       collection = Collection.where('uuid like ?', hash_part + '+%').first
209       if collection
210         # We know the collection exists, and what its real uuid is in
211         # the database. Now, throw out @objects and repeat the usual
212         # lookup procedure. (Returning the collection at this point
213         # would bypass permission checks.)
214         @objects = nil
215         @where = { uuid: collection.uuid }
216         find_objects_for_index
217         @object = @objects.first
218       end
219     end
220   end
221
222 end