'use strict'; const wait = require('timers-ext/promise/sleep'); const { log, progress } = require('@serverless/utils/log'); const apiRequest = require('@serverless/utils/api-request'); const progressKey = 'dev-mode-progress'; const allFunctionsExist = async (context) => { const { total, hits } = await apiRequest(`/api/search/orgs/${context.org.orgId}/search`, { method: 'POST', body: { from: 0, size: context.consoleDevModeTargetFunctions.length, query: { bool: { must: [ { match: { type: 'resource_aws_lambda' }, }, { match: { tag_account_id: context.awsAccountId }, }, { terms: { 'aws_lambda_name.keyword': context.consoleDevModeTargetFunctions }, }, ], }, }, }, }); return { hits, allExist: total === context.consoleDevModeTargetFunctions.length, total: context.consoleDevModeTargetFunctions.length, functionCount: total, }; }; const checkInstrumentationStatus = async (context) => { const { total } = await apiRequest(`/api/search/orgs/${context.org.orgId}/search`, { method: 'POST', body: { from: 0, size: context.consoleDevModeTargetFunctions.length, query: { bool: { must: [ { match: { type: 'resource_aws_lambda' }, }, { match: { tag_account_id: context.awsAccountId }, }, { match: { instrument_mode: 'dev' }, }, { terms: { 'aws_lambda_name.keyword': context.consoleDevModeTargetFunctions }, }, ], }, }, }, }); return { isInstrumented: total === context.consoleDevModeTargetFunctions.length, total: context.consoleDevModeTargetFunctions.length, instrumented: total, }; }; const headerMessage = 'Instrumenting functions'; const waitForInstrumentation = async (context) => { const instrumentationProgress = progress.get(progressKey); let isInstrumenting = true; while (isInstrumenting) { const { isInstrumented: done, total, instrumented } = await checkInstrumentationStatus(context); instrumentationProgress.update( `${headerMessage}\nInstrumenting ${instrumented}/${total} functions` ); if (done) { isInstrumenting = false; } else { await wait(1000); } } }; module.exports = { async isApplicable(context) { const { isConsoleDevMode, org } = context; if (!isConsoleDevMode) { context.inapplicabilityReasonCode = 'NON_DEV_MODE_CONTEXT'; return false; } if (!org) { context.inapplicabilityReasonCode = 'UNRESOLVED_ORG'; return false; } const instrumentationProgress = progress.get(progressKey); instrumentationProgress.update('Validating Serverless Console instrumentation status'); // Add single function name or all function names to the list const targetFunctions = []; const targetInstrumentations = []; context.serverless.service.setFunctionNames(context.options); if (context.options.function) { const func = context.serverless.service.getFunction(context.options.function); const functionName = func.name; targetInstrumentations.push({ instrumentations: { mode: 'dev', }, resourceKey: `aws_${context.awsAccountId}_function_${context.serverless.service.provider.region}_${functionName}`, }); targetFunctions.push(functionName); } else { const names = context.serverless.service.getAllFunctionsNames(); for (const name of names) { const functionName = name; targetInstrumentations.push({ instrumentations: { mode: 'dev', }, resourceKey: `aws_${context.awsAccountId}_function_${context.serverless.service.provider.region}_${functionName}`, }); targetFunctions.push(functionName); } } context.targetInstrumentations = targetInstrumentations; context.consoleDevModeTargetFunctions = targetFunctions; const { allExist, total, functionCount, hits } = await allFunctionsExist(context); if (!allExist) { const foundFunctionNames = hits.map(({ aws_lambda_name: awsLambdaName }) => awsLambdaName); log.notice(); const promptLogger = functionCount === 0 ? log.error : log.warning; promptLogger( `${functionCount} of ${total} functions exist in your console integration. Deploy your service now to add these functions to your integration.\n` ); if (functionCount === 0) { context.inapplicabilityReasonCode = 'NO_FUNCTIONS_EXIST'; context.targetInstrumentations = undefined; context.consoleDevModeTargetFunctions = undefined; return false; } context.consoleDevModeTargetFunctions = foundFunctionNames; context.targetInstrumentations = context.targetInstrumentations.filter((target) => { const name = target.resourceKey.split('_').pop(); return foundFunctionNames.includes(name); }); } const { isInstrumented } = await checkInstrumentationStatus(context); if (isInstrumented) { context.inapplicabilityReasonCode = 'ALREADY_INSTRUMENTED'; } return !isInstrumented; }, async run(context) { const instrumentationProgress = progress.get(progressKey); instrumentationProgress.notice(`${headerMessage}\nThis may take a few minutes...`); // Chunk targetInstrumentations into 50 resources per request const distributeArrayBy50 = (array) => { const result = []; let index = 0; while (index < array.length) result.push(array.slice(index, (index += 50))); return result; }; const chunkedResources = distributeArrayBy50(context.targetInstrumentations); // Send requests to instrument for (const chunk of chunkedResources) { await apiRequest('/api/integrations/aws/instrumentations', { urlName: 'integrationsBackend', method: 'POST', body: { orgId: context.org.orgId, resources: chunk, }, }); } // Wait for instrumentation to complete await waitForInstrumentation(context); return true; }, };