1package setting
2
3import (
4	"fmt"
5	"io/ioutil"
6	"os"
7	"regexp"
8	"sort"
9	"strings"
10
11	"gopkg.in/ini.v1"
12)
13
14type Expander interface {
15	SetupExpander(file *ini.File) error
16	Expand(string) (string, error)
17}
18
19type registeredExpander struct {
20	name     string
21	priority int64
22	expander Expander
23}
24
25var expanders = []registeredExpander{
26	{
27		name:     "env",
28		priority: -10,
29		expander: envExpander{},
30	},
31	{
32		name:     "file",
33		priority: -5,
34		expander: fileExpander{},
35	},
36}
37
38func AddExpander(name string, priority int64, e Expander) {
39	expanders = append(expanders, registeredExpander{
40		name:     name,
41		priority: priority,
42		expander: e,
43	})
44}
45
46var regex = regexp.MustCompile(`\$(|__\w+){([^}]+)}`)
47
48func expandConfig(file *ini.File) error {
49	sort.Slice(expanders, func(i, j int) bool {
50		return expanders[i].priority < expanders[j].priority
51	})
52
53	for _, expander := range expanders {
54		err := expander.expander.SetupExpander(file)
55		if err != nil {
56			return fmt.Errorf("got error during initilazation of expander '%s': %w", expander.name, err)
57		}
58
59		for _, section := range file.Sections() {
60			for _, key := range section.Keys() {
61				updated, err := applyExpander(key.Value(), expander)
62				if err != nil {
63					return fmt.Errorf("got error while expanding %s.%s with expander '%s': %w",
64						section.Name(),
65						key.Name(),
66						expander.name,
67						err)
68				}
69
70				key.SetValue(updated)
71			}
72		}
73	}
74	return nil
75}
76
77func ExpandVar(s string) (string, error) {
78	for _, expander := range expanders {
79		var err error
80		s, err = applyExpander(s, expander)
81		if err != nil {
82			return "", fmt.Errorf("got error while expanding expander %s: %w", expander.name, err)
83		}
84	}
85	return s, nil
86}
87
88func applyExpander(s string, e registeredExpander) (string, error) {
89	matches := regex.FindAllStringSubmatch(s, -1)
90
91	for _, match := range matches {
92		if len(match) < 3 {
93			return "", fmt.Errorf("regex error, got %d results back for match, expected 3", len(match))
94		}
95
96		_, isEnv := e.expander.(envExpander)
97		if match[1] == "__"+e.name || (match[1] == "" && isEnv) {
98			updated, err := e.expander.Expand(match[2])
99			if err != nil {
100				return "", err
101			}
102
103			s = strings.Replace(s, match[0], updated, 1)
104		}
105	}
106
107	return s, nil
108}
109
110type envExpander struct {
111}
112
113func (e envExpander) SetupExpander(file *ini.File) error {
114	return nil
115}
116
117func (e envExpander) Expand(s string) (string, error) {
118	envValue := os.Getenv(s)
119
120	// if env variable is hostname and it is empty use os.Hostname as default
121	if s == "HOSTNAME" && envValue == "" {
122		return os.Hostname()
123	}
124
125	return os.Getenv(s), nil
126}
127
128type fileExpander struct {
129}
130
131func (e fileExpander) SetupExpander(file *ini.File) error {
132	return nil
133}
134
135func (e fileExpander) Expand(s string) (string, error) {
136	_, err := os.Stat(s)
137	if err != nil {
138		return "", err
139	}
140
141	// nolint:gosec
142	// We can ignore the gosec G304 warning on this one because `s` comes from configuration section keys
143	f, err := ioutil.ReadFile(s)
144	if err != nil {
145		return "", err
146	}
147
148	return strings.TrimSpace(string(f)), nil
149}
150