UNPKG

cloudworker-proxy

Version:
473 lines (423 loc) 19.5 kB
'use strict'; const util = require('util'); const domains = require('./domains'); const Connection = require('./Connection'); const registryPackage = require('./registryPackage'); const instance = require('./instance'); const utils = require('./utils'); /** * The Serverless Platform SDK Class */ class ServerlessSDK { /** * Creates an instance of the SDK. Accepts a configuration object and calls the `config()` method. See the `config()` method for more information on allowed configuration. * @param {string} [config.accessKey] Can either be a Serverless Platform Access Key or an ID Token. * @param {string} [config.platformStage] The Serverless Platform Stage you wish to interact with. This can also be set by the environment variable SERVERLESS_PLATFORM_STAGE= * @param {string} [context.orgName] The name of the Serverless Platform Organization you wish to interact with. If set, this value is used upon creating a Websockets connection, and auto-added to every Event you publish. * @param {string} [context.orgUid] The ID of the Serverless Platform Organization you wish to interact with. If set, this value is used upon creating a Websockets connection, and auto-added to every Event you publish. * @param {string} [context.stageName] The Serverless Platform Organization Stage you wish to interact with. If set, this value is auto-added to every Event you publish. * @param {string} [context.appName] The Serverless Platform Application you wish to interact with. If set, this value is auto-added to every Event you publish. * @param {string} [context.instanceName] The Serverless Platform Instance you wish to interact with. If set, this value is auto-added to every Event you publish. * @param {string} [context.componentName] The Serverless Platform Component you wish to interact with. If set, this value is auto-added to every Event you publish. * @param {string} [context.componentVersion] The Serverless Platform Component version you wish to interact with. If set, this value is auto-added to every Event you publish. * @return {null} * * @example * * const { ServerlessSDK } = require('@serverless/platform-client-china') * * const sdk = new ServerlessSDK({ * accessKey: '123456789', * context: { * orgName: 'my-org', * orgUid: '1234', * stageName: 'prod', * appName: 'my-app', * instanceName: 'my-instance' * } * }) * @return {null} */ constructor(config = {}) { this.context = {}; // Set config this.config(config); // Domains this.domains = domains[this.platformStage]; // Other this.intercepts = {}; this.intercepts.logs = []; this.intercepts.logsInterval = 200; } /** * Updates the SDK configuration * @method * @param {string} [config.accessKey] Can either be a Serverless Platform Access Key or an ID Token. * @param {string} [config.platformStage] The Serverless Platform Stage you wish to interact with. This can also be set by the environment variable SERVERLESS_PLATFORM_STAGE= * @param {string} [context.orgName] The name of the Serverless Platform Organization you wish to interact with. If set, this value is used upon creating a Websockets connection, and auto-added to every Event you publish. * @param {string} [context.orgUid] The ID of the Serverless Platform Organization you wish to interact with. If set, this value is used upon creating a Websockets connection, and auto-added to every Event you publish. * @param {string} [context.stageName] The Serverless Platform Organization Stage you wish to interact with. If set, this value is auto-added to every Event you publish. * @param {string} [context.appName] The Serverless Platform Application you wish to interact with. If set, this value is auto-added to every Event you publish. * @param {string} [context.instanceName] The Serverless Platform Instance you wish to interact with. If set, this value is auto-added to every Event you publish. * @param {string} [context.componentName] The Serverless Platform Component you wish to interact with. If set, this value is auto-added to every Event you publish. * @param {string} [context.componentVersion] The Serverless Platform Component version you wish to interact with. If set, this value is auto-added to every Event you publish. * @return {null} * * @example * * const { ServerlessSDK } = require('@serverless/platform-client-china') * * const sdk = new ServerlessSDK() * sdk.config({ * accessKey: '123456789', * context: { * orgName: 'my-org', * orgUid: '1234', * stageName: 'prod', * appName: 'my-app', * instanceName: 'my-instance' * } * }) */ config(config = {}) { this.accessKey = config.accessKey || this.accessKey; // Can be either an Access Key or an ID Token this.agent = config.agent || this.agent; this.platformAccessKey = config.platformAccessKey || this.platformAccessKey; this.platformStage = process.env.SERVERLESS_PLATFORM_STAGE || config.platformStage || 'prod'; // TODO this is temporary used for deciding whether we need bind specific role for tencent case or not this.skipRoleBinding = config.skipRoleBinding !== undefined ? config.skipRoleBinding : false; config.context = config.context || {}; this.context.orgUid = config.context.orgUid || this.context.orgUid || null; this.context.orgName = config.context.orgName || this.context.orgName || null; this.context.stageName = config.context.stageName || this.context.stageName || null; this.context.appName = config.context.appName || this.context.appName || null; this.context.instanceName = config.context.instanceName || this.context.instanceName || null; this.context.componentName = config.context.componentName || this.context.componentName || null; this.context.componentVersion = config.context.componentVersion || this.context.componentVersion || null; } /** * Gets a domain for a specific service, currently CN verison only has 'events' and 'events-streaming' * Registry and engine are exposed through Tencent API * @param {string} serviceName The name of the Serverless Platform Service you want the domain for. * @return {string} The domain of that service. */ getDomain(serviceName = null) { return this.domains[serviceName] || null; } /** * * Events * */ /** * Establishes a websockets connection with the Serverless Platform * @param {string} options.orgName Name of the Serverless Platform Org. * @param {string} options.orgUid ID of the Serverless Platform Org. * @param {function} options.onEvent A function that handles events recieved from the Serverless Platform * @param {string} options.filter Filters which events this connection should receive * @param {string} options.filter.stageName Tells the SDK to only receive events on a specific stage * @param {string} options.filter.appName Tells the SDK to only receive events on a specific app * @param {string} options.filter.instanceName Tells the SDK to only receive events on a specific service * @return {null} */ async connect(options = {}) { this.connection = new Connection(this); await this.connection.connect(options); } /** * Disconnects a websockets connection with the Serverless Platform * @return {null} */ disconnect() { if (this.connection) { this.connection.disconnect(); } } /** * Checks if the SDK is currently connected to the Serverless Platform * @return {boolean} Will return true if the websocket connection is active. */ isConnected() { if (!this.connection || !this.connection.isConnected()) { return false; } return true; } /** * Publishes a Serverless Platform Event via Websockets. The use-case for this is asynchronous publishing, where you do not want to synchronous auth requests, where every message must be authorized first, adding latency. * @return {null} */ publish(event) { if (!this.isConnected()) { throw new Error('You are not currently connected to the Serverless Platform.'); } this.connection.send(event); } /** * * Registry Packages * */ /** * DEPRECATED: this method is deprecated, use createPackage instead * Returns a new Component as a Javascript object. * @param {*} orgName The name of the Serverless Platform Organization. * @param {*} componentName The name of the Serverless Platform Stage. */ createComponent(orgName = null, componentName = null) { return registryPackage.create(orgName, componentName); } createPackage(orgName = null, packageName = null, type = 'component') { return registryPackage.create(orgName, packageName, type); } /** * DEPRECATED: this method is deprecated, use publishPackage instead * This method publishes a Component to the Serverless Platform Registry. This method accepts a Component Definition (e.g. serverless.component.yml) as an object. In the Component Definition, it looks at the 'main' property to find the path to the Component source directory. Then it collects that directory, adds in the special Component Handler, and packages everything into a zip file. After that, it fetches secure upload URLs and uploads the Component to the Serverless Registry. Once a Component has been uploaded to the Registry, the Registry must do some background processing to publish it, while that's happening, this method polls the API to see when the Component is finished being processed, and then returns the published definition. * @param {*} componentDefinition An object containing the properties of a registryPackage. * @returns {object} The published Component definition is returned from the Platform API. */ async publishComponent(componentDefinition = {}) { return registryPackage.publish(this, componentDefinition); } async publishPackage(packageDefinition = {}) { return registryPackage.publish(this, packageDefinition); } /** * DEPRECATED: this method is deprecated, use getPackage instead * Get Component * @param {*} componentName The name of the Component within the Registry. * @param {*} componentVersion The version of a specific Component within the Registry. If this is not included, this method will always return the latest version. * @return {object} Returns a Component definition from the Registry. */ async getComponent(componentName = null, componentVersion = null) { return registryPackage.getFromRegistry(componentName, componentVersion); } async getPackage(packageName = null, version = null) { return registryPackage.getFromRegistry(packageName, version); } /** * DEPRECATED: this method is deprecated, use listPackages instead * List Components * @param {*} orgUid The org ID which components belongs to * @param {*} filter The filter we can apply */ async listComponents(orgUid = null, { isFeatured } = {}) { return registryPackage.list(orgUid, { isFeatured }); } async listPackages(orgUid = null, { isFeatured } = {}) { return registryPackage.list(orgUid, { isFeatured }); } /** * * Instances * */ /** * Returns a properly formatted ID for an Instance * @param {*} orgUid The Uid of the Serverless Platform Organization. * @param {*} stageName The name of the Serverless Platform Stage. * @param {*} appUid The Uid of the Serverless Platform Application. * @param {*} instanceName The name of the Serverless Platform Instance. * @return {object} Returns a properly formatted ID for an Instance */ generateInstanceId(orgName = null, stageName = null, appUid = null, instanceName = null) { return instance.generateId(orgName, stageName, appUid, instanceName); } /** * Validates the properties of an Instance, as well as auto-corrects shortened syntax (e.g. org => orgName) * @param {*} instanceToValidate The Serverless Platform Instance you want to validate. * @return {object} Returns a validated, formatted version of the Instance */ validateInstance(instanceToValidate) { return instance.validateAndFormat(instanceToValidate); } /** * Returns a new Instance as a Javascript object. * @param {*} orgName The name of the Serverless Platform Organization. * @param {*} stageName The name of the Serverless Platform Stage. * @param {*} appName The name of the Serverless Platform Application. * @param {*} instanceName The name of the Serverless Platform Instance. * @return {object} Returns a new Instance definition as a Javascript Object. */ createInstance(orgName = null, stageName = null, appName = null, instanceName = null) { return instance.create(orgName, stageName, appName, instanceName); } /** * Get an Instance from the Serverless Platform by it's name and the names of its Organization, Stage, Application. * @param {*} orgName The name of the Serverless Platform Organization. * @param {*} stageName The name of the Serverless Platform Stage. * @param {*} appName The name of the Serverless Platform Application. * @param {*} instanceName The name of the Serverless Platform Instance. */ async getInstance( orgName = null, stageName = null, appName = null, instanceName = null, options = null ) { return await instance.getByName(this, orgName, stageName, appName, instanceName, options); } /** * List all Component Instances within an Org, given an org name or org UId * @param {*} orgName Optional. Must include either orgName or orgUid. * @param {*} orgUid Optional. Must include either orgName or orgUid. */ async listInstances(orgName = null, orgUid = null) { return await instance.listByOrgName(this, orgName, orgUid); } /** * Run an action on a Component Instance. Is an asynchronous call by default, but you can perform this synchronously if you set `options.sync`. Please note that synchronous runs have a 24 second timeout limit. That is not ideal for long operations, and is not recommended for deployments. * @param {*} action * @param {*} instanceData * @param {*} credentials * @param {*} options */ async run(action, instanceData = {}, credentials = {}, options = {}) { return await instance.run(this, action, instanceData, credentials, options); } /** * Run Finish * @param {string} method The action that was performed on the component. * @param {object} instanceData An object representing your Instance definition. */ async runFinish(method = null, instanceData) { return await instance.runFinish(this, method, instanceData); } /** * Performs a 'deploy' action and polls the 'getInstance' endpoint until its 'instanceStatus' reflects a successful deployment, or error. */ async deploy(instanceData = {}, credentials = {}, options = {}) { return await instance.deploy(this, instanceData, credentials, options); } /** * Performs a 'remove' action and polls the 'getInstance' endpoint until its 'instanceStatus' reflects a successful deployment, or error. */ async remove(instanceData = {}, credentials = {}, options = {}) { return await instance.remove(this, instanceData, credentials, options); } /** * * Other * */ /** * Intercepts console 'log' 'debug' and 'error' methods, and sends their data to the Serverless Platform as an Event, before writing to stdout. */ async startInterceptingLogs(eventType = null) { // Do nothing, if not connected via websockets if (!this.isConnected()) { throw new Error('Not connected to the Serverless Platform'); } // Save original console methods this.intercepts.logs = []; this.intercepts.console = {}; this.intercepts.console.log = console.log; // eslint-disable-line this.intercepts.console.debug = console.debug; // eslint-disable-line this.intercepts.console.error = console.error; // eslint-disable-line this.intercepts.console.warn = console.warn; // eslint-disable-line this.intercepts.stdout = {}; this.intercepts.stdout.write = process.stdout.write.bind(process.stdout); this.intercepts.stderr = {}; this.intercepts.stderr.write = process.stderr.write.bind(process.stderr); const self = this; // Publish function this.intercepts.publish = async () => { if (!self.intercepts.logs || !self.intercepts.logs.length) { return; } // Copy logs and clear them out immediately const logsCopy = self.intercepts.logs.map((l) => l); self.intercepts.logs = []; // Publish event const evt = { data: { logs: logsCopy } }; evt.event = eventType || 'instance.logs'; self.publish(evt); }; // Set Interval function this.intercepts.interval = setInterval(async () => { await self.intercepts.publish(); }, self.intercepts.logsInterval); // Set Intercept function const intercept = (type, logs) => { // Add logs to queue and format so they print correctly after being sent via Websockets logs.forEach((l) => { if (l === undefined) { l = 'undefined'; } if (typeof l === 'number') { l = JSON.stringify(l); } // Add to logs queue, and add type of log const log = { type }; log.createdAt = Date.now(); log.data = util.inspect(l); // util.inspect converts ciruclar objects to '[Circular]'. Without, errors will happen on stringify... self.intercepts.logs.push(log); }); // Apply old method if (type === 'stdout') { self.intercepts.stdout.write(...logs); } else if (type === 'stderr') { self.intercepts.stderr.write(...logs); } else { self.intercepts.console[type].apply(console, logs); } }; // Replace console methods // eslint-disable-next-line console.log = (...args) => { intercept('log', args); }; // eslint-disable-next-line console.debug = (...args) => { intercept('debug', args); }; // eslint-disable-next-line console.error = (...args) => { intercept('error', args); }; // eslint-disable-next-line console.warn = (...args) => { intercept('warn', args); }; process.stdout.write = (...args) => { intercept('stdout', args); }; process.stderr.write = (...args) => { intercept('stderr', args); }; } /** * Stop intercepting console methods */ async stopInterceptingLogs() { // Do nothing, if not connected via websockets if (!this.isConnected()) { return; } // Clear interval timer if (this.intercepts.interval) { clearInterval(this.intercepts.interval); } // Send any remaining logs await this.intercepts.publish(); await utils.sleep(100); // Reset logs this.intercepts.logs = []; // Replace console methods console.log = this.intercepts.console.log; console.debug = this.intercepts.console.debug; console.error = this.intercepts.console.error; console.warn = this.intercepts.console.warn; process.stdout.write = this.intercepts.stdout.write; process.stderr.write = this.intercepts.stderr.write; } } /** * Exports */ module.exports = { ServerlessSDK, utils, };