blob: 1588f2eed8ef8924700a63942f4f38e291ea3e34 [file] [log] [blame]
Abhilash S.L3b494632019-07-16 15:51:09 +05301package api
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "io"
8 "strconv"
9 "strings"
10
11 "github.com/mitchellh/mapstructure"
12)
13
14const (
David Bainbridge788e5202019-10-21 18:49:40 +000015 ServiceDefaults string = "service-defaults"
16 ProxyDefaults string = "proxy-defaults"
17 ServiceRouter string = "service-router"
18 ServiceSplitter string = "service-splitter"
19 ServiceResolver string = "service-resolver"
20
Abhilash S.L3b494632019-07-16 15:51:09 +053021 ProxyConfigGlobal string = "global"
22)
23
24type ConfigEntry interface {
25 GetKind() string
26 GetName() string
27 GetCreateIndex() uint64
28 GetModifyIndex() uint64
29}
30
David Bainbridge788e5202019-10-21 18:49:40 +000031type MeshGatewayMode string
32
33const (
34 // MeshGatewayModeDefault represents no specific mode and should
35 // be used to indicate that a different layer of the configuration
36 // chain should take precedence
37 MeshGatewayModeDefault MeshGatewayMode = ""
38
39 // MeshGatewayModeNone represents that the Upstream Connect connections
40 // should be direct and not flow through a mesh gateway.
41 MeshGatewayModeNone MeshGatewayMode = "none"
42
43 // MeshGatewayModeLocal represents that the Upstrea Connect connections
44 // should be made to a mesh gateway in the local datacenter. This is
45 MeshGatewayModeLocal MeshGatewayMode = "local"
46
47 // MeshGatewayModeRemote represents that the Upstream Connect connections
48 // should be made to a mesh gateway in a remote datacenter.
49 MeshGatewayModeRemote MeshGatewayMode = "remote"
50)
51
52// MeshGatewayConfig controls how Mesh Gateways are used for upstream Connect
53// services
54type MeshGatewayConfig struct {
55 // Mode is the mode that should be used for the upstream connection.
56 Mode MeshGatewayMode `json:",omitempty"`
57}
58
Abhilash S.L3b494632019-07-16 15:51:09 +053059type ServiceConfigEntry struct {
60 Kind string
61 Name string
David Bainbridge788e5202019-10-21 18:49:40 +000062 Protocol string `json:",omitempty"`
63 MeshGateway MeshGatewayConfig `json:",omitempty"`
64 ExternalSNI string `json:",omitempty"`
Abhilash S.L3b494632019-07-16 15:51:09 +053065 CreateIndex uint64
66 ModifyIndex uint64
67}
68
69func (s *ServiceConfigEntry) GetKind() string {
70 return s.Kind
71}
72
73func (s *ServiceConfigEntry) GetName() string {
74 return s.Name
75}
76
77func (s *ServiceConfigEntry) GetCreateIndex() uint64 {
78 return s.CreateIndex
79}
80
81func (s *ServiceConfigEntry) GetModifyIndex() uint64 {
82 return s.ModifyIndex
83}
84
85type ProxyConfigEntry struct {
86 Kind string
87 Name string
David Bainbridge788e5202019-10-21 18:49:40 +000088 Config map[string]interface{} `json:",omitempty"`
89 MeshGateway MeshGatewayConfig `json:",omitempty"`
Abhilash S.L3b494632019-07-16 15:51:09 +053090 CreateIndex uint64
91 ModifyIndex uint64
92}
93
94func (p *ProxyConfigEntry) GetKind() string {
95 return p.Kind
96}
97
98func (p *ProxyConfigEntry) GetName() string {
99 return p.Name
100}
101
102func (p *ProxyConfigEntry) GetCreateIndex() uint64 {
103 return p.CreateIndex
104}
105
106func (p *ProxyConfigEntry) GetModifyIndex() uint64 {
107 return p.ModifyIndex
108}
109
110type rawEntryListResponse struct {
111 kind string
112 Entries []map[string]interface{}
113}
114
115func makeConfigEntry(kind, name string) (ConfigEntry, error) {
116 switch kind {
117 case ServiceDefaults:
David Bainbridge788e5202019-10-21 18:49:40 +0000118 return &ServiceConfigEntry{Kind: kind, Name: name}, nil
Abhilash S.L3b494632019-07-16 15:51:09 +0530119 case ProxyDefaults:
David Bainbridge788e5202019-10-21 18:49:40 +0000120 return &ProxyConfigEntry{Kind: kind, Name: name}, nil
121 case ServiceRouter:
122 return &ServiceRouterConfigEntry{Kind: kind, Name: name}, nil
123 case ServiceSplitter:
124 return &ServiceSplitterConfigEntry{Kind: kind, Name: name}, nil
125 case ServiceResolver:
126 return &ServiceResolverConfigEntry{Kind: kind, Name: name}, nil
Abhilash S.L3b494632019-07-16 15:51:09 +0530127 default:
128 return nil, fmt.Errorf("invalid config entry kind: %s", kind)
129 }
130}
131
David Bainbridge788e5202019-10-21 18:49:40 +0000132func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
133 return makeConfigEntry(kind, name)
134}
135
136// DecodeConfigEntry will decode the result of using json.Unmarshal of a config
137// entry into a map[string]interface{}.
138//
139// Important caveats:
140//
141// - This will NOT work if the map[string]interface{} was produced using HCL
142// decoding as that requires more extensive parsing to work around the issues
143// with map[string][]interface{} that arise.
144//
145// - This will only decode fields using their camel case json field
146// representations.
Abhilash S.L3b494632019-07-16 15:51:09 +0530147func DecodeConfigEntry(raw map[string]interface{}) (ConfigEntry, error) {
148 var entry ConfigEntry
149
150 kindVal, ok := raw["Kind"]
151 if !ok {
152 kindVal, ok = raw["kind"]
153 }
154 if !ok {
155 return nil, fmt.Errorf("Payload does not contain a kind/Kind key at the top level")
156 }
157
158 if kindStr, ok := kindVal.(string); ok {
159 newEntry, err := makeConfigEntry(kindStr, "")
160 if err != nil {
161 return nil, err
162 }
163 entry = newEntry
164 } else {
165 return nil, fmt.Errorf("Kind value in payload is not a string")
166 }
167
168 decodeConf := &mapstructure.DecoderConfig{
169 DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
170 Result: &entry,
171 WeaklyTypedInput: true,
172 }
173
174 decoder, err := mapstructure.NewDecoder(decodeConf)
175 if err != nil {
176 return nil, err
177 }
178
179 return entry, decoder.Decode(raw)
180}
181
182func DecodeConfigEntryFromJSON(data []byte) (ConfigEntry, error) {
183 var raw map[string]interface{}
184 if err := json.Unmarshal(data, &raw); err != nil {
185 return nil, err
186 }
187
188 return DecodeConfigEntry(raw)
189}
190
David Bainbridge788e5202019-10-21 18:49:40 +0000191func decodeConfigEntrySlice(raw []map[string]interface{}) ([]ConfigEntry, error) {
192 var entries []ConfigEntry
193 for _, rawEntry := range raw {
194 entry, err := DecodeConfigEntry(rawEntry)
195 if err != nil {
196 return nil, err
197 }
198 entries = append(entries, entry)
199 }
200 return entries, nil
201}
202
203// ConfigEntries can be used to query the Config endpoints
Abhilash S.L3b494632019-07-16 15:51:09 +0530204type ConfigEntries struct {
205 c *Client
206}
207
208// Config returns a handle to the Config endpoints
209func (c *Client) ConfigEntries() *ConfigEntries {
210 return &ConfigEntries{c}
211}
212
213func (conf *ConfigEntries) Get(kind string, name string, q *QueryOptions) (ConfigEntry, *QueryMeta, error) {
214 if kind == "" || name == "" {
215 return nil, nil, fmt.Errorf("Both kind and name parameters must not be empty")
216 }
217
218 entry, err := makeConfigEntry(kind, name)
219 if err != nil {
220 return nil, nil, err
221 }
222
223 r := conf.c.newRequest("GET", fmt.Sprintf("/v1/config/%s/%s", kind, name))
224 r.setQueryOptions(q)
225 rtt, resp, err := requireOK(conf.c.doRequest(r))
226 if err != nil {
227 return nil, nil, err
228 }
229
230 defer resp.Body.Close()
231
232 qm := &QueryMeta{}
233 parseQueryMeta(resp, qm)
234 qm.RequestTime = rtt
235
236 if err := decodeBody(resp, entry); err != nil {
237 return nil, nil, err
238 }
239
240 return entry, qm, nil
241}
242
243func (conf *ConfigEntries) List(kind string, q *QueryOptions) ([]ConfigEntry, *QueryMeta, error) {
244 if kind == "" {
245 return nil, nil, fmt.Errorf("The kind parameter must not be empty")
246 }
247
248 r := conf.c.newRequest("GET", fmt.Sprintf("/v1/config/%s", kind))
249 r.setQueryOptions(q)
250 rtt, resp, err := requireOK(conf.c.doRequest(r))
251 if err != nil {
252 return nil, nil, err
253 }
254
255 defer resp.Body.Close()
256
257 qm := &QueryMeta{}
258 parseQueryMeta(resp, qm)
259 qm.RequestTime = rtt
260
261 var raw []map[string]interface{}
262 if err := decodeBody(resp, &raw); err != nil {
263 return nil, nil, err
264 }
265
David Bainbridge788e5202019-10-21 18:49:40 +0000266 entries, err := decodeConfigEntrySlice(raw)
267 if err != nil {
268 return nil, nil, err
Abhilash S.L3b494632019-07-16 15:51:09 +0530269 }
270
271 return entries, qm, nil
272}
273
274func (conf *ConfigEntries) Set(entry ConfigEntry, w *WriteOptions) (bool, *WriteMeta, error) {
275 return conf.set(entry, nil, w)
276}
277
278func (conf *ConfigEntries) CAS(entry ConfigEntry, index uint64, w *WriteOptions) (bool, *WriteMeta, error) {
279 return conf.set(entry, map[string]string{"cas": strconv.FormatUint(index, 10)}, w)
280}
281
282func (conf *ConfigEntries) set(entry ConfigEntry, params map[string]string, w *WriteOptions) (bool, *WriteMeta, error) {
283 r := conf.c.newRequest("PUT", "/v1/config")
284 r.setWriteOptions(w)
285 for param, value := range params {
286 r.params.Set(param, value)
287 }
288 r.obj = entry
289 rtt, resp, err := requireOK(conf.c.doRequest(r))
290 if err != nil {
291 return false, nil, err
292 }
293 defer resp.Body.Close()
294
295 var buf bytes.Buffer
296 if _, err := io.Copy(&buf, resp.Body); err != nil {
297 return false, nil, fmt.Errorf("Failed to read response: %v", err)
298 }
299 res := strings.Contains(buf.String(), "true")
300
301 wm := &WriteMeta{RequestTime: rtt}
302 return res, wm, nil
303}
304
305func (conf *ConfigEntries) Delete(kind string, name string, w *WriteOptions) (*WriteMeta, error) {
306 if kind == "" || name == "" {
307 return nil, fmt.Errorf("Both kind and name parameters must not be empty")
308 }
309
310 r := conf.c.newRequest("DELETE", fmt.Sprintf("/v1/config/%s/%s", kind, name))
311 r.setWriteOptions(w)
312 rtt, resp, err := requireOK(conf.c.doRequest(r))
313 if err != nil {
314 return nil, err
315 }
316 resp.Body.Close()
317 wm := &WriteMeta{RequestTime: rtt}
318 return wm, nil
319}