13996: Adjust config:dump to dump active 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
28       def visit_URI_Generic o
29         @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
30       end
31
32       def visit_URI_HTTP o
33         @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
34       end
35
36       def visit_Pathname o
37         @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
38       end
39     end
40   end
41 end
42
43 def set_cfg cfg, k, v
44   # "foo.bar = baz" --> { cfg["foo"]["bar"] = baz }
45   ks = k.split '.'
46   k = ks.pop
47   ks.each do |kk|
48     cfg = cfg[kk]
49     if cfg.nil?
50       break
51     end
52   end
53   if !cfg.nil?
54     cfg[k] = v
55   end
56 end
57
58 $config_migrate_map = {}
59 $config_types = {}
60 def declare_config(assign_to, configtype, migrate_from=nil, migrate_fn=nil)
61   if migrate_from
62     $config_migrate_map[migrate_from] = migrate_fn || ->(cfg, k, v) {
63       set_cfg cfg, assign_to, v
64     }
65   end
66   $config_types[assign_to] = configtype
67 end
68
69 module Boolean; end
70 class TrueClass; include Boolean; end
71 class FalseClass; include Boolean; end
72
73 class NonemptyString < String
74 end
75
76 def parse_duration durstr
77   duration_re = /(\d+(\.\d+)?)(s|m|h)/
78   dursec = 0
79   while durstr != ""
80     mt = duration_re.match durstr
81     if !mt
82       raise "#{cfgkey} not a valid duration: '#{cfg[k]}', accepted suffixes are s, m, h"
83     end
84     multiplier = {s: 1, m: 60, h: 3600}
85     dursec += (Float(mt[1]) * multiplier[mt[3].to_sym])
86     durstr = durstr[mt[0].length..-1]
87   end
88   return dursec.seconds
89 end
90
91 def migrate_config from_config, to_config
92   remainders = {}
93   from_config.each do |k, v|
94     if $config_migrate_map[k.to_sym]
95       $config_migrate_map[k.to_sym].call to_config, k, v
96     else
97       remainders[k] = v
98     end
99   end
100   remainders
101 end
102
103 def coercion_and_check check_cfg
104   $config_types.each do |cfgkey, cfgtype|
105     cfg = check_cfg
106     k = cfgkey
107     ks = k.split '.'
108     k = ks.pop
109     ks.each do |kk|
110       cfg = cfg[kk]
111       if cfg.nil?
112         break
113       end
114     end
115
116     if cfg.nil?
117       raise "missing #{cfgkey}"
118     end
119
120     if cfgtype == String and !cfg[k]
121       cfg[k] = ""
122     end
123
124     if cfgtype == String and cfg[k].is_a? Symbol
125       cfg[k] = cfg[k].to_s
126     end
127
128     if cfgtype == Pathname and cfg[k].is_a? String
129
130       if cfg[k] == ""
131         cfg[k] = Pathname.new("")
132       else
133         cfg[k] = Pathname.new(cfg[k])
134         if !cfg[k].exist?
135           raise "#{cfgkey} path #{cfg[k]} does not exist"
136         end
137       end
138     end
139
140     if cfgtype == NonemptyString
141       if (!cfg[k] || cfg[k] == "")
142         raise "#{cfgkey} cannot be empty"
143       end
144       if cfg[k].is_a? String
145         next
146       end
147     end
148
149     if cfgtype == ActiveSupport::Duration
150       if cfg[k].is_a? Integer
151         cfg[k] = cfg[k].seconds
152       elsif cfg[k].is_a? String
153         cfg[k] = parse_duration cfg[k]
154       end
155     end
156
157     if cfgtype == URI
158       cfg[k] = URI(cfg[k])
159     end
160
161     if !cfg[k].is_a? cfgtype
162       raise "#{cfgkey} expected #{cfgtype} but was #{cfg[k].class}"
163     end
164   end
165
166 end
167
168 def copy_into_config src, dst
169   src.each do |k, v|
170     dst.send "#{k}=", Marshal.load(Marshal.dump v)
171   end
172 end