Merge branch '21535-multi-wf-delete'
[arvados.git] / services / api / lib / config_loader.rb
index 5cb6e28419e1ff3b35b6e86cd68d6aac49e83e91..1d897b39bf4de9af98b4710035199ce831a97d5c 100644 (file)
@@ -2,6 +2,16 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
+# When loading YAML, deserialize :foo as ":foo", rather than raising
+# "Psych::DisallowedClass: Tried to load unspecified class: Symbol"
+class Psych::ScalarScanner
+  alias :orig_tokenize :tokenize
+  def tokenize string
+    return string if string =~ /^:[a-zA-Z]/
+    orig_tokenize(string)
+  end
+end
+
 module Psych
   module Visitors
     class YAMLTree < Psych::Visitors::Visitor
@@ -76,7 +86,7 @@ class ConfigLoader
     remainders
   end
 
-  def coercion_and_check check_cfg
+  def coercion_and_check check_cfg, check_nonempty: true
     @config_types.each do |cfgkey, cfgtype|
       cfg = check_cfg
       k = cfgkey
@@ -114,7 +124,7 @@ class ConfigLoader
       end
 
       if cfgtype == NonemptyString
-        if (!cfg[k] || cfg[k] == "")
+        if (!cfg[k] || cfg[k] == "") && check_nonempty
           raise "#{cfgkey} cannot be empty"
         end
         if cfg[k].is_a? String
@@ -126,7 +136,7 @@ class ConfigLoader
         if cfg[k].is_a? Integer
           cfg[k] = cfg[k].seconds
         elsif cfg[k].is_a? String
-          cfg[k] = ConfigLoader.parse_duration cfg[k]
+          cfg[k] = ConfigLoader.parse_duration(cfg[k], cfgkey: cfgkey)
         end
       end
 
@@ -134,6 +144,31 @@ class ConfigLoader
         cfg[k] = URI(cfg[k])
       end
 
+      if cfgtype == Integer && cfg[k].is_a?(String)
+        v = cfg[k].sub(/B\s*$/, '')
+        if mt = /(-?\d*\.?\d+)\s*([KMGTPE]i?)$/.match(v)
+          if mt[1].index('.')
+            v = mt[1].to_f
+          else
+            v = mt[1].to_i
+          end
+          cfg[k] = v * {
+            'K' => 1000,
+            'Ki' => 1 << 10,
+            'M' => 1000000,
+            'Mi' => 1 << 20,
+            "G" =>  1000000000,
+            "Gi" => 1 << 30,
+            "T" =>  1000000000000,
+            "Ti" => 1 << 40,
+            "P" =>  1000000000000000,
+            "Pi" => 1 << 50,
+            "E" =>  1000000000000000000,
+            "Ei" => 1 << 60,
+          }[mt[2]]
+        end
+      end
+
       if !cfg[k].is_a? cfgtype
         raise "#{cfgkey} expected #{cfgtype} but was #{cfg[k].class}"
       end
@@ -155,16 +190,21 @@ class ConfigLoader
     end
   end
 
-  def self.parse_duration durstr
+  def self.parse_duration(durstr, cfgkey:)
+    sign = 1
+    if durstr[0] == '-'
+      durstr = durstr[1..-1]
+      sign = -1
+    end
     duration_re = /(\d+(\.\d+)?)(s|m|h)/
     dursec = 0
     while durstr != ""
       mt = duration_re.match durstr
       if !mt
-        raise "#{cfgkey} not a valid duration: '#{cfg[k]}', accepted suffixes are s, m, h"
+        raise "#{cfgkey} not a valid duration: '#{durstr}', accepted suffixes are s, m, h"
       end
       multiplier = {s: 1, m: 60, h: 3600}
-      dursec += (Float(mt[1]) * multiplier[mt[3].to_sym])
+      dursec += (Float(mt[1]) * multiplier[mt[3].to_sym] * sign)
       durstr = durstr[mt[0].length..-1]
     end
     return dursec.seconds
@@ -190,9 +230,16 @@ class ConfigLoader
     end
   end
 
-  def self.load path
-    yaml = ERB.new(IO.read path).result(binding)
-    YAML.load(yaml, deserialize_symbols: false)
+  def self.load path, erb: false
+    if File.exist? path
+      yaml = IO.read path
+      if erb
+        yaml = ERB.new(yaml).result(binding)
+      end
+      YAML.safe_load(yaml)
+    else
+      {}
+    end
   end
 
 end