Invoca una AWS Lambda función en una canalización en CodePipeline - AWS CodePipeline

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Invoca una AWS Lambda función en una canalización en CodePipeline

AWS Lambda es un servicio automático que permite ejecutar código sin aprovisionar ni administrar servidores. Puede crear funciones de Lambda y añadirlas como acciones en las canalizaciones. Puesto que Lambda le permite escribir funciones para realizar casi cualquier tarea, puede personalizar el funcionamiento de la canalización.

importante

No registre el evento JSON que se CodePipeline envía a Lambda, ya que esto puede provocar que las credenciales de usuario se registren en CloudWatch los registros. El CodePipeline rol usa un evento JSON para pasar credenciales temporales a Lambda en el artifactCredentials campo. Para ver un ejemplo de evento, consulte Ejemplo de evento JSON.

Estos son modos posibles de usar las funciones de Lambda en las canalizaciones:

  • Crear recursos bajo demanda en una etapa de una canalización AWS CloudFormation y eliminarlos en otra etapa.

  • Para implementar versiones de aplicaciones sin tiempo de inactividad AWS Elastic Beanstalk con una función Lambda que intercambie valores de CNAME.

  • Para implementar instancias Docker de Amazon ECS.

  • Para hacer una copia de seguridad (es decir, crear un snapshot de AMI) antes de proceder a la creación o la implementación.

  • Para hacer posible la integración con productos de terceros en su canalización, por ejemplo para publicar mensajes en un cliente IRC.

nota

La creación y ejecución de funciones de Lambda pueden generar cargos en su AWS cuenta. Para obtener más información, consulte Precios.

En este tema se presupone que conoce AWS CodePipeline AWS Lambda y sabe cómo crear canalizaciones, funciones y las políticas y funciones de IAM de las que dependen. En este tema puede ver cómo:

  • Crear una función de Lambda que pruebe si se ha implementado correctamente una página web.

  • Configure las funciones de ejecución CodePipeline y las de Lambda y los permisos necesarios para ejecutar la función como parte de la canalización.

  • Editar una canalización para añadir la función de Lambda como una acción.

  • Probar la acción lanzando un cambio manualmente.

nota

Cuando se utiliza la acción de invocación de Lambda entre regiones, el estado de la ejecución de lambda mediante PutJobFailureResulty debe enviarse a PutJobSuccessResultla AWS región CodePipeline en la que está presente la función Lambda y no a la región en la que existe. CodePipeline

En este tema se incluyen ejemplos de funciones para demostrar la flexibilidad de trabajar con funciones Lambda en: CodePipeline

  • Basic Lambda function

    • Crear una función Lambda básica para usarla con. CodePipeline

    • Si se devuelve el resultado correcto o erróneo, CodePipeline diríjase al enlace de detalles de la acción.

  • Ejemplo de función de Python que usa una AWS CloudFormation plantilla

    • Usar parámetros de usuario codificados en JSON para pasar varios valores de configuración a la función (get_user_params)

    • Interactuar con artefactos .zip en un bucket de artefactos (get_template)

    • Usar un token de continuación para monitorizar procesos asíncronos de ejecución prolongada (continue_job_later). Esto permite que la acción continúe y la función se realice correctamente aunque se supere el tiempo de ejecución de quince minutos (esto es un límite de Lambda).

Cada función de ejemplo incluye información sobre los permisos que debe añadir al rol. Para obtener información sobre los límites AWS Lambda, consulta los límites en la Guía para AWS Lambda desarrolladores.

importante

Los roles, las políticas y el código de muestra que se incluyen en este tema son meramente ilustrativos y se ofrecen "tal cual".

Paso 1: Crear una canalización

En este paso, va a crear una canalización a la que le añadirá luego la función de Lambda. Es la misma canalización que creó en CodePipeline tutoriales. Si esa canalización sigue estando configurada en su cuenta y se encuentra en la misma región en la que tiene pensado crear la función de Lambda, puede omitir este paso.

Para crear la canalización
  1. Siga los tres primeros pasos Tutorial: Crear una canalización simple (bucket de S3) para crear un bucket de Amazon S3, CodeDeploy recursos y una canalización de dos etapas. Elija la opción Amazon Linux para los tipos de instancias. Puede usar el nombre que desee para la canalización, pero en los pasos de este tema se usa MyLambdaTestPipeline.

  2. En la página de estado de tu canalización, en la CodeDeploy acción, selecciona Detalles. En la página de detalles de la implementación del grupo de implementaciones, elija un ID de instancia de la lista.

  3. En la consola de Amazon EC2, en la pestaña Detalles de la instancia, copie la dirección IP en Dirección IP pública (por ejemplo, 192.0.2.4). Usará esta dirección como objetivo de la función en AWS Lambda.

nota

La política de rol de servicio predeterminada CodePipeline incluye los permisos de Lambda necesarios para invocar la función. Sin embargo, si ha modificado el rol de servicio predeterminado o seleccionado uno distinto, asegúrese de que la política o el rol admiten los permisos de lambda:InvokeFunction y lambda:ListFunctions. De lo contrario, las canalizaciones que incluyan las acciones de Lambda fallarán.

Paso 2: Crear la función de Lambda

En este paso, va a crear una función de Lambda que realiza una solicitud HTTP y busca una línea de texto en una página web. Durante este paso, también creará una política de IAM y un rol de ejecución de Lambda. Para obtener más información, consulte Modelo de permisos en la Guía para desarrolladores de AWS Lambda .

Para crear el rol de ejecución
  1. Inicie sesión en la consola de IAM AWS Management Console y ábrala en https://console.aws.amazon.com/iam/.

  2. Elija Policies y después, Create Policy. Seleccione la pestaña JSON y pegue la siguiente política en el campo correspondiente.

    { "Version": "2012-10-17", "Statement": [ { "Action": [ "logs:*" ], "Effect": "Allow", "Resource": "arn:aws:logs:*:*:*" }, { "Action": [ "codepipeline:PutJobSuccessResult", "codepipeline:PutJobFailureResult" ], "Effect": "Allow", "Resource": "*" } ] }
  3. Elija Revisar política.

  4. En la página Review policy (Revisar política), en Name (Nombre), escriba un nombre para la política (por ejemplo, CodePipelineLambdaExecPolicy). En Description (Descripción), escriba Enables Lambda to execute code.

    Seleccione Crear política.

    nota

    Estos son los permisos mínimos necesarios para que una función de Lambda interactúe con Amazon. CodePipeline CloudWatch Si desea ampliar esta política para permitir funciones que interactúen con otros AWS recursos, debe modificarla para permitir las acciones requeridas por esas funciones de Lambda.

  5. En la página del panel de la política, elija Roles (Funciones) y, a continuación, elija Create role (Crear función).

  6. En la página Crear rol, elija Servicio de AWS. Elija Lambda y, a continuación, elija Siguiente: permisos.

  7. En la página Adjuntar políticas de permisos, seleccione la casilla de verificación situada junto a y CodePipelineLambdaExecPolicy, a continuación, elija Siguiente: etiquetas. Elija Siguiente: Revisar.

  8. En la página Review (Revisión), en Role name (Nombre del rol), escriba el nombre que quiera darle y, a continuación, seleccione Create role (Crear rol).

Para crear la función Lambda de ejemplo para usarla con CodePipeline
  1. Inicie sesión AWS Management Console y abra la AWS Lambda consola en https://console.aws.amazon.com/lambda/.

  2. En la página Functions (Funciones), seleccione Create function (Crear función).

    nota

    Si ve una página Bienvenida en lugar de la página Lambda elija Empezar ahora.

  3. En la página Crear función, elija Diseñar desde cero. En Nombre de función, escriba un nombre para la función de Lambda (por ejemplo, MyLambdaFunctionForAWSCodePipeline). En Runtime, elija Node.js 20.x.

  4. En Role (Rol), seleccione Choose an existing role (Elegir un rol existente). En Existing role (Función existente), seleccione su función y, a continuación, Create function (Crear función)​.

    Se abrirá la página de detalles de la función creada.

  5. Copie el siguiente código en el cuadro Function code (Código de función):

    nota

    El objeto de evento, bajo la clave CodePipeline .job, contiene los detalles del trabajo. Para ver un ejemplo completo del CodePipeline retorno del evento JSON a Lambda, consulte. Ejemplo de evento JSON

    import { CodePipelineClient, PutJobSuccessResultCommand, PutJobFailureResultCommand } from "@aws-sdk/client-codepipeline"; import http from 'http'; import assert from 'assert'; export const handler = (event, context) => { const codepipeline = new CodePipelineClient(); // Retrieve the Job ID from the Lambda action const jobId = event["CodePipeline.job"].id; // Retrieve the value of UserParameters from the Lambda action configuration in CodePipeline, in this case a URL which will be // health checked by this function. const url = event["CodePipeline.job"].data.actionConfiguration.configuration.UserParameters; // Notify CodePipeline of a successful job const putJobSuccess = async function(message) { const command = new PutJobSuccessResultCommand({ jobId: jobId }); try { await codepipeline.send(command); context.succeed(message); } catch (err) { context.fail(err); } }; // Notify CodePipeline of a failed job const putJobFailure = async function(message) { const command = new PutJobFailureResultCommand({ jobId: jobId, failureDetails: { message: JSON.stringify(message), type: 'JobFailed', externalExecutionId: context.awsRequestId } }); await codepipeline.send(command); context.fail(message); }; // Validate the URL passed in UserParameters if(!url || url.indexOf('http://') === -1) { putJobFailure('The UserParameters field must contain a valid URL address to test, including http:// or https://'); return; } // Helper function to make a HTTP GET request to the page. // The helper will test the response and succeed or fail the job accordingly const getPage = function(url, callback) { var pageObject = { body: '', statusCode: 0, contains: function(search) { return this.body.indexOf(search) > -1; } }; http.get(url, function(response) { pageObject.body = ''; pageObject.statusCode = response.statusCode; response.on('data', function (chunk) { pageObject.body += chunk; }); response.on('end', function () { callback(pageObject); }); response.resume(); }).on('error', function(error) { // Fail the job if our request failed putJobFailure(error); }); }; getPage(url, function(returnedPage) { try { // Check if the HTTP response has a 200 status assert(returnedPage.statusCode === 200); // Check if the page contains the text "Congratulations" // You can change this to check for different text, or add other tests as required assert(returnedPage.contains('Congratulations')); // Succeed the job putJobSuccess("Tests passed."); } catch (ex) { // If any of the assertions failed then fail the job putJobFailure(ex); } }); };
  6. Deje el valor predeterminado de Handler (Controlador) y, en Role (Función), deje CodePipelineLambdaExecRole como la opción predeterminada.

  7. En Basic settings (Configuración básica), para Timeout (Tiempo de espera), introduzca 20 segundos.

  8. Seleccione Guardar.

Paso 3: Añadir la función Lambda a una canalización de la consola CodePipeline

En este paso, añadirá una nueva etapa a la canalización y después añadirá una acción de Lambda que llame a la función a esa etapa.

Para añadir una etapa
  1. Inicie sesión en la CodePipeline consola AWS Management Console y ábrala en http://console.aws.amazon.com/codesuite/codepipeline/home.

  2. En la página Welcome (Bienvenida), elija la canalización que acaba de crear.

  3. En la página para ver la canalización, elija Edit.

  4. En la página de edición, seleccione + Añadir fase para añadir una fase después de la fase de despliegue con la CodeDeploy acción. Escriba un nombre para la etapa (por ejemplo, LambdaStage) y elija Add stage (Añadir etapa).

    nota

    También puede añadir su acción de Lambda a una etapa. Para fines de demostración, añadiremos la función de Lambda como la única acción de una etapa para que pueda ver fácilmente su progreso a medida que los artefactos progresan en la canalización.

  5. Elija + Add action group (Añadir grupo de acciones). En el panel Editar acción, en Nombre de acción, escriba un nombre para la acción de Lambda (por ejemplo, MyLambdaAction). En Provider (Proveedor), elija AWS Lambda. En Nombre de función, elija o escriba el nombre de la función de Lambda (por ejemplo, MyLambdaFunctionForAWSCodePipeline). En Parámetros de usuario, especifique la dirección IP de la instancia de Amazon EC2 que ha copiado previamente (por ejemplo, http://192.0.2.4) y, a continuación, elija Listo.

    nota

    En este tema se usa una dirección IP, pero en una situación real podría proporcionar el nombre de su sitio web registrado (por ejemplo, http://www.example.com). Para obtener más información sobre los datos de eventos y los controladores AWS Lambda, consulte el Modelo de programación en la Guía para AWS Lambda desarrolladores.

  6. En la página Edit action (Editar acción), elija Save (Guardar).

Paso 4: Probar la canalización con la función de Lambda

Para probar la función, lance el cambio más reciente en la canalización.

Para usar la consola para ejecutar la versión más reciente de un artefacto en una canalización
  1. En la página de detalles de la canalización, elija Liberar cambio. Esto ejecuta la revisión más reciente disponible en cada ubicación de código fuente especificada en una acción de código fuente a través de la canalización.

  2. Cuando se complete la acción de Lambda, seleccione el enlace Detalles para ver el flujo de registro de la función en Amazon CloudWatch, incluida la duración facturada del evento. Si la función ha fallado, el CloudWatch registro proporciona información sobre la causa.

Paso 5: Siguientes pasos

Ahora que ya ha creado una función de Lambda y la ha añadido como acción en una canalización, puede intentar lo siguiente:

  • Añadir más acciones de Lambda a una etapa para comprobar otras páginas web.

  • Modificar la función de Lambda para comprobar una cadena de texto distinta.

  • Explorar las funciones de Lambda y crear y añadir sus propias funciones de lambda a las canalizaciones.

AWS Lambda Acción que se ejecuta en una canalización.

Cuando haya terminado de experimentar con la función Lambda, considere eliminarla de su canalización, eliminarla y eliminar la función AWS Lambda de IAM para evitar posibles cargos. Para obtener más información, consulte Editar una canalización en CodePipeline, Eliminar una canalización en CodePipeline y Eliminación de roles o perfiles de instancia.

Ejemplo de evento JSON

El siguiente ejemplo muestra un ejemplo de evento JSON enviado a Lambda por. CodePipeline La estructura de este evento se parece a la respuesta a la GetJobDetails API, pero sin los tipos de datos actionTypeId ni pipelineContext. Se incluyen dos detalles de configuración de acción, FunctionName y UserParameters, tanto en el evento JSON como en la respuesta a la API GetJobDetails. Los valores con el texto rojo en cursiva son ejemplos o explicaciones, y no valores reales.

{ "CodePipeline.job": { "id": "11111111-abcd-1111-abcd-111111abcdef", "accountId": "111111111111", "data": { "actionConfiguration": { "configuration": { "FunctionName": "MyLambdaFunctionForAWSCodePipeline", "UserParameters": "some-input-such-as-a-URL" } }, "inputArtifacts": [ { "location": { "s3Location": { "bucketName": "the name of the bucket configured as the pipeline artifact store in Amazon S3, for example codepipeline-us-east-2-1234567890", "objectKey": "the name of the application, for example CodePipelineDemoApplication.zip" }, "type": "S3" }, "revision": null, "name": "ArtifactName" } ], "outputArtifacts": [], "artifactCredentials": { "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "sessionToken": "MIICiTCCAfICCQD6m7oRw0uXOjANBgkqhkiG9w 0BAQUFADCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZ WF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIw EAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5 jb20wHhcNMTEwNDI1MjA0NTIxWhcNMTIwNDI0MjA0NTIxWjCBiDELMAkGA1UEBh MCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBb WF6b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMx HzAdBgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wgZ8wDQYJKoZIhvcNAQE BBQADgY0AMIGJAoGBAMaK0dn+a4GmWIWJ21uUSfwfEvySWtC2XADZ4nB+BLYgVI k60CpiwsZ3G93vUEIO3IyNoH/f0wYK8m9TrDHudUZg3qX4waLG5M43q7Wgc/MbQ ITxOUSQv7c7ugFFDzQGBzZswY6786m86gpEIbb3OhjZnzcvQAaRHhdlQWIMm2nr AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAtCu4nUhVVxYUntneD9+h8Mg9q6q+auN KyExzyLwaxlAoo7TJHidbtS4J5iNmZgXL0FkbFFBjvSfpJIlJ00zbhNYS5f6Guo EDmFJl0ZxBHjJnyp378OD8uTs7fLvjx79LjSTbNYiytVbZPQUQ5Yaxu2jXnimvw 3rrszlaEXAMPLE=", "accessKeyId": "AKIAIOSFODNN7EXAMPLE" }, "continuationToken": "A continuation token if continuing job", "encryptionKey": { "id": "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", "type": "KMS" } } } }

Más funciones de ejemplo

En los siguientes ejemplos de funciones Lambda se muestran funciones adicionales que puede utilizar para sus canalizaciones. CodePipeline Para utilizar estas funciones, puede que sea necesario modificar la política del rol de ejecución de Lambda, como se indica en la introducción de cada ejemplo.

Ejemplo de función de Python que usa una AWS CloudFormation plantilla

En el siguiente ejemplo, se muestra una función que crea o actualiza una pila basada en una AWS CloudFormation plantilla proporcionada. La plantilla crea un bucket de Amazon S3. Tiene una finalidad ilustrativa únicamente, para reducir costos. Lo ideal es eliminar la pila antes de cargar cosas en el bucket. Si carga archivos en el bucket, no podrá eliminarlo cuando elimine la pila. Tendrá que quitar manualmente el contenido del bucket para poder eliminar el bucket en sí.

En este ejemplo de Python, se presupone que tiene una canalización que utiliza un bucket de Amazon S3 como acción de origen o bien que tiene acceso a un bucket de Amazon S3 con control de versiones que puede usar con la canalización. Puede crear la AWS CloudFormation plantilla, comprimirla y subirla a ese depósito como un archivo.zip. Luego añadirá una acción de código fuente a la canalización para recuperar el archivo .zip del bucket.

nota

Si Amazon S3 es el proveedor de origen de la canalización, debe comprimir los archivos de origen en un solo archivo.zip y cargarlo en el bucket de origen. También puede cargar un archivo sin comprimir; sin embargo, se producirá un error en las acciones posteriores que esperan un archivo.zip.

El siguiente ejemplo muestra:

  • El uso de parámetros de usuario codificados en JSON para pasar varios valores de configuración a la función (get_user_params)

  • La interacción con artefactos .zip en un bucket de artefactos (get_template)

  • El uso de un token de continuación para monitorizar procesos asíncronos de ejecución prolongada (continue_job_later) Esto permite que la acción continúe y la función se realice correctamente aunque se supere el tiempo de ejecución de quince minutos (esto es un límite de Lambda).

Para usar este ejemplo de función Lambda, la política del rol de ejecución de Lambda debe tener Allow permisos en Amazon AWS CloudFormation S3 y CodePipeline, como se muestra en este ejemplo de política:

{ "Version": "2012-10-17", "Statement": [ { "Action": [ "logs:*" ], "Effect": "Allow", "Resource": "arn:aws:logs:*:*:*" }, { "Action": [ "codepipeline:PutJobSuccessResult", "codepipeline:PutJobFailureResult" ], "Effect": "Allow", "Resource": "*" }, { "Action": [ "cloudformation:DescribeStacks", "cloudformation:CreateStack", "cloudformation:UpdateStack" ], "Effect": "Allow", "Resource": "*" }, { "Action": [ "s3:*" ], "Effect": "Allow", "Resource": "*" } ] }

Para crear la AWS CloudFormation plantilla, abra cualquier editor de texto sin formato y copie y pegue el siguiente código:

{ "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "CloudFormation template which creates an S3 bucket", "Resources" : { "MySampleBucket" : { "Type" : "AWS::S3::Bucket", "Properties" : { } } }, "Outputs" : { "BucketName" : { "Value" : { "Ref" : "MySampleBucket" }, "Description" : "The name of the S3 bucket" } } }

Guárdelo como un archivo JSON con el nombre template.json en un directorio denominado template-package. Cree un archivo comprimido (.zip) con este directorio, asígnele el nombre template-package.zip y cárguelo en un bucket de Amazon S3 con control de versiones. Si ya tiene un bucket configurado para la canalización, puede usarlo. Edite la canalización para añadir una acción de código fuente que recupere el archivo .zip. Asigne un nombre al resultado de esta acción. MyTemplate Para obtener más información, consulte Editar una canalización en CodePipeline.

nota

La función de Lambda de ejemplo espera estos nombres de archivo y esta estructura comprimida. Sin embargo, puede sustituir este ejemplo por su propia AWS CloudFormation plantilla. Si usa su propia plantilla, asegúrese de modificar la política del rol de ejecución de Lambda para permitir cualquier funcionalidad adicional que requiera la plantilla AWS CloudFormation .

Para añadir el siguiente código como función en Lambda;
  1. Abra la consola de Lambda; y elija Crear función.

  2. En la página Crear función, elija Diseñar desde cero. En Nombre de función, introduzca un nombre para la función de Lambda.

  3. En Runtime (Tiempo de ejecución), elija Python 2.7.

  4. En Elegir o crear un rol de ejecución, seleccione Usar un rol existente. En Existing role (Función existente), seleccione su función y, a continuación, Create function (Crear función)​.

    Se abrirá la página de detalles de la función creada.

  5. Copie el siguiente código en el cuadro Function code (Código de función):

    from __future__ import print_function from boto3.session import Session import json import urllib import boto3 import zipfile import tempfile import botocore import traceback print('Loading function') cf = boto3.client('cloudformation') code_pipeline = boto3.client('codepipeline') def find_artifact(artifacts, name): """Finds the artifact 'name' among the 'artifacts' Args: artifacts: The list of artifacts available to the function name: The artifact we wish to use Returns: The artifact dictionary found Raises: Exception: If no matching artifact is found """ for artifact in artifacts: if artifact['name'] == name: return artifact raise Exception('Input artifact named "{0}" not found in event'.format(name)) def get_template(s3, artifact, file_in_zip): """Gets the template artifact Downloads the artifact from the S3 artifact store to a temporary file then extracts the zip and returns the file containing the CloudFormation template. Args: artifact: The artifact to download file_in_zip: The path to the file within the zip containing the template Returns: The CloudFormation template as a string Raises: Exception: Any exception thrown while downloading the artifact or unzipping it """ tmp_file = tempfile.NamedTemporaryFile() bucket = artifact['location']['s3Location']['bucketName'] key = artifact['location']['s3Location']['objectKey'] with tempfile.NamedTemporaryFile() as tmp_file: s3.download_file(bucket, key, tmp_file.name) with zipfile.ZipFile(tmp_file.name, 'r') as zip: return zip.read(file_in_zip) def update_stack(stack, template): """Start a CloudFormation stack update Args: stack: The stack to update template: The template to apply Returns: True if an update was started, false if there were no changes to the template since the last update. Raises: Exception: Any exception besides "No updates are to be performed." """ try: cf.update_stack(StackName=stack, TemplateBody=template) return True except botocore.exceptions.ClientError as e: if e.response['Error']['Message'] == 'No updates are to be performed.': return False else: raise Exception('Error updating CloudFormation stack "{0}"'.format(stack), e) def stack_exists(stack): """Check if a stack exists or not Args: stack: The stack to check Returns: True or False depending on whether the stack exists Raises: Any exceptions raised .describe_stacks() besides that the stack doesn't exist. """ try: cf.describe_stacks(StackName=stack) return True except botocore.exceptions.ClientError as e: if "does not exist" in e.response['Error']['Message']: return False else: raise e def create_stack(stack, template): """Starts a new CloudFormation stack creation Args: stack: The stack to be created template: The template for the stack to be created with Throws: Exception: Any exception thrown by .create_stack() """ cf.create_stack(StackName=stack, TemplateBody=template) def get_stack_status(stack): """Get the status of an existing CloudFormation stack Args: stack: The name of the stack to check Returns: The CloudFormation status string of the stack such as CREATE_COMPLETE Raises: Exception: Any exception thrown by .describe_stacks() """ stack_description = cf.describe_stacks(StackName=stack) return stack_description['Stacks'][0]['StackStatus'] def put_job_success(job, message): """Notify CodePipeline of a successful job Args: job: The CodePipeline job ID message: A message to be logged relating to the job status Raises: Exception: Any exception thrown by .put_job_success_result() """ print('Putting job success') print(message) code_pipeline.put_job_success_result(jobId=job) def put_job_failure(job, message): """Notify CodePipeline of a failed job Args: job: The CodePipeline job ID message: A message to be logged relating to the job status Raises: Exception: Any exception thrown by .put_job_failure_result() """ print('Putting job failure') print(message) code_pipeline.put_job_failure_result(jobId=job, failureDetails={'message': message, 'type': 'JobFailed'}) def continue_job_later(job, message): """Notify CodePipeline of a continuing job This will cause CodePipeline to invoke the function again with the supplied continuation token. Args: job: The JobID message: A message to be logged relating to the job status continuation_token: The continuation token Raises: Exception: Any exception thrown by .put_job_success_result() """ # Use the continuation token to keep track of any job execution state # This data will be available when a new job is scheduled to continue the current execution continuation_token = json.dumps({'previous_job_id': job}) print('Putting job continuation') print(message) code_pipeline.put_job_success_result(jobId=job, continuationToken=continuation_token) def start_update_or_create(job_id, stack, template): """Starts the stack update or create process If the stack exists then update, otherwise create. Args: job_id: The ID of the CodePipeline job stack: The stack to create or update template: The template to create/update the stack with """ if stack_exists(stack): status = get_stack_status(stack) if status not in ['CREATE_COMPLETE', 'ROLLBACK_COMPLETE', 'UPDATE_COMPLETE']: # If the CloudFormation stack is not in a state where # it can be updated again then fail the job right away. put_job_failure(job_id, 'Stack cannot be updated when status is: ' + status) return were_updates = update_stack(stack, template) if were_updates: # If there were updates then continue the job so it can monitor # the progress of the update. continue_job_later(job_id, 'Stack update started') else: # If there were no updates then succeed the job immediately put_job_success(job_id, 'There were no stack updates') else: # If the stack doesn't already exist then create it instead # of updating it. create_stack(stack, template) # Continue the job so the pipeline will wait for the CloudFormation # stack to be created. continue_job_later(job_id, 'Stack create started') def check_stack_update_status(job_id, stack): """Monitor an already-running CloudFormation update/create Succeeds, fails or continues the job depending on the stack status. Args: job_id: The CodePipeline job ID stack: The stack to monitor """ status = get_stack_status(stack) if status in ['UPDATE_COMPLETE', 'CREATE_COMPLETE']: # If the update/create finished successfully then # succeed the job and don't continue. put_job_success(job_id, 'Stack update complete') elif status in ['UPDATE_IN_PROGRESS', 'UPDATE_ROLLBACK_IN_PROGRESS', 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS']: # If the job isn't finished yet then continue it continue_job_later(job_id, 'Stack update still in progress') else: # If the Stack is a state which isn't "in progress" or "complete" # then the stack update/create has failed so end the job with # a failed result. put_job_failure(job_id, 'Update failed: ' + status) def get_user_params(job_data): """Decodes the JSON user parameters and validates the required properties. Args: job_data: The job data structure containing the UserParameters string which should be a valid JSON structure Returns: The JSON parameters decoded as a dictionary. Raises: Exception: The JSON can't be decoded or a property is missing. """ try: # Get the user parameters which contain the stack, artifact and file settings user_parameters = job_data['actionConfiguration']['configuration']['UserParameters'] decoded_parameters = json.loads(user_parameters) except Exception as e: # We're expecting the user parameters to be encoded as JSON # so we can pass multiple values. If the JSON can't be decoded # then fail the job with a helpful message. raise Exception('UserParameters could not be decoded as JSON') if 'stack' not in decoded_parameters: # Validate that the stack is provided, otherwise fail the job # with a helpful message. raise Exception('Your UserParameters JSON must include the stack name') if 'artifact' not in decoded_parameters: # Validate that the artifact name is provided, otherwise fail the job # with a helpful message. raise Exception('Your UserParameters JSON must include the artifact name') if 'file' not in decoded_parameters: # Validate that the template file is provided, otherwise fail the job # with a helpful message. raise Exception('Your UserParameters JSON must include the template file name') return decoded_parameters def setup_s3_client(job_data): """Creates an S3 client Uses the credentials passed in the event by CodePipeline. These credentials can be used to access the artifact bucket. Args: job_data: The job data structure Returns: An S3 client with the appropriate credentials """ key_id = job_data['artifactCredentials']['accessKeyId'] key_secret = job_data['artifactCredentials']['secretAccessKey'] session_token = job_data['artifactCredentials']['sessionToken'] session = Session(aws_access_key_id=key_id, aws_secret_access_key=key_secret, aws_session_token=session_token) return session.client('s3', config=botocore.client.Config(signature_version='s3v4')) def lambda_handler(event, context): """The Lambda function handler If a continuing job then checks the CloudFormation stack status and updates the job accordingly. If a new job then kick of an update or creation of the target CloudFormation stack. Args: event: The event passed by Lambda context: The context passed by Lambda """ try: # Extract the Job ID job_id = event['CodePipeline.job']['id'] # Extract the Job Data job_data = event['CodePipeline.job']['data'] # Extract the params params = get_user_params(job_data) # Get the list of artifacts passed to the function artifacts = job_data['inputArtifacts'] stack = params['stack'] artifact = params['artifact'] template_file = params['file'] if 'continuationToken' in job_data: # If we're continuing then the create/update has already been triggered # we just need to check if it has finished. check_stack_update_status(job_id, stack) else: # Get the artifact details artifact_data = find_artifact(artifacts, artifact) # Get S3 client to access artifact with s3 = setup_s3_client(job_data) # Get the JSON template file out of the artifact template = get_template(s3, artifact_data, template_file) # Kick off a stack update or create start_update_or_create(job_id, stack, template) except Exception as e: # If any other exceptions which we didn't expect are raised # then fail the job and log the exception message. print('Function failed due to exception.') print(e) traceback.print_exc() put_job_failure(job_id, 'Function exception: ' + str(e)) print('Function complete.') return "Complete."
  6. Deje Controlador con el valor predeterminado y deje Rol con el nombre que seleccionó o creó anteriormente, CodePipelineLambdaExecRole.

  7. En Basic settings (Configuración básica), para Timeout (Tiempo de espera), sustituya el valor predeterminado de 3 segundos por 20.

  8. Seleccione Guardar.

  9. Desde la CodePipeline consola, edite la canalización para añadir la función como una acción en una etapa de la canalización. Seleccione Editar para la etapa de la canalización que deseas cambiar y selecciona Añadir grupo de acciones. En la página Editar acción, en Nombre de la acción, introduzca un nombre para su acción. En Proveedor de la acción, elija Lambda.

    En Artefactos de entrada, elija MyTemplate. En UserParameters, debes proporcionar una cadena JSON con tres parámetros:

    • Nombre de pila

    • AWS CloudFormation nombre de la plantilla y ruta al archivo

    • Artefactos de entrada

    Use corchetes ({ }) y separe los parámetros con comas. Por ejemplo, para crear una pila con el nombre MyTestStackde una canalización con el artefacto de entrada MyTemplate, introduzca: {"stack»:» MyTestStack«UserParameters, "file» :"template-package/template.json», "artifact»:» «}. MyTemplate

    nota

    Aunque hayas especificado el artefacto de entrada, también debes especificar este artefacto de entrada para la acción en UserParametersArtefactos de entrada.

  10. Guarde los cambios en la canalización y lance manualmente un cambio para probar la acción y la función de Lambda.