Book Image

Serverless Programming Cookbook

By : Heartin Kanikathottu
Book Image

Serverless Programming Cookbook

By: Heartin Kanikathottu

Overview of this book

Managing physical servers will be a thing of the past once you’re able to harness the power of serverless computing. If you’re already prepped with the basics of serverless computing, Serverless Programming Cookbook will help you take the next step ahead. This recipe-based guide provides solutions to problems you might face while building serverless applications. You'll begin by setting up Amazon Web Services (AWS), the primary cloud provider used for most recipes. The next set of recipes will cover various components to build a Serverless application including REST APIs, database, user management, authentication, web hosting, domain registration, DNS management, CDN, messaging, notifications and monitoring. The book also introduces you to the latest technology trends such as Data Streams, Machine Learning and NLP. You will also see patterns and practices for using various services in a real world application. Finally, to broaden your understanding of Serverless computing, you'll also cover getting started guides for other cloud providers such as Azure, Google Cloud Platform and IBM cloud. By the end of this book, you’ll have acquired the skills you need to build serverless applications efficiently using various cloud offerings.
Table of Contents (12 chapters)

Using AWS SDK, Amazon CloudFormation, and AWS CLI with Lambda

AWS SDK allows you to write code that interacts with AWS services. In this recipe, we will use AWS Java SDK for IAM to do some basic IAM operations to form a Lambda programmatically. We will use it along with Amazon CloudWatch and AWS CLI, which is a general practice followed in most real-world projects.

The aim of this recipe is to understand the use of AWS Java SDK inside Lambda. Therefore, we will not go deep into the details of the IAM operations discussed in the recipe. The IAM operations details are available at https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/examples-iam-users.html.

Getting ready

You need an active AWS account, and read and follow the Getting started section of the recipes, Your first AWS Lambda and Your first Lambda with AWS CLI to set up Java, Maven, the parent project, serverless-cookbook-parent-aws-java, and AWS CLI, and other code usage guidelines.

How to do it...

We will create a Java Maven project and set the parent as serverless-cookbook-parent-aws-java.

  1. Create a Java Maven project and set dependencies:
<parent>
<groupId>tech.heartin.books.serverlesscookbook</groupId>
<artifactId>serverless-cookbook-parent-aws-java</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
  1. Specify dependencies in the POM file:
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>${aws.lambda.java.core.version}</version>
</dependency>

<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-iam</artifactId>
<version>${aws.sdk.version}</version>
</dependency>

</dependencies>
Do not directly define the whole AWS Java SDK (aws-java-sdk) dependency for a Lambda handler. Instead, only declare the dependencies you need (such as aws-java-sdk-iam). I tried adding aws-java-sdk to our Lambda and generated the Uber JAR. It was around 93 MB. AWS console did not allow me to upload the file manually into the Lambda function as the limit was 50MB. So, I uploaded it to S3. However, it failed again while extracting the JAR as the size of the extracted contents exceeded the allowed size of 262144000 bytes.

Creating the POJOs for requests and response.

  1. Create a request POJO for accepting requests:
import lombok.Data;

@Data
public class IAMOperationRequest {
private String operation;
private String userName;
}
  1. Create a POJO for sending back the response from the handler:
import lombok.AllArgsConstructor;
import lombok.Data;

@AllArgsConstructor
@Data
public class IAMOperationResponse {
private String message;
private String errorMessage;
}
For our POJOs, we use project lombok (@Data) to auto-generate getters, setters, and so on. Project lombok dependency is added to the parent project simple-starter-parent-java. If you are using an IDE for development, you will have to install a plugin for your IDE to recognize project lombok annotations.

Creating a service class to implement the IAM Operations using AWS SDK:

  1. Import the required classes:
import com.amazonaws.services.identitymanagement.AmazonIdentityManagement;
import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClientBuilder;
import com.amazonaws.services.identitymanagement.model.CreateUserRequest;
import com.amazonaws.services.identitymanagement.model.CreateUserResult;
import com.amazonaws.services.identitymanagement.model.DeleteConflictException;
import com.amazonaws.services.identitymanagement.model.DeleteUserRequest;
import com.amazonaws.services.identitymanagement.model.ListUsersRequest;
import com.amazonaws.services.identitymanagement.model.ListUsersResult;
import com.amazonaws.services.identitymanagement.model.User;
  1. Create and initialize a client object of AmazonIdentityManagement type:
private final AmazonIdentityManagement iamClient;

public IAMService() {
iamClient = AmazonIdentityManagementClientBuilder.defaultClient();
}
  1. Write code for creating a user in a method:
CreateUserRequest request = new CreateUserRequest().withUserName(userName);
CreateUserResult response = iamClient.createUser(request);
// get user details from response.
  1. Write code for checking if a user is present in another method:
 boolean done = false;
ListUsersRequest request = new ListUsersRequest();
while (!done) {
ListUsersResult response = iamClient.listUsers(request);

for (User user : response.getUsers()) {
if (user.getUserName().equals(userName)) {
//return success message
}
}
request.setMarker(response.getMarker());
if (!response.getIsTruncated()) {
done = true;
}
}
// return error message
  1. Write code for deleting a user in another method:
DeleteUserRequest request = new DeleteUserRequest()
.withUserName(userName);
try {
iamClient.deleteUser(request);
} catch (DeleteConflictException e) {
// Handle exception
}

Let us now see how to create a handler.

  1. Create a handler class with input and output POJOs:
public final class HelloWorldLambdaHandler implements RequestHandler<IAMOperationRequest, IAMOperationResponse> {
  1. Implement the handleRequest method with a switch statement to invoke an appropriate service method:
public IAMOperationResponse handleRequest(final IAMOperationRequest request, final Context context) {
context.getLogger().log("Requested operation = " + request.getOperation()
+ ". User name = " + request.getUserName());

switch (request.getOperation()) {
case "CREATE" :
return this.service.createUser(request.getUserName());
case "CHECK" :
return this.service.checkUser(request.getUserName());
case "DELETE" :
return this.service.deleteUser(request.getUserName());

default:
return new IAMOperationResponse(null,
"Invalid operation " + request.getOperation()
+ ". Allowed: CREATE, CHECK, DELETE.");
}
}
  1. Package the dependencies into an uber JAR using mvn clean package.

Two JARs will be created: one with only class files (starting with original-) and an Uber JAR with all dependencies (starting with serverless-). We will use the Uber JAR in this recipe.

  1. Upload the JAR to S3:
aws s3 cp target/serverless-cookbook-iam-operations-0.0.1-SNAPSHOT.jar s3://serverless-cookbook/iam-operations-0.0.1-SNAPSHOT.jar --profile admin
  1. Create a CloudFormation template for our lambda function.

You need to create a role with a trust policy that allows our Lambda to assume the role. You also need to create a policy with CloudFormation and IAM permissions.

We need to add permissions for IAM operations in our policies:

- Effect: Allow
Action:
- iam:CreateUser
- iam:DeleteUser
- iam:ListUsers
Resource:
- Fn::Sub: arn:aws:iam::${AWS::AccountId}:user/*

We have used a pseudo-parameter, AWS::AccountId, within a sub-intrinsic function to dynamically populate the account ID. I also improved the CloudWatch logging permission policy from the previous recipe using the pseudo-parameters:

- Effect: Allow
Action:
- logs:CreateLogStream
Resource:
- Fn::Sub: arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/aws-sdk-iam-with-cf-cli:*
- Effect: Allow
Action:
- logs:PutLogEvents
Resource:
- Fn::Sub: arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/aws-sdk-iam-with-cf-cli:*:*

You should be able to complete this recipe by referring to the previous recipe, Your First Lambda using CloudFormation.

The completed template file is available in the resources folder as cf-template-iam-operations.yml.
  1. Upload the CloudFormation template to S3:
aws s3 cp ../resources/cf-template-iam-operations.yml s3://serverless-cookbook/cf-template-iam-operations.yml --profile admin
  1. Create a CloudFormation stack using the CloudFormation template from AWS CLI:
aws cloudformation create-stack --stack-name myteststack --template-url https://s3.amazonaws.com/serverless-cookbook/cf-template-iam-operations.yml --capabilities CAPABILITY_NAMED_IAM --profile admin

This immediately responds with StackId. Note that you used a parameter, --capabilities CAPABILITY_NAMED_IAM. This is a security-related precaution. You are explicitly telling CloudFormation that you know what you are doing.

You can check the status of stack creation using the describe-stacks command:

aws cloudformation describe-stacks --stack-name <StackId> --profile admin

StackStatus: CREATE_COMPLETE means stack creation was successful.

  1. Verify the deployment with AWS CLI Lambda invoke:
aws lambda invoke --invocation-type RequestResponse --function-name aws-sdk-iam-with-cf-cli --log-type Tail --payload '{"operation":"CREATE", "userName":"abcd"}' --profile admin outputfile.txt

You can replace CREATE in the payload with CHECK for checking if the user was created, and DELETE for deleting the user.

  1. Delete the CloudFormation stack:
aws cloudformation delete-stack --stack-name <StackId> --profile admin

How it works...

AWS SDKs are used to interact with AWS services programmatically. There are SDKs available for programming languages such as Java, .Net, Node.js. PHP, Python, Ruby, Browser, Go, and C++.

We uploaded our CloudFormation template to S3 and provided the location using --template-url. You can also specify the template contents directly or from a file using file:// with another option --template-body.

We created our roles for Lambda manually. If we are using Management console, we can create custom Lambda roles from within our Lambda create function page, or directly from IAM.

We used one new intrinsic function in our CloudFormation template, Fn::Sub. Fn::Sub, which substitutes variables in an input string with values that you specify. We used it to substitute the AWS Account ID and a few other values rather than hard-coding them.

We also used the following pseudo-parameters: AWS::AccountId, AWS::Partition, and AWS::Region, which represents the current account ID, partition, and region respectively. For most regions, the partition is aws. For resources in other partitions, the partition is named as aws-partitionn (for instance, aws-cn for China and aws-us-gov for the AWS GovCloud (US) region). Using pseudo-parameters lets us avoid worrying about the actual partition name.

There's more...

We used only basic IAM operations in this recipe. You can check the documentation and implement more complex operations from within Lambda code if interested.

We will use CloudFormation and AWS CLI for most of our recipes. However, you may follow these steps to try to do the same in the management console. Doing things visually will help you remember the concepts for a longer time.

Pseudo-parameters

Pseudo-parameters are predefined parameters provided by AWS CLoudFormation. You can use them within a Ref or a Sub function to dynamically populate values. Pseudo-parameters available to use within a CloudFormation template include AWS::AccountId, AWS::NotificationARNs, AWS::NoValue, AWS::Partition, AWS::Region, AWS::StackId, AWS::StackName, and AWS::URLSuffix.

Read more about pseudo-parameters at https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html.

See also