-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2170 from hashicorp/f-aws-lambda
AWS Lambda functionality
- Loading branch information
Showing
7 changed files
with
408 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
125
builtin/providers/aws/resource_aws_lambda_function_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
Oops, something went wrong.