@@ -2,13 +2,11 @@ package aws
2
2
3
3
import (
4
4
"context"
5
- "reflect"
6
- "strconv"
7
- "strings"
8
5
9
6
"github.com/aws/aws-sdk-go-v2/aws"
10
7
"github.com/aws/aws-sdk-go-v2/service/ssm"
11
8
"github.com/railsware/go-global/v2"
9
+ "github.com/railsware/go-global/v2/utils"
12
10
)
13
11
14
12
const paramStoreSeparator = "/"
@@ -31,6 +29,11 @@ func LoadConfigFromParameterStore(awsConfig aws.Config, options LoadConfigOption
31
29
}
32
30
}()
33
31
32
+ reflectedConfig , err := utils .ReflectConfig (globalConfig )
33
+ if err != nil {
34
+ return err
35
+ }
36
+
34
37
paramPaginator := ssm .NewGetParametersByPathPaginator (
35
38
ssm .NewFromConfig (awsConfig ),
36
39
& ssm.GetParametersByPathInput {
@@ -40,145 +43,27 @@ func LoadConfigFromParameterStore(awsConfig aws.Config, options LoadConfigOption
40
43
},
41
44
)
42
45
43
- var paramWarnings []global. Error
46
+ var params []param
44
47
45
48
for paramPaginator .HasMorePages () {
46
49
page , err := paramPaginator .NextPage (context .Background ())
47
50
if err != nil {
48
51
return global .NewError ("global: failed to load from Parameter Store: %v" , err )
49
52
}
50
-
51
- for _ , param := range page .Parameters {
52
- paramNameWithoutPrefix := (* param .Name )[len (options .ParamPrefix ):]
53
- destination , err := findParamDestination (globalConfig , paramNameWithoutPrefix )
54
- if err != nil {
55
- if ! err .Warning () {
56
- return global .NewError ("global: %s: %v" , paramNameWithoutPrefix , err )
57
- } else if ! options .IgnoreUnmappedParams {
58
- paramWarnings = append (paramWarnings , global .NewWarning ("%s: %v" , paramNameWithoutPrefix , err ))
59
- }
60
- continue
61
- }
62
-
63
- err = writeParamToConfig (destination , * param .Value )
64
- if err != nil {
65
- if err .Warning () {
66
- paramWarnings = append (paramWarnings , global .NewWarning ("%s: %v" , paramNameWithoutPrefix , err ))
67
- } else {
68
- return global .NewError ("global: %v" , err )
69
- }
70
- }
53
+ for _ , ssmParam := range page .Parameters {
54
+ paramNameWithoutPrefix := (* ssmParam .Name )[len (options .ParamPrefix ):]
55
+ params = append (params , param {paramNameWithoutPrefix , * ssmParam .Value })
71
56
}
72
57
}
73
58
74
- if paramWarnings != nil {
75
- var warningMessages []string
76
- for _ , warning := range paramWarnings {
77
- warningMessages = append (warningMessages , warning .Error ())
78
- }
59
+ paramTree := buildParamTree (params )
79
60
80
- return global .NewWarning ("global: failed to read some parameters: %s" , strings .Join (warningMessages , "; " ))
81
- }
61
+ errors := paramTree .Write (reflectedConfig )
82
62
83
- return nil
63
+ return errors . Join ()
84
64
}
85
65
86
- func findParamDestination (globalConfig interface {}, name string ) (reflect.Value , global.Error ) {
87
- destination := reflect .ValueOf (globalConfig )
88
-
89
- if destination .Kind () != reflect .Ptr {
90
- return reflect.Value {}, global .NewError ("config must be a pointer to a structure" )
91
- }
92
-
93
- destination = destination .Elem ()
94
-
95
- if destination .Kind () != reflect .Struct && destination .Kind () != reflect .Array {
96
- return reflect.Value {}, global .NewError ("config must be a pointer to a structure or array" )
97
- }
98
-
99
- // find nested field in config struct
100
- pathParts := strings .Split (name , paramStoreSeparator )
101
- for _ , part := range pathParts {
102
- if destination .Kind () == reflect .Struct {
103
- destination = lookupFieldByName (destination , part )
104
- } else if destination .Kind () == reflect .Slice {
105
- index , err := strconv .Atoi (part )
106
- if err != nil || index < 0 {
107
- return reflect.Value {}, global .NewWarning ("could not map param to array index" )
108
- }
109
- if destination .Cap () <= index {
110
- // grow destination array to match
111
- destination .SetLen (destination .Cap ())
112
- additionalLength := index - destination .Cap () + 1
113
- additionalElements := reflect .MakeSlice (destination .Type (), additionalLength , additionalLength )
114
- destination .Set (reflect .AppendSlice (destination , additionalElements ))
115
- } else if destination .Len () <= index {
116
- destination .SetLen (index + 1 )
117
- }
118
- destination = destination .Index (index )
119
- } else {
120
- return reflect.Value {}, global .NewWarning ("could not map param to config field" )
121
- }
122
- // resolve pointer, if struct was nil
123
- if destination .Kind () == reflect .Ptr {
124
- if destination .IsNil () {
125
- destination .Set (reflect .New (destination .Type ().Elem ()))
126
- }
127
- destination = destination .Elem ()
128
- }
129
- }
130
-
131
- // assign value to field
132
- if ! destination .IsValid () {
133
- return reflect.Value {}, global .NewWarning ("could not map param to config field" )
134
- }
135
-
136
- return destination , nil
137
- }
138
-
139
- func writeParamToConfig (destination reflect.Value , value string ) global.Error {
140
- if ! destination .CanSet () {
141
- return global .NewWarning ("config key is not writable" )
142
- } else if destination .Kind () == reflect .String {
143
- destination .SetString (value )
144
- } else if destination .Kind () == reflect .Int {
145
- intval , err := strconv .Atoi (value )
146
- if err != nil {
147
- return global .NewWarning ("cannot read int param value" )
148
- } else {
149
- destination .SetInt (int64 (intval ))
150
- }
151
- } else if destination .Kind () == reflect .Bool {
152
- if value == "true" {
153
- destination .SetBool (true )
154
- } else if value == "false" {
155
- destination .SetBool (false )
156
- } else {
157
- return global .NewWarning ("cannot read bool param value (must be true or false)" )
158
- }
159
- } else {
160
- return global .NewWarning ("cannot write param: config key is of unsupported type %s" , destination .Kind ())
161
- }
162
-
163
- return nil
164
- }
165
-
166
- func lookupFieldByName (structure reflect.Value , name string ) reflect.Value {
167
- fieldByName := structure .FieldByName (name )
168
- if fieldByName .IsValid () {
169
- return fieldByName
170
- }
171
-
172
- // TODO might be inefficient, but fine for one-time loading of a not-crazy-big config
173
- for i := 0 ; i < structure .NumField (); i ++ {
174
- fieldTag := structure .Type ().Field (i ).Tag
175
- if fieldTag .Get ("global" ) == name {
176
- return structure .Field (i )
177
- }
178
- if fieldTag .Get ("json" ) == name {
179
- return structure .Field (i )
180
- }
181
- }
182
-
183
- return reflect.Value {}
66
+ type param struct {
67
+ path string
68
+ value string
184
69
}
0 commit comments