AWS Lambda functionality

- Includes documentation
- Includes acceptance tests
This commit is contained in:
Chris Bednarski 2015-06-01 09:33:22 -07:00
parent 265b9b254e
commit 6986121305
7 changed files with 406 additions and 0 deletions

View File

@ -15,6 +15,7 @@ import (
"github.com/awslabs/aws-sdk-go/service/elb" "github.com/awslabs/aws-sdk-go/service/elb"
"github.com/awslabs/aws-sdk-go/service/iam" "github.com/awslabs/aws-sdk-go/service/iam"
"github.com/awslabs/aws-sdk-go/service/kinesis" "github.com/awslabs/aws-sdk-go/service/kinesis"
"github.com/awslabs/aws-sdk-go/service/lambda"
"github.com/awslabs/aws-sdk-go/service/rds" "github.com/awslabs/aws-sdk-go/service/rds"
"github.com/awslabs/aws-sdk-go/service/route53" "github.com/awslabs/aws-sdk-go/service/route53"
"github.com/awslabs/aws-sdk-go/service/s3" "github.com/awslabs/aws-sdk-go/service/s3"
@ -46,6 +47,7 @@ type AWSClient struct {
iamconn *iam.IAM iamconn *iam.IAM
kinesisconn *kinesis.Kinesis kinesisconn *kinesis.Kinesis
elasticacheconn *elasticache.ElastiCache elasticacheconn *elasticache.ElastiCache
lambdaconn *lambda.Lambda
} }
// Client configures and returns a fully initailized AWSClient // Client configures and returns a fully initailized AWSClient
@ -128,6 +130,9 @@ func (c *Config) Client() (interface{}, error) {
log.Println("[INFO] Initializing Elasticache Connection") log.Println("[INFO] Initializing Elasticache Connection")
client.elasticacheconn = elasticache.New(awsConfig) client.elasticacheconn = elasticache.New(awsConfig)
log.Println("[INFO] Initializing Lambda Connection")
client.lambdaconn = lambda.New(awsConfig)
} }
if len(errs) > 0 { if len(errs) > 0 {

View File

@ -110,6 +110,7 @@ func Provider() terraform.ResourceProvider {
"aws_internet_gateway": resourceAwsInternetGateway(), "aws_internet_gateway": resourceAwsInternetGateway(),
"aws_key_pair": resourceAwsKeyPair(), "aws_key_pair": resourceAwsKeyPair(),
"aws_kinesis_stream": resourceAwsKinesisStream(), "aws_kinesis_stream": resourceAwsKinesisStream(),
"aws_lambda_function": resourceAwsLambdaFunction(),
"aws_launch_configuration": resourceAwsLaunchConfiguration(), "aws_launch_configuration": resourceAwsLaunchConfiguration(),
"aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(), "aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(),
"aws_main_route_table_association": resourceAwsMainRouteTableAssociation(), "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(),

View File

@ -0,0 +1,203 @@
package aws
import (
"crypto/sha256"
"fmt"
"io/ioutil"
"log"
"strings"
"time"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/aws/awserr"
"github.com/awslabs/aws-sdk-go/service/lambda"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsLambdaFunction() *schema.Resource {
return &schema.Resource{
Create: resourceAwsLambdaFunctionCreate,
Read: resourceAwsLambdaFunctionRead,
Update: resourceAwsLambdaFunctionUpdate,
Delete: resourceAwsLambdaFunctionDelete,
Schema: map[string]*schema.Schema{
"filename": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true, // TODO make this editable
},
"function_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"handler": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true, // TODO make this editable
},
"memory_size": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 128,
ForceNew: true, // TODO make this editable
},
"role": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true, // TODO make this editable
},
"runtime": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "nodejs",
},
"timeout": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 3,
ForceNew: true, // TODO make this editable
},
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"last_modified": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"source_code_hash": &schema.Schema{
Type: schema.TypeString,
Computed: true,
ForceNew: true,
},
},
}
}
// resourceAwsLambdaFunction maps to:
// CreateFunction in the API / SDK
func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lambdaconn
functionName := d.Get("function_name").(string)
iamRole := d.Get("role").(string)
log.Printf("[DEBUG] Creating Lambda Function %s with role %s", functionName, iamRole)
zipfile, err := ioutil.ReadFile(d.Get("filename").(string))
if err != nil {
return err
}
d.Set("source_code_hash", sha256.Sum256(zipfile))
log.Printf("[DEBUG] ")
params := &lambda.CreateFunctionInput{
Code: &lambda.FunctionCode{
ZipFile: zipfile,
},
Description: aws.String(d.Get("description").(string)),
FunctionName: aws.String(functionName),
Handler: aws.String(d.Get("handler").(string)),
MemorySize: aws.Long(int64(d.Get("memory_size").(int))),
Role: aws.String(iamRole),
Runtime: aws.String(d.Get("runtime").(string)),
Timeout: aws.Long(int64(d.Get("timeout").(int))),
}
for i := 0; i < 5; i++ {
_, err = conn.CreateFunction(params)
if awsErr, ok := err.(awserr.Error); ok {
// IAM profiles can take ~10 seconds to propagate in AWS:
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
// Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda.
if awsErr.Code() == "InvalidParameterValueException" && strings.Contains(awsErr.Message(), "The role defined for the task cannot be assumed by Lambda.") {
log.Printf("[DEBUG] Invalid IAM Instance Profile referenced, retrying...")
time.Sleep(2 * time.Second)
continue
}
}
break
}
if err != nil {
return fmt.Errorf("Error creating Lambda function: %s", err)
}
// Add retry wrapper around this
d.SetId(d.Get("function_name").(string))
return resourceAwsLambdaFunctionRead(d, meta)
}
// resourceAwsLambdaFunctionRead maps to:
// GetFunction in the API / SDK
func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lambdaconn
log.Printf("[DEBUG] Fetching Lambda Function: %s", d.Id())
params := &lambda.GetFunctionInput{
FunctionName: aws.String(d.Get("function_name").(string)),
}
getFunctionOutput, err := conn.GetFunction(params)
if err != nil {
return err
}
// getFunctionOutput.Code.Location is a pre-signed URL pointing at the zip
// file that we uploaded when we created the resource. You can use it to
// download the code from AWS. The other part is
// getFunctionOutput.Configuration which holds metadata.
function := getFunctionOutput.Configuration
// TODO error checking / handling on the Set() calls.
d.Set("arn", function.FunctionARN)
d.Set("description", function.Description)
d.Set("handler", function.Handler)
d.Set("memory_size", function.MemorySize)
d.Set("last_modified", function.LastModified)
d.Set("role", function.Role)
d.Set("runtime", function.Runtime)
d.Set("timeout", function.Timeout)
return nil
}
// resourceAwsLambdaFunction maps to:
// DeleteFunction in the API / SDK
func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).lambdaconn
log.Printf("[INFO] Deleting Lambda Function: %s", d.Id())
params := &lambda.DeleteFunctionInput{
FunctionName: aws.String(d.Get("function_name").(string)),
}
_, err := conn.DeleteFunction(params)
if err != nil {
return fmt.Errorf("Error deleting Lambda Function: %s", err)
}
d.SetId("")
return nil
}
// resourceAwsLambdaFunctionUpdate maps to:
// UpdateFunctionCode in the API / SDK
func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error {
// conn := meta.(*AWSClient).lambdaconn
return nil
}

View File

@ -0,0 +1,127 @@
package aws
import (
"fmt"
"testing"
"time"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/lambda"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSLambdaFunction_normal(t *testing.T) {
var conf lambda.GetFunctionOutput
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLambdaFunctionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSLambdaConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsLambdaFunctionExists("aws_lambda_function.lambda_function_test", &conf),
testAccCheckAWSLambdaAttributes(&conf),
),
},
},
})
}
func testAccCheckLambdaFunctionDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).lambdaconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_lambda_function" {
continue
}
_, err := conn.GetFunction(&lambda.GetFunctionInput{
FunctionName: aws.String(rs.Primary.ID),
})
if err == nil {
return fmt.Errorf("Lambda Function still exists")
}
}
return nil
}
func testAccCheckAwsLambdaFunctionExists(n string, function *lambda.GetFunctionOutput) resource.TestCheckFunc {
// Wait for IAM role
time.Sleep(100 * time.Millisecond)
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Lambda function not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("Lambda function ID not set")
}
conn := testAccProvider.Meta().(*AWSClient).lambdaconn
params := &lambda.GetFunctionInput{
FunctionName: aws.String("example_lambda_name"),
}
getFunction, err := conn.GetFunction(params)
if err != nil {
return err
}
*function = *getFunction
return nil
}
}
func testAccCheckAWSLambdaAttributes(function *lambda.GetFunctionOutput) resource.TestCheckFunc {
return func(s *terraform.State) error {
c := function.Configuration
const expectedName = "example_lambda_name"
if *c.FunctionName != expectedName {
return fmt.Errorf("Expected function name %s, got %s", expectedName, *c.FunctionName)
}
if *c.FunctionARN == "" {
return fmt.Errorf("Could not read Lambda Function's ARN")
}
return nil
}
}
const testAccAWSLambdaConfig = `
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_lambda_function" "lambda_function_test" {
filename = "test-fixtures/lambdatest.zip"
function_name = "example_lambda_name"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "exports.example"
}
`

Binary file not shown.

View File

@ -0,0 +1,66 @@
---
layout: "aws"
page_title: "AWS: aws_lambda_function"
sidebar_current: "docs-aws-resource-aws-lambda-function"
description: |-
Provides a Lambda Function resource. Lambda allows you to trigger execution of code in response to events in AWS. The Lambda Function itself includes source code and runtime configuration.
---
# aws\_lambda\_function
Provides a Lambda Function resource. Lambda allows you to trigger execution of code in response to events in AWS. The Lambda Function itself includes source code and runtime configuration.
For information about Lambda and how to use it, see [What is AWS Lambda?][1]
## Example Usage
```
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_lambda_function" "test_lambda" {
filename = "lambda_function_payload.zip"
function_name = "lambda_function_name"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "exports.test"
}
```
## Argument Reference
* `filename` - (Required) A [zip file][2] containing your lambda function source code.
* `function_name` - (Required) A unique name for your Lambda Function.
* `handler` - (Required) The function [entrypoint][3] in your code.
* `role` - (Required) IAM role attached to the Lambda Function. This governs both who / what can invoke your Lambda Function, as well as what resources our Lambda Function has access to. See [Lambda Permission Model][4] for more details.
* `description` - (Optional) Description of what your Lambda Function does.
* `memory_size` - (Optional) Amount of memory in MB your Lambda Function can use at runtime. Defaults to `128`. See [Limits][5]
* `runtime` - (Optional) Defaults to `nodejs`.
* `timeout` - (Optional) The amount of time your Lambda Function has to run in seconds. Defaults to `3`. See [Limits][5]
## Attributes Reference
* `arn` - The Amazon Resource Name (ARN) identifying your Lambda Function.
* `last_modified` - The date this resource was last modified.
[1]: http://docs.aws.amazon.com/lambda/latest/dg/welcome.html
[2]: http://docs.aws.amazon.com/lambda/latest/dg/walkthrough-s3-events-adminuser-create-test-function-create-function.html
[3]: http://docs.aws.amazon.com/lambda/latest/dg/walkthrough-custom-events-create-test-function.html
[4]: http://docs.aws.amazon.com/lambda/latest/dg/intro-permission-model.html
[5]: http://docs.aws.amazon.com/lambda/latest/dg/limits.html

View File

@ -216,6 +216,10 @@
<li<%= sidebar_current("docs-aws-resource-vpn-gateway") %>> <li<%= sidebar_current("docs-aws-resource-vpn-gateway") %>>
<a href="/docs/providers/aws/r/vpn_gateway.html">aws_vpn_gateway</a> <a href="/docs/providers/aws/r/vpn_gateway.html">aws_vpn_gateway</a>
</li> </li>
<li<%= sidebar_current("docs-aws-resource-lambda-function") %>>
<a href="/docs/providers/aws/r/lambda_function.html">aws_lambda_function</a>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>