-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathconfiguration.go
196 lines (182 loc) · 7.3 KB
/
configuration.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
package cloudconfigclient
import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
)
// Source is the application's source configurations. It con contain zero to n number of property sources.
type Source struct {
Name string `json:"name"`
Profiles []string `json:"profiles"`
Label string `json:"label"`
Version string `json:"version"`
State string `json:"state"`
PropertySources []PropertySource `json:"propertySources"`
}
// GetPropertySource retrieves the PropertySource that has the specifies fileName. The fileName is the name of the file
// with extension - e.g. application-foo.yml.
//
// Usually the Config Server will return the PropertySource.Name as an URL of sorts
// (e.g. ssh://base-url.com/path/to/repository/path/to/file.[yml/properties]). So in order to find the specific file
// with the desired configurations, the ending of the name needs to be matched against.
func (s *Source) GetPropertySource(fileName string) (PropertySource, error) {
for _, propertySource := range s.PropertySources {
if strings.HasSuffix(propertySource.Name, fileName) {
return propertySource, nil
}
}
return PropertySource{}, ErrPropertySourceDoesNotExist
}
// ErrPropertySourceDoesNotExist is the error that is returned when there are no PropertySource that match the specified
// file name.
var ErrPropertySourceDoesNotExist = errors.New("property source does not exist")
// PropertySourceHandler handles the specific PropertySource.
type PropertySourceHandler func(propertySource PropertySource)
// HandlePropertySources handles all PropertySource configurations that are files. This is a convenience method to
// handle boilerplate for-loop code and filtering of non-configuration files.
//
// Config Server may return other configurations (e.g. credhub property sources) that contain no configurations
// (PropertySource.Source is empty).
func (s *Source) HandlePropertySources(handler PropertySourceHandler) {
for _, propertySource := range s.PropertySources {
if len(filepath.Ext(propertySource.Name)) > 0 {
handler(propertySource)
}
}
}
// Unmarshal converts the Source.PropertySources to the specified type. The type must be a pointer to a struct.
//
// The provided pointer struct must use JSON tags to map to the PropertySource.Source.
//
// This function is not optimized (ugly) and is intended to only be used at startup.
func (s *Source) Unmarshal(v interface{}) error {
// covert to a map[string]interface{} so we can convert to the target type
obj, err := toJSON(s.PropertySources)
if err != nil {
return err
}
// convert to bytes
b, err := json.Marshal(obj)
if err != nil {
return err
}
// now we can get to our target type
return json.Unmarshal(b, v)
}
var sliceRegex = regexp.MustCompile(`(.*)\[(\d+)]`)
func toJSON(propertySources []PropertySource) (map[string]interface{}, error) {
// get ready for a wild ride...
output := map[string]interface{}{}
// save the root, so we can get back there when we walk the tree
root := output
_ = root
for _, propertySource := range propertySources {
for k, v := range propertySource.Source {
keys := strings.Split(k, ".")
for i, key := range keys {
// determine if we are detailing with a slice - e.g. foo[0] or bar[0]
matches := sliceRegex.FindStringSubmatch(key)
if matches != nil {
actualKey := matches[1]
if _, ok := output[actualKey]; !ok {
output[actualKey] = []interface{}{}
}
if len(keys)-1 == i {
// the value go straight into the slice, we don't have any slice of objects
output[actualKey] = append(output[actualKey].([]interface{}), v)
output = root
} else {
// ugh... we have a slice of objects
// convert the index of the path, the index now matters
index, err := strconv.Atoi(matches[2])
if err != nil {
return nil, err
}
var obj map[string]interface{}
slice := output[actualKey].([]interface{})
// determine if the index we are walking exists yet in the slice we have built up
if len(slice) > index {
obj = slice[index].(map[string]interface{})
if obj == nil {
obj = map[string]interface{}{}
}
} else {
// the index does not exist, so we need to create it
for j := len(slice); j <= index; j++ {
output[actualKey] = append(output[actualKey].([]interface{}), map[string]interface{}{})
}
obj = output[actualKey].([]interface{})[index].(map[string]interface{})
}
output = obj
}
} else if len(keys)-1 == i {
// the value go straight into the key
output[key] = v
output = root
} else {
// need to create a nested object
if _, ok := output[key]; !ok {
output[key] = map[string]interface{}{}
}
output = output[key].(map[string]interface{})
}
}
}
}
return output, nil
}
// PropertySource is the property source for the application.
//
// A property source is either a YAML or a PROPERTIES file located in the repository that a Config Server is pointed at.
type PropertySource struct {
Source map[string]interface{} `json:"source"`
Name string `json:"name"`
}
// Configuration interface for retrieving an application's configuration files from the Config Server.
type Configuration interface {
// GetConfiguration retrieves the configurations/property sources of an application based on the name of the application
// and the profiles of the application.
GetConfiguration(applicationName string, profiles ...string) (Source, error)
// GetConfigurationWithLabel retrieves the configurations/property sources of an application based on the name of the application
// and the profiles of the application and the label.
GetConfigurationWithLabel(label string, applicationName string, profiles ...string) (Source, error)
}
// GetConfiguration retrieves the configurations/property sources of an application based on the name of the application
// and the profiles of the application.
func (c *Client) GetConfiguration(applicationName string, profiles ...string) (Source, error) {
var source Source
paths := []string{applicationName, joinProfiles(profiles)}
for _, client := range c.clients {
if err := client.GetResource(paths, nil, &source); err != nil {
if errors.Is(err, ErrResourceNotFound) {
continue
}
return Source{}, err
}
return source, nil
}
return Source{}, fmt.Errorf("failed to find configuration for application %s with profiles %s", applicationName, profiles)
}
// GetConfigurationWithLabel retrieves the configurations/property sources of an application based on the name of the application
// and the profiles of the application and the label.
func (c *Client) GetConfigurationWithLabel(label string, applicationName string, profiles ...string) (Source, error) {
var source Source
paths := []string{applicationName, joinProfiles(profiles), label}
for _, client := range c.clients {
if err := client.GetResource(paths, nil, &source); err != nil {
if errors.Is(err, ErrResourceNotFound) {
continue
}
return Source{}, err
}
return source, nil
}
return Source{}, fmt.Errorf("failed to find configuration for application %s with profiles %s and label %s", applicationName, profiles, label)
}
func joinProfiles(profiles []string) string {
return strings.Join(profiles, ",")
}