AWS StepFunctionsLocal StepFunctions test with definition substitutions - amazon-web-services

I have been looking into StepFunctionsLocal (SFL) to test. To get a project bootstrapped, I aws the SAM cli to generate a new project - which comes pre-packed with SFL tests and a make file to run everything.
However, it seems broken out of the box. When running the tests using directions in the README, I get this error:
InvalidDefinition: An error occurred (InvalidDefinition) when calling the CreateStateMachine operation: Invalid State Machine Definition: ''SCHEMA_VALIDATION_FAILED:
Value is not a valid resource ARN at /States/Check Stock Value/Resource','SCHEMA_VALIDATION_FAILED: Value is not a valid resource ARN at /States/Sell Stock/Resource', 'SCHEMA_VALIDATION_FAILED: Value is not a valid resource ARN at /States/Buy Stock/Resource', 'SCHEMA_VALIDATION_FAILED: Value is not a valid resource ARN at /States/Record Transaction/Resource''
And, indeed, the state machine definition is provided as a file that uses DefinitionSubstitutions:
{
"Comment": "A state machine that does mock stock trading.",
"StartAt": "Check Stock Value",
"States": {
"Check Stock Value": {
"Type": "Task",
"Resource": "${StockCheckerFunctionArn}", <--
"Retry": [
{
"ErrorEquals": [
"States.TaskFailed"
],
"IntervalSeconds": 15,
"MaxAttempts": 5,
"BackoffRate": 1.5
}
],
"Next": "Buy or Sell?"
},
...
The CloudFormation template injects those values
StockTradingStateMachine:
Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html
Properties:
DefinitionUri: statemachine/stock_trader.asl.json
DefinitionSubstitutions:
StockCheckerFunctionArn: !GetAtt StockCheckerFunction.Arn <--
the makefile commands to run the test
run:
docker run -p 8083:8083 -d \
--mount type=bind,readonly,source=$(ROOT_DIR)/statemachine/test/MockConfigFile.json,destination=/home/StepFunctionsLocal/MockConfigFile.json \
-e SFN_MOCK_CONFIG="/home/StepFunctionsLocal/MockConfigFile.json" \
amazon/aws-stepfunctions-local
create:
aws stepfunctions create-state-machine \
--endpoint-url http://localhost:8083 \
--definition file://statemachine/stock_trader.asl.json \
--name "StockTradingLocalTesting" \
--role-arn "arn:aws:iam::123456789012:role/DummyRole" \
--no-cli-pager \
--debug
happypathsellstocktest:
aws stepfunctions start-execution \
--endpoint http://localhost:8083 \
--name HappyPathSellStockTest \
--state-machine arn:aws:states:us-east-1:123456789012:stateMachine:StockTradingLocalTesting#HappyPathSellStockTest \
--no-cli-pager
It appears that nothing provides the definition substitutions. I've come up dry when combing through the AWS docs for how to provide those substitutions through the API, maybe I just don't know what to look for. Any clues?
I did make an issue to fix the template: https://github.com/aws/aws-sam-cli-app-templates/issues/342

Unfortunately, DefinitionSubstitutions is a feature of the CloudFormation resource and not supported directly in the Step Functions API. You would need to parse and replace the substitution variables in your own code before you call Create State Machine in your test.

Related

AWS EventBridge discards request headers and parameters for scheduled API Destinations calls using Localstack

I am using Localstack in local environment to schedule cron calls every minute to a dummy POST endpoint (using https://webhook.site/) which works fine. However, only the body is kept (from put-targets, not from create-connection) but the request headers, query string parameters and path parameters are all discarded. Whether I test this with AWS CLI or with a Golang example (just to confirm the issue), the issue still persist. Just wondering if anyone has come across with such issue before or am I missing something? As the documentation states we could set these info on both Connection and Targets which I did just in case with both.
Localstack
version: "2.1"
services:
localstack:
image: "localstack/localstack"
container_name: "localstack"
ports:
- "4566-4599:4566-4599"
environment:
- DEBUG=1
- DEFAULT_REGION=eu-west-1
- SERVICES=events
- DATA_DIR=/tmp/localstack/data
- DOCKER_HOST=unix:///var/run/docker.sock
- LAMBDA_EXECUTOR=docker
volumes:
- "/tmp/localstack:/tmp/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
CLI example
Create rule
aws --profile localstack --endpoint-url http://localhost:4566 events put-rule \
--name http-api-cron-rule \
--schedule-expression "cron(* * * * *)"
{
"RuleArn": "arn:aws:events:eu-west-1:000000000000:rule/http-api-cron-rule"
}
Create connection
aws --profile localstack --endpoint-url http://localhost:4566 events create-connection \
--name http-api-connection \
--authorization-type Basic \
--auth-parameters "{\"BasicAuthParameters\":{\"Username\":\"hello\",\"Password\":\"world\"},\"InvocationHttpParameters\":{\"HeaderParameters\":[{\"Key\":\"hdr\",\"Value\":\"val\",\"IsValueSecret\":false}],\"QueryStringParameters\":[{\"Key\":\"qry\",\"Value\":\"val\",\"IsValueSecret\":false}],\"BodyParameters\":[{\"Key\":\"bdy\",\"Value\":\"val\",\"IsValueSecret\":false}]}}"
{
"ConnectionArn": "arn:aws:events:eu-west-1:000000000000:connection/http-api-connection/4c6a29cf-4665-41f1-b90f-a43e41712e5e",
"ConnectionState": "AUTHORIZED",
"CreationTime": "2022-01-07T17:24:57.854127+00:00",
"LastModifiedTime": "2022-01-07T17:24:57.854127+00:00"
}
Create destination
Obtain a URL from https://webhook.site first and use below.
aws --profile localstack --endpoint-url http://localhost:4566 events create-api-destination \
--name http-api-destination \
--connection-arn "arn:aws:events:eu-west-1:000000000000:connection/http-api-connection/4c6a29cf-4665-41f1-b90f-a43e41712e5e" \
--http-method POST \
--invocation-endpoint "https://webhook.site/PUT-YOUR-OWN-UUID-HERE"
{
"ApiDestinationArn": "arn:aws:events:eu-west-1:000000000000:api-destination/http-api-destination/c582470b-4413-4dba-bde9-e7d1aef64ac9",
"ApiDestinationState": "ACTIVE",
"CreationTime": "2022-01-07T17:27:27.608361+00:00",
"LastModifiedTime": "2022-01-07T17:27:27.608361+00:00"
}
Create target
aws --profile localstack --endpoint-url http://localhost:4566 events put-targets \
--rule http-api-cron-rule \
--targets '[{"Id":"1","Arn":"arn:aws:events:eu-west-1:000000000000:api-destination/http-api-destination/c582470b-4413-4dba-bde9-e7d1aef64ac9","Input":"{\"bdyx\":\"val\"}","HttpParameters":{"PathParameterValues":["parx"],"HeaderParameters":{"hdrx":"val"},"QueryStringParameters":{"qryx":"val"}}}]'
{
"Targets": [
{
"Id": "1",
"Arn": "arn:aws:events:eu-west-1:000000000000:api-destination/http-api-destination/c582470b-4413-4dba-bde9-e7d1aef64ac9",
"Input": "{\"bdyx\":\"val\"}",
"HttpParameters": {
"PathParameterValues": [
"parx"
],
"HeaderParameters": {
"hdrx": "val"
},
"QueryStringParameters": {
"qryx": "val"
}
}
}
]
}
Logs
localstack | DEBUG:localstack.services.events.events_listener: Notifying 1 targets in response to triggered Events rule http-cron-rule
localstack | DEBUG:localstack.utils.aws.message_forwarding: Calling EventBridge API destination (state "ACTIVE"): POST https://webhook.site/your-uuid-goes-here

How to Create an API with VPC Link Integration for EKS?

I have a working EKS Cluster with some services running in there. The challenge right now, is to have an API gateways to call those services. That is why I started reading "Integrate Amazon API Gateway with Amazon EKS" explaining that this is possible but there are some steps to do in order to make this work. This is what I did but after some progress, I ran into an issue.
Here is one of the command-line statements I executed.
aws apigatewayv2 get-vpc-links --region $AGW_AWS_REGION
This results in the following output:
Apparently, the VPC link is available, a security group Id is available, two SubnetIds are available (in the documented example it is three but that should not matter so much, feel free to comment about this) and the status message says that the available VPC link is ready to route traffic.
There for I decided to continue with the next documented step:
Create an API with VPC Link integration
cat > apigw-api.yaml<<EOF
apiVersion: apigatewayv2.services.k8s.aws/v1alpha1
kind: API
metadata:
name: apitest-private-nlb
spec:
body: '{
"openapi": "3.0.1",
"info": {
"title": "ack-apigwv2-import-test-private-nlb",
"version": "v1"
},
"paths": {
"/\$default": {
"x-amazon-apigateway-any-method" : {
"isDefaultRoute" : true,
"x-amazon-apigateway-integration" : {
"payloadFormatVersion" : "1.0",
"connectionId" : "$(kubectl get vpclinks.apigatewayv2.services.k8s.aws \
nlb-internal \
-o jsonpath="{.status.vpcLinkID}")",
"type" : "http_proxy",
"httpMethod" : "GET",
"uri" : "$(aws elbv2 describe-listeners \
--load-balancer-arn $(aws elbv2 describe-load-balancers \
--region $AGW_AWS_REGION \
--query "LoadBalancers[?contains(DNSName, '$(kubectl get service authorservice \
-o jsonpath="{.status.loadBalancer.ingress[].hostname}")')].LoadBalancerArn" \
--output text) \
--region $AGW_AWS_REGION \
--query "Listeners[0].ListenerArn" \
--output text)",
"connectionType" : "VPC_LINK"
}
}
},
"/meta": {
"get": {
"x-amazon-apigateway-integration": {
"uri" : "$(aws elbv2 describe-listeners \
--load-balancer-arn $(aws elbv2 describe-load-balancers \
--region $AGW_AWS_REGION \
--query "LoadBalancers[?contains(DNSName, '$(kubectl get service echoserver \
-o jsonpath="{.status.loadBalancer.ingress[].hostname}")')].LoadBalancerArn" \
--output text) \
--region $AGW_AWS_REGION \
--query "Listeners[0].ListenerArn" \
--output text)",
"httpMethod": "GET",
"connectionId": "$(kubectl get vpclinks.apigatewayv2.services.k8s.aws \
nlb-internal \
-o jsonpath="{.status.vpcLinkID}")",
"type": "HTTP_PROXY",
"connectionType": "VPC_LINK",
"payloadFormatVersion": "1.0"
}
}
}
},
"components": {}
}'
EOF
Unfortunately, this resulted in the following errors:
I tried to isolate the problem but then I ran into another issue. In the command-line, I just executed a small fragment of what is shown above.
aws elbv2 describe-load-balancers --load-balancer-arn --region $AGW_AWS_REGION query "LoadBalancers[?contains(DNSName, '$(kubectl get service authorservice -o jsonpath="{.status.loadBalancer.ingress[].hostname}")')].LoadBalancerArn" --output text
but that resulted in another error:
An error occurred (ValidationError) when calling the
DescribeLoadBalancers operation: 'query' is not a valid load balancer
ARN
So how to get this working? All I want is to just to create an API with VPC link integration. But the documented way to do so does not work for me. Please let me know how I can fix this way or just do the same in a different way.
I am exactly on the same step - trying to create API for micro service on EKS following the same tutorial. I also started to break down the executions to the small chunks to check if things are still correct from the time the tutorial was written.
So taking the smallest piece which should be executable
kubectl get service authorservice -o jsonpath="{.status.loadBalancer.ingress[].hostname}"
You must have the "authorservice" already configured and running or it might be called something else for your particular case.
For me that lines give me back the needed hostname. If it is not giving you the hostname you better check with
kubectl get service <the_service_you_have> -o json
whether the service you have is properly configured and having at the end of the output something similar to
"status": {
"loadBalancer": {
"ingress": [
{
"hostname": "k8s-XXXXXXXXXXXXXXXXXXXXXX.elb.us-west-2.amazonaws.com"
}
]
}
My advice is to go over all the "kubectl" executions and check if they are working and giving back some meaningful results for your case and adjust if needed. At first it might be hard to get where the "kubectl" commands ends but later it gets easy.

Specify AWS CodeDeploy Target instances in AWS CodePipeline for Blue/Green deployment

I'm trying to create a CodePipeline to deploy an application to EC2 instances using Blue/Green Deployment.
My Deployment Group looks like this:
aws deploy update-deployment-group \
--application-name MySampleAppDeploy \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--service-role-arn arn:aws:iam::1111111111:role/CodeDeployRole \
--ec2-tag-filters Key=Stage,Type=KEY_AND_VALUE,Value=Blue \
--deployment-style deploymentType=BLUE_GREEN,deploymentOption=WITH_TRAFFIC_CONTROL \
--load-balancer-info targetGroupInfoList=[{name="sample-app-alb-targets"}] \
--blue-green-deployment-configuration file://configs/blue-green-deploy-config.json \
--current-deployment-group-name MySampleAppDeployGroup
blue-green-deploy-config.json
{
"terminateBlueInstancesOnDeploymentSuccess": {
"action": "KEEP_ALIVE",
"terminationWaitTimeInMinutes": 1
},
"deploymentReadyOption": {
"actionOnTimeout": "STOP_DEPLOYMENT",
"waitTimeInMinutes": 1
},
"greenFleetProvisioningOption": {
"action": "DISCOVER_EXISTING"
}
}
I'm able to create a blue/green deployment manually using this command, it Works! :
aws deploy create-deployment \
--application-name MySampleAppDeploy \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name MySampleAppDeployGroup \
# I can specify the Target Instances here
--target-instances file://configs/blue-green-target-instances.json \
--s3-location XXX
blue-green-target-instances.json
{
"tagFilters": [
{
"Key": "Stage",
"Value": "Green",
"Type": "KEY_AND_VALUE"
}
]
}
Now, In my CodePipeline Deploy Stage, I have this:
{
"name": "Deploy",
"actions": [
{
"inputArtifacts": [
{
"name": "app"
}
],
"name": "Deploy",
"actionTypeId": {
"category": "Deploy",
"owner": "AWS",
"version": "1",
"provider": "CodeDeploy"
},
"outputArtifacts": [],
"configuration": {
"ApplicationName": "MySampleAppDeploy",
"DeploymentGroupName": "MySampleAppDeployGroup"
/* How can I specify Target Instances here? */
},
"runOrder": 1
}
]
}
All EC2 instances are tagged correctly and everything works as expected when using CodeDeploy via the command line, I'm missing something about how AWS CodePipeline works in this case.
Thanks
You didn't mention which error you get when you invoke the pipeline? Are you getting this error:
"The deployment failed because no instances were found in your green fleet"
Taking this assumption, since you are using manual tagging in your CodeDeploy configuration, this is not going to work to deploy using Blue/Green with manual tags as CodeDeploy expects to see a tagSet to find the "Green" instances and there is no way to provide this information via CodePipeline.
To workaround this, please use the 'Copy AutoScaling' option for implementing Blue/Green deployments in CodeDeploy using CodePipeline. See Step 10 here [1]
Another workaround is that you can create lambda function that is invoked as an action in your CodePipeline. This lambda function can be used to trigger the CodeDeploy deployment where you specify the target-instances with the value of the green AutoScalingGroup. You will then need to make describe calls at frequent intervals to the CodeDeploy API to get the status of the deployment. Once the deployment has completed, your lambda function will need to signal back to the CodePipeline based on the status of the deployment.
Here is an example which walks through how to invoke an AWS lambda function in a pipeline in CodePipeline [2].
Ref:
[1] https://docs.aws.amazon.com/codedeploy/latest/userguide/applications-create-blue-green.html
[2] https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html

Setting Environment Variables per step in AWS EMR

I am unable to set environment variables for my spark application. I am using AWS EMR to run a spark application. Which is more like a framework I wrote in python on top of spark, to run multiple spark jobs according to environment variables present. So in order for me to start the exact job, I need to pass the environment variable into the spark-submit. I tried several methods to do this. But none of them works. As I try to print the value of the environment variable inside the application it returns empty.
To run the cluster in the EMR I am using following AWS CLI command
aws emr create-cluster --applications Name=Hadoop Name=Hive Name=Spark --ec2-attributes '{"KeyName":"<Key>","InstanceProfile":"<Profile>","SubnetId":"<Subnet-Id>","EmrManagedSlaveSecurityGroup":"<Group-Id>","EmrManagedMasterSecurityGroup":"<Group-Id>"}' --release-label emr-5.13.0 --log-uri 's3n://<bucket>/elasticmapreduce/' --bootstrap-action 'Path="s3://<bucket>/bootstrap.sh"' --steps file://./.envs/steps.json --instance-groups '[{"InstanceCount":1,"InstanceGroupType":"MASTER","InstanceType":"c4.xlarge","Name":"Master"}]' --configurations file://./.envs/Production.json --ebs-root-volume-size 64 --service-role EMRRole --enable-debugging --name 'Application' --auto-terminate --scale-down-behavior TERMINATE_AT_TASK_COMPLETION --region <region>
Now Production.json looks like this:
[
{
"Classification": "yarn-env",
"Properties": {},
"Configurations": [
{
"Classification": "export",
"Properties": {
"FOO": "bar"
}
}
]
},
{
"Classification": "spark-defaults",
"Properties": {
"spark.executor.memory": "2800m",
"spark.driver.memory": "900m"
}
}
]
And steps.json like this :
[
{
"Name": "Job",
"Args": [
"--deploy-mode","cluster",
"--master","yarn","--py-files",
"s3://<bucket>/code/dependencies.zip",
"s3://<bucket>/code/__init__.py",
"--conf", "spark.yarn.appMasterEnv.SPARK_YARN_USER_ENV=SHAPE=TRIANGLE",
"--conf", "spark.yarn.appMasterEnv.SHAPE=RECTANGLE",
"--conf", "spark.executorEnv.SHAPE=SQUARE"
],
"ActionOnFailure": "CONTINUE",
"Type": "Spark"
}
]
When I try to access the environment variable inside my __init__.py code, it simply prints empty. As you can see I am running the step using spark with yarn cluster in cluster mode. I went through these links to reach this position.
How do I set an environment variable in a YARN Spark job?
https://spark.apache.org/docs/latest/configuration.html#environment-variables
https://spark.apache.org/docs/latest/configuration.html#runtime-environment
Thanks for any help.
Use classification yarn-env to pass environment variables to the worker nodes.
Use classification spark-env to pass environment variables to the driver, with deploy mode client. When using deploy mode cluster, use yarn-env.
(Dear moderator, if you want to delete the post, let me know why.)
To work with EMR clusters I work using the AWS Lambda, creating a project that build an EMR cluster when a flag is set in the condition.
Inside this project, we define the variables that you can set in the Lambda and then, replace this to its value. To use this, we have to use the AWS API. The possible method you have to use is the AWSSimpleSystemsManagement.getParameters.
Then, make a map like val parametersValues = parameterResult.getParameters.asScala.map(k => (k.getName, k.getValue)) to have a tuple with its name and value.
Eg: ${BUCKET} = "s3://bucket-name/
What this means, you only have to write in your JSON ${BUCKET} instead all the name of your path.
Once you have replace the value, the step JSON can have a view like this,
[
{
"Name": "Job",
"Args": [
"--deploy-mode","cluster",
"--master","yarn","--py-files",
"${BUCKET}/code/dependencies.zip",
"${BUCKET}/code/__init__.py",
"--conf", "spark.yarn.appMasterEnv.SPARK_YARN_USER_ENV=SHAPE=TRIANGLE",
"--conf", "spark.yarn.appMasterEnv.SHAPE=RECTANGLE",
"--conf", "spark.executorEnv.SHAPE=SQUARE"
],
"ActionOnFailure": "CONTINUE",
"Type": "Spark"
}
]
I hope this can help you to solve your problem.

Update apigateway deployment with cli

How do I update a apigateway deployment using the cli? I can find the update-deployment command, but I do not know what to put in for the values it needs in this skeleton (taken from the documentation):
{
"op": "add"|"remove"|"replace"|"move"|"copy"|"test",
"path": "string",
"value": "string",
"from": "string"
}
update-deployment is used to update the metadata of an existing deployment. For example, to update the description of a deployment:
aws apigateway update-deployment \
--rest-api-id <value> \
--deployment-id <value> \
--patch-operations 'op=replace,path=/description,value=<value>'
If you wanted to re-deploy an API (this is what happens when you click on "Deploy API" in the web console), you'd use the create-deployment command:
aws apigateway create-deployment \
--rest-api-id <value> \
--stage-name <value>