1 package gateway
2
3 import (
4
5 "fmt"
6
7
8 "github.com/pkg/errors"
9 "google.golang.org/protobuf/types/known/anypb"
10 "google.golang.org/protobuf/types/known/wrapperspb"
11 gw "sigs.k8s.io/gateway-api/apis/v1alpha1"
12
13
14 apiv2 "github.com/datawire/ambassador/v2/pkg/api/envoy/api/v2"
15 apiv2_core "github.com/datawire/ambassador/v2/pkg/api/envoy/api/v2/core"
16 apiv2_listener "github.com/datawire/ambassador/v2/pkg/api/envoy/api/v2/listener"
17 apiv2_route "github.com/datawire/ambassador/v2/pkg/api/envoy/api/v2/route"
18 apiv2_httpman "github.com/datawire/ambassador/v2/pkg/api/envoy/config/filter/network/http_connection_manager/v2"
19 api_matcher "github.com/datawire/ambassador/v2/pkg/api/envoy/type/matcher"
20
21
22 ecp_wellknown "github.com/datawire/ambassador/v2/pkg/envoy-control-plane/wellknown"
23
24
25 "github.com/datawire/ambassador/v2/pkg/kates"
26 )
27
28 func Compile_Gateway(gateway *gw.Gateway) (*CompiledConfig, error) {
29 src := SourceFromResource(gateway)
30 var listeners []*CompiledListener
31 for idx, l := range gateway.Spec.Listeners {
32 name := fmt.Sprintf("%s-%d", getName(gateway), idx)
33 listener, err := Compile_Listener(src, l, name)
34 if err != nil {
35 return nil, err
36 }
37 listeners = append(listeners, listener)
38 }
39 return &CompiledConfig{
40 CompiledItem: NewCompiledItem(src),
41 Listeners: listeners,
42 }, nil
43 }
44
45 func Compile_Listener(parent Source, lst gw.Listener, name string) (*CompiledListener, error) {
46 hcm := &apiv2_httpman.HttpConnectionManager{
47 StatPrefix: name,
48 HttpFilters: []*apiv2_httpman.HttpFilter{
49 {Name: ecp_wellknown.CORS},
50 {Name: ecp_wellknown.Router},
51 },
52 RouteSpecifier: &apiv2_httpman.HttpConnectionManager_Rds{
53 Rds: &apiv2_httpman.Rds{
54 ConfigSource: &apiv2_core.ConfigSource{
55 ConfigSourceSpecifier: &apiv2_core.ConfigSource_Ads{
56 Ads: &apiv2_core.AggregatedConfigSource{},
57 },
58 },
59 RouteConfigName: name,
60 },
61 },
62 }
63 hcmAny, err := anypb.New(hcm)
64 if err != nil {
65 return nil, err
66 }
67
68 return &CompiledListener{
69 CompiledItem: NewCompiledItem(Sourcef("listener %s", lst.Hostname)),
70 Listener: &apiv2.Listener{
71 Name: name,
72 Address: &apiv2_core.Address{Address: &apiv2_core.Address_SocketAddress{SocketAddress: &apiv2_core.SocketAddress{
73 Address: "0.0.0.0",
74 PortSpecifier: &apiv2_core.SocketAddress_PortValue{PortValue: uint32(lst.Port)},
75 }}},
76 FilterChains: []*apiv2_listener.FilterChain{
77 {
78 Filters: []*apiv2_listener.Filter{
79 {
80 Name: ecp_wellknown.HTTPConnectionManager,
81 ConfigType: &apiv2_listener.Filter_TypedConfig{TypedConfig: hcmAny},
82 },
83 },
84 },
85 },
86 },
87 Predicate: func(route *CompiledRoute) bool {
88 return true
89 },
90 Domains: []string{"*"},
91 }, nil
92
93 }
94
95 func Compile_HTTPRoute(httpRoute *gw.HTTPRoute) (*CompiledConfig, error) {
96 src := SourceFromResource(httpRoute)
97 clusterRefs := []*ClusterRef{}
98 var routes []*apiv2_route.Route
99 for idx, rule := range httpRoute.Spec.Rules {
100 s := Sourcef("rule %d in %s", idx, src)
101 _routes, err := Compile_HTTPRouteRule(s, rule, httpRoute.Namespace, &clusterRefs)
102 if err != nil {
103 return nil, err
104 }
105 routes = append(routes, _routes...)
106 }
107 return &CompiledConfig{
108 CompiledItem: NewCompiledItem(src),
109 Routes: []*CompiledRoute{
110 {
111 CompiledItem: CompiledItem{Source: src, Namespace: httpRoute.Namespace},
112 HTTPRoute: httpRoute,
113 Routes: routes,
114 ClusterRefs: clusterRefs,
115 },
116 },
117 }, nil
118 }
119
120 func Compile_HTTPRouteRule(src Source, rule gw.HTTPRouteRule, namespace string, clusterRefs *[]*ClusterRef) ([]*apiv2_route.Route, error) {
121 var clusters []*apiv2_route.WeightedCluster_ClusterWeight
122 for idx, fwd := range rule.ForwardTo {
123 s := Sourcef("forwardTo %d in %s", idx, src)
124 clusters = append(clusters, Compile_HTTPRouteForwardTo(s, fwd, namespace, clusterRefs))
125 }
126
127 wc := &apiv2_route.WeightedCluster{Clusters: clusters}
128
129 matches, err := Compile_HTTPRouteMatches(rule.Matches)
130 if err != nil {
131 return nil, err
132 }
133 var result []*apiv2_route.Route
134 for _, match := range matches {
135 result = append(result, &apiv2_route.Route{
136 Match: match,
137 Action: &apiv2_route.Route_Route{Route: &apiv2_route.RouteAction{
138 ClusterSpecifier: &apiv2_route.RouteAction_WeightedClusters{WeightedClusters: wc},
139 }},
140 })
141 }
142
143 return result, err
144 }
145
146 func Compile_HTTPRouteForwardTo(src Source, forward gw.HTTPRouteForwardTo, namespace string, clusterRefs *[]*ClusterRef) *apiv2_route.WeightedCluster_ClusterWeight {
147 suffix := ""
148 clusterName := *forward.ServiceName
149 if forward.Port != nil {
150 suffix = fmt.Sprintf("/%d", *forward.Port)
151 clusterName = fmt.Sprintf("%s_%d", *forward.ServiceName, *forward.Port)
152 }
153
154 *clusterRefs = append(*clusterRefs, &ClusterRef{
155 CompiledItem: NewCompiledItem(src),
156 Name: clusterName,
157 EndpointPath: fmt.Sprintf("k8s/%s/%s%s", namespace, *forward.ServiceName, suffix),
158 })
159 return &apiv2_route.WeightedCluster_ClusterWeight{
160 Name: clusterName,
161 Weight: &wrapperspb.UInt32Value{Value: uint32(forward.Weight)},
162 }
163 }
164
165 func Compile_HTTPRouteMatches(matches []gw.HTTPRouteMatch) ([]*apiv2_route.RouteMatch, error) {
166 var result []*apiv2_route.RouteMatch
167 for _, match := range matches {
168 item, err := Compile_HTTPRouteMatch(match)
169 if err != nil {
170 return nil, err
171 }
172 result = append(result, item)
173 }
174 return result, nil
175 }
176
177 func Compile_HTTPRouteMatch(match gw.HTTPRouteMatch) (*apiv2_route.RouteMatch, error) {
178 headers, err := Compile_HTTPHeaderMatch(match.Headers)
179 if err != nil {
180 return nil, err
181 }
182 result := &apiv2_route.RouteMatch{
183 Headers: headers,
184 }
185
186 switch match.Path.Type {
187 case gw.PathMatchExact:
188 result.PathSpecifier = &apiv2_route.RouteMatch_Path{Path: match.Path.Value}
189 case gw.PathMatchPrefix:
190 result.PathSpecifier = &apiv2_route.RouteMatch_Prefix{Prefix: match.Path.Value}
191 case gw.PathMatchRegularExpression:
192 result.PathSpecifier = &apiv2_route.RouteMatch_SafeRegex{SafeRegex: regexMatcher(match.Path.Value)}
193 case "":
194
195 result.PathSpecifier = &apiv2_route.RouteMatch_Prefix{}
196 default:
197 return nil, errors.Errorf("unknown path match type: %q", match.Path.Type)
198 }
199
200 return result, nil
201 }
202
203 func Compile_HTTPHeaderMatch(headerMatch *gw.HTTPHeaderMatch) ([]*apiv2_route.HeaderMatcher, error) {
204 if headerMatch == nil {
205 return nil, nil
206 }
207
208 var result []*apiv2_route.HeaderMatcher
209 for hdr, pattern := range headerMatch.Values {
210 hm := &apiv2_route.HeaderMatcher{
211 Name: hdr,
212 InvertMatch: false,
213 }
214
215 switch headerMatch.Type {
216 case gw.HeaderMatchExact:
217 hm.HeaderMatchSpecifier = &apiv2_route.HeaderMatcher_ExactMatch{ExactMatch: pattern}
218 case gw.HeaderMatchRegularExpression:
219 hm.HeaderMatchSpecifier = &apiv2_route.HeaderMatcher_SafeRegexMatch{SafeRegexMatch: regexMatcher(pattern)}
220 default:
221 return nil, errors.Errorf("unknown header match type: %s", headerMatch.Type)
222 }
223
224 result = append(result, hm)
225 }
226 return result, nil
227 }
228
229 func regexMatcher(pattern string) *api_matcher.RegexMatcher {
230 return &api_matcher.RegexMatcher{
231 EngineType: &api_matcher.RegexMatcher_GoogleRe2{GoogleRe2: &api_matcher.RegexMatcher_GoogleRE2{}},
232 Regex: pattern,
233 }
234 }
235
236 func getName(resource kates.Object) string {
237 return fmt.Sprintf("%s-%s", resource.GetNamespace(), resource.GetName())
238 }
239
View as plain text