@@ -5,9 +5,16 @@ import {
55 dataAwsCloudfrontOriginRequestPolicy ,
66 dataAwsCloudfrontResponseHeadersPolicy ,
77} from "@cdktn/provider-aws" ;
8- import { IResolvable , Token , Lazy } from "cdktn" ;
8+ import { Annotations , IResolvable , Token , Lazy } from "cdktn" ;
99import { Construct } from "constructs" ;
10- import { ICertificate , IOrigin , FunctionAssociation } from "." ;
10+ import {
11+ ICertificate ,
12+ IOrigin ,
13+ FunctionAssociation ,
14+ FunctionEventType ,
15+ } from "." ;
16+ // aliased to avoid shadowing the global `Function` constructor
17+ import { Function as CloudFrontFunction } from "./function" ;
1118import { Duration } from "../../duration" ;
1219import { ArnFormat } from "../arn" ;
1320import {
@@ -243,6 +250,7 @@ export class Distribution extends AwsConstructBase implements IDistribution {
243250
244251 private readonly errorResponses : ErrorResponse [ ] ;
245252 private readonly certificate ?: ICertificate ;
253+ private readonly warnedUnpublishedFunctions = new Set < string > ( ) ;
246254
247255 constructor ( scope : Construct , name : string , props : DistributionProps ) {
248256 super ( scope , name , props ) ;
@@ -297,11 +305,30 @@ export class Distribution extends AwsConstructBase implements IDistribution {
297305 ) ,
298306 ) ,
299307 } ) ,
300- defaultCacheBehavior : this . _renderDefaultCacheBehavior ( {
301- pathPattern : "*" , // ignored for Default Cache Behavior
302- targetOriginId : defaultOriginId ,
303- ...props . defaultBehavior ,
304- } ) ,
308+ defaultCacheBehavior : {
309+ ...this . _renderDefaultCacheBehavior ( {
310+ pathPattern : "*" , // ignored for Default Cache Behavior
311+ targetOriginId : defaultOriginId ,
312+ ...props . defaultBehavior ,
313+ // rendered lazily below so associations pushed onto a caller-held
314+ // array *after* construction are still picked up at synth time
315+ functionAssociations : undefined ,
316+ } ) ,
317+ functionAssociation : Lazy . anyValue (
318+ {
319+ produce : ( ) =>
320+ this . renderFunctionAssociations (
321+ props . defaultBehavior . functionAssociations ,
322+ ) ?. map ( ( fa ) =>
323+ // Lazy producers need additional xxxToTerraform wrap
324+ cloudfrontDistribution . cloudfrontDistributionDefaultCacheBehaviorFunctionAssociationToTerraform (
325+ fa ,
326+ ) ,
327+ ) ,
328+ } ,
329+ { omitEmptyArray : true } ,
330+ ) ,
331+ } ,
305332 orderedCacheBehavior : Lazy . anyValue (
306333 {
307334 produce : ( ) =>
@@ -480,9 +507,67 @@ export class Distribution extends AwsConstructBase implements IDistribution {
480507 smoothStreaming : props . smoothStreaming ,
481508 viewerProtocolPolicy :
482509 props . viewerProtocolPolicy ?? ViewerProtocolPolicy . ALLOW_ALL ,
510+ functionAssociation : this . renderFunctionAssociations (
511+ props . functionAssociations ,
512+ ) ,
483513 } ;
484514 }
485515
516+ /**
517+ * Renders the `functionAssociation` blocks for a cache behavior from the
518+ * given `FunctionAssociation`s.
519+ *
520+ * CloudFront allows at most one function association per `FunctionEventType`
521+ * for each cache behavior.
522+ *
523+ * @internal
524+ */
525+ private renderFunctionAssociations (
526+ functionAssociations ?: FunctionAssociation [ ] ,
527+ ) :
528+ | cloudfrontDistribution . CloudfrontDistributionDefaultCacheBehaviorFunctionAssociation [ ]
529+ | undefined {
530+ if ( ! functionAssociations || functionAssociations . length === 0 ) {
531+ return undefined ;
532+ }
533+ const eventTypes = new Set < FunctionEventType > ( ) ;
534+ for ( const fa of functionAssociations ) {
535+ if ( eventTypes . has ( fa . eventType ) ) {
536+ throw new Error (
537+ `Only one function association is allowed per event type, got multiple for event type ${ fa . eventType } ` ,
538+ ) ;
539+ }
540+ eventTypes . add ( fa . eventType ) ;
541+ // Only locally-created `Function`s are verifiable here - imported/general
542+ // `IFunction` implementations may or may not be published, so leave them
543+ // alone. CloudFront only allows LIVE-stage (published) functions to be
544+ // associated with a distribution's cache behaviors.
545+ // Lazy producers resolve more than once per synth (prepareStack +
546+ // final render), so dedupe to avoid stacking identical warnings on
547+ // this node's metadata.
548+ if (
549+ CloudFrontFunction . isFunction ( fa . function ) &&
550+ ! fa . function . _autoPublish &&
551+ ! fa . skipPublishCheck &&
552+ ! this . warnedUnpublishedFunctions . has ( fa . function . node . path )
553+ ) {
554+ this . warnedUnpublishedFunctions . add ( fa . function . node . path ) ;
555+ // TODO(https://github.com/TerraConstructs/base/issues/161): switch to
556+ // Annotations.addWarningV2()/acknowledgeWarning() once the Annotations
557+ // facade lands, using the id prefix below as the warning's stable id.
558+ Annotations . of ( this ) . addWarning (
559+ `[terraconstructs/aws-edge:unpublishedFunctionAssociation] Function '${ fa . function . node . path } ' is associated with a cache behavior but was created with autoPublish: false; ` +
560+ "CloudFront only allows LIVE-stage functions in cache behaviors, so this will fail at apply time unless the function is published out of band. " +
561+ "Set skipPublishCheck: true on the association to acknowledge." ,
562+ ) ;
563+ }
564+ }
565+ return functionAssociations . map ( ( fa ) => ( {
566+ eventType : fa . eventType ,
567+ functionArn : fa . function . functionArn ,
568+ } ) ) ;
569+ }
570+
486571 private renderRestrictions ( geoRestriction ?: GeoRestriction ) {
487572 return geoRestriction
488573 ? {
0 commit comments