@architect/deploy
Version:
Deploys @architect projects
160 lines (152 loc) • 5.1 kB
JavaScript
let { series, waterfall } = require('@architect/utils')
let hydrate = require('@architect/hydrate')
let zip = require('./zip')
/**
* zips and uploads the function; overwrites its configuration based on any .arc-config found
*
* ...as quickly as possible
*
* @param {Object} params - updateLambda parameters
* @param {Object} params.aws - aws-lite client
* @param {Object} params.env - environment variables
* @param {String} params.FunctionName - a valid lambda function name or arn
* @param {Object} params.lambda - Inventory Lambda object
* @param {Boolean} params.shouldHydrate - whether to hydrate the function
* @param {String} params.src - source code path
* @param {Object} params.update - architect/utils updater
* @param {Function} callback
*/
module.exports = function updateLambda (params, callback) {
let {
aws,
env,
FunctionName,
shouldHydrate,
src,
update,
} = params
// Check the Lambda lifecycle state after each mutation to prevent async update issues
// 40x checks every 250ms = 10s
function checkin (count, callback) {
if (count === 40) {
callback(Error('Timed out waiting to perform Lambda update'))
}
else {
aws.lambda.GetFunctionConfiguration({ FunctionName })
.then(config => {
if (config?.LastUpdateStatus === 'InProgress') {
setTimeout(() => checkin(++count, callback), 250)
}
else if (config?.LastUpdateStatus === 'Failed') {
callback(Error('Lambda update error'))
}
// Only three states: InProgress, Failed, or Successful
else callback()
})
.catch(callback)
}
}
series([
// Hydrate the Lambda
function (callback) {
if (shouldHydrate) {
hydrate.install({ autoinstall: true, basepath: src }, callback)
}
else callback()
},
// Zip its contents
function (callback) {
update.start('Publishing code to Lambda')
waterfall([
function (callback) {
let { build, src } = params.lambda
zip(build || src, callback)
},
function (buffer, callback) {
let { architecture } = params.lambda.config
aws.lambda.UpdateFunctionCode({
FunctionName,
ZipFile: buffer,
Architectures: [ architecture ],
})
.then(() => callback())
.catch(callback)
},
],
function done (err) {
update.done('Published code to Lambda')
if (err) callback(err)
else callback()
})
},
// Publish the new payload (and update its configuration)
function (callback) {
let { timeout, memory, runtime, handler, concurrency, layers, policies } = params.lambda.config
let args = {
FunctionName,
Handler: handler,
MemorySize: memory,
Timeout: timeout,
Runtime: runtime,
}
if (layers.length > 0) args.Layers = layers
if (policies.length > 0) args.Policies = policies
series([
function getFunctionConfiguration (callback) {
let updateEnv = env && (params.lambda.config.env !== false)
// TODO probably want to warn here somehow? Because of how Lambda config updates work and the env vars we have access to at this time, we can only reliably add/update env vars, not remove
if (updateEnv) {
update.start('Updating Lambda env vars')
aws.lambda.GetFunctionConfiguration({ FunctionName })
.then(config => {
args.Environment = {
Variables: { ...config.Environment.Variables, ...env },
}
update.done('Updated Lambda env vars')
callback()
})
.catch(callback)
}
else callback()
},
function updateFunctionConfiguration (callback) {
update.start('Updating Lambda configuration')
checkin(0, err => {
if (err) callback(err)
else aws.lambda.UpdateFunctionConfiguration(args)
.then(() => callback())
.catch(callback)
})
},
function updateFunctionConcurrency (callback) {
if (!concurrency || concurrency === 'unthrottled') {
checkin(0, err => {
if (err) callback(err)
else aws.lambda.DeleteFunctionConcurrency({ FunctionName })
.then(() => callback())
.catch(callback)
})
}
else {
checkin(0, err => {
if (err) callback(err)
else aws.lambda.PutFunctionConcurrency({
FunctionName,
ReservedConcurrentExecutions: concurrency,
})
.then(() => callback())
.catch(callback)
})
}
},
], callback)
},
],
function done (err) {
if (err) callback(err)
else {
update.done('Updated Lambda configuration')
callback()
}
})
}