Skip to content

Commit

Permalink
Add GraphQL API CR generation to config deployer
Browse files Browse the repository at this point in the history
  • Loading branch information
sgayangi committed Feb 1, 2024
1 parent 1ede74f commit 7a72127
Show file tree
Hide file tree
Showing 22 changed files with 497 additions and 220 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class GraphQLPayloadUtils {
private static final Logger logger = LogManager.getLogger(GraphQLPayloadUtils.class);

/**
* This method will decode the qraphQL query body.
* This method will decode the graphQL query body.
*
* @param api matched api
* @param queryBody graphQL query
Expand Down
280 changes: 201 additions & 79 deletions runtime/config-deployer-service/ballerina/APIClient.bal

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class ConfigGeneratorClient {
if validateAndRetrieveDefinitionResult.isValid() {
runtimeapi:APIDefinition parser = validateAndRetrieveDefinitionResult.getParser();
runtimeModels:API apiFromDefinition = check parser.getAPIFromDefinition(validateAndRetrieveDefinitionResult.getContent());
apiFromDefinition.setType(apiType);
APIClient apiclient = new ();
APKConf generatedAPKConf = check apiclient.fromAPIModelToAPKConf(apiFromDefinition);
string|() apkConfYaml = check commons:newYamlUtil1().fromJsonStringToYaml(generatedAPKConf.toJsonString());
Expand Down Expand Up @@ -93,7 +94,7 @@ public class ConfigGeneratorClient {
private isolated function validateAndRetrieveDefinition(string 'type, string? url, byte[]? content, string? fileName) returns runtimeapi:APIDefinitionValidationResponse|runtimeapi:APIManagementException|error|commons:APKError {
runtimeapi:APIDefinitionValidationResponse|runtimeapi:APIManagementException|error validationResponse;
boolean typeAvailable = 'type.length() > 0;
string[] ALLOWED_API_DEFINITION_TYPES = ["REST", "GRAPHQL", "ASYNC"];
string[] ALLOWED_API_DEFINITION_TYPES = [API_TYPE_REST, API_TYPE_GRAPHQL, "ASYNC"];
if !typeAvailable {
return e909005("type");
}
Expand Down Expand Up @@ -171,14 +172,22 @@ public class ConfigGeneratorClient {
string yamlString = check self.convertJsonToYaml(authenticationCr.toJsonString());
_ = check self.storeFile(yamlString, authenticationCr.metadata.name, zipDir);
}
foreach model:Httproute httpRoute in apiArtifact.productionRoute {
foreach model:HTTPRoute httpRoute in apiArtifact.productionHttpRoutes {
string yamlString = check self.convertJsonToYaml(httpRoute.toJsonString());
_ = check self.storeFile(yamlString, httpRoute.metadata.name, zipDir);
}
foreach model:Httproute httpRoute in apiArtifact.sandboxRoute {
foreach model:HTTPRoute httpRoute in apiArtifact.sandboxHttpRoutes {
string yamlString = check self.convertJsonToYaml(httpRoute.toJsonString());
_ = check self.storeFile(yamlString, httpRoute.metadata.name, zipDir);
}
foreach model:GQLRoute gqlRoute in apiArtifact.productionGqlRoutes {
string yamlString = check self.convertJsonToYaml(gqlRoute.toJsonString());
_ = check self.storeFile(yamlString, gqlRoute.metadata.name, zipDir);
}
foreach model:GQLRoute gqlRoute in apiArtifact.sandboxGqlRoutes {
string yamlString = check self.convertJsonToYaml(gqlRoute.toJsonString());
_ = check self.storeFile(yamlString, gqlRoute.metadata.name, zipDir);
}
foreach model:Backend backend in apiArtifact.backendServices {
string yamlString = check self.convertJsonToYaml(backend.toJsonString());
_ = check self.storeFile(yamlString, backend.metadata.name, zipDir);
Expand Down
111 changes: 80 additions & 31 deletions runtime/config-deployer-service/ballerina/DeployerClient.bal
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ public class DeployerClient {
check self.deployInterceptorServiceCRs(apiArtifact, ownerReference);
check self.deployBackendJWTConfigs(apiArtifact, ownerReference);
check self.deployAPIPolicyCRs(apiArtifact, ownerReference);
check self.deployHttpRoutes(apiArtifact.productionRoute, <string>apiArtifact?.namespace, ownerReference);
check self.deployHttpRoutes(apiArtifact.sandboxRoute, <string>apiArtifact?.namespace, ownerReference);

check self.deployRoutes(apiArtifact.productionHttpRoutes, apiArtifact.productionGqlRoutes, <string>apiArtifact?.namespace, ownerReference);
check self.deployRoutes(apiArtifact.sandboxHttpRoutes, apiArtifact.sandboxGqlRoutes, <string>apiArtifact?.namespace, ownerReference);

return deployK8sAPICrResult;
} on fail var e {
http:Response|http:ClientError apiCRDeletionResponse = deleteAPICR(api.metadata.name, apiArtifact.namespace ?: "");
Expand Down Expand Up @@ -173,9 +175,9 @@ public class DeployerClient {

private isolated function deleteHttpRoutes(model:API api, string organization) returns commons:APKError? {
do {
model:HttprouteList|http:ClientError httpRouteListResponse = check getHttproutesForAPIS(api.spec.apiName, api.spec.apiVersion, <string>api.metadata?.namespace, organization);
if httpRouteListResponse is model:HttprouteList {
foreach model:Httproute item in httpRouteListResponse.items {
model:HTTPRouteList|http:ClientError httpRouteListResponse = check getHttproutesForAPIS(api.spec.apiName, api.spec.apiVersion, <string>api.metadata?.namespace, organization);
if httpRouteListResponse is model:HTTPRouteList {
foreach model:HTTPRoute item in httpRouteListResponse.items {
http:Response|http:ClientError httprouteDeletionResponse = deleteHttpRoute(item.metadata.name, <string>api.metadata?.namespace);
if httprouteDeletionResponse is http:Response {
if httprouteDeletionResponse.statusCode != http:STATUS_OK {
Expand Down Expand Up @@ -347,41 +349,72 @@ public class DeployerClient {
return e909022("Internal error occured", e = error("Internal error occured"));
}
}

private isolated function deployHttpRoutes(model:Httproute[] httproutes, string namespace, model:OwnerReference ownerReference) returns error? {
model:Httproute[] deployReadyHttproutes = httproutes;
model:Httproute[]|commons:APKError orderedHttproutes = self.createHttpRoutesOrder(httproutes);
if orderedHttproutes is model:Httproute[] {
deployReadyHttproutes = orderedHttproutes;
}
foreach model:Httproute httpRoute in deployReadyHttproutes {
httpRoute.metadata.ownerReferences = [ownerReference];
if httpRoute.spec.rules.length() > 0 {
http:Response deployHttpRouteResult = check deployHttpRoute(httpRoute, namespace);
if deployHttpRouteResult.statusCode == http:STATUS_CREATED {
log:printDebug("Deployed HttpRoute Successfully" + httpRoute.toString());
} else if deployHttpRouteResult.statusCode == http:STATUS_CONFLICT {
log:printDebug("HttpRoute already exists" + httpRoute.toString());
model:Httproute httpRouteFromK8s = check getHttpRoute(httpRoute.metadata.name, namespace);
httpRoute.metadata.resourceVersion = httpRouteFromK8s.metadata.resourceVersion;
http:Response httpRouteCR = check updateHttpRoute(httpRoute, namespace);
if httpRouteCR.statusCode != http:STATUS_OK {
json responsePayLoad = check httpRouteCR.getJsonPayload();
private isolated function deployRoutes(model:HTTPRoute[]? httproutes, model:GQLRoute[]? gqlroutes, string namespace, model:OwnerReference ownerReference) returns error? {
if httproutes is model:HTTPRoute[] {
model:HTTPRoute[] deployReadyHttproutes = httproutes;
model:HTTPRoute[]|commons:APKError orderedHttproutes = self.createHttpRoutesOrder(httproutes);
if orderedHttproutes is model:HTTPRoute[] {
deployReadyHttproutes = orderedHttproutes;
}
foreach model:HTTPRoute httpRoute in deployReadyHttproutes {
httpRoute.metadata.ownerReferences = [ownerReference];
if httpRoute.spec.rules.length() > 0 {
http:Response deployHttpRouteResult = check deployHttpRoute(httpRoute, namespace);
if deployHttpRouteResult.statusCode == http:STATUS_CREATED {
log:printDebug("Deployed HttpRoute Successfully" + httpRoute.toString());
} else if deployHttpRouteResult.statusCode == http:STATUS_CONFLICT {
log:printDebug("HttpRoute already exists" + httpRoute.toString());
model:HTTPRoute httpRouteFromK8s = check getHttpRoute(httpRoute.metadata.name, namespace);
httpRoute.metadata.resourceVersion = httpRouteFromK8s.metadata.resourceVersion;
http:Response httpRouteCR = check updateHttpRoute(httpRoute, namespace);
if httpRouteCR.statusCode != http:STATUS_OK {
json responsePayLoad = check httpRouteCR.getJsonPayload();
model:Status statusResponse = check responsePayLoad.cloneWithType(model:Status);
check self.handleK8sTimeout(statusResponse);
}
} else {
json responsePayLoad = check deployHttpRouteResult.getJsonPayload();
model:Status statusResponse = check responsePayLoad.cloneWithType(model:Status);
check self.handleK8sTimeout(statusResponse);
}
}
}
} else if gqlroutes is model:GQLRoute[] {
model:GQLRoute[] deployReadyGqlRoutes = gqlroutes;
model:GQLRoute[]|commons:APKError orderedGqlRoutes = self.createGqlRoutesOrder(gqlroutes);
if orderedGqlRoutes is model:GQLRoute[] {
deployReadyGqlRoutes = orderedGqlRoutes;
}
foreach model:GQLRoute gqlRoute in deployReadyGqlRoutes {
gqlRoute.metadata.ownerReferences = [ownerReference];
if gqlRoute.spec.rules.length() > 0 {
http:Response deployGqlRouteResult = check deployGqlRoute(gqlRoute, namespace);
if deployGqlRouteResult.statusCode == http:STATUS_CREATED {
log:printDebug("Deployed GqlRoute Successfully" + gqlRoute.toString());
} else if deployGqlRouteResult.statusCode == http:STATUS_CONFLICT {
log:printDebug("GqlRoute already exists" + gqlRoute.toString());
model:GQLRoute gqlRouteFromK8s = check getGqlRoute(gqlRoute.metadata.name, namespace);
gqlRoute.metadata.resourceVersion = gqlRouteFromK8s.metadata.resourceVersion;
http:Response gqlRouteCR = check updateGqlRoute(gqlRoute, namespace);
if gqlRouteCR.statusCode != http:STATUS_OK {
json responsePayLoad = check gqlRouteCR.getJsonPayload();
model:Status statusResponse = check responsePayLoad.cloneWithType(model:Status);
check self.handleK8sTimeout(statusResponse);
}
} else {
json responsePayLoad = check deployGqlRouteResult.getJsonPayload();
model:Status statusResponse = check responsePayLoad.cloneWithType(model:Status);
check self.handleK8sTimeout(statusResponse);
}
} else {
json responsePayLoad = check deployHttpRouteResult.getJsonPayload();
model:Status statusResponse = check responsePayLoad.cloneWithType(model:Status);
check self.handleK8sTimeout(statusResponse);
}
}
}

}

public isolated function createHttpRoutesOrder(model:Httproute[] httproutes) returns model:Httproute[]|commons:APKError {
public isolated function createHttpRoutesOrder(model:HTTPRoute[] httproutes) returns model:HTTPRoute[]|commons:APKError {
do {
foreach model:Httproute route in httproutes {
foreach model:HTTPRoute route in httproutes {
model:HTTPRouteRule[] routeRules = route.spec.rules;
model:HTTPRouteRule[] sortedRouteRules = from var routeRule in routeRules
order by (<model:HTTPPathMatch>((<model:HTTPRouteMatch[]>routeRule.matches)[0]).path).value descending
Expand All @@ -395,6 +428,22 @@ public class DeployerClient {
}
}

public isolated function createGqlRoutesOrder(model:GQLRoute[] gqlRoutes) returns model:GQLRoute[]|commons:APKError {
do {
foreach model:GQLRoute route in gqlRoutes {
model:GQLRouteRule[] routeRules = route.spec.rules;
model:GQLRouteRule[] sortedRouteRules = from var routeRule in routeRules
order by <string>((<model:GQLRouteMatch[]>routeRule.matches)[0]).path descending
select routeRule;
route.spec.rules = sortedRouteRules;
}
return gqlRoutes;
} on fail var e {
log:printError("Error occured while sorting gqlRoutes", e);
return e909022("Error occured while sorting gqlRoutes", e);
}
}

private isolated function deployAuthenticationCRs(model:APIArtifact apiArtifact, model:OwnerReference ownerReference) returns error? {
string[] keys = apiArtifact.authenticationMap.keys();
log:printDebug("Inside Deploy Authentication CRs" + keys.toString());
Expand Down
37 changes: 31 additions & 6 deletions runtime/config-deployer-service/ballerina/K8sClient.bal
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,26 @@ isolated function updateAuthenticationCR(model:Authentication authentication, st
return k8sApiServerEp->put(endpoint, authentication, targetType = http:Response);
}

isolated function getHttpRoute(string name, string namespace) returns model:Httproute|http:ClientError {
isolated function getHttpRoute(string name, string namespace) returns model:HTTPRoute|http:ClientError {
string endpoint = "/apis/gateway.networking.k8s.io/v1beta1/namespaces/" + namespace + "/httproutes/" + name;
return k8sApiServerEp->get(endpoint, targetType = model:Httproute);
return k8sApiServerEp->get(endpoint, targetType = model:HTTPRoute);
}

isolated function deleteHttpRoute(string name, string namespace) returns http:Response|http:ClientError {
string endpoint = "/apis/gateway.networking.k8s.io/v1beta1/namespaces/" + namespace + "/httproutes/" + name;
return k8sApiServerEp->delete(endpoint, targetType = http:Response);
}

isolated function getGqlRoute(string name, string namespace) returns model:GQLRoute|http:ClientError {
string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/gqlroutes/" + name;
return k8sApiServerEp->get(endpoint, targetType = model:GQLRoute);
}

isolated function deleteGqlRoute(string name, string namespace) returns http:Response|http:ClientError {
string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/gqlroutes/" + name;
return k8sApiServerEp->delete(endpoint, targetType = http:Response);
}

isolated function getConfigMap(string name, string namespace) returns model:ConfigMap|http:ClientError {
string endpoint = "/api/v1/namespaces/" + namespace + "/configmaps/" + name;
return k8sApiServerEp->get(endpoint, targetType = model:ConfigMap);
Expand Down Expand Up @@ -120,16 +130,26 @@ isolated function updateConfigMap(model:ConfigMap configMap, string namespace) r
return k8sApiServerEp->put(endpoint, configMap, targetType = http:Response);
}

isolated function deployHttpRoute(model:Httproute httproute, string namespace) returns http:Response|http:ClientError {
isolated function deployHttpRoute(model:HTTPRoute httproute, string namespace) returns http:Response|http:ClientError {
string endpoint = "/apis/gateway.networking.k8s.io/v1beta1/namespaces/" + namespace + "/httproutes";
return k8sApiServerEp->post(endpoint, httproute, targetType = http:Response);
}

isolated function updateHttpRoute(model:Httproute httproute, string namespace) returns http:Response|http:ClientError {
isolated function updateHttpRoute(model:HTTPRoute httproute, string namespace) returns http:Response|http:ClientError {
string endpoint = "/apis/gateway.networking.k8s.io/v1beta1/namespaces/" + namespace + "/httproutes/" + httproute.metadata.name;
return k8sApiServerEp->put(endpoint, httproute, targetType = http:Response);
}

isolated function deployGqlRoute(model:GQLRoute gqlroute, string namespace) returns http:Response|http:ClientError {
string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/gqlroutes";
return k8sApiServerEp->post(endpoint, gqlroute, targetType = http:Response);
}

isolated function updateGqlRoute(model:GQLRoute gqlroute, string namespace) returns http:Response|http:ClientError {
string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/gqlroutes/" + gqlroute.metadata.name;
return k8sApiServerEp->put(endpoint, gqlroute, targetType = http:Response);
}

public isolated function getK8sAPIByNameAndNamespace(string name, string namespace) returns model:API?|commons:APKError {
string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/apis/" + name;
do {
Expand Down Expand Up @@ -215,9 +235,14 @@ isolated function getBackendServicesForAPI(string apiName, string apiVersion, st
return k8sApiServerEp->get(endpoint, targetType = model:ServiceList);
}

public isolated function getHttproutesForAPIS(string apiName, string apiVersion, string namespace, string organization) returns model:HttprouteList|http:ClientError|error {
public isolated function getHttproutesForAPIS(string apiName, string apiVersion, string namespace, string organization) returns model:HTTPRouteList|http:ClientError|error {
string endpoint = "/apis/gateway.networking.k8s.io/v1beta1/namespaces/" + namespace + "/httproutes/?labelSelector=" + check generateUrlEncodedLabelSelector(apiName, apiVersion, organization);
return k8sApiServerEp->get(endpoint, targetType = model:HttprouteList);
return k8sApiServerEp->get(endpoint, targetType = model:HTTPRouteList);
}

public isolated function getGqlRoutesForAPIs(string apiName, string apiVersion, string namespace, string organization) returns model:GQLRouteList|http:ClientError|error {
string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/gqlroutes/?labelSelector=" + check generateUrlEncodedLabelSelector(apiName, apiVersion, organization);
return k8sApiServerEp->get(endpoint, targetType = model:GQLRouteList);
}

isolated function deployRateLimitPolicyCR(model:RateLimitPolicy rateLimitPolicy, string namespace) returns http:Response|http:ClientError {
Expand Down
Loading

0 comments on commit 7a72127

Please sign in to comment.