-
Notifications
You must be signed in to change notification settings - Fork 2
/
plugin.go
213 lines (176 loc) · 5.17 KB
/
plugin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package config
import (
"bytes"
"fmt"
"os"
"strings"
"time"
"github.com/roadrunner-server/errors"
"github.com/spf13/viper"
)
const (
PluginName string = "config"
versionKey string = "version"
includeKey string = "include"
envFileKey string = "envfile"
defaultConfigVersion string = "3"
prevConfigVersion string = "2.7"
// default envs
envDefault = ":-"
)
type Plugin struct {
viper *viper.Viper
Path string
// Deprecated: Prefix is deprecated and will be removed in the next major version.
Prefix string
Type string
ReadInCfg []byte
// user defined Flags in the form of <option>.<key> = <value>
// which overwrites initial a config key
Flags []string
// ExperimentalFeatures enables experimental features
ExperimentalFeatures bool
// Timeout ...
Timeout time.Duration
// RRVersion passed from the Endure.
Version string
}
// Init config provider.
func (p *Plugin) Init() error {
const op = errors.Op("config_plugin_init")
p.viper = viper.New()
// If user provided []byte data with config, read it and ignore Path and Prefix
if p.ReadInCfg != nil && p.Type != "" {
p.viper.SetConfigType("yaml")
return p.viper.ReadConfig(bytes.NewBuffer(p.ReadInCfg))
}
if p.Path == "" {
return errors.E(op, errors.Str("path should be set"))
}
p.viper.SetConfigFile(p.Path)
err := p.viper.ReadInConfig()
if err != nil {
return errors.E(op, err)
}
// possibility to use env file
// 'envfile' is an experimental feature
if p.Experimental() {
err = p.handleEnvFile()
if err != nil {
return errors.E(op, err)
}
}
// automatically inject ENV variables using ${ENV}/$ENV pattern
expandEnvViper(p.viper)
// override config Flags
if len(p.Flags) > 0 {
for _, f := range p.Flags {
key, val, errP := parseFlag(f)
if errP != nil {
return errors.E(op, errP)
}
p.viper.Set(key, parseEnvDefault(val))
}
}
// get a configuration version
// we should perform this check after all overrides
ver := p.viper.Get(versionKey)
if ver == nil {
return errors.Str("rr configuration file should contain a version e.g: version: 3")
}
if _, ok := ver.(string); !ok {
return errors.E(op, errors.Errorf("version should be a string: `version: \"3\"`, actual type is: %T", ver))
}
// handle includes syntax
err = p.handleInclude(ver.(string))
if err != nil {
return errors.E(op, err)
}
// RR includes the config feature by default starting from v2.7.
// However, this is only required for tests because, starting with v2.7, the rr-binary will pass the version automatically.
if p.Version == "" || p.Version == "local" {
p.Version = defaultConfigVersion
}
// configuration v2.7
if ver.(string) == prevConfigVersion {
println("please, update your configuration version from version: '2.7' to version: '3', see changes here: https://docs.roadrunner.dev/docs/general/compatibility#v3.0-configuration-and-rr-v2023.x.x")
}
return nil
}
// Overwrite overwriting existing config with provided values
func (p *Plugin) Overwrite(values map[string]any) error {
for key, value := range values {
p.viper.Set(key, value)
}
return nil
}
// Experimental returns true if experimental features are enabled
func (p *Plugin) Experimental() bool {
return p.ExperimentalFeatures
}
// UnmarshalKey reads a configuration section into a configuration object.
func (p *Plugin) UnmarshalKey(name string, out any) error {
const op = errors.Op("config_plugin_unmarshal_key")
err := p.viper.UnmarshalKey(name, &out)
if err != nil {
return errors.E(op, err)
}
return nil
}
func (p *Plugin) Unmarshal(out any) error {
const op = errors.Op("config_plugin_unmarshal")
err := p.viper.Unmarshal(&out)
if err != nil {
return errors.E(op, err)
}
return nil
}
// Get raw config in the form of a config section.
func (p *Plugin) Get(name string) any {
return p.viper.Get(name)
}
// Has checks if a config section exists.
func (p *Plugin) Has(name string) bool {
return p.viper.IsSet(name)
}
// RRVersion returns current RR version
func (p *Plugin) RRVersion() string {
return p.Version
}
func (p *Plugin) GracefulTimeout() time.Duration {
return p.Timeout
}
// Name returns user-friendly plugin name
func (p *Plugin) Name() string {
return PluginName
}
func parseFlag(flag string) (string, string, error) {
const op = errors.Op("parse_flag")
if !strings.Contains(flag, "=") {
return "", "", errors.E(op, errors.Errorf("invalid flag `%s`", flag))
}
parts := strings.SplitN(strings.TrimLeft(flag, " \"'`"), "=", 2)
if len(parts) < 2 {
return "", "", errors.Str("usage: -o key=value")
}
if parts[0] == "" {
return "", "", errors.Str("key should not be empty")
}
if parts[1] == "" {
return "", "", errors.Str("value should not be empty")
}
return strings.Trim(parts[0], " \n\t"), parseValue(strings.Trim(parts[1], " \n\t")), nil
}
func parseValue(value string) string {
escape := []rune(value)[0]
if escape == '"' || escape == '\'' || escape == '`' {
value = strings.Trim(value, string(escape))
value = strings.ReplaceAll(value, fmt.Sprintf("\\%s", string(escape)), string(escape))
}
return value
}
func parseEnvDefault(val string) string {
// tcp://127.0.0.1:${RPC_PORT:-36643}
// for envs like this, part would be tcp://127.0.0.1:
return ExpandVal(val, os.Getenv)
}