

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.

# Déploiement d'un flux de travail en attente d'approbation humaine dans Step Functions
<a name="tutorial-human-approval"></a>

Ce didacticiel vous montre comment déployer un projet d'approbation humaine qui autorise une exécution AWS Step Functions à s'interrompre exécution au cours d'une tâche, et à attendre qu'un utilisateur réponde à un e-mail. Le flux de travail progresse jusqu'à l'état suivant une fois que l'utilisateur a approuvé la tâche pour poursuivre. 

Le déploiement de la CloudFormation pile incluse dans ce didacticiel créera toutes les ressources nécessaires, notamment :
+ Ressources Amazon API Gateway
+ Et AWS Lambda fonctions
+ Une machine AWS Step Functions étatique
+ Sujet d'e-mail d'Amazon Simple Notification Service
+  Gestion des identités et des accès AWS Rôles et autorisations associés

**Note**  
Vous devrez fournir une adresse e-mail valide à laquelle vous aurez accès lors de la création de la CloudFormation pile.

Pour plus d'informations, consultez la section [Utilisation des CloudFormation modèles](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-guide.html) et la `[AWS::StepFunctions::StateMachine](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html)` ressource du *Guide de AWS CloudFormation l'utilisateur*.

## Étape 1 : Création d'un CloudFormation modèle
<a name="human-approval-deploy"></a>

1. Copiez l'exemple de code à partir de la section [CloudFormation Code source du modèle](#human-approval-yaml).

1. Collez la source du CloudFormation modèle dans un fichier sur votre ordinateur local.

   Dans cet exemple, le fichier est nommé `human-approval.yaml`.

## Étape 2 : créer une pile
<a name="human-approval-create-stack"></a>

1. Connectez-vous à la [console CloudFormation](https://console.aws.amazon.com/cloudformation/home).

1. Choisissez **Create Stack**, puis choisissez **Avec de nouvelles ressources (standard)**.

1. Sur la page **Créer une pile**, procédez de la manière suivante :

   1. Dans la section **Prérequis - Préparer le modèle**, assurez-vous que le **modèle est prêt** est sélectionné.

   1. Dans la section **Spécifier le modèle**, choisissez **Télécharger un fichier modèle**, puis **choisissez Choisir un fichier** pour télécharger le `human-approval.yaml` fichier que vous avez créé précédemment qui inclut le [code source du modèle](#human-approval-yaml).

1. Choisissez **Suivant**.

1. Sur la page **Spécifier les détails de la pile**, procédez comme suit :

   1. Dans **Nom de la pile**, entrez le nom de votre pile.

   1. Sous **Paramètres**, entrez une adresse e-mail valide. Vous utiliserez cette adresse e-mail pour vous abonner à la rubrique Amazon SNS.

1. Choisissez **Next**, puis de nouveau **Next**.

1. Sur la page de **révision**, choisissez **Je reconnais que cela CloudFormation pourrait créer des ressources IAM**, puis sélectionnez **Créer**.

   CloudFormation commence à créer votre pile et affiche le statut **CREATE\$1IN\$1PROGRESS**. Lorsque le processus est terminé, CloudFormation affiche le statut **CREATE\$1COMPLETE**.

1. (Facultatif) Pour afficher les ressources de votre pile, sélectionnez la pile et choisissez l'onglet **Ressources**.

## Étape 3 : Approuver l'abonnement Amazon SNS
<a name="human-approval-approve-sub"></a>

Une fois le sujet Amazon SNS créé, vous recevrez un e-mail vous demandant de confirmer votre inscription.

1. Ouvrez le compte e-mail que vous avez fourni lors de la création de la CloudFormation pile.

1. Ouvrez le message **AWS Notification - Confirmation d'abonnement** envoyé par **no-reply@sns.amazonaws.com**

   L'e-mail indiquera le nom de la ressource Amazon pour le sujet Amazon SNS, ainsi qu'un lien de confirmation.

1. Choisissez le lien de **confirmation de l'abonnement**.  
![\[Capture d'écran illustrative d'un e-mail de confirmation d'abonnement.\]](http://docs.aws.amazon.com/fr_fr/step-functions/latest/dg/images/tutorial-human-approval-sub-conf.png)

## Étape 4 : Exécutez la machine d'état
<a name="human-approval-run"></a>

1. Sur la **HumanApprovalLambdaStateMachine**page, choisissez **Démarrer l'exécution**.

   La boîte de dialogue **Démarrer l'exécution** s'affiche.

1. Dans la boîte de dialogue **Démarrer l'exécution**, procédez comme suit :

   1. (Facultatif) Entrez un nom d'exécution personnalisé pour remplacer le nom par défaut généré.
**Noms non ASCII et journalisation**  
Step Functions accepte les noms des machines à états, des exécutions, des activités et des étiquettes contenant des caractères non ASCII. Dans la mesure où ces caractères empêcheront Amazon CloudWatch d'enregistrer les données, nous vous recommandons de n'utiliser que des caractères ASCII afin de pouvoir suivre les métriques de Step Functions.

   1. Dans le champ **Entrée**, entrez l'entrée JSON suivante pour exécuter votre flux de travail.

      ```
      {
          "Comment": "Testing the human approval tutorial."
      }
      ```

   1. Choisissez **Start execution (Démarrer l'exécution)**.

      L'exécution de la machine à **ApprovalTest**états commence et s'arrête lors de la tâche **Lambda Callback**.

   1. La console Step Functions vous dirige vers une page intitulée avec votre ID d'exécution. Cette page est connue sous le nom de page *Détails de l'exécution*. Sur cette page, vous pouvez consulter les résultats de l'exécution au fur et à mesure que l'exécution progresse ou une fois celle-ci terminée.

      Pour consulter les résultats de l'exécution, choisissez des états individuels dans la **vue graphique**, puis choisissez les onglets individuels du [Détails de l'étape](concepts-view-execution-details.md#exec-details-intf-step-details) volet pour afficher les détails de chaque état, y compris les entrées, les sorties et la définition respectivement. Pour plus de détails sur les informations d'exécution que vous pouvez consulter sur la page *Détails de l'exécution*, voir[Vue d'ensemble des détails d'exécution](concepts-view-execution-details.md#exec-details-interface-overview).  
![\[Exécution en attente de rappel\]](http://docs.aws.amazon.com/fr_fr/step-functions/latest/dg/images/tutorial-human-approval-pause.png)

1. Dans le compte e-mail que vous avez utilisé pour le sujet Amazon SNS plus tôt, ouvrez le message dont le sujet est Required **approval from**. AWS Step Functions

   Le message contient des informations distinctes URLs pour **approuver** et **rejeter**.

1. Choisissez l'URL **Approve (Approuver)**.

   Le flux de travail continue en fonction de votre choix.  
![\[Exécution en attente de rappel\]](http://docs.aws.amazon.com/fr_fr/step-functions/latest/dg/images/tutorial-human-approval-continue.png)

## CloudFormation Code source du modèle
<a name="human-approval-yaml"></a>

Utilisez ce AWS CloudFormation modèle pour déployer un exemple de flux de travail de processus d'approbation humaine.

```
AWSTemplateFormatVersion: "2010-09-09"
Description: "AWS Step Functions Human based task example. It sends an email with an HTTP URL for approval."
Parameters:
  Email:
    Type: String
    AllowedPattern: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
    ConstraintDescription: Must be a valid email address.
Resources:
  # Begin API Gateway Resources
  ExecutionApi:
    Type: "AWS::ApiGateway::RestApi"
    Properties:
      Name: "Human approval endpoint"
      Description: "HTTP Endpoint backed by API Gateway and Lambda"
      FailOnWarnings: true

  ExecutionResource:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      RestApiId: !Ref ExecutionApi
      ParentId: !GetAtt "ExecutionApi.RootResourceId"
      PathPart: execution

  ExecutionMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      AuthorizationType: NONE
      HttpMethod: GET
      Integration:
        Type: AWS
        IntegrationHttpMethod: POST
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaApprovalFunction.Arn}/invocations"
        IntegrationResponses:
          - StatusCode: 302
            ResponseParameters:
              method.response.header.Location: "integration.response.body.headers.Location"
        RequestTemplates:
          application/json: |
            {
              "body" : $input.json('$'),
              "headers": {
                #foreach($header in $input.params().header.keySet())
                "$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end

                #end
              },
              "method": "$context.httpMethod",
              "params": {
                #foreach($param in $input.params().path.keySet())
                "$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end

                #end
              },
              "query": {
                #foreach($queryParam in $input.params().querystring.keySet())
                "$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end

                #end
              }  
            }
      ResourceId: !Ref ExecutionResource
      RestApiId: !Ref ExecutionApi
      MethodResponses:
        - StatusCode: 302
          ResponseParameters:
            method.response.header.Location: true

  ApiGatewayAccount:
    Type: 'AWS::ApiGateway::Account'
    Properties:
      CloudWatchRoleArn: !GetAtt "ApiGatewayCloudWatchLogsRole.Arn"
  
  ApiGatewayCloudWatchLogsRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"		 	 	 
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - apigateway.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: ApiGatewayLogsPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "logs:*"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"

  ExecutionApiStage:
    DependsOn:
      - ApiGatewayAccount
    Type: 'AWS::ApiGateway::Stage'
    Properties:
      DeploymentId: !Ref ApiDeployment
      MethodSettings:
        - DataTraceEnabled: true
          HttpMethod: '*'
          LoggingLevel: INFO
          ResourcePath: /*
      RestApiId: !Ref ExecutionApi
      StageName: states

  ApiDeployment:
    Type: "AWS::ApiGateway::Deployment"
    DependsOn:
      - ExecutionMethod
    Properties:
      RestApiId: !Ref ExecutionApi
      StageName: DummyStage
  # End API Gateway Resources

  # Begin
  # Lambda that will be invoked by API Gateway
  LambdaApprovalFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile:
          Fn::Sub: |
            const { SFN: StepFunctions } = require("@aws-sdk/client-sfn");
            var redirectToStepFunctions = function(lambdaArn, statemachineName, executionName, callback) {
              const lambdaArnTokens = lambdaArn.split(":");
              const partition = lambdaArnTokens[1];
              const region = lambdaArnTokens[3];
              const accountId = lambdaArnTokens[4];

              console.log("partition=" + partition);
              console.log("region=" + region);
              console.log("accountId=" + accountId);

              const executionArn = "arn:" + partition + ":states:" + region + ":" + accountId + ":execution:" + statemachineName + ":" + executionName;
              console.log("executionArn=" + executionArn);

              const url = "https://console.aws.amazon.com/states/home?region=" + region + "#/executions/details/" + executionArn;
              callback(null, {
                  statusCode: 302,
                  headers: {
                    Location: url
                  }
              });
            };

            exports.handler = (event, context, callback) => {
              console.log('Event= ' + JSON.stringify(event));
              const action = event.query.action;
              const taskToken = event.query.taskToken;
              const statemachineName = event.query.sm;
              const executionName = event.query.ex;

              const stepfunctions = new StepFunctions();

              var message = "";

              if (action === "approve") {
                message = { "Status": "Approved! Task approved by ${Email}" };
              } else if (action === "reject") {
                message = { "Status": "Rejected! Task rejected by ${Email}" };
              } else {
                console.error("Unrecognized action. Expected: approve, reject.");
                callback({"Status": "Failed to process the request. Unrecognized Action."});
              }

              stepfunctions.sendTaskSuccess({
                output: JSON.stringify(message),
                taskToken: event.query.taskToken
              })
              .then(function(data) {
                redirectToStepFunctions(context.invokedFunctionArn, statemachineName, executionName, callback);
              }).catch(function(err) {
                console.error(err, err.stack);
                callback(err);
              });
            }
      Description: Lambda function that callback to AWS Step Functions
      FunctionName: LambdaApprovalFunction
      Handler: index.handler
      Role: !GetAtt "LambdaApiGatewayIAMRole.Arn"
      Runtime: nodejs18.x

  LambdaApiGatewayInvoke:
    Type: "AWS::Lambda::Permission"
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !GetAtt "LambdaApprovalFunction.Arn"
      Principal: "apigateway.amazonaws.com"
      SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ExecutionApi}/*"

  LambdaApiGatewayIAMRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"		 	 	 
        Statement:
          - Action:
              - "sts:AssumeRole"
            Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
      Policies:
        - PolicyName: CloudWatchLogsPolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - "logs:*"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"
        - PolicyName: StepFunctionsPolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - "states:SendTaskFailure"
                  - "states:SendTaskSuccess"
                Resource: "*"
  # End Lambda that will be invoked by API Gateway

  # Begin state machine that publishes to Lambda and sends an email with the link for approval
  HumanApprovalLambdaStateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      RoleArn: !GetAtt LambdaStateMachineExecutionRole.Arn
      DefinitionString:
        Fn::Sub: |
          {
              "StartAt": "Lambda Callback",
              "TimeoutSeconds": 3600,
              "States": {
                  "Lambda Callback": {
                      "Type": "Task",
                      "Resource": "arn:${AWS::Partition}:states:::lambda:invoke.waitForTaskToken",
                      "Parameters": {
                        "FunctionName": "${LambdaHumanApprovalSendEmailFunction.Arn}",
                        "Payload": {
                          "ExecutionContext.$": "$$",
                          "APIGatewayEndpoint": "https://${ExecutionApi}.execute-api.${AWS::Region}.amazonaws.com/states"
                        }
                      },
                      "Next": "ManualApprovalChoiceState"
                  },
                  "ManualApprovalChoiceState": {
                    "Type": "Choice",
                    "Choices": [
                      {
                        "Variable": "$.Status",
                        "StringEquals": "Approved! Task approved by ${Email}",
                        "Next": "ApprovedPassState"
                      },
                      {
                        "Variable": "$.Status",
                        "StringEquals": "Rejected! Task rejected by ${Email}",
                        "Next": "RejectedPassState"
                      }
                    ]
                  },
                  "ApprovedPassState": {
                    "Type": "Pass",
                    "End": true
                  },
                  "RejectedPassState": {
                    "Type": "Pass",
                    "End": true
                  }
              }
          }

  SNSHumanApprovalEmailTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        -
           Endpoint: !Sub ${Email}
           Protocol: email
  
  LambdaHumanApprovalSendEmailFunction:
    Type: "AWS::Lambda::Function"
    Properties:
      Handler: "index.lambda_handler"
      Role: !GetAtt LambdaSendEmailExecutionRole.Arn
      Runtime: "nodejs18.x"
      Timeout: "25"
      Code:
        ZipFile:
          Fn::Sub: |
            console.log('Loading function');
            const { SNS } = require("@aws-sdk/client-sns");
            exports.lambda_handler = (event, context, callback) => {
                console.log('event= ' + JSON.stringify(event));
                console.log('context= ' + JSON.stringify(context));

                const executionContext = event.ExecutionContext;
                console.log('executionContext= ' + executionContext);

                const executionName = executionContext.Execution.Name;
                console.log('executionName= ' + executionName);

                const statemachineName = executionContext.StateMachine.Name;
                console.log('statemachineName= ' + statemachineName);

                const taskToken = executionContext.Task.Token;
                console.log('taskToken= ' + taskToken);

                const apigwEndpint = event.APIGatewayEndpoint;
                console.log('apigwEndpint = ' + apigwEndpint)

                const approveEndpoint = apigwEndpint + "/execution?action=approve&ex=" + executionName + "&sm=" + statemachineName + "&taskToken=" + encodeURIComponent(taskToken);
                console.log('approveEndpoint= ' + approveEndpoint);

                const rejectEndpoint = apigwEndpint + "/execution?action=reject&ex=" + executionName + "&sm=" + statemachineName + "&taskToken=" + encodeURIComponent(taskToken);
                console.log('rejectEndpoint= ' + rejectEndpoint);

                const emailSnsTopic = "${SNSHumanApprovalEmailTopic}";
                console.log('emailSnsTopic= ' + emailSnsTopic);

                var emailMessage = 'Welcome! \n\n';
                emailMessage += 'This is an email requiring an approval for a step functions execution. \n\n'
                emailMessage += 'Check the following information and click "Approve" link if you want to approve. \n\n'
                emailMessage += 'Execution Name -> ' + executionName + '\n\n'
                emailMessage += 'Approve ' + approveEndpoint + '\n\n'
                emailMessage += 'Reject ' + rejectEndpoint + '\n\n'
                emailMessage += 'Thanks for using Step functions!'
                
                const sns = new SNS();
                var params = {
                  Message: emailMessage,
                  Subject: "Required approval from AWS Step Functions",
                  TopicArn: emailSnsTopic
                };

                sns.publish(params)
                  .then(function(data) {
                    console.log("MessageID is " + data.MessageId);
                    callback(null);
                  }).catch(
                    function(err) {
                    console.error(err, err.stack);
                    callback(err);
                  });
            }

  LambdaStateMachineExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"		 	 	 
        Statement:
          - Effect: Allow
            Principal:
              Service: states.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: InvokeCallbackLambda
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - "lambda:InvokeFunction"
                Resource:
                  - !Sub "${LambdaHumanApprovalSendEmailFunction.Arn}"

  LambdaSendEmailExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"		 	 	 
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: CloudWatchLogsPolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"
        - PolicyName: SNSSendEmailPolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - "SNS:Publish"
                Resource:
                  - !Sub "${SNSHumanApprovalEmailTopic}"

# End state machine that publishes to Lambda and sends an email with the link for approval
Outputs:
  ApiGatewayInvokeURL:
    Value: !Sub "https://${ExecutionApi}.execute-api.${AWS::Region}.amazonaws.com/states"
  StateMachineHumanApprovalArn:
    Value: !Ref HumanApprovalLambdaStateMachine
```