Comment mettre à jour le module AWS CloudFormation cfn-response pour les fonctions AWS Lambda exécutées sur Python 2.7/3.6/3.7 ?

Dernière mise à jour : 16/02/2021

Je veux mettre à jour le module AWS CloudFormation cfn-response pour les fonctions AWS Lambda exécutées sur Python 2.7/3.6/3.7.

Résolution

Remarque : les étapes suivantes s'appliquent uniquement aux fonctions Lambda exécutées sur Python 2.7/3.6/3.7. Les commandes suivantes s'appliquent aux environnements Linux et macOS. La syntaxe peut varier sur Windows PowerShell.

Remarque : Si vous recevez des erreurs lors de l'exécution de commandes depuis l'interface de ligne de commande AWS (AWS CLI), assurez-vous d'utiliser la version la plus récente d'AWS CLI.

1.    Pour rechercher les piles qui contiennent des ressources personnalisées, exécutez la commande suivante :

aws cloudformation list-stacks --region us-east-1 | grep -oE 'arn:[^"]+' | while read arn; do aws cloudformation list-stack-resources --stack-name $arn --region us-east-1 | grep -E '(Custom::)|(::CustomResource)' | awk '{print $2}' | while read resource; do if [[ -n $resource ]]; then echo $arn; echo $resource; fi; done; done

Vous devriez voir une sortie similaire à l'exemple de sortie suivant :

arn:aws:cloudformation:us-east-1:123456789012:stack/TestStack/3497b950-55f1-11eb-aad4-124a026c8667
"ResourceType": "AWS::CloudFormation::CustomResource",

2.    Pour rechercher la fonction Lambda associée à la ressource personnalisée, exécutez la commande suivante pour vérifier la propriété ServiceToken de la ressource personnalisée à partir du modèle de la pile :

aws cloudformation get-template --stack-name TestStack | jq -r .TemplateBody

Remarque : La commande de l'étape 2 prévisualise le modèle de la pile à l'aide de l'option jq (à partir du site web jq) pour formater la réponse.

Vous devriez voir une sortie similaire à l'exemple de sortie suivant :

Resources:
  MyCustomResource:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt MyFunction.Arn
      Name: "John"
  MyFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt MyRole.Arn
      Runtime: python3.7
      Code:
        ZipFile: |
          import cfnresponse
          def handler(event, context):
            responseData = {'Message': 'Hello {}!'.format(event['ResourceProperties']['Name'])}
            cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
  MyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
Outputs:
  Result:
    Value: !GetAtt MyCustomResource.Message

Remarque : le modèle obtenu à partir de la sortie de l'étape 2 est un exemple de modèle minimal pour une ressource personnalisée basée sur Lambda. La propriété ServiceToken: !GetAtt MyFunction.Arn se trouve dans la section MyCustomResource. La valeur résolue par le !GetAtt MyFunction.Arn de la propriété ServiceToken est soit l'Amazon Resource Name (ARN) de la rubrique Amazon Simple Notification Service (Amazon SNS) ou la fonction Lambda.

3.    Dans le modèle de l'étape 2, identifiez où votre fonction Lambda est définie.

Si votre fonction Lambda est dans la même pile que la ressource personnalisée, passez à l'étape 4. Par exemple, la fonction Fn::GetAtt à l'étape 2 montre que la fonction Lambda est définie dans le même modèle que la ressource personnalisée.

Si la propriété ServiceToken pointe vers un ARN codé en dur, la fonction Lambda pourrait être dans une autre pile. Si la propriété ServiceToken est résolue via Fn::Import, utilisez l'API list-export dans AWS CloudFormation pour rechercher la valeur. Par exemple :

aws cloudformation list-exports --region us-east-1
{
    "Exports": [
        {
            "ExportingStackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/SomeOtherStack/481dc040-b283-11e9-b1bd-12d607a4fd1c",
            "Value": "arn:aws:lambda:us-east-1:123456789012:function:SomeOtherStack-MyFunction-5ZE2CQO8RAA9",
            "Name": "MyExport"
        }
    ]
}

Ensuite, recherchez les balises de fonction situées dans une pile séparée en utilisant des list-tags (balises de liste) pour localiser l'ARN de la pile AWS CloudFormation. Par exemple :

aws lambda list-tags --resource arn:aws:lambda:us-east-1:123456789012:function:TestStack-MyFunction-5ZE2CQO8RAA9 | grep stack-id

Vous obtenez une sortie similaire à ce qui suit :

"aws:cloudformation:stack-id": "arn:aws:cloudformation:us-east-1:123456789012:stack/TestStack/3497b950-55f1-11eb-aad4-124a026c8667"

Remarque : vous pouvez également trouver des balises de fonction dans la console AWS Lambda.

4.    Pour permettre à AWS CloudFormation de charger le dernier module cfn-response dans votre fonction Lambda, mettez à jour le code source en ligne de votre fonction Lambda. Par exemple :

Code:
        ZipFile: |
          import cfnresponse
          def handler(event, context):
            responseData = {'Message': 'Hello {}!'.format(event['ResourceProperties']['Name'])}
            cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")

Remarque : Reportez-vous à l'étape 2 pour un exemple de modèle doté d'une fonction Lambda avec du code source en ligne.

Maintenant, l'exemple de code de module cfn-response suivant est chargé par AWS CloudFormation dans votre fonction Lambda. Par exemple :

from botocore.vendored import requests
import json
 
SUCCESS = "SUCCESS"
FAILED = "FAILED"
 
def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False):
    responseUrl = event['ResponseURL']
 
    print(responseUrl)
 
    responseBody = {}
    responseBody['Status'] = responseStatus
    responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name
    responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name
    responseBody['StackId'] = event['StackId']
    responseBody['RequestId'] = event['RequestId']
    responseBody['LogicalResourceId'] = event['LogicalResourceId']
    responseBody['NoEcho'] = noEcho
    responseBody['Data'] = responseData

Remarque : Pour plus d'informations, consultez les exemples de code dans la section « Code source du module » du module cfn-response.

L'exemple de code du module cfn-response utilise botocore.requests dans le package de déploiement de la fonction Lambda.

Pour mettre à jour le module cfn-response vers la dernière version qui utilise urllib3, mettez à jour le code en ligne de la fonction dans le modèle AWS CloudFormation. Pour ce faire, ajoutez un commentaire au code de la fonction Lambda inline. Par exemple :

ZipFile: |
           import cfnresponse
           def handler(event, context):
+            # This comment was added to force an update on this function's code
             responseData = {'Message': 'Hello {}!'.format(event['ResourceProperties']['Name'])}
             cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
   MyRole:

5.    Enregistrez toutes les modifications apportées au modèle qui contient votre fonction Lambda.

6.    Mettez à jour votre pile.

Le module cfn-response est modifié après la mise à jour de la pile.

Remarque : si le code de votre fonction réside dans un compartiment Amazon Simple Storage Service (Amazon S3) ou une image Amazon Elastic Container Registry (Amazon ECR), vous devez mettre à jour le module vous-même pour inclure la version avec urllib3. Pour obtenir le code source de la dernière version du module cfn-response, reportez-vous au module cfn-response.

Remarque : si un nouveau environnement d'exécution Python ou JavaScript introduit un changement important, vous devez mettre à jour le module cfn-response. Au lieu de mettre à jour à nouveau le fichier ZipFile, vous pouvez attacher automatiquement la dernière version du module cfn-response chaque fois que la propriété Runtime(Exécution) d'une fonction est mise à jour.


Cet article vous a-t-il été utile ?


Besoin d'aide pour une question technique ou de facturation ?