2024-05-29 11:51:04 -04:00

165 lines
4.5 KiB
JavaScript

import path from 'path'
import validate from './lib/validate.js'
import stdin from 'get-stdin'
import formatLambdaLogEvent from './utils/format-lambda-log-event.js'
import ServerlessError from '../../serverless-error.js'
import utils from '@serverlessinc/sf-core/src/utils.js'
const { writeText, style } = utils
class AwsInvoke {
constructor(serverless, options, pluginUtils) {
this.serverless = serverless
this.options = options || {}
this.provider = this.serverless.getProvider('aws')
this.logger = pluginUtils.log
this.progress = pluginUtils.progress
Object.assign(this, validate)
this.hooks = {
'invoke:invoke': async () => {
this.progress.notice('Invoking function')
await this.extendedValidate()
this.log(await this.invoke())
},
}
}
async validateFile(key) {
const absolutePath = path.resolve(
this.serverless.serviceDir,
this.options[key],
)
try {
return await this.serverless.utils.readFile(absolutePath)
} catch (err) {
if (err.code === 'ENOENT') {
throw new ServerlessError(
'The file you provided does not exist.',
'FILE_NOT_FOUND',
)
}
throw err
}
}
async extendedValidate() {
this.validate()
// validate function exists in service
this.options.functionObj = this.serverless.service.getFunction(
this.options.function,
)
this.options.data = this.options.data || ''
if (!this.options.data) {
if (this.options.path) {
this.options.data = await this.validateFile('path')
} else {
try {
this.options.data = await stdin()
} catch {
// continue if no stdin was provided
}
}
}
if (!this.options.context && this.options.contextPath) {
this.options.context = await this.validateFile('contextPath')
}
try {
if (!this.options.raw) {
this.options.data = JSON.parse(this.options.data)
}
} catch (exception) {
// do nothing if it's a simple string or object already
}
try {
if (!this.options.raw && this.options.context) {
this.options.context = JSON.parse(this.options.context)
}
} catch (exception) {
// do nothing if it's a simple string or object already
}
}
async invoke() {
const invocationType = this.options.type || 'RequestResponse'
if (invocationType !== 'RequestResponse') {
this.options.log = 'None'
} else {
this.options.log = this.options.log ? 'Tail' : 'None'
}
const params = {
FunctionName: this.options.functionObj.name,
InvocationType: invocationType,
LogType: this.options.log,
Payload: Buffer.from(JSON.stringify(this.options.data || {})),
}
if (this.options.context) {
params.ClientContext = Buffer.from(
JSON.stringify(this.options.context),
).toString('base64')
}
if (this.options.qualifier) {
params.Qualifier = this.options.qualifier
}
return this.provider.request('Lambda', 'invoke', params)
}
log(invocationReply) {
this.progress.remove()
if (invocationReply.Payload) {
const response = JSON.parse(invocationReply.Payload)
writeText(JSON.stringify(response, null, 4))
}
if (invocationReply.LogResult) {
this.logger.blankLine()
writeText(style.aside('----------------------'))
this.logger.blankLine()
const logResult = Buffer.from(
invocationReply.LogResult,
'base64',
).toString()
const logResultLines = logResult.split('\n')
// Loop through and ensure the log that starts with "START" is always shown first
// and the log that starts with "REPORT" is always shown last
const startLog = logResultLines.find((line) => line.startsWith('START'))
const reportLog = logResultLines.find((line) => line.startsWith('REPORT'))
if (startLog) {
this.logger.aside(formatLambdaLogEvent(startLog))
}
logResultLines.forEach((line) => {
if (
!line.startsWith('START') &&
!line.startsWith('END') &&
!line.startsWith('REPORT')
) {
this.logger.aside(formatLambdaLogEvent(line))
}
})
if (reportLog) {
this.logger.aside(formatLambdaLogEvent(reportLog))
}
}
if (invocationReply.FunctionError) {
throw new ServerlessError(
'Invoked function failed',
'AWS_LAMBDA_INVOCATION_FAILED',
)
}
}
}
export default AwsInvoke