-
Notifications
You must be signed in to change notification settings - Fork 9.2k
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
Add lambda container image support #16512
Changes from 15 commits
a82489b
c5c8955
d278949
2d08991
298a59c
5f9f905
13e93ad
28edd73
1eaa4c4
869fbba
383064c
270c960
4f23b1b
58a86d0
1b9c740
44bd184
6b9f1f7
eba4514
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"context" | ||
"errors" | ||
"fmt" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" | ||
"io/ioutil" | ||
"log" | ||
"regexp" | ||
|
@@ -47,22 +48,57 @@ func resourceAwsLambdaFunction() *schema.Resource { | |
"filename": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"s3_bucket", "s3_key", "s3_object_version"}, | ||
ConflictsWith: []string{"s3_bucket", "s3_key", "s3_object_version", "image_uri"}, | ||
}, | ||
"s3_bucket": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"filename"}, | ||
ConflictsWith: []string{"filename", "image_uri"}, | ||
}, | ||
"s3_key": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"filename"}, | ||
ConflictsWith: []string{"filename", "image_uri"}, | ||
}, | ||
"s3_object_version": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"filename"}, | ||
ConflictsWith: []string{"filename", "image_uri"}, | ||
}, | ||
"image_uri": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"filename", "s3_bucket", "s3_key", "s3_object_version"}, | ||
}, | ||
"package_type": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't this just be computed based on s3 / code or image? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, you can now end up in a situation where a user will choose a ZIP and an image which isn't correct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I know there's a validator but why even need it) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if AWS adds support for another archive type later on? im assuming the API was designed to be open for expansion in the future, we should follow the API. https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/ and such... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. We typically don't automatically set parameters like this. We should, however, add validation along the lines of:
|
||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
Default: lambda.PackageTypeZip, | ||
ValidateFunc: validation.StringInSlice(lambda.PackageType_Values(), false), | ||
}, | ||
"image_config": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesnt conflict with zip type arguments? |
||
Type: schema.TypeList, | ||
Optional: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"entry_point": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
}, | ||
"command": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
}, | ||
"working_directory": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"code_signing_config_arn": { | ||
Type: schema.TypeString, | ||
|
@@ -126,7 +162,7 @@ func resourceAwsLambdaFunction() *schema.Resource { | |
}, | ||
"handler": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Optional: true, | ||
ValidateFunc: validation.StringLenBetween(1, 128), | ||
}, | ||
"layers": { | ||
|
@@ -155,7 +191,7 @@ func resourceAwsLambdaFunction() *schema.Resource { | |
}, | ||
"runtime": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Optional: true, | ||
ValidateFunc: validation.StringInSlice(lambda.Runtime_Values(), false), | ||
}, | ||
"timeout": { | ||
|
@@ -280,10 +316,24 @@ func resourceAwsLambdaFunction() *schema.Resource { | |
"tags": tagsSchema(), | ||
}, | ||
|
||
CustomizeDiff: updateComputedAttributesOnPublish, | ||
CustomizeDiff: customdiff.Sequence( | ||
checkHandlerRuntimeForZipFunction, | ||
updateComputedAttributesOnPublish, | ||
), | ||
} | ||
} | ||
|
||
func checkHandlerRuntimeForZipFunction(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { | ||
packageType := d.Get("package_type") | ||
_, handlerOk := d.GetOk("handler") | ||
_, runtimeOk := d.GetOk("runtime") | ||
|
||
if packageType == lambda.PackageTypeZip && !handlerOk && !runtimeOk { | ||
return fmt.Errorf("handler and runtime must be set when PackageType is Zip") | ||
} | ||
return nil | ||
} | ||
|
||
func updateComputedAttributesOnPublish(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { | ||
configChanged := hasConfigChanges(d) | ||
functionCodeUpdated := needsFunctionCodeUpdate(d) | ||
|
@@ -304,6 +354,7 @@ func hasConfigChanges(d resourceDiffer) bool { | |
return d.HasChange("description") || | ||
d.HasChange("handler") || | ||
d.HasChange("file_system_config") || | ||
d.HasChange("image_config") || | ||
d.HasChange("memory_size") || | ||
d.HasChange("role") || | ||
d.HasChange("timeout") || | ||
|
@@ -331,9 +382,10 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e | |
s3Bucket, bucketOk := d.GetOk("s3_bucket") | ||
s3Key, keyOk := d.GetOk("s3_key") | ||
s3ObjectVersion, versionOk := d.GetOk("s3_object_version") | ||
imageUri, hasImageUri := d.GetOk("image_uri") | ||
|
||
if !hasFilename && !bucketOk && !keyOk && !versionOk { | ||
return errors.New("filename or s3_* attributes must be set") | ||
if !hasFilename && !bucketOk && !keyOk && !versionOk && !hasImageUri { | ||
return errors.New("filename, s3_* or image_uri attributes must be set") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change is causing a failure in the test |
||
} | ||
|
||
var functionCode *lambda.FunctionCode | ||
|
@@ -350,6 +402,10 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e | |
functionCode = &lambda.FunctionCode{ | ||
ZipFile: file, | ||
} | ||
} else if hasImageUri { | ||
functionCode = &lambda.FunctionCode{ | ||
ImageUri: aws.String(imageUri.(string)), | ||
} | ||
} else { | ||
if !bucketOk || !keyOk { | ||
return errors.New("s3_bucket and s3_key must all be set while using S3 code source") | ||
|
@@ -363,16 +419,28 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e | |
} | ||
} | ||
|
||
packageType := d.Get("package_type") | ||
handler, handlerOk := d.GetOk("handler") | ||
runtime, runtimeOk := d.GetOk("runtime") | ||
|
||
if packageType == lambda.PackageTypeZip && !handlerOk && !runtimeOk { | ||
return errors.New("handler and runtime must be set when PackageType is Zip") | ||
} | ||
|
||
params := &lambda.CreateFunctionInput{ | ||
Code: functionCode, | ||
Description: aws.String(d.Get("description").(string)), | ||
FunctionName: aws.String(functionName), | ||
Handler: aws.String(d.Get("handler").(string)), | ||
MemorySize: aws.Int64(int64(d.Get("memory_size").(int))), | ||
Role: aws.String(iamRole), | ||
Runtime: aws.String(d.Get("runtime").(string)), | ||
Timeout: aws.Int64(int64(d.Get("timeout").(int))), | ||
Publish: aws.Bool(d.Get("publish").(bool)), | ||
PackageType: aws.String(d.Get("package_type").(string)), | ||
} | ||
|
||
if packageType == lambda.PackageTypeZip { | ||
params.Handler = aws.String(handler.(string)) | ||
params.Runtime = aws.String(runtime.(string)) | ||
} | ||
|
||
if v, ok := d.GetOk("code_signing_config_arn"); ok { | ||
|
@@ -401,6 +469,10 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e | |
params.FileSystemConfigs = expandLambdaFileSystemConfigs(v.([]interface{})) | ||
} | ||
|
||
if v, ok := d.GetOk("image_config"); ok && len(v.([]interface{})) > 0 { | ||
params.ImageConfig = expandLambdaImageConfigs(v.([]interface{})) | ||
} | ||
|
||
if v, ok := d.GetOk("vpc_config"); ok && len(v.([]interface{})) > 0 { | ||
config := v.([]interface{})[0].(map[string]interface{}) | ||
|
||
|
@@ -641,6 +713,23 @@ func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) err | |
return fmt.Errorf("Error setting file system config for Lambda Function (%s): %w", d.Id(), err) | ||
} | ||
|
||
// Add Package Type | ||
log.Printf("[INFO] Setting Lambda %s package type %#v from API", d.Id(), function.PackageType) | ||
if err := d.Set("package_type", function.PackageType); err != nil { | ||
return fmt.Errorf("Error setting package type for Lambda Function: %w", err) | ||
} | ||
|
||
// Add Image Configuration | ||
imageConfig := flattenLambdaImageConfig(function.ImageConfigResponse) | ||
log.Printf("[INFO] Setting Lambda %s Image config %#v from API", d.Id(), imageConfig) | ||
if err := d.Set("image_config", imageConfig); err != nil { | ||
return fmt.Errorf("Error setting image config for Lambda Function: %s", err) | ||
} | ||
|
||
if err := d.Set("image_uri", getFunctionOutput.Code.ImageUri); err != nil { | ||
return fmt.Errorf("Error setting image uri for Lambda Function: %s", err) | ||
} | ||
|
||
layers := flattenLambdaLayers(function.Layers) | ||
log.Printf("[INFO] Setting Lambda %s Layers %#v from API", d.Id(), layers) | ||
if err := d.Set("layers", layers); err != nil { | ||
|
@@ -723,15 +812,20 @@ func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) err | |
FunctionName: aws.String(d.Get("function_name").(string)), | ||
} | ||
|
||
getCodeSigningConfigOutput, err := conn.GetFunctionCodeSigningConfig(codeSigningConfigInput) | ||
if err != nil { | ||
return fmt.Errorf("error getting Lambda Function (%s) code signing config %w", d.Id(), err) | ||
} | ||
// Code Signing is only supported on zip packaged lambda functions. | ||
if *function.PackageType == lambda.PackageTypeZip { | ||
getCodeSigningConfigOutput, err := conn.GetFunctionCodeSigningConfig(codeSigningConfigInput) | ||
if err != nil { | ||
return fmt.Errorf("error getting Lambda Function (%s) code signing config %w", d.Id(), err) | ||
} | ||
|
||
if getCodeSigningConfigOutput == nil || getCodeSigningConfigOutput.CodeSigningConfigArn == nil { | ||
d.Set("code_signing_config_arn", "") | ||
if getCodeSigningConfigOutput == nil || getCodeSigningConfigOutput.CodeSigningConfigArn == nil { | ||
d.Set("code_signing_config_arn", "") | ||
} else { | ||
d.Set("code_signing_config_arn", getCodeSigningConfigOutput.CodeSigningConfigArn) | ||
} | ||
} else { | ||
d.Set("code_signing_config_arn", getCodeSigningConfigOutput.CodeSigningConfigArn) | ||
d.Set("code_signing_config_arn", "") | ||
} | ||
|
||
return nil | ||
|
@@ -779,7 +873,9 @@ func needsFunctionCodeUpdate(d resourceDiffer) bool { | |
d.HasChange("source_code_hash") || | ||
d.HasChange("s3_bucket") || | ||
d.HasChange("s3_key") || | ||
d.HasChange("s3_object_version") | ||
d.HasChange("s3_object_version") || | ||
d.HasChange("image_uri") | ||
|
||
} | ||
|
||
// resourceAwsLambdaFunctionUpdate maps to: | ||
|
@@ -840,6 +936,12 @@ func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) e | |
configReq.FileSystemConfigs = expandLambdaFileSystemConfigs(v.([]interface{})) | ||
} | ||
} | ||
if d.HasChange("image_config") { | ||
configReq.ImageConfig = &lambda.ImageConfig{} | ||
if v, ok := d.GetOk("image_config"); ok && len(v.([]interface{})) > 0 { | ||
configReq.ImageConfig = expandLambdaImageConfigs(v.([]interface{})) | ||
} | ||
} | ||
if d.HasChange("memory_size") { | ||
configReq.MemorySize = aws.Int64(int64(d.Get("memory_size").(int))) | ||
} | ||
|
@@ -990,6 +1092,8 @@ func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) e | |
return fmt.Errorf("Unable to load %q: %w", v.(string), err) | ||
} | ||
codeReq.ZipFile = file | ||
} else if v, ok := d.GetOk("image_uri"); ok { | ||
codeReq.ImageUri = aws.String(v.(string)) | ||
} else { | ||
s3Bucket, _ := d.GetOk("s3_bucket") | ||
s3Key, _ := d.GetOk("s3_key") | ||
|
@@ -1188,3 +1292,33 @@ func expandLambdaFileSystemConfigs(fscMaps []interface{}) []*lambda.FileSystemCo | |
} | ||
return fileSystemConfigs | ||
} | ||
|
||
func flattenLambdaImageConfig(response *lambda.ImageConfigResponse) []map[string]interface{} { | ||
settings := make(map[string]interface{}) | ||
|
||
if response == nil || response.Error != nil { | ||
return nil | ||
} | ||
|
||
settings["command"] = response.ImageConfig.Command | ||
settings["entry_point"] = response.ImageConfig.EntryPoint | ||
settings["working_directory"] = response.ImageConfig.WorkingDirectory | ||
|
||
return []map[string]interface{}{settings} | ||
} | ||
|
||
func expandLambdaImageConfigs(imageConfigMaps []interface{}) *lambda.ImageConfig { | ||
imageConfig := &lambda.ImageConfig{} | ||
// only one image_config block is allowed | ||
if len(imageConfigMaps) == 1 && imageConfigMaps[0] != nil { | ||
config := imageConfigMaps[0].(map[string]interface{}) | ||
if len(config["entry_point"].([]interface{})) > 0 { | ||
imageConfig.EntryPoint = expandStringList(config["entry_point"].([]interface{})) | ||
} | ||
if len(config["command"].([]interface{})) > 0 { | ||
imageConfig.Command = expandStringList(config["command"].([]interface{})) | ||
} | ||
imageConfig.WorkingDirectory = aws.String(config["working_directory"].(string)) | ||
} | ||
return imageConfig | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a good chance to substitute with
ExactlyOneOf: []string{"filename", "s3_bucket", "image_uri"}
this will save the custom check below.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. We should probably also set
RequiredWith: []string{"s3_key"}
ons3_bucket