Appel d'une fonction AWS Lambda dans un pipeline dans CodePipeline - AWS CodePipeline

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Appel d'une fonction AWS Lambda dans un pipeline dans CodePipeline

AWS Lambda est un service informatique qui vous permet d'exécuter un code sans demander la mise en service ou la gestion des serveurs. Vous pouvez créer des fonctions Lambda et les ajouter en tant qu'actions dans vos pipelines. Comme Lambda vous permet d'écrire des fonctions pour effectuer presque toutes les tâches, vous pouvez personnaliser le fonctionnement de votre pipeline.

Important

N'enregistrez pas l'événement JSON qui estCodePipeline envoyé à Lambda, car cela peut entraîner la connexion des informations d'identification de l'utilisateur dansCloudWatch les journaux. LeCodePipeline rôle utilise un événement JSON pour transmettre des informations d'identification temporaires à LambdaartifactCredentials sur le terrain. Pour voir un exemple d'événement, consultez la section Exemple d'événement JSON.

Voici quelques manières dont les fonctions Lambda peuvent être utilisées dans les pipelines :

  • Pour créer des ressources à la demande dans une étape d'un pipeline à l'aide d'AWS CloudFormation et les supprimer dans une autre étape.

  • Pour déployer des versions d'applications sans interruption de service à l'AWS Elastic Beanstalkaide d'une fonction Lambda qui échange les valeurs CNAME.

  • Pour déployer sur des instances Amazon ECS Docker.

  • Pour sauvegarder les ressources avant une génération ou un déploiement en créant un instantané de l'AMI.

  • Pour ajouter l'intégration avec des produits tiers à votre pipeline, comme l'envoi de messages à un client IRC.

Note

La création et l'exécution de fonctions Lambda peuvent entraîner des frais sur votreAWS compte. Pour plus d'informations, consultez Pricing (Tarification CTlong).

Cette rubrique suppose que vous connaissezAWS CodePipelineAWS Lambda et savez comment créer des pipelines, des fonctions, ainsi que les politiques et les rôles IAM dont ils dépendent. Cette rubrique vous montre comment :

  • Créez une fonction Lambda qui teste si une page Web a été déployée correctement.

  • Configurez les rôles d'exécutionCodePipeline et Lambda ainsi que les autorisations requises pour exécuter la fonction dans le cadre du pipeline.

  • Modifiez un pipeline pour ajouter la fonction Lambda en tant qu'action.

  • Testez l'action en publiant manuellement une modification.

Note

Lorsque vous utilisez une action d'appel Lambda entre régionsCodePipeline, l'état de l'exécution Lambda à l'aide du PutJobSuccessResultet PutJobFailureResultdoit être envoyé à laAWS région dans laquelle la fonction Lambda est présente et non à la région oùCodePipeline existe.

Cette rubrique inclut des exemples de fonctions pour démontrer la flexibilité de l'utilisation des fonctions Lambda dansCodePipeline :

  • Basic Lambda function

    • Création d'une fonction Lambda de base à utiliser avecCodePipeline.

    • Envoi du résultat de réussite ou d'échec à CodePipeline dans le lien Détails de l'action.

  • Modèle de fonction Python utilisant un modèle AWS CloudFormation

    • Utilisation des paramètres utilisateur codés en JSON pour transmettre plusieurs valeurs de configuration à la fonction (get_user_params).

    • Interaction avec des artéfacts .zip dans un compartiment d'artéfacts (get_template).

    • Utilisation d'un jeton de poursuite pour surveiller un processus asynchrone longue durée (continue_job_later). Cela permet à l'action de continuer et à la fonction de réussir même si elle dépasse une durée d'exécution de 15 minutes (une limite dans Lambda).

Chaque modèle de fonction inclut des informations sur les autorisations, que vous devez ajouter au rôle. Pour en savoir plus sur les limites dans AWS Lambda, consultez Limites dans le Manuel du développeur AWS Lambda.

Important

L'exemple de code, les rôles et les stratégies inclus dans cette rubrique sont fournis à titre d'exemple uniquement et tels quels.

Étape 1 : Créer un pipeline

Au cours de cette étape, vous créez un pipeline, auquel vous créez la fonction Lambda. Il s'agit du même pipeline que vous avez créé dans CodePipeline tutoriels. Si ce pipeline est toujours configuré pour votre compte et se trouve dans la même région que celle dans laquelle vous prévoyez de créer la fonction Lambda, vous pouvez ignorer cette étape.

Pour créer le pipeline
  1. Suivez les trois premières étapesDidacticiel : Création d'un pipeline simple (compartiment S3) pour créer un compartiment Amazon S3,CodeDeploy des ressources et un pipeline en deux étapes. Choisissez l'option Amazon Linux pour vos types d'instances. Vous pouvez utiliser le nom de votre choix pour le pipeline, mais dans les étapes de cette rubrique le nom MyLambdaTestPipeline est utilisé.

  2. Sur la page d'état de votre pipeline, dans l'action CodeDeploy, choisissez Détails. Sur la page des détails du déploiement pour le groupe de déploiement, choisissez un identifiant d'instance dans la liste.

  3. Dans la console Amazon EC2, dans l'onglet Détails de l'instance, copiez l'adresse IP dans Adresse IPv4 publique (par exemple,192.0.2.4). Vous utilisez cette adresse comme cible de la fonction dans AWS Lambda.

Note

La politique de rôle de service par défautCodePipeline inclut les autorisations Lambda requises pour appeler la fonction. Toutefois, si vous avez modifié le rôle du service par défaut ou que vous en avez sélectionné un autre, assurez-vous que la stratégie du rôle permet les autorisations lambda:InvokeFunction et lambda:ListFunctions. Sinon, les pipelines qui incluent des actions Lambda échouent.

Étape 2 : création de la fonction Lambda

Au cours de cette étape, vous créez une fonction Lambda qui émet une requête HTTP et recherche une ligne de texte sur une page Web. Dans le cadre de cette étape, vous créez une politique IAM et un rôle d'exécution Lambda. Pour de plus amples informations, veuillez consulter Modèle d'autorisations dans le Manuel du développeur AWS Lambda.

Pour créer le rôle d'exécution
  1. Connectez-vous à la AWS Management Console et ouvrez la console IAM à l'adresse https://console.aws.amazon.com/iam/.

  2. Sélectionnez Policies, puis Create Policy. Choisissez l'onglet JSON, puis collez la stratégie suivante dans le champ.

    { "Version": "2012-10-17", "Statement": [ { "Action": [ "logs:*" ], "Effect": "Allow", "Resource": "arn:aws:logs:*:*:*" }, { "Action": [ "codepipeline:PutJobSuccessResult", "codepipeline:PutJobFailureResult" ], "Effect": "Allow", "Resource": "*" } ] }
  3. Choisissez Review policy (Examiner une politique).

  4. Sur la page Examiner, dans Nom, tapez un nom pour la stratégie (par exemple, CodePipelineLambdaExecPolicy). Dans Description, entrez Enables Lambda to execute code.

    Choisissez Create Policy (Créer une politique).

    Note

    Il s'agit des autorisations minimales requises pour qu'une fonction Lambda puisse interagir avecCodePipeline AmazonCloudWatch. Si vous souhaitez étendre cette politique pour autoriser les fonctions qui interagissent avec d'autresAWS ressources, vous devez la modifier pour autoriser les actions requises par ces fonctions Lambda.

  5. Sur la page du tableau de bord de la stratégie, choisissez Rôles, puis Créer un rôle .

  6. Sur la page Créer un rôle, choisissez Service AWS. Choisissez Lambda, puis Suivant : Autorisations.

  7. Sur la page Attacher des stratégies d'autorisation, cochez la case à côté de CodePipelineLambdaExecPolicy, puis choisissez Suivant : Balises. Choisissez Next: Review (Suivant : Vérification).

  8. Sur la page Review (Examiner), dans Role name (Nom du rôle), saisissez le nom, puis choisissez Create role (Créer le rôle).

Pour créer l'exemple de fonction Lambda à utiliser avecCodePipeline
  1. Connectez-vous à la AWS Management Console et ouvrez la console AWS Lambda à l'adresse https://console.aws.amazon.com/lambda/.

  2. Sur la page Fonctions, choisissez Créer une fonction.

    Note

    Si vous voyez une page de bienvenue au lieu de la page Lambda, choisissez Commencer maintenant.

  3. Sur la page Create function, sélectionnez Author from scratch. Dans Nom de fonction, saisissez un nom pour votre fonction Lambda (par exemple,MyLambdaFunctionForAWSCodePipeline). Dans Runtime, choisissez Node.js 14.x.

  4. Dans Rôle, sélectionnez Choisissez un rôle existant. Dans Existing role (Rôle existant), choisissez le rôle, puis Create function (Créer la fonction).

    La page de détails de la fonction que vous avez créée s'ouvre.

  5. Copiez le code suivant dans la zone Code de fonction :

    Note

    L'objet d'événement, sous la cléCodePipeline .job, contient les détails de la tâche. Pour un exemple complet d'événement JSONCodePipeline renvoyé à Lambda, consultezExemple d'événement JSON.

    var assert = require('assert'); var AWS = require('aws-sdk'); var http = require('http'); exports.handler = function(event, context) { var codepipeline = new AWS.CodePipeline(); // Retrieve the Job ID from the Lambda action var 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. var url = event["CodePipeline.job"].data.actionConfiguration.configuration.UserParameters; // Notify CodePipeline of a successful job var putJobSuccess = function(message) { var params = { jobId: jobId }; codepipeline.putJobSuccessResult(params, function(err, data) { if(err) { context.fail(err); } else { context.succeed(message); } }); }; // Notify CodePipeline of a failed job var putJobFailure = function(message) { var params = { jobId: jobId, failureDetails: { message: JSON.stringify(message), type: 'JobFailed', externalExecutionId: context.awsRequestId } }; codepipeline.putJobFailureResult(params, function(err, data) { 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 var 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. Conservez la valeur par défaut Handler (Gestionnaire) et conservez Role (Rôle)CodePipelineLambdaExecRole.

  7. Dans Paramètres de base, pour Délai d'expiration, entrez 20 secondes.

  8. Choisissez Save (Enregistrer).

Étape 3 : Ajouter la fonction Lambda à un pipeline dans laCodePipeline console

Au cours de cette étape, vous ajoutez une nouvelle étape à votre pipeline, puis vous ajoutez une action Lambda qui appelle votre fonction à cette étape.

Pour ajouter une étape
  1. Connectez-vous à la consoleAWS Management Console et ouvrez laCodePipeline console à l'adresse http://console.aws.amazon.com/codesuite/codepipeline/home.

  2. Sur la page Welcome (Bienvenue), choisissez le pipeline que vous avez créé.

  3. Sur la page de la vue du pipeline, choisissez Edit.

  4. Sur la page Modifier, choisissez l'option + Ajouter une étape après l'étape de déploiement avec l'action CodeDeploy. Saisissez un nom pour l'étape (par exemple, LambdaStage), puis choisissez Ajouter une étape.

    Note

    Vous pouvez également choisir d'ajouter votre action Lambda à une étape existante. À des fins de démonstration, nous ajoutons la fonction Lambda comme seule action d'une étape afin de vous permettre de visualiser facilement sa progression au fur et à mesure que les artefacts progressent dans un pipeline.

  5. Choisissez + Ajouter un groupe d'actions. Dans Modifier l'action, dans Nom de l'action, entrez le nom de votre action Lambda (par exemple,MyLambdaAction). Dans Fournisseur, choisissez AWS Lambda. Dans Nom de fonction, choisissez ou saisissez le nom de votre fonction Lambda (par exemple,MyLambdaFunctionForAWSCodePipeline). Dans Paramètres utilisateur, spécifiez l'adresse IP de l'instance Amazon EC2 que vous avez copiée précédemment (par exemple,http://192.0.2.4), puis choisissez OK.

    Note

    Cette rubrique utilise une adresse IP, mais dans un cas réel, vous pourriez renseigner le nom de votre site web à la place (par exemple, http://www.example.com). Pour plus d'informations sur les données de l'événement et les gestionnaires dans AWS Lambda, consultez Modèle de programmation dans le Manuel du développeur AWS Lambda.

  6. Sur la page Modifier l'action, choisissez Enregistrer.

Étape 4 : Tester le pipeline avec la fonction Lambda

Pour tester la fonction, publiez la modification la plus récente dans le pipeline.

Pour utiliser la console afin d'exécuter la version la plus récente d'un artéfact dans un pipeline
  1. Sur la page de détails du pipeline, choisissez Publier la modification. Cette opération exécute la révision la plus récente disponible dans chaque emplacement source spécifié d'une action source à travers le pipeline.

  2. Lorsque l'action Lambda est terminée, cliquez sur le lien Détails pour afficher le flux de journal de la fonction sur AmazonCloudWatch, y compris la durée facturée de l'événement. En cas d'échec de la fonction, le journal CloudWatch fournit des informations sur la cause.

Étape 5 : étapes suivantes

Maintenant que vous avez créé avec succès une fonction Lambda et que vous l'avez ajoutée en tant qu'action dans un pipeline, vous pouvez essayer ce qui suit :

  • Ajoutez d'autres actions Lambda à votre scène pour consulter d'autres pages Web.

  • Modifiez la fonction Lambda pour rechercher une chaîne de texte différente.

  • Explorez les fonctions Lambda et créez et ajoutez vos propres fonctions Lambda aux pipelines.


                AWS LambdaAction s'exécutant dans un pipeline.

Une fois que vous aurez fini d'expérimenter la fonction Lambda, envisagez de la supprimer de votre pipelineAWS Lambda, de la supprimer et de supprimer le rôle d'IAM pour éviter d'éventuels frais. Pour plus d'informations, consultez Modifier un pipeline dans CodePipeline, Suppression d'un pipeline dans CodePipeline et Suppression des rôles ou des profils d'instance.

Exemple d'événement JSON

L'exemple suivant montre un exemple d'événement JSON, envoyé à Lambda parCodePipeline. La structure de cet événement est semblable à la réponse à l'GetJobDetails API, mais sans les types de données actionTypeId et pipelineContext. Deux détails de la configuration de l'action, FunctionName et UserParameters, sont inclus dans l'événement JSON et dans la réponse à l'API GetJobDetails. Les valeurs en italique rouge sont des exemples ou des explications, et non pas de vraies valeurs.

{ "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" } } } }

Autres modèles de fonctions

Les exemples de fonctions Lambda suivants présentent des fonctionnalités supplémentaires que vous pouvez utiliser pour vos pipelines dansCodePipeline. Pour utiliser ces fonctions, vous devrez peut-être modifier la politique du rôle d'exécution Lambda, comme indiqué dans l'introduction de chaque exemple.

Modèle de fonction Python utilisant un modèle AWS CloudFormation

Le modèle suivant présente une fonction qui crée ou met à jour une pile en fonction d'un modèle AWS CloudFormation fourni. Le modèle crée un compartiment Amazon S3. Il est présenté à des fins de démonstration seulement, pour réduire les coûts. Idéalement, vous devez supprimer la pile avant de charger quoi que ce soit dans le compartiment. Si vous chargez des fichiers dans le compartiment, vous ne pouvez pas supprimer ce dernier lorsque vous supprimez la pile. Vous devrez supprimer manuellement tout ce qui se trouve dans le compartiment avant de pouvoir supprimer le compartiment lui-même.

Cet exemple Python suppose que vous disposez d'un pipeline qui utilise un compartiment Amazon S3 comme action source, ou que vous avez accès à un compartiment Amazon S3 versionné que vous pouvez utiliser avec le pipeline. Vous créez le modèle AWS CloudFormation, le compresser et le charger dans ce compartiment en tant que fichier .zip. Vous devez ensuite ajouter une action source à votre pipeline qui récupère ce fichier .zip dans le compartiment.

Note

Lorsqu'Amazon S3 est le fournisseur source de votre pipeline, vous pouvez compresser votre ou vos fichiers source dans un seul fichier .zip et charger le .zip dans votre compartiment source. Vous pouvez également charger un seul fichier décompressé ; toutefois, les actions en aval qui attendent un fichier .zip échoueront.

Ce modèle illustre :

  • L'utilisation des paramètres utilisateur codés en JSON pour transmettre plusieurs valeurs de configuration de la fonction (get_user_params).

  • L'interaction avec des artéfacts .zip dans un compartiment d'artéfacts (get_template).

  • L'utilisation d'un jeton de poursuite pour surveiller un processus asynchrone longue durée (continue_job_later). Cela permet à l'action de continuer et à la fonction de réussir même si elle dépasse une durée d'exécution de 15 minutes (une limite dans Lambda).

Pour utiliser cet exemple de fonction Lambda, la politique relative au rôle d'exécution Lambda doit disposer d'Allowautorisations dansAWS CloudFormation Amazon S3 etCodePipeline, comme indiqué dans cet exemple de politique :

{ "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": "*" } ] }

Pour créer le modèle AWS CloudFormation, ouvrez un éditeur de texte brut et copiez-collez le code suivant :

{ "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" } } }

Enregistrez ce dernier sous la forme d'un fichier JSON avec le nom template.json dans un répertoire nommé template-package. Créez un fichier compressé (.zip) à partir de ce répertoire et du fichier nommétemplate-package.zip, puis chargez le fichier compressé dans un compartiment Amazon S3 versionné. Si vous possédez déjà un compartiment configuré pour votre pipeline, vous pouvez l'utiliser. Ensuite, modifiez votre pipeline pour ajouter une action source qui récupérera le fichier .zip. Nommez la sortie de cette action MyTemplate. Pour plus d'informations, veuillez consulter Modifier un pipeline dans CodePipeline.

Note

L'exemple de fonction Lambda attend ces noms de fichiers et cette structure compressée. Cependant, vous pouvez utiliser à la place votre propre modèle AWS CloudFormation pour ce modèle. Si vous utilisez votre propre modèle, veillez à modifier la politique du rôle d'exécution Lambda afin d'autoriser toute fonctionnalité supplémentaire requise par votreAWS CloudFormation modèle.

Pour ajouter le code suivant en tant que fonction dans Lambda
  1. Ouvrez la console Lambda et choisissez Créer une fonction.

  2. Sur la page Create function, sélectionnez Author from scratch. Dans Nom de fonction, saisissez un nom pour votre fonction Lambda.

  3. Dans Runtime, choisissez Python 2.7.

  4. Sous Choisir ou créer un rôle d'exécution, sélectionnez Utiliser un rôle existant. Dans Existing role (Rôle existant), choisissez le rôle, puis Create function (Créer la fonction).

    La page de détails de la fonction que vous avez créée s'ouvre.

  5. Copiez le code suivant dans la zone Code de fonction :

    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. Laissez Handler à la valeur par défaut et laissez Role au nom que vous avez sélectionné ou créé précédemmentCodePipelineLambdaExecRole.

  7. Dans Paramètres de base, pour Délai d'expiration, remplacez la valeur par défaut de 3 secondes par 20.

  8. Choisissez Save (Enregistrer).

  9. Depuis la console CodePipeline, modifiez le pipeline pour ajouter la fonction en tant qu'action d'une étape de votre pipeline. Choisissez Modifier pour l'étape du pipeline que vous souhaitez modifier, puis choisissez Ajouter un groupe d'actions. Sur la page Modifier l'action, dans Nom de l'action, entrez le nom de votre action. Dans Fournisseur d'actions, choisissez Lambda.

    Sous Artefacts d'entrée, choisissezMyTemplate. Dans UserParameters, vous devez fournir une chaîne JSON avec trois paramètres :

    • Nom de la pile

    • Nom de modèle et chemin d'accès au fichier AWS CloudFormation

    • Artefact d'entrée

    Utilisez des accolades ({}) et séparez les paramètres à l'aide de virgules. Par exemple, pour créer une pile nommée MyTestStack, pour un pipeline contenant l'artefact MyTemplated'entrée UserParameters, entrez : {"stack » : » MyTestStack«, "file », « template-package/template.json », "artifact » : » MyTemplate«}.

    Note

    Même si vous avez spécifié l'artefact d'entrée dans UserParameters, vous devez également spécifier cet artefact d'entrée pour l'action dans Artéfacts d'entrée.

  10. Enregistrez vos modifications dans le pipeline, puis publiez manuellement une modification pour tester l'action et la fonction Lambda.