Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS Lambda functionality #2170

Merged
merged 4 commits into from
Jun 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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