Merge branch 'master' into 3036-collection-uuids
[arvados.git] / services / api / app / models / collection.rb
index ac845f50adc71e5e95d4eded09779be72ffdca46..c0c2b7ef7d3af5c21e67fb51bebde066907d7276 100644 (file)
@@ -3,9 +3,16 @@ class Collection < ArvadosModel
   include KindAndEtag
   include CommonApiTemplate
 
+  before_validation :set_portable_data_hash
+  validate :ensure_hash_matches_manifest_text
+
   api_accessible :user, extend: :common do |t|
     t.add :data_size
     t.add :files
+    t.add :name
+    t.add :description
+    t.add :properties
+    t.add :portable_data_hash
     t.add :manifest_text
   end
 
@@ -15,6 +22,34 @@ class Collection < ArvadosModel
                 })
   end
 
+  def set_portable_data_hash
+    if (self.portable_data_hash.nil? or (self.portable_data_hash == "") or (manifest_text_changed? and !portable_data_hash_changed?))
+      self.portable_data_hash = "#{Digest::MD5.hexdigest(manifest_text)}+#{manifest_text.length}"
+    elsif portable_data_hash_changed?
+      begin
+        loc = Locator.parse!(self.portable_data_hash)
+        loc.strip_hints!
+        self.portable_data_hash = loc.to_s
+      rescue ArgumentError => e
+        errors.add(:portable_data_hash, "#{e}")
+        return false
+      end
+    end
+    true
+  end
+
+  def ensure_hash_matches_manifest_text
+    if manifest_text_changed? or portable_data_hash_changed?
+      computed_hash = "#{Digest::MD5.hexdigest(manifest_text)}+#{manifest_text.length}"
+      unless computed_hash == portable_data_hash
+        logger.debug "(computed) '#{computed_hash}' != '#{portable_data_hash}' (provided)"
+        errors.add(:portable_data_hash, "does not match hash of manifest_text")
+        return false
+      end
+    end
+    true
+  end
+
   def redundancy_status
     if redundancy_confirmed_as.nil?
       'unconfirmed'
@@ -31,39 +66,6 @@ class Collection < ArvadosModel
     end
   end
 
-  def assign_uuid
-    if not self.manifest_text
-      errors.add :manifest_text, 'not supplied'
-      return false
-    end
-    expect_uuid = Digest::MD5.hexdigest(self.manifest_text)
-    if self.uuid
-      self.uuid.gsub! /\+.*/, ''
-      if self.uuid != expect_uuid
-        errors.add :uuid, 'must match checksum of manifest_text'
-        return false
-      end
-    else
-      self.uuid = expect_uuid
-    end
-    self.uuid.gsub! /$/, '+' + self.manifest_text.length.to_s
-    true
-  end
-
-  # TODO (#3036/tom) replace above assign_uuid method with below assign_uuid and self.generate_uuid
-  # def assign_uuid
-  #   # Even admins cannot assign collection uuids.
-  #   self.uuid = self.class.generate_uuid
-  # end
-  # def self.generate_uuid
-  #   # The last 10 characters of a collection uuid are the last 10
-  #   # characters of the base-36 SHA256 digest of manifest_text.
-  #   [Server::Application.config.uuid_prefix,
-  #    self.uuid_prefix,
-  #    rand(2**256).to_s(36)[-5..-1] + Digest::SHA256.hexdigest(self.manifest_text).to_i(16).to_s(36)[-10..-1],
-  #   ].join '-'
-  # end
-
   def data_size
     inspect_manifest_text if @data_size.nil? or manifest_text_changed?
     @data_size
@@ -134,10 +136,6 @@ class Collection < ArvadosModel
     end
   end
 
-  def self.uuid_like_pattern
-    "________________________________+%"
-  end
-
   def self.normalize_uuid uuid
     hash_part = nil
     size_part = nil
@@ -166,13 +164,18 @@ class Collection < ArvadosModel
     # that looks like a Docker image, return it.
     if loc = Locator.parse(search_term)
       loc.strip_hints!
-      coll_match = readable_by(*readers).where(uuid: loc.to_s).first
+      coll_match = readable_by(*readers).where(portable_data_hash: loc.to_s).limit(1).first
       if coll_match and (coll_match.files.size == 1) and
           (coll_match.files[0][1] =~ /^[0-9A-Fa-f]{64}\.tar$/)
-        return [loc.to_s]
+        return [coll_match.uuid]
       end
     end
 
+    if search_tag.nil? and (n = search_term.index(":"))
+      search_tag = search_term[n+1..-1]
+      search_term = search_term[0..n-1]
+    end
+
     # Find Collections with matching Docker image repository+tag pairs.
     matches = base_search.
       where(link_class: "docker_image_repo+tag",
@@ -181,7 +184,7 @@ class Collection < ArvadosModel
     # If that didn't work, find Collections with matching Docker image hashes.
     if matches.empty?
       matches = base_search.
-        where("link_class = ? and name LIKE ?",
+        where("link_class = ? and links.name LIKE ?",
               "docker_image_hash", "#{search_term}%")
     end
 
@@ -190,7 +193,8 @@ class Collection < ArvadosModel
     # anything without; then we use the link's created_at as a tiebreaker.
     uuid_timestamps = {}
     matches.find_each do |link|
-      uuid_timestamps[link.head_uuid] =
+      c = Collection.find_by_uuid(link.head_uuid)
+      uuid_timestamps[c.uuid] =
         [(-link.properties["image_timestamp"].to_datetime.to_i rescue 0),
          -link.created_at.to_i]
     end