| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF 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 |
| * |
| * http://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. |
| */ |
| |
| /* |
| * Add a new or update an existing API configuration in the API Gateway |
| * https://docs.cloudant.com/document.html#documentCreate |
| * |
| * Parameters (all as fields in the message JSON object) |
| * gwUrlV2 Required when accesstoken is provided. The V2 API Gateway base path (i.e. http://gw.com) |
| * gwUrl Required. The API Gateway base path (i.e. http://gw.com) |
| * gwUser Optional. The API Gateway authentication |
| * gwPwd Optional. The API Gateway authentication |
| * __ow_user Required. Namespace of API author. Set by controller |
| * The value overrides namespace values in the apidoc |
| * Don't override namespace values in the swagger though |
| * tenantInstance Optional. Instance identifier used when creating the specific API GW Tenant |
| * accesstoken Optional. Dynamic API GW auth. Overrides gwUser/gwPwd |
| * spaceguid Optional. Namespace unique id. |
| * responsetype Optional. web action response .extension to use. default to json |
| * apidoc Required. The API Gateway mapping document |
| * namespace Required. Namespace of user/caller |
| * apiName Optional if swagger not specified. API descriptive name |
| * gatewayBasePath Required if swagger not specified. API base path |
| * gatewayPath Required if swagger not specified. Specific API path (relative to base path) |
| * gatewayMethod Required if swagger not specified. API path operation |
| * id Optional if swagger not specified. Unique id of API |
| * action Required. if swagger not specified |
| * name Required. Action name (includes package) |
| * namespace Required. Action namespace |
| * backendMethod Required. Action invocation REST verb. "POST" |
| * backendUrl Required. Action invocation REST url |
| * authkey Required. Action invocation auth key |
| * secureKey Optional. Action's require-whisk-auth value |
| * swagger Required if gatewayBasePath not provided. API swagger JSON |
| * |
| * NOTE: The package containing this action will be bound to the following values: |
| * gwUrl, gwAuth |
| * As such, the caller to this action should normally avoid explicitly setting |
| * these values |
| */ |
| var utils = require('./utils.js'); |
| var utils2 = require('./apigw-utils.js'); |
| |
| function main(message) { |
| //console.log('message: '+JSON.stringify(message)); // ONLY FOR TEMPORARY/LOCAL DEBUG; DON'T ENABLE PERMANENTLY |
| var badArgMsg = validateArgs(message); |
| if (badArgMsg) { |
| return Promise.reject(utils2.makeErrorResponseObject(badArgMsg, (message.__ow_method !== undefined))); |
| } |
| |
| var gwInfo = { |
| gwUrl: message.gwUrl, |
| }; |
| |
| // Replace the CLI provided namespace values with the controller provided namespace value |
| // If __ow_user is not set, the namespace values are left alone |
| if (message.accesstoken) { |
| utils2.updateNamespace(message.apidoc, message.__ow_user); |
| } else { |
| utils.updateNamespace(message.apidoc, message.__ow_user); |
| } |
| |
| // Set the User-Agent header value |
| if (message.__ow_headers && message.__ow_headers['user-agent']) { |
| utils2.setSubUserAgent(message.__ow_headers['user-agent']); |
| } |
| |
| // message.apidoc already validated; creating shortcut to it |
| var doc; |
| if (typeof message.apidoc === 'object') { |
| doc = message.apidoc; |
| } else if (typeof message.apidoc === 'string') { |
| doc = JSON.parse(message.apidoc); |
| } |
| |
| // message.swagger already validated; creating object |
| var swaggerObj; |
| if (typeof doc.swagger === 'object') { |
| swaggerObj = doc.swagger; |
| } else if (typeof doc.swagger === 'string') { |
| swaggerObj = JSON.parse(doc.swagger); |
| } |
| doc.swagger = swaggerObj; |
| |
| var basepath = getBasePath(doc); |
| |
| var tenantInstance = message.tenantInstance || 'openwhisk'; |
| |
| // This can be invoked as either a standard web action or as a normal action |
| var calledAsWebAction = message.__ow_method !== undefined; |
| |
| // Log parameter values |
| console.log('GW URL : '+message.gwUrl); |
| console.log('GW URL V2 : '+message.gwUrlV2); |
| console.log('GW Auth : '+utils.confidentialPrint(message.gwPwd)); |
| console.log('__ow_user : '+message.__ow_user); |
| console.log('namespace : '+doc.namespace); |
| console.log('tenantInstance: '+message.tenantInstance+' / '+tenantInstance); |
| console.log('accesstoken : '+message.accesstoken); |
| console.log('spaceguid : '+message.spaceguid); |
| console.log('responsetype : '+message.responsetype); |
| console.log('API name : '+doc.apiName); |
| console.log('basepath : '+basepath); |
| console.log('relpath : '+doc.gatewayPath); |
| console.log('GW method : '+doc.gatewayMethod); |
| if (doc.action) { |
| console.log('action name: '+doc.action.name); |
| console.log('action namespace: '+doc.action.namespace); |
| console.log('action backendUrl: '+doc.action.backendUrl); |
| console.log('action backendMethod: '+doc.action.backendMethod); |
| console.log('action authkey: '+utils.confidentialPrint(doc.action.authkey)); |
| console.log('action secureKey: '+utils.confidentialPrint(doc.action.secureKey)); |
| } |
| console.log('calledAsWebAction: '+calledAsWebAction); |
| console.log('apidoc :\n'+JSON.stringify(doc)); |
| |
| // If an API GW access token is provided, use the API GW V2 URL and use this token to auth with the API GW |
| // Otherwise, use the API GW "V1" URL and use the supplied GW auth credentials to auth with the API GW |
| if (message.accesstoken) { |
| var apiDocId; |
| gwInfo.gwUrl = message.gwUrlV2; |
| gwInfo.gwAuth = message.accesstoken; |
| // 1. If an existing API exists for this namespace/basepath combination, retrieve it and update it |
| // 2. If not, create a new API |
| return utils2.getApis(gwInfo, message.spaceguid, basepath) |
| .then(function(endpointDocs) { |
| console.log('Got '+endpointDocs.length+' APIs'); |
| if (endpointDocs.length === 0) { |
| console.log('No API found for namespace '+doc.namespace + ' with basePath '+ basepath); |
| return Promise.resolve(utils2.generateBaseSwaggerApi(basepath, doc.apiName)); |
| } else { |
| apiDocId = endpointDocs[0].artifact_id; |
| return Promise.resolve(endpointDocs[0].open_api_doc); |
| } |
| }) |
| .then(function(endpointDoc) { |
| if (doc.swagger) { |
| console.log('Use provided swagger as the entire API; override any existing API'); |
| return Promise.resolve(doc.swagger); |
| } else { |
| console.log('Add the provided API endpoint'); |
| return Promise.resolve(utils2.addEndpointToSwaggerApi(endpointDoc, doc, message.responsetype)); |
| } |
| }) |
| .then(function(apiSwagger){ |
| console.log("Validating Swagger doc before sending it to API GW.") |
| return utils2.validateFinalSwagger(apiSwagger); |
| }) |
| .then(function(apiSwagger) { |
| console.log('Final swagger API config: '+ JSON.stringify(apiSwagger)); |
| return utils2.addApiToGateway(gwInfo, message.spaceguid, apiSwagger, apiDocId); |
| }) |
| .then(function(gwApi) { |
| console.log('API GW configured with API'); |
| var cliApi = utils2.generateCliApiFromGwApi(gwApi).value; |
| console.log('createApi success'); |
| return Promise.resolve(utils2.makeResponseObject(cliApi, calledAsWebAction)); |
| }) |
| .catch(function(reason) { |
| var rejmsg = 'API creation failure: ' + JSON.parse(utils2.makeJsonString(reason)); // Avoid unnecessary JSON escapes |
| console.error(rejmsg); |
| return Promise.reject(utils2.makeErrorResponseObject(rejmsg, calledAsWebAction)); |
| }); |
| } else { |
| // Create and activate a new API path |
| // 1. Create tenant id this namespace. If id exists, create is a noop |
| // 2. Obtain any existing configuration for the target API. If none, this is a new API |
| // 3. Create the API document to send to the API GW. If API exists, update it |
| // 4. Configure API GW with the new/updated API |
| var tenantId; |
| var gwApiId; |
| if (message.gwUser && message.gwPwd) { |
| gwInfo.gwAuth = Buffer.from(message.gwUser+':'+message.gwPwd,'ascii').toString('base64'); |
| } |
| return utils.createTenant(gwInfo, doc.namespace, tenantInstance) |
| .then(function(tenant) { |
| console.log('Got the API GW tenant: '+JSON.stringify(tenant)); |
| tenantId = tenant.id; |
| return Promise.resolve(utils.getApis(gwInfo, tenant.id, basepath)); |
| }) |
| .then(function(apis) { |
| console.log('Got '+apis.length+' APIs'); |
| if (apis.length === 0) { |
| console.log('No APIs found for namespace '+doc.namespace+' with basepath '+basepath); |
| return Promise.resolve(utils.generateBaseSwaggerApi(basepath, doc.apiName)); |
| } else if (apis.length > 1) { |
| console.error('Multiple APIs found for namespace '+doc.namespace+' with basepath '+basepath); |
| return Promise.reject('Internal error. Multiple APIs found for namespace '+doc.namespace+' with basepath '+basepath); |
| } |
| gwApiId = apis[0].id; |
| return Promise.resolve(utils.generateSwaggerApiFromGwApi(apis[0])); |
| }) |
| .then(function(swaggerApi) { |
| if (doc.swagger) { |
| console.log('Use provided swagger as the entire API; override any existing API'); |
| return Promise.resolve(doc.swagger); |
| } else { |
| console.log('Add the provided API endpoint'); |
| return Promise.resolve(utils.addEndpointToSwaggerApi(swaggerApi, doc)); |
| } |
| }) |
| .then(function(apiSwagger){ |
| console.log("Validating Swagger doc before sending it to API GW.") |
| return utils2.validateFinalSwagger(apiSwagger); |
| }) |
| .then(function(swaggerApi) { |
| console.log('Final swagger API config: '+ JSON.stringify(swaggerApi)); |
| return utils.addApiToGateway(gwInfo, tenantId, swaggerApi, gwApiId); |
| }) |
| .then(function(gwApi) { |
| console.log('API GW configured with API'); |
| var cliApi = utils.generateCliApiFromGwApi(gwApi).value; |
| console.log('createApi success'); |
| return Promise.resolve(utils2.makeResponseObject(cliApi, calledAsWebAction)); |
| }) |
| .catch(function(reason) { |
| var rejmsg = 'API creation failure: ' + JSON.parse(utils2.makeJsonString(reason)); // Avoid unnecessary JSON escapes |
| console.error(rejmsg); |
| return Promise.reject(utils2.makeErrorResponseObject(rejmsg, calledAsWebAction)); |
| }); |
| } |
| } |
| |
| function getBasePath(apidoc) { |
| if (apidoc.swagger) { |
| return apidoc.swagger.basePath; |
| } |
| return apidoc.gatewayBasePath; |
| } |
| |
| |
| function validateArgs(message) { |
| var tmpdoc; |
| if(!message) { |
| console.error('No message argument!'); |
| return 'Internal error. A message parameter was not supplied.'; |
| } |
| |
| if (!message.gwUrl && !message.gwUrlV2) { |
| return 'gwUrl is required.'; |
| } |
| |
| if (!message.__ow_user) { |
| return 'A valid auth key is required.'; |
| } |
| |
| if(!message.apidoc) { |
| return 'apidoc is required.'; |
| } |
| if (typeof message.apidoc == 'object') { |
| tmpdoc = message.apidoc; |
| } else if (typeof message.apidoc === 'string') { |
| try { |
| tmpdoc = JSON.parse(message.apidoc); |
| } catch (e) { |
| return 'apidoc field cannot be parsed. Ensure it is valid JSON.'; |
| } |
| } else { |
| return 'apidoc field is of type ' + (typeof message.apidoc) + ' and should be a JSON object or a JSON string.'; |
| } |
| |
| if (!tmpdoc.namespace) { |
| return 'apidoc is missing the namespace field'; |
| } |
| |
| var tmpSwaggerDoc; |
| if(tmpdoc.swagger) { |
| if (tmpdoc.gatewayBasePath) { |
| return 'swagger and gatewayBasePath are mutually exclusive and cannot be specified together.'; |
| } |
| if (typeof tmpdoc.swagger == 'object') { |
| tmpSwaggerDoc = tmpdoc.swagger; |
| } else if (typeof tmpdoc.swagger === 'string') { |
| try { |
| tmpSwaggerDoc = JSON.parse(tmpdoc.swagger); |
| } catch (e) { |
| return 'swagger field cannot be parsed. Ensure it is valid JSON.'; |
| } |
| } else { |
| return 'swagger field is ' + (typeof tmpdoc.swagger) + ' and should be an object or a JSON string.'; |
| } |
| console.log('Swagger JSON object: ', tmpSwaggerDoc); |
| if (!tmpSwaggerDoc.basePath) { |
| return 'swagger is missing the basePath field.'; |
| } |
| if (!tmpSwaggerDoc.paths) { |
| return 'swagger is missing the paths field.'; |
| } |
| if (!tmpSwaggerDoc.info) { |
| return 'swagger is missing the info field.'; |
| } |
| } else { |
| if (!tmpdoc.gatewayBasePath) { |
| return 'apidoc is missing the gatewayBasePath field'; |
| } |
| |
| if (!tmpdoc.gatewayPath) { |
| return 'apidoc is missing the gatewayPath field'; |
| } |
| |
| if (!tmpdoc.gatewayMethod) { |
| return 'apidoc is missing the gatewayMethod field'; |
| } |
| |
| if (!tmpdoc.action) { |
| return 'apidoc is missing the action field.'; |
| } |
| |
| if (!tmpdoc.action.backendMethod) { |
| return 'action is missing the backendMethod field.'; |
| } |
| |
| if (!tmpdoc.action.backendUrl) { |
| return 'action is missing the backendUrl field.'; |
| } |
| |
| if (!tmpdoc.action.namespace) { |
| return 'action is missing the namespace field.'; |
| } |
| |
| if(!tmpdoc.action.name) { |
| return 'action is missing the name field.'; |
| } |
| |
| if (!tmpdoc.action.authkey) { |
| return 'action is missing the authkey field.'; |
| } |
| } |
| |
| return ''; |
| } |
| |
| module.exports.main = main; |