Despliegue automatizado de páginas web estáticas desde un repositorio a un bucket S3 en AWS
Actualización: AWS dejó de ofrecer CodeCommit a nuevos clientes en julio de 2024. Mantengo este post como registro histórico del pipeline que construí; si estás empezando un proyecto nuevo hoy, plantéate usar GitHub como repositorio de origen en su lugar.
En este artículo presento el despliegue automatizado de páginas web estáticas desde un repositorio git a un bucket S3 en AWS. La implementación es segura, escalable, optimizada, y tiene despliegue automatizado desde un repositorio git en CodeCommit. Uso una plantilla de CloudFormation para desplegar la infraestructura en AWS, y una función Lambda para sincronizar el repositorio con el bucket S3. Consulta el repositorio de GitHub del proyecto.
Alojar una página web estática en AWS
Para alojar una página web estática en AWS, Amazon S3 es el servicio de referencia, con la opción de habilitar el bucket como página web estática. Para mejorar el rendimiento de la web, despliego una distribución de Amazon CloudFront con el bucket S3 como origen. Sin embargo, subir los cambios nuevos a la web manualmente en S3 era un problema, así que busqué una solución automatizada que actualizara el código en el repositorio git y lo sincronizara con el bucket S3 en cada commit.
Hice un primer intento configurando un AWS CodePipeline con una regla de Amazon EventBridge que se disparaba cada vez que se hacía un commit nuevo en el repositorio. Sin embargo, esta solución tenía dos inconvenientes: subía todos los ficheros del repositorio, se hubieran modificado o no, y no eliminaba los ficheros borrados del bucket S3.
AWS Amplify Hosting era otra solución posible, pero no era ideal porque reduce la visibilidad y el control del usuario sobre los recursos usados para el hosting web. La solución final que mejor funcionó fue invocar una función de AWS Lambda en cada commit nuevo del repositorio, que analizaba los cambios y los sincronizaba con el bucket S3.
Un bucket de Amazon S3 como almacenamiento de logs
Para llevar un registro y trazabilidad de todas las llamadas hechas a los ficheros de mi web (incluidas las llamadas de internet y las acciones de la función Lambda de sincronización), uso un bucket de Amazon S3 para almacenar estos logs.
BucketLogs:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${DomainName}-logs'
AccessControl: LogDeliveryWrite
# Encryption configuration with S3 managed keys
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
# Lifecycle configuration to expire logs after days defined in LogRetention parameter
LifecycleConfiguration:
Rules:
- Id: DeleteLogsAfterTwoMonths
Status: Enabled
Prefix: hosting/
ExpirationInDays: !Ref LogRetention
Para asegurar el bucket, lo cifré con las claves KMS por defecto del servicio S3 y fijé el AccessControl a LogDeliveryWrite. Esto concede permisos de escritura a los ficheros de log generados por servicios de AWS con esa política. Para evitar que los logs se almacenen indefinidamente y ocupen demasiado espacio en el bucket con información irrelevante, implementé un lifecycle que elimina los logs automáticamente pasados dos meses.
Un bucket de Amazon S3 como hosting web
Usar un servicio de almacenamiento de objetos como hosting para mi web estática es una elección interesante porque no necesito ejecución en el servidor. El servicio simplemente devuelve los ficheros tal cual al navegador del usuario cuando los pide.
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref DomainName
# Encryption configuration with S3 managed keys
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
# Logs configuration
LoggingConfiguration:
DestinationBucketName: !Ref BucketLogs
LogFilePrefix: hosting/
Las únicas particularidades al configurar este bucket son cifrarlo con las claves KMS por defecto de S3 y habilitar los logs de acceso del servidor, indicando el bucket de destino creado en el paso anterior.
Política del bucket
El bucket S3 no es accesible por defecto, lo que significa que no puedo servir el contenido de mi web a las peticiones entrantes. Para resolver esto, hace falta establecer una política de bucket que conceda acceso de lectura a la distribución de CloudFront que voy a desplegar a continuación.
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref Bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- s3:GetObject
Effect: Allow
Resource: !Sub '${Bucket.Arn}/*'
Principal:
CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
Amazon CloudFront para una entrega de contenido segura y rápida
Usar solo Amazon S3 no me da las herramientas necesarias para configurar aspectos fundamentales de mi web, como un dominio personalizado o HTTPS. Por suerte, Amazon CloudFront puede ayudarme con esto. Para usar un dominio personalizado en mi distribución de CloudFront y servir contenido de forma segura por HTTPS, es imprescindible tener un certificado SSL desplegado en la región de Norte de Virginia (us-east-1) mediante AWS Certificate Manager.
CloudFront:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
# Custom domain name
Aliases:
- !Ref DomainName
# A name for the distribution
Comment: !Sub 'CloudFront distribution for ${DomainName}'
DefaultCacheBehavior:
# Content will be compressed before it is cached (gzip), unless specifically instructed otherwise
Compress: true
# Cache content for 1 day
DefaultTTL: 86400
# Pass query strings to the origin
ForwardedValues:
QueryString: true
# Max cache content for 1 year
MaxTTL: 31536000
# Only allow HTTPS
ViewerProtocolPolicy: redirect-to-https
# Set the response headers policy
ResponseHeadersPolicyId: !Ref ResponseHeadersPolicy
TargetOriginId: !Sub 'S3Bucket-${AWS::StackName}'
DefaultRootObject: !Ref RootDocumentPath
CustomErrorResponses:
- ErrorCachingMinTTL: 300
ErrorCode: 404
ResponseCode: 200
ResponsePagePath: !Ref ErrorDocumentPath
- ErrorCachingMinTTL: 300
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: !Ref ErrorDocumentPath
IPV6Enabled: true
Enabled: true
HttpVersion: http2and3
# Set the origin to the S3 bucket and specify the origin access identity
Origins:
- DomainName: !GetAtt Bucket.DomainName
Id: !Sub 'S3Bucket-${AWS::StackName}'
S3OriginConfig:
OriginAccessIdentity:
!Join ['', ['origin-access-identity/cloudfront/', !Ref CloudFrontOriginAccessIdentity]]
# Allow CloudFront to use the all edge location
PriceClass: 'PriceClass_All'
# Set the certificate
ViewerCertificate:
AcmCertificateArn: !Ref CertificateArn
MinimumProtocolVersion: 'TLSv1.1_2016'
SslSupportMethod: 'sni-only'
Incorporar un servicio de red de distribución de contenidos (CDN) por encima de mi hosting web mejora la velocidad de entrega de contenido a los usuarios finales a escala global. AWS tiene muchas edge locations que cachean los ficheros estáticos de mi web. Como resultado, cuando un usuario visita la página, el contenido se recupera desde la edge location más cercana a su ubicación, evitando tener que volver al origen. Dentro de mi distribución, establezco el fichero raíz de mi web y detallo la ruta para redirigir peticiones en caso de errores 403 y 404.
Política de cabeceras de respuesta
CloudFront permite configurar cabeceras personalizadas en la respuesta HTTP. En este caso, he establecido varias políticas para prevenir posibles ataques de cross-site scripting (XSS), ataques de inyección de scripts, y ejecución de código malicioso, entre otras vulnerabilidades de seguridad.
ResponseHeadersPolicy:
Type: AWS::CloudFront::ResponseHeadersPolicy
Properties:
ResponseHeadersPolicyConfig:
# A name for the ResponseHeadersPolicy
Name: !Sub "${AWS::StackName}-static-site-security-headers"
# Specifies the security headers configuration
SecurityHeadersConfig:
# Specifies the Strict Transport Security (HSTS) header
StrictTransportSecurity:
# Specifies the maximum age (in seconds) for which the browser should cache the HSTS policy
AccessControlMaxAgeSec: 63072000
# Indicates whether the HSTS policy should apply to all subdomains
IncludeSubdomains: true
# Specifies whether to override an existing HSTS policy
Override: true
# Specifies whether to preload the HSTS policy in supported browsers
Preload: true
# Specifies the Content Security Policy (CSP) header
ContentSecurityPolicy:
# Specifies the CSP header value
ContentSecurityPolicy: !Ref CSPHeader
# Specifies whether to override an existing CSP policy
Override: true
# Specifies the X-Content-Type-Options header
ContentTypeOptions:
# Specifies whether to override an existing X-Content-Type-Options policy
Override: true
# Specifies the X-Frame-Options header
FrameOptions:
# Specifies the value of the X-Frame-Options header
FrameOption: DENY
# Specifies whether to override an existing X-Frame-Options policy
Override: true
# Specifies the Referrer-Policy header
ReferrerPolicy:
# Specifies the value of the Referrer-Policy header
ReferrerPolicy: "same-origin"
# Specifies whether to override an existing Referrer-Policy policy
Override: true
# Specifies the X-XSS-Protection header
XSSProtection:
# Specifies whether to block pages from loading when they detect reflected cross-site scripting (XSS) attacks
ModeBlock: true
# Specifies whether to override an existing X-XSS-Protection policy
Override: true
# Specifies whether to enable the XSS Protection policy
Protection: true
Origin Access Identity (OAI)
CloudFront Origin Access Identity (OAI) es un método para autenticar y autorizar peticiones entre Amazon CloudFront y un recurso de origen de Amazon S3. CloudFront OAI permite crear una identidad de acceso de origen que actúa como intermediaria entre CloudFront y S3. CloudFront usa esta identidad de acceso para solicitar objetos de S3 en nombre de los usuarios, lo que significa que los objetos ya no son accesibles directamente en S3, sino solo a través de CloudFront. Esto ofrece mayor control y seguridad sobre el acceso a los objetos almacenados en S3 y ayuda a proteger frente a posibles ataques.
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Sub 'CloudFront OAI for ${DomainName}'
AWS CodeCommit como repositorio Git para el código de mi web
Para mantener el control sobre el código que escribo para mi web, hace falta tenerlo subido a un repositorio Git. Aprovechando que trabajo en AWS, uso AWS CodeCommit para esto. Este repositorio será el punto de partida para subir mi código a producción cada vez que se haga un commit nuevo en mi repositorio. Para detectar cuándo ocurre ese commit, configuro un trigger en CodeCommit que reacciona a los commits nuevos en una rama concreta. Este trigger invoca de forma asíncrona una función lambda.
Repository:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: MyWebsite
RepositoryDescription: CodeCommit repository for MyWebsite
# For each new commit on the master branch, a new deployment on S3 will be triggered through a Lambda function
Triggers:
- Events:
- updateReference
DestinationArn: !GetAtt SyncCodeCommitWithS3Function.Arn
Name: SyncCodeCommitWithS3Trigger
Branches:
- !Ref BranchName
Amazon SNS para notificar errores de sincronización
Como mi función Lambda se ejecuta de forma asíncrona cada vez que se hace un commit nuevo en el repositorio, no estoy monitorizando el resultado de esa ejecución. Por eso es importante que me notifiquen cuando, por cualquier motivo, la función Lambda falle al realizar su tarea. Para recibir estas notificaciones, creo un nuevo topic de Amazon SNS.
SyncCodeCommitWithS3Topic:
Type: AWS::SNS::Topic
Properties:
TopicName: SyncCodeCommitWithS3
DisplayName: SyncCodeCommitWithS3
Una vez creado el topic, hace falta suscribirse a él. En mi caso, quiero recibir esas notificaciones de fallo por email. Creo una nueva suscripción usando email como protocolo, añadiendo mi dirección de correo.
SyncCodeCommitWithS3EmailSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref SyncCodeCommitWithS3Topic
Protocol: email
Endpoint: !Ref SubscriptionEndpoint
Usar AWS Lambda para sincronizar un repositorio con un bucket
El objetivo de mi función Lambda, una vez invocada de forma asíncrona por el trigger de CodeCommit, es obtener la información contenida en el commit nuevo, diferenciando entre ficheros que se han modificado o añadido y ficheros que se han eliminado. Los ficheros modificados o añadidos se suben al bucket S3, mientras que los ficheros eliminados en el repositorio también se eliminan del bucket. Una vez hecha esta sincronización, creo una invalidación de caché en CloudFront para los ficheros afectados por este commit nuevo. Así fuerzo a la CDN a actualizar el contenido de su caché con las versiones nuevas de los ficheros en S3.
Despliegue de la función Lambda
Antes de pasar al código, primero echo un vistazo a cómo configurar la función en AWS. Para asegurar que mi función Lambda pueda ejecutar ciertas acciones con otros servicios de AWS con los que necesita interactuar, es importante asignarle un rol con permisos suficientes. Por eso configuro una política de permisos restrictiva que le concede solo los permisos necesarios para completar su tarea correctamente.
SyncCodeCommitWithS3FunctionRole:
Type: AWS::IAM::Role
Properties:
RoleName: SyncCodeCommitWithS3FunctionRole
Description: Lambda Role to perform logs and sync file from CodeCommit With a S3 bucket and invalidates the CloudFront distribution cache for that files.
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
SyncCodeCommitWithS3FunctionRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: SyncCodeCommitWithS3FunctionRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${SyncCodeCommitWithS3Function}:*"
- Effect: Allow
Action:
- codecommit:GetCommit
- codecommit:GetDifferences
- codecommit:GetFile
Resource: !GetAtt Repository.Arn
- Effect: Allow
Action:
- s3:PutObject
- s3:DeleteObject
Resource: !Sub '${Bucket.Arn}/*'
- Effect: Allow
Action:
- cloudfront:CreateInvalidation
Resource: !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFront}'
- Effect: Allow
Action:
- sns:Publish
Resource: !Ref SyncCodeCommitWithS3Topic
Roles:
- !Ref SyncCodeCommitWithS3FunctionRole
Para asegurar que el trigger que configuré en CodeCommit pueda invocar la función correctamente, es importante recordar que AWS Lambda no permite que CodeCommit ejecute directamente la función usando un rol con permisos. En su lugar, hace falta asignar a la función una política de recursos que autorice a CodeCommit a ejecutarla de forma segura.
SyncCodeCommitWithS3FunctionResourcePolicy:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref SyncCodeCommitWithS3Function
Principal: codecommit.amazonaws.com
SourceArn: !GetAtt Repository.Arn
Una vez establecidos todos los permisos, despliego mi función Lambda. Aunque normalmente me siento más cómodo programando en JavaScript, en este caso he desarrollado la función en Python, concretamente en su versión 3.10, el runtime más actualizado que puedo usar hoy en AWS.
El parámetro timeout es crucial en mi función, ya que necesitará más o menos tiempo de ejecución según el número de ficheros modificados en mi último commit. Mi consejo es que cada persona adapte este parámetro a sus necesidades concretas. El código de la función debe estar comprimido en formato ZIP y subido a un bucket S3 para que CloudFormation pueda acceder al código al desplegarlo.
SyncCodeCommitWithS3Function:
Type: AWS::Lambda::Function
Properties:
FunctionName: SyncCodeCommitWithS3
Description: Function that sync file from CodeCommit With a S3 bucket and invalidates the CloudFront distribution cache for that files.
Runtime: python3.10
Architectures:
- x86_64
MemorySize: 128
# Timeout in seconds. Increase if you have commits with a lot of files to sync
Timeout: !Ref LambdaTimeout
# Place Lambda function code in S3 and reference it here. Zip file must contain the index.py file
Code:
S3Bucket: !Ref LambdaS3Bucket
S3Key: !Ref LambdaS3Key
PackageType: Zip
Handler: index.lambda_handler
Role: !GetAtt SyncCodeCommitWithS3FunctionRole.Arn
# Define environment variables to know the bucket name and the CloudFront distribution ID
Environment:
Variables:
bucketName: !Ref Bucket
distributionId: !Ref CloudFront
topicArn: !Ref SyncCodeCommitWithS3Topic
Para que mi función Lambda sepa qué bucket sincronizar, a qué topic de SNS enviar las notificaciones de error, y qué distribución de CloudFront usar para las invalidaciones, defino tres variables de entorno con esta información, para que el código pueda acceder a ellas durante el despliegue.
Código de la función Lambda
Para empezar mi función, importo el AWS SDK para interactuar con otros servicios de AWS desde mi código. Además, importo la librería time para obtener la hora actual y asignarla a una referencia, y la librería os para recuperar los valores de mis variables de entorno. Este código se ejecuta solo cuando mi función Lambda se despliega e inicia, así que los valores de las variables declaradas en este punto no se vuelven a declarar ni reasignar cada vez que se invoca la función. Por eso, para hacer mi código más eficiente, inicializo los distintos clientes del AWS SDK que voy a usar y fijo los valores obtenidos de las variables de entorno. Por último, declaro un diccionario que contiene todos los tipos MIME existentes, para poder recuperar más adelante en mi código el tipo MIME según la extensión del fichero.
# Boto3 is the official AWS library for Python
import boto3
# Import the time library to get the current time
import time
# Import the os library to get environment variables
import os
# Create an instance of the CodeCommit, S3, SNS and CloudFront client
codecommit = boto3.client('codecommit')
s3 = boto3.client('s3')
cloudfront = boto3.client('cloudfront')
sns = boto3.client('sns')
# Get the bucket name and distribution id from the environment variables
try:
bucketName = os.environ['bucketName']
distributionId = os.environ['distributionId']
topicArn = os.environ['topicArn']
except Exception as e:
raise Exception('Missing environment variable: ' + str(e))
# All the possible MIME types
contentTypes = {
'3dm': 'x-world/x-3dmf',
'3dmf': 'x-world/x-3dmf',
'3g2': 'video/3gpp2',
...
}
Una vez listo el código que se ejecuta al arrancar mi función, toca desarrollar el handler. El handler es la función de Python que se ejecuta cada vez que se invoca la función Lambda.
Mi handler recibe el event como argumento. Esta variable contiene un diccionario con información sobre el evento que invoca la función. De este diccionario obtengo datos como el nombre del repositorio y el ID del commit realizado. Con estos dos datos, y usando la llamada a la API de CodeCommit get_commit, también puedo recuperar el ID del commit anterior al último.
La llamada a la API de CodeCommit get_differences, al darle dos IDs de commit y un nombre de repositorio, devuelve un array con las diferencias encontradas entre ambos commits. Esto me permite comprobar qué cambios se han hecho en el último commit y, por tanto, qué acciones necesito tomar para sincronizar el bucket con el repositorio.
Usando list comprehensions, relleno arrays diferenciando entre ficheros actualizados o añadidos y ficheros eliminados, y los recorro para realizar acciones. En el caso de actualizar o crear un fichero nuevo, lo recupero del repositorio. Como se devuelve en base64 y pierde sus metadatos, uso su extensión para determinar el tipo MIME correspondiente. Además, compruebo si el fichero es HTML y, si lo es, elimino la extensión para que no aparezca en la URL. Por último, uso la API del cliente de S3 para subir el fichero al bucket.
En el caso de necesitar eliminar un fichero, quito la extensión HTML si la tiene y añado la ruta al array de invalidación de caché. Hago esto también para cada fichero que subo al bucket en el paso anterior. Una vez hecho esto, elimino el fichero del bucket S3 usando la llamada a la API. Después de haber eliminado y subido los ficheros correspondientes, creo una invalidación de caché en CloudFront para forzar a la CDN a actualizar estos ficheros y no servir contenido desactualizado.
# Handler function
def lambda_handler(event, context):
# Get the repository name and the id of the last commit made
repoName = event['Records'][0]['eventSourceARN'].split(':')[-1]
lastCommitId = event['Records'][0]['codecommit']['references'][0]['commit']
try:
# Get commit information to get the id of the commit prior to the last commit made
commitInfo = codecommit.get_commit(
repositoryName = repoName,
commitId = lastCommitId
)
except Exception as e:
sns.publish(TopicArn = topicArn, Message = 'Error getting commit information: ' + str(e), Subject = 'Sync Error')
return {
'statusCode': 500,
'body': 'Error getting commit information: ' + str(e)
}
# Get the id of the commit prior to the last commit made
parentCommitId = commitInfo['commit']['parents'][0] if commitInfo['commit']['parents'] else None
try:
# Get the changes that have occurred in the last commit made
if parentCommitId:
commitChanges = codecommit.get_differences(
repositoryName = repoName,
afterCommitSpecifier = lastCommitId,
beforeCommitSpecifier = parentCommitId
)
else:
commitChanges = codecommit.get_differences(
repositoryName = repoName,
afterCommitSpecifier = lastCommitId
)
except Exception as e:
sns.publish(TopicArn = topicArn, Message = 'Error getting commit changes: ' + str(e), Subject = 'Sync Error')
return {
'statusCode': 500,
'body': 'Error getting commit changes: ' + str(e)
}
# Declare three arrays to group the updated, deleted and invalidated files| Populate the arrays using list comprehension
updatedFiles = [difference['afterBlob']['path'] for difference in commitChanges['differences'] if difference.get('afterBlob') and difference['afterBlob'].get('path')]
deletedFiles = [difference['beforeBlob']['path'] for difference in commitChanges['differences'] if difference.get('beforeBlob') and difference['beforeBlob'].get('path') and difference['beforeBlob']['path'] not in updatedFiles]
invalidateFiles = []
# Iterate over the updated files and upload them to the S3 bucket
for filePath in updatedFiles:
try:
# Get the file content from CodeCommit
codecommitFile = codecommit.get_file(
repositoryName = repoName,
commitSpecifier = lastCommitId,
filePath = filePath
)
except Exception as e:
sns.publish(TopicArn = topicArn, Message = 'Error getting file content from CodeCommit: ' + str(e), Subject = 'Sync Error')
return {
'statusCode': 500,
'body': 'Error getting file content from CodeCommit: ' + str(e)
}
# Set Content-Type based on file extension
contentType = contentTypes.get(filePath.split('.')[-1], 'text/plain')
# For html files, remove de html extension
filePath = filePath.replace('.html', '')
# Add the file to the list of files to invalidate
invalidateFiles.append('/' + filePath)
try:
# Use the put_object method of S3 to upload the file to the bucket
s3.put_object(Bucket = bucketName, Key = filePath, Body = codecommitFile['fileContent'], ContentType = contentType)
except Exception as e:
sns.publish(TopicArn = topicArn, Message = 'Error uploading file to S3: ' + str(e), Subject = 'Sync Error')
return {
'statusCode': 500,
'body': 'Error uploading file to S3: ' + str(e)
}
# Iterate over the deleted files and delete them from the S3 bucket
for filePath in deletedFiles:
# For html files, remove de html extension
filePath = filePath.replace('.html', '')
# Add the file to the list of files to invalidate
invalidateFiles.append('/' + filePath)
try:
# Use the delete_object method of S3 to delete the file from the bucket
s3.delete_object(Bucket = bucketName, Key = filePath)
except Exception as e:
sns.publish(TopicArn = topicArn, Message = 'Error deleting file from S3: ' + str(e), Subject = 'Sync Error')
return {
'statusCode': 500,
'body': 'Error deleting file from S3: ' + str(e)
}
try:
# Invalidate the modified files in CloudFront cache
cloudfront.create_invalidation(
DistributionId = distributionId,
InvalidationBatch = {
'Paths': {
'Quantity': len(invalidateFiles),
'Items': invalidateFiles
},
'CallerReference': str(time.time()).replace('.', '')
}
)
except Exception as e:
sns.publish(TopicArn = topicArn, Message = 'Error invalidating files in CloudFront: ' + str(e), Subject = 'Sync Error')
return {
'statusCode': 500,
'body': 'Error invalidating files in CloudFront: ' + str(e)
}
# Return a 200 response
return {
'statusCode': 200,
'body': 'All correct!'
}
¡Gracias por leer! Nos vemos en el próximo.