mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
feat: Console Dev Mode onboarding via dev command (#11896)
This commit is contained in:
parent
6abea2477a
commit
73d0dc6cf4
49
docs/guides/dev.md
Normal file
49
docs/guides/dev.md
Normal file
@ -0,0 +1,49 @@
|
||||
<!--
|
||||
title: Serverless Framework - Console Dev Mode
|
||||
menuText: Serverless Console Dev Mode
|
||||
menuOrder: 12
|
||||
description: Launch a Serverless Console dev mode session in the terminal
|
||||
layout: Doc
|
||||
-->
|
||||
|
||||
<!-- DOCS-SITE-LINK:START automatically generated -->
|
||||
|
||||
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/guides/dev/)
|
||||
|
||||
<!-- DOCS-SITE-LINK:END -->
|
||||
|
||||
# Serverless Console Dev Mode
|
||||
|
||||
The `serverless dev` command will launch a [Serverless Console Dev Mode](https://www.serverless.com/console/docs/application-guide/dev-mode) session in your terminal.
|
||||
|
||||
```bash
|
||||
serverless dev
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `--org` The organization that your AWS account is associated with in Serverless Console.
|
||||
- `--region` or `-r` The region in that your function was deployed to.
|
||||
- `--stage` or `-s` The stage in your service was deploy to.
|
||||
- `--function` or `-f` The name of the function that you want to focus your dev mode activity on. If this option is excluded then all function activity will be streamed to your terminal.
|
||||
- `--verbose` or `-v` If this flag is included all span input/output and lambda request/response data will be streamed to the terminal.
|
||||
|
||||
## Examples
|
||||
|
||||
### Start dev mode interactively selecting an organization
|
||||
|
||||
```bash
|
||||
serverless dev
|
||||
```
|
||||
|
||||
### Start dev mode with an org pre selected
|
||||
|
||||
```bash
|
||||
serverless dev --org myorg
|
||||
```
|
||||
|
||||
### Start dev mode with an org pre selected and all input output information logged
|
||||
|
||||
```bash
|
||||
serverless deploy function --function helloWorld --update-config
|
||||
```
|
||||
@ -30,6 +30,15 @@ commands.set('', {
|
||||
usage: 'Enable Serverless Console integration. See: http://slss.io/console',
|
||||
type: 'boolean',
|
||||
},
|
||||
'dev': {
|
||||
usage: 'Launch dev mode activity feed. See: http://slss.io/console',
|
||||
type: 'boolean',
|
||||
},
|
||||
'function': {
|
||||
usage: 'Name of the function you would like the dev mode activity feed to observe.',
|
||||
type: 'string',
|
||||
shortcut: 'f',
|
||||
},
|
||||
},
|
||||
lifecycleEvents: ['initializeService', 'setupAws', 'autoUpdate', 'end'],
|
||||
});
|
||||
|
||||
294
lib/cli/interactive-setup/console-dev-mode-feed.js
Normal file
294
lib/cli/interactive-setup/console-dev-mode-feed.js
Normal file
@ -0,0 +1,294 @@
|
||||
'use strict';
|
||||
|
||||
const { writeText, style, log, progress } = require('@serverless/utils/log');
|
||||
const { frontend } = require('@serverless/utils/lib/auth/urls');
|
||||
const colorize = require('json-colorizer');
|
||||
const WebSocket = require('ws');
|
||||
const chalk = require('chalk');
|
||||
const { devModeFeed } = require('@serverless/utils/lib/auth/urls');
|
||||
const consoleUi = require('@serverless/utils/console-ui');
|
||||
const streamBuffers = require('stream-buffers');
|
||||
const apiRequest = require('@serverless/utils/api-request');
|
||||
const promptWithHistory = require('@serverless/utils/inquirer/prompt-with-history');
|
||||
|
||||
const streamBuff = new streamBuffers.ReadableStreamBuffer({
|
||||
frequency: 500,
|
||||
chunkSize: 2048 * 1000000,
|
||||
});
|
||||
|
||||
const consoleMonitoringCounter = {
|
||||
logBatches: 0,
|
||||
events: 0,
|
||||
responses: 0,
|
||||
};
|
||||
|
||||
const jsonError = {
|
||||
colors: {
|
||||
BRACE: '#FD5750',
|
||||
BRACKET: '#FD5750',
|
||||
COLON: '#FD5750',
|
||||
COMMA: '#FD5750',
|
||||
STRING_KEY: '#FD5750',
|
||||
STRING_LITERAL: '#FD5750',
|
||||
NUMBER_LITERAL: '#FD5750',
|
||||
BOOLEAN_LITERAL: '#FD5750',
|
||||
NULL_LITERAL: '#FD5750',
|
||||
},
|
||||
};
|
||||
|
||||
const jsonColors = {
|
||||
colors: {
|
||||
BRACE: 'white',
|
||||
BRACKET: 'white',
|
||||
COLON: 'white.bold',
|
||||
COMMA: 'white',
|
||||
STRING_KEY: 'white.bold',
|
||||
STRING_LITERAL: 'white',
|
||||
NUMBER_LITERAL: 'white',
|
||||
BOOLEAN_LITERAL: 'white',
|
||||
NULL_LITERAL: 'white',
|
||||
},
|
||||
};
|
||||
const headerChalk = chalk.grey;
|
||||
const errorTracker = {};
|
||||
|
||||
const handleSocketMessage = (context) => (data) => {
|
||||
try {
|
||||
const verbose = context.options.verbose;
|
||||
const splitData = data.toString('utf-8').split(';;;');
|
||||
const jsonArray = splitData
|
||||
.filter((item) => item !== '' && item.startsWith('['))
|
||||
.flatMap((item) => JSON.parse(item));
|
||||
const sortedItems = consoleUi.omitAndSortDevModeActivity(jsonArray);
|
||||
|
||||
for (const activity of sortedItems) {
|
||||
const resourceName = ((activity.tags || {}).aws || {}).resourceName;
|
||||
const time = consoleUi.formatConsoleDate(new Date(activity.timestamp));
|
||||
|
||||
const tryPrintJSON = (str) => {
|
||||
try {
|
||||
const parsedBody = JSON.parse(str);
|
||||
if (typeof parsedBody === 'string') {
|
||||
throw new Error('Not a JSON object');
|
||||
}
|
||||
const colors = activity.severityText === 'ERROR' ? jsonError : jsonColors;
|
||||
process.stdout.write(`${colorize(JSON.stringify(parsedBody, null, 2), colors)}\n`);
|
||||
} catch (error) {
|
||||
process.stdout.write(chalk.white(`${str}${str.endsWith('\n') ? '' : '\n'}`));
|
||||
}
|
||||
};
|
||||
|
||||
switch (activity.type) {
|
||||
case 'log':
|
||||
consoleMonitoringCounter.logBatches += 1;
|
||||
process.stdout.write(headerChalk(`\n${time} • ${resourceName} • Log\n`));
|
||||
tryPrintJSON(activity.body);
|
||||
break;
|
||||
case 'span': {
|
||||
const span = consoleUi.formatConsoleSpan(activity);
|
||||
process.stdout.write(
|
||||
headerChalk(`\n${time} • ${resourceName} • Span • ${span.niceName}\n`)
|
||||
);
|
||||
if (verbose) {
|
||||
if (activity.input) {
|
||||
process.stdout.write(headerChalk('Input\n'));
|
||||
tryPrintJSON(activity.input);
|
||||
}
|
||||
if (activity.output) {
|
||||
process.stdout.write(headerChalk('Output\n'));
|
||||
tryPrintJSON(activity.output);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'aws-lambda-request':
|
||||
process.stdout.write(headerChalk(`\n${time} • ${resourceName} • Invocation Started\n`));
|
||||
if (verbose) {
|
||||
tryPrintJSON(activity.body);
|
||||
}
|
||||
break;
|
||||
case 'aws-lambda-response':
|
||||
consoleMonitoringCounter.responses += 1;
|
||||
process.stdout.write(headerChalk(`\n${time} • ${resourceName} • Invocation Ended\n`));
|
||||
if (verbose) {
|
||||
tryPrintJSON(activity.body);
|
||||
}
|
||||
if (errorTracker[activity.traceId]) {
|
||||
const uiLink = `${frontend}/${
|
||||
context.org.orgName
|
||||
}/explorer?explorerSubScope=invocations&explorerTraceId=${encodeURIComponent(
|
||||
activity.traceId
|
||||
)}&globalScope=awsLambda&globalTimeFrame=24h`;
|
||||
process.stdout.write(chalk.white(`View full trace: ${uiLink}\n`));
|
||||
delete errorTracker[activity.traceId];
|
||||
}
|
||||
break;
|
||||
case 'event': {
|
||||
consoleMonitoringCounter.events += 1;
|
||||
const { message, payload } = consoleUi.formatConsoleEvent(activity);
|
||||
const isError = /ERROR •/.test(message);
|
||||
const headerWriter = isError ? chalk.hex('#FD5750') : headerChalk;
|
||||
const options = isError ? jsonError : jsonColors;
|
||||
process.stdout.write(headerWriter(`\n${time} • ${resourceName} • ${message}\n`));
|
||||
process.stdout.write(`${colorize(JSON.stringify(payload, null, 2), options)}\n`);
|
||||
if (isError) {
|
||||
errorTracker[activity.traceId] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
process.stdout.write(error, '\n');
|
||||
}
|
||||
};
|
||||
|
||||
const connectToWebSocket = async ({ functionName, region, accountId, org, state }) => {
|
||||
const { token } = await apiRequest(`/api/identity/orgs/${org.orgId}/token`);
|
||||
const ws = new WebSocket(`${devModeFeed}?Auth=${token}`, {
|
||||
perMessageDeflate: false,
|
||||
});
|
||||
|
||||
ws.on('open', () => {
|
||||
if (state && state === 'firstConnection') {
|
||||
const functionNameQueryParams = functionName
|
||||
.map((name) => `devModeFunctionName=${encodeURIComponent(name)}`)
|
||||
.join('&');
|
||||
const uiLink = `${frontend}/${org.orgName}/dev-mode?devModeCloudAccountId=${accountId}&${functionNameQueryParams}`;
|
||||
writeText(
|
||||
style.aside(
|
||||
'\n• Use the `--verbose` flag to see inputs and outputs of all requests (e.g. DynamoDB inputs/outputs).',
|
||||
`• Use the Console Dev Mode UI for deeper inspection: ${uiLink}\n`,
|
||||
'Waiting for activity... Invoke your functions now.'
|
||||
)
|
||||
);
|
||||
} else if (state && state === 'resume') {
|
||||
writeText(style.aside('\nResuming for dev mode activity...'));
|
||||
}
|
||||
ws.send(
|
||||
JSON.stringify({ filters: { functionName, region: [region], accountId: [accountId] } })
|
||||
);
|
||||
});
|
||||
ws.on('message', (data) => {
|
||||
streamBuff.put(`${data.toString('utf-8')};;;`);
|
||||
});
|
||||
return ws;
|
||||
};
|
||||
|
||||
const startDevModeFeed = async (context, devModeFeedConnection) =>
|
||||
new Promise((resolve) => {
|
||||
const createStillWorkingTimeout = () =>
|
||||
setTimeout(async () => {
|
||||
clearInterval(eventPublishTimer);
|
||||
clearInterval(connectionRefreshTimer);
|
||||
devModeFeedConnection.terminate();
|
||||
writeText(style.aside('Pausing for dev mode activity.\n'));
|
||||
const shouldContinue = await promptWithHistory({
|
||||
name: 'shouldContinue',
|
||||
message: 'Are you still working?',
|
||||
stepHistory: context.stepHistory,
|
||||
type: 'confirm',
|
||||
});
|
||||
|
||||
if (shouldContinue) {
|
||||
await startDevModeFeed(context, 'resume');
|
||||
}
|
||||
resolve();
|
||||
}, 1000 * 60 * 60 * 1.5); // Check for activity every 1.5 hours
|
||||
|
||||
let stillWorkingTimer = createStillWorkingTimeout();
|
||||
|
||||
const connectionRefreshTimer = setInterval(async () => {
|
||||
const newConnection = await connectToWebSocket({
|
||||
functionName: context.consoleDevModeTargetFunctions,
|
||||
accountId: context.awsAccountId,
|
||||
region: context.serverless.service.provider.region,
|
||||
org: context.org,
|
||||
state: false,
|
||||
});
|
||||
const oldConnection = devModeFeedConnection;
|
||||
oldConnection.terminate();
|
||||
devModeFeedConnection = newConnection;
|
||||
watchStream(devModeFeedConnection);
|
||||
}, 1000 * 60 * 60); // Refresh every hour
|
||||
|
||||
const eventPublishTimer = setInterval(async () => {
|
||||
const { userId } = await apiRequest('/api/identity/me');
|
||||
const body = {
|
||||
source: 'web.dev_mode.activity.v1',
|
||||
event: {
|
||||
orgUid: context.org.orgId,
|
||||
userId,
|
||||
logBatches: consoleMonitoringCounter.logBatches,
|
||||
responses: consoleMonitoringCounter.responses,
|
||||
events: consoleMonitoringCounter.events,
|
||||
source: 'cli:serverless',
|
||||
},
|
||||
};
|
||||
await apiRequest('/api/events/publish', {
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
consoleMonitoringCounter.logBatches = 0;
|
||||
consoleMonitoringCounter.responses = 0;
|
||||
consoleMonitoringCounter.events = 0;
|
||||
}, 1000 * 60); // Publish every 60 seconds
|
||||
|
||||
const watchStream = (feed) => {
|
||||
feed.on('message', (data) => {
|
||||
// Ignore connection message
|
||||
const parsedData = JSON.parse(data.toString('utf-8'));
|
||||
if (!parsedData.resetThrottle) {
|
||||
clearTimeout(stillWorkingTimer);
|
||||
stillWorkingTimer = createStillWorkingTimeout();
|
||||
}
|
||||
});
|
||||
feed.on('close', () => {
|
||||
// Clean up if we receive a close event
|
||||
clearInterval(eventPublishTimer);
|
||||
clearInterval(connectionRefreshTimer);
|
||||
clearTimeout(stillWorkingTimer);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
watchStream(devModeFeedConnection);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
async isApplicable(context) {
|
||||
const { isConsoleDevMode, org, consoleDevModeTargetFunctions } = context;
|
||||
|
||||
if (!isConsoleDevMode) {
|
||||
context.inapplicabilityReasonCode = 'NON_DEV_MODE_CONTEXT';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!org) {
|
||||
context.inapplicabilityReasonCode = 'UNRESOLVED_ORG';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!consoleDevModeTargetFunctions) {
|
||||
context.inapplicabilityReasonCode = 'NO_TARGET_FUNCTIONS';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const devModeProgress = progress.get('dev-mode-progress');
|
||||
devModeProgress.remove();
|
||||
log.notice.success('Dev Mode Initialized.');
|
||||
streamBuff.on('data', handleSocketMessage(context));
|
||||
const devModeFeedConnection = await connectToWebSocket({
|
||||
functionName: context.consoleDevModeTargetFunctions,
|
||||
accountId: context.awsAccountId,
|
||||
region: context.serverless.service.provider.region,
|
||||
org: context.org,
|
||||
state: 'firstConnection',
|
||||
});
|
||||
await startDevModeFeed(context, devModeFeedConnection);
|
||||
},
|
||||
};
|
||||
191
lib/cli/interactive-setup/console-enable-dev-mode.js
Normal file
191
lib/cli/interactive-setup/console-enable-dev-mode.js
Normal file
@ -0,0 +1,191 @@
|
||||
'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 waitForInstrumentation = async (context) => {
|
||||
const instrumentationProgress = progress.get(progressKey);
|
||||
let isInstrumenting = true;
|
||||
while (isInstrumenting) {
|
||||
const { isInstrumented: done, total, instrumented } = await checkInstrumentationStatus(context);
|
||||
instrumentationProgress.update(`Instrumenting ${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('Instrumenting functions', 'This 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;
|
||||
},
|
||||
};
|
||||
@ -30,6 +30,8 @@ module.exports = {
|
||||
return false;
|
||||
}
|
||||
|
||||
showOnboardingWelcome(context);
|
||||
|
||||
if (await resolveAuthMode()) {
|
||||
context.inapplicabilityReasonCode = 'ALREADY_LOGGED_IN';
|
||||
return false;
|
||||
@ -38,8 +40,6 @@ module.exports = {
|
||||
return true;
|
||||
},
|
||||
async run(context) {
|
||||
showOnboardingWelcome(context);
|
||||
|
||||
return steps.loginOrRegister(context);
|
||||
},
|
||||
steps,
|
||||
|
||||
@ -4,11 +4,10 @@ const { log } = require('@serverless/utils/log');
|
||||
const resolveAuthMode = require('@serverless/utils/auth/resolve-mode');
|
||||
const apiRequest = require('@serverless/utils/api-request');
|
||||
const promptWithHistory = require('@serverless/utils/inquirer/prompt-with-history');
|
||||
const { showOnboardingWelcome } = require('./utils');
|
||||
|
||||
const orgsChoice = async (orgs, stepHistory) =>
|
||||
promptWithHistory({
|
||||
message: 'What org do you want to add this service to?',
|
||||
message: 'What org do you want to use this service with?',
|
||||
type: 'list',
|
||||
name: 'orgName',
|
||||
choices: [
|
||||
@ -45,9 +44,6 @@ module.exports = {
|
||||
return false;
|
||||
}
|
||||
|
||||
log.notice();
|
||||
showOnboardingWelcome(context);
|
||||
|
||||
if (orgName) {
|
||||
const org = orgs.find((someOrg) => someOrg.orgName === orgName);
|
||||
if (org) {
|
||||
|
||||
@ -54,7 +54,7 @@ const waitUntilIntegrationIsReady = async (context) => {
|
||||
|
||||
module.exports = {
|
||||
async isApplicable(context) {
|
||||
const { isConsole } = context;
|
||||
const { isConsole, isConsoleDevMode } = context;
|
||||
|
||||
if (!isConsole) {
|
||||
context.inapplicabilityReasonCode = 'NON_CONSOLE_CONTEXT';
|
||||
@ -91,7 +91,9 @@ module.exports = {
|
||||
}
|
||||
|
||||
log.notice();
|
||||
log.notice.success('Your AWS account is integrated with Serverless Console');
|
||||
if (!isConsoleDevMode) {
|
||||
log.notice.success('Your AWS account is integrated with Serverless Console');
|
||||
}
|
||||
context.inapplicabilityReasonCode = 'INTEGRATED';
|
||||
return false;
|
||||
}
|
||||
@ -100,10 +102,13 @@ module.exports = {
|
||||
await awsRequest(context, cloudFormationServiceConfig, 'describeStacks', {
|
||||
StackName: iamRoleStackName,
|
||||
});
|
||||
log.warning(
|
||||
log.error(
|
||||
'Cannot integrate with Serverless Console: ' +
|
||||
'AWS account is already integrated with another org'
|
||||
'AWS account is already integrated with another org. ' +
|
||||
'You can set the AWS_PROFILE environment variable to use a different AWS Profile.'
|
||||
);
|
||||
context.isConsole = false;
|
||||
context.isConsoleDevMode = false;
|
||||
context.inapplicabilityReasonCode = 'AWS_ACCOUNT_ALREADY_INTEGRATED';
|
||||
return false;
|
||||
} catch (error) {
|
||||
@ -114,19 +119,20 @@ module.exports = {
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const { stepHistory } = context;
|
||||
const { stepHistory, isConsoleDevMode } = context;
|
||||
|
||||
if (
|
||||
!(await promptWithHistory({
|
||||
message: `Press [Enter] to enable Serverless Console's next-generation monitoring.\n\n${style.aside(
|
||||
[
|
||||
'This will create an IAM Role in your AWS account with the following permissions:',
|
||||
'- Subscribe to CloudWatch logs and metrics',
|
||||
'- Update Lambda layers and env vars to add tracing and real-time logging',
|
||||
'- Read resource info for security alerts',
|
||||
'• Subscribe to CloudWatch logs and metrics',
|
||||
'• Update Lambda layers and env vars to add tracing and real-time logging',
|
||||
'• Read resource info for security alerts',
|
||||
`See the IAM Permissions transparently here: ${style.link(
|
||||
'https://slss.io/iam-role-permissions'
|
||||
)}`,
|
||||
'Would you like to proceed?',
|
||||
]
|
||||
)}`,
|
||||
type: 'confirm',
|
||||
@ -168,7 +174,9 @@ module.exports = {
|
||||
|
||||
await waitUntilIntegrationIsReady(context);
|
||||
|
||||
log.notice.success('Your AWS account is integrated with Serverless Console');
|
||||
if (!isConsoleDevMode) {
|
||||
log.notice.success('Your AWS account is integrated with Serverless Console');
|
||||
}
|
||||
} finally {
|
||||
integrationSetupProgress.remove();
|
||||
}
|
||||
|
||||
@ -26,9 +26,9 @@ const steps = {
|
||||
|
||||
module.exports = {
|
||||
async isApplicable(context) {
|
||||
const { configuration, options, serviceDir } = context;
|
||||
const { isDashboard, configuration, options, serviceDir } = context;
|
||||
|
||||
if (options.console) {
|
||||
if (!isDashboard) {
|
||||
context.inapplicabilityReasonCode = 'CONSOLE_CONTEXT';
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -193,15 +193,15 @@ const steps = {
|
||||
|
||||
module.exports = {
|
||||
async isApplicable(context) {
|
||||
const { configuration, options, serviceDir } = context;
|
||||
const { isDashboard, configuration, options, serviceDir } = context;
|
||||
|
||||
if (!serviceDir) {
|
||||
context.inapplicabilityReasonCode = 'NOT_IN_SERVICE_DIRECTORY';
|
||||
if (!isDashboard) {
|
||||
context.inapplicabilityReasonCode = 'CONSOLE_CONTEXT';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.console) {
|
||||
context.inapplicabilityReasonCode = 'CONSOLE_CONTEXT';
|
||||
if (!serviceDir) {
|
||||
context.inapplicabilityReasonCode = 'NOT_IN_SERVICE_DIRECTORY';
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -243,6 +243,7 @@ module.exports = {
|
||||
context.inapplicabilityReasonCode = 'NO_ORGS_AVAILABLE';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!usesServerlessAccessKey) {
|
||||
user = configUtils.getLoggedInUser(); // Refreshed, as new token might have been generated
|
||||
}
|
||||
|
||||
@ -23,13 +23,14 @@ const printMessage = () => {
|
||||
|
||||
module.exports = {
|
||||
async isApplicable(context) {
|
||||
const { configuration, serviceDir, options, initial } = context;
|
||||
const { isConsole, isOnboarding, configuration, serviceDir, options, initial } = context;
|
||||
|
||||
if (!serviceDir) {
|
||||
context.inapplicabilityReasonCode = 'NOT_IN_SERVICE_DIRECTORY';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.console && initial.isInServiceContext) {
|
||||
if ((isConsole || !isOnboarding) && initial.isInServiceContext) {
|
||||
context.inapplicabilityReasonCode = 'CONSOLE_INTEGRATION';
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const apiRequest = require('@serverless/utils/api-request');
|
||||
const inquirer = require('@serverless/utils/inquirer');
|
||||
const ServerlessError = require('@serverless/utils/serverless-error');
|
||||
const { StepHistory } = require('@serverless/utils/telemetry');
|
||||
const log = require('@serverless/utils/log').log.get('onboarding');
|
||||
const { resolveInitialContext } = require('./utils');
|
||||
@ -15,6 +17,8 @@ const steps = {
|
||||
dashboardSetOrg: require('./dashboard-set-org'),
|
||||
awsCredentials: require('./aws-credentials'),
|
||||
deploy: require('./deploy'),
|
||||
consoleEnableDevMode: require('./console-enable-dev-mode'),
|
||||
consoleDevModeFeed: require('./console-dev-mode-feed'),
|
||||
};
|
||||
|
||||
const resolveAwsAccountId = async (context) => {
|
||||
@ -51,8 +55,9 @@ module.exports = async (context) => {
|
||||
commandUsage.initialContext = initialContext;
|
||||
context.initial = initialContext;
|
||||
context.awsAccountId = await resolveAwsAccountId(context);
|
||||
|
||||
if (options.console) {
|
||||
context.isOnboarding = !options.dev;
|
||||
context.isDashboard = !options.console && !options.dev;
|
||||
if (options.console || (options.dev && initialContext.isInServiceContext)) {
|
||||
if (!context.awsAccountId) {
|
||||
log.error(
|
||||
'We’re unable to connect Console via the CLI - No local AWS credentials found\n' +
|
||||
@ -63,6 +68,26 @@ module.exports = async (context) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (context.isConsole && options.dev && initialContext.isInServiceContext) {
|
||||
const compatibilityMap = await apiRequest('/api/inventories/compatibility', {
|
||||
method: 'GET',
|
||||
noAuth: true,
|
||||
});
|
||||
const devModeRuntimeCompatibility = compatibilityMap.mode.dev.runtimes;
|
||||
const { provider } = context.serverless.service;
|
||||
if (!devModeRuntimeCompatibility.includes(provider.runtime)) {
|
||||
log.error('This services runtime is not currently supported by Serverless Console Dev Mode.');
|
||||
context.isConsole = false;
|
||||
} else {
|
||||
context.isConsoleDevMode = true;
|
||||
}
|
||||
} else if (options.dev && !initialContext.isInServiceContext) {
|
||||
throw new ServerlessError(
|
||||
'Cannot launch dev mode when not in a service context.',
|
||||
'NOT_APPLICABLE_DEV_MODE_CONTEXT'
|
||||
);
|
||||
}
|
||||
|
||||
for (const [stepName, step] of Object.entries(steps)) {
|
||||
delete context.stepHistory;
|
||||
delete context.inapplicabilityReasonCode;
|
||||
|
||||
@ -114,7 +114,9 @@ module.exports = {
|
||||
},
|
||||
showOnboardingWelcome: memoizee(
|
||||
(context) => {
|
||||
if (context.isConsole) {
|
||||
if (context.isConsoleDevMode) {
|
||||
log.notice('Initializing Dev Mode via Serverless Console...');
|
||||
} else if (context.isConsole) {
|
||||
log.notice("Enabling Serverless Console's next-generation monitoring.");
|
||||
log.notice(style.aside('Learn more at https://serverless.com/console'));
|
||||
} else {
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"is-docker": "^2.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-colorizer": "^2.2.2",
|
||||
"json-cycle": "^1.3.0",
|
||||
"json-refs": "^3.0.15",
|
||||
"lodash": "^4.17.21",
|
||||
@ -67,6 +68,7 @@
|
||||
"require-from-string": "^2.0.2",
|
||||
"semver": "^7.3.8",
|
||||
"signal-exit": "^3.0.7",
|
||||
"stream-buffers": "^3.0.2",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"supports-color": "^8.1.1",
|
||||
"tar": "^6.1.13",
|
||||
@ -74,6 +76,7 @@
|
||||
"type": "^2.7.2",
|
||||
"untildify": "^4.0.0",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^7.5.9",
|
||||
"yaml-ast-parser": "0.0.43"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -105,7 +108,6 @@
|
||||
"sinon": "^13.0.2",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"ws": "^7.5.9",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
@ -135,11 +135,18 @@ processSpanPromise = (async () => {
|
||||
|
||||
(() => {
|
||||
// Rewrite eventual `sls deploy -f` into `sls deploy function -f`
|
||||
// Also rewrite `serverless dev` to `serverless --dev``
|
||||
const isParamName = RegExp.prototype.test.bind(require('../lib/cli/param-reg-exp'));
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const firstParamIndex = args.findIndex(isParamName);
|
||||
const commands = args.slice(0, firstParamIndex === -1 ? Infinity : firstParamIndex);
|
||||
|
||||
if (commands.join('') === 'dev') {
|
||||
process.argv[2] = '--dev';
|
||||
return;
|
||||
}
|
||||
|
||||
if (commands.join(' ') !== 'deploy') return;
|
||||
if (!args.includes('-f') && !args.includes('--function')) return;
|
||||
logDeprecation(
|
||||
|
||||
@ -0,0 +1,381 @@
|
||||
'use strict';
|
||||
|
||||
const chai = require('chai');
|
||||
const WebSocket = require('ws');
|
||||
const sinon = require('sinon');
|
||||
const sleep = require('timers-ext/promise/sleep');
|
||||
const consoleUi = require('@serverless/utils/console-ui');
|
||||
const proxyquire = require('proxyquire').noPreserveCache();
|
||||
|
||||
const { expect } = chai;
|
||||
chai.use(require('chai-as-promised'));
|
||||
|
||||
let step;
|
||||
const originalSetInterval = setInterval;
|
||||
describe('test/unit/lib/cli/interactive-setup/console-dev-mode-feed.test.js', function () {
|
||||
this.timeout(1000 * 60 * 3);
|
||||
const fakeOrgId = '123';
|
||||
const fakeAWSAccountId = 'account1';
|
||||
const publishFake = sinon.fake();
|
||||
const fakeRegion = 'us-east-1';
|
||||
const fakeTime = 'fakeTime';
|
||||
const consoleDevModeTargetFunctions = ['function1'];
|
||||
|
||||
const fakeGreyWriter = sinon.fake.returns('');
|
||||
const fakeJSONWriter = sinon.fake.returns('');
|
||||
const fakeErrorWriter = sinon.fake.returns('');
|
||||
let socketConnection;
|
||||
let socketServer;
|
||||
let timers = [];
|
||||
|
||||
before(() => {
|
||||
step = proxyquire('../../../../../lib/cli/interactive-setup/console-dev-mode-feed', {
|
||||
'@serverless/utils/api-request': async (pathname, options) => {
|
||||
if (pathname === `/api/identity/orgs/${fakeOrgId}/token`) {
|
||||
return { token: 'fakeToken' };
|
||||
}
|
||||
if (pathname === '/api/identity/me') {
|
||||
return { userId: 'user123' };
|
||||
}
|
||||
if (pathname === '/api/events/publish') {
|
||||
publishFake(options);
|
||||
return { success: true };
|
||||
}
|
||||
throw new Error(`Unexpected pathname "${pathname}"`);
|
||||
},
|
||||
'@serverless/utils/console-ui': {
|
||||
omitAndSortDevModeActivity: consoleUi.omitAndSortDevModeActivity,
|
||||
formatConsoleDate: () => fakeTime,
|
||||
formatConsoleSpan: (span) => ({
|
||||
niceName: span.name,
|
||||
}),
|
||||
formatConsoleEvent: (event) => ({
|
||||
message: /\.error\./.test(event.eventName) ? 'ERROR • fake' : 'WARNING • fake',
|
||||
payload: /\.error\./.test(event.eventName) ? event.tags.error : event.tags.warning,
|
||||
}),
|
||||
},
|
||||
'@serverless/utils/lib/auth/urls': {
|
||||
devModeFeed: 'ws://localhost:9988',
|
||||
},
|
||||
'chalk': {
|
||||
white: fakeGreyWriter,
|
||||
grey: fakeGreyWriter,
|
||||
hex: () => fakeErrorWriter,
|
||||
},
|
||||
'json-colorizer': fakeJSONWriter,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
timers = [];
|
||||
// eslint-disable-next-line no-global-assign
|
||||
setInterval = (cb) => {
|
||||
timers.push(cb);
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (socketConnection) {
|
||||
socketConnection.terminate();
|
||||
}
|
||||
if (socketServer) {
|
||||
socketServer.close();
|
||||
}
|
||||
// eslint-disable-next-line no-global-assign
|
||||
setInterval = originalSetInterval;
|
||||
});
|
||||
|
||||
it('Should be ineffective, when not in console dev mode context', async () => {
|
||||
const context = { isConsoleDevMode: false, options: {} };
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NON_DEV_MODE_CONTEXT');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when no org is selected', async () => {
|
||||
const context = { isConsoleDevMode: true, options: {}, org: null };
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('UNRESOLVED_ORG');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when functions are targeted', async () => {
|
||||
const context = { isConsoleDevMode: true, options: {}, org: { orgId: fakeOrgId } };
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NO_TARGET_FUNCTIONS');
|
||||
});
|
||||
|
||||
it('Should be effective and connect to websocket', async () => {
|
||||
const context = {
|
||||
isConsoleDevMode: true,
|
||||
options: {
|
||||
verbose: true,
|
||||
},
|
||||
org: { orgId: fakeOrgId },
|
||||
consoleDevModeTargetFunctions,
|
||||
awsAccountId: fakeAWSAccountId,
|
||||
serverless: {
|
||||
service: {
|
||||
provider: fakeRegion,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.be.true;
|
||||
|
||||
const waitForConnection = () =>
|
||||
new Promise((resolve) => {
|
||||
socketServer = new WebSocket.Server({ port: 9988 });
|
||||
step.run(context);
|
||||
socketServer.on('connection', (ws) => {
|
||||
ws.on('message', () => {
|
||||
ws.send(
|
||||
JSON.stringify({ message: 'filters successfully applied', resetThrottle: true })
|
||||
);
|
||||
});
|
||||
resolve(ws);
|
||||
});
|
||||
});
|
||||
socketConnection = await waitForConnection();
|
||||
|
||||
/**
|
||||
* Set of messages containing 👇
|
||||
*
|
||||
* 1. request
|
||||
* 2. JSON log
|
||||
* 3. text log
|
||||
* 4. JSON parsable text log
|
||||
* 5. s3 span
|
||||
* 6. Warning event
|
||||
* 7. Error event
|
||||
* 8. response
|
||||
*
|
||||
* It also included the aws.lambda* spans that should be ignored :)
|
||||
*/
|
||||
const mockMessages = [
|
||||
[
|
||||
{
|
||||
body: '{"key1":"value1","key2":"value2","key3":"value3"}',
|
||||
timestamp: '2023-03-20T21:26:10.790Z',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
type: 'aws-lambda-request',
|
||||
sequenceId: 1679347571057,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'aws.lambda.initialization',
|
||||
timestamp: '2023-03-20T21:26:10.365Z',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
type: 'span',
|
||||
sequenceId: 1679347571276,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
body: '{"message":"Hi dev mode 👋"}\n',
|
||||
severityNumber: '1',
|
||||
severityText: 'INFO',
|
||||
timestamp: '2023-03-20T21:26:10.802Z',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
type: 'log',
|
||||
sequenceId: 1679344258090,
|
||||
},
|
||||
{
|
||||
body: 'text log\n',
|
||||
severityNumber: '1',
|
||||
severityText: 'INFO',
|
||||
timestamp: '2023-03-20T21:26:10.802Z',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
type: 'log',
|
||||
sequenceId: 1679344258091,
|
||||
},
|
||||
{
|
||||
body: '"hello"',
|
||||
severityNumber: '1',
|
||||
severityText: 'INFO',
|
||||
timestamp: '2023-03-20T21:26:10.802Z',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
type: 'log',
|
||||
sequenceId: 1679344258091,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
customTags: '{}',
|
||||
input: '{"Bucket":"fake-bucket"}',
|
||||
name: 'aws.sdk.s3.listobjectsv2',
|
||||
output: '{"message": "s3 output"}',
|
||||
timestamp: '2023-03-20T21:26:10.804Z',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
type: 'span',
|
||||
sequenceId: 1679347571306,
|
||||
},
|
||||
{
|
||||
customTags: '{"foo":"bar"}',
|
||||
eventName: 'telemetry.warning.generated.v1',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
warning: {
|
||||
message: 'This is a warning',
|
||||
stacktrace:
|
||||
'at module.exports.handler (/var/task/index.js:12:7)\nat process.processTicksAndRejections (node:internal/process/task_queues:95:5)',
|
||||
type: 'WARNING_TYPE_USER',
|
||||
},
|
||||
},
|
||||
timestamp: '2023-03-20T21:26:10.916Z',
|
||||
type: 'event',
|
||||
sequenceId: 1679347571307,
|
||||
},
|
||||
{
|
||||
customTags: '{"foo":"bar"}',
|
||||
eventName: 'telemetry.error.generated.v1',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
error: {
|
||||
message: 'Oh no!',
|
||||
name: 'Error',
|
||||
stacktrace:
|
||||
'at module.exports.handler (/var/task/index.js:13:20)\nat process.processTicksAndRejections (node:internal/process/task_queues:95:5)',
|
||||
type: 'ERROR_TYPE_CAUGHT_USER',
|
||||
},
|
||||
},
|
||||
timestamp: '2023-03-20T21:26:10.924Z',
|
||||
type: 'event',
|
||||
sequenceId: 1679347571308,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
customTags: '{}',
|
||||
name: 'aws.lambda.invocation',
|
||||
timestamp: '2023-03-20T21:26:10.790Z',
|
||||
type: 'span',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
sequenceId: 1679347572067,
|
||||
},
|
||||
{
|
||||
customTags: '{}',
|
||||
isHistorical: false,
|
||||
name: 'aws.lambda',
|
||||
timestamp: '2023-03-20T21:26:10.365Z',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
type: 'span',
|
||||
sequenceId: 1679347572068,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
body: '{"response":"hello there"}',
|
||||
timestamp: '2023-03-20T21:26:11.934Z',
|
||||
tags: {
|
||||
aws: {
|
||||
resourceName: 'example-dev-function1',
|
||||
},
|
||||
},
|
||||
type: 'aws-lambda-response',
|
||||
sequenceId: 1679347572127,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
// Send all messages
|
||||
for (const message of mockMessages) {
|
||||
socketConnection.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
// Wait for all messages to be processed
|
||||
await sleep(600);
|
||||
|
||||
// Publish dev mode events
|
||||
await timers[1]();
|
||||
|
||||
// Close connection to socket
|
||||
socketConnection.terminate();
|
||||
|
||||
// Assert that each message had a header and our text log was written
|
||||
expect(fakeGreyWriter.callCount).to.equal(12);
|
||||
expect(fakeGreyWriter.getCall(0).args[0]).to.equal(
|
||||
`\n${fakeTime} • example-dev-function1 • Invocation Started\n`
|
||||
);
|
||||
// Plain text log message
|
||||
expect(fakeGreyWriter.getCall(3).args[0]).to.equal('text log\n');
|
||||
// Empty text log message
|
||||
expect(fakeGreyWriter.getCall(5).args[0]).to.equal('"hello"\n');
|
||||
expect(fakeGreyWriter.getCall(6).args[0]).to.equal(
|
||||
`\n${fakeTime} • example-dev-function1 • Span • aws.sdk.s3.listobjectsv2\n`
|
||||
);
|
||||
// Check end message is last
|
||||
expect(fakeGreyWriter.getCall(10).args[0]).to.equal(
|
||||
`\n${fakeTime} • example-dev-function1 • Invocation Ended\n`
|
||||
);
|
||||
|
||||
// Assert that our first log message was processed as JSON and both the warning and error event were printed to the console
|
||||
expect(fakeJSONWriter.callCount).to.equal(7);
|
||||
expect(fakeJSONWriter.getCall(0).args[0]).to.equal(
|
||||
`${JSON.stringify(JSON.parse(mockMessages[0][0].body), null, 2)}`
|
||||
);
|
||||
expect(fakeJSONWriter.getCall(1).args[0]).to.equal(
|
||||
`${JSON.stringify(JSON.parse(mockMessages[2][0].body), null, 2)}`
|
||||
);
|
||||
expect(fakeJSONWriter.getCall(2).args[0]).to.equal(
|
||||
`${JSON.stringify(JSON.parse(mockMessages[3][0].input), null, 2)}`
|
||||
);
|
||||
expect(fakeJSONWriter.getCall(3).args[0]).to.equal(
|
||||
`${JSON.stringify(JSON.parse(mockMessages[3][0].output), null, 2)}`
|
||||
);
|
||||
expect(fakeJSONWriter.getCall(4).args[0]).to.equal(
|
||||
`${JSON.stringify(mockMessages[3][1].tags.warning, null, 2)}`
|
||||
);
|
||||
expect(fakeJSONWriter.getCall(5).args[0]).to.equal(
|
||||
`${JSON.stringify(mockMessages[3][2].tags.error, null, 2)}`
|
||||
);
|
||||
expect(fakeJSONWriter.getCall(5).args[1].colors.BRACE).to.equal('#FD5750');
|
||||
|
||||
// Assert that the error event was printed with the error
|
||||
expect(fakeErrorWriter.callCount).to.equal(1);
|
||||
expect(fakeErrorWriter.getCall(0).args[0]).to.equal(
|
||||
`\n${fakeTime} • example-dev-function1 • ERROR • fake\n`
|
||||
);
|
||||
|
||||
// Validate publish event was called
|
||||
expect(publishFake.callCount).to.equal(1);
|
||||
expect(publishFake.getCall(0).args[0].body.event.logBatches).to.equal(3);
|
||||
expect(publishFake.getCall(0).args[0].body.event.responses).to.equal(1);
|
||||
expect(publishFake.getCall(0).args[0].body.event.events).to.equal(2);
|
||||
expect(publishFake.getCall(0).args[0].body.event.source).to.equal('cli:serverless');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,298 @@
|
||||
'use strict';
|
||||
|
||||
const chai = require('chai');
|
||||
const proxyquire = require('proxyquire').noPreserveCache();
|
||||
|
||||
const { expect } = chai;
|
||||
chai.use(require('chai-as-promised'));
|
||||
|
||||
let step;
|
||||
describe('test/unit/lib/cli/interactive-setup/console-enable-dev-mode.test.js', () => {
|
||||
let fakeOrgId;
|
||||
let expectedFunctionCount;
|
||||
let fakeRegion;
|
||||
let expectedFunctionHits;
|
||||
let expectedServiceFunctionNames;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeOrgId = '123';
|
||||
expectedFunctionHits = [
|
||||
{
|
||||
aws_lambda_name: 'function1',
|
||||
},
|
||||
];
|
||||
expectedServiceFunctionNames = expectedFunctionHits.map((hit) => hit.aws_lambda_name);
|
||||
expectedFunctionCount = expectedFunctionHits.length;
|
||||
fakeRegion = 'us-east-1';
|
||||
});
|
||||
|
||||
const configureStep = ({
|
||||
functionExistResponse,
|
||||
checkInstrumentationResponse,
|
||||
instrumentFunctionResponse = { success: true },
|
||||
}) => {
|
||||
step = proxyquire('../../../../../lib/cli/interactive-setup/console-enable-dev-mode', {
|
||||
'@serverless/utils/api-request': async (pathname, options) => {
|
||||
if (
|
||||
pathname === `/api/search/orgs/${fakeOrgId}/search` &&
|
||||
options.body.query.bool.must.length === 3
|
||||
) {
|
||||
return functionExistResponse;
|
||||
}
|
||||
if (
|
||||
pathname === `/api/search/orgs/${fakeOrgId}/search` &&
|
||||
options.body.query.bool.must.length === 4
|
||||
) {
|
||||
return checkInstrumentationResponse;
|
||||
}
|
||||
if (pathname === '/api/integrations/aws/instrumentations') {
|
||||
if (options.body.resources.length > 50) {
|
||||
throw new Error('Too many resources to instrument');
|
||||
}
|
||||
return instrumentFunctionResponse;
|
||||
}
|
||||
throw new Error(`Unexpected pathname "${pathname}"`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('Should be ineffective, when not in console dev mode context', async () => {
|
||||
configureStep({
|
||||
functionExistResponse: {},
|
||||
checkInstrumentationResponse: {},
|
||||
});
|
||||
const context = { isConsoleDevMode: false, options: {} };
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NON_DEV_MODE_CONTEXT');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when no org is selected', async () => {
|
||||
configureStep({
|
||||
functionExistResponse: {},
|
||||
checkInstrumentationResponse: {},
|
||||
});
|
||||
const context = { isConsoleDevMode: true, options: {}, org: null };
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('UNRESOLVED_ORG');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when functions are already instrumented', async () => {
|
||||
configureStep({
|
||||
functionExistResponse: {
|
||||
total: expectedFunctionCount,
|
||||
hits: expectedFunctionHits,
|
||||
},
|
||||
checkInstrumentationResponse: {
|
||||
total: expectedFunctionCount,
|
||||
hits: expectedFunctionHits,
|
||||
},
|
||||
});
|
||||
|
||||
const context = {
|
||||
isConsoleDevMode: true,
|
||||
options: {},
|
||||
org: {
|
||||
orgId: fakeOrgId,
|
||||
},
|
||||
serverless: {
|
||||
service: {
|
||||
provider: {
|
||||
region: fakeRegion,
|
||||
},
|
||||
setFunctionNames: () => {},
|
||||
getAllFunctionsNames: () => expectedServiceFunctionNames,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('ALREADY_INSTRUMENTED');
|
||||
expect(context.targetInstrumentations.length).to.equal(1);
|
||||
expect(context.consoleDevModeTargetFunctions.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('Should be ineffective and cancel, when only one function exists and it is not included in the integration', async () => {
|
||||
configureStep({
|
||||
functionExistResponse: {
|
||||
total: 0,
|
||||
hits: [],
|
||||
},
|
||||
checkInstrumentationResponse: {
|
||||
total: 0,
|
||||
hits: [],
|
||||
},
|
||||
});
|
||||
|
||||
const context = {
|
||||
isConsoleDevMode: true,
|
||||
options: {},
|
||||
org: {
|
||||
orgId: fakeOrgId,
|
||||
},
|
||||
serverless: {
|
||||
service: {
|
||||
provider: {
|
||||
region: fakeRegion,
|
||||
},
|
||||
setFunctionNames: () => {},
|
||||
getAllFunctionsNames: () => expectedServiceFunctionNames,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NO_FUNCTIONS_EXIST');
|
||||
expect(context.targetInstrumentations).to.be.undefined;
|
||||
expect(context.consoleDevModeTargetFunctions).to.be.undefined;
|
||||
});
|
||||
|
||||
it('Should be effective and only update functions that were found in the integration', async () => {
|
||||
// Add a function that is not in the integration to the serverless service
|
||||
expectedServiceFunctionNames.push('function2');
|
||||
// Set up the expected responses from the API
|
||||
const functionExistResponse = {
|
||||
total: expectedFunctionCount,
|
||||
hits: expectedFunctionHits,
|
||||
};
|
||||
const checkInstrumentationResponse = {
|
||||
total: 0,
|
||||
hits: [],
|
||||
};
|
||||
configureStep({
|
||||
functionExistResponse,
|
||||
checkInstrumentationResponse,
|
||||
});
|
||||
|
||||
const context = {
|
||||
isConsoleDevMode: true,
|
||||
options: {},
|
||||
org: {
|
||||
orgId: fakeOrgId,
|
||||
},
|
||||
serverless: {
|
||||
service: {
|
||||
provider: {
|
||||
region: fakeRegion,
|
||||
},
|
||||
setFunctionNames: () => {},
|
||||
getAllFunctionsNames: () => expectedServiceFunctionNames,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.be.true;
|
||||
expect(context.targetInstrumentations.length).to.equal(1);
|
||||
expect(context.consoleDevModeTargetFunctions.length).to.equal(1);
|
||||
|
||||
// Re-proxyquire step so we can update the response to the checkInstrumentation call
|
||||
configureStep({
|
||||
functionExistResponse,
|
||||
checkInstrumentationResponse: {
|
||||
total: expectedFunctionCount,
|
||||
hits: expectedFunctionHits,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await step.run(context)).to.be.true;
|
||||
});
|
||||
|
||||
it('Should be effective and only target function from -f option', async () => {
|
||||
const functionExistResponse = {
|
||||
total: expectedFunctionCount,
|
||||
hits: expectedFunctionHits,
|
||||
};
|
||||
const checkInstrumentationResponse = {
|
||||
total: 0,
|
||||
hits: [],
|
||||
};
|
||||
configureStep({
|
||||
functionExistResponse,
|
||||
checkInstrumentationResponse,
|
||||
});
|
||||
|
||||
const context = {
|
||||
isConsoleDevMode: true,
|
||||
options: {
|
||||
function: expectedServiceFunctionNames[0],
|
||||
},
|
||||
org: {
|
||||
orgId: fakeOrgId,
|
||||
},
|
||||
serverless: {
|
||||
service: {
|
||||
provider: {
|
||||
region: fakeRegion,
|
||||
},
|
||||
setFunctionNames: () => {},
|
||||
getFunction: (name) => ({
|
||||
name,
|
||||
}),
|
||||
getAllFunctionsNames: () => [],
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.be.true;
|
||||
expect(context.targetInstrumentations.length).to.equal(1);
|
||||
expect(context.consoleDevModeTargetFunctions.length).to.equal(1);
|
||||
|
||||
// Re-proxyquire step so we can update the response to the checkInstrumentation call
|
||||
configureStep({
|
||||
functionExistResponse,
|
||||
checkInstrumentationResponse: {
|
||||
total: expectedFunctionCount,
|
||||
hits: expectedFunctionHits,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await step.run(context)).to.be.true;
|
||||
});
|
||||
|
||||
it('Should be effective and update 50 functions at a time', async () => {
|
||||
expectedFunctionHits = new Array(100)
|
||||
.fill(0)
|
||||
.map((_, i) => ({ aws_lambda_name: `function${i + 1}` }));
|
||||
expectedFunctionCount = expectedFunctionHits.length;
|
||||
expectedServiceFunctionNames = expectedFunctionHits.map((hit) => hit.aws_lambda_name);
|
||||
const functionExistResponse = {
|
||||
total: expectedFunctionCount,
|
||||
hits: expectedFunctionHits,
|
||||
};
|
||||
const checkInstrumentationResponse = {
|
||||
total: 0,
|
||||
hits: [],
|
||||
};
|
||||
configureStep({
|
||||
functionExistResponse,
|
||||
checkInstrumentationResponse,
|
||||
});
|
||||
|
||||
const context = {
|
||||
isConsoleDevMode: true,
|
||||
options: {},
|
||||
org: {
|
||||
orgId: fakeOrgId,
|
||||
},
|
||||
serverless: {
|
||||
service: {
|
||||
provider: {
|
||||
region: fakeRegion,
|
||||
},
|
||||
setFunctionNames: () => {},
|
||||
getFunction: () => ({}),
|
||||
getAllFunctionsNames: () => expectedServiceFunctionNames,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.be.true;
|
||||
expect(context.targetInstrumentations.length).to.equal(expectedFunctionCount);
|
||||
expect(context.consoleDevModeTargetFunctions.length).to.equal(expectedFunctionCount);
|
||||
|
||||
// Re-proxyquire step so we can update the response to the checkInstrumentation call
|
||||
configureStep({
|
||||
functionExistResponse,
|
||||
checkInstrumentationResponse: {
|
||||
total: expectedFunctionCount,
|
||||
hits: expectedFunctionHits,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await step.run(context)).to.be.true;
|
||||
});
|
||||
});
|
||||
@ -53,18 +53,25 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function
|
||||
loginStub.resetHistory();
|
||||
});
|
||||
|
||||
it('Should be ineffective in console context', async () => {
|
||||
const context = { isConsole: true, options: { console: true } };
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('CONSOLE_CONTEXT');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when not at service path', async () => {
|
||||
const context = { options: {} };
|
||||
const context = { options: {}, isDashboard: true };
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NOT_IN_SERVICE_DIRECTORY');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when not in dashboard context', async () => {
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
inquirer,
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('CONSOLE_CONTEXT');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when not at AWS service path', async () => {
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
@ -72,6 +79,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
@ -85,6 +93,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
@ -101,6 +110,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
};
|
||||
expect(await overrideCwd(serviceDir, async () => await step.isApplicable(context))).to.equal(
|
||||
@ -125,6 +135,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -148,6 +159,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { org: 'someorg' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -168,6 +180,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -191,6 +204,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
|
||||
@ -116,21 +116,23 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
it('Should be ineffective, when not at service path', async () => {
|
||||
const context = {
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NOT_IN_SERVICE_DIRECTORY');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when in console context', async () => {
|
||||
it('Should be ineffective, when not in dashboard context', async () => {
|
||||
const context = {
|
||||
initial: {},
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { console: true },
|
||||
isConsole: true,
|
||||
options: {},
|
||||
isDashboard: false,
|
||||
isConsole: false,
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.be.false;
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('CONSOLE_CONTEXT');
|
||||
});
|
||||
|
||||
@ -141,6 +143,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
@ -154,6 +157,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
@ -170,6 +174,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
@ -208,6 +213,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
};
|
||||
await overrideCwd(serviceDir, async () => {
|
||||
@ -226,6 +232,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
};
|
||||
await overrideCwd(serviceDir, async () => {
|
||||
@ -251,6 +258,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -283,6 +291,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -309,6 +318,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -336,6 +346,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -373,6 +384,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -411,6 +423,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -444,6 +457,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -489,6 +503,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -520,6 +535,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -551,6 +567,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map([['dashboardLogin', []]]),
|
||||
stepHistory: new StepHistory(),
|
||||
@ -580,6 +597,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map([
|
||||
['dashboardLogin', []],
|
||||
@ -615,6 +633,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { org: 'testinteractivecli', app: 'other-app' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
@ -648,6 +667,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { org: 'otherorg', app: 'app-from-flag' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
@ -684,6 +704,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { org: 'otherorg', app: 'app-from-flag' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
@ -714,6 +735,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { org: 'invalid-testinteractivecli', app: 'irrelevant' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
@ -755,6 +777,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { org: 'invalid-testinteractivecli', app: 'irrelevant' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
@ -796,6 +819,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { org: 'invalid-testinteractivecli', app: 'irrelevant' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
@ -834,6 +858,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
configurationFilename: 'serverless.yml',
|
||||
options: { org: 'testinteractivecli', app: 'invalid' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
inquirer,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
@ -872,6 +897,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
inquirer,
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -914,6 +940,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
inquirer,
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -954,6 +981,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
inquirer,
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -988,6 +1016,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
inquirer,
|
||||
options: { app: 'app-from-flag' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -1019,6 +1048,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
inquirer,
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -1052,6 +1082,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
inquirer,
|
||||
options: { app: 'invalid-app-from-flag' },
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -1095,6 +1126,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
inquirer,
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
@ -1136,6 +1168,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi
|
||||
inquirer,
|
||||
options: {},
|
||||
initial: {},
|
||||
isDashboard: true,
|
||||
history: new Map(),
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
|
||||
@ -29,12 +29,40 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
configuration: { provider: { name: 'notaws' } },
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
isOnboarding: true,
|
||||
history: new Map([['service', []]]),
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NON_AWS_PROVIDER');
|
||||
});
|
||||
|
||||
it('Should be not applied, when service is not in onboarding context', async () => {
|
||||
const context = {
|
||||
configuration: { provider: { name: 'aws' } },
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
isOnboarding: false,
|
||||
history: new Map([['awsCredentials', []]]),
|
||||
initial: { isInServiceContext: true },
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('CONSOLE_INTEGRATION');
|
||||
});
|
||||
|
||||
it('Should be not applied, when in console context', async () => {
|
||||
const context = {
|
||||
configuration: { provider: { name: 'aws' } },
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
isOnboarding: true,
|
||||
isConsole: true,
|
||||
history: new Map([['awsCredentials', []]]),
|
||||
initial: { isInServiceContext: true },
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('CONSOLE_INTEGRATION');
|
||||
});
|
||||
|
||||
it('Should be applied if user configured local credentials', async () => {
|
||||
await overrideEnv(
|
||||
{ variables: { AWS_ACCESS_KEY_ID: 'somekey', AWS_SECRET_ACCESS_KEY: 'somesecret' } },
|
||||
@ -44,6 +72,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
configuration: { provider: { name: 'aws' } },
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
isOnboarding: true,
|
||||
history: new Map([['awsCredentials', []]]),
|
||||
})
|
||||
).to.equal(true);
|
||||
@ -64,6 +93,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
configuration: { provider: { name: 'aws' }, org: 'someorg', app: 'someapp' },
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
isOnboarding: true,
|
||||
history: new Map([['awsCredentials', []]]),
|
||||
})
|
||||
).to.equal(true);
|
||||
@ -87,6 +117,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
},
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
isOnboarding: true,
|
||||
history: new Map([['awsCredentials', []]]),
|
||||
})
|
||||
).to.equal(true);
|
||||
@ -110,6 +141,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
initial: {
|
||||
isInServiceContext: false,
|
||||
},
|
||||
isOnboarding: true,
|
||||
};
|
||||
await step.run(context);
|
||||
|
||||
@ -133,6 +165,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
initial: {
|
||||
isInServiceContext: true,
|
||||
},
|
||||
isOnboarding: true,
|
||||
};
|
||||
await step.run(context);
|
||||
|
||||
@ -158,6 +191,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
initial: {
|
||||
isInServiceContext: false,
|
||||
},
|
||||
isOnboarding: true,
|
||||
};
|
||||
await step.run(context);
|
||||
|
||||
@ -183,6 +217,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
initial: {
|
||||
isInServiceContext: true,
|
||||
},
|
||||
isOnboarding: true,
|
||||
};
|
||||
await step.run(context);
|
||||
|
||||
@ -236,6 +271,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
initial: {
|
||||
isInServiceContext: false,
|
||||
},
|
||||
isOnboarding: true,
|
||||
};
|
||||
await mockedStep.run(context);
|
||||
|
||||
@ -289,6 +325,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
initial: {
|
||||
isInServiceContext: true,
|
||||
},
|
||||
isOnboarding: true,
|
||||
};
|
||||
await mockedStep.run(context);
|
||||
|
||||
@ -336,6 +373,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
initial: {
|
||||
isInServiceContext: false,
|
||||
},
|
||||
isOnboarding: true,
|
||||
};
|
||||
await mockedStep.run(context);
|
||||
|
||||
@ -383,6 +421,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
initial: {
|
||||
isInServiceContext: true,
|
||||
},
|
||||
isOnboarding: true,
|
||||
};
|
||||
await mockedStep.run(context);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user