We have a CF distribution that is used for caching responses from MediaPackage. We have multiple MediaPackage endpoints and for each endpoint there is a corresponding origin/behavior on the same CF distribution.
Using CDK how can we add a new origin/behavior on the existing CF distribution?. I tried below but ran into an error:
// load existing distribution
const distribution = Distribution.fromDistributionAttributes(scope, `${props.stackName}-Distribution`, {
distributionId: 'E33333B',
domainName: 'test.example.com'
}) as Distribution;
// Convert to CfnDistribution
const distributionLive = distribution.node.defaultChild as CfnDistribution;
const distributionConfig =
distributionLive.distributionConfig as CfnDistribution.DistributionConfigProperty;
// Fetch origin/behaviors list
const cacheBehaviors = distributionConfig.cacheBehaviors as CfnDistribution.CacheBehaviorProperty[];
const origins = distributionConfig.origins as CfnDistribution.OriginProperty[];
// Add new origin/behavior
origins.push({..})
cacheBehaviors.push({..})
Error:
/dist/lib/resources/cloudfront.js:95
const distConfig = distributionLive.distributionConfig;
^
TypeError: Cannot read property 'distributionConfig' of undefined
You can create a lambda function which will update the CF distribution to add new behavior. Then create a custom AWS resource to call this lambda function on create/update as below -
#lambda function code to be saved in lambda_codes/update_cf/update_distribution.py
import boto3
import os
client = boto3.client('cloudfront')
def lambda_handler(event,context):
responseData={}
try:
if (event['RequestType'] == 'Create') or (event['RequestType'] == 'Update'):
config_res = client.get_distribution_config(Id=event['ResourceProperties']["Id"])
config_req = dict(config_res["DistributionConfig"])
ETag = config_res["ETag"]
LambdaFunctionARN = event['ResourceProperties']["LambdaFunctionARN"]
LambdaFunctionAssociations = {
'Quantity': 1,
'Items': [{
'LambdaFunctionARN': LambdaFunctionARN,
'EventType': 'viewer-request',
'IncludeBody': False
}]
}
CacheBehaviors= {}
CacheBehaviors_Item=dict(config_req['DefaultCacheBehavior'])
CacheBehaviors_Item['PathPattern'] ='/index.html'
CacheBehaviors['Items'] = [CacheBehaviors_Item]
CacheBehaviors['Items'][0]['LambdaFunctionAssociations'] = LambdaFunctionAssociations
CacheBehaviors['Quantity'] = 1
config_req['CacheBehaviors']=CacheBehaviors
print(config_req)
response = client.update_distribution(Id=event['ResourceProperties']["Id"],
IfMatch=ETag,
DistributionConfig=config_req)
print("Response",response)
responseData['Status'] = 'SUCCESS'
print ('SUCCESS')
return { "status" : responseData['Status'] }
else:
print("SUCCESS - operation not Create or Update, ResponseData=" + str(responseData))
return { "status" : 'SUCCESS' }
except Exception as e:
responseData['Error'] = str(e)
print("FAILED ERROR: " + responseData['Error'])
Below is the python CDK stack to create above updating CF lambda and custom resource calling it.
from constructs import Construct
import json
import os
import base64
from aws_cdk import (
Duration,
Stack,
aws_iam as iam,
aws_lambda as lambda_,
)
class InfraCodeStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
#Policy Statement for CF access
cf_update_lambda_role_policy_statement = iam.PolicyDocument(
statements= [
iam.PolicyStatement(
effect = iam.Effect.ALLOW,
actions= [
"cloudfront:GetDistributionConfig",
"cloudfront:UpdateDistribution"
],
resources= ["*"],
)
]
)
#Cf_Update lambda role
cf_lambda_iamrole = iam.Role(
self, "Cf_Update_Lambda_Role",
role_name= "Cf_Update_Lambda_Role",
assumed_by= iam.CompositePrincipal(iam.ServicePrincipal("lambda.amazonaws.com")),
description= 'Role to allow lambda to update cloudfront config',
inline_policies= {'CF_Update_lambda_Cloudfront_Policy':cf_update_lambda_role_policy_statement },
managed_policies= [
iam.ManagedPolicy.from_aws_managed_policy_name('service-role/AWSLambdaBasicExecutionRole')
]
)
#Cf_Update lambda function
cf_update_lambda = lambda_.Function(self, "cf-update-lambda-dev",
code=lambda_.Code.from_asset("lambda_codes/update_cf"),
function_name="cf-update-lambda-dev",
handler='update_distribution.lambda_handler',
role=cf_lambda_iamrole,
tracing=lambda_.Tracing.ACTIVE,
runtime=lambda_.Runtime.PYTHON_3_9
)
cf_update_lambda.node.add_dependency(get_version)
#Updating cloudfront via custom sdk call
input_event_to_cf_update_lambda = {
"RequestType" : "Update",
"ResourceProperties" : {
"Id" : cloudfront_object.distribution_id, ## your cloudfront object created in the stack's id
"LambdaFunctionARN" : function_arn, ## lambda@edge arn to be attached to behavior
"AWSRegion": "us-east-1"
}
}
def lambda_context(custom=None,env=None,client=None):
client_context = dict(
custom=custom,
env=env,
client=client)
json_context = json.dumps(client_context).encode('utf-8')
return base64.b64encode(json_context).decode('utf-8')
context = {
"custom": {},
"env": {},
"client": {}
}
cf_update_lambda_custom = cr.AwsCustomResource(self, "cf_update_lambda_custom",
on_update=cr.AwsSdkCall(
service="Lambda",
action="invoke",
parameters={ "FunctionName": cf_update_lambda.function_name,
"InvocationType" : "RequestResponse",
"LogType" : "Tail",
"ClientContext" : lambda_context(**context),
"Payload" : json.dumps(input_event_to_cf_update_lambda),
},
physical_resource_id=cr.PhysicalResourceId.of("lambda_update_cf_custom_resource")
),
policy=cr.AwsCustomResourcePolicy.from_statements(
[ iam.PolicyStatement(
effect = iam.Effect.ALLOW,
actions= [
"lambda:InvokeAsync",
"lambda:InvokeFunction"
],
resources= ['*'],
)]
),
)
cf_update_lambda_custom.node.add_dependency(cf_update_lambda)