Skip to content

Commit

Permalink
Merge pull request #2381 from sgayangi/#2378-httproute-filters
Browse files Browse the repository at this point in the history
Add support for HeaderModifier filter in Config Deployer
  • Loading branch information
CrowleyRajapakse authored Jun 18, 2024
2 parents c48ad64 + cf4675c commit 3115de5
Show file tree
Hide file tree
Showing 9 changed files with 587 additions and 71 deletions.
25 changes: 22 additions & 3 deletions adapter/internal/controlplane/eventPublisher.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,30 @@ type API struct {
APIHash string `json:"-"`
}

// Headers contains the request and response header modifier information
type Headers struct {
RequestHeaders HeaderModifier `json:"requestHeaders"`
ResponseHeaders HeaderModifier `json:"responseHeaders"`
}

// HeaderModifier contains header modifier values
type HeaderModifier struct {
AddHeaders []Header `json:"addHeaders"`
RemoveHeaders []string `json:"removeHeaders"`
}

// Header contains the header information
type Header struct {
Name string `json:"headerName"`
Value string `json:"headerValue,omitempty"`
}

// Operation holds the path, verb, throttling and interceptor policy
type Operation struct {
Path string `json:"path"`
Verb string `json:"verb"`
Scopes []string `json:"scopes"`
Path string `json:"path"`
Verb string `json:"verb"`
Scopes []string `json:"scopes"`
Headers Headers `json:"headers"`
}

// CORSPolicy hold cors configs
Expand Down
47 changes: 46 additions & 1 deletion adapter/internal/operator/controllers/dp/api_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2672,14 +2672,45 @@ func prepareOperations(apiState *synchronizer.APIState) []controlplane.Operation
if apiState.ProdHTTPRoute != nil && apiState.ProdHTTPRoute.HTTPRouteCombined != nil {
for _, rule := range apiState.ProdHTTPRoute.HTTPRouteCombined.Spec.Rules {
scopes := []string{}
requestAddHeaders := []controlplane.Header{}
responseAddHeaders := []controlplane.Header{}
requestRemoveHeaders := []string{}
responseRemoveHeaders := []string{}
for _, filter := range rule.Filters {
if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == "Scope" {
scope, found := apiState.ProdHTTPRoute.Scopes[types.NamespacedName{Namespace: apiState.APIDefinition.ObjectMeta.Namespace, Name: string(filter.ExtensionRef.Name)}.String()]
if found {
scopes = append(scopes, scope.Spec.Names...)
}
}

if filter.RequestHeaderModifier != nil {
requestHeaderModifier := filter.RequestHeaderModifier
for _, addHeader := range requestHeaderModifier.Add {
requestAddHeaders = append(requestAddHeaders, controlplane.Header{Name: string(addHeader.Name), Value: string(addHeader.Value)})
}
for _, setHeader := range requestHeaderModifier.Set {
requestAddHeaders = append(requestAddHeaders, controlplane.Header{Name: string(setHeader.Name), Value: string(setHeader.Value)})
}
for _, removeHeader := range requestHeaderModifier.Remove {
requestRemoveHeaders = append(requestRemoveHeaders, removeHeader)
}
}

if filter.ResponseHeaderModifier != nil {
responseHeaderModifier := filter.ResponseHeaderModifier
for _, addHeader := range responseHeaderModifier.Add {
responseAddHeaders = append(responseAddHeaders, controlplane.Header{Name: string(addHeader.Name), Value: string(addHeader.Value)})
}
for _, setHeader := range responseHeaderModifier.Set {
responseAddHeaders = append(responseAddHeaders, controlplane.Header{Name: string(setHeader.Name), Value: string(setHeader.Value)})
}
for _, removeHeader := range responseHeaderModifier.Remove {
responseRemoveHeaders = append(responseRemoveHeaders, removeHeader)
}
}
}

for _, match := range rule.Matches {
path := "/"
verb := "GET"
Expand All @@ -2693,7 +2724,21 @@ func prepareOperations(apiState *synchronizer.APIState) []controlplane.Operation
path = path + "*"
}
path = "^" + path + "$"
operations = append(operations, controlplane.Operation{Path: path, Verb: verb, Scopes: scopes})
operations = append(operations, controlplane.Operation{
Path: path,
Verb: verb,
Scopes: scopes,
Headers: controlplane.Headers{
RequestHeaders: controlplane.HeaderModifier{
AddHeaders: requestAddHeaders,
RemoveHeaders: requestRemoveHeaders,
},
ResponseHeaders: controlplane.HeaderModifier{
AddHeaders: responseAddHeaders,
RemoveHeaders: responseRemoveHeaders,
},
},
})
}
}
}
Expand Down
102 changes: 73 additions & 29 deletions runtime/config-deployer-service/ballerina/APIClient.bal
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ public class APIClient {
sandboxRoutes.push(gqlRoute.metadata.name);
}
}
} else {
} else if apkConf.'type == API_TYPE_REST {
foreach model:HTTPRoute httpRoute in apiArtifact.productionHttpRoutes {
if httpRoute.spec.rules.length() > 0 {
productionRoutes.push(httpRoute.metadata.name);
Expand Down Expand Up @@ -538,7 +538,7 @@ public class APIClient {
apiArtifact.sandboxGqlRoutes.push(gqlRoute);
}
}
} else {
} else if apkConf.'type == API_TYPE_REST {
model:HTTPRoute httpRoute = {
metadata:
{
Expand All @@ -558,6 +558,8 @@ public class APIClient {
apiArtifact.sandboxHttpRoutes.push(httpRoute);
}
}
} else {
return e909018("Invalid API Type specified");
}

return;
Expand Down Expand Up @@ -784,14 +786,23 @@ public class APIClient {
} else {
return e909022("Provided Type currently not supported for GraphQL APIs.", error("Provided Type currently not supported for GraphQL APIs."));
}
} else if apkConf.'type == API_TYPE_REST {
{
model:HTTPRouteRule httpRouteRule = {
matches: self.retrieveHTTPMatches(apkConf, operation, organization),
backendRefs: self.retrieveGeneratedBackend(apkConf, endpointToUse, endpointType),
filters: self.generateFilters(apiArtifact, apkConf, endpointToUse, operation, endpointType, organization)
};
return httpRouteRule;
}
} else {
model:HTTPRouteRule httpRouteRule = {matches: self.retrieveHTTPMatches(apkConf, operation, organization), backendRefs: self.retrieveGeneratedBackend(apkConf, endpointToUse, endpointType), filters: self.generateFilters(apiArtifact, apkConf, endpointToUse, operation, endpointType, organization)};
return httpRouteRule;
return e909018("Invalid API Type specified");
}
} else {
return ();
}
} on fail var e {
}
on fail var e {
log:printError("Internal Error occured", e);
return e909022("Internal Error occured", e);
}
Expand All @@ -800,46 +811,80 @@ public class APIClient {
private isolated function generateFilters(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint endpoint, APKOperations operation, string endpointType, commons:Organization organization) returns model:HTTPRouteFilter[] {
model:HTTPRouteFilter[] routeFilters = [];
string generatedPath = self.generatePrefixMatch(endpoint, operation);
model:HTTPRouteFilter replacePathFilter = {'type: "URLRewrite", urlRewrite: {path: {'type: "ReplaceFullPath", replaceFullPath: generatedPath}}};
model:HTTPRouteFilter replacePathFilter = {
'type: "URLRewrite",
urlRewrite: {
path: {
'type: "ReplaceFullPath",
replaceFullPath: generatedPath
}
}
};
routeFilters.push(replacePathFilter);
APIOperationPolicies? operationPoliciesToUse = ();
if (apkConf.apiPolicies is APIOperationPolicies) {
operationPoliciesToUse = apkConf.apiPolicies;
APIOperationPolicies? operationPolicies = apkConf.apiPolicies;
if (operationPolicies is APIOperationPolicies && operationPolicies != {}) {
if operationPolicies.request is APKOperationPolicy[] || operationPolicies.response is APKOperationPolicy[] {
operationPoliciesToUse = apkConf.apiPolicies;
}
} else {
operationPoliciesToUse = operation.operationPolicies;
}
if operationPoliciesToUse is APIOperationPolicies {
APKOperationPolicy[]? request = operationPoliciesToUse.request;
APKOperationPolicy[]? requestPolicies = operationPoliciesToUse.request;
APKOperationPolicy[]? responsePolicies = operationPoliciesToUse.response;

if requestPolicies is APKOperationPolicy[] && requestPolicies.length() > 0 {
model:HTTPRouteFilter headerModifierFilter = {'type: "RequestHeaderModifier"};
headerModifierFilter.requestHeaderModifier = self.extractHttpHeaderFilterData(requestPolicies, organization);
routeFilters.push(headerModifierFilter);
}
if responsePolicies is APKOperationPolicy[] && responsePolicies.length() > 0 {
model:HTTPRouteFilter headerModifierFilter = {'type: "ResponseHeaderModifier"};
headerModifierFilter.responseHeaderModifier = self.extractHttpHeaderFilterData(responsePolicies, organization);
routeFilters.push(headerModifierFilter);
}
}
return routeFilters;
}

isolated function extractHttpHeaderFilterData(APKOperationPolicy[] operationPolicy, commons:Organization organization) returns model:HTTPHeaderFilter {
model:HTTPHeader[] addPolicies = [];
model:HTTPHeader[] setPolicies = [];
string[] removePolicies = [];
foreach APKOperationPolicy policy in operationPolicy {
string policyName = policy.policyName;

record {}? policyParameters = policy.parameters;
if (policyParameters is record {}) {
if (policyName == "addHeader") {
model:HTTPHeader httpHeader = {
name: <string>policyParameters.get("headerName"),
value: <string>policyParameters.get("headerValue")
};
setPolicies.push(httpHeader);
}
if (policyName == "removeHeader") {
string httpHeader = <string>policyParameters.get("headerName");
removePolicies.push(httpHeader);
if policy is HeaderModifierPolicy {
HeaderModifierPolicyParameters policyParameters = policy.parameters;
match policy.policyName {
AddHeaders => {
ModifierHeader[] addHeaders = <ModifierHeader[]>policyParameters.headers;
foreach ModifierHeader header in addHeaders {
addPolicies.push(header);
}
}
SetHeaders => {
ModifierHeader[] setHeaders = <ModifierHeader[]>policyParameters.headers;
foreach ModifierHeader header in setHeaders {
setPolicies.push(header);
}
}
RemoveHeaders => {
string[] removeHeaders = <string[]>policyParameters.headers;
foreach string header in removeHeaders {
removePolicies.push(header);
}
}
}
}
}
model:HTTPHeaderFilter headerModifier = {};
if (setPolicies != []) {
if addPolicies != [] {
headerModifier.add = addPolicies;
}
if setPolicies != [] {
headerModifier.set = setPolicies;
}
if (removePolicies != []) {
if removePolicies != [] {
headerModifier.remove = removePolicies;
}
return headerModifier;
Expand Down Expand Up @@ -1204,7 +1249,7 @@ public class APIClient {
foreach APKOperationPolicy policy in policies {
string policyName = policy.policyName;
if policy.parameters is record {} {
if (policyName == "Interceptor") {
if (policyName == Interceptor) {
InterceptorPolicy interceptorPolicy = check policy.cloneWithType(InterceptorPolicy);
InterceptorPolicy_parameters parameters = <InterceptorPolicy_parameters>interceptorPolicy?.parameters;
EndpointConfiguration endpointConfig = {endpoint: parameters.backendUrl ?: "", certificate: {secretName: parameters.tlsSecretName, secretKey: parameters.tlsSecretKey}};
Expand All @@ -1223,12 +1268,12 @@ public class APIClient {
};
}
policyReferences.push(interceptorReference);
} else if (policyName == "BackendJwt") {
} else if (policyName == BackendJwt) {
BackendJWTPolicy backendJWTPolicy = check policy.cloneWithType(BackendJWTPolicy);
model:BackendJWT backendJwt = self.retrieveBackendJWTPolicy(apkConf, apiArtifact, backendJWTPolicy, operations, organization);
apiArtifact.backendJwt = backendJwt;
policyReferences.push(<model:BackendJwtReference>{name: backendJwt.metadata.name});
} else {
} else if policyName != AddHeaders && policyName != SetHeaders && policyName != RemoveHeaders {
return e909052(error("Incorrect API Policy name provided."));
}
}
Expand Down Expand Up @@ -1593,7 +1638,6 @@ public class APIClient {
private isolated function validateAndRetrieveAPKConfiguration(json apkconfJson) returns APKConf|commons:APKError? {
do {
runtimeapi:APKConfValidationResponse validationResponse = check apkConfValidator.validate(apkconfJson.toJsonString());

if validationResponse.isValidated() {
APKConf apkConf = check apkconfJson.cloneWithType(APKConf);
map<string> errors = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,15 @@ components:
oneOf:
- $ref: "#/components/schemas/InterceptorPolicy"
- $ref: "#/components/schemas/BackendJWTPolicy"
- $ref: "#/components/schemas/HeaderModifierPolicy"
discriminator:
propertyName: "policyName"
mapping:
BackendJwt: "#/components/schemas/BackendJWTPolicy"
Interceptor: "#/components/schemas/InterceptorPolicy"
AddHeaders: "#/components/schemas/HeaderModifierPolicy"
SetHeaders: "#/components/schemas/HeaderModifierPolicy"
RemoveHeadersHeaders: "#/components/schemas/HeaderModifierPolicy"
BaseOperationPolicy:
title: API Operation Policy
required:
Expand All @@ -269,6 +273,12 @@ components:
properties:
policyName:
type: string
enum:
- AddHeaders
- RemoveHeaders
- SetHeaders
- Interceptor
- BackendJwt
policyVersion:
type: string
default: "v1"
Expand Down Expand Up @@ -493,6 +503,24 @@ components:
required:
- enabled
additionalProperties: false
HeaderModifierPolicy:
title: Header Modifier Parameters
type: object
properties:
headers:
type: array
items:
oneOf:
- $ref: "#/components/schemas/Header"
- type: string
additionalProperties: false
Header:
type: object
properties:
name:
type: string
value:
type: string
CustomClaims:
type: object
required:
Expand Down
Loading

0 comments on commit 3115de5

Please sign in to comment.