From 082db963852e1f70f1ff1d041b043ce5d94b016d Mon Sep 17 00:00:00 2001 From: sgayangi Date: Wed, 31 Jan 2024 12:03:53 +0530 Subject: [PATCH] Add GraphQL API CR generation to config deployer --- .../enforcer/graphql/GraphQLPayloadUtils.java | 2 +- .../ballerina/APIClient.bal | 280 +++++++++++++----- .../ballerina/ConfigGenreatorClient.bal | 15 +- .../ballerina/DeployerClient.bal | 111 +++++-- .../ballerina/K8sClient.bal | 37 ++- .../ballerina/constants.bal | 9 +- .../ballerina/modules/model/API.bal | 2 +- .../ballerina/modules/model/APIArtifact.bal | 6 +- .../ballerina/modules/model/GraphQLRoute.bal | 57 ++++ .../ballerina/modules/model/HttpRoute.bal | 13 +- .../modules/org.wso2.apk.config.model/API.bal | 8 +- .../org.wso2.apk.config.model/URITemplate.bal | 44 +-- .../ballerina/tests/APIClientTest.bal | 39 +-- .../ballerina/tests/DeployerClientTest.bal | 10 +- .../ballerina/types.bal | 2 +- .../org/wso2/apk/config/APIConstants.java | 21 +- .../apk/config/DefinitionParserFactory.java | 14 +- .../java/org/wso2/apk/config/api/Info.java | 8 +- .../config/definitions/AsyncApiParser.java | 2 +- .../apk/config/definitions/OAS3Parser.java | 5 +- .../java/org/wso2/apk/config/model/API.java | 2 - .../wso2/apk/config/model/SwaggerData.java | 4 +- .../wso2/apk/config/model/URITemplate.java | 28 +- 23 files changed, 498 insertions(+), 221 deletions(-) create mode 100644 runtime/config-deployer-service/ballerina/modules/model/GraphQLRoute.bal diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/graphql/GraphQLPayloadUtils.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/graphql/GraphQLPayloadUtils.java index 61c8919357..2bd2147f17 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/graphql/GraphQLPayloadUtils.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/graphql/GraphQLPayloadUtils.java @@ -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 diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index 6ed202950f..05cbc9545f 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -45,7 +45,8 @@ public class APIClient { APKConf apkConf = { name: api.getName(), basePath: api.getBasePath().length() > 0 ? api.getBasePath() : encodedString, - version: api.getVersion() + version: api.getVersion(), + 'type: api.getType() == "" ? API_TYPE_REST : api.getType() }; string endpoint = api.getEndpoint(); if endpoint.length() > 0 { @@ -57,7 +58,7 @@ public class APIClient { if uriTemplates is runtimeModels:URITemplate[] { foreach runtimeModels:URITemplate uriTemplate in uriTemplates { APKOperations operation = { - verb: uriTemplate.getHTTPVerb(), + verb: uriTemplate.getVerb(), target: uriTemplate.getUriTemplate(), secured: uriTemplate.isAuthEnabled(), scopes: check uriTemplate.getScopes() @@ -75,7 +76,6 @@ public class APIClient { public isolated function generateK8sArtifacts(APKConf apkConf, string? definition, commons:Organization organization) returns model:APIArtifact|commons:APKError { do { - string uniqueId = self.getUniqueIdForAPI(apkConf.name, apkConf.version, organization); if apkConf.id is string { uniqueId = apkConf.id; @@ -97,7 +97,7 @@ public class APIClient { map createdEndpoints = {}; EndpointConfigurations? endpointConfigurations = apkConf.endpointConfigurations; if endpointConfigurations is EndpointConfigurations { - createdEndpoints = check self.createAndAddBackendServics(apiArtifact, apkConf, endpointConfigurations, (), (), organization); + createdEndpoints = check self.createAndAddBackendServices(apiArtifact, apkConf, endpointConfigurations, (), (), organization); } AuthenticationRequest[]? authentication = apkConf.authentication; if authentication is AuthenticationRequest[] { @@ -114,14 +114,14 @@ public class APIClient { resourceEndpointIdMap[SANDBOX_TYPE] = { name: "", serviceEntry: false, - url: self.construcURlFromService(sandboxEndpointConfig.endpoint) + url: self.constructURlFromService(sandboxEndpointConfig.endpoint) }; } if productionEndpointConfig is EndpointConfiguration { resourceEndpointIdMap[PRODUCTION_TYPE] = { name: "", serviceEntry: false, - url: self.construcURlFromService(productionEndpointConfig.endpoint) + url: self.constructURlFromService(productionEndpointConfig.endpoint) }; } _ = check self.populateAuthenticationMap(apiArtifact, apkConf, authentication, resourceEndpointIdMap, organization); @@ -132,16 +132,16 @@ public class APIClient { } } - _ = check self.setHttpRoute(apiArtifact, apkConf, createdEndpoints.hasKey(PRODUCTION_TYPE) ? createdEndpoints.get(PRODUCTION_TYPE) : (), uniqueId, PRODUCTION_TYPE, organization); - _ = check self.setHttpRoute(apiArtifact, apkConf, createdEndpoints.hasKey(SANDBOX_TYPE) ? createdEndpoints.get(SANDBOX_TYPE) : (), uniqueId, SANDBOX_TYPE, organization); - - json generatedSwagger = check self.retrieveGeneratedSwaggerDefinition(apkConf, definition); + _ = check self.setRoute(apiArtifact, apkConf, createdEndpoints.hasKey(PRODUCTION_TYPE) ? createdEndpoints.get(PRODUCTION_TYPE) : (), uniqueId, PRODUCTION_TYPE, organization); + _ = check self.setRoute(apiArtifact, apkConf, createdEndpoints.hasKey(SANDBOX_TYPE) ? createdEndpoints.get(SANDBOX_TYPE) : (), uniqueId, SANDBOX_TYPE, organization); + string|json generatedSwagger = check self.retrieveGeneratedSwaggerDefinition(apkConf, definition); check self.retrieveGeneratedConfigmapForDefinition(apiArtifact, apkConf, generatedSwagger, uniqueId, organization); self.generateAndSetAPICRArtifact(apiArtifact, apkConf, organization); _ = check self.generateAndSetPolicyCRArtifact(apiArtifact, apkConf, organization); apiArtifact.organization = organization.name; return apiArtifact; - } on fail var e { + } + on fail var e { if e is commons:APKError { return e; } @@ -213,7 +213,7 @@ public class APIClient { return (); } - private isolated function createAndAddBackendServics(model:APIArtifact apiArtifact, APKConf apkConf, EndpointConfigurations endpointConfigurations, APKOperations? apiOperation, string? endpointType, commons:Organization organization) returns map|commons:APKError|error { + private isolated function createAndAddBackendServices(model:APIArtifact apiArtifact, APKConf apkConf, EndpointConfigurations endpointConfigurations, APKOperations? apiOperation, string? endpointType, commons:Organization organization) returns map|commons:APKError|error { map endpointIdMap = {}; EndpointConfiguration? productionEndpointConfig = endpointConfigurations.production; EndpointConfiguration? sandboxEndpointConfig = endpointConfigurations.sandbox; @@ -227,7 +227,7 @@ public class APIClient { endpointIdMap[SANDBOX_TYPE] = { name: backendService.metadata.name, serviceEntry: false, - url: self.construcURlFromService(sandboxEndpointConfig.endpoint) + url: self.constructURlFromService(sandboxEndpointConfig.endpoint) }; } } @@ -241,18 +241,18 @@ public class APIClient { endpointIdMap[PRODUCTION_TYPE] = { name: backendService.metadata.name, serviceEntry: false, - url: self.construcURlFromService(productionEndpointConfig.endpoint) + url: self.constructURlFromService(productionEndpointConfig.endpoint) }; } } return endpointIdMap; } - isolated function construcURlFromService(string|K8sService endpoint) returns string { + isolated function constructURlFromService(string|K8sService endpoint) returns string { if endpoint is string { return endpoint; } else { - return self.construcURlFromK8sService(endpoint); + return self.constructURlFromK8sService(endpoint); } } @@ -277,7 +277,7 @@ public class APIClient { return fullBasePath; } - private isolated function construcURlFromK8sService(K8sService 'k8sService) returns string { + private isolated function constructURlFromK8sService(K8sService 'k8sService) returns string { return k8sService.protocol + "://" + string:'join(".", k8sService.name, k8sService.namespace, "svc.cluster.local") + ":" + k8sService.port.toString(); } @@ -285,10 +285,15 @@ public class APIClient { return backendSpec.protocol + "://" + backendSpec.services[0].host + backendSpec.services[0].port.toString(); } - private isolated function retrieveGeneratedConfigmapForDefinition(model:APIArtifact apiArtifact, APKConf apkConf, json generatedSwaggerDefinition, string uniqueId, commons:Organization organization) returns error? { - byte[]|javaio:IOException compressedContent = check commons:GzipUtil_compressGzipFile(generatedSwaggerDefinition.toJsonString().toBytes()); + private isolated function retrieveGeneratedConfigmapForDefinition(model:APIArtifact apiArtifact, APKConf apkConf, string|json generatedSwaggerDefinition, string uniqueId, commons:Organization organization) returns error? { + byte[]|javaio:IOException compressedContent = []; + if apkConf.'type == API_TYPE_REST { + compressedContent = check commons:GzipUtil_compressGzipFile(generatedSwaggerDefinition.toJsonString().toBytes()); + } + else if generatedSwaggerDefinition is string { + compressedContent = check commons:GzipUtil_compressGzipFile(generatedSwaggerDefinition.toBytes()); + } if compressedContent is byte[] { - byte[] base64EncodedContent = check commons:EncoderUtil_encodeBase64(compressedContent); model:ConfigMap configMap = { metadata: { @@ -404,7 +409,7 @@ public class APIClient { }, spec: { apiName: apkConf.name, - apiType: apkConf.'type, + apiType: apkConf.'type == "GRAPHQL" ? "GraphQL" : apkConf.'type, apiVersion: apkConf.'version, basePath: self.returnFullBasePath(apkConf.basePath, apkConf.'version), isDefaultVersion: apkConf.defaultVersion, @@ -417,23 +422,38 @@ public class APIClient { if definition is model:ConfigMap { k8sAPI.spec.definitionFileRef = definition.metadata.name; } - string[] productionHttpRoutes = []; - foreach model:Httproute httpRoute in apiArtifact.productionRoute { - if httpRoute.spec.rules.length() > 0 { - productionHttpRoutes.push(httpRoute.metadata.name); + string[] productionRoutes = []; + string[] sandboxRoutes = []; + + if apkConf.'type == API_TYPE_GRAPHQL { + foreach model:GQLRoute gqlRoute in apiArtifact.productionGqlRoutes { + if gqlRoute.spec.rules.length() > 0 { + productionRoutes.push(gqlRoute.metadata.name); + } } - } - string[] sandBoxHttpRoutes = []; - foreach model:Httproute httpRoute in apiArtifact.sandboxRoute { - if httpRoute.spec.rules.length() > 0 { - sandBoxHttpRoutes.push(httpRoute.metadata.name); + foreach model:GQLRoute gqlRoute in apiArtifact.sandboxGqlRoutes { + if gqlRoute.spec.rules.length() > 0 { + sandboxRoutes.push(gqlRoute.metadata.name); + } + } + } else { + foreach model:HTTPRoute httpRoute in apiArtifact.productionHttpRoutes { + if httpRoute.spec.rules.length() > 0 { + productionRoutes.push(httpRoute.metadata.name); + } + } + foreach model:HTTPRoute httpRoute in apiArtifact.sandboxHttpRoutes { + if httpRoute.spec.rules.length() > 0 { + sandboxRoutes.push(httpRoute.metadata.name); + } } } - if productionHttpRoutes.length() > 0 { - k8sAPI.spec.production = [{httpRouteRefs: productionHttpRoutes}]; + + if productionRoutes.length() > 0 { + k8sAPI.spec.production = [{routeRefs: productionRoutes}]; } - if sandBoxHttpRoutes.length() > 0 { - k8sAPI.spec.sandbox = [{httpRouteRefs: sandBoxHttpRoutes}]; + if sandboxRoutes.length() > 0 { + k8sAPI.spec.sandbox = [{routeRefs: sandboxRoutes}]; } if apkConf.id != () { k8sAPI.metadata["annotations"] = {[API_UUID_ANNOTATION] : apkConf.id}; @@ -459,7 +479,7 @@ public class APIClient { private isolated function retrieveAuthenticationRefName(APKConf apkConf, string 'type, commons:Organization organization) returns string { return self.getUniqueIdForAPI(apkConf.name, apkConf.'version, organization) + "-" + 'type + "-authentication"; } - private isolated function setHttpRoute(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string uniqueId, string endpointType, commons:Organization organization) returns commons:APKError|error? { + private isolated function setRoute(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string uniqueId, string endpointType, commons:Organization organization) returns commons:APKError|error? { APKOperations[] apiOperations = apkConf.operations ?: []; APKOperations[][] operationsArray = []; int row = 0; @@ -476,29 +496,55 @@ public class APIClient { foreach APKOperations[] item in operationsArray { APKConf clonedAPKConf = apkConf.clone(); clonedAPKConf.operations = item.clone(); - _ = check self.putHttpRouteForPartition(apiArtifact, clonedAPKConf, endpoint, uniqueId, endpointType, organization, count); + _ = check self.putRouteForPartition(apiArtifact, clonedAPKConf, endpoint, uniqueId, endpointType, organization, count); count = count + 1; } } - private isolated function putHttpRouteForPartition(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string uniqueId, string endpointType, commons:Organization organization, int count) returns commons:APKError|error? { - model:Httproute httpRoute = { - metadata: + private isolated function putRouteForPartition(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string uniqueId, string endpointType, commons:Organization organization, int count) returns commons:APKError|error? { + + if apkConf.'type == API_TYPE_GRAPHQL { + model:GQLRoute gqlRoute = { + metadata: { - name: uniqueId + "-" + endpointType + "-httproute-" + count.toString(), - labels: self.getLabels(apkConf, organization) - }, - spec: { - parentRefs: self.generateAndRetrieveParentRefs(apkConf, uniqueId), - rules: check self.generateHttpRouteRules(apiArtifact, apkConf, endpoint, endpointType, organization), - hostnames: self.getHostNames(apkConf, uniqueId, endpointType, organization) + name: uniqueId + "-" + endpointType + "-gqlroute-" + count.toString(), + labels: self.getLabels(apkConf, organization) + }, + spec: { + parentRefs: self.generateAndRetrieveParentRefs(apkConf, uniqueId), + rules: check self.generateGQLRouteRules(apiArtifact, apkConf, endpoint, endpointType, organization), + hostnames: self.getHostNames(apkConf, uniqueId, endpointType, organization) + } + }; + if endpoint is model:Endpoint { + gqlRoute.spec.backendRefs = self.retrieveGeneratedBackend(apkConf, endpoint, endpointType); } - }; - if httpRoute.spec.rules.length() > 0 { - if endpointType == PRODUCTION_TYPE { - apiArtifact.productionRoute.push(httpRoute); - } else { - apiArtifact.sandboxRoute.push(httpRoute); + if gqlRoute.spec.rules.length() > 0 { + if endpointType == PRODUCTION_TYPE { + apiArtifact.productionGqlRoutes.push(gqlRoute); + } else { + apiArtifact.sandboxGqlRoutes.push(gqlRoute); + } + } + } else { + model:HTTPRoute httpRoute = { + metadata: + { + name: uniqueId + "-" + endpointType + "-httproute-" + count.toString(), + labels: self.getLabels(apkConf, organization) + }, + spec: { + parentRefs: self.generateAndRetrieveParentRefs(apkConf, uniqueId), + rules: check self.generateHTTPRouteRules(apiArtifact, apkConf, endpoint, endpointType, organization), + hostnames: self.getHostNames(apkConf, uniqueId, endpointType, organization) + } + }; + if httpRoute.spec.rules.length() > 0 { + if endpointType == PRODUCTION_TYPE { + apiArtifact.productionHttpRoutes.push(httpRoute); + } else { + apiArtifact.sandboxHttpRoutes.push(httpRoute); + } } } @@ -514,17 +560,17 @@ public class APIClient { return parentRefs; } - private isolated function generateHttpRouteRules(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string endpointType, commons:Organization organization) returns model:HTTPRouteRule[]|commons:APKError|error { + private isolated function generateHTTPRouteRules(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string endpointType, commons:Organization organization) returns model:HTTPRouteRule[]|commons:APKError|error { model:HTTPRouteRule[] httpRouteRules = []; APKOperations[]? operations = apkConf.operations; if operations is APKOperations[] { foreach APKOperations operation in operations { - model:HTTPRouteRule|() httpRouteRule = check self.generateHttpRouteRule(apiArtifact, apkConf, endpoint, operation, endpointType, organization); - if httpRouteRule is model:HTTPRouteRule { - model:HTTPRouteFilter[]? filters = httpRouteRule.filters; + model:HTTPRouteRule|model:GQLRouteRule|() routeRule = check self.generateRouteRule(apiArtifact, apkConf, endpoint, operation, endpointType, organization); + if routeRule is model:HTTPRouteRule { + model:HTTPRouteFilter[]? filters = routeRule.filters; if filters is () { filters = []; - httpRouteRule.filters = filters; + routeRule.filters = filters; } string disableAuthenticationRefName = self.retrieveDisableAuthenticationRefName(apkConf, endpointType, organization); if !(operation.secured ?: true) { @@ -566,13 +612,64 @@ public class APIClient { (filters).push(apiPolicyFilter); } } - httpRouteRules.push(httpRouteRule); + httpRouteRules.push(routeRule); } } } return httpRouteRules; } + private isolated function generateGQLRouteRules(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string endpointType, commons:Organization organization) returns model:GQLRouteRule[]|commons:APKError|error { + model:GQLRouteRule[] gqlRouteRules = []; + APKOperations[]? operations = apkConf.operations; + if operations is APKOperations[] { + foreach APKOperations operation in operations { + model:HTTPRouteRule|model:GQLRouteRule|() routeRule = check self.generateRouteRule(apiArtifact, apkConf, endpoint, operation, endpointType, organization); + if routeRule is model:GQLRouteRule { + model:GQLRouteFilter[]? filters = routeRule.filters; + if filters is () { + filters = []; + routeRule.filters = filters; + } + string disableAuthenticationRefName = self.retrieveDisableAuthenticationRefName(apkConf, endpointType, organization); + if !(operation.secured ?: true) { + if !apiArtifact.authenticationMap.hasKey(disableAuthenticationRefName) { + model:Authentication generateDisableAuthenticationCR = self.generateDisableAuthenticationCR(apiArtifact, apkConf, endpointType, organization); + apiArtifact.authenticationMap[disableAuthenticationRefName] = generateDisableAuthenticationCR; + } + model:GQLRouteFilter disableAuthenticationFilter = {extensionRef: {group: "dp.wso2.com", kind: "Authentication", name: disableAuthenticationRefName}}; + (filters).push(disableAuthenticationFilter); + } + string[]? scopes = operation.scopes; + if scopes is string[] { + int count = 1; + foreach string scope in scopes { + model:Scope scopeCr; + if apiArtifact.scopes.hasKey(scope) { + scopeCr = apiArtifact.scopes.get(scope); + } else { + scopeCr = self.generateScopeCR(apiArtifact, apkConf, organization, scope, count); + count = count + 1; + } + model:GQLRouteFilter scopeFilter = {extensionRef: {group: "dp.wso2.com", kind: scopeCr.kind, name: scopeCr.metadata.name}}; + (filters).push(scopeFilter); + } + } + if operation.operationPolicies != () { + model:APIPolicy? apiPolicyCR = check self.generateAPIPolicyAndBackendCR(apiArtifact, apkConf, operation, operation.operationPolicies, organization, apiArtifact.uniqueId); + if apiPolicyCR != () { + apiArtifact.apiPolicies[apiPolicyCR.metadata.name] = apiPolicyCR; + model:HTTPRouteFilter apiPolicyFilter = {'type: "ExtensionRef", extensionRef: {group: "dp.wso2.com", kind: "APIPolicy", name: apiPolicyCR.metadata.name}}; + (filters).push(apiPolicyFilter); + } + } + gqlRouteRules.push(routeRule); + } + } + } + return gqlRouteRules; + } + private isolated function generateAPIPolicyAndBackendCR(model:APIArtifact apiArtifact, APKConf apkConf, APKOperations? operations, APIOperationPolicies? policies, commons:Organization organization, string targetRefName) returns model:APIPolicy?|error { model:APIPolicyData defaultSpecData = {}; APKOperationPolicy[]? request = policies?.request; @@ -643,13 +740,13 @@ public class APIClient { return authentication; } - private isolated function generateHttpRouteRule(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, APKOperations operation, string endpointType, commons:Organization organization) returns model:HTTPRouteRule|()|commons:APKError { + private isolated function generateRouteRule(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, APKOperations operation, string endpointType, commons:Organization organization) returns model:HTTPRouteRule|model:GQLRouteRule|()|commons:APKError { do { EndpointConfigurations? endpointConfig = operation.endpointConfigurations; model:Endpoint? endpointToUse = (); if endpointConfig is EndpointConfigurations { - // endpointConfig presense at Operation Level. - map operationalLevelBackend = check self.createAndAddBackendServics(apiArtifact, apkConf, endpointConfig, operation, endpointType, organization); + // endpointConfig presence at Operation Level. + map operationalLevelBackend = check self.createAndAddBackendServices(apiArtifact, apkConf, endpointConfig, operation, endpointType, organization); if operationalLevelBackend.hasKey(endpointType) { endpointToUse = operationalLevelBackend.get(endpointType); } @@ -659,8 +756,13 @@ public class APIClient { } } if endpointToUse != () { - model:HTTPRouteRule httpRouteRule = {matches: self.retrieveMatches(apkConf, operation, organization), backendRefs: self.retrieveGeneratedBackend(apkConf, endpointToUse, endpointType), filters: self.generateFilters(apiArtifact, apkConf, endpointToUse, operation, endpointType, organization)}; - return httpRouteRule; + if apkConf.'type == API_TYPE_GRAPHQL { + model:GQLRouteRule gqlRouteRule = {matches: self.retrieveGQLMatches(apkConf, operation, organization)}; + return gqlRouteRule; + } 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; + } } else { return (); } @@ -673,10 +775,8 @@ 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); - if (generatedPath != operation.target) { - model:HTTPRouteFilter replacePathFilter = {'type: "URLRewrite", urlRewrite: {path: {'type: "ReplaceFullPath", replaceFullPath: generatedPath}}}; - routeFilters.push(replacePathFilter); - } + model:HTTPRouteFilter replacePathFilter = {'type: "URLRewrite", urlRewrite: {path: {'type: "ReplaceFullPath", replaceFullPath: generatedPath}}}; + routeFilters.push(replacePathFilter); APIOperationPolicies? operationPoliciesToUse = (); if (apkConf.apiPolicies is APIOperationPolicies) { operationPoliciesToUse = apkConf.apiPolicies; @@ -788,24 +888,34 @@ public class APIClient { return [httpBackend]; } - private isolated function retrieveMatches(APKConf apkConf, APKOperations apiOperation, commons:Organization organization) returns model:HTTPRouteMatch[] { + private isolated function retrieveHTTPMatches(APKConf apkConf, APKOperations apiOperation, commons:Organization organization) returns model:HTTPRouteMatch[] { model:HTTPRouteMatch[] httpRouteMatch = []; model:HTTPRouteMatch httpRoute = self.retrieveHttpRouteMatch(apkConf, apiOperation, organization); - httpRouteMatch.push(httpRoute); return httpRouteMatch; } - private isolated function retrieveHttpRouteMatch(APKConf apkConf, APKOperations apiOperation, commons:Organization organization) returns model:HTTPRouteMatch { + private isolated function retrieveGQLMatches(APKConf apkConf, APKOperations apiOperation, commons:Organization organization) returns model:GQLRouteMatch[] { + model:GQLRouteMatch[] gqlRouteMatch = []; + model:GQLRouteMatch gqlRoute = self.retrieveGQLRouteMatch(apiOperation); + gqlRouteMatch.push(gqlRoute); + return gqlRouteMatch; + } + private isolated function retrieveHttpRouteMatch(APKConf apkConf, APKOperations apiOperation, commons:Organization organization) returns model:HTTPRouteMatch { return {method: apiOperation.verb, path: {'type: "RegularExpression", value: self.retrievePathPrefix(apkConf.basePath, apkConf.'version, apiOperation.target ?: "/*", organization)}}; } - isolated function retrieveGeneratedSwaggerDefinition(APKConf apkConf, string? definition) returns json|commons:APKError|error { + private isolated function retrieveGQLRouteMatch(APKOperations apiOperation) returns model:GQLRouteMatch { + return {'type: apiOperation.verb, path: apiOperation.target}; + } + + isolated function retrieveGeneratedSwaggerDefinition(APKConf apkConf, string? definition) returns string|json|commons:APKError|error { runtimeModels:API api1 = runtimeModels:newAPI1(); api1.setName(apkConf.name); api1.setType(apkConf.'type); api1.setVersion(apkConf.'version); + runtimeModels:URITemplate[] uritemplatesSet = []; if apkConf.operations is APKOperations[] { foreach APKOperations apiOperation in apkConf.operations { @@ -813,7 +923,7 @@ public class APIClient { uriTemplate.setUriTemplate(apiOperation.target); string? verb = apiOperation.verb; if verb is string { - uriTemplate.setHTTPVerb(verb.toUpperAscii()); + uriTemplate.setVerb(verb.toUpperAscii()); } boolean? secured = apiOperation.secured; if secured is boolean { @@ -832,11 +942,16 @@ public class APIClient { } check api1.setUriTemplates(uritemplatesSet); string?|runtimeapi:APIManagementException retrievedDefinition = ""; + if apkConf.'type == API_TYPE_GRAPHQL && definition is string { + api1.setGraphQLSchema(definition); + return definition; + } if definition is string && definition.toString().trim().length() > 0 { retrievedDefinition = runtimeUtil:RuntimeAPICommonUtil_generateDefinition2(api1, definition); } else { retrievedDefinition = runtimeUtil:RuntimeAPICommonUtil_generateDefinition(api1); } + if retrievedDefinition is string && retrievedDefinition.toString().trim().length() > 0 { json|error jsonString = value:fromJsonString(retrievedDefinition); if jsonString is json { @@ -852,12 +967,12 @@ public class APIClient { } } - isolated function gethost(string|K8sService endpoint) returns string { + isolated function getHost(string|K8sService endpoint) returns string { string url; if endpoint is string { url = endpoint; } else { - url = self.construcURlFromK8sService(endpoint); + url = self.constructURlFromK8sService(endpoint); } string host = ""; if url.startsWith("https://") { @@ -893,7 +1008,7 @@ public class APIClient { if endpoint is string { url = endpoint; } else { - url = self.construcURlFromK8sService(endpoint); + url = self.constructURlFromK8sService(endpoint); } string hostPort = ""; string protocol = ""; @@ -946,7 +1061,7 @@ public class APIClient { spec: { services: [ { - host: self.gethost(endpointConfig.endpoint), + host: self.getHost(endpointConfig.endpoint), port: check self.getPort(endpointConfig.endpoint) } ], @@ -1086,6 +1201,7 @@ public class APIClient { } return policyReferences; } + private isolated function retrieveBackendJWTPolicy(APKConf apkConf, model:APIArtifact apiArtifact, BackendJWTPolicy backendJWTPolicy, commons:Organization organization) returns model:BackendJWT { BackendJWTPolicy_parameters parameters = backendJWTPolicy.parameters ?: {}; model:BackendJWT backendJwt = { @@ -1120,6 +1236,7 @@ public class APIClient { } return backendJwt; } + private isolated function retrieveCORSPolicyDetails(model:APIArtifact apiArtifact, APKConf apkConf, CORSConfiguration corsConfiguration, commons:Organization organization) returns model:CORSPolicy? { model:CORSPolicy corsPolicy = {}; if corsConfiguration.accessControlAllowCredentials is boolean { @@ -1332,7 +1449,7 @@ public class APIClient { } public isolated function getInterceptorBackendUid(APKConf apkConf, string endpointType, commons:Organization organization, string|K8sService backend) returns string { - string concatanatedString = string:'join("-", organization.name, apkConf.name, 'apkConf.'version, endpointType, self.construcURlFromService(backend)); + string concatanatedString = string:'join("-", organization.name, apkConf.name, 'apkConf.'version, endpointType, self.constructURlFromService(backend)); byte[] hashedValue = crypto:hashSha1(concatanatedString.toBytes()); concatanatedString = hashedValue.toBase16(); return "backend-" + concatanatedString + "-interceptor"; @@ -1344,6 +1461,7 @@ public class APIClient { concatanatedString = hashedValue.toBase16(); return string:'join("-", concatanatedString, "backend-jwt-policy"); } + public isolated function getBackendServiceUid(APKConf apkConf, APKOperations? apiOperation, string endpointType, commons:Organization organization) returns string { string concatanatedString = uuid:createType1AsString(); if (apiOperation is APKOperations) { @@ -1398,6 +1516,7 @@ public class APIClient { return "api-" + targetRef; } } + private isolated function validateAndRetrieveAPKConfiguration(json apkconfJson) returns APKConf|commons:APKError? { do { runtimeapi:APKConfValidationResponse validationResponse = check apkConfValidator.validate(apkconfJson.toJsonString()); @@ -1422,6 +1541,7 @@ public class APIClient { return e909022("APK configuration is not valid", e); } } + private isolated function validateEndpointConfigurations(APKConf apkConf, map errors) { EndpointConfigurations? endpointConfigurations = apkConf.endpointConfigurations; boolean productionEndpointAvailable = false; @@ -1469,12 +1589,14 @@ public class APIClient { } else if definitionFile.fileName.endsWith(".json") { apiDefinition = definitionFileContent; } + } else if apiType == API_TYPE_GRAPHQL { + apiDefinition = definitionFileContent; } if apkConf is () { return e909022("apkConfiguration is not provided", ()); } - APIClient apiclent = new (); - return check apiclent.generateK8sArtifacts(apkConf, apiDefinition, organization); + APIClient apiclient = new (); + return check apiclient.generateK8sArtifacts(apkConf, apiDefinition, organization); } on fail var e { if e is commons:APKError { return e; diff --git a/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal b/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal index b5033d528c..35a4c92f47 100644 --- a/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal +++ b/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal @@ -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()); @@ -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"); } @@ -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); diff --git a/runtime/config-deployer-service/ballerina/DeployerClient.bal b/runtime/config-deployer-service/ballerina/DeployerClient.bal index b8d8732265..aa522f3a91 100644 --- a/runtime/config-deployer-service/ballerina/DeployerClient.bal +++ b/runtime/config-deployer-service/ballerina/DeployerClient.bal @@ -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, apiArtifact?.namespace, ownerReference); - check self.deployHttpRoutes(apiArtifact.sandboxRoute, apiArtifact?.namespace, ownerReference); + + check self.deployRoutes(apiArtifact.productionHttpRoutes, apiArtifact.productionGqlRoutes, apiArtifact?.namespace, ownerReference); + check self.deployRoutes(apiArtifact.sandboxHttpRoutes, apiArtifact.sandboxGqlRoutes, apiArtifact?.namespace, ownerReference); + return deployK8sAPICrResult; } on fail var e { http:Response|http:ClientError apiCRDeletionResponse = deleteAPICR(api.metadata.name, apiArtifact.namespace ?: ""); @@ -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, 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, 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, api.metadata?.namespace); if httprouteDeletionResponse is http:Response { if httprouteDeletionResponse.statusCode != http:STATUS_OK { @@ -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 (((routeRule.matches)[0]).path).value descending @@ -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 ((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()); diff --git a/runtime/config-deployer-service/ballerina/K8sClient.bal b/runtime/config-deployer-service/ballerina/K8sClient.bal index 60fdea4a55..5e7757206f 100644 --- a/runtime/config-deployer-service/ballerina/K8sClient.bal +++ b/runtime/config-deployer-service/ballerina/K8sClient.bal @@ -80,9 +80,9 @@ 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 { @@ -90,6 +90,16 @@ isolated function deleteHttpRoute(string name, string namespace) returns http:Re 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); @@ -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 { @@ -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 { diff --git a/runtime/config-deployer-service/ballerina/constants.bal b/runtime/config-deployer-service/ballerina/constants.bal index a746c657f6..66f523456b 100644 --- a/runtime/config-deployer-service/ballerina/constants.bal +++ b/runtime/config-deployer-service/ballerina/constants.bal @@ -10,6 +10,7 @@ public final string[] SSE_SUPPORTED_METHODS = ["subscribe"]; public final string[] WS_SUPPORTED_METHODS = ["subscribe", "publish"]; const string API_TYPE_REST = "REST"; +const string API_TYPE_GRAPHQL = "GRAPHQL"; const string API_TYPE_SOAP = "SOAP"; const string API_TYPE_SSE = "SSE"; const string API_TYPE_WS = "WS"; @@ -51,7 +52,7 @@ const string ENDPOINT_SECURITY_PASSWORD = "password"; const string ZIP_FILE_EXTENSTION = ".zip"; const string PROTOCOL_HTTP = "http"; const string PROTOCOL_HTTPS = "https"; -final string[]&readonly ALLOWED_API_TYPES = [API_TYPE_REST]; +final string[] & readonly ALLOWED_API_TYPES = [API_TYPE_REST, API_TYPE_GRAPHQL]; const string MEDIATION_POLICY_TYPE_REQUEST_HEADER_MODIFIER = "RequestHeaderModifier"; const string MEDIATION_POLICY_TYPE_RESPONSE_HEADER_MODIFIER = "ResponseHeaderModifier"; @@ -60,8 +61,8 @@ const string POLICY_TYPE_BACKEND_JWT = "BackendJwt"; const string MEDIATION_POLICY_NAME_ADD_HEADER = "addHeader"; const string MEDIATION_POLICY_NAME_REMOVE_HEADER = "removeHeader"; const string MEDIATION_POLICY_TYPE_URL_REWRITE = "URLRewrite"; -const string MEDIATION_POLICY_FLOW_REQUEST = "request"; -const string MEDIATION_POLICY_FLOW_RESPONSE = "response"; +const string MEDIATION_POLICY_FLOW_REQUEST = "request"; +const string MEDIATION_POLICY_FLOW_RESPONSE = "response"; const string API_NAME_HASH_LABEL = "api-name"; const string API_VERSION_HASH_LABEL = "api-version"; @@ -89,4 +90,4 @@ const string ORG_RESOLVER_NONE = "none"; const string APPLICATION_JSON_MEDIA_TYPE = "application/json"; const string APPLICATION_YAML_MEDIA_TYPE = "application/yaml"; const string ALL_MEDIA_TYPE = "*/*"; -const string AUTH_TYPE_JWT = "JWT"; \ No newline at end of file +const string AUTH_TYPE_JWT = "JWT"; diff --git a/runtime/config-deployer-service/ballerina/modules/model/API.bal b/runtime/config-deployer-service/ballerina/modules/model/API.bal index b0510e5403..25d7bd40c0 100644 --- a/runtime/config-deployer-service/ballerina/modules/model/API.bal +++ b/runtime/config-deployer-service/ballerina/modules/model/API.bal @@ -58,7 +58,7 @@ public type APIStatus record { }; public type EnvConfig record { - string[] httpRouteRefs; + string[] routeRefs; }; public type APIList record { diff --git a/runtime/config-deployer-service/ballerina/modules/model/APIArtifact.bal b/runtime/config-deployer-service/ballerina/modules/model/APIArtifact.bal index 01467b6c02..7f272176aa 100644 --- a/runtime/config-deployer-service/ballerina/modules/model/APIArtifact.bal +++ b/runtime/config-deployer-service/ballerina/modules/model/APIArtifact.bal @@ -2,8 +2,10 @@ public type APIArtifact record {| string name; string 'version; API api?; - Httproute[] productionRoute = []; - Httproute[] sandboxRoute = []; + HTTPRoute[] productionHttpRoutes = []; + HTTPRoute[] sandboxHttpRoutes = []; + GQLRoute[] productionGqlRoutes = []; + GQLRoute[] sandboxGqlRoutes = []; ConfigMap definition?; map endpointCertificates = {}; map certificateMap = {}; diff --git a/runtime/config-deployer-service/ballerina/modules/model/GraphQLRoute.bal b/runtime/config-deployer-service/ballerina/modules/model/GraphQLRoute.bal new file mode 100644 index 0000000000..e74c1075be --- /dev/null +++ b/runtime/config-deployer-service/ballerina/modules/model/GraphQLRoute.bal @@ -0,0 +1,57 @@ +// +// Copyright (c) 2024, WSO2 LLC. (GraphQL://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// GraphQL://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +public type GQLRoute record {| + string apiVersion = "dp.wso2.com/v1alpha2"; + string kind = "GQLRoute"; + Metadata metadata; + GQLRouteSpec spec; +|}; + +public type GQLRouteList record {| + string apiVersion = "dp.wso2.com/v1alpha2"; + string kind = "GQLRouteList"; + ListMeta metadata; + GQLRoute[] items; +|}; + +public type GQLRouteMatch record { + string 'type; //TODO: make enum + string path; +}; + +enum GQLType { + QUERY, + MUTATION +}; + +public type GQLRouteFilter record { + LocalObjectReference extensionRef?; +}; + +public type GQLRouteRule record { + GQLRouteMatch[] matches?; + GQLRouteFilter[] filters?; +}; + +public type GQLRouteSpec record { + *CommonRouteSpec; + string[] hostnames?; + GQLRouteRule[] rules = []; + BackendRef[] backendRefs?; +}; + diff --git a/runtime/config-deployer-service/ballerina/modules/model/HttpRoute.bal b/runtime/config-deployer-service/ballerina/modules/model/HttpRoute.bal index baa3f2d461..38ff5974c8 100644 --- a/runtime/config-deployer-service/ballerina/modules/model/HttpRoute.bal +++ b/runtime/config-deployer-service/ballerina/modules/model/HttpRoute.bal @@ -105,7 +105,7 @@ public type HTTPRouteFilter record { LocalObjectReference extensionRef?; }; -type BackendRef record { +public type BackendRef record { *BackendObjectReference; int weight?; }; @@ -124,10 +124,10 @@ public type HTTPRouteRule record { public type HTTPRouteSpec record { *CommonRouteSpec; string[] hostnames?; - HTTPRouteRule[] rules=[]; + HTTPRouteRule[] rules = []; }; -public type Httproute record {| +public type HTTPRoute record {| string apiVersion = "gateway.networking.k8s.io/v1beta1"; string kind = "HTTPRoute"; Metadata metadata; @@ -143,9 +143,10 @@ public type ParentReference record {| string port?; |}; -public type HttprouteList record {| + +public type HTTPRouteList record {| string apiVersion = "gateway.networking.k8s.io/v1beta1"; string kind = "HTTPRouteList"; ListMeta metadata; - Httproute[] items; -|}; \ No newline at end of file + HTTPRoute[] items; +|}; diff --git a/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/API.bal b/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/API.bal index 0728ee0eb6..5722b46076 100644 --- a/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/API.bal +++ b/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/API.bal @@ -105,7 +105,7 @@ public distinct class API { # The function that maps to the `getType` method of `org.wso2.apk.config.model.API`. # # + return - The `string` value returning from the Java mapping. - public function getType() returns string { + public isolated function getType() returns string { return java:toString(org_wso2_apk_config_model_API_getType(self.jObj)) ?: ""; } @@ -179,7 +179,7 @@ public distinct class API { # The function that maps to the `setGraphQLSchema` method of `org.wso2.apk.config.model.API`. # # + arg0 - The `string` value required to map with the Java method parameter. - public function setGraphQLSchema(string arg0) { + public isolated function setGraphQLSchema(string arg0) { org_wso2_apk_config_model_API_setGraphQLSchema(self.jObj, java:fromString(arg0)); } @@ -346,7 +346,7 @@ function org_wso2_apk_config_model_API_getSwaggerDefinition(handle receiver) ret paramTypes: [] } external; -function org_wso2_apk_config_model_API_getType(handle receiver) returns handle = @java:Method { +isolated function org_wso2_apk_config_model_API_getType(handle receiver) returns handle = @java:Method { name: "getType", 'class: "org.wso2.apk.config.model.API", paramTypes: [] @@ -406,7 +406,7 @@ function org_wso2_apk_config_model_API_setEnvironment(handle receiver, handle ar paramTypes: ["java.lang.String"] } external; -function org_wso2_apk_config_model_API_setGraphQLSchema(handle receiver, handle arg0) = @java:Method { +isolated function org_wso2_apk_config_model_API_setGraphQLSchema(handle receiver, handle arg0) = @java:Method { name: "setGraphQLSchema", 'class: "org.wso2.apk.config.model.API", paramTypes: ["java.lang.String"] diff --git a/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/URITemplate.bal b/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/URITemplate.bal index e545e79a32..b0eb243373 100644 --- a/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/URITemplate.bal +++ b/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/URITemplate.bal @@ -57,13 +57,6 @@ public distinct class URITemplate { return java:toString(org_wso2_apk_config_model_URITemplate_getEndpoint(self.jObj)) ?: ""; } - # The function that maps to the `getHTTPVerb` method of `org.wso2.apk.config.model.URITemplate`. - # - # + return - The `string` value returning from the Java mapping. - public isolated function getHTTPVerb() returns string { - return java:toString(org_wso2_apk_config_model_URITemplate_getHTTPVerb(self.jObj)) ?: ""; - } - # The function that maps to the `getId` method of `org.wso2.apk.config.model.URITemplate`. # # + return - The `int` value returning from the Java mapping. @@ -96,6 +89,13 @@ public distinct class URITemplate { return java:toString(org_wso2_apk_config_model_URITemplate_getUriTemplate(self.jObj)) ?: ""; } + # The function that maps to the `getVerb` method of `org.wso2.apk.config.model.URITemplate`. + # + # + return - The `string` value returning from the Java mapping. + public isolated function getVerb() returns string { + return java:toString(org_wso2_apk_config_model_URITemplate_getVerb(self.jObj)) ?: ""; + } + # The function that maps to the `hashCode` method of `org.wso2.apk.config.model.URITemplate`. # # + return - The `int` value returning from the Java mapping. @@ -145,13 +145,6 @@ public distinct class URITemplate { org_wso2_apk_config_model_URITemplate_setEndpoint(self.jObj, java:fromString(arg0)); } - # The function that maps to the `setHTTPVerb` method of `org.wso2.apk.config.model.URITemplate`. - # - # + arg0 - The `string` value required to map with the Java method parameter. - public isolated function setHTTPVerb(string arg0) { - org_wso2_apk_config_model_URITemplate_setHTTPVerb(self.jObj, java:fromString(arg0)); - } - # The function that maps to the `setId` method of `org.wso2.apk.config.model.URITemplate`. # # + arg0 - The `int` value required to map with the Java method parameter. @@ -180,6 +173,13 @@ public distinct class URITemplate { org_wso2_apk_config_model_URITemplate_setUriTemplate(self.jObj, java:fromString(arg0)); } + # The function that maps to the `setVerb` method of `org.wso2.apk.config.model.URITemplate`. + # + # + arg0 - The `string` value required to map with the Java method parameter. + public isolated function setVerb(string arg0) { + org_wso2_apk_config_model_URITemplate_setVerb(self.jObj, java:fromString(arg0)); + } + # The function that maps to the `wait` method of `org.wso2.apk.config.model.URITemplate`. # # + return - The `javalang:InterruptedException` value returning from the Java mapping. @@ -251,12 +251,6 @@ isolated function org_wso2_apk_config_model_URITemplate_getEndpoint(handle recei paramTypes: [] } external; -isolated function org_wso2_apk_config_model_URITemplate_getHTTPVerb(handle receiver) returns handle = @java:Method { - name: "getHTTPVerb", - 'class: "org.wso2.apk.config.model.URITemplate", - paramTypes: [] -} external; - function org_wso2_apk_config_model_URITemplate_getId(handle receiver) returns int = @java:Method { name: "getId", 'class: "org.wso2.apk.config.model.URITemplate", @@ -281,6 +275,12 @@ isolated function org_wso2_apk_config_model_URITemplate_getUriTemplate(handle re paramTypes: [] } external; +isolated function org_wso2_apk_config_model_URITemplate_getVerb(handle receiver) returns handle = @java:Method { + name: "getVerb", + 'class: "org.wso2.apk.config.model.URITemplate", + paramTypes: [] +} external; + function org_wso2_apk_config_model_URITemplate_hashCode(handle receiver) returns int = @java:Method { name: "hashCode", 'class: "org.wso2.apk.config.model.URITemplate", @@ -323,8 +323,8 @@ isolated function org_wso2_apk_config_model_URITemplate_setEndpoint(handle recei paramTypes: ["java.lang.String"] } external; -isolated function org_wso2_apk_config_model_URITemplate_setHTTPVerb(handle receiver, handle arg0) = @java:Method { - name: "setHTTPVerb", +isolated function org_wso2_apk_config_model_URITemplate_setVerb(handle receiver, handle arg0) = @java:Method { + name: "setVerb", 'class: "org.wso2.apk.config.model.URITemplate", paramTypes: ["java.lang.String"] } external; diff --git a/runtime/config-deployer-service/ballerina/tests/APIClientTest.bal b/runtime/config-deployer-service/ballerina/tests/APIClientTest.bal index c3d4a9537f..8f7f4997b5 100644 --- a/runtime/config-deployer-service/ballerina/tests/APIClientTest.bal +++ b/runtime/config-deployer-service/ballerina/tests/APIClientTest.bal @@ -209,12 +209,12 @@ public function testBackendConfigGenerationFromAPKConf() returns error? { test:assertEquals(apiArtifact.backendServices.length(), 3, "Required number of endpoints not found"); test:assertTrue(apiArtifact.productionEndpointAvailable, "Production endpoint not defined"); - test:assertEquals(apiArtifact.productionRoute.length(), 1, "Production endpoint not defined"); - foreach model:Httproute httpRoute in apiArtifact.productionRoute { + test:assertEquals(apiArtifact.productionHttpRoutes.length(), 1, "Production endpoint not defined"); + foreach model:HTTPRoute httpRoute in apiArtifact.productionHttpRoutes { test:assertEquals(httpRoute.spec.hostnames, ["default.gw.wso2.com"], "Production endpoint vhost mismatch"); test:assertEquals(httpRoute.spec.rules.length(), 2, "Required number of HTTP Route rules not found"); - model:HTTPBackendRef[]? backendRefs = httpRoute.spec.rules[0].backendRefs; - if backendRefs is model:HTTPBackendRef[] { + model:BackendRef[]? backendRefs = httpRoute.spec.rules[0].backendRefs; + if backendRefs is model:BackendRef[] { string backendUUID = backendRefs[0].name; test:assertEquals(apiArtifact.backendServices.get(backendUUID).spec, prodBackendSpec, "Production Backend is not equal to expected Production Backend Config"); } else { @@ -223,11 +223,11 @@ public function testBackendConfigGenerationFromAPKConf() returns error? { } test:assertTrue(apiArtifact.sandboxEndpointAvailable, "Sandbox endpoint not defined"); - test:assertEquals(apiArtifact.sandboxRoute.length(), 1, "Sandbox Backend not defined"); - foreach model:Httproute httpRoute in apiArtifact.sandboxRoute { + test:assertEquals(apiArtifact.sandboxHttpRoutes.length(), 1, "Sandbox Backend not defined"); + foreach model:HTTPRoute httpRoute in apiArtifact.sandboxHttpRoutes { test:assertEquals(httpRoute.spec.hostnames, ["default.sandbox.gw.wso2.com"], "Sandbox vhost mismatch"); - model:HTTPBackendRef[]? backendRefs = httpRoute.spec.rules[0].backendRefs; - if backendRefs is model:HTTPBackendRef[] { + model:BackendRef[]? backendRefs = httpRoute.spec.rules[0].backendRefs; + if backendRefs is model:BackendRef[] { string backendUUID = backendRefs[0].name; test:assertEquals(apiArtifact.backendServices.get(backendUUID).spec, sandboxBackendSpec, "Sandbox Backend is not equal to expected Sandbox Backend Config"); } else { @@ -303,15 +303,18 @@ public function testScopeConfigGenerationFromAPKConf() returns error? { apiArtifact.scopes.get("publisher").metadata.name, apiArtifact.scopes.get("reader").metadata.name ]; - foreach model:Httproute httpRoute in apiArtifact.productionRoute { + foreach model:HTTPRoute httpRoute in apiArtifact.productionHttpRoutes { model:HTTPRouteFilter[]? httpFilters = httpRoute.spec.rules[0].filters; if httpFilters is model:HTTPRouteFilter[] { foreach model:HTTPRouteFilter httpFilter in httpFilters { - if (httpFilter.'type.equalsIgnoreCaseAscii("ExtensionRef")) { - model:LocalObjectReference? extensionRef = httpFilter.extensionRef; - if extensionRef is model:LocalObjectReference { - test:assertEquals(extensionRef.kind, "Scope", "ExtensionRef for scope is not equal to expected Config"); - test:assertTrue(scopeUUIDs.indexOf(extensionRef.name) != (), "Scope not found in the scope resources"); + if httpFilter.'type is string { + string httpFilterType = httpFilter.'type; + if (httpFilterType.equalsIgnoreCaseAscii("ExtensionRef")) { + model:LocalObjectReference? extensionRef = httpFilter.extensionRef; + if extensionRef is model:LocalObjectReference { + test:assertEquals(extensionRef.kind, "Scope", "ExtensionRef for scope is not equal to expected Config"); + test:assertTrue(scopeUUIDs.indexOf(extensionRef.name) != (), "Scope not found in the scope resources"); + } } } } @@ -599,7 +602,7 @@ public function testEnvironmentGenerationFromAPKConf() returns error? { test:assertFail("API is not equal to expected API Config"); } - model:Httproute[] productionRoutes = apiArtifact.productionRoute; + model:HTTPRoute[] productionRoutes = apiArtifact.productionHttpRoutes; foreach var route in productionRoutes { test:assertEquals(route.spec.hostnames, ["default-dev.gw.wso2.com"], "Production endpoint vhost mismatch"); } @@ -646,7 +649,7 @@ public function testBasicAPIFromAPKConf() returns error? { test:assertFail("API is not equal to expected API Config"); } - model:Httproute[] productionRoutes = apiArtifact.productionRoute; + model:HTTPRoute[] productionRoutes = apiArtifact.productionHttpRoutes; foreach var route in productionRoutes { test:assertEquals(route.spec.hostnames, ["default.gw.wso2.com"], "Production endpoint vhost mismatch"); } @@ -691,11 +694,11 @@ public function APIToAPKConfDataProvider() returns map<[runtimeModels:API, APKCo runtimeModels:URITemplate[] uriTemplates = []; runtimeModels:URITemplate uriTemplate = runtimeModels:newURITemplate1(); uriTemplate.setUriTemplate("/menu"); - uriTemplate.setHTTPVerb("GET"); + uriTemplate.setVerb("GET"); uriTemplates.push(uriTemplate); runtimeModels:URITemplate uriTemplate1 = runtimeModels:newURITemplate1(); uriTemplate1.setUriTemplate("/order"); - uriTemplate1.setHTTPVerb("POST"); + uriTemplate1.setVerb("POST"); uriTemplate1.setAuthEnabled(false); uriTemplate1.setEndpoint("http://localhost:9091"); uriTemplate1.setScopes("scope1"); diff --git a/runtime/config-deployer-service/ballerina/tests/DeployerClientTest.bal b/runtime/config-deployer-service/ballerina/tests/DeployerClientTest.bal index 6d04d9774e..710c0e68ca 100644 --- a/runtime/config-deployer-service/ballerina/tests/DeployerClientTest.bal +++ b/runtime/config-deployer-service/ballerina/tests/DeployerClientTest.bal @@ -2,15 +2,15 @@ import ballerina/test; import config_deployer_service.model; @test:Config {dataProvider: RoutesOrderDataProvider} -public isolated function testCreateHttpRoutesOrder(model:Httproute[] httpRoutes, model:Httproute[] expectedSortedhttpRoutes) returns error? { +public isolated function testCreateHttpRoutesOrder(model:HTTPRoute[] httpRoutes, model:HTTPRoute[] expectedSortedhttpRoutes) returns error? { DeployerClient deployerClient = new; - model:Httproute[] sortedHttpRoutes = check deployerClient.createHttpRoutesOrder(httpRoutes); + model:HTTPRoute[] sortedHttpRoutes = check deployerClient.createHttpRoutesOrder(httpRoutes); test:assertEquals(sortedHttpRoutes, expectedSortedhttpRoutes, "Sorted HttpRoutes are not equal to expected SortedRoutes"); } -public function RoutesOrderDataProvider() returns map<[model:Httproute[], model:Httproute[]]>|error { - model:Httproute sortedHttpRoute = { +public function RoutesOrderDataProvider() returns map<[model:HTTPRoute[], model:HTTPRoute[]]>|error { + model:HTTPRoute sortedHttpRoute = { apiVersion: "gateway.networking.k8s.io/v1beta1", kind: "HTTPRoute", metadata: {name: "01ee37b2-57b1-12ee-b8c5-e9b11538a0c9"}, @@ -136,7 +136,7 @@ public function RoutesOrderDataProvider() returns map<[model:Httproute[], model: } }; - map<[model:Httproute[], model:Httproute[]]> routesMap = { + map<[model:HTTPRoute[], model:HTTPRoute[]]> routesMap = { "1": [ [ { diff --git a/runtime/config-deployer-service/ballerina/types.bal b/runtime/config-deployer-service/ballerina/types.bal index 835dad9846..22ffcd5a24 100644 --- a/runtime/config-deployer-service/ballerina/types.bal +++ b/runtime/config-deployer-service/ballerina/types.bal @@ -205,7 +205,7 @@ public type APKConf record { string basePath; @constraint:String {maxLength: 30, minLength: 1} string version; - string 'type = "REST"; + string 'type = API_TYPE_REST; # Endpoint to expose API Definition string definitionPath?; # Is this the default version of the API diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/APIConstants.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/APIConstants.java index 7df2d33425..178436ddf2 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/APIConstants.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/APIConstants.java @@ -18,15 +18,19 @@ package org.wso2.apk.config; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.Set; /** - * This class represents the constants that are used for APIManager implementation + * This class represents the constants that are used for APIManager + * implementation */ public final class APIConstants { public static final String DEFAULT_SUB_POLICY_UNLIMITED = "Unlimited"; public static final String HTTP_POST = "POST"; - //Swagger v2.0 constants + // Swagger v2.0 constants public static final String SWAGGER_X_SCOPE = "x-scope"; public static final String SWAGGER_X_AMZN_RESOURCE_NAME = "x-amzn-resource-name"; public static final String SWAGGER_X_AMZN_RESOURCE_TIMEOUT = "x-amzn-resource-timeout"; @@ -54,10 +58,9 @@ public final class APIConstants { public static final String OPENAPI_MASTER_JSON = "swagger.json"; public static final String OPENAPI_MASTER_YAML = "swagger.yaml"; - - //URI Authentication Schemes - public static final Set SUPPORTED_METHODS = - Set.of("get", "put", "post", "delete", "patch", "head", "options"); + // URI Authentication Schemes + public static final Set SUPPORTED_METHODS = Set.of("get", "put", "post", "delete", "patch", "head", + "options"); public static final String TYPE = "Type"; public static final String JAVA_IO_TMPDIR = "java.io.tmpdir"; public static final String WSO2_GATEWAY_ENVIRONMENT = "wso2"; @@ -79,12 +82,13 @@ public final class APIConstants { public static final String STOMP_TRANSPORT_PROTOCOL_NAME = "stomp"; public static final String REDIS_TRANSPORT_PROTOCOL_NAME = "redis"; // GraphQL related constants + public static final Set GRAPHQL_SUPPORTED_METHOD_LIST = Collections.unmodifiableSet(new HashSet( + Arrays.asList(new String[] { "QUERY", "MUTATION", "SUBSCRIPTION", "head", "options" }))); public enum ParserType { - REST, ASYNC + REST, ASYNC, GRAPHQL } - public static class OperationParameter { public static final String PAYLOAD_PARAM_NAME = "Payload"; @@ -95,4 +99,3 @@ private OperationParameter() { } } - diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java index 4def67b08b..8cfb710849 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java @@ -17,21 +17,23 @@ private DefinitionParserFactory() { static { parsers.add(new AsyncApiParser()); parsers.add(new OAS3Parser()); - } public static APIDefinition getParser(API api) { - if (APIConstants.ParserType.REST.name().equals(api.getType())) { + if (APIConstants.ParserType.REST.name().equals(api.getType()) + || APIConstants.ParserType.GRAPHQL.name().equals(api.getType())) { return new OAS3Parser(); - } else if (APIConstants.ParserType.ASYNC.name().equals(api.getType())){ + } else if (APIConstants.ParserType.ASYNC.name().equals(api.getType())) { return new AsyncApiParser(); } return null; } + public static APIDefinition getParser(String apiType) { - if ("REST".equals(apiType)) { + if (APIConstants.ParserType.REST.name().equals(apiType) + || APIConstants.ParserType.GRAPHQL.name().equals(apiType)) { return new OAS3Parser(); - } else if ("ASYNC".equals(apiType)){ + } else if ("ASYNC".equals(apiType)) { return new AsyncApiParser(); } return null; @@ -39,7 +41,7 @@ public static APIDefinition getParser(String apiType) { public static APIDefinition getValidatedParser(String definition) { for (APIDefinition parser : parsers) { - if (parser.canHandleDefinition(definition)){ + if (parser.canHandleDefinition(definition)) { return parser; } } diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/api/Info.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/api/Info.java index 41ea6c6e20..8e32d08f02 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/api/Info.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/api/Info.java @@ -50,7 +50,11 @@ public void setContext(String context) { this.context = context; } - public List getEndpoints() { return endpoints; } + public List getEndpoints() { + return endpoints; + } - public void setEndpoints(List endpoints) { this.endpoints = endpoints; } + public void setEndpoints(List endpoints) { + this.endpoints = endpoints; + } } diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/AsyncApiParser.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/AsyncApiParser.java index 5ffd17db5b..9bf15b44f8 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/AsyncApiParser.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/AsyncApiParser.java @@ -1590,7 +1590,7 @@ public Set getURITemplates(String apiDefinition, boolean includePub private URITemplate buildURITemplate(String target, String verb, Aai20Operation operation, String[] scopes, Aai20ChannelItem channel) throws APIManagementException { URITemplate template = new URITemplate(); - template.setHTTPVerb(verb); + template.setVerb(verb); template.setUriTemplate(target); Extension authTypeExtension = channel.getExtension(APIConstants.SWAGGER_X_AUTH_TYPE); diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/OAS3Parser.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/OAS3Parser.java index 341a0d87f1..cb450a1ee6 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/OAS3Parser.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/OAS3Parser.java @@ -112,8 +112,9 @@ private Set getURITemplates(OpenAPI openAPI) throws APIManagementEx for (Map.Entry entry : pathItem.readOperationsMap().entrySet()) { Operation operation = entry.getValue(); URITemplate template = new URITemplate(); - if (APIConstants.SUPPORTED_METHODS.contains(entry.getKey().name().toLowerCase())) { - template.setHTTPVerb(entry.getKey().name().toUpperCase()); + if (APIConstants.SUPPORTED_METHODS.contains(entry.getKey().name().toLowerCase()) + || (APIConstants.GRAPHQL_SUPPORTED_METHOD_LIST.contains(entry.getKey().name().toUpperCase()))) { + template.setVerb(entry.getKey().name().toUpperCase()); template.setUriTemplate(pathKey); List opScopes = getScopeOfOperations(OPENAPI_SECURITY_SCHEMA_KEY, operation); if (!opScopes.isEmpty()) { diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java index e8060a6588..d2715d77b2 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java @@ -1,7 +1,5 @@ package org.wso2.apk.config.model; -import java.util.Set; - public class API { private String name; private String basePath; diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/SwaggerData.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/SwaggerData.java index 757bc9df7e..2f74a2350a 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/SwaggerData.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/SwaggerData.java @@ -77,8 +77,6 @@ public void setPolicy(String policy) { this.policy = policy; } - - public String[] getScopes() { return scopes; @@ -112,7 +110,7 @@ public SwaggerData(API api) { for (URITemplate uriTemplate : uriTemplates) { Resource resource = new Resource(); resource.path = uriTemplate.getUriTemplate(); - resource.verb = uriTemplate.getHTTPVerb(); + resource.verb = uriTemplate.getVerb(); resource.authType = uriTemplate.isAuthEnabled(); resource.scopes = uriTemplate.retrieveAllScopes(); resources.add(resource); diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/URITemplate.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/URITemplate.java index 523bbebe5a..a49eaf1e7f 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/URITemplate.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/URITemplate.java @@ -17,30 +17,29 @@ */ package org.wso2.apk.config.model; - import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Objects; -public class URITemplate implements Serializable{ +public class URITemplate implements Serializable { private static final long serialVersionUID = 1L; private String uriTemplate; private String resourceURI; - private String httpVerb; + private String verb; private boolean authEnabled = true; private List scopes = new ArrayList(); private int id; private String endpoint; - public String getHTTPVerb() { - return httpVerb; + public String getVerb() { + return verb; } - public void setHTTPVerb(String httpVerb) { - this.httpVerb = httpVerb; + public void setVerb(String verb) { + this.verb = verb; } public boolean isAuthEnabled() { @@ -71,24 +70,27 @@ public String[] getScopes() { return scopes.toArray(new String[scopes.size()]); } - - public void setScopes(String scope){ + public void setScopes(String scope) { this.scopes.add(scope); } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; URITemplate that = (URITemplate) o; - return authEnabled == that.authEnabled && id == that.id && Objects.equals(uriTemplate, that.uriTemplate) && Objects.equals(resourceURI, that.resourceURI) && Objects.equals(httpVerb, that.httpVerb) && Objects.equals(scopes, that.scopes) && Objects.equals(endpoint, that.endpoint); + return authEnabled == that.authEnabled && id == that.id && Objects.equals(uriTemplate, that.uriTemplate) + && Objects.equals(resourceURI, that.resourceURI) && Objects.equals(verb, that.verb) + && Objects.equals(scopes, that.scopes) && Objects.equals(endpoint, that.endpoint); } @Override public int hashCode() { - return Objects.hash(uriTemplate, resourceURI, httpVerb, authEnabled, scopes, id, endpoint); + return Objects.hash(uriTemplate, resourceURI, verb, authEnabled, scopes, id, endpoint); } public int getId() {