15003: Split out deprecated config stuff.
[arvados.git] / lib / config / load.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package config
6
7 import (
8         "bytes"
9         "encoding/json"
10         "errors"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "os"
15
16         "git.curoverse.com/arvados.git/sdk/go/arvados"
17         "github.com/ghodss/yaml"
18         "github.com/imdario/mergo"
19 )
20
21 type logger interface {
22         Warnf(string, ...interface{})
23 }
24
25 func LoadFile(path string, log logger) (*arvados.Config, error) {
26         f, err := os.Open(path)
27         if err != nil {
28                 return nil, err
29         }
30         defer f.Close()
31         return Load(f, log)
32 }
33
34 func Load(rdr io.Reader, log logger) (*arvados.Config, error) {
35         var cfg arvados.Config
36         buf, err := ioutil.ReadAll(rdr)
37         if err != nil {
38                 return nil, err
39         }
40
41         // Load the config into a dummy map to get the cluster ID
42         // keys, discarding the values; then set up defaults for each
43         // cluster ID; then load the real config on top of the
44         // defaults.
45         var dummy struct {
46                 Clusters map[string]struct{}
47         }
48         err = yaml.Unmarshal(buf, &dummy)
49         if err != nil {
50                 return nil, err
51         }
52         if len(dummy.Clusters) == 0 {
53                 return nil, errors.New("config does not define any clusters")
54         }
55
56         // We can't merge deep structs here; instead, we unmarshal the
57         // default & loaded config files into generic maps, merge
58         // those, and then json-encode+decode the result into the
59         // config struct type.
60         var merged map[string]interface{}
61         for id := range dummy.Clusters {
62                 var src map[string]interface{}
63                 err = yaml.Unmarshal(bytes.Replace(DefaultYAML, []byte(" xxxxx:"), []byte(" "+id+":"), -1), &src)
64                 if err != nil {
65                         return nil, fmt.Errorf("loading defaults for %s: %s", id, err)
66                 }
67                 mergo.Merge(&merged, src, mergo.WithOverride)
68         }
69         var src map[string]interface{}
70         err = yaml.Unmarshal(buf, &src)
71         if err != nil {
72                 return nil, fmt.Errorf("loading config data: %s", err)
73         }
74         mergo.Merge(&merged, src, mergo.WithOverride)
75
76         var errEnc error
77         pr, pw := io.Pipe()
78         go func() {
79                 errEnc = json.NewEncoder(pw).Encode(merged)
80                 pw.Close()
81         }()
82         err = json.NewDecoder(pr).Decode(&cfg)
83         if errEnc != nil {
84                 err = errEnc
85         }
86         if err != nil {
87                 return nil, fmt.Errorf("transcoding config data: %s", err)
88         }
89
90         err = applyDeprecatedConfig(&cfg, buf, log)
91         if err != nil {
92                 return nil, err
93         }
94         return &cfg, nil
95 }