@@ -2,8 +2,10 @@ package yaml
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"fmt"
6
7
"regexp"
8
+ "strconv"
7
9
"strings"
8
10
9
11
"github.com/armory/go-yaml-tools/pkg/secrets"
@@ -12,141 +14,224 @@ import (
12
14
log "github.com/sirupsen/logrus"
13
15
)
14
16
17
+ type ObjectMap = map [interface {}]interface {}
18
+ type StringMap = map [string ]string
19
+ type OutputMap = map [string ]interface {}
20
+
15
21
// Resolve takes an array of yaml maps and returns a single map of a merged
16
22
// properties. The order of `ymlTemplates` matters, it should go from lowest
17
23
// to highest precendence.
18
- func Resolve (ymlTemplates []map [ interface {}] interface {} , envKeyPairs map [ string ] string ) (map [ string ] interface {} , error ) {
24
+ func Resolve (ymlTemplates []ObjectMap , envKeyPairs StringMap ) (OutputMap , error ) {
19
25
log .Debugf ("Using environ %+v\n " , envKeyPairs )
20
26
21
- mergedMap := map [ interface {}] interface {} {}
27
+ mergedMap := ObjectMap {}
22
28
for _ , yml := range ymlTemplates {
23
29
if err := mergo .Merge (& mergedMap , yml , mergo .WithOverride ); err != nil {
24
30
log .Error (err )
25
31
}
26
32
}
27
33
28
34
// unlike other secret engines, the vault config needs to be registered before it can decrypt anything
29
- vaultCfg := extractVaultConfig (mergedMap )
30
- if vaultCfg != nil && (secrets. VaultConfig {}) != * vaultCfg {
35
+ vaultCfg , err := extractVaultConfig (mergedMap )
36
+ if err == nil {
31
37
if err := secrets .RegisterVaultConfig (* vaultCfg ); err != nil {
32
38
log .Errorf ("Error registering vault config: %v" , err )
33
39
}
34
40
}
35
41
36
42
stringMap := convertToStringMap (mergedMap )
37
43
38
- err := subValues (stringMap , stringMap , envKeyPairs )
39
-
40
- if err != nil {
44
+ if err := subValues (stringMap , stringMap , envKeyPairs ); err != nil {
41
45
return nil , err
42
46
}
43
47
44
48
return stringMap , nil
45
49
}
46
50
47
- func extractVaultConfig (m map [interface {}]interface {}) * secrets.VaultConfig {
48
- if secretsMap , ok := m ["secrets" ].(map [interface {}]interface {}); ok {
49
- if vaultmap , ok := secretsMap ["vault" ].(map [interface {}]interface {}); ok {
50
- cfg , err := secrets .DecodeVaultConfig (vaultmap )
51
- if err != nil {
52
- log .Errorf ("Error decoding vault config: %v" , err )
53
- return nil
54
- }
55
- return cfg
56
- }
51
+ var EVCErrorMissingKey = errors .New ("missing secrets.vault key" )
52
+ var EVCErrorDecoding = errors .New ("error decoding vault config" )
53
+ var EVCErrorEmpty = errors .New ("empty decoded vault config" )
54
+
55
+ func extractVaultConfig (m ObjectMap ) (* secrets.VaultConfig , error ) {
56
+ secretsMap , ok := m ["secrets" ].(ObjectMap )
57
+ if ! ok {
58
+ return nil , EVCErrorMissingKey
57
59
}
58
- return nil
60
+ vaultmap , ok := secretsMap ["vault" ].(ObjectMap )
61
+ if ! ok {
62
+ return nil , EVCErrorMissingKey
63
+ }
64
+ cfg , err := secrets .DecodeVaultConfig (vaultmap )
65
+ if err != nil {
66
+ return nil , fmt .Errorf ("%w: %v" , EVCErrorDecoding , err )
67
+ }
68
+ if cfg == nil {
69
+ return nil , EVCErrorEmpty
70
+ }
71
+ if * cfg == (secrets.VaultConfig {}) {
72
+ return nil , EVCErrorEmpty
73
+ }
74
+ return cfg , nil
59
75
}
60
76
61
- func convertToStringMap (m map [interface {}]interface {}) map [string ]interface {} {
62
- newMap := map [string ]interface {}{}
77
+ func convertToStringMap (m ObjectMap ) OutputMap {
78
+ newMap := OutputMap {}
79
+ var kstring string
63
80
for k , v := range m {
64
- switch v .(type ) {
65
- case map [interface {}]interface {}:
66
- stringMap := convertToStringMap (v .(map [interface {}]interface {}))
67
- newMap [k .(string )] = stringMap
68
- case []interface {}:
69
- var collection []interface {}
70
- for _ , vv := range v .([]interface {}) {
71
- switch vv .(type ) {
72
- case map [interface {}]interface {}:
73
- collection = append (collection , convertToStringMap (vv .(map [interface {}]interface {})))
74
- case string , int , bool , float64 :
75
- collection = append (collection , fmt .Sprintf ("%v" , vv ))
76
- }
77
- }
78
- newMap [k .(string )] = collection
81
+ kstring = k .(string )
82
+ convertOneValueToStringMap (v , newMap , kstring )
83
+ }
84
+ return newMap
85
+ }
79
86
80
- default :
81
- newMap [k .(string )] = fmt .Sprintf ("%v" , v )
87
+ func convertOneValueToStringMap (v interface {}, newMap OutputMap , kstring string ) {
88
+ switch v := v .(type ) {
89
+ case ObjectMap :
90
+ newMap [kstring ] = convertToStringMap (v )
91
+ case []interface {}:
92
+ for i := range v {
93
+ converOneArrayToStringMap (v [:], i )
82
94
}
95
+ newMap [kstring ] = v
96
+ case string :
97
+ newMap [kstring ] = v
98
+ case int :
99
+ newMap [kstring ] = strconv .Itoa (v )
100
+ case bool :
101
+ newMap [kstring ] = strconv .FormatBool (v )
102
+ case float64 :
103
+ newMap [kstring ] = strconv .FormatFloat (v , 'g' , - 1 , 64 )
104
+ case float32 :
105
+ newMap [kstring ] = strconv .FormatFloat (float64 (v ), 'g' , - 1 , 64 )
106
+ case fmt.Stringer :
107
+ newMap [kstring ] = v .String ()
108
+ default :
109
+ newMap [kstring ] = fmt .Sprintf ("%v" , v )
110
+ }
111
+ }
112
+
113
+ func converOneArrayToStringMap (v []interface {}, i int ) {
114
+ switch vv := v [i ].(type ) {
115
+ case ObjectMap :
116
+ v [i ] = convertToStringMap (vv )
117
+ case []interface {}:
118
+ for j := range v {
119
+ converOneArrayToStringMap (vv [:], j )
120
+ }
121
+ v [i ] = vv
122
+ case string :
123
+ v [i ] = vv
124
+ case int :
125
+ v [i ] = strconv .Itoa (vv )
126
+ case bool :
127
+ v [i ] = strconv .FormatBool (vv )
128
+ case float64 :
129
+ v [i ] = strconv .FormatFloat (vv , 'g' , - 1 , 64 )
130
+ case float32 :
131
+ v [i ] = strconv .FormatFloat (float64 (vv ), 'g' , - 1 , 64 )
132
+ case fmt.Stringer :
133
+ v [i ] = vv .String ()
134
+ default :
135
+ v [i ] = fmt .Sprintf ("%v" , vv )
83
136
}
84
- return newMap
85
137
}
86
138
87
- func subValues (fullMap map [string ]interface {}, subMap map [string ]interface {}, env map [string ]string ) error {
139
+ var re = regexp .MustCompile ("\\ $\\ {(.*?)}" )
140
+
141
+ func subValues (fullMap OutputMap , subMap OutputMap , env StringMap ) error {
88
142
//responsible for finding all variables that need to be substituted
89
- keepResolving := true
90
143
loops := 0
91
- re := regexp .MustCompile ("\\ $\\ {(.*?)\\ }" )
92
- for keepResolving && loops < len (subMap ) {
144
+ for loops < len (subMap ) {
93
145
loops ++
94
146
for k , value := range subMap {
95
- switch value .(type ) {
96
- case map [string ]interface {}:
97
- err := subValues (fullMap , value .(map [string ]interface {}), env )
98
- if err != nil {
99
- return err
100
- }
101
- case []interface {}:
102
- sliceMap := make (map [string ]interface {})
103
- for i := 0 ; i < len (value .([]interface {})); i ++ {
104
- sliceMap [fmt .Sprint (i )] = value .([]interface {})[i ]
105
- }
106
- err := subValues (fullMap , sliceMap , env )
107
- if err != nil {
108
- return err
109
- }
110
- case string :
111
- valStr := value .(string )
112
-
113
- secret , wasSecret , err := resolveSecret (valStr )
114
- if err != nil {
115
- return err
116
- }
117
-
118
- if wasSecret {
119
- subMap [k ] = secret
120
- continue
121
- }
122
-
123
- keys := re .FindAllStringSubmatch (valStr , - 1 )
124
- for _ , keyToSub := range keys {
125
- resolvedValue := resolveSubs (fullMap , keyToSub [1 ], env )
126
- subMap [k ] = strings .Replace (valStr , "${" + keyToSub [1 ]+ "}" , resolvedValue , - 1 )
127
- }
147
+ if err := processOneSubvalue (fullMap , subMap , env , value , k ); err != nil {
148
+ return err
128
149
}
129
150
}
130
151
}
131
152
return nil
132
153
}
133
154
134
- func resolveSecret (valStr string ) (string , bool , error ) {
135
- // if the value is a secret resolve it
136
- if secrets .IsEncryptedSecret (valStr ) {
137
- d , err := secrets .NewDecrypter (context .TODO (), valStr )
155
+ func processOneSubvalue (fullMap OutputMap , subMap OutputMap , env StringMap , value interface {}, k string ) error {
156
+ var secret string
157
+ var decrypter secrets.Decrypter
158
+ var valueBytes []byte
159
+ switch value := value .(type ) {
160
+ case map [string ]interface {}:
161
+ err := subValues (fullMap , value , env )
138
162
if err != nil {
139
- return "" , true , err
163
+ return err
164
+ }
165
+ case []interface {}:
166
+ for i := 0 ; i < len (value ); i ++ {
167
+ err := processOneSubvalueFromArray (fullMap , value [:], env , value [i ], i )
168
+ if err != nil {
169
+ return err
170
+ }
140
171
}
172
+ case string :
173
+ if secrets .IsEncryptedSecret (value ) {
174
+ var err error
175
+ if decrypter , err = secrets .NewDecrypter (context .TODO (), value ); err != nil {
176
+ return err
177
+ } else if secret , err = decrypter .Decrypt (); err != nil {
178
+ return err
179
+ }
180
+ subMap [k ] = secret
181
+ return nil
182
+ }
183
+
184
+ valueBytes = []byte (value )
185
+ valueBytes = re .ReplaceAllFunc (valueBytes , func (key []byte ) []byte {
186
+ i := len (key ) - 1
187
+ myKey := string (key [2 :i ])
188
+ return []byte (resolveSubs (fullMap , myKey , env ))
189
+ })
190
+ value = string (valueBytes )
191
+ subMap [k ] = value
192
+ }
193
+ return nil
194
+ }
141
195
142
- secret , err := d .Decrypt ()
196
+ func processOneSubvalueFromArray (fullMap OutputMap , subslice []interface {}, env StringMap , value interface {}, k int ) error {
197
+ var secret string
198
+ var decrypter secrets.Decrypter
199
+ var valueBytes []byte
200
+ switch value := value .(type ) {
201
+ case map [string ]interface {}:
202
+ err := subValues (fullMap , value , env )
143
203
if err != nil {
144
- return "" , true , err
204
+ return err
205
+ }
206
+ case []interface {}:
207
+ for i := 0 ; i < len (value ); i ++ {
208
+ err := processOneSubvalueFromArray (fullMap , value [:], env , value [i ], i )
209
+ if err != nil {
210
+ return err
211
+ }
212
+ }
213
+ case string :
214
+ if secrets .IsEncryptedSecret (value ) {
215
+ var err error
216
+ if decrypter , err = secrets .NewDecrypter (context .TODO (), value ); err != nil {
217
+ return err
218
+ } else if secret , err = decrypter .Decrypt (); err != nil {
219
+ return err
220
+ }
221
+ subslice [k ] = secret
222
+ return nil
145
223
}
146
224
147
- return secret , true , nil
225
+ valueBytes = []byte (value )
226
+ valueBytes = re .ReplaceAllFunc (valueBytes , func (key []byte ) []byte {
227
+ i := len (key ) - 1
228
+ myKey := string (key [2 :i ])
229
+ return []byte (resolveSubs (fullMap , myKey , env ))
230
+ })
231
+ value = string (valueBytes )
232
+ subslice [k ] = value
148
233
}
149
- return valStr , false , nil
234
+ return nil
150
235
}
151
236
152
237
func resolveSubs (m map [string ]interface {}, keyToSub string , env map [string ]string ) string {
@@ -159,7 +244,7 @@ func resolveSubs(m map[string]interface{}, keyToSub string, env map[string]strin
159
244
defaultKey = keyDefaultSplit [1 ]
160
245
}
161
246
162
- if v := valueFromFlatKey (subKey , m ); v != "" {
247
+ if v , err := valueFromFlatKey (subKey , m ); err == nil {
163
248
defaultKey = v
164
249
} else if v , ok := env [subKey ]; ok {
165
250
defaultKey = v
@@ -168,17 +253,40 @@ func resolveSubs(m map[string]interface{}, keyToSub string, env map[string]strin
168
253
return defaultKey
169
254
}
170
255
171
- func valueFromFlatKey (flatKey string , m map [string ]interface {}) string {
172
- keys := strings .Split (flatKey , "." )
173
- for _ , key := range keys {
174
- switch m [key ].(type ) {
175
- case map [string ]interface {}:
176
- m = m [key ].(map [string ]interface {})
177
- case string :
178
- return m [key ].(string )
179
- default :
180
- continue
256
+ var VFFKErrorNotFound = errors .New ("not found" )
257
+ var VFFKErrorInvalidIntermediaryType = errors .New ("expected map[string]interface{}" )
258
+ var VFFKErrorInvalidLeafType = errors .New ("expected string or stringer()" )
259
+
260
+ func valueFromFlatKey (flatKey string , root map [string ]interface {}) (string , error ) {
261
+ fields := strings .Split (flatKey , "." )
262
+ var currVal interface {} = root
263
+ var currMap OutputMap // pre-alloc OutputMap ref. Actually assigned & used in loop below
264
+ var ok bool
265
+ for i := range fields {
266
+ if currVal == nil {
267
+ return "" , fmt .Errorf ("path %q was %w" , flatKey , VFFKErrorNotFound )
268
+ }
269
+ if currMap , ok = currVal .(OutputMap ); ! ok {
270
+ return "" , fmt .Errorf ("path %q was of type %T, %w" , strings .Join (fields [:i ], "." ), currVal , VFFKErrorInvalidIntermediaryType )
181
271
}
272
+ if currVal , ok = currMap [fields [i ]]; ! ok {
273
+ return "" , fmt .Errorf ("path %q was %w" , flatKey , VFFKErrorNotFound )
274
+ }
275
+ }
276
+ switch v := currVal .(type ) {
277
+ case string :
278
+ return v , nil
279
+ case fmt.Stringer :
280
+ return v .String (), nil
281
+ case float32 :
282
+ return strconv .FormatFloat (float64 (v ), 'g' , - 1 , 64 ), nil
283
+ case float64 :
284
+ return strconv .FormatFloat (v , 'g' , - 1 , 64 ), nil
285
+ case int :
286
+ return strconv .Itoa (v ), nil
287
+ case bool :
288
+ return strconv .FormatBool (v ), nil
289
+ default :
290
+ return "" , fmt .Errorf ("path %q is type %T, %w" , flatKey , v , VFFKErrorInvalidLeafType )
182
291
}
183
- return ""
184
292
}
0 commit comments