serverless/lib/cli/interactive-setup/console-enable-dev-mode.js
2023-06-09 07:11:10 -05:00

196 lines
6.2 KiB
JavaScript

'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;
},
};