1 package entrypoint_test
2
3 import (
4
5 "encoding/json"
6 "fmt"
7 "io/ioutil"
8 "sort"
9 "strings"
10 "testing"
11
12
13 apiv3_bootstrap "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/config/bootstrap/v3"
14 apiv3_httpman "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/extensions/filters/network/http_connection_manager/v3"
15
16
17 ecp_v3_resource "github.com/emissary-ingress/emissary/v3/pkg/envoy-control-plane/resource/v3"
18 ecp_wellknown "github.com/emissary-ingress/emissary/v3/pkg/envoy-control-plane/wellknown"
19
20
21 "github.com/emissary-ingress/emissary/v3/pkg/api/getambassador.io/v3alpha1"
22 "github.com/emissary-ingress/emissary/v3/pkg/kates"
23 )
24
25 func JSONify(obj interface{}) (string, error) {
26 bytes, err := json.MarshalIndent(obj, "", " ")
27 if err != nil {
28 return "", err
29 }
30
31 return string(bytes), nil
32 }
33
34 func LoadYAML(path string) ([]kates.Object, error) {
35 content, err := ioutil.ReadFile(path)
36 if err != nil {
37 return nil, err
38 }
39
40 objs, err := kates.ParseManifests(string(content))
41 if err != nil {
42 return nil, err
43 }
44
45 return objs, nil
46 }
47
48 type RenderedRoute struct {
49 Scheme string `json:"scheme"`
50 Host string `json:"host"`
51 Path string `json:"path"`
52 Authority string `json:"authority"`
53 AuthorityMatch string `json:"authorityMatch"`
54 Action string `json:"action"`
55 ActionArg string `json:"action_arg"`
56 }
57
58 func (rr *RenderedRoute) String() string {
59 s := fmt.Sprintf("%s%s: %s://%s%s", rr.Action, rr.ActionArg, rr.Scheme, rr.Host, rr.Path)
60
61 if rr.Authority != "" {
62 s += fmt.Sprintf(" (:authority %s %s)", rr.AuthorityMatch, rr.Authority)
63 }
64
65 return s
66 }
67
68 type RenderedVHost struct {
69 Name string `json:"name"`
70 Routes []RenderedRoute `json:"routes"`
71 }
72
73 func (rvh *RenderedVHost) AddRoute(rr RenderedRoute) {
74 rvh.Routes = append(rvh.Routes, rr)
75 }
76
77 func NewRenderedVHost(name string) RenderedVHost {
78 return RenderedVHost{
79 Name: name,
80 Routes: []RenderedRoute{},
81 }
82 }
83
84 type RenderedChain struct {
85 ServerNames []string `json:"server_names"`
86 TransportProtocol string `json:"transport_protocol"`
87 VHosts map[string]*RenderedVHost `json:"-"`
88 VHostList []*RenderedVHost `json:"vhosts"`
89 }
90
91 func (rchain *RenderedChain) AddVHost(rvh *RenderedVHost) {
92 rchain.VHosts[rvh.Name] = rvh
93 }
94
95 func (rchain *RenderedChain) GetVHost(vhostname string) *RenderedVHost {
96 return rchain.VHosts[vhostname]
97 }
98
99 func NewRenderedChain(serverNames []string, transportProtocol string) RenderedChain {
100 chain := RenderedChain{
101 ServerNames: nil,
102 TransportProtocol: transportProtocol,
103 VHosts: map[string]*RenderedVHost{},
104 VHostList: []*RenderedVHost{},
105 }
106
107 if len(serverNames) > 0 {
108 chain.ServerNames = []string{}
109
110 for _, name := range serverNames {
111 if (name != "") && (name != "*") {
112 chain.ServerNames = append(chain.ServerNames, name)
113 }
114 }
115 }
116
117 return chain
118 }
119
120 type RenderedListener struct {
121 Name string `json:"name"`
122 Port uint32 `json:"port"`
123 Chains map[string]*RenderedChain `json:"-"`
124 ChainList []*RenderedChain `json:"chains"`
125 }
126
127 func (rl *RenderedListener) AddChain(rchain *RenderedChain) error {
128 hostname := "*"
129
130 if len(rchain.ServerNames) > 0 {
131 hostname = rchain.ServerNames[0]
132 }
133
134 xport := rchain.TransportProtocol
135
136 extant := rl.GetChain(hostname, xport)
137
138 if extant != nil {
139 return fmt.Errorf("chain for %s, %s already exists in %s", hostname, xport, rl.Name)
140 }
141
142 key := fmt.Sprintf("%s-%s", hostname, xport)
143
144 rl.Chains[key] = rchain
145 return nil
146 }
147
148 func (rl *RenderedListener) GetChain(hostname string, xport string) *RenderedChain {
149 key := fmt.Sprintf("%s-%s", hostname, xport)
150
151 return rl.Chains[key]
152 }
153
154 func NewRenderedListener(name string, port uint32) RenderedListener {
155 return RenderedListener{
156 Name: name,
157 Port: port,
158 Chains: map[string]*RenderedChain{},
159 ChainList: []*RenderedChain{},
160 }
161 }
162
163 func NewListener(port uint32) RenderedListener {
164 return RenderedListener{
165 Name: fmt.Sprintf("ambassador-listener-0.0.0.0-%d", port),
166 Port: port,
167 Chains: map[string]*RenderedChain{},
168 }
169 }
170
171 func NewMapping(name string, pfx string) v3alpha1.Mapping {
172 return v3alpha1.Mapping{
173 TypeMeta: kates.TypeMeta{Kind: "Mapping"},
174 ObjectMeta: kates.ObjectMeta{Namespace: "default", Name: name},
175 Spec: v3alpha1.MappingSpec{
176 Prefix: pfx,
177 Service: "127.0.0.1:8877",
178 },
179 }
180 }
181
182 func JSONifyRenderedListeners(renderedListeners []RenderedListener) (string, error) {
183
184
185
186 toDump := []RenderedListener{}
187
188 for _, l := range renderedListeners {
189 for _, c := range l.Chains {
190 for _, v := range c.VHosts {
191 if len(v.Routes) > 1 {
192 sort.SliceStable(v.Routes, func(i, j int) bool {
193 if v.Routes[i].Path != v.Routes[j].Path {
194 return v.Routes[i].Path < v.Routes[j].Path
195 }
196
197 if v.Routes[i].Host != v.Routes[j].Host {
198 return v.Routes[i].Host < v.Routes[j].Host
199 }
200
201 if v.Routes[i].Action != v.Routes[j].Action {
202 return v.Routes[i].Action < v.Routes[j].Action
203 }
204
205 return v.Routes[i].ActionArg < v.Routes[j].ActionArg
206 })
207 }
208
209 c.VHostList = append(c.VHostList, v)
210 }
211
212 if len(c.VHostList) > 1 {
213 sort.SliceStable(c.VHostList, func(i, j int) bool {
214 return c.VHostList[i].Name < c.VHostList[j].Name
215 })
216 }
217
218 l.ChainList = append(l.ChainList, c)
219 }
220
221 if len(l.ChainList) > 1 {
222 sort.SliceStable(l.ChainList, func(i, j int) bool {
223 sNamesI := l.ChainList[i].ServerNames
224 sNamesJ := l.ChainList[j].ServerNames
225
226 if (len(sNamesI) > 0) && (len(sNamesJ) > 0) {
227 if l.ChainList[i].ServerNames[0] != l.ChainList[j].ServerNames[0] {
228 return l.ChainList[i].ServerNames[0] < l.ChainList[j].ServerNames[0]
229 }
230 }
231
232 return l.ChainList[i].TransportProtocol < l.ChainList[j].TransportProtocol
233 })
234 }
235
236 toDump = append(toDump, l)
237 }
238
239 if len(toDump) > 1 {
240 sort.SliceStable(toDump, func(i, j int) bool {
241 return toDump[i].Port < toDump[j].Port
242 })
243 }
244
245 return JSONify(toDump)
246 }
247
248 type Candidate struct {
249 Scheme string
250 Action string
251 ActionArg string
252 }
253
254 func RenderEnvoyConfig(t *testing.T, envoyConfig *apiv3_bootstrap.Bootstrap) ([]RenderedListener, error) {
255 renderedListeners := make([]RenderedListener, 0, 3)
256
257 for _, l := range envoyConfig.StaticResources.Listeners {
258 port := l.Address.GetSocketAddress().GetPortValue()
259
260 t.Logf("LISTENER %s on port %d (chains %d)", l.Name, port, len(l.FilterChains))
261 rlistener := NewRenderedListener(l.Name, port)
262
263 for _, chain := range l.FilterChains {
264 t.Logf(" CHAIN %s", chain.FilterChainMatch)
265
266 rchain := NewRenderedChain(chain.FilterChainMatch.ServerNames, chain.FilterChainMatch.TransportProtocol)
267
268 for _, filter := range chain.Filters {
269 if filter.Name != ecp_wellknown.HTTPConnectionManager {
270
271
272 continue
273 }
274
275
276
277 hcm := ecp_v3_resource.GetHTTPConnectionManager(filter)
278 if hcm == nil {
279 continue
280 }
281
282
283
284 rs, ok := hcm.RouteSpecifier.(*apiv3_httpman.HttpConnectionManager_RouteConfig)
285 if !ok {
286 continue
287 }
288
289 rc := rs.RouteConfig
290
291 for _, vhost := range rc.VirtualHosts {
292 t.Logf(" VHost %s", vhost.Name)
293
294 rvh := NewRenderedVHost(vhost.Name)
295
296 for _, domain := range vhost.Domains {
297 for _, route := range vhost.Routes {
298 m := route.Match
299 pfx := m.GetPrefix()
300 hdrs := m.GetHeaders()
301 scheme := "implicit-http"
302
303 if !strings.HasPrefix(pfx, "/") {
304 pfx = "/" + pfx
305 }
306
307 authority := ""
308 authorityMatch := ""
309
310 for _, h := range hdrs {
311 hName := h.Name
312 prefixMatch := h.GetPrefixMatch()
313 suffixMatch := h.GetSuffixMatch()
314 exactMatch := h.GetExactMatch()
315
316 regexMatch := ""
317 srm := h.GetSafeRegexMatch()
318
319 if srm != nil {
320 regexMatch = srm.Regex
321
322
323 }
324
325
326
327 if exactMatch != "" {
328 if hName == "x-forwarded-proto" {
329 scheme = exactMatch
330 continue
331 }
332
333 authority = exactMatch
334 authorityMatch = "=="
335 } else if prefixMatch != "" {
336 authority = prefixMatch + "*"
337 authorityMatch = "gl~"
338 } else if suffixMatch != "" {
339 authority = "*" + suffixMatch
340 authorityMatch = "gl~"
341 } else if regexMatch != "" {
342 authority = regexMatch
343 authorityMatch = "re~"
344 }
345 }
346
347 actionRoute := route.GetRoute()
348 actionRedirect := route.GetRedirect()
349
350 finalAction := "???"
351 finalActionArg := ""
352
353 if actionRoute != nil {
354 finalAction = "ROUTE"
355 finalActionArg = " " + actionRoute.GetCluster()
356 } else if actionRedirect != nil {
357 finalAction = "REDIRECT"
358
359 if actionRedirect.GetHttpsRedirect() {
360 finalActionArg = " HTTPS"
361 } else {
362 finalActionArg = fmt.Sprintf(" %#v", actionRedirect)
363 }
364 }
365
366 rroute := RenderedRoute{
367 Scheme: scheme,
368 Host: domain,
369 Path: pfx,
370 Authority: authority,
371 AuthorityMatch: authorityMatch,
372 Action: finalAction,
373 ActionArg: finalActionArg,
374 }
375
376 rvh.AddRoute(rroute)
377
378 t.Logf(" %s", rroute.String())
379
380
381
382
383
384
385
386
387 }
388 }
389
390 rchain.AddVHost(&rvh)
391 }
392 }
393
394 if err := rlistener.AddChain(&rchain); err != nil {
395 return nil, err
396 }
397 }
398
399 renderedListeners = append(renderedListeners, rlistener)
400 }
401
402 return renderedListeners, nil
403 }
404
View as plain text