mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
770 lines
25 KiB
JavaScript
770 lines
25 KiB
JavaScript
import _ from 'lodash'
|
|
import url from 'url'
|
|
import ServerlessError from '../../../../../serverless-error.js'
|
|
import utils from '@serverlessinc/sf-core/src/utils.js'
|
|
|
|
const { log, style } = utils
|
|
|
|
const originLimits = { maxTimeout: 30, maxMemorySize: 10240 }
|
|
const viewerLimits = { maxTimeout: 5, maxMemorySize: 128 }
|
|
|
|
class AwsCompileCloudFrontEvents {
|
|
constructor(serverless, options) {
|
|
this.serverless = serverless
|
|
this.options = options
|
|
this.provider = this.serverless.getProvider('aws')
|
|
this.lambdaEdgeLimits = {
|
|
'origin-request': originLimits,
|
|
'origin-response': originLimits,
|
|
'viewer-request': viewerLimits,
|
|
'viewer-response': viewerLimits,
|
|
default: viewerLimits,
|
|
}
|
|
this.cachePolicies = new Set()
|
|
|
|
const originObjectSchema = {
|
|
type: 'object',
|
|
properties: {
|
|
ConnectionAttempts: { type: 'integer', minimum: 1, maximum: 3 },
|
|
ConnectionTimeout: { type: 'integer', minimum: 1, maximum: 10 },
|
|
CustomOriginConfig: {
|
|
type: 'object',
|
|
properties: {
|
|
HTTPPort: { type: 'integer', minimum: 0, maximum: 65535 },
|
|
HTTPSPort: { type: 'integer', minimum: 0, maximum: 65535 },
|
|
OriginKeepaliveTimeout: {
|
|
type: 'integer',
|
|
minimum: 1,
|
|
maximum: 60,
|
|
},
|
|
OriginProtocolPolicy: {
|
|
enum: ['http-only', 'match-viewer', 'https-only'],
|
|
},
|
|
OriginReadTimeout: { type: 'integer', minimum: 1, maximum: 60 },
|
|
OriginSSLProtocols: {
|
|
type: 'array',
|
|
items: { enum: ['SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2'] },
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
required: ['OriginProtocolPolicy'],
|
|
},
|
|
DomainName: {
|
|
anyOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfFunction' }],
|
|
},
|
|
OriginAccessControlId: {
|
|
anyOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfFunction' }],
|
|
},
|
|
OriginCustomHeaders: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
HeaderName: { type: 'string' },
|
|
HeaderValue: { type: 'string' },
|
|
},
|
|
additionalProperties: false,
|
|
required: ['HeaderName', 'HeaderValue'],
|
|
},
|
|
},
|
|
OriginPath: { type: 'string' },
|
|
S3OriginConfig: {
|
|
type: 'object',
|
|
properties: {
|
|
OriginAccessIdentity: {
|
|
anyOf: [
|
|
{
|
|
type: 'string',
|
|
pattern: '^origin-access-identity/cloudfront/.+',
|
|
},
|
|
{ $ref: '#/definitions/awsCfFunction' },
|
|
],
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
required: ['DomainName'],
|
|
oneOf: [
|
|
{ required: ['CustomOriginConfig'] },
|
|
{ required: ['S3OriginConfig'] },
|
|
],
|
|
}
|
|
|
|
const behaviorObjectSchema = {
|
|
type: 'object',
|
|
properties: {
|
|
AllowedMethods: {
|
|
anyOf: [
|
|
{
|
|
type: 'array',
|
|
uniqueItems: true,
|
|
minItems: 2,
|
|
items: { enum: ['GET', 'HEAD'] },
|
|
},
|
|
{
|
|
type: 'array',
|
|
uniqueItems: true,
|
|
minItems: 3,
|
|
items: { enum: ['GET', 'HEAD', 'OPTIONS'] },
|
|
},
|
|
{
|
|
type: 'array',
|
|
uniqueItems: true,
|
|
minItems: 7,
|
|
items: {
|
|
enum: [
|
|
'GET',
|
|
'HEAD',
|
|
'OPTIONS',
|
|
'PUT',
|
|
'PATCH',
|
|
'POST',
|
|
'DELETE',
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
CachedMethods: {
|
|
anyOf: [
|
|
{
|
|
type: 'array',
|
|
uniqueItems: true,
|
|
minItems: 2,
|
|
items: { enum: ['GET', 'HEAD'] },
|
|
},
|
|
{
|
|
type: 'array',
|
|
uniqueItems: true,
|
|
minItems: 3,
|
|
items: { enum: ['GET', 'HEAD', 'OPTIONS'] },
|
|
},
|
|
],
|
|
},
|
|
CachePolicyId: { type: 'string' },
|
|
Compress: { type: 'boolean' },
|
|
FieldLevelEncryptionId: { type: 'string' },
|
|
OriginRequestPolicyId: {
|
|
anyOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfFunction' }],
|
|
},
|
|
ResponseHeadersPolicyId: {
|
|
anyOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfFunction' }],
|
|
},
|
|
SmoothStreaming: { type: 'boolean' },
|
|
TrustedSigners: { type: 'array', items: { type: 'string' } },
|
|
ViewerProtocolPolicy: {
|
|
enum: ['allow-all', 'redirect-to-https', 'https-only'],
|
|
},
|
|
TrustedKeyGroups: {
|
|
type: 'array',
|
|
items: {
|
|
anyOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfRef' }],
|
|
},
|
|
},
|
|
MaxTTL: { type: 'number' },
|
|
MinTTL: { type: 'number' },
|
|
DefaultTTL: { type: 'number' },
|
|
ForwardedValues: {
|
|
type: 'object',
|
|
properties: {
|
|
Cookies: {
|
|
anyOf: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
Forward: { enum: ['all', 'none'] },
|
|
},
|
|
additionalProperties: false,
|
|
required: ['Forward'],
|
|
},
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
Forward: { const: 'whitelist' },
|
|
WhitelistedNames: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
required: ['Forward', 'WhitelistedNames'],
|
|
},
|
|
],
|
|
},
|
|
Headers: { type: 'array', items: { type: 'string' } },
|
|
QueryString: { type: 'boolean' },
|
|
QueryStringCacheKeys: { type: 'array', items: { type: 'string' } },
|
|
},
|
|
additionalProperties: false,
|
|
required: ['QueryString'],
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
}
|
|
|
|
this.serverless.configSchemaHandler.defineFunctionEvent(
|
|
'aws',
|
|
'cloudFront',
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
behavior: behaviorObjectSchema,
|
|
cachePolicy: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { $ref: '#/definitions/awsCfInstruction' },
|
|
name: { type: 'string', minLength: 1 },
|
|
},
|
|
oneOf: [{ required: ['id'] }, { required: ['name'] }],
|
|
additionalProperties: false,
|
|
},
|
|
eventType: {
|
|
enum: [
|
|
'viewer-request',
|
|
'origin-request',
|
|
'origin-response',
|
|
'viewer-response',
|
|
],
|
|
},
|
|
isDefaultOrigin: { type: 'boolean' },
|
|
includeBody: { type: 'boolean' },
|
|
origin: {
|
|
anyOf: [{ type: 'string', format: 'uri' }, originObjectSchema],
|
|
},
|
|
// Allowed characters reference:
|
|
// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#DownloadDistValuesPathPattern
|
|
// Still note it doesn't reference "?" character, which appears in prior examples,
|
|
// Hence it's now included in this regex
|
|
pathPattern: {
|
|
type: 'string',
|
|
pattern: '^([A-Za-z0-9_.*?$/~"\'@:+-]|&)+$',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
)
|
|
|
|
this.hooks = {
|
|
'package:initialize': async () => this.validate(),
|
|
'before:package:compileFunctions': async () => this.prepareFunctions(),
|
|
'package:compileEvents': () => {
|
|
this.compileCloudFrontCachePolicies()
|
|
this.compileCloudFrontEvents()
|
|
},
|
|
'before:remove:remove': async () => this.logRemoveReminder(),
|
|
}
|
|
}
|
|
|
|
logRemoveReminder() {
|
|
if (this.serverless.processedInput.commands[0] === 'remove') {
|
|
let isEventUsed = false
|
|
const funcKeys = this.serverless.service.getAllFunctions()
|
|
if (funcKeys.length) {
|
|
isEventUsed = funcKeys.some((funcKey) => {
|
|
const func = this.serverless.service.getFunction(funcKey)
|
|
return (
|
|
func.events &&
|
|
func.events.find((e) => Object.keys(e)[0] === 'cloudFront')
|
|
)
|
|
})
|
|
}
|
|
if (isEventUsed) {
|
|
const message = [
|
|
"Don't forget to manually remove your Lambda@Edge functions ",
|
|
'once the CloudFront distribution removal is successfully propagated!',
|
|
].join('')
|
|
log.warning(message)
|
|
}
|
|
}
|
|
}
|
|
|
|
validate() {
|
|
this.serverless.service.getAllFunctions().forEach((functionName) => {
|
|
const functionObj = this.serverless.service.getFunction(functionName)
|
|
functionObj.events.forEach(({ cloudFront }) => {
|
|
if (!cloudFront) return
|
|
const { eventType = 'default' } = cloudFront
|
|
const { maxMemorySize, maxTimeout } = this.lambdaEdgeLimits[eventType]
|
|
if (functionObj.memorySize && functionObj.memorySize > maxMemorySize) {
|
|
throw new ServerlessError(
|
|
`"${functionName}" memorySize is greater than ${maxMemorySize} which is not supported by Lambda@Edge functions of type "${eventType}"`,
|
|
'LAMBDA_EDGE_UNSUPPORTED_MEMORY_SIZE',
|
|
)
|
|
}
|
|
if (functionObj.timeout && functionObj.timeout > maxTimeout) {
|
|
throw new ServerlessError(
|
|
`"${functionName}" timeout is greater than ${maxTimeout} which is not supported by Lambda@Edge functions of type "${eventType}"`,
|
|
'LAMBDA_EDGE_UNSUPPORTED_TIMEOUT_VALUE',
|
|
)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
prepareFunctions() {
|
|
// Lambda@Edge functions need to be versioned
|
|
this.serverless.service.getAllFunctions().forEach((functionName) => {
|
|
const functionObj = this.serverless.service.getFunction(functionName)
|
|
if (functionObj.events.find((event) => event.cloudFront)) {
|
|
// ensure that functions are versioned
|
|
Object.assign(functionObj, { versionFunction: true })
|
|
// set the maximum memory size if not explicitly configured
|
|
if (!functionObj.memorySize) {
|
|
Object.assign(functionObj, { memorySize: 128 })
|
|
}
|
|
// set the maximum timeout if not explicitly configured
|
|
if (!functionObj.timeout) {
|
|
Object.assign(functionObj, { timeout: 5 })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
compileCloudFrontCachePolicies() {
|
|
const userConfig = this.serverless.service.provider.cloudFront || {}
|
|
if (userConfig.cachePolicies) {
|
|
const Resources =
|
|
this.serverless.service.provider.compiledCloudFormationTemplate
|
|
.Resources
|
|
for (const [name, cachePolicyConfig] of Object.entries(
|
|
userConfig.cachePolicies,
|
|
)) {
|
|
this.cachePolicies.add(name)
|
|
|
|
Object.assign(Resources, {
|
|
[this.provider.naming.getCloudFrontCachePolicyLogicalId(name)]: {
|
|
Type: 'AWS::CloudFront::CachePolicy',
|
|
Properties: {
|
|
CachePolicyConfig: {
|
|
...cachePolicyConfig,
|
|
Name: this.provider.naming.getCloudFrontCachePolicyName(name),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
compileCloudFrontEvents() {
|
|
this.cloudFrontDistributionLogicalId =
|
|
this.provider.naming.getCloudFrontDistributionLogicalId()
|
|
|
|
this.cloudFrontDistributionDomainNameLogicalId =
|
|
this.provider.naming.getCloudFrontDistributionDomainNameLogicalId()
|
|
|
|
const lambdaAtEdgeFunctions = []
|
|
|
|
const origins = []
|
|
const behaviors = []
|
|
let defaultOrigin
|
|
|
|
const Resources =
|
|
this.serverless.service.provider.compiledCloudFormationTemplate.Resources
|
|
const Outputs =
|
|
this.serverless.service.provider.compiledCloudFormationTemplate.Outputs
|
|
|
|
// helper function for joining origins and behaviors
|
|
function extendDeep(object, source) {
|
|
return _.assignWith(object, source, (a, b) => {
|
|
if (Array.isArray(a)) {
|
|
return _.uniqWith(a.concat(b), _.isEqual)
|
|
}
|
|
if (_.isObject(a)) {
|
|
extendDeep(a, b)
|
|
}
|
|
return a
|
|
})
|
|
}
|
|
|
|
function createOrigin(origin, naming) {
|
|
const originObj = {}
|
|
if (typeof origin === 'string') {
|
|
const originUrl = url.parse(origin)
|
|
Object.assign(originObj, {
|
|
DomainName: originUrl.hostname,
|
|
})
|
|
|
|
if (originUrl.pathname && originUrl.pathname.length > 1) {
|
|
Object.assign(originObj, { OriginPath: originUrl.pathname })
|
|
}
|
|
|
|
if (originUrl.protocol === 's3:') {
|
|
Object.assign(originObj, { S3OriginConfig: {} })
|
|
} else {
|
|
Object.assign(originObj, {
|
|
CustomOriginConfig: {
|
|
OriginProtocolPolicy: 'match-viewer',
|
|
},
|
|
})
|
|
}
|
|
} else {
|
|
Object.assign(originObj, origin)
|
|
}
|
|
|
|
Object.assign(originObj, {
|
|
Id: naming.getCloudFrontOriginId(originObj),
|
|
})
|
|
return originObj
|
|
}
|
|
|
|
const unusedUserDefinedCachePolicies = new Set(this.cachePolicies)
|
|
this.serverless.service.getAllFunctions().forEach((functionName) => {
|
|
const functionObj = this.serverless.service.getFunction(functionName)
|
|
if (functionObj.events) {
|
|
functionObj.events.forEach((event) => {
|
|
if (event.cloudFront) {
|
|
const lambdaFunctionLogicalId = Object.keys(Resources).find(
|
|
(key) =>
|
|
Resources[key].Type === 'AWS::Lambda::Function' &&
|
|
Resources[key].Properties.FunctionName === functionObj.name,
|
|
)
|
|
|
|
// Remove VPC & Env vars from lambda@Edge
|
|
delete Resources[lambdaFunctionLogicalId].Properties.VpcConfig
|
|
delete Resources[lambdaFunctionLogicalId].Properties.Environment
|
|
|
|
// Retain Lambda@Edge functions to avoid issues when removing the CloudFormation stack
|
|
Object.assign(Resources[lambdaFunctionLogicalId], {
|
|
DeletionPolicy: 'Retain',
|
|
})
|
|
|
|
const lambdaVersionLogicalId = Object.keys(Resources).find(
|
|
(key) => {
|
|
const resource = Resources[key]
|
|
if (resource.Type !== 'AWS::Lambda::Version') return false
|
|
return (
|
|
_.get(resource, 'Properties.FunctionName.Ref') ===
|
|
lambdaFunctionLogicalId
|
|
)
|
|
},
|
|
)
|
|
|
|
const pathPattern =
|
|
typeof event.cloudFront.pathPattern === 'string'
|
|
? event.cloudFront.pathPattern
|
|
: undefined
|
|
|
|
let origin = createOrigin(
|
|
event.cloudFront.origin,
|
|
this.provider.naming,
|
|
)
|
|
const existingOrigin = origins.find((o) => o.Id === origin.Id)
|
|
|
|
if (!existingOrigin) {
|
|
origins.push(origin)
|
|
} else {
|
|
origin = extendDeep(existingOrigin, origin)
|
|
}
|
|
|
|
if (event.cloudFront.isDefaultOrigin) {
|
|
if (defaultOrigin && defaultOrigin !== origin) {
|
|
throw new ServerlessError(
|
|
'Found more than one cloudfront event with "isDefaultOrigin" defined',
|
|
'CLOUDFRONT_MULTIPLE_DEFAULT_ORIGIN_EVENTS',
|
|
)
|
|
}
|
|
defaultOrigin = origin
|
|
}
|
|
|
|
let behavior = {
|
|
ViewerProtocolPolicy: 'allow-all',
|
|
}
|
|
let shouldAssignCachePolicy = true
|
|
if (event.cloudFront.behavior) {
|
|
Object.assign(behavior, event.cloudFront.behavior)
|
|
}
|
|
|
|
if (
|
|
event.cloudFront.behavior &&
|
|
event.cloudFront.behavior.CachePolicyId
|
|
) {
|
|
Object.assign(behavior, {
|
|
CachePolicyId: event.cloudFront.behavior.CachePolicyId,
|
|
})
|
|
shouldAssignCachePolicy = false
|
|
}
|
|
|
|
if (
|
|
event.cloudFront.behavior &&
|
|
(event.cloudFront.behavior.ForwardedValues ||
|
|
event.cloudFront.behavior.MaxTTL != null ||
|
|
event.cloudFront.behavior.MinTTL != null ||
|
|
event.cloudFront.behavior.DefaultTTL != null)
|
|
) {
|
|
shouldAssignCachePolicy = false
|
|
}
|
|
|
|
if (event.cloudFront.cachePolicy) {
|
|
const { id, name } = event.cloudFront.cachePolicy
|
|
if (name) {
|
|
if (!this.cachePolicies.has(name)) {
|
|
throw new ServerlessError(
|
|
`Event references not configured cache policy '${name}'`,
|
|
'UNRECOGNIZED_CLOUDFRONT_CACHE_POLICY',
|
|
)
|
|
}
|
|
unusedUserDefinedCachePolicies.delete(name)
|
|
}
|
|
Object.assign(behavior, {
|
|
CachePolicyId: id || {
|
|
Ref: this.provider.naming.getCloudFrontCachePolicyLogicalId(
|
|
name,
|
|
),
|
|
},
|
|
})
|
|
shouldAssignCachePolicy = false
|
|
}
|
|
|
|
// Assigning default cache policy only if cache policy reference is not defined.
|
|
if (shouldAssignCachePolicy) {
|
|
// Assigning default Managed-CachingOptimized Cache Policy.
|
|
// See details at https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html#managed-cache-policies-list
|
|
Object.assign(behavior, {
|
|
CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6',
|
|
})
|
|
}
|
|
|
|
const lambdaFunctionAssociation = {
|
|
EventType: event.cloudFront.eventType,
|
|
LambdaFunctionARN: {
|
|
Ref: lambdaVersionLogicalId,
|
|
},
|
|
}
|
|
|
|
if (event.cloudFront.includeBody != null) {
|
|
lambdaFunctionAssociation.IncludeBody =
|
|
event.cloudFront.includeBody
|
|
}
|
|
|
|
Object.assign(behavior, {
|
|
TargetOriginId: origin.Id,
|
|
LambdaFunctionAssociations: [lambdaFunctionAssociation],
|
|
})
|
|
|
|
if (pathPattern) {
|
|
Object.assign(behavior, { PathPattern: pathPattern })
|
|
}
|
|
|
|
const existingBehaviour = behaviors.find(
|
|
(o) =>
|
|
o.PathPattern === behavior.PathPattern &&
|
|
o.TargetOriginId === behavior.TargetOriginId,
|
|
)
|
|
|
|
if (!existingBehaviour) {
|
|
behaviors.push(behavior)
|
|
} else {
|
|
behavior = extendDeep(existingBehaviour, behavior)
|
|
}
|
|
|
|
lambdaAtEdgeFunctions.push(
|
|
Object.assign({}, functionObj, {
|
|
functionName,
|
|
lambdaVersionLogicalId,
|
|
}),
|
|
)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
unusedUserDefinedCachePolicies.forEach((unusedUserDefinedCachePolicy) => {
|
|
log.warning(
|
|
`Setting "provider.cloudFront.cachePolicies.${unusedUserDefinedCachePolicy}" is not used by any cloudFront event configuration.`,
|
|
)
|
|
})
|
|
|
|
// sort that first is without PathPattern if available
|
|
behaviors.sort((a, b) => {
|
|
if (a.PathPattern && !b.PathPattern) {
|
|
return 1
|
|
}
|
|
if (b.PathPattern && !a.PathPattern) {
|
|
return -1
|
|
}
|
|
return 0
|
|
})
|
|
|
|
if (lambdaAtEdgeFunctions.length) {
|
|
if (this.provider.getRegion() !== 'us-east-1') {
|
|
throw new ServerlessError(
|
|
'CloudFront associated functions have to be deployed to the us-east-1 region.',
|
|
'CLOUDFRONT_INVALID_REGION',
|
|
)
|
|
}
|
|
|
|
// Check if all behaviors got unique pathPatterns
|
|
if (behaviors.length !== _.uniqBy(behaviors, 'PathPattern').length) {
|
|
throw new ServerlessError(
|
|
'Found more than one behavior with the same PathPattern',
|
|
'CLOUDFRONT_MULTIPLE_BEHAVIORS_FOR_SINGLE_PATH_PATTERN',
|
|
)
|
|
}
|
|
|
|
// Check if all event types in every behavior is unique
|
|
if (
|
|
behaviors.some((o) => {
|
|
return (
|
|
o.LambdaFunctionAssociations.length !==
|
|
_.uniqBy(o.LambdaFunctionAssociations, 'EventType').length
|
|
)
|
|
})
|
|
) {
|
|
throw new ServerlessError(
|
|
'The event type of a function association must be unique in the cache behavior',
|
|
'CLOUDFRONT_EVENT_TYPE_NON_UNIQUE_CACHE_BEHAVIOR',
|
|
)
|
|
}
|
|
|
|
// DefaultCacheBehavior does not support PathPattern property
|
|
if (behaviors[0].PathPattern) {
|
|
let origin = defaultOrigin
|
|
if (!origin) {
|
|
if (origins.length > 1) {
|
|
throw new ServerlessError(
|
|
'Found more than one origin but none of the cloudfront event has "isDefaultOrigin" defined',
|
|
'CLOUDFRONT_MULTIPLE_DEFAULT_ORIGIN_EVENTS',
|
|
)
|
|
}
|
|
origin = origins[0]
|
|
}
|
|
const behavior = _.omit(behaviors[0], [
|
|
'PathPattern',
|
|
'LambdaFunctionAssociations',
|
|
])
|
|
behavior.TargetOriginId = origin.Id
|
|
behaviors.unshift(behavior)
|
|
}
|
|
|
|
const lambdaInvokePermissions = lambdaAtEdgeFunctions.reduce(
|
|
(permissions, lambdaAtEdgeFunction) => {
|
|
const invokePermissionName =
|
|
this.provider.naming.getLambdaAtEdgeInvokePermissionLogicalId(
|
|
lambdaAtEdgeFunction.functionName,
|
|
)
|
|
const invokePermission = {
|
|
Type: 'AWS::Lambda::Permission',
|
|
Properties: {
|
|
FunctionName: {
|
|
Ref: lambdaAtEdgeFunction.lambdaVersionLogicalId,
|
|
},
|
|
Action: 'lambda:InvokeFunction',
|
|
Principal: 'edgelambda.amazonaws.com',
|
|
SourceArn: {
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'',
|
|
'arn:',
|
|
{ Ref: 'AWS::Partition' },
|
|
':cloudfront::',
|
|
{ Ref: 'AWS::AccountId' },
|
|
':distribution/',
|
|
{
|
|
Ref: this.provider.naming.getCloudFrontDistributionLogicalId(),
|
|
},
|
|
],
|
|
],
|
|
},
|
|
},
|
|
}
|
|
return Object.assign(permissions, {
|
|
[invokePermissionName]: invokePermission,
|
|
})
|
|
},
|
|
{},
|
|
)
|
|
|
|
Object.assign(Resources, lambdaInvokePermissions)
|
|
|
|
if (!Resources.IamRoleLambdaExecution) {
|
|
log.notice(
|
|
`Remember to add required lambda@edge permissions to your execution role. Documentation: ${style.link(
|
|
'https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html',
|
|
)}`,
|
|
)
|
|
} else {
|
|
const lambdaAssumeStatement =
|
|
Resources.IamRoleLambdaExecution.Properties.AssumeRolePolicyDocument.Statement.find(
|
|
(statement) =>
|
|
statement.Principal.Service.includes('lambda.amazonaws.com'),
|
|
)
|
|
if (lambdaAssumeStatement) {
|
|
lambdaAssumeStatement.Principal.Service.push(
|
|
'edgelambda.amazonaws.com',
|
|
)
|
|
}
|
|
|
|
// Lambda creates CloudWatch Logs log streams
|
|
// in the CloudWatch Logs regions closest
|
|
// to the locations where the function is executed.
|
|
// The format of the name for each log stream is
|
|
// /aws/lambda/us-east-1.function-name where
|
|
// function-name is the name that you gave
|
|
// to the function when you created it.
|
|
Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement.push(
|
|
{
|
|
Effect: 'Allow',
|
|
Action: [
|
|
'logs:CreateLogGroup',
|
|
'logs:CreateLogStream',
|
|
'logs:PutLogEvents',
|
|
'logs:TagResource',
|
|
],
|
|
Resource: [{ 'Fn::Sub': 'arn:${AWS::Partition}:logs:*:*:*' }],
|
|
},
|
|
)
|
|
}
|
|
|
|
const CacheBehaviors = behaviors.slice(1)
|
|
|
|
const CloudFrontDistribution = {
|
|
Type: 'AWS::CloudFront::Distribution',
|
|
Properties: {
|
|
DistributionConfig: {
|
|
Comment: `${
|
|
this.serverless.service.service
|
|
} ${this.provider.getStage()}`,
|
|
Enabled: true,
|
|
DefaultCacheBehavior: behaviors[0],
|
|
Origins: origins,
|
|
},
|
|
},
|
|
}
|
|
|
|
if (CacheBehaviors.length > 0) {
|
|
Object.assign(CloudFrontDistribution.Properties.DistributionConfig, {
|
|
CacheBehaviors,
|
|
})
|
|
}
|
|
|
|
Object.assign(Resources, {
|
|
[this.cloudFrontDistributionLogicalId]: CloudFrontDistribution,
|
|
})
|
|
|
|
_.merge(Outputs, {
|
|
[this.cloudFrontDistributionLogicalId]: {
|
|
Description: 'CloudFront Distribution Id',
|
|
Value: {
|
|
Ref: this.provider.naming.getCloudFrontDistributionLogicalId(),
|
|
},
|
|
},
|
|
[this.cloudFrontDistributionDomainNameLogicalId]: {
|
|
Description: 'CloudFront Distribution Domain Name',
|
|
Value: {
|
|
'Fn::GetAtt': [
|
|
this.provider.naming.getCloudFrontDistributionLogicalId(),
|
|
'DomainName',
|
|
],
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
export default AwsCompileCloudFrontEvents
|