Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NET-5531 Translate response header modifier(s) from HTTPRoute onto Consul config entry #2904

Merged
merged 7 commits into from
Sep 12, 2023
3 changes: 3 additions & 0 deletions .changelog/2904.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
api-gateway: Add support for response header modifiers in HTTPRoute filters
```
4 changes: 3 additions & 1 deletion control-plane/api-gateway/common/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ func (e entryComparator) httpRoutesEqual(a, b api.HTTPRouteConfigEntry) bool {
func (e entryComparator) httpRouteRulesEqual(a, b api.HTTPRouteRule) bool {
return slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) &&
bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) &&
slices.EqualFunc(a.ResponseFilters.Headers, b.ResponseFilters.Headers, e.httpHeaderFiltersEqual) &&
slices.EqualFunc(a.Matches, b.Matches, e.httpMatchesEqual) &&
slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) &&
bothNilOrEqualFunc(a.Filters.RetryFilter, b.Filters.RetryFilter, e.retryFiltersEqual) &&
Expand All @@ -160,7 +161,8 @@ func (e entryComparator) httpServicesEqual(a, b api.HTTPService) bool {
orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) &&
orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) &&
slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) &&
bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual)
bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) &&
slices.EqualFunc(a.ResponseFilters.Headers, b.ResponseFilters.Headers, e.httpHeaderFiltersEqual)
}

func (e entryComparator) httpMatchesEqual(a, b api.HTTPMatch) bool {
Expand Down
109 changes: 77 additions & 32 deletions control-plane/api-gateway/common/translation.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,13 @@ func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, ru
}

matches := ConvertSliceFunc(rule.Matches, t.translateHTTPMatch)
filters := t.translateHTTPFilters(rule.Filters, resources, route.Namespace)
filters, responseFilters := t.translateHTTPFilters(rule.Filters, resources, route.Namespace)

return api.HTTPRouteRule{
Services: services,
Matches: matches,
Filters: filters,
Filters: filters,
Matches: matches,
ResponseFilters: responseFilters,
Services: services,
}, true
}

Expand All @@ -196,28 +197,30 @@ func (t ResourceTranslator) translateHTTPBackendRef(route gwv1beta1.HTTPRoute, r
isServiceRef := NilOrEqual(ref.Group, "") && NilOrEqual(ref.Kind, "Service")

if isServiceRef && resources.HasService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) {
filters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace)
filters, responseFilters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace)
service := resources.Service(id)
return api.HTTPService{
Name: service.Name,
Namespace: service.Namespace,
Partition: t.ConsulPartition,
Filters: filters,
Weight: DerefIntOr(ref.Weight, 1),
Name: service.Name,
Namespace: service.Namespace,
Partition: t.ConsulPartition,
Filters: filters,
ResponseFilters: responseFilters,
Weight: DerefIntOr(ref.Weight, 1),
}, true
}

isMeshServiceRef := DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && DerefEqual(ref.Kind, v1alpha1.MeshServiceKind)
if isMeshServiceRef && resources.HasMeshService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) {
filters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace)
filters, responseFilters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace)
service := resources.MeshService(id)

return api.HTTPService{
Name: service.Name,
Namespace: service.Namespace,
Partition: t.ConsulPartition,
Filters: filters,
Weight: DerefIntOr(ref.Weight, 1),
Name: service.Name,
Namespace: service.Namespace,
Partition: t.ConsulPartition,
Filters: filters,
ResponseFilters: responseFilters,
Weight: DerefIntOr(ref.Weight, 1),
}, true
}

Expand Down Expand Up @@ -275,26 +278,61 @@ func (t ResourceTranslator) translateHTTPQueryMatch(match gwv1beta1.HTTPQueryPar
}
}

func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter, resourceMap *ResourceMap, namespace string) api.HTTPFilters {
var urlRewrite *api.URLRewrite
consulFilter := api.HTTPHeaderFilter{
Add: make(map[string]string),
Set: make(map[string]string),
}
var retryFilter *api.RetryFilter
var timeoutFilter *api.TimeoutFilter

func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter, resourceMap *ResourceMap, namespace string) (api.HTTPFilters, api.HTTPResponseFilters) {
var (
urlRewrite *api.URLRewrite
retryFilter *api.RetryFilter
timeoutFilter *api.TimeoutFilter
requestHeaderFilters = []api.HTTPHeaderFilter{}
responseHeaderFilters = []api.HTTPHeaderFilter{}
)

// Convert Gateway API filters to portions of the Consul request and response filters.
// Multiple filters applying the same or conflicting operations are allowed but may
// result in unexpected behavior.
for _, filter := range filters {
if filter.RequestHeaderModifier != nil {
consulFilter.Remove = append(consulFilter.Remove, filter.RequestHeaderModifier.Remove...)
newFilter := api.HTTPHeaderFilter{}

for _, toAdd := range filter.RequestHeaderModifier.Add {
consulFilter.Add[string(toAdd.Name)] = toAdd.Value
newFilter.Remove = append(newFilter.Remove, filter.RequestHeaderModifier.Remove...)

if len(filter.RequestHeaderModifier.Add) > 0 {
newFilter.Add = map[string]string{}
for _, toAdd := range filter.RequestHeaderModifier.Add {
newFilter.Add[string(toAdd.Name)] = toAdd.Value
}
}

for _, toSet := range filter.RequestHeaderModifier.Set {
consulFilter.Set[string(toSet.Name)] = toSet.Value
if len(filter.RequestHeaderModifier.Set) > 0 {
newFilter.Set = map[string]string{}
for _, toSet := range filter.RequestHeaderModifier.Set {
newFilter.Set[string(toSet.Name)] = toSet.Value
}
}

requestHeaderFilters = append(requestHeaderFilters, newFilter)
}

if filter.ResponseHeaderModifier != nil {
newFilter := api.HTTPHeaderFilter{}

newFilter.Remove = append(newFilter.Remove, filter.ResponseHeaderModifier.Remove...)

if len(filter.ResponseHeaderModifier.Add) > 0 {
newFilter.Add = map[string]string{}
for _, toAdd := range filter.ResponseHeaderModifier.Add {
newFilter.Add[string(toAdd.Name)] = toAdd.Value
}
}

if len(filter.ResponseHeaderModifier.Set) > 0 {
newFilter.Set = map[string]string{}
for _, toSet := range filter.ResponseHeaderModifier.Set {
newFilter.Set[string(toSet.Name)] = toSet.Value
}
}

responseHeaderFilters = append(responseHeaderFilters, newFilter)
}

// we drop any path rewrites that are not prefix matches as we don't support those
Expand Down Expand Up @@ -338,12 +376,19 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi
}

}
return api.HTTPFilters{
Headers: []api.HTTPHeaderFilter{consulFilter},

requestFilter := api.HTTPFilters{
Headers: requestHeaderFilters,
URLRewrite: urlRewrite,
RetryFilter: retryFilter,
TimeoutFilter: timeoutFilter,
}

responseFilter := api.HTTPResponseFilters{
Headers: responseHeaderFilters,
}

return requestFilter, responseFilter
}

func (t ResourceTranslator) ToTCPRoute(route gwv1alpha2.TCPRoute, resources *ResourceMap) *api.TCPRouteConfigEntry {
Expand Down
Loading