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

provider/aws: Add support for updating Lambda function #5239

Merged
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
113 changes: 98 additions & 15 deletions builtin/providers/aws/resource_aws_lambda_function.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package aws

import (
"crypto/sha256"
"fmt"
"io/ioutil"
"log"
Expand Down Expand Up @@ -58,18 +57,15 @@ func resourceAwsLambdaFunction() *schema.Resource {
"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,
Expand All @@ -81,7 +77,6 @@ func resourceAwsLambdaFunction() *schema.Resource {
Type: schema.TypeInt,
Optional: true,
Default: 3,
ForceNew: true, // TODO make this editable
},
"vpc_config": &schema.Schema{
Type: schema.TypeList,
Expand Down Expand Up @@ -116,8 +111,8 @@ func resourceAwsLambdaFunction() *schema.Resource {
},
"source_code_hash": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
}
Expand All @@ -135,17 +130,12 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e

var functionCode *lambda.FunctionCode
if v, ok := d.GetOk("filename"); ok {
filename, err := homedir.Expand(v.(string))
if err != nil {
return err
}
zipfile, err := ioutil.ReadFile(filename)
file, err := loadFileContent(v.(string))
if err != nil {
return err
return fmt.Errorf("Unable to load %q: %s", v.(string), err)
}
d.Set("source_code_hash", sha256.Sum256(zipfile))
functionCode = &lambda.FunctionCode{
ZipFile: zipfile,
ZipFile: file,
}
} else {
s3Bucket, bucketOk := d.GetOk("s3_bucket")
Expand Down Expand Up @@ -202,6 +192,7 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err := conn.CreateFunction(params)
if err != nil {
log.Printf("[ERROR] Received %q, retrying CreateFunction", err)
if awserr, ok := err.(awserr.Error); ok {
if awserr.Code() == "InvalidParameterValueException" {
log.Printf("[DEBUG] InvalidParameterValueException creating Lambda Function: %s", awserr)
Expand Down Expand Up @@ -256,6 +247,7 @@ func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) err
if config := flattenLambdaVpcConfigResponse(function.VpcConfig); len(config) > 0 {
d.Set("vpc_config", config)
}
d.Set("source_code_hash", function.CodeSha256)

return nil
}
Expand Down Expand Up @@ -284,7 +276,98 @@ func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) e
// resourceAwsLambdaFunctionUpdate maps to:
// UpdateFunctionCode in the API / SDK
func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error {
return nil
conn := meta.(*AWSClient).lambdaconn

d.Partial(true)

codeReq := &lambda.UpdateFunctionCodeInput{
FunctionName: aws.String(d.Id()),
}

codeUpdate := false
if v, ok := d.GetOk("filename"); ok && d.HasChange("source_code_hash") {
file, err := loadFileContent(v.(string))
if err != nil {
return fmt.Errorf("Unable to load %q: %s", v.(string), err)
}
codeReq.ZipFile = file
codeUpdate = true
}
if d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") {
codeReq.S3Bucket = aws.String(d.Get("s3_bucket").(string))
codeReq.S3Key = aws.String(d.Get("s3_key").(string))
codeReq.S3ObjectVersion = aws.String(d.Get("s3_object_version").(string))
codeUpdate = true
}

log.Printf("[DEBUG] Send Update Lambda Function Code request: %#v", codeReq)
if codeUpdate {
_, err := conn.UpdateFunctionCode(codeReq)
if err != nil {
return fmt.Errorf("Error modifying Lambda Function Code %s: %s", d.Id(), err)
}

d.SetPartial("filename")
d.SetPartial("source_code_hash")
d.SetPartial("s3_bucket")
d.SetPartial("s3_key")
d.SetPartial("s3_object_version")
}

configReq := &lambda.UpdateFunctionConfigurationInput{
FunctionName: aws.String(d.Id()),
}

configUpdate := false
if d.HasChange("description") {
configReq.Description = aws.String(d.Get("description").(string))
configUpdate = true
}
if d.HasChange("handler") {
configReq.Handler = aws.String(d.Get("handler").(string))
configUpdate = true
}
if d.HasChange("memory_size") {
configReq.MemorySize = aws.Int64(int64(d.Get("memory_size").(int)))
configUpdate = true
}
if d.HasChange("role") {
configReq.Role = aws.String(d.Get("role").(string))
configUpdate = true
}
if d.HasChange("timeout") {
configReq.Timeout = aws.Int64(int64(d.Get("timeout").(int)))
configUpdate = true
}

log.Printf("[DEBUG] Send Update Lambda Function Configuration request: %#v", configReq)
if configUpdate {
_, err := conn.UpdateFunctionConfiguration(configReq)
if err != nil {
return fmt.Errorf("Error modifying Lambda Function Configuration %s: %s", d.Id(), err)
}
d.SetPartial("description")
d.SetPartial("handler")
d.SetPartial("memory_size")
d.SetPartial("role")
d.SetPartial("timeout")
}
d.Partial(false)

return resourceAwsLambdaFunctionRead(d, meta)
}

// loadFileContent returns contents of a file in a given path
func loadFileContent(v string) ([]byte, error) {
filename, err := homedir.Expand(v)
if err != nil {
return nil, err
}
fileContent, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return fileContent, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this was already here, but noting that this is an exception to our normal policy of "prefer file contents to paths" - I assume it's made complicated by Lambda function files being zips and potentially large.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that consistency (i.e. preference of content = "${file()} over path) would be nice, but I share the same concerns you mentioned. It will always be a ZIP file (= in terms of readability practically garbage unless we are base64-encoding/decoding it) and potentially big which may not be any good for tfstate files.

}

func validateVPCConfig(v interface{}) (map[string]interface{}, error) {
Expand Down
Loading