Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.
In diesem Tutorial erfahren Sie, wie Sie ein Projekt mit Genehmigungen durch Menschen bereitstellen, das das Anhalten einer AWS Step Functions -Ausführung während einer Aufgabe und das Warten auf die E-Mail-Antwort eines Benutzers ermöglicht. Der Workflow wird mit der nächsten Phase fortgesetzt, sobald der Benutzer die Aufgabe genehmigt hat.
Durch die Bereitstellung des in diesem Tutorial enthaltenen AWS CloudFormation Stacks werden alle erforderlichen Ressourcen bereitgestellt, darunter:
-
Ressourcen für Amazon API Gateway
-
Und AWS Lambda Funktionen
-
Eine AWS Step Functions Staatsmaschine
-
Ein E-Mail-Thema von Amazon Simple Notification Service
-
Verwandte AWS Identity and Access Management Rollen und Berechtigungen
Anmerkung
Sie müssen eine gültige E-Mail-Adresse angeben, auf die Sie Zugriff haben, wenn Sie den AWS CloudFormation Stack erstellen.
Weitere Informationen finden Sie unter Arbeiten mit CloudFormation Vorlagen und in der AWS::StepFunctions::StateMachine
Ressource im AWS CloudFormation Benutzerhandbuch.
Schritt 1: Erstellen Sie eine AWS CloudFormation Vorlage
-
Kopieren Sie den Beispielcode aus dem Abschnitt AWS CloudFormation Quellcode der Vorlage.
-
Fügen Sie die Quelle der AWS CloudFormation Vorlage in eine Datei auf Ihrem lokalen Computer ein.
In diesem Beispiel hat die Datei den Namen
human-approval.yaml
.
Schritt 2: Erstellen Sie einen Stapel
-
Melden Sie sich an der AWS CloudFormation -Konsole
an. -
Wählen Sie „Stack erstellen“ und anschließend „Mit neuen Ressourcen (Standard)“.
-
Gehen Sie auf der Seite Create stack (Stack erstellen) wie folgt vor:
-
Vergewissern Sie sich, dass im Abschnitt Voraussetzung — Vorlage vorbereiten die Option Vorlage ist bereit ausgewählt ist.
-
Wählen Sie im Abschnitt Vorlage angeben die Option Vorlagendatei hochladen und dann Datei auswählen aus, um die zuvor erstellte
human-approval.yaml
Datei hochzuladen, die den Quellcode der Vorlage enthält.
-
-
Wählen Sie Weiter.
-
Führen Sie auf der Seite Specify DB Details (Festlegen von DB-Detail) die folgenden Schritte aus:
-
Geben Sie unter Stackname einen Namen für Ihren Stack ein.
-
Geben Sie unter Parameter eine gültige E-Mail-Adresse ein. Sie verwenden diese E-Mail-Adresse, um das Amazon SNS SNS-Thema zu abonnieren.
-
-
Wählen Sie Weiter und dann erneut Weiter.
-
Wählen Sie auf der Seite Überprüfen die Option Ich bestätige, dass AWS CloudFormation möglicherweise IAM-Ressourcen erstellt werden, und wählen Sie dann Erstellen aus.
AWS CloudFormation beginnt mit der Erstellung Ihres Stacks und zeigt den Status CREATE_IN_PROGRESS an. Wenn der Vorgang abgeschlossen ist, wird der Status CREATE_COMPLETE AWS CloudFormation angezeigt.
-
(Optional) Wählen Sie zum Anzeigen der Ressourcen in Ihrem Stack den Stack und anschließend die Registerkarte Resources aus.
Schritt 3: Genehmigen Sie das Amazon SNS SNS-Abonnement
Sobald das Amazon SNS SNS-Thema erstellt wurde, erhalten Sie eine E-Mail, in der Sie aufgefordert werden, das Abonnement zu bestätigen.
-
Öffnen Sie das E-Mail-Konto, das Sie bei der Erstellung des AWS CloudFormation Stacks angegeben haben.
-
Öffnen Sie die Nachricht AWS Benachrichtigung — Abonnementbestätigung von no-reply@sns.amazonaws.com
In der E-Mail werden der Amazon-Ressourcenname für das Amazon SNS-Thema sowie ein Bestätigungslink aufgeführt.
-
Wählen Sie den Link confirm subscription (Abonnement bestätigen) aus.
Schritt 4: Führen Sie die Zustandsmaschine aus
-
Wählen Sie auf der HumanApprovalLambdaStateMachineSeite Ausführung starten aus.
Das Dialogfeld Ausführung starten wird angezeigt.
-
Gehen Sie im Dialogfeld Ausführung starten wie folgt vor:
-
(Optional) Geben Sie einen benutzerdefinierten Ausführungsnamen ein, um den generierten Standard zu überschreiben.
Nicht-ASCII-Namen und Protokollierung
Step Functions akzeptiert Namen für Zustandsmaschinen, Ausführungen, Aktivitäten und Beschriftungen, die Nicht-ASCII-Zeichen enthalten. Da solche Zeichen mit Amazon nicht funktionieren, empfehlen wir CloudWatch, nur ASCII-Zeichen zu verwenden, damit Sie die Messwerte verfolgen können. CloudWatch
-
Geben Sie in das Eingabefeld die folgende JSON-Eingabe ein, um Ihren Workflow auszuführen.
{ "Comment": "Testing the human approval tutorial." }
-
Wählen Sie Start execution (Ausführung starten) aus.
Die Ausführung der ApprovalTestZustandsmaschine wird bei der Lambda-Callback-Aufgabe gestartet und angehalten.
-
Die Step Functions Functions-Konsole leitet Sie zu einer Seite weiter, die mit Ihrer Ausführungs-ID betitelt ist. Diese Seite wird als Seite mit den Ausführungsdetails bezeichnet. Auf dieser Seite können Sie die Ausführungsergebnisse im Verlauf der Ausführung oder nach deren Abschluss überprüfen.
Um die Ausführungsergebnisse zu überprüfen, wählen Sie in der Diagrammansicht einzelne Status aus und wählen Sie dann die einzelnen Registerkarten im Einzelheiten zu den Schritten Bereich, um die Details der einzelnen Status, einschließlich Eingabe, Ausgabe und Definition, anzuzeigen. Einzelheiten zu den Ausführungsinformationen, die Sie auf der Seite mit den Ausführungsdetails einsehen können, finden Sie unterÜberblick über die Ausführungsdetails.
-
-
Öffnen Sie in dem E-Mail-Konto, das Sie zuvor für das Amazon SNS SNS-Thema verwendet haben, die Nachricht mit dem Betreff Genehmigung erforderlich von AWS Step Functions.
Die Nachricht enthält separate Optionen URLs für Genehmigen und Ablehnen.
-
Wählen Sie die URL für Approve (Genehmigen) aus.
Der Workflow wird abhängig von Ihrer Wahl fortgesetzt.
AWS CloudFormation Quellcode der Vorlage
Verwenden Sie diese AWS CloudFormation Vorlage, um ein Beispiel für einen menschlichen Genehmigungsprozess bereitzustellen.
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