Taking AWS SDK Go 2.0 For A Spin

by
Tags: , , ,

The Amazon Web Services SDK for Go 2.0 contains a number of enhancements compared to Go v1.
Golang is a great programming language for modern cloud native applications. The language provides great facilities for better concurrency, internet content parsing (JSON, XML, etc…), support for HTTP(S), ease of cross-platform compilation, and static binary executable generation, just to mention a few.

In this blog I’ll go over some of the changes provided by the AWS SDK for Go 2.0 by walking through a sample application.

The sample application has a trivial scenario that gives you a quick view of the SDK Go 2.0 and some of the differences with respect to the SDK Go 1.0.
The sample application demonstrates how to:

  • Connect to AWS using an EC2 client.
  • Find the latest Ubuntu AMI to be used later in the application.
  • Create an SSH Key Pair and download the private key to a file.
  • Run an EC2 instance using the Ubuntu AMI found in the above step.
  • The instance is configured with a User Data script that installs Python 2.x and Docker.
  • Wait for the instance to become ready.
  • Query for the public IP address of the EC2 instance.

When it comes to building cloud infrastructure, I enjoy writing code using Go as much as using DevOps tools like Terraform and Ansible. I find that using the SDK directly provides certain benefits:

  • Ability to write custom solutions, like searching for the latest AMI from a particular vendor or custom AMI.
  • Access to AWS services from within the AWS infrastructure, like EC2 instances or from Lambda functions using the Go programming model.
  • Access to the AWS services from your external applications or CI/CD builds.
  • Better alternative than using the AWS CLI.

Programming infrastructure using the AWS SDK directly gives you more flexibility, like creating custom build flows for your CI/CD build pipeline, or creating lambdas to to stop running EC2 instances without permitted criterias. You could even create custom reports at the end of your build with an estimate of the infrastructure consumption.

The application starts by creating an EC2 client using the new LoadDefaultAWSConfig from the external package:

import (
  ....
   "github.com/aws/aws-sdk-go-v2/aws"
   "github.com/aws/aws-sdk-go-v2/aws/endpoints"
   "github.com/aws/aws-sdk-go-v2/aws/external"
   "github.com/aws/aws-sdk-go-v2/service/ec2"
)
...
func getEC2Client(region string) (*ec2.EC2, error) {
   cfg, err := external.LoadDefaultAWSConfig()
   ...
   cfg.Region = endpoints.UsEast1RegionID
}

The LoadDefaultAWSConfig loads configuration values from environment variables, shared credentials file (~/.aws/credentials)
, and a shared config file (~/.aws/config).
It also allows for specifying configuration profiles through the use of WithSharedConfigProfile.
You can specify regions using a constant value as seen in the snippet above.

After you have a config object, you can simply use it to instantiate clients like:

   ec2Svc := ec2.New(cfg)

Once you have a client object, you can send requests to the AWS services. This is done with a new way of sending requests to the API.
First we need create request input similar to the 1.0 SDK, but without the need to convert slice and map values to pointers. Nice improvement!

  filter := ec2.Filter{
     Name: aws.String("name"),
     Values: []string{ubuntuImageSearch},
  }
  filters := []ec2.Filter{filter}
  dii := ec2.DescribeImagesInput{
    Filters: filters,
  }

We now pass a pointer the input parameter to a ‘Request’ method. What’s a ‘Request’ method? Good question – Calls to the API are performed through the use of a method which suffixed with ‘Request’ at the end. For example the 1.0 SDK provided a ec2svc.DescribeImages method, now for the 2.0 SDK, the method is called ec2svc.DescribeImagesRequest.
The ec2svc.DescribeImagesRequest method creates a request object that can be used to pass optional parameters, like a ‘Context’ to control timeouts, etc.

...
dir := client.DescribeImagesRequest(&dii)
...

Having the ability to modify the request before its sent allows you to add the cancelable Context. For example:

...
ctx := context.Background()
ctx, cancelFn := context.WithTimeout(ctx, time.Minute*5)
defer cancelFn()
dir.SetContext(ctx)
...

With the request object we can call the AWS API like:

...
amis, err := dir.Send()
if err != nil {
...
}

The result value and error are returned by the Send method. The error is your typical Go error and the result value is pointer to the ‘Output’ object. The output object is similar
the one provided by the 1.0 SDK – ex: *DescribeImagesOutput

Take a look at the complete sample application and you’ll find that the patterns explained above are consistent across other parameter inputs, function calls, and return values.

Enjoy!

Resources: