mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
* feat: Remove Console Login * fix: Update login tests to remove console login options * feat: Remove Console login options * feat: Remove console onboarding * fix: Remove console tests * chore: Remove remaining console flags * fix: Remove console deploy unit test * fix: Interactive Setup tests * fix: Update generate-payload tests
854 lines
34 KiB
JavaScript
Executable File
854 lines
34 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
'use strict';
|
|
|
|
require('essentials');
|
|
|
|
// global graceful-fs patch
|
|
// https://github.com/isaacs/node-graceful-fs#global-patching
|
|
require('graceful-fs').gracefulify(require('fs'));
|
|
|
|
// Setup log writing
|
|
require('@serverless/utils/log-reporters/node');
|
|
const { log, progress, isInteractive: isInteractiveTerminal } = require('@serverless/utils/log');
|
|
|
|
const processLog = log.get('process');
|
|
|
|
const handleError = require('../lib/cli/handle-error');
|
|
const {
|
|
storeLocally: storeTelemetryLocally,
|
|
send: sendTelemetry,
|
|
} = require('../lib/utils/telemetry');
|
|
const generateTelemetryPayload = require('../lib/utils/telemetry/generate-payload');
|
|
const isTelemetryDisabled = require('../lib/utils/telemetry/are-disabled');
|
|
const logDeprecation = require('../lib/utils/log-deprecation');
|
|
|
|
let command;
|
|
let isHelpRequest;
|
|
let options;
|
|
let commandSchema;
|
|
let serviceDir = null;
|
|
let configuration = null;
|
|
let serverless;
|
|
let dockerVersion;
|
|
const commandUsage = {};
|
|
const variableSourcesInConfig = new Set();
|
|
|
|
// Inquirer async operations do not keep node process alive
|
|
// We need to issue a keep alive timer so process does not die
|
|
// to properly handle e.g. `SIGINT` interrupt
|
|
const keepAliveTimer = setTimeout(() => {}, 60 * 60 * 1000);
|
|
|
|
const trueWithProbability = (probability) => Math.random() < probability;
|
|
|
|
let processSpanPromise;
|
|
let hasBeenFinalized = false;
|
|
const finalize = async ({ error, shouldBeSync, telemetryData, shouldSendTelemetry } = {}) => {
|
|
processLog.debug('finalize %o', { error, shouldBeSync, telemetryData, shouldSendTelemetry });
|
|
if (hasBeenFinalized) {
|
|
if (error) {
|
|
// Programmer error in finalize handling, ensure to expose
|
|
process.nextTick(() => {
|
|
throw error;
|
|
});
|
|
}
|
|
return null;
|
|
}
|
|
hasBeenFinalized = true;
|
|
clearTimeout(keepAliveTimer);
|
|
progress.clear();
|
|
if (error) ({ telemetryData } = await handleError(error, { serverless }));
|
|
if (!shouldBeSync) {
|
|
await logDeprecation.printSummary();
|
|
}
|
|
if (isTelemetryDisabled || !commandSchema) return null;
|
|
if (!error && isHelpRequest) return null;
|
|
storeTelemetryLocally({
|
|
...generateTelemetryPayload({
|
|
command,
|
|
options,
|
|
commandSchema,
|
|
serviceDir,
|
|
configuration,
|
|
serverless,
|
|
commandUsage,
|
|
variableSources: variableSourcesInConfig,
|
|
dockerVersion,
|
|
}),
|
|
...telemetryData,
|
|
});
|
|
|
|
// We want to explicitly ensure that when processing should be sync, we never attempt sending telemetry data
|
|
if (shouldBeSync) return null;
|
|
|
|
// We want to send telemetry at least roughly every 20 commands (in addition to sending on deploy and on errors)
|
|
// to avoid situations where we have very big batches of telemetry events that cannot be processed on the backend side
|
|
const shouldForceTelemetry = trueWithProbability(0.05);
|
|
|
|
if (!error && !shouldSendTelemetry && !shouldForceTelemetry) return null;
|
|
return sendTelemetry({ serverlessExecutionSpan: processSpanPromise });
|
|
};
|
|
|
|
process.once('uncaughtException', (error) => {
|
|
log.error('Uncaught exception');
|
|
finalize({ error }).then(() => process.exit());
|
|
});
|
|
|
|
processSpanPromise = (async () => {
|
|
try {
|
|
const wait = require('timers-ext/promise/sleep');
|
|
await wait(); // Ensure access to "processSpanPromise"
|
|
|
|
require('signal-exit/signals').forEach((signal) => {
|
|
process.once(signal, () => {
|
|
processLog.debug('exit signal %s', signal);
|
|
// If there's another listener (e.g. we're in daemon context or reading stdin input)
|
|
// then let the other listener decide how process will exit
|
|
const isOtherSigintListener = Boolean(process.listenerCount(signal));
|
|
finalize({
|
|
shouldBeSync: true,
|
|
telemetryData: { outcome: 'interrupt', interruptSignal: signal },
|
|
});
|
|
if (isOtherSigintListener) return;
|
|
// Follow recommendation from signal-exit:
|
|
// https://github.com/tapjs/signal-exit/blob/654117d6c9035ff6a805db4d4acf1f0c820fcb21/index.js#L97-L98
|
|
if (process.platform === 'win32' && signal === 'SIGHUP') signal = 'SIGINT';
|
|
process.kill(process.pid, signal);
|
|
});
|
|
});
|
|
|
|
const humanizePropertyPathKeys = require('../lib/configuration/variables/humanize-property-path-keys');
|
|
const processBackendNotificationRequest = require('../lib/utils/process-backend-notification-request');
|
|
|
|
(() => {
|
|
// 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(
|
|
'CLI_DEPLOY_FUNCTION_OPTION_V3',
|
|
'Starting with v4.0.0, `--function` or `-f` option for `deploy` command will no longer be supported. In order to deploy a single function, please use `deploy function` command instead.'
|
|
);
|
|
process.argv.splice(3, 0, 'function');
|
|
})();
|
|
|
|
const resolveInput = require('../lib/cli/resolve-input');
|
|
|
|
let commands;
|
|
processLog.debug('resolve CLI input (no service schema)');
|
|
// Parse args against schemas of commands which do not require to be run in service context
|
|
({ command, commands, options, isHelpRequest, commandSchema } = resolveInput(
|
|
require('../lib/cli/commands-schema/no-service')
|
|
));
|
|
|
|
// If version number request, show it and abort
|
|
if (options.version) {
|
|
processLog.debug('render version');
|
|
await require('../lib/cli/render-version')();
|
|
await finalize();
|
|
return;
|
|
}
|
|
|
|
const ServerlessError = require('../lib/serverless-error');
|
|
|
|
// Abort if command is not supported in this environment
|
|
if (commandSchema && commandSchema.isHidden && commandSchema.noSupportNotice) {
|
|
throw new ServerlessError(
|
|
`Cannot run \`${command}\` command: ${commandSchema.noSupportNotice}`,
|
|
'NOT_SUPPORTED_COMMAND'
|
|
);
|
|
}
|
|
|
|
const path = require('path');
|
|
const uuid = require('uuid');
|
|
const _ = require('lodash');
|
|
const clear = require('ext/object/clear');
|
|
const Serverless = require('../lib/serverless');
|
|
const resolveVariables = require('../lib/configuration/variables/resolve');
|
|
const isPropertyResolved = require('../lib/configuration/variables/is-property-resolved');
|
|
const eventuallyReportVariableResolutionErrors = require('../lib/configuration/variables/eventually-report-resolution-errors');
|
|
const filterSupportedOptions = require('../lib/cli/filter-supported-options');
|
|
const isDashboardEnabled = require('../lib/configuration/is-dashboard-enabled');
|
|
|
|
let configurationPath = null;
|
|
let providerName;
|
|
let variablesMeta;
|
|
let resolverConfiguration;
|
|
let isInteractiveSetup;
|
|
|
|
const ensureResolvedProperty = (propertyPath) => {
|
|
if (isPropertyResolved(variablesMeta, propertyPath)) return true;
|
|
variablesMeta = null;
|
|
if (isHelpRequest) return false;
|
|
const humanizedPropertyPath = humanizePropertyPathKeys(propertyPath.split('\0'));
|
|
throw new ServerlessError(
|
|
`Cannot resolve ${path.basename(
|
|
configurationPath
|
|
)}: "${humanizedPropertyPath}" property is not accessible ` +
|
|
'(configured behind variables which cannot be resolved at this stage)',
|
|
'INACCESSIBLE_CONFIGURATION_PROPERTY'
|
|
);
|
|
};
|
|
|
|
if (!commandSchema || commandSchema.serviceDependencyMode) {
|
|
// Command is potentially service specific, follow up with resolution of service config
|
|
|
|
// Parse args again, taking account schema of service-specific flags
|
|
// as they may influence configuration resolution
|
|
processLog.debug('resolve CLI input (service schema)');
|
|
resolveInput.clear();
|
|
({ command, commands, options, isHelpRequest, commandSchema } = resolveInput(
|
|
require('../lib/cli/commands-schema/service')
|
|
));
|
|
|
|
isInteractiveSetup = !isHelpRequest && command === '';
|
|
|
|
processLog.debug('resolve eventual service configuration');
|
|
const resolveConfigurationPath = require('../lib/cli/resolve-configuration-path');
|
|
const readConfiguration = require('../lib/configuration/read');
|
|
const resolveProviderName = require('../lib/configuration/resolve-provider-name');
|
|
|
|
// Resolve eventual service configuration path
|
|
configurationPath = await resolveConfigurationPath();
|
|
if (configurationPath) {
|
|
processLog.debug('service configuration found at %s', configurationPath);
|
|
} else processLog.debug('no service configuration found');
|
|
|
|
// If service configuration file is found, load its content
|
|
configuration = configurationPath
|
|
? await (async () => {
|
|
try {
|
|
return await readConfiguration(configurationPath);
|
|
} catch (error) {
|
|
// Configuration syntax error should not prevent help from being displayed
|
|
// (if possible configuration should be read for help request as registered
|
|
// plugins may introduce new commands to be listed in help output)
|
|
if (isHelpRequest) return null;
|
|
throw error;
|
|
}
|
|
})()
|
|
: null;
|
|
|
|
if (configuration) {
|
|
processLog.debug('service configuration file successfully parsed');
|
|
serviceDir = process.cwd();
|
|
|
|
// IIFE for maintenance convenience
|
|
await (async () => {
|
|
// We do not need to attempt resolution of further variables for login command as
|
|
// the only variables from configuration that we potentially rely on is `app` and `org`
|
|
// TODO: Remove when dashboard/console login prompt won't be needed - when that happens
|
|
// login command should once again be service independent
|
|
if (command === 'login') return;
|
|
|
|
processLog.debug('resolve variables meta');
|
|
const resolveVariablesMeta = require('../lib/configuration/variables/resolve-meta');
|
|
|
|
variablesMeta = resolveVariablesMeta(configuration);
|
|
|
|
if (
|
|
eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
)
|
|
) {
|
|
// Variable syntax errors, abort
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
|
|
if (!ensureResolvedProperty('disabledDeprecations')) return;
|
|
if (!ensureResolvedProperty('deprecationNotificationMode')) return;
|
|
|
|
if (isPropertyResolved(variablesMeta, 'provider\0name')) {
|
|
providerName = resolveProviderName(configuration);
|
|
if (providerName == null) {
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
}
|
|
if (!commandSchema && providerName === 'aws') {
|
|
// If command was not recognized in previous resolution phases
|
|
// parse args again also against schemas commands which require AWS service context
|
|
processLog.debug('resolve CLI input (AWS service schema)');
|
|
resolveInput.clear();
|
|
({ command, commands, options, isHelpRequest, commandSchema } = resolveInput(
|
|
require('../lib/cli/commands-schema/aws-service')
|
|
));
|
|
}
|
|
|
|
let envVarNamesNeededForDotenvResolution;
|
|
if (variablesMeta.size) {
|
|
processLog.debug('resolve variables in core properties');
|
|
// Some properties are configured with variables
|
|
|
|
// Resolve eventual variables in `provider.stage` and `useDotEnv`
|
|
// (required for reliable .env resolution)
|
|
resolverConfiguration = {
|
|
serviceDir,
|
|
configuration,
|
|
variablesMeta,
|
|
sources: {
|
|
env: require('../lib/configuration/variables/sources/env'),
|
|
file: require('../lib/configuration/variables/sources/file'),
|
|
opt: require('../lib/configuration/variables/sources/opt'),
|
|
self: require('../lib/configuration/variables/sources/self'),
|
|
strToBool: require('../lib/configuration/variables/sources/str-to-bool'),
|
|
sls: require('../lib/configuration/variables/sources/instance-dependent/get-sls')(),
|
|
},
|
|
options: filterSupportedOptions(options, { commandSchema, providerName }),
|
|
fulfilledSources: new Set(['file', 'self', 'strToBool']),
|
|
propertyPathsToResolve: new Set(['provider\0name', 'provider\0stage', 'useDotenv']),
|
|
variableSourcesInConfig,
|
|
};
|
|
|
|
if (isInteractiveSetup) resolverConfiguration.fulfilledSources.add('opt');
|
|
await resolveVariables(resolverConfiguration);
|
|
|
|
if (
|
|
eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
)
|
|
) {
|
|
// Unrecoverable resolution errors, abort
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
|
|
if (!providerName && isPropertyResolved(variablesMeta, 'provider\0name')) {
|
|
providerName = resolveProviderName(configuration);
|
|
if (providerName == null) {
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
if (!commandSchema && providerName === 'aws') {
|
|
// If command was not recognized in previous resolution phases
|
|
// Parse args again also against schemas of commands which work in context of an AWS
|
|
// service
|
|
processLog.debug('resolve CLI input (AWS service schema)');
|
|
resolveInput.clear();
|
|
({ command, commands, options, isHelpRequest, commandSchema } = resolveInput(
|
|
require('../lib/cli/commands-schema/aws-service')
|
|
));
|
|
|
|
if (commandSchema) {
|
|
processLog.debug('resolve variables in core properties #2');
|
|
resolverConfiguration.options = filterSupportedOptions(options, {
|
|
commandSchema,
|
|
providerName,
|
|
});
|
|
await resolveVariables(resolverConfiguration);
|
|
if (
|
|
eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
)
|
|
) {
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
resolverConfiguration.fulfilledSources.add('env');
|
|
if (
|
|
!isPropertyResolved(variablesMeta, 'provider\0stage') ||
|
|
!isPropertyResolved(variablesMeta, 'useDotenv')
|
|
) {
|
|
// Assume "env" source fulfilled for `provider.stage` and `useDotenv` resolution.
|
|
// To pick eventual resolution conflict, track what env variables were reported
|
|
// missing when applying this resolution
|
|
processLog.debug('resolve variables in stage related properties');
|
|
const envSource = require('../lib/configuration/variables/sources/env');
|
|
envSource.missingEnvVariables.clear();
|
|
await resolveVariables({
|
|
...resolverConfiguration,
|
|
propertyPathsToResolve: new Set(['provider\0stage', 'useDotenv']),
|
|
});
|
|
if (
|
|
eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
)
|
|
) {
|
|
// Unrecoverable resolution errors, abort
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
|
|
if (
|
|
!ensureResolvedProperty('provider\0stage', {
|
|
shouldSilentlyReturnIfLegacyMode: true,
|
|
})
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (!ensureResolvedProperty('useDotenv')) return;
|
|
|
|
envVarNamesNeededForDotenvResolution = envSource.missingEnvVariables;
|
|
}
|
|
}
|
|
|
|
// Load eventual environment variables from .env files
|
|
if (await require('../lib/cli/conditionally-load-dotenv')(options, configuration)) {
|
|
if (envVarNamesNeededForDotenvResolution) {
|
|
for (const envVarName of envVarNamesNeededForDotenvResolution) {
|
|
if (process.env[envVarName]) {
|
|
throw new ServerlessError(
|
|
'Cannot reliably resolve "env" variables due to resolution conflict.\n' +
|
|
`Environment variable "${envVarName}" which influences resolution of ` +
|
|
'".env" file were found to be defined in resolved ".env" file.' +
|
|
'DOTENV_ENV_VAR_RESOLUTION_CONFLICT'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (!isPropertyResolved(variablesMeta, 'provider\0name')) {
|
|
processLog.debug('resolve variables in "provider.name"');
|
|
await resolveVariables(resolverConfiguration);
|
|
if (
|
|
eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
)
|
|
) {
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!variablesMeta.size) return; // No properties configured with variables
|
|
|
|
if (!providerName) {
|
|
if (!ensureResolvedProperty('provider\0name')) return;
|
|
providerName = resolveProviderName(configuration);
|
|
if (providerName == null) {
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
if (!commandSchema && providerName === 'aws') {
|
|
processLog.debug('resolve CLI input (AWS service schema)');
|
|
resolveInput.clear();
|
|
({ command, commands, options, isHelpRequest, commandSchema } = resolveInput(
|
|
require('../lib/cli/commands-schema/aws-service')
|
|
));
|
|
if (commandSchema) {
|
|
resolverConfiguration.options = filterSupportedOptions(options, {
|
|
commandSchema,
|
|
providerName,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (isHelpRequest || commands[0] === 'plugin') {
|
|
processLog.debug('resolve variables in "plugins"');
|
|
// We do not need full config resolved, we just need to know what
|
|
// provider is service setup with, and with what eventual plugins Framework is extended
|
|
// as that influences what CLI commands and options could be used,
|
|
resolverConfiguration.propertyPathsToResolve.add('plugins');
|
|
} else {
|
|
processLog.debug('resolve variables in all properties');
|
|
delete resolverConfiguration.propertyPathsToResolve;
|
|
}
|
|
|
|
await resolveVariables(resolverConfiguration);
|
|
if (
|
|
eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
)
|
|
) {
|
|
variablesMeta = null;
|
|
return;
|
|
}
|
|
|
|
if (!variablesMeta.size) return; // All properties successfully resolved
|
|
|
|
if (!ensureResolvedProperty('plugins')) return;
|
|
|
|
// At this point we have all properties needed for `plugin install/uninstall` commands
|
|
if (commands[0] === 'plugin') {
|
|
return;
|
|
}
|
|
|
|
if (!ensureResolvedProperty('package\0path')) return;
|
|
|
|
if (!ensureResolvedProperty('frameworkVersion')) return;
|
|
if (!ensureResolvedProperty('app')) return;
|
|
if (!ensureResolvedProperty('org')) return;
|
|
if (!ensureResolvedProperty('dashboard')) return;
|
|
if (!ensureResolvedProperty('service')) return;
|
|
if (isDashboardEnabled({ configuration, options })) {
|
|
// Dashboard requires AWS region to be resolved upfront
|
|
ensureResolvedProperty('provider\0region');
|
|
}
|
|
})();
|
|
|
|
// Ensure to have full AWS commands schema loaded if we're in context of AWS provider
|
|
// It's not the case if not AWS service specific command was resolved
|
|
if (configuration && resolveProviderName(configuration) === 'aws') {
|
|
processLog.debug('resolve CLI input (AWS service schema)');
|
|
resolveInput.clear();
|
|
({ command, commands, options, isHelpRequest, commandSchema } = resolveInput(
|
|
require('../lib/cli/commands-schema/aws-service')
|
|
));
|
|
}
|
|
} else {
|
|
// In non-service context we recognize all AWS service commands
|
|
processLog.debug('parsing of configuration file failed');
|
|
processLog.debug('resolve CLI input (AWS service schema)');
|
|
resolveInput.clear();
|
|
({ command, commands, options, isHelpRequest, commandSchema } = resolveInput(
|
|
require('../lib/cli/commands-schema/aws-service')
|
|
));
|
|
|
|
// Validate result command and options
|
|
require('../lib/cli/ensure-supported-command')();
|
|
}
|
|
} else {
|
|
require('../lib/cli/ensure-supported-command')();
|
|
}
|
|
|
|
const configurationFilename = configuration && configurationPath.slice(serviceDir.length + 1);
|
|
|
|
// Names of the commands which are configured independently in root `commands` folder
|
|
// and not in Serverless class internals
|
|
const notIntegratedCommands = new Set([
|
|
'doctor',
|
|
'login',
|
|
'logout',
|
|
'plugin install',
|
|
'plugin uninstall',
|
|
]);
|
|
const isStandaloneCommand = notIntegratedCommands.has(command);
|
|
|
|
if (!isHelpRequest) {
|
|
if (isStandaloneCommand) {
|
|
processLog.debug('run standalone command');
|
|
if (configuration) require('../lib/cli/ensure-supported-command')(configuration);
|
|
await require(`../commands/${commands.join('-')}`)({
|
|
configuration,
|
|
serviceDir,
|
|
configurationFilename,
|
|
options,
|
|
});
|
|
await finalize({ telemetryData: { outcome: 'success' } });
|
|
return;
|
|
} else if (isInteractiveSetup) {
|
|
if (!isInteractiveTerminal) {
|
|
throw new ServerlessError(
|
|
'Attempted to run an interactive setup in non TTY environment.\n' +
|
|
"If that's intended, run with the SLS_INTERACTIVE_SETUP_ENABLE=1 environment variable",
|
|
'INTERACTIVE_SETUP_IN_NON_TTY'
|
|
);
|
|
}
|
|
if (!configuration) {
|
|
processLog.debug('run interactive onboarding');
|
|
const interactiveContext = await require('../lib/cli/interactive-setup')({
|
|
configuration,
|
|
serviceDir,
|
|
configurationFilename,
|
|
options,
|
|
commandUsage,
|
|
});
|
|
if (interactiveContext.configuration) {
|
|
configuration = interactiveContext.configuration;
|
|
}
|
|
if (interactiveContext.serverless) {
|
|
serverless = interactiveContext.serverless;
|
|
}
|
|
await finalize({ telemetryData: { outcome: 'success' }, shouldSendTelemetry: true });
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
processLog.debug('construct Serverless instance');
|
|
serverless = new Serverless({
|
|
configuration,
|
|
serviceDir,
|
|
configurationFilename,
|
|
commands,
|
|
options,
|
|
variablesMeta,
|
|
});
|
|
|
|
try {
|
|
serverless.onExitPromise = processSpanPromise;
|
|
serverless.invocationId = uuid.v4();
|
|
processLog.debug('initialize Serverless instance');
|
|
await serverless.init();
|
|
|
|
// IIFE for maintenance convenience
|
|
await (async () => {
|
|
if (!configuration) return;
|
|
|
|
let hasFinalCommandSchema = false;
|
|
if (configuration.plugins) {
|
|
// After plugins are loaded, re-resolve CLI command and options schema as plugin
|
|
// might have defined extra commands and options
|
|
|
|
if (serverless.pluginManager.externalPlugins.size) {
|
|
processLog.debug('resolve CLI input (+ plugins schema)');
|
|
const commandsSchema = require('../lib/cli/commands-schema/resolve-final')(
|
|
serverless.pluginManager.externalPlugins,
|
|
{ providerName: providerName || 'aws', configuration }
|
|
);
|
|
resolveInput.clear();
|
|
({ command, commands, options, isHelpRequest, commandSchema } =
|
|
resolveInput(commandsSchema));
|
|
serverless.processedInput.commands = serverless.pluginManager.cliCommands = commands;
|
|
serverless.processedInput.options = options;
|
|
Object.assign(clear(serverless.pluginManager.cliOptions), options);
|
|
hasFinalCommandSchema = true;
|
|
}
|
|
}
|
|
if (!providerName && !hasFinalCommandSchema) {
|
|
// Invalid configuration, ensure to recognize all AWS commands
|
|
processLog.debug('resolve CLI input (AWS service schema)');
|
|
resolveInput.clear();
|
|
({ command, commands, options, isHelpRequest, commandSchema } = resolveInput(
|
|
require('../lib/cli/commands-schema/aws-service')
|
|
));
|
|
}
|
|
hasFinalCommandSchema = true;
|
|
|
|
// Validate result command and options
|
|
if (hasFinalCommandSchema) require('../lib/cli/ensure-supported-command')(configuration);
|
|
if (isHelpRequest) return;
|
|
if (!_.get(variablesMeta, 'size')) return;
|
|
if (!resolverConfiguration) {
|
|
// There were no variables in the initial configuration, yet it was extended by
|
|
// the plugins with ones.
|
|
// In this case we need to ensure `resolverConfiguration` which initially was not setup
|
|
resolverConfiguration = {
|
|
serviceDir,
|
|
configuration,
|
|
variablesMeta,
|
|
sources: {
|
|
env: require('../lib/configuration/variables/sources/env'),
|
|
file: require('../lib/configuration/variables/sources/file'),
|
|
opt: require('../lib/configuration/variables/sources/opt'),
|
|
self: require('../lib/configuration/variables/sources/self'),
|
|
strToBool: require('../lib/configuration/variables/sources/str-to-bool'),
|
|
sls: require('../lib/configuration/variables/sources/instance-dependent/get-sls')(),
|
|
},
|
|
options: filterSupportedOptions(options, { commandSchema, providerName }),
|
|
fulfilledSources: new Set(['env', 'file', 'self', 'strToBool']),
|
|
propertyPathsToResolve:
|
|
commands[0] === 'plugin'
|
|
? new Set(['plugins', 'provider\0name', 'provider\0stage', 'useDotenv'])
|
|
: null,
|
|
variableSourcesInConfig,
|
|
};
|
|
}
|
|
|
|
if (commandSchema) {
|
|
resolverConfiguration.options = filterSupportedOptions(options, {
|
|
commandSchema,
|
|
providerName,
|
|
});
|
|
}
|
|
resolverConfiguration.fulfilledSources.add('opt');
|
|
|
|
// Register serverless instance specific variable sources
|
|
resolverConfiguration.sources.sls =
|
|
require('../lib/configuration/variables/sources/instance-dependent/get-sls')(serverless);
|
|
resolverConfiguration.fulfilledSources.add('sls');
|
|
|
|
resolverConfiguration.sources.param =
|
|
serverless.pluginManager.dashboardPlugin.configurationVariablesSources.param;
|
|
resolverConfiguration.fulfilledSources.add('param');
|
|
|
|
// Register dashboard specific variable source resolvers
|
|
if (isDashboardEnabled({ configuration, options })) {
|
|
for (const [sourceName, sourceConfig] of Object.entries(
|
|
serverless.pluginManager.dashboardPlugin.configurationVariablesSources
|
|
)) {
|
|
if (sourceName === 'param') continue;
|
|
resolverConfiguration.sources[sourceName] = sourceConfig;
|
|
resolverConfiguration.fulfilledSources.add(sourceName);
|
|
}
|
|
}
|
|
|
|
// Register AWS provider specific variable sources
|
|
if (providerName === 'aws') {
|
|
// Pre-resolve to eventually pick not yet resolved AWS auth related properties
|
|
processLog.debug('resolve variables');
|
|
await resolveVariables(resolverConfiguration);
|
|
if (!variablesMeta.size) return;
|
|
if (
|
|
eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Ensure properties which are crucial to some variable source resolvers
|
|
// are actually resolved.
|
|
if (
|
|
!ensureResolvedProperty('provider\0credentials') ||
|
|
!ensureResolvedProperty('provider\0deploymentBucket\0serverSideEncryption') ||
|
|
!ensureResolvedProperty('provider\0profile') ||
|
|
!ensureResolvedProperty('provider\0region')
|
|
) {
|
|
return;
|
|
}
|
|
Object.assign(resolverConfiguration.sources, {
|
|
cf: require('../lib/configuration/variables/sources/instance-dependent/get-cf')(
|
|
serverless
|
|
),
|
|
s3: require('../lib/configuration/variables/sources/instance-dependent/get-s3')(
|
|
serverless
|
|
),
|
|
ssm: require('../lib/configuration/variables/sources/instance-dependent/get-ssm')(
|
|
serverless
|
|
),
|
|
aws: require('../lib/configuration/variables/sources/instance-dependent/get-aws')(
|
|
serverless
|
|
),
|
|
});
|
|
resolverConfiguration.fulfilledSources.add('cf').add('s3').add('ssm').add('aws');
|
|
}
|
|
|
|
// Register variable source resolvers provided by external plugins
|
|
const resolverExternalPluginSources = require('../lib/configuration/variables/sources/resolve-external-plugin-sources');
|
|
resolverExternalPluginSources(
|
|
configuration,
|
|
resolverConfiguration,
|
|
serverless.pluginManager.externalPlugins
|
|
);
|
|
|
|
// Having all source resolvers configured, resolve variables
|
|
processLog.debug('resolve all variables');
|
|
await resolveVariables(resolverConfiguration);
|
|
if (!variablesMeta.size) return;
|
|
if (
|
|
eventuallyReportVariableResolutionErrors(configurationPath, configuration, variablesMeta)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Do not confirm on unresolved sources with partially resolved configuration
|
|
if (resolverConfiguration.propertyPathsToResolve) return;
|
|
|
|
processLog.debug('uresolved variables meta: %o', variablesMeta);
|
|
// Report unrecognized variable sources found in variables configured in service config
|
|
const unresolvedSources =
|
|
require('../lib/configuration/variables/resolve-unresolved-source-types')(variablesMeta);
|
|
const recognizedSourceNames = new Set(Object.keys(resolverConfiguration.sources));
|
|
|
|
const unrecognizedSourceNames = Array.from(unresolvedSources.keys()).filter(
|
|
(sourceName) => !recognizedSourceNames.has(sourceName)
|
|
);
|
|
|
|
if (unrecognizedSourceNames.includes('output')) {
|
|
throw new ServerlessError(
|
|
'"Cannot resolve configuration: ' +
|
|
'"output" variable can only be used in ' +
|
|
'services deployed with Serverless Dashboard (with "org" setting configured)',
|
|
'DASHBOARD_VARIABLE_SOURCES_MISUSE'
|
|
);
|
|
}
|
|
throw new ServerlessError(
|
|
`Unrecognized configuration variable sources: "${unrecognizedSourceNames.join('", "')}"`,
|
|
'UNRECOGNIZED_VARIABLE_SOURCES'
|
|
);
|
|
})();
|
|
|
|
if (isHelpRequest && serverless.pluginManager.externalPlugins) {
|
|
// Show help
|
|
processLog.debug('render help');
|
|
require('../lib/cli/render-help')(serverless.pluginManager.externalPlugins);
|
|
} else if (isInteractiveSetup) {
|
|
processLog.debug('run interactive onboarding');
|
|
const interactiveContext = await require('../lib/cli/interactive-setup')({
|
|
configuration,
|
|
serverless,
|
|
serviceDir,
|
|
configurationFilename,
|
|
options,
|
|
commandUsage,
|
|
});
|
|
if (interactiveContext.configuration) {
|
|
configuration = interactiveContext.configuration;
|
|
}
|
|
if (interactiveContext.serverless) {
|
|
serverless = interactiveContext.serverless;
|
|
}
|
|
} else {
|
|
if (commands.join(' ') === 'deploy') {
|
|
const spawn = require('child-process-ext/spawn');
|
|
spawn('docker', ['--version']).then(
|
|
({ stdoutBuffer }) => {
|
|
dockerVersion = null;
|
|
const matcher = String(stdoutBuffer).match(/(?<version>\d+\.\d+\.\d+),/);
|
|
if (!matcher) return;
|
|
dockerVersion = matcher.groups.version;
|
|
},
|
|
() => (dockerVersion = null)
|
|
);
|
|
}
|
|
|
|
processLog.debug('run Serverless instance');
|
|
// Run command
|
|
await serverless.run();
|
|
}
|
|
|
|
const backendNotificationRequest = await finalize({
|
|
telemetryData: { outcome: 'success' },
|
|
shouldSendTelemetry: isInteractiveSetup || commands.join(' ') === 'deploy',
|
|
});
|
|
if (!isInteractiveSetup && backendNotificationRequest) {
|
|
await processBackendNotificationRequest(backendNotificationRequest);
|
|
}
|
|
} catch (error) {
|
|
processLog.debug('handle error');
|
|
// If Dashboard Plugin, capture error
|
|
const dashboardPlugin = serverless.pluginManager.dashboardPlugin;
|
|
const dashboardErrorHandler = _.get(dashboardPlugin, 'enterprise.errorHandler');
|
|
if (!dashboardErrorHandler) throw error;
|
|
try {
|
|
await dashboardErrorHandler(error, serverless.invocationId);
|
|
} catch (dashboardErrorHandlerError) {
|
|
const tokenizeException = require('../lib/utils/tokenize-exception');
|
|
const exceptionTokens = tokenizeException(dashboardErrorHandlerError);
|
|
log.warning(
|
|
`Publication to Serverless Dashboard errored with:\n${' '.repeat('Serverless: '.length)}${
|
|
exceptionTokens.isUserError || !exceptionTokens.stack
|
|
? exceptionTokens.message
|
|
: exceptionTokens.stack
|
|
}`
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
} catch (error) {
|
|
await finalize({ error });
|
|
}
|
|
})();
|