Skip to content

Commit

Permalink
Merge pull request #2170 from hashicorp/f-aws-lambda
Browse files Browse the repository at this point in the history
AWS Lambda functionality
  • Loading branch information
cbednarski committed Jun 12, 2015
2 parents dd24b58 + 6591603 commit 380f3ce
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 0 deletions.
5 changes: 5 additions & 0 deletions builtin/providers/aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/kinesis"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/service/s3"
Expand Down Expand Up @@ -48,6 +49,7 @@ type AWSClient struct {
iamconn *iam.IAM
kinesisconn *kinesis.Kinesis
elasticacheconn *elasticache.ElastiCache
lambdaconn *lambda.Lambda
}

// Client configures and returns a fully initailized AWSClient
Expand Down Expand Up @@ -133,6 +135,9 @@ func (c *Config) Client() (interface{}, error) {

log.Println("[INFO] Initializing Elasticache Connection")
client.elasticacheconn = elasticache.New(awsConfig)

log.Println("[INFO] Initializing Lambda Connection")
client.lambdaconn = lambda.New(awsConfig)
}

if len(errs) > 0 {
Expand Down
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func Provider() terraform.ResourceProvider {
"aws_internet_gateway": resourceAwsInternetGateway(),
"aws_key_pair": resourceAwsKeyPair(),
"aws_kinesis_stream": resourceAwsKinesisStream(),
"aws_lambda_function": resourceAwsLambdaFunction(),
"aws_launch_configuration": resourceAwsLaunchConfiguration(),
"aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(),
"aws_main_route_table_association": resourceAwsMainRouteTableAssociation(),
Expand Down
207 changes: 207 additions & 0 deletions builtin/providers/aws/resource_aws_lambda_function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package aws

import (
"crypto/sha256"
"fmt"
"io/ioutil"
"log"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/mitchellh/go-homedir"

"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)

filename, err := homedir.Expand(d.Get("filename").(string))
if err != nil {
return err
}
zipfile, err := ioutil.ReadFile(filename)
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)
}

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
}
125 changes: 125 additions & 0 deletions builtin/providers/aws/resource_aws_lambda_function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/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
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 added builtin/providers/aws/test-fixtures/lambdatest.zip
Binary file not shown.
Loading

0 comments on commit 380f3ce

Please sign in to comment.