13996: Migrate tests to new config
[arvados.git] / services / api / lib / config_loader.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 module Psych
6   module Visitors
7     class YAMLTree < Psych::Visitors::Visitor
8       def visit_ActiveSupport_Duration o
9         seconds = o.to_i
10         outstr = ""
11         if seconds / 3600 > 0
12           outstr += "#{seconds / 3600}h"
13           seconds = seconds % 3600
14         end
15         if seconds / 60 > 0
16           outstr += "#{seconds / 60}m"
17           seconds = seconds % 60
18         end
19         if seconds > 0
20           outstr += "#{seconds}s"
21         end
22         if outstr == ""
23           outstr = "0s"
24         end
25         @emitter.scalar outstr, nil, nil, true, false, Nodes::Scalar::ANY
26       end
27     end
28   end
29 end
30
31 def set_cfg cfg, k, v
32   # "foo.bar = baz" --> { cfg["foo"]["bar"] = baz }
33   ks = k.split '.'
34   k = ks.pop
35   ks.each do |kk|
36     cfg = cfg[kk]
37     if cfg.nil?
38       break
39     end
40   end
41   if !cfg.nil?
42     cfg[k] = v
43   end
44 end
45
46 $config_migrate_map = {}
47 $config_types = {}
48 def declare_config(assign_to, configtype, migrate_from=nil, migrate_fn=nil)
49   if migrate_from
50     $config_migrate_map[migrate_from] = migrate_fn || ->(cfg, k, v) {
51       set_cfg cfg, assign_to, v
52     }
53   end
54   $config_types[assign_to] = configtype
55 end
56
57 module Boolean; end
58 class TrueClass; include Boolean; end
59 class FalseClass; include Boolean; end
60
61 class NonemptyString < String
62 end
63
64 def parse_duration durstr
65   duration_re = /(\d+(\.\d+)?)(s|m|h)/
66   dursec = 0
67   while durstr != ""
68     mt = duration_re.match durstr
69     if !mt
70       raise "#{cfgkey} not a valid duration: '#{cfg[k]}', accepted suffixes are s, m, h"
71     end
72     multiplier = {s: 1, m: 60, h: 3600}
73     dursec += (Float(mt[1]) * multiplier[mt[3].to_sym])
74     durstr = durstr[mt[0].length..-1]
75   end
76   return dursec.seconds
77 end
78
79 def migrate_config from_config, to_config
80   remainders = {}
81   from_config.each do |k, v|
82     if $config_migrate_map[k.to_sym]
83       $config_migrate_map[k.to_sym].call to_config, k, v
84     else
85       remainders[k] = v
86     end
87   end
88   remainders
89 end
90
91 def coercion_and_check check_cfg
92   $config_types.each do |cfgkey, cfgtype|
93     cfg = check_cfg
94     k = cfgkey
95     ks = k.split '.'
96     k = ks.pop
97     ks.each do |kk|
98       cfg = cfg[kk]
99       if cfg.nil?
100         break
101       end
102     end
103
104     if cfg.nil?
105       raise "missing #{cfgkey}"
106     end
107
108     if cfgtype == String and !cfg[k]
109       cfg[k] = ""
110     end
111
112     if cfgtype == NonemptyString
113       if (!cfg[k] || cfg[k] == "")
114         raise "#{cfgkey} cannot be empty"
115       end
116       if cfg[k].is_a? String
117         next
118       end
119     end
120
121     if cfgtype == ActiveSupport::Duration
122       if cfg[k].is_a? Integer
123         cfg[k] = cfg[k].seconds
124       elsif cfg[k].is_a? String
125         cfg[k] = parse_duration cfg[k]
126       end
127     end
128
129     if cfgtype == URI
130       cfg[k] = URI(cfg[k])
131     end
132
133     if !cfg[k].is_a? cfgtype
134       raise "#{cfgkey} expected #{cfgtype} but was #{cfg[k].class}"
135     end
136   end
137
138 end
139
140 def copy_into_config src, dst
141   src.each do |k, v|
142     dst.send "#{k}=", Marshal.load(Marshal.dump v)
143   end
144 end