Getting Started / Hands-on / ...
Follow the step-by-step instructions below to build a serverless backend. Click on each step number to expand the section.
-
Step 1. Create an IAM policy for a custom IAM role.
To grant permissions to handle backend requests for your website, you create a custom IAM policy that grants permissions for the following AWS services:
- Step Functions
- StartExecution (stateMachine:examplecorp_eao)
- Sts:AssumeRole
- Lambda
- function:examplecorp_eao_createfleet
- function:examplecorp_eao_createimagebuilder
- function:examplecorp_eao_getimagename
- function:examplecorp_eao_getstatus
- function:examplecorp_eao_sendstreamingurl
- function:examplecorp_eao_stopresources
- Sts:AssumeRole
- AppStream 2.0
- AssociateFleet (Fleet/*)
- CreateFleet (Fleet/*)
- CreateImageBuilder (Image-Builder/*)
- CreateImageBuilderStreamingURL (Image-Builder/*)
- CreateStack (Stack/*)
- CreateStreamingURL (Fleet/*)
- DescribeFleets (Fleet/*)
- DescribeImageBuilders (Image-Builder/*)
- DescribeImages (Image/*)
- StartFleet (Fleet/*)
- StartImageBuilder (Image-Builder/*)
- StopFleet (Fleet/*)
- StopImageBuilder (Image-Builder/*)
- SES
- SendEmail
- API Gateway
- Sts:AssumeRole
- CloudWatch
- *
Complete the following steps to create the custom IAM policy.
1. Open the IAM console at https://console.aws.amazon.com/iam/
2. In the navigation pane, choose Policies.
3. If this is your first time choosing Policies, the Welcome to Managed Policies page appears. Choose Get Started.
4. Choose Create policy.
5. Choose the JSON tab.
6. Copy and paste the following JSON policy into the policy document field.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "lambda:InvokeFunction", "appstream:DescribeImages", "appstream:StartFleet", "appstream:StopImageBuilder", "appstream:CreateStack", "appstream:CreateImageBuilderStreamingURL", "appstream:AssociateFleet", "appstream:DescribeImageBuilders", "appstream:StopFleet", "appstream:CreateImageBuilder", "appstream:CreateFleet", "appstream:DescribeFleets", "states:StartExecution", "appstream:StartImageBuilder", "appstream:CreateStreamingURL" ], "Resource": [ "arn:aws:appstream:*:*:fleet/*", "arn:aws:appstream:*:*:image/*", "arn:aws:appstream:*:*:stack/*", "arn:aws:appstream:*:*:image-builder/*", "arn:aws:states:<REGION-CODE>:<AWS-ACCOUNT-ID>:stateMachine:examplecorp_eao", "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_sendstreamingurl", "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_createimagebuilder", "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_getstatus", "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_getimagename", "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_createfleet", "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_stopresources" ] }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "ses:SendEmail", "logs:CreateLogStream", "cloudwatch:*", "logs:CreateLogGroup", "logs:PutLogEvents" ], "Resource": "*" } ] }
7. Replace <REGION-CODE> with the value that corresponds to the AWS Region where you are deploying the application onboarding portal. The value must be in all lowercase. For a list of AWS Region codes for AppStream 2.0, see the Region column in the Amazon AppStream 2.0 endpoints table in the AWS General Reference. Replace <AWS-ACCOUNT-ID> with your AWS Account ID.
8. When you’re done, choose Review policy.
9. For Name, type the following name for your new policy: examplecorp_eao_policy.
10. Choose Create policy.
- Step Functions
-
Step 2. Create an IAM service-linked role that lets Lambda functions call AWS services.
Lambda requires an IAM service-linked role to allow the service to access resources in other services on your behalf. Complete the following steps to create an IAM service-linked role and attach the policy that you created to this role.
1. Open the IAM console at https://console.aws.amazon.com/iam/
2. In the navigation pane, under Roles, choose Create role.
3. For Select type of trusted entity, keep AWS service selected.
4. Choose Lambda, and then choose Next: Permissions.
5. In the Filter policies search box, type examplecorp_eao_policy. When the policy appears in the list, select the check box next to the policy name.
6. Choose Next: Tags. Although you can specify a tag for the policy, a tag is not required.
7. Choose Next: Review.
8. For Role name, type examplecorp_eao_role.
9. Choose Create role.
10. In the search box, type examplecorp_eao_role. When the role appears in the list, select the name of the role.
11. At the top of the Summary page, the Role ARN displays. Make a note of this ARN. This ARN is required later in the project.
12. Choose the Trust relationships tab, and then choose Edit trust relationship.
13. Under Policy Document, configure the trust relationship policy to include the sts:AssumeRole action for the lambda.amazonaws.com, states.amazonaws.com, and apigateway.amazonaws.com service principals. The format should appear as follows:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": [ "lambda.amazonaws.com", “states.amazonaws.com”, "apigateway.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }
14. When you’re done, choose Update Trust Policy to save your changes.
-
Step 3. Create and configure six Lambda functions.
Complete the following steps to create six Lambda functions.
1. Open the Lambda console at https://console.aws.amazon.com/lambda/
2. Do one of the following:
• If you haven’t created any Lambda functions, a Getting Started page displays. Under Getting Started, choose Create a function.
• If you have created a Lambda function, in the upper right corner of the Functions page, choose Create function.
3. On the Create function page, keep Author from scratch selected
4. Under Basic information, do the following:
• For Name, type examplecorp_eao_createimagebuilder.
• For Runtime, choose Python 3.7.
5. Under Permissions, choose the icon next to Choose or create an execution role. Then do the following:
• For Execution role, choose Use an existing role.
• For Existing role, choose examplecorp_eao_role from the list.
6. Choose Create function.
7. In the Function code section, on the lambda_function tab, the placeholder code displays. Delete the placeholder code, and copy and paste the following code onto the tab:
from __future__ import print_function import json import boto3 appstream = boto3.client('appstream') def lambda_handler(event, context): # Log the received event print("Received event: " + json.dumps(event, indent=2)) # Get variables from web form strIBName = event['ib_name'] strIBARN = event['ib_arn'] strInstType = event['instance_type'] strSubnetId = event['subnet1id'] strSecurityGroups = event['securitygroups'] strUserEmail = event['email'] strUserName = event['user_name'] strUserEmail = strUserEmail.strip() strUserName= strUserName.strip() try: response = appstream.create_image_builder( Name=strIBName, ImageArn=strIBARN, VpcConfig={ 'SubnetIds':[ strSubnetId ], 'SecurityGroupIds':[ strSecurityGroups ] }, InstanceType=strInstType) print("Started: " + strIBName) return 1 except Exception as e: print(e) message = 'Error creating image builder' print(message)
8. Save the function. This function creates an AppStream 2.0 image builder.
9. Repeat steps 2 through 8 for each of the following Lambda functions.
Note: For step 7, paste the code for the appropriate Lambda function.Lambda function 2: Get the status of the AppStream 2.0 image builder and fleet
When you create this function, name it examplecorp_eao_getstatus.
from __future__ import print_function import json import boto3 print('Loading function') appstream = boto3.client('appstream') def Status_to_Return(received_state): switcher = { "PENDING": 0, "RUNNING": 1, "SNAPSHOTTING": 2, "STOPPED": 3, "STOPPING": 4, "STARTING": 5 } return switcher.get(received_state, -1) def lambda_handler(event, context): # Log the received event print("Received event: " + json.dumps(event, indent=2)) # Get names to query against strIBName = event['ib_name'] strFleetName = event['fleet_name'] strType = event['type'] try: #Fleet status check if strType == 2: print ("Type is Fleet") AS2state = appstream.describe_fleets(Names=[strFleetName]) Status = AS2state['Fleets'][0]['State'] print ("State: " + Status) #Image Builder status check elif strType == 1: print ("Type is ImageBuilder") AS2state = appstream.describe_image_builders(Names=[strIBName]) Status = AS2state['ImageBuilders'][0]['State'] print ("State: " + Status) return Status_to_Return(Status) except Exception as e: print(e) print("Error getting status") return -1
Lambda function 3: Send a streaming URL
When you create this function, name it examplecorp_eao_sendstreamingurl.
Note: Before you save this function, search for <REGION-CODE> and replace with the region code that corresponds to the AWS Region where you configured SES. Then search for Source='noreply@example.com' and change this value to the email address that you verified in SES in the previous module
from __future__ import print_function import json import boto3 print('Loading function') appstream = boto3.client('appstream') ses = boto3.client('ses',region_name=<REGION-CODE>) def lambda_handler(event, context): # Log the received event print("Received event: " + json.dumps(event, indent=2)) # Get resource names strUserName = event['user_name'] strUserName = strUserName.strip() strEmail = event['email'] strEmail = strEmail.strip() strIBName = event['ib_name'] strFleetName = event['fleet_name'] strStackName = event['stack_name'] strType = event['type'] try: #Create Fleet Streaming URL if strType == 2: print ("Type is Fleet") mailBody = "Your fleet is now created and ready for verification. Please use the connect link below to connect to the instance." url = appstream.create_streaming_url( StackName=strStackName, FleetName=strFleetName, UserId=strUserName, Validity=604800 ) #Create Image Builder Streaming URL elif strType == 1: print ("Type is ImageBuilder") mailBody = "If this is your first time installing your application with AppStream2.0, you can check the getting started guide below for assistance. Otherwise, please click the connect link below to get started." url = appstream.create_image_builder_streaming_url( Name=strIBName, Validity=604800 ) strStreamingURL = url['StreamingURL'] print("Streaming URL: " + strStreamingURL) #Send Streaming URL in E-mail via SES ses.send_email( Source='noreply@example.com', Destination={ 'ToAddresses': [strEmail], }, Message={ 'Body': { 'Html': { 'Charset': 'UTF-8', 'Data': '<html><head><title>Example Corp</title></head><body bgcolor="#e0e0e0">' + strUserName + ',<p>Thank you for trying out the Example Corp <strong>Enterprise Application Onboarding</strong> demo. ' + mailBody + '</p><p><a href=" ' + strStreamingURL + ' ">Connect</a></p><a href="https://d1.awsstatic.com/product-marketing/AppStream2.0/Amazon%20AppStream%202.0%20Getting%20Started%20Guide%20April%202018.pdf">Getting Started Guide</a></body></html>', }, }, 'Subject': { 'Charset': 'UTF-8', 'Data': 'Example Corp Enterprise Application Onboarding for ' + strUserName }, } ) return 1 except Exception as e: print(e) print("Error getting streaming URL") return -1
Lambda function 4: Get the AppStream 2.0 image name
When you create this function, call it examplecorp_eao_getimagename.
from __future__ import print_function import json import boto3 import datetime print('Loading function') appstream = boto3.client('appstream') def lambda_handler(event, context): # Log the received event print("Received event: " + json.dumps(event, indent=2)) # Get names to query against strIBName = event['ib_name'] try: ImagesCMD = appstream.describe_images(Type='PRIVATE') Images = ImagesCMD['Images'] for Image in Images: if Image.get('ImageBuilderName','') == strIBName: ImageTime = Image['CreatedTime'] now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) howold = now - ImageTime HowOldSec = howold.total_seconds() if HowOldSec < 3600: imagename = Image['Name'] return (imagename) except Exception as e: print(e) print("Error getting Image Name") return -1
Lambda function 5: Create an AppStream fleet and stack, and associate the fleet with a stack
When you create this function, name it examplecorp_eao_createfleet.
from __future__ import print_function import json import boto3 print('Loading function') appstream = boto3.client('appstream') def lambda_handler(event, context): # Log the received event print("Received event: " + json.dumps(event, indent=2)) strFleetName = event['fleet_name'] strStackName = event['stack_name'] strImageName = event['imagename'] strInstType = event['instance_type'] strSubnet1Id = event['subnet1id'] strSubnet2Id = event['subnet2id'] strSecurityGroups = event['securitygroups'] try: #Create AppStream fleet fleet = appstream.create_fleet( Name=strFleetName, ImageName=strImageName, ComputeCapacity={ 'DesiredInstances': 1 }, VpcConfig={ 'SubnetIds':[ strSubnet1Id, strSubnet2Id ], 'SecurityGroupIds':[ strSecurityGroups ] }, InstanceType=strInstType) #Create AppStream stack stack = appstream.create_stack( Name=strStackName, StorageConnectors=[ { 'ConnectorType': 'HOMEFOLDERS' }, ], UserSettings=[ { 'Action': 'CLIPBOARD_COPY_FROM_LOCAL_DEVICE', 'Permission': 'ENABLED' }, ], ApplicationSettings={ 'Enabled': False } ) #Associate Stack to Fleet associate = appstream.associate_fleet( FleetName=strFleetName, StackName=strStackName ) startfleet = appstream.start_fleet( Name=strFleetName ) return 2 except Exception as e: print(e) print("Error creating fleet") return -1
Lambda function 6: Stop the AppStream 2.0 fleet and image builder
When you create this function, name it examplecorp_eao_stopresources.
from __future__ import print_function import json import boto3 print('Loading function') appstream = boto3.client('appstream') def Status_to_Return(received_state): switcher = { "PENDING": 0, "RUNNING": 1, "SNAPSHOTTING": 2, "STOPPED": 3, "STOPPING": 4 } return switcher.get(received_state, -1) def lambda_handler(event, context): # Log the received event print("Received event: " + json.dumps(event, indent=2)) # Get names to query against strIBName = event['ib_name'] strFleetName = event['fleet_name'] strType = event['type'] try: #Fleet status check if strType == 2: print ("Type is Fleet") AS2state = appstream.describe_fleets(Names=[strFleetName]) Status = AS2state['Fleets'][0]['State'] State = Status_to_Return(Status) if State < 3: print (strFleetName + " is " + Status + " attempting to stop") AS2stop = appstream.stop_fleet(Name=strFleetName) print ("State: " + Status) #Image Builder status check elif strType == 1: print ("Type is ImageBuilder") AS2state = appstream.describe_image_builders(Names=[strIBName]) Status = response['ImageBuilders'][0]['State'] State = Status_to_Return(Status) if State < 3: print (strIBName + " is " + Status + " attempting to stop") AS2stop = appstream.stop_image_builder(Names=[strIBName]) print ("State: " + Status) return Status_to_Return(Status) except Exception as e: print(e) print("Error stopping services") return -1