15003: Add missed error checks.
[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         buf, err := ioutil.ReadAll(rdr)
36         if err != nil {
37                 return nil, err
38         }
39
40         // Load the config into a dummy map to get the cluster ID
41         // keys, discarding the values; then set up defaults for each
42         // cluster ID; then load the real config on top of the
43         // defaults.
44         var dummy struct {
45                 Clusters map[string]struct{}
46         }
47         err = yaml.Unmarshal(buf, &dummy)
48         if err != nil {
49                 return nil, err
50         }
51         if len(dummy.Clusters) == 0 {
52                 return nil, errors.New("config does not define any clusters")
53         }
54
55         // We can't merge deep structs here; instead, we unmarshal the
56         // default & loaded config files into generic maps, merge
57         // those, and then json-encode+decode the result into the
58         // config struct type.
59         var merged map[string]interface{}
60         for id := range dummy.Clusters {
61                 var src map[string]interface{}
62                 err = yaml.Unmarshal(bytes.Replace(DefaultYAML, []byte(" xxxxx:"), []byte(" "+id+":"), -1), &src)
63                 if err != nil {
64                         return nil, fmt.Errorf("loading defaults for %s: %s", id, err)
65                 }
66                 err = mergo.Merge(&merged, src, mergo.WithOverride)
67                 if err != nil {
68                         return nil, fmt.Errorf("merging defaults for %s: %s", id, err)
69                 }
70         }
71         var src map[string]interface{}
72         err = yaml.Unmarshal(buf, &src)
73         if err != nil {
74                 return nil, fmt.Errorf("loading config data: %s", err)
75         }
76         err = mergo.Merge(&merged, src, mergo.WithOverride)
77         if err != nil {
78                 return nil, fmt.Errorf("merging config data: %s", err)
79         }
80
81         // map[string]interface{} => json => arvados.Config
82         var cfg arvados.Config
83         var errEnc error
84         pr, pw := io.Pipe()
85         go func() {
86                 errEnc = json.NewEncoder(pw).Encode(merged)
87                 pw.Close()
88         }()
89         err = json.NewDecoder(pr).Decode(&cfg)
90         if errEnc != nil {
91                 err = errEnc
92         }
93         if err != nil {
94                 return nil, fmt.Errorf("transcoding config data: %s", err)
95         }
96
97         err = applyDeprecatedConfig(&cfg, buf, log)
98         if err != nil {
99                 return nil, err
100         }
101         return &cfg, nil
102 }