UNPKG

serverless-offline-msk

Version:

A serverless offline plugin that enables AWS MSK events

240 lines (222 loc) 8.39 kB
'use strict'; const _ = require('lodash'); const d = require('d'); const lazy = require('d/lazy'); const path = require('path'); const semver = require('semver'); const log = require('@serverless/utils/log').log.get('console'); const resolveAuthMode = require('@serverless/utils/auth/resolve-mode'); const urls = require('@serverless/utils/lib/auth/urls'); const apiRequest = require('@serverless/utils/api-request'); const ServerlessError = require('../serverless-error'); const supportedCommands = new Set([ 'deploy', 'deploy function', 'info', 'package', 'remove', 'rollback', ]); class Console { constructor(serverless) { this.serverless = serverless; // Used to confirm that we obtained compatible console state data for deployment this.stateSchemaVersion = '2'; } async initialize() { const { configurationInput: configuration } = this.serverless; this.isEnabled = (() => { const { processedInput: { commands, options }, } = this.serverless; if (!_.get(configuration, 'console')) return false; this.org = options.org || configuration.console.org || configuration.org; if (!this.org) return false; const command = commands.join(' '); if (!supportedCommands.has(command)) return false; const providerName = configuration.provider.name || configuration.provider; if (providerName !== 'aws') { log.error(`Provider "${providerName}" is currently not supported by the console`); return false; } if (command !== 'rollback' && (command !== 'deploy' || !options.package)) { if ( !Object.values(this.serverless.service.functions).some((functionConfig) => this.isFunctionSupported(functionConfig) ) ) { log.warning( "Cannot enable console: Service doesn't configure any function with the supported runtime" ); return false; } } return true; })(); if (!this.isEnabled) return; if (!(await resolveAuthMode())) { const errorMessage = process.env.CI ? 'You are not currently logged in. Follow instructions in http://slss.io/run-in-cicd to setup env vars for authentication.' : 'You are not currently logged in. To log in, run "serverless login --console"'; throw new ServerlessError(errorMessage, 'CONSOLE_NOT_AUTHENTICATED'); } this.packagePath = this.serverless.processedInput.options.package || this.serverless.service.package.path || path.join(this.serverless.serviceDir, '.serverless'); this.provider = this.serverless.getProvider('aws'); try { this.orgId = (await apiRequest(`/api/identity/orgs/name/${this.org}`)).orgId; } catch (error) { if (error.httpStatusCode === 404) { throw new ServerlessError( `You are not authenticated to deploy into the org "${this.org}"`, 'CONSOLE_ORG_MISMATCH' ); } throw error; } this.service = this.serverless.service.service; this.stage = this.provider.getStage(); this.region = this.provider.getRegion(); this.config = configuration.console; } isFunctionSupported({ handler, runtime }) { if (!handler) return false; // Docker container image (not supported yet) if (!runtime) return true; // Default is supported nodejs runtime return runtime.startsWith('nodejs'); } async createOtelIngestionToken() { const responseBody = await apiRequest( `/ingestion/kinesis/org/${this.orgId}/service/${this.service}/stage/${this.stage}` ); if (!_.get(responseBody, 'token.accessToken')) { throw new Error( `Cannot deploy to the Console: Unexpected server response (${JSON.stringify(responseBody)})` ); } if (responseBody.status === 'new_token') { this.isFreshOtelIngestionToken = true; if (this.serverless.processedInput.commands.join(' ') === 'deploy') { log.info( 'Generated a new access token for the Console (deployment may take longer than' + ' anticipated as the configuration of all functions will be updated)' ); } } return responseBody.token.accessToken; } async activateOtelIngestionToken() { await apiRequest('/ingestion/kinesis/token', { method: 'PATCH', body: { orgId: this.orgId, serviceId: this.service, stage: this.stage, token: await this.deferredOtelIngestionToken, }, }); } async deactivateOtherOtelIngestionTokens() { const searchParams = new URLSearchParams(); searchParams.set('orgId', this.orgId); searchParams.set('serviceId', this.service); searchParams.set('stage', this.stage); searchParams.set('token', await this.deferredOtelIngestionToken); await apiRequest(`/ingestion/kinesis/tokens?${searchParams}`, { method: 'DELETE' }); } async deactivateAllOtelIngestionTokens() { const searchParams = new URLSearchParams(); searchParams.set('orgId', this.orgId); searchParams.set('serviceId', this.service); searchParams.set('stage', this.stage); await apiRequest(`/ingestion/kinesis/tokens?${searchParams}`, { method: 'DELETE' }); } async deactivateOtelIngestionToken() { await apiRequest(`/ingestion/kinesis/token?token=${await this.deferredOtelIngestionToken}`, { method: 'DELETE', }); } overrideSettings({ otelIngestionToken, layerVersion, service, stage, region }) { this.layerVersion = layerVersion; Object.defineProperties(this, { deferredOtelIngestionToken: d(Promise.resolve(otelIngestionToken)), service: d('cew', service), stage: d('cew', stage), region: d('cew', region), }); } } Object.defineProperties( Console.prototype, lazy({ deferredLayerArn: d(async function () { if (process.env.SLS_OTEL_LAYER_ARN) { if (!process.env.SLS_OTEL_LAYER_ARN.includes(':layer:sls-otel-extension-')) { throw new ServerlessError( 'Cannot rely on custom extension layer ARN: ' + 'For compatibility reasons layer name must be prefixed with "sls-otel-extension-"', 'CONSOLE_INCOMPATIBLE_CUSTOM_LAYER_ARN' ); } this.layerVersion = 'custom'; return process.env.SLS_OTEL_LAYER_ARN; } const data = JSON.parse( String( ( await this.provider.request('S3', 'getObject', { Bucket: 'sls-layers-registry', Key: 'sls-otel-extension-node.json', }) ).Body ) ); if (!data[this.region]) { throw new ServerlessError( `Region "${this.region} is not supported by the Console"`, 'CONSOLE_UNSUPPORTED_REGION' ); } const version = semver.maxSatisfying(Object.keys(data[this.region]), '^0.5.0'); if (!version) { throw new ServerlessError( `Region "${this.region} is not supported by the Console"`, 'CONSOLE_UNSUPPORTED_VERSION_IN_REGION' ); } this.layerVersion = version; return data[this.region][version]; }), deferredFunctionEnvironmentVariables: d(function () { return this.deferredOtelIngestionToken.then((otelIngestionToken) => { const userSettings = _.merge({}, this.config.monitoring, { ingestToken: otelIngestionToken, orgId: this.orgId, namespace: this.service, environment: this.stage, }); const result = { AWS_LAMBDA_EXEC_WRAPPER: '/opt/otel-extension-internal-node/exec-wrapper.sh', SLS_EXTENSION: JSON.stringify(userSettings), }; if (process.env.SERVERLESS_PLATFORM_STAGE) { result.SERVERLESS_PLATFORM_STAGE = process.env.SERVERLESS_PLATFORM_STAGE; } if (process.env.SLS_OTEL_LAYER_DEV_BUILD) result.SLS_DEBUG_EXTENSION = '1'; return result; }); }), deferredOtelIngestionToken: d(function () { return this.createOtelIngestionToken(); }), url: d(function () { return ( `${urls.frontend}/${this.org}/metrics/functions` + `?globalEnvironments=${this.stage}&globalNamespaces=${this.service}` + `&globalRegions=${this.region}&globalScope=functions&globalTimeFrame=15m` ); }), }) ); module.exports = Console;